summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2010-09-22 16:19:28 -0500
committerDan Williams <dcbw@redhat.com>2010-09-22 16:19:28 -0500
commita211fadce06e7a78e693b85192b50f9e09df8972 (patch)
tree3421272e1605f701516ea0346faf76c1be4bbad0
parenta1731c60644432cdec94841a033340bfc88568ec (diff)
parent06bd99f61779417453a049934ab136c8be9df955 (diff)
Merge remote branch 'origin/cachingdns'
Caching DNS with dnsmasq works well enough to merge for now. THere are still some issues with the BIND plugin because BIND is god-awful unecessarily complex so we'll disable that in a further commit.
-rw-r--r--man/NetworkManager.conf.5.in30
-rw-r--r--src/dns-manager/Makefile.am13
-rw-r--r--src/dns-manager/nm-dns-bind.c527
-rw-r--r--src/dns-manager/nm-dns-bind.h47
-rw-r--r--src/dns-manager/nm-dns-dnsmasq.c370
-rw-r--r--src/dns-manager/nm-dns-dnsmasq.h47
-rw-r--r--src/dns-manager/nm-dns-manager.c334
-rw-r--r--src/dns-manager/nm-dns-manager.h2
-rw-r--r--src/dns-manager/nm-dns-plugin.c318
-rw-r--r--src/dns-manager/nm-dns-plugin.h112
-rw-r--r--src/main.c12
-rw-r--r--src/nm-device.c4
-rw-r--r--src/nm-policy.c6
-rw-r--r--src/vpn-manager/nm-vpn-connection.c4
14 files changed, 1746 insertions, 80 deletions
diff --git a/man/NetworkManager.conf.5.in b/man/NetworkManager.conf.5.in
index 44d6da1be0..4e2bae9eb6 100644
--- a/man/NetworkManager.conf.5.in
+++ b/man/NetworkManager.conf.5.in
@@ -41,18 +41,19 @@ plugins=keyfile
.P
Description of sections and available keys follows:
.SS [main]
This section is the only mandatory section of the configuration file.
.TP
.B plugins=\fIplugin1\fP,\fIplugin2\fP, ...
-List plugin names separated by ','. Plugins are used to read/write system-wide
-connection. When more plugins are specified, the connections are read from all
-listed plugins. When writing connections, the plugins will be asked to save the
-connection in the order listed here. If the first plugin cannot write out that
-connection type, or can't write out any connections, the next plugin is tried.
-If none of the plugins can save the connection, the error is returned to the user.
+List system settings plugin names separated by ','. These plugins are used to
+read/write system-wide connection. When more plugins are specified, the
+connections are read from all listed plugins. When writing connections, the
+plugins will be asked to save the connection in the order listed here. If the
+first plugin cannot write out that connection type, or can't write out any
+connections, the next plugin is tried. If none of the plugins can save the
+connection, the error is returned to the user.
.P
.RS
.B "Available plugins:"
.br
.TP
.I keyfile
@@ -101,12 +102,29 @@ Devices are specified by their MAC addresses, in lowercase. Multiple
entries are separated by commas.
.br
Example:
.nf
no-auto-default=00:22:68:5c:5d:c4,00:1e:65:ff:aa:ee
.fi
+.TP
+.B dns=\fIplugin1\fP,\fIplugin2\fP, ...
+List DNS plugin names separated by ','. DNS plugins are used to provide local
+caching nameserver functionality (which speeds up DNS queries) and to push
+DNS data to applications that use it.
+.P
+.RS
+.B "Available plugins:"
+.br
+.TP
+.I dnsmasq
+this plugin uses dnsmasq to provide local caching nameserver functionality.
+.TP
+.I bind
+this plugin uses the ISC BIND program to provide local caching nameserver
+functionality.
+.RE
.SS [keyfile]
This section contains keyfile-specific options and thus only has effect when using \fIkeyfile\fP plugin.
.TP
.B hostname=\fI<hostname>\fP
Set a persistent hostname when using the \fIkeyfile\fP plugin.
.TP
diff --git a/src/dns-manager/Makefile.am b/src/dns-manager/Makefile.am
index d1ea5f87e9..1ffe62dcf0 100644
--- a/src/dns-manager/Makefile.am
+++ b/src/dns-manager/Makefile.am
@@ -3,19 +3,26 @@ INCLUDES = \
-I${top_srcdir}/libnm-util \
-I${top_srcdir}/src \
-I${top_srcdir}/include
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 \
+ nm-dns-bind.h \
+ nm-dns-bind.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 \
$(DBUS_LIBS) \
$(GLIB_LIBS)
diff --git a/src/dns-manager/nm-dns-bind.c b/src/dns-manager/nm-dns-bind.c
new file mode 100644
index 0000000000..2e1ec67dec
--- /dev/null
+++ b/src/dns-manager/nm-dns-bind.c
@@ -0,0 +1,527 @@
+/* -*- 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-bind.h"
+#include "nm-logging.h"
+#include "nm-ip4-config.h"
+#include "nm-ip6-config.h"
+
+G_DEFINE_TYPE (NMDnsBind, nm_dns_bind, NM_TYPE_DNS_PLUGIN)
+
+#define NM_DNS_BIND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DNS_BIND, NMDnsBindPrivate))
+
+#define PIDFILE LOCALSTATEDIR "/run/nm-dns-named.pid"
+#define CONFFILE LOCALSTATEDIR "/run/nm-dns-named.conf"
+
+typedef struct {
+ GPid pid;
+} NMDnsBindPrivate;
+
+/*******************************************/
+
+static inline const char *
+find_bind (void)
+{
+ static const char *paths[] = {
+ "/usr/local/sbin/named",
+ "/usr/sbin/named",
+ "/sbin/named",
+ NULL
+ };
+ const char **binary = paths;
+
+ while (*binary != NULL) {
+ if (g_file_test (*binary, G_FILE_TEST_EXISTS))
+ return *binary;
+ binary++;
+ }
+ return NULL;
+}
+
+static gboolean
+start_bind (NMDnsBind *self)
+{
+ const char *argv[10];
+
+ argv[0] = find_bind ();
+ argv[1] = "-f"; /* don't daemonize; stay in foreground */
+ argv[2] = "-c";
+ argv[3] = CONFFILE;
+ argv[4] = NULL;
+
+ /* And finally spawn bind */
+ return nm_dns_plugin_child_spawn (NM_DNS_PLUGIN (self), argv, PIDFILE, "bin/named");
+}
+
+/*******************************************/
+
+static gboolean
+find_address (GPtrArray *array, const char *addr)
+{
+ int n;
+
+ for (n = 0; n < array->len; n++) {
+ if (g_strcmp0 ((const char*) g_ptr_array_index (array, n), addr) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+add_ip4_nameservers (NMIP4Config *ip4, GPtrArray *array)
+{
+ int i;
+
+ for (i = 0; i < nm_ip4_config_get_num_nameservers (ip4); i++) {
+ char buf[INET_ADDRSTRLEN + 1];
+ struct in_addr addr;
+
+ memset (&buf[0], 0, sizeof (buf));
+ addr.s_addr = nm_ip4_config_get_nameserver (ip4, i);
+ if (inet_ntop (AF_INET, &addr, buf, sizeof (buf))) {
+ if (!find_address (array, buf))
+ g_ptr_array_add (array, g_strdup (buf));
+ }
+ }
+}
+
+static gboolean
+ip6_addr_to_string (const struct in6_addr *addr, char *buf, size_t buflen)
+{
+ /* inet_ntop is probably supposed to do this for us, but it doesn't */
+ if (IN6_IS_ADDR_V4MAPPED (addr))
+ return !!inet_ntop (AF_INET, &(addr->s6_addr32[3]), buf, buflen);
+
+ return !!inet_ntop (AF_INET6, addr, buf, buflen);
+}
+
+static void
+add_ip6_nameservers (NMIP6Config *ip6, GPtrArray *array)
+{
+ char buf[INET6_ADDRSTRLEN + 1];
+ int i;
+
+ for (i = 0; i < nm_ip6_config_get_num_nameservers (ip6); i++) {
+ memset (buf, 0, sizeof (buf));
+ if (ip6_addr_to_string (nm_ip6_config_get_nameserver (ip6, i), buf, sizeof (buf))) {
+ if (!find_address (array, buf))
+ g_ptr_array_add (array, g_strdup (buf));
+ }
+ }
+}
+
+typedef struct {
+ guint32 dhash;
+ char *domain;
+ GPtrArray *servers;
+} ZoneInfo;
+
+static ZoneInfo *
+zone_new (const char *domain)
+{
+ ZoneInfo *info;
+
+ g_return_val_if_fail (domain != NULL, NULL);
+
+ info = g_malloc0 (sizeof (ZoneInfo));
+ info->domain = g_strdup (domain);
+ info->dhash = g_str_hash (domain);
+ info->servers = g_ptr_array_sized_new (4);
+ return info;
+}
+
+static void
+zone_add_nameserver (ZoneInfo *info, const char *server)
+{
+ guint32 i;
+
+ g_return_if_fail (info != NULL);
+ g_return_if_fail (server != NULL);
+
+ for (i = 0; i < info->servers->len; i++) {
+ if (g_strcmp0 ((char *) g_ptr_array_index (info->servers, i), server) == 0)
+ return;
+ }
+ g_ptr_array_add (info->servers, g_strdup (server));
+}
+
+static void
+zone_free (ZoneInfo *info)
+{
+ g_return_if_fail (info != NULL);
+
+ g_free (info->domain);
+ g_ptr_array_foreach (info->servers, (GFunc) g_free, NULL);
+ g_ptr_array_free (info->servers, TRUE);
+ memset (info, 0, sizeof (ZoneInfo));
+ g_free (info);
+}
+
+static ZoneInfo *
+find_zone (GPtrArray *zones, const char *domain)
+{
+ guint32 dhash, i;
+
+ g_return_val_if_fail (domain != NULL, FALSE);
+
+ dhash = g_str_hash (domain);
+ for (i = 0; i < zones->len; i++) {
+ ZoneInfo *zone = g_ptr_array_index (zones, i);
+
+ if (zone->dhash == dhash)
+ return zone;
+ }
+ return NULL;
+}
+
+static void
+add_zone (GObject *ip, GPtrArray *zones)
+{
+ guint32 i, j, ns, nd, nn;
+ GPtrArray *to_add;
+ ZoneInfo *z;
+
+ if (NM_IS_IP4_CONFIG (ip)) {
+ ns = nm_ip4_config_get_num_searches (NM_IP4_CONFIG (ip));
+ nd = nm_ip4_config_get_num_domains (NM_IP4_CONFIG (ip));
+ nn = nm_ip4_config_get_num_nameservers (NM_IP4_CONFIG (ip));
+ } else if (NM_IS_IP6_CONFIG (ip)) {
+ ns = nm_ip6_config_get_num_searches (NM_IP6_CONFIG (ip));
+ nd = nm_ip6_config_get_num_domains (NM_IP6_CONFIG (ip));
+ nn = nm_ip6_config_get_num_nameservers (NM_IP6_CONFIG (ip));
+ } else
+ g_assert_not_reached ();
+
+ /* If we don't have any domains or searches, or we don't have any
+ * nameservers, we can't do split DNS for this config.
+ */
+ if ((!nd && !ns) || !nn)
+ return;
+
+ to_add = g_ptr_array_sized_new (MAX (ns, nd));
+
+ /* searches are preferred over domains */
+ for (i = 0; i < ns; i++) {
+ const char *domain = NULL;
+
+ if (NM_IS_IP4_CONFIG (ip))
+ domain = nm_ip4_config_get_search (NM_IP4_CONFIG (ip), i);
+ else if (NM_IS_IP6_CONFIG (ip))
+ domain = nm_ip6_config_get_search (NM_IP6_CONFIG (ip), i);
+
+ z = find_zone (zones, domain);
+ if (!z) {
+ z = zone_new (domain);
+ g_ptr_array_add (zones, z);
+ }
+ g_ptr_array_add (to_add, z);
+ }
+
+ if (ns == 0) {
+ /* If no searches, add any domains */
+ for (i = 0; i < nd; i++) {
+ const char *domain = NULL;
+
+ if (NM_IS_IP4_CONFIG (ip))
+ domain = nm_ip4_config_get_domain (NM_IP4_CONFIG (ip), i);
+ else if (NM_IS_IP6_CONFIG (ip))
+ domain = nm_ip6_config_get_domain (NM_IP6_CONFIG (ip), i);
+
+ z = find_zone (zones, domain);
+ if (!z) {
+ z = zone_new (domain);
+ g_ptr_array_add (zones, z);
+ }
+ g_ptr_array_add (to_add, z);
+ }
+ }
+
+ /* Now add the nameservers to every zone for this config */
+ for (i = 0; i < nn; i++) {
+ char buf[INET6_ADDRSTRLEN + 1];
+ struct in_addr addr4;
+ const struct in6_addr *addr6;
+
+ memset (&buf[0], 0, sizeof (buf));
+
+ if (NM_IS_IP4_CONFIG (ip)) {
+ addr4.s_addr = nm_ip4_config_get_nameserver (NM_IP4_CONFIG (ip), i);
+ if (!inet_ntop (AF_INET, &addr4, buf, sizeof (buf)))
+ continue;
+ } else if (NM_IS_IP6_CONFIG (ip)) {
+ addr6 = nm_ip6_config_get_nameserver (NM_IP6_CONFIG (ip), i);
+ if (!ip6_addr_to_string (addr6, buf, sizeof (buf)))
+ continue;
+ }
+
+ /* Add this nameserver to every zone from this IP config */
+ for (j = 0; j < to_add->len; j++) {
+ z = g_ptr_array_index (to_add, j);
+ zone_add_nameserver (z, buf);
+ }
+ }
+
+ g_ptr_array_free (to_add, TRUE);
+}
+
+static gboolean
+update (NMDnsPlugin *plugin,
+ const GSList *vpn_configs,
+ const GSList *dev_configs,
+ const GSList *other_configs,
+ const char *hostname)
+{
+ NMDnsBind *self = NM_DNS_BIND (plugin);
+ NMDnsBindPrivate *priv = NM_DNS_BIND_GET_PRIVATE (self);
+ GString *conf;
+ GPtrArray *globals, *zones;
+ GSList *iter;
+ GError *error = NULL;
+ int ignored, i, j;
+ gboolean success = FALSE;
+
+ /* Build up the new bind config file */
+ conf = g_string_sized_new (200);
+ globals = g_ptr_array_sized_new (6);
+
+ /* If any of the VPN configs *don't* have domains or searches, then we
+ * dont' have any split DNS configuration for them, and we add them
+ * first in the global nameserver lists. Otherwise we add them later as
+ * split DNS zones.
+ */
+ for (iter = (GSList *) vpn_configs; iter;iter = g_slist_next (iter)) {
+ if (NM_IS_IP4_CONFIG (iter->data)) {
+ NMIP4Config *ip4 = NM_IP4_CONFIG (iter->data);
+
+ if (!nm_ip4_config_get_num_domains (ip4) && !nm_ip4_config_get_num_searches (ip4))
+ add_ip4_nameservers (ip4, globals);
+ } else if (NM_IS_IP6_CONFIG (iter->data)) {
+ NMIP6Config *ip6 = NM_IP6_CONFIG (iter->data);
+
+ if (!nm_ip6_config_get_num_domains (ip6) && !nm_ip6_config_get_num_searches (ip6))
+ add_ip6_nameservers (ip6, globals);
+ }
+ }
+
+ /* Get a list of global upstream servers with dupe checking */
+ for (iter = (GSList *) dev_configs; iter;iter = g_slist_next (iter)) {
+ if (NM_IS_IP4_CONFIG (iter->data))
+ add_ip4_nameservers (NM_IP4_CONFIG (iter->data), globals);
+ else if (NM_IS_IP6_CONFIG (iter->data))
+ add_ip6_nameservers (NM_IP6_CONFIG (iter->data), globals);
+ }
+
+ /* And any other random configs with dupe checking */
+ for (iter = (GSList *) other_configs; iter;iter = g_slist_next (iter)) {
+ if (NM_IS_IP4_CONFIG (iter->data))
+ add_ip4_nameservers (NM_IP4_CONFIG (iter->data), globals);
+ else if (NM_IS_IP6_CONFIG (iter->data))
+ add_ip6_nameservers (NM_IP6_CONFIG (iter->data), globals);
+ }
+
+ g_string_append (conf,
+ "options {\n"
+ " directory \"" LOCALSTATEDIR "/named\";\n"
+ " forward only;\n"
+ " recursion yes;\n"
+ " listen-on-v6 { ::1; };\n"
+ " listen-on { 127.0.0.1; };\n"
+ " forwarders {\n");
+
+ for (i = 0; i < globals->len; i++) {
+ char *ns = g_ptr_array_index (globals, i);
+
+ g_string_append_printf (conf, " %s;\n", ns);
+ g_free (ns);
+ }
+ g_ptr_array_free (globals, TRUE);
+
+ g_string_append (conf,
+ " };\n"
+ "};\n\n");
+
+ /* Build up the list of any split DNS zones, avoiding duplicates */
+ zones = g_ptr_array_sized_new (4);
+ for (iter = (GSList *) vpn_configs; iter;iter = g_slist_next (iter)) {
+ if (NM_IS_IP4_CONFIG (iter->data))
+ add_zone (G_OBJECT (iter->data), zones);
+ else if (NM_IS_IP6_CONFIG (iter->data))
+ add_zone (G_OBJECT (iter->data), zones);
+ }
+
+ /* Add all the zones to the config */
+ for (i = 0; i < zones->len; i++) {
+ ZoneInfo *z = g_ptr_array_index (zones, i);
+
+ g_string_append_printf (conf,
+ "zone \"%s\" IN {\n"
+ " type forward;\n"
+ " forward only;\n"
+ " forwarders {\n",
+ z->domain);
+
+ /* Add each nameserver for this zone */
+ for (j = 0; j < z->servers->len; j++) {
+ g_string_append_printf (conf,
+ " %s;\n",
+ (const char *) g_ptr_array_index (z->servers, j));
+ }
+
+ g_string_append (conf,
+ " };\n"
+ "};\n\n");
+
+ zone_free (z);
+ }
+ g_ptr_array_free (zones, TRUE);
+
+ /* Write out the config file */
+ if (!g_file_set_contents (CONFFILE, conf->str, -1, &error)) {
+ nm_log_warn (LOGD_DNS, "Failed to write named 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, "BIND local caching DNS configuration:");
+ nm_log_dbg (LOGD_DNS, "%s", conf->str);
+
+ if (priv->pid) {
+ /* Send it SIGHUP to reload the new configuration */
+ if (kill (priv->pid, SIGHUP) == 0)
+ success = TRUE;
+ else {
+ /* Sigh... some error. Kill it and restart */
+ nm_dns_plugin_child_kill (NM_DNS_PLUGIN (self));
+ priv->pid = 0;
+ }
+ }
+
+ if (!success) {
+ /* Spawn it */
+ priv->pid = start_bind (self);
+ if (priv->pid)
+ success = TRUE;
+ }
+
+out:
+ g_string_free (conf, TRUE);
+ return success;
+}
+
+/****************************************************************/
+
+static void
+child_quit (NMDnsPlugin *plugin, gint status)
+{
+ NMDnsBind *self = NM_DNS_BIND (plugin);
+ gboolean failed = TRUE;
+ int err;
+
+ if (WIFEXITED (status)) {
+ err = WEXITSTATUS (status);
+ if (err) {
+ nm_log_warn (LOGD_DNS, "named exited with error %d", err);
+ } else
+ failed = FALSE;
+ } else if (WIFSTOPPED (status)) {
+ nm_log_warn (LOGD_DNS, "named stopped unexpectedly with signal %d", WSTOPSIG (status));
+ } else if (WIFSIGNALED (status)) {
+ nm_log_warn (LOGD_DNS, "named died with signal %d", WTERMSIG (status));
+ } else {
+ nm_log_warn (LOGD_DNS, "named died from an unknown cause");
+ }
+ unlink (CONFFILE);
+
+ if (failed)
+ g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
+}
+
+/****************************************************************/
+
+static gboolean
+init (NMDnsPlugin *plugin)
+{
+ return TRUE;
+}
+
+static gboolean
+is_caching (NMDnsPlugin *plugin)
+{
+ return TRUE;
+}
+
+static const char *
+get_name (NMDnsPlugin *plugin)
+{
+ return "bind";
+}
+
+/****************************************************************/
+
+NMDnsBind *
+nm_dns_bind_new (void)
+{
+ return (NMDnsBind *) g_object_new (NM_TYPE_DNS_BIND, NULL);
+}
+
+static void
+nm_dns_bind_init (NMDnsBind *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+ unlink (CONFFILE);
+
+ G_OBJECT_CLASS (nm_dns_bind_parent_class)->dispose (object);
+}
+
+static void
+nm_dns_bind_class_init (NMDnsBindClass *dns_class)
+{
+ NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS (dns_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (dns_class);
+
+ g_type_class_add_private (dns_class, sizeof (NMDnsBindPrivate));
+
+ object_class->dispose = dispose;
+
+ plugin_class->init = init;
+ plugin_class->child_quit = child_quit;
+ plugin_class->is_caching = is_caching;
+ plugin_class->update = update;
+ plugin_class->get_name = get_name;
+}
+
diff --git a/src/dns-manager/nm-dns-bind.h b/src/dns-manager/nm-dns-bind.h
new file mode 100644
index 0000000000..7127265f18
--- /dev/null
+++ b/src/dns-manager/nm-dns-bind.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_BIND_H
+#define NM_DNS_BIND_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "nm-dns-plugin.h"
+
+#define NM_TYPE_DNS_BIND (nm_dns_bind_get_type ())
+#define NM_DNS_BIND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNS_BIND, NMDnsBind))
+#define NM_DNS_BIND_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNS_BIND, NMDnsBindClass))
+#define NM_IS_DNS_BIND(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DNS_BIND))
+#define NM_IS_DNS_BIND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_DNS_BIND))
+#define NM_DNS_BIND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DNS_BIND, NMDnsBindClass))
+
+typedef struct {
+ NMDnsPlugin parent;
+} NMDnsBind;
+
+typedef struct {
+ NMDnsPluginClass parent;
+} NMDnsBindClass;
+
+GType nm_dns_bind_get_type (void);
+
+NMDnsBind *nm_dns_bind_new (void);
+
+#endif /* NM_DNS_BIND_H */
+
diff --git a/src/dns-manager/nm-dns-dnsmasq.c b/src/dns-manager/nm-dns-dnsmasq.c
new file mode 100644
index 0000000000..254e2ffbb6
--- /dev/null
+++ b/src/dns-manager/nm-dns-dnsmasq.c
@@ -0,0 +1,370 @@
+/* -*- 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 {
+ guint32 foo;
+} NMDnsDnsmasqPrivate;
+
+/*******************************************/
+
+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;
+ gboolean added = FALSE;
+
+ if (split) {
+ /* 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\n",
+ nm_ip4_config_get_search (ip4, i),
+ buf);
+ added = TRUE;
+ }
+
+ 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\n",
+ nm_ip4_config_get_domain (ip4, i),
+ buf);
+ added = TRUE;
+ }
+ }
+ }
+
+ /* If no searches or domains, just add the namservers */
+ if (!added) {
+ n = nm_ip4_config_get_num_nameservers (ip4);
+ for (i = 0; i < n; i++) {
+ memset (&buf[0], 0, sizeof (buf));
+ addr.s_addr = nm_ip4_config_get_nameserver (ip4, i);
+ if (inet_ntop (AF_INET, &addr, buf, sizeof (buf)))
+ g_string_append_printf (str, "server=%s\n", buf);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+ip6_addr_to_string (const struct in6_addr *addr, char *buf, size_t buflen)
+{
+ memset (buf, 0, buflen);
+
+ /* inet_ntop is probably supposed to do this for us, but it doesn't */
+ if (IN6_IS_ADDR_V4MAPPED (addr))
+ return !!inet_ntop (AF_INET, &(addr->s6_addr32[3]), buf, buflen);
+
+ return !!inet_ntop (AF_INET6, addr, buf, buflen);
+}
+
+static gboolean
+add_ip6_config (GString *str, NMIP6Config *ip6, gboolean split)
+{
+ char buf[INET6_ADDRSTRLEN + 1];
+ const struct in6_addr *addr;
+ int n, i;
+ gboolean added = FALSE;
+
+ if (split) {
+ /* 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);
+ if (!ip6_addr_to_string (addr, &buf[0], 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\n",
+ nm_ip6_config_get_search (ip6, i),
+ buf);
+ added = TRUE;
+ }
+
+ 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\n",
+ nm_ip6_config_get_domain (ip6, i),
+ buf);
+ added = TRUE;
+ }
+ }
+ }
+
+ /* If no searches or domains, just add the namservers */
+ if (!added) {
+ n = nm_ip6_config_get_num_nameservers (ip6);
+ for (i = 0; i < n; i++) {
+ addr = nm_ip6_config_get_nameserver (ip6, i);
+ if (ip6_addr_to_string (addr, &buf[0], sizeof (buf)))
+ g_string_append_printf (str, "server=%s\n", 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);
+ GString *conf;
+ GSList *iter;
+ const char *argv[10];
+ GError *error = NULL;
+ int ignored;
+ GPid pid = 0;
+
+ /* Kill the old dnsmasq; there doesn't appear to be a way to get dnsmasq
+ * to reread the config file using SIGHUP or similar. This is a small race
+ * here when restarting dnsmasq when DNS requests could go to the upstream
+ * servers instead of to dnsmasq.
+ */
+ nm_dns_plugin_child_kill (plugin);
+
+ /* 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] = "--strict-order";
+ argv[4] = "--bind-interfaces";
+ argv[5] = "--pid-file=" PIDFILE;
+ argv[6] = "--listen-address=127.0.0.1"; /* Should work for both 4 and 6 */
+ argv[7] = "--conf-file=" CONFFILE;
+ argv[8] = NULL;
+
+ /* And finally spawn dnsmasq */
+ pid = nm_dns_plugin_child_spawn (NM_DNS_PLUGIN (self), argv, PIDFILE, "bin/dnsmasq");
+
+out:
+ g_string_free (conf, TRUE);
+ return 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);
+ 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");
+ }
+ unlink (CONFFILE);
+
+ if (failed)
+ g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
+}
+
+/****************************************************************/
+
+static gboolean
+init (NMDnsPlugin *plugin)
+{
+ return TRUE;
+}
+
+static gboolean
+is_caching (NMDnsPlugin *plugin)
+{
+ return TRUE;
+}
+
+static const char *
+get_name (NMDnsPlugin *plugin)
+{
+ return "dnsmasq";
+}
+
+/****************************************************************/
+
+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
+dispose (GObject *object)
+{
+ unlink (CONFFILE);
+
+ G_OBJECT_CLASS (nm_dns_dnsmasq_parent_class)->dispose (object);
+}
+
+static void
+nm_dns_dnsmasq_class_init (NMDnsDnsmasqClass *dns_class)
+{
+ NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS (dns_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (dns_class);
+
+ g_type_class_add_private (dns_class, sizeof (NMDnsDnsmasqPrivate));
+
+ object_class->dispose = dispose;
+
+ plugin_class->init = init;
+ plugin_class->child_quit = child_quit;
+ plugin_class->is_caching = is_caching;
+ plugin_class->update = update;
+ plugin_class->get_name = get_name;
+}
+
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 6b4b67543c..77ad9d73eb 100644
--- a/src/dns-manager/nm-dns-manager.c
+++ b/src/dns-manager/nm-dns-manager.c
@@ -40,70 +40,56 @@
#include "nm-ip4-config.h"
#include "nm-ip6-config.h"
#include "nm-logging.h"
#include "nm-system.h"
#include "NetworkManagerUtils.h"
+#include "nm-dns-plugin.h"
+#include "nm-dns-dnsmasq.h"
+#include "nm-dns-bind.h"
+
#ifdef HAVE_SELINUX
#include <selinux/selinux.h>
#endif
#ifndef RESOLV_CONF
#define RESOLV_CONF "/etc/resolv.conf"
#endif
-#define ADDR_BUF_LEN 50
-
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, \
NMDnsManagerPrivate))
-
struct NMDnsManagerPrivate {
NMIP4Config *ip4_vpn_config;
NMIP4Config *ip4_device_config;
NMIP6Config *ip6_vpn_config;
NMIP6Config *ip6_device_config;
GSList *configs;
char *hostname;
+ /* poor man's hash; we assume that the IP4 config object won't change
+ * after it's given to us, which is (at this time) a fair assumption. So
+ * we track the order of the currently applied IP configs and if they
+ * haven't changed we don't need to rewrite resolv.conf.
+ */
+ #define HLEN 6
+ gpointer hash[HLEN];
+
+ GSList *plugins;
+
/* This is a hack because SUSE's netconfig always wants changes
* associated with a network interface, but sometimes a change isn't
* associated with a network interface (like hostnames).
*/
char *last_iface;
};
-NMDnsManager *
-nm_dns_manager_get (void)
-{
- static NMDnsManager * singleton = NULL;
-
- if (!singleton)
- singleton = NM_DNS_MANAGER (g_object_new (NM_TYPE_DNS_MANAGER, NULL));
- else
- g_object_ref (singleton);
-
- g_assert (singleton);
- return singleton;
-}
-
-
-GQuark
-nm_dns_manager_error_quark (void)
-{
- static GQuark quark = 0;
- if (!quark)
- quark = g_quark_from_static_string ("nm_dns_manager_error");
-
- return quark;
-}
-
typedef struct {
GPtrArray *nameservers;
const char *domain;
GPtrArray *searches;
const char *nis_domain;
GPtrArray *nis_servers;
@@ -267,13 +253,13 @@ dispatch_netconfig (const char *domain,
char **nameservers,
const char *nis_domain,
char **nis_servers,
const char *iface,
GError **error)
{
- char *str;
+ char *str, *tmp;
GPid pid;
gint fd;
int ret;
pid = run_netconfig (error, &fd);
if (pid < 0)
@@ -291,14 +277,12 @@ dispatch_netconfig (const char *domain,
write_to_netconfig (fd, "INTERFACE", iface);
if (searches) {
str = g_strjoinv (" ", searches);
if (domain) {
- char *tmp;
-
tmp = g_strconcat (domain, " ", str, NULL);
g_free (str);
str = tmp;
}
write_to_netconfig (fd, "DNSSEARCH", str);
@@ -347,12 +331,13 @@ write_resolv_conf (FILE *f, const char *domain,
{
char *domain_str = NULL;
char *searches_str = NULL;
char *nameservers_str = NULL;
int i;
gboolean retval = FALSE;
+ GString *str;
if (fprintf (f, "%s","# Generated by NetworkManager\n") < 0) {
g_set_error (error,
NM_DNS_MANAGER_ERROR,
NM_DNS_MANAGER_ERROR_SYSTEM,
"Could not write " RESOLV_CONF ": %s\n",
@@ -368,18 +353,16 @@ write_resolv_conf (FILE *f, const char *domain,
tmp_str = g_strjoinv (" ", searches);
searches_str = g_strconcat ("search ", tmp_str, "\n", NULL);
g_free (tmp_str);
}
- if (nameservers) {
- GString *str;
- int num;
+ str = g_string_new ("");
- str = g_string_new ("");
- num = g_strv_length (nameservers);
+ if (nameservers) {
+ int num = g_strv_length (nameservers);
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# ");
@@ -388,20 +371,20 @@ write_resolv_conf (FILE *f, const char *domain,
}
g_string_append (str, "nameserver ");
g_string_append (str, nameservers[i]);
g_string_append_c (str, '\n');
}
-
- nameservers_str = g_string_free (str, FALSE);
}
+ nameservers_str = g_string_free (str, FALSE);
+
if (fprintf (f, "%s%s%s",
domain_str ? domain_str : "",
searches_str ? searches_str : "",
- nameservers_str ? nameservers_str : "") != -1)
+ strlen (nameservers_str) ? nameservers_str : "") != -1)
retval = TRUE;
g_free (domain_str);
g_free (searches_str);
g_free (nameservers_str);
@@ -533,36 +516,73 @@ update_resolv_conf (const char *domain,
out:
free (tmp_resolv_conf_realpath);
free (resolv_conf_realpath);
return *error ? FALSE : TRUE;
}
+static void
+compute_hash (NMDnsManager *self, gpointer *hash)
+{
+ NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+ gpointer check[HLEN];
+ GSList *iter;
+ int i = 0;
+
+ memset (check, 0, sizeof (check));
+
+ if (priv->ip4_vpn_config)
+ check[i++] = priv->ip4_vpn_config;
+ if (priv->ip4_device_config)
+ check[i++] = priv->ip4_device_config;
+
+ if (priv->ip6_vpn_config)
+ check[i++] = priv->ip6_vpn_config;
+ if (priv->ip6_device_config)
+ check[i++] = priv->ip6_device_config;
+
+ /* Add two more "other" configs if any exist */
+ for (iter = priv->configs; iter && i < HLEN; iter = g_slist_next (iter)) {
+ if ( (iter->data != priv->ip4_vpn_config)
+ && (iter->data != priv->ip4_device_config)
+ && (iter->data != priv->ip6_vpn_config)
+ && (iter->data != priv->ip6_device_config))
+ check[i++] = iter->data;
+ }
+ memcpy (hash, check, sizeof (check));
+}
+
static gboolean
-rewrite_resolv_conf (NMDnsManager *mgr, const char *iface, GError **error)
+update_dns (NMDnsManager *self,
+ const char *iface,
+ gboolean no_caching,
+ GError **error)
{
NMDnsManagerPrivate *priv;
NMResolvConfData rc;
- GSList *iter;
+ 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;
char **nis_servers = NULL;
int num, i, len;
- gboolean success = FALSE;
+ gboolean success = FALSE, caching = FALSE;
g_return_val_if_fail (error != NULL, FALSE);
g_return_val_if_fail (*error == NULL, FALSE);
- priv = NM_DNS_MANAGER_GET_PRIVATE (mgr);
+ priv = NM_DNS_MANAGER_GET_PRIVATE (self);
if (iface) {
g_free (priv->last_iface);
priv->last_iface = g_strdup (iface);
}
+ /* Update hash with config we're applying */
+ compute_hash (self, priv->hash);
+
rc.nameservers = g_ptr_array_new ();
rc.domain = NULL;
rc.searches = g_ptr_array_new ();
rc.nis_servers = g_ptr_array_new ();
if (priv->ip4_vpn_config)
@@ -637,12 +657,77 @@ rewrite_resolv_conf (NMDnsManager *mgr, const char *iface, GError **error)
nis_servers = (char **) g_ptr_array_free (rc.nis_servers, FALSE);
} else
g_ptr_array_free (rc.nis_servers, TRUE);
nis_domain = rc.nis_domain;
+ /* Build up config lists for plugins; we use the raw configs here, not the
+ * merged information that we write to resolv.conf so that the plugins can
+ * still use the domain information in each config to provide split DNS if
+ * they want to.
+ */
+ if (priv->ip4_vpn_config)
+ vpn_configs = g_slist_append (vpn_configs, priv->ip4_vpn_config);
+ if (priv->ip6_vpn_config)
+ vpn_configs = g_slist_append (vpn_configs, priv->ip6_vpn_config);
+ if (priv->ip4_device_config)
+ dev_configs = g_slist_append (dev_configs, priv->ip4_device_config);
+ if (priv->ip6_device_config)
+ dev_configs = g_slist_append (dev_configs, priv->ip6_device_config);
+
+ for (iter = priv->configs; iter; iter = g_slist_next (iter)) {
+ if ( (iter->data != priv->ip4_vpn_config)
+ && (iter->data != priv->ip4_device_config)
+ && (iter->data != priv->ip6_vpn_config)
+ && (iter->data != priv->ip6_device_config))
+ other_configs = g_slist_append (other_configs, iter->data);
+ }
+
+ /* Let any plugins do their thing first */
+ for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
+ NMDnsPlugin *plugin = NM_DNS_PLUGIN (iter->data);
+ 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;
+ }
+ caching = TRUE;
+ }
+
+ nm_log_dbg (LOGD_DNS, "DNS: updating plugin %s", plugin_name);
+ if (!nm_dns_plugin_update (plugin,
+ vpn_configs,
+ dev_configs,
+ other_configs,
+ priv->hostname)) {
+ nm_log_warn (LOGD_DNS, "DNS: plugin %s update failed", plugin_name);
+
+ /* If the plugin failed to update, we shouldn't write out a local
+ * caching DNS configuration to resolv.conf.
+ */
+ caching = FALSE;
+ }
+ }
+ g_slist_free (vpn_configs);
+ g_slist_free (dev_configs);
+ g_slist_free (other_configs);
+
+ /* 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.
+ */
+ if (caching) {
+ if (nameservers)
+ g_strfreev (nameservers);
+ nameservers = g_new0 (char*, 2);
+ nameservers[0] = g_strdup ("127.0.0.1");
+ }
+
#ifdef RESOLVCONF_PATH
success = dispatch_resolvconf (domain, searches, nameservers, iface, error);
#endif
#ifdef TARGET_SUSE
if (success == FALSE) {
@@ -665,12 +750,49 @@ rewrite_resolv_conf (NMDnsManager *mgr, const char *iface, GError **error)
if (nis_servers)
g_strfreev (nis_servers);
return success;
}
+static void
+plugin_failed (NMDnsPlugin *plugin, gpointer user_data)
+{
+ NMDnsManager *self = NM_DNS_MANAGER (user_data);
+ NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+ GError *error = NULL;
+
+ /* Errors with non-caching plugins aren't fatal */
+ if (!nm_dns_plugin_is_caching (plugin))
+ return;
+
+ /* Disable caching until the next DNS update */
+ if (!update_dns (self, priv->last_iface, TRUE, &error)) {
+ nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
+ }
+}
+
+static gboolean
+config_changed (NMDnsManager *self)
+{
+ NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+ gpointer check[HLEN];
+
+ /* We only store HLEN configs; so if there are actually more than that,
+ * we have to assume that the config has changed.
+ */
+ if (g_slist_length (priv->configs) > HLEN)
+ return TRUE;
+
+ /* Otherwise return TRUE if the configuration has changed */
+ compute_hash (self, check);
+ return memcmp (check, priv->hash, sizeof (check)) ? TRUE : FALSE;
+}
+
gboolean
nm_dns_manager_add_ip4_config (NMDnsManager *mgr,
const char *iface,
NMIP4Config *config,
NMDnsIPConfigType cfg_type)
{
@@ -695,15 +817,20 @@ nm_dns_manager_add_ip4_config (NMDnsManager *mgr,
}
/* Don't allow the same zone added twice */
if (!g_slist_find (priv->configs, config))
priv->configs = g_slist_append (priv->configs, g_object_ref (config));
- if (!rewrite_resolv_conf (mgr, iface, &error)) {
- nm_log_warn (LOGD_DNS, "could not commit DNS changes: '%s'", error ? error->message : "(none)");
- g_error_free (error);
+ if (!config_changed (mgr))
+ return TRUE;
+
+ if (!update_dns (mgr, iface, FALSE, &error)) {
+ nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
}
return TRUE;
}
gboolean
@@ -730,16 +857,20 @@ nm_dns_manager_remove_ip4_config (NMDnsManager *mgr,
priv->ip4_vpn_config = NULL;
if (config == priv->ip4_device_config)
priv->ip4_device_config = NULL;
g_object_unref (config);
- if (!rewrite_resolv_conf (mgr, iface, &error)) {
- nm_log_warn (LOGD_DNS, "could not commit DNS changes: '%s'", error ? error->message : "(none)");
- if (error)
- g_error_free (error);
+ if (config_changed (mgr))
+ return TRUE;
+
+ if (!update_dns (mgr, iface, FALSE, &error)) {
+ nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
}
return TRUE;
}
gboolean
@@ -771,15 +902,20 @@ nm_dns_manager_add_ip6_config (NMDnsManager *mgr,
}
/* Don't allow the same zone added twice */
if (!g_slist_find (priv->configs, config))
priv->configs = g_slist_append (priv->configs, g_object_ref (config));
- if (!rewrite_resolv_conf (mgr, iface, &error)) {
- nm_log_warn (LOGD_DNS, "could not commit DNS changes: '%s'", error ? error->message : "(none)");
- g_error_free (error);
+ if (config_changed (mgr))
+ return TRUE;
+
+ if (!update_dns (mgr, iface, FALSE, &error)) {
+ nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
}
return TRUE;
}
gboolean
@@ -806,16 +942,20 @@ nm_dns_manager_remove_ip6_config (NMDnsManager *mgr,
priv->ip6_vpn_config = NULL;
if (config == priv->ip6_device_config)
priv->ip6_device_config = NULL;
g_object_unref (config);
- if (!rewrite_resolv_conf (mgr, iface, &error)) {
- nm_log_warn (LOGD_DNS, "could not commit DNS changes: '%s'", error ? error->message : "(none)");
- if (error)
- g_error_free (error);
+ if (config_changed (mgr))
+ return TRUE;
+
+ if (!update_dns (mgr, iface, FALSE, &error)) {
+ nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
}
return TRUE;
}
void
@@ -843,18 +983,91 @@ nm_dns_manager_set_hostname (NMDnsManager *mgr,
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 (!rewrite_resolv_conf (mgr, priv->last_iface, &error)) {
- nm_log_warn (LOGD_DNS, "could not commit DNS changes: '%s'", error ? error->message : "(none)");
+ if (!update_dns (mgr, priv->last_iface, FALSE, &error)) {
+ nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
+ error ? error->code : -1,
+ error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
}
+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 if (!strcasecmp (*iter, "bind"))
+ plugin = NM_DNS_PLUGIN (nm_dns_bind_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)
+{
+ 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;
+}
+
+GQuark
+nm_dns_manager_error_quark (void)
+{
+ static GQuark quark = 0;
+ if (!quark)
+ quark = g_quark_from_static_string ("nm_dns_manager_error");
+
+ return quark;
+}
static void
nm_dns_manager_init (NMDnsManager *mgr)
{
}
@@ -865,12 +1078,15 @@ nm_dns_manager_finalize (GObject *object)
g_slist_foreach (priv->configs, (GFunc) g_object_unref, NULL);
g_slist_free (priv->configs);
g_free (priv->hostname);
g_free (priv->last_iface);
+ g_slist_foreach (priv->plugins, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->plugins);
+
G_OBJECT_CLASS (nm_dns_manager_parent_class)->finalize (object);
}
static void
nm_dns_manager_class_init (NMDnsManagerClass *klass)
{
diff --git a/src/dns-manager/nm-dns-manager.h b/src/dns-manager/nm-dns-manager.h
index 0002d69990..eb1c73a7c2 100644
--- a/src/dns-manager/nm-dns-manager.h
+++ b/src/dns-manager/nm-dns-manager.h
@@ -64,13 +64,13 @@ typedef struct {
typedef struct {
GObjectClass parent;
} NMDnsManagerClass;
GType nm_dns_manager_get_type (void);
-NMDnsManager * nm_dns_manager_get (void);
+NMDnsManager * nm_dns_manager_get (const char **plugins);
gboolean nm_dns_manager_add_ip4_config (NMDnsManager *mgr,
const char *iface,
NMIP4Config *config,
NMDnsIPConfigType cfg_type);
diff --git a/src/dns-manager/nm-dns-plugin.c b/src/dns-manager/nm-dns-plugin.c
new file mode 100644
index 0000000000..d47640b1c3
--- /dev/null
+++ b/src/dns-manager/nm-dns-plugin.c
@@ -0,0 +1,318 @@
+/* -*- 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_caching (NMDnsPlugin *self)
+{
+ return FALSE;
+}
+
+gboolean
+nm_dns_plugin_is_caching (NMDnsPlugin *self)
+{
+ return NM_DNS_PLUGIN_GET_CLASS (self)->is_caching (self);
+}
+
+const char *
+nm_dns_plugin_get_name (NMDnsPlugin *self)
+{
+ g_assert (NM_DNS_PLUGIN_GET_CLASS (self)->get_name);
+ return NM_DNS_PLUGIN_GET_CLASS (self)->get_name (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, "DNS: starting %s...", priv->progname);
+ cmdline = g_strjoinv (" ", (char **) argv);
+ nm_log_dbg (LOGD_DNS, "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_caching = is_caching;
+
+ /* 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..d4298b869d
--- /dev/null
+++ b/src/dns-manager/nm-dns-plugin.h
@@ -0,0 +1,112 @@
+/* -*- 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_caching) (NMDnsPlugin *self);
+
+ /* Subclasses should override this and return their plugin name */
+ const char *(*get_name) (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_caching (NMDnsPlugin *self);
+
+const char *nm_dns_plugin_get_name (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 */
+
diff --git a/src/main.c b/src/main.c
index 01bca9dc3e..7e75f05efb 100644
--- a/src/main.c
+++ b/src/main.c
@@ -297,12 +297,13 @@ done:
}
static gboolean
parse_config_file (const char *filename,
char **plugins,
char **dhcp_client,
+ char ***dns_plugins,
char **log_level,
char **log_domains,
GError **error)
{
GKeyFile *config;
@@ -319,12 +320,13 @@ parse_config_file (const char *filename,
*plugins = g_key_file_get_value (config, "main", "plugins", error);
if (*error)
return FALSE;
*dhcp_client = g_key_file_get_value (config, "main", "dhcp", NULL);
+ *dns_plugins = g_key_file_get_string_list (config, "main", "dns", NULL, NULL);
*log_level = g_key_file_get_value (config, "logging", "level", NULL);
*log_domains = g_key_file_get_value (config, "logging", "domains", NULL);
g_key_file_free (config);
return TRUE;
@@ -439,12 +441,13 @@ main (int argc, char *argv[])
GOptionContext *opt_ctx = NULL;
gboolean become_daemon = FALSE;
gboolean g_fatal_warnings = FALSE;
char *pidfile = NULL, *state_file = NULL, *dhcp = NULL;
char *config = NULL, *plugins = NULL, *conf_plugins = NULL;
char *log_level = NULL, *log_domains = NULL;
+ char **dns = NULL;
gboolean wifi_enabled = TRUE, net_enabled = TRUE, wwan_enabled = TRUE;
gboolean success;
NMPolicy *policy = NULL;
NMVPNManager *vpn_manager = NULL;
NMDnsManager *dns_mgr = NULL;
NMDBusManager *dbus_mgr = NULL;
@@ -512,13 +515,13 @@ main (int argc, char *argv[])
/* check pid file */
if (check_pidfile (pidfile))
exit (1);
/* Parse the config file */
if (config) {
- if (!parse_config_file (config, &conf_plugins, &dhcp, &cfg_log_level, &cfg_log_domains, &error)) {
+ if (!parse_config_file (config, &conf_plugins, &dhcp, &dns, &cfg_log_level, &cfg_log_domains, &error)) {
fprintf (stderr, "Config file %s invalid: (%d) %s\n",
config,
error ? error->code : -1,
(error && error->message) ? error->message : "unknown");
exit (1);
}
@@ -532,13 +535,13 @@ main (int argc, char *argv[])
* changing behavior during an upgrade. We don't want that.
*/
/* Try deprecated nm-system-settings.conf first */
if (g_file_test (NM_OLD_SYSTEM_CONF_FILE, G_FILE_TEST_EXISTS)) {
config = g_strdup (NM_OLD_SYSTEM_CONF_FILE);
- parsed = parse_config_file (config, &conf_plugins, &dhcp, &cfg_log_level, &cfg_log_domains, &error);
+ parsed = parse_config_file (config, &conf_plugins, &dhcp, &dns, &cfg_log_level, &cfg_log_domains, &error);
if (!parsed) {
fprintf (stderr, "Default config file %s invalid: (%d) %s\n",
config,
error ? error->code : -1,
(error && error->message) ? error->message : "unknown");
g_free (config);
@@ -547,13 +550,13 @@ main (int argc, char *argv[])
}
}
/* Try the preferred NetworkManager.conf last */
if (!parsed && g_file_test (NM_DEFAULT_SYSTEM_CONF_FILE, G_FILE_TEST_EXISTS)) {
config = g_strdup (NM_DEFAULT_SYSTEM_CONF_FILE);
- parsed = parse_config_file (config, &conf_plugins, &dhcp, &cfg_log_level, &cfg_log_domains, &error);
+ parsed = parse_config_file (config, &conf_plugins, &dhcp, &dns, &cfg_log_level, &cfg_log_domains, &error);
if (!parsed) {
fprintf (stderr, "Default config file %s invalid: (%d) %s\n",
config,
error ? error->code : -1,
(error && error->message) ? error->message : "unknown");
g_free (config);
@@ -660,13 +663,13 @@ main (int argc, char *argv[])
vpn_manager = nm_vpn_manager_get ();
if (!vpn_manager) {
nm_log_err (LOGD_CORE, "failed to start the VPN manager.");
goto done;
}
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get ((const char **) dns);
if (!dns_mgr) {
nm_log_err (LOGD_CORE, "failed to start the DNS manager.");
goto done;
}
manager = nm_manager_get (config,
@@ -753,12 +756,13 @@ done:
/* Free options */
g_free (pidfile);
g_free (state_file);
g_free (config);
g_free (plugins);
g_free (dhcp);
+ g_strfreev (dns);
g_free (log_level);
g_free (log_domains);
g_free (cfg_log_level);
g_free (cfg_log_domains);
nm_log_info (LOGD_CORE, "exiting (%s)", success ? "success" : "error");
diff --git a/src/nm-device.c b/src/nm-device.c
index ad1976b03c..abffe1264f 100644
--- a/src/nm-device.c
+++ b/src/nm-device.c
@@ -3035,13 +3035,13 @@ nm_device_set_ip4_config (NMDevice *self,
diff = nm_ip4_config_diff (new_config, old_config);
/* No actual change, do nothing */
if (diff == NM_IP4_COMPARE_FLAG_NONE)
return TRUE;
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get (NULL);
if (old_config) {
/* Remove any previous IP4 Config from the DNS manager */
nm_dns_manager_remove_ip4_config (dns_mgr, ip_iface, old_config);
g_object_unref (old_config);
priv->ip4_config = NULL;
}
@@ -3138,13 +3138,13 @@ nm_device_set_ip6_config (NMDevice *self,
diff = nm_ip6_config_diff (new_config, old_config);
/* No actual change, do nothing */
if (diff == NM_IP6_COMPARE_FLAG_NONE)
return TRUE;
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get (NULL);
if (old_config) {
/* Remove any previous IP6 Config from the DNS manager */
nm_dns_manager_remove_ip6_config (dns_mgr, ip_iface, old_config);
g_object_unref (old_config);
priv->ip6_config = NULL;
}
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 28864a8780..3ab4db550c 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -238,13 +238,13 @@ _set_hostname (NMPolicy *policy,
if (change_hostname) {
NMDnsManager *dns_mgr;
g_free (policy->cur_hostname);
policy->cur_hostname = g_strdup (new_hostname);
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get (NULL);
nm_dns_manager_set_hostname (dns_mgr, policy->cur_hostname);
g_object_unref (dns_mgr);
}
/* Get the default IPv4 and IPv6 addresses so we can assign
* the hostname to them in /etc/hosts.
@@ -550,13 +550,13 @@ update_ip4_routing_and_dns (NMPolicy *policy, gboolean force_update)
req = nm_device_get_act_request (dev);
if (req && (req != best_req))
nm_act_request_set_default (req, FALSE);
}
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get (NULL);
nm_dns_manager_add_ip4_config (dns_mgr, ip_iface, ip4_config, dns_type);
g_object_unref (dns_mgr);
/* Now set new default active connection _after_ updating DNS info, so that
* if the connection is shared dnsmasq picks up the right stuff.
*/
@@ -676,13 +676,13 @@ update_ip6_routing_and_dns (NMPolicy *policy, gboolean force_update)
req = nm_device_get_act_request (dev);
if (req && (req != best_req))
nm_act_request_set_default6 (req, FALSE);
}
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get (NULL);
nm_dns_manager_add_ip6_config (dns_mgr, ip_iface, ip6_config, dns_type);
g_object_unref (dns_mgr);
/* Now set new default active connection _after_ updating DNS info, so that
* if the connection is shared dnsmasq picks up the right stuff.
*/
diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c
index d496e48ee3..cf844992c0 100644
--- a/src/vpn-manager/nm-vpn-connection.c
+++ b/src/vpn-manager/nm-vpn-connection.c
@@ -546,13 +546,13 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
NMDnsManager *dns_mgr;
/* Add any explicit route to the VPN gateway through the parent device */
priv->gw_route = nm_system_add_ip4_vpn_gateway_route (priv->parent_dev, config);
/* Add the VPN to DNS */
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get (NULL);
nm_dns_manager_add_ip4_config (dns_mgr, priv->ip_iface, config, NM_DNS_IP_CONFIG_TYPE_VPN);
g_object_unref (dns_mgr);
priv->ip4_config = config;
nm_log_info (LOGD_VPN, "VPN connection '%s' (IP Config Get) complete.",
@@ -899,13 +899,13 @@ vpn_cleanup (NMVPNConnection *connection)
if (priv->ip4_config) {
NMIP4Config *parent_config;
NMDnsManager *dns_mgr;
/* Remove attributes of the VPN's IP4 Config */
- dns_mgr = nm_dns_manager_get ();
+ dns_mgr = nm_dns_manager_get (NULL);
nm_dns_manager_remove_ip4_config (dns_mgr, priv->ip_iface, priv->ip4_config);
g_object_unref (dns_mgr);
/* Remove any previously added VPN gateway host route */
if (priv->gw_route)
rtnl_route_del (nm_netlink_get_default_handle (), priv->gw_route, 0);