From 64b6cd1ebc4d8449b824616d6b25bcb677e9cc18 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 9 Sep 2010 17:14:20 -0500 Subject: core: add DNS plugin config options --- man/NetworkManager.conf.5.in | 34 ++++++++++-- src/dns-manager/nm-dns-manager.c | 108 +++++++++++++++++++++++++++--------- src/dns-manager/nm-dns-manager.h | 2 +- src/main.c | 12 ++-- src/nm-device.c | 4 +- src/nm-policy.c | 6 +- src/vpn-manager/nm-vpn-connection.c | 4 +- 7 files changed, 126 insertions(+), 44 deletions(-) diff --git a/man/NetworkManager.conf.5.in b/man/NetworkManager.conf.5.in index c3af611675..fca2a4dbb4 100644 --- a/man/NetworkManager.conf.5.in +++ b/man/NetworkManager.conf.5.in @@ -44,12 +44,13 @@ Description of sections and available keys follows: 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:" @@ -87,6 +88,27 @@ This key sets up what DHCP client NetworkManager will use. Presently \fIdhclient\fP and \fIdhcpcd\fP are supported. The client configured here should be available on your system too. If this key is missing, available DHCP clients are looked for in this order: dhclient, dhcpcd. +.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. +.TP +.I chromium +this plugin provides DNS information to the Chromium web browser. It does not +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 diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c index 6b4b67543c..c3a0a4ff45 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -59,7 +59,6 @@ G_DEFINE_TYPE(NMDnsManager, nm_dns_manager, G_TYPE_OBJECT) NM_TYPE_DNS_MANAGER, \ NMDnsManagerPrivate)) - struct NMDnsManagerPrivate { NMIP4Config *ip4_vpn_config; NMIP4Config *ip4_device_config; @@ -68,6 +67,8 @@ struct NMDnsManagerPrivate { GSList *configs; char *hostname; + 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). @@ -76,31 +77,6 @@ struct NMDnsManagerPrivate { }; -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; @@ -852,6 +828,83 @@ 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; +} + +static GObject * +nm_dns_chromium_new (void) +{ + return NULL; +} + +static void +load_plugins (NMDnsManager *self, const char **plugins) +{ + NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self); + GObject *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 (); + else if (!strcasecmp (*iter, "bind")) + plugin = nm_dns_bind_new (); + else if (!strcasecmp (*iter, "chromium")) + plugin = nm_dns_chromium_new (); + else { + nm_log_warn (LOGD_DNS, "Unknown DNS plugin '%s'", *iter); + } + + if (plugin) + priv->plugins = g_slist_append (priv->plugins, plugin); + } + } else { + /* Create default plugins */ + + /* Chromium support */ + plugin = nm_dns_chromium_new (); + g_assert (plugin); + priv->plugins = g_slist_append (priv->plugins, plugin); + } +} + +/******************************************************************/ + +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) @@ -868,6 +921,9 @@ nm_dns_manager_finalize (GObject *object) 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); } 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 @@ -67,7 +67,7 @@ typedef struct { 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, diff --git a/src/main.c b/src/main.c index 01bca9dc3e..7e75f05efb 100644 --- a/src/main.c +++ b/src/main.c @@ -300,6 +300,7 @@ static gboolean parse_config_file (const char *filename, char **plugins, char **dhcp_client, + char ***dns_plugins, char **log_level, char **log_domains, GError **error) @@ -322,6 +323,7 @@ parse_config_file (const char *filename, 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); @@ -442,6 +444,7 @@ main (int argc, char *argv[]) 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; @@ -515,7 +518,7 @@ main (int argc, char *argv[]) /* 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, @@ -535,7 +538,7 @@ main (int argc, char *argv[]) /* 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, @@ -550,7 +553,7 @@ 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, @@ -663,7 +666,7 @@ main (int argc, char *argv[]) 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; @@ -756,6 +759,7 @@ done: 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); 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 @@ -3038,7 +3038,7 @@ nm_device_set_ip4_config (NMDevice *self, 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); @@ -3141,7 +3141,7 @@ nm_device_set_ip6_config (NMDevice *self, 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); 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 @@ -241,7 +241,7 @@ _set_hostname (NMPolicy *policy, 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); } @@ -553,7 +553,7 @@ update_ip4_routing_and_dns (NMPolicy *policy, gboolean force_update) 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); @@ -679,7 +679,7 @@ update_ip6_routing_and_dns (NMPolicy *policy, gboolean force_update) 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); 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 @@ -549,7 +549,7 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy, 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); @@ -902,7 +902,7 @@ vpn_cleanup (NMVPNConnection *connection) 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); -- cgit v1.2.3 From 4da443dc69a7aa218dbbeb5b2e4fc9f9076826e7 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 10 Sep 2010 18:02:40 -0500 Subject: dns: first cut of a dnsmasq local caching plugin --- src/dns-manager/Makefile.am | 11 +- src/dns-manager/nm-dns-dnsmasq.c | 391 +++++++++++++++++++++++++++++++++++++++ src/dns-manager/nm-dns-dnsmasq.h | 47 +++++ src/dns-manager/nm-dns-manager.c | 18 +- src/dns-manager/nm-dns-plugin.c | 311 +++++++++++++++++++++++++++++++ src/dns-manager/nm-dns-plugin.h | 107 +++++++++++ 6 files changed, 871 insertions(+), 14 deletions(-) create mode 100644 src/dns-manager/nm-dns-dnsmasq.c create mode 100644 src/dns-manager/nm-dns-dnsmasq.h create mode 100644 src/dns-manager/nm-dns-plugin.c create mode 100644 src/dns-manager/nm-dns-plugin.h 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 + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#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 +#include + +#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 #endif @@ -828,12 +830,6 @@ nm_dns_manager_set_hostname (NMDnsManager *mgr, } } -static GObject * -nm_dns_dnsmasq_new (void) -{ - return NULL; -} - static GObject * nm_dns_bind_new (void) { @@ -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 +#include +#include +#include +#include +#include + +#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 +#include + +#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 */ + -- cgit v1.2.3 From a2982b5f7b22d9c162d9251229f8955a30aaaf37 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 12 Sep 2010 22:25:30 -0500 Subject: dns: hook plugins into DNS updates and make dnsmasq plugin actually work --- src/dns-manager/nm-dns-dnsmasq.c | 123 ++++++++---------------- src/dns-manager/nm-dns-manager.c | 195 +++++++++++++++++++++++++++++++-------- src/dns-manager/nm-dns-plugin.c | 19 ++-- src/dns-manager/nm-dns-plugin.h | 9 +- 4 files changed, 213 insertions(+), 133 deletions(-) diff --git a/src/dns-manager/nm-dns-dnsmasq.c b/src/dns-manager/nm-dns-dnsmasq.c index 5fd6fe6570..a3bf516429 100644 --- a/src/dns-manager/nm-dns-dnsmasq.c +++ b/src/dns-manager/nm-dns-dnsmasq.c @@ -41,83 +41,11 @@ G_DEFINE_TYPE (NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN) #define CONFFILE LOCALSTATEDIR "/run/nm-dns-dnsmasq.conf" typedef struct { - int pid; + guint32 foo; } 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) { @@ -234,12 +162,19 @@ update (NMDnsPlugin *plugin, 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; + 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); @@ -285,17 +220,18 @@ update (NMDnsPlugin *plugin, 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; + argv[3] = "--bind-interfaces"; + argv[4] = "--pid-file=" PIDFILE; + argv[5] = "--listen-address=127.0.0.1"; /* Should work for both 4 and 6 */ + argv[6] = "--conf-file=" CONFFILE; + argv[7] = NULL; /* And finally spawn dnsmasq */ - priv->pid = nm_dns_plugin_child_spawn (NM_DNS_PLUGIN (self), argv, PIDFILE, "bin/dnsmasq"); + 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; + return pid ? TRUE : FALSE; } /****************************************************************/ @@ -322,7 +258,6 @@ 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; @@ -341,12 +276,10 @@ child_quit (NMDnsPlugin *plugin, gint 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); - - priv->pid = 0; - unlink (CONFFILE); } /****************************************************************/ @@ -358,11 +291,17 @@ init (NMDnsPlugin *plugin) } static gboolean -is_exclusive (NMDnsPlugin *plugin) +is_caching (NMDnsPlugin *plugin) { return TRUE; } +static const char * +get_name (NMDnsPlugin *plugin) +{ + return "dnsmasq"; +} + /****************************************************************/ NMDnsDnsmasq * @@ -376,16 +315,28 @@ 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_exclusive = is_exclusive; + plugin_class->is_caching = is_caching; plugin_class->update = update; + plugin_class->get_name = get_name; } diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c index 615532133c..cd39a651e6 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -245,10 +245,11 @@ dispatch_netconfig (const char *domain, char **nameservers, const char *nis_domain, char **nis_servers, + gboolean caching, const char *iface, GError **error) { - char *str; + char *str, *tmp; GPid pid; gint fd; int ret; @@ -284,9 +285,14 @@ dispatch_netconfig (const char *domain, } if (nameservers) { - str = g_strjoinv (" ", nameservers); + tmp = g_strjoinv (" ", nameservers); + if (caching) + str = g_strdup_printf ("127.0.0.1 %s", tmp); + else + str = g_strdup (tmp); write_to_netconfig (fd, "DNSSERVERS", str); g_free (str); + g_free (tmp); } if (nis_domain) @@ -321,6 +327,7 @@ static gboolean write_resolv_conf (FILE *f, const char *domain, char **searches, char **nameservers, + gboolean caching, GError **error) { char *domain_str = NULL; @@ -328,6 +335,7 @@ write_resolv_conf (FILE *f, const char *domain, char *nameservers_str = NULL; int i; gboolean retval = FALSE; + GString *str; if (fprintf (f, "%s","# Generated by NetworkManager\n") < 0) { g_set_error (error, @@ -349,15 +357,16 @@ write_resolv_conf (FILE *f, const char *domain, g_free (tmp_str); } - if (nameservers) { - GString *str; - int num; + str = g_string_new (""); + + if (caching) + g_string_append_printf (str, "nameserver 127.0.0.1\n"); - 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) { + if (i == 3 && !caching) { g_string_append (str, "# "); g_string_append (str, _("NOTE: the libc resolver may not support more than 3 nameservers.")); g_string_append (str, "\n# "); @@ -369,14 +378,14 @@ write_resolv_conf (FILE *f, const char *domain, 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); @@ -391,6 +400,7 @@ static gboolean dispatch_resolvconf (const char *domain, char **searches, char **nameservers, + gboolean caching, const char *iface, GError **error) { @@ -412,7 +422,7 @@ dispatch_resolvconf (const char *domain, RESOLVCONF_PATH, g_strerror (errno)); else { - retval = write_resolv_conf (f, domain, searches, nameservers, error); + retval = write_resolv_conf (f, domain, searches, nameservers, caching, error); retval &= (pclose (f) == 0); } } else { @@ -432,6 +442,7 @@ static gboolean update_resolv_conf (const char *domain, char **searches, char **nameservers, + gboolean caching, const char *iface, GError **error) { @@ -479,7 +490,7 @@ update_resolv_conf (const char *domain, strcpy (tmp_resolv_conf_realpath, RESOLV_CONF); } - write_resolv_conf (f, domain, searches, nameservers, error); + write_resolv_conf (f, domain, searches, nameservers, caching, error); if (fclose (f) < 0) { if (*error == NULL) { @@ -515,23 +526,26 @@ out: } 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); @@ -618,20 +632,74 @@ rewrite_resolv_conf (NMDnsManager *mgr, const char *iface, GError **error) 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); + #ifdef RESOLVCONF_PATH - success = dispatch_resolvconf (domain, searches, nameservers, iface, error); + success = dispatch_resolvconf (domain, searches, nameservers, caching, iface, error); #endif #ifdef TARGET_SUSE if (success == FALSE) { success = dispatch_netconfig (domain, searches, nameservers, nis_domain, nis_servers, - iface, error); + caching, iface, error); } #endif if (success == FALSE) - success = update_resolv_conf (domain, searches, nameservers, iface, error); + success = update_resolv_conf (domain, searches, nameservers, caching, iface, error); if (success) nm_system_update_dns (); @@ -646,6 +714,26 @@ rewrite_resolv_conf (NMDnsManager *mgr, const char *iface, GError **error) 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); + } +} + gboolean nm_dns_manager_add_ip4_config (NMDnsManager *mgr, const char *iface, @@ -676,9 +764,11 @@ nm_dns_manager_add_ip4_config (NMDnsManager *mgr, 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 (!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; @@ -711,10 +801,11 @@ nm_dns_manager_remove_ip4_config (NMDnsManager *mgr, 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 (!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; @@ -752,9 +843,11 @@ nm_dns_manager_add_ip6_config (NMDnsManager *mgr, 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 (!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; @@ -787,10 +880,11 @@ nm_dns_manager_remove_ip6_config (NMDnsManager *mgr, 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 (!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; @@ -824,8 +918,10 @@ nm_dns_manager_set_hostname (NMDnsManager *mgr, * 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); } } @@ -848,6 +944,7 @@ 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 */ @@ -859,19 +956,39 @@ load_plugins (NMDnsManager *self, const char **plugins) else if (!strcasecmp (*iter, "chromium")) plugin = NM_DNS_PLUGIN (nm_dns_chromium_new ()); else { - nm_log_warn (LOGD_DNS, "Unknown DNS plugin '%s'", *iter); + 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; } - if (plugin) - priv->plugins = g_slist_append (priv->plugins, plugin); + 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 */ /* Chromium support */ +#if 0 plugin = NM_DNS_PLUGIN (nm_dns_chromium_new ()); g_assert (plugin); priv->plugins = g_slist_append (priv->plugins, plugin); +#endif } } diff --git a/src/dns-manager/nm-dns-plugin.c b/src/dns-manager/nm-dns-plugin.c index bfa80bd87f..d47640b1c3 100644 --- a/src/dns-manager/nm-dns-plugin.c +++ b/src/dns-manager/nm-dns-plugin.c @@ -66,15 +66,22 @@ nm_dns_plugin_update (NMDnsPlugin *self, } static gboolean -is_exclusive (NMDnsPlugin *self) +is_caching (NMDnsPlugin *self) { return FALSE; } gboolean -nm_dns_plugin_is_exclusive (NMDnsPlugin *self) +nm_dns_plugin_is_caching (NMDnsPlugin *self) { - return NM_DNS_PLUGIN_GET_CLASS (self)->is_exclusive (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); } /********************************************/ @@ -158,9 +165,9 @@ nm_dns_plugin_child_spawn (NMDnsPlugin *self, priv->pidfile = g_strdup (pidfile); } - nm_log_info (LOGD_DNS, "Starting %s...", priv->progname); + nm_log_info (LOGD_DNS, "DNS: starting %s...", priv->progname); cmdline = g_strjoinv (" ", (char **) argv); - nm_log_dbg (LOGD_DNS, "Command line: %s", cmdline); + nm_log_dbg (LOGD_DNS, "DNS: command line: %s", cmdline); g_free (cmdline); priv->pid = 0; @@ -287,7 +294,7 @@ nm_dns_plugin_class_init (NMDnsPluginClass *plugin_class) /* virtual methods */ object_class->dispose = dispose; object_class->finalize = finalize; - plugin_class->is_exclusive = is_exclusive; + plugin_class->is_caching = is_caching; /* signals */ signals[FAILED] = diff --git a/src/dns-manager/nm-dns-plugin.h b/src/dns-manager/nm-dns-plugin.h index cb8fabe1ff..d4298b869d 100644 --- a/src/dns-manager/nm-dns-plugin.h +++ b/src/dns-manager/nm-dns-plugin.h @@ -59,7 +59,10 @@ typedef struct { * caching nameserver that listens on localhost and would block any * other local caching nameserver from operating. */ - gboolean (*is_exclusive) (NMDnsPlugin *self); + gboolean (*is_caching) (NMDnsPlugin *self); + + /* Subclasses should override this and return their plugin name */ + const char *(*get_name) (NMDnsPlugin *self); /* Signals */ @@ -80,7 +83,9 @@ typedef struct { GType nm_dns_plugin_get_type (void); -gboolean nm_dns_plugin_is_exclusive (NMDnsPlugin *self); +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, -- cgit v1.2.3 From 9d0775448ce88feb0bb17aa6b6df85fa671333e1 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Sun, 12 Sep 2010 23:16:25 -0500 Subject: dns: use VPN nameservers first even if no domain was given If the VPN client didn't provide a domain we still want to use the VPN nameservers first, we just can't do split DNS. Also use --strict-order to ensure VPN nameservers are always chosen first. --- src/dns-manager/nm-dns-dnsmasq.c | 56 +++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/dns-manager/nm-dns-dnsmasq.c b/src/dns-manager/nm-dns-dnsmasq.c index a3bf516429..132579dc6c 100644 --- a/src/dns-manager/nm-dns-dnsmasq.c +++ b/src/dns-manager/nm-dns-dnsmasq.c @@ -103,9 +103,32 @@ add_ip4_config (GString *str, NMIP4Config *ip4, gboolean split) } } + /* If no searches or domains, just add the namservers */ + if (n == 0) { + 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) { @@ -118,16 +141,8 @@ add_ip6_config (GString *str, NMIP6Config *ip6, gboolean split) * 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; - } + 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); @@ -151,6 +166,16 @@ add_ip6_config (GString *str, NMIP6Config *ip6, gboolean split) } } + /* If no searches or domains, just add the namservers */ + if (n == 0) { + 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; } @@ -220,11 +245,12 @@ update (NMDnsPlugin *plugin, argv[0] = find_dnsmasq (); argv[1] = "--no-resolv"; /* Use only commandline */ argv[2] = "--keep-in-foreground"; - argv[3] = "--bind-interfaces"; - argv[4] = "--pid-file=" PIDFILE; - argv[5] = "--listen-address=127.0.0.1"; /* Should work for both 4 and 6 */ - argv[6] = "--conf-file=" CONFFILE; - argv[7] = NULL; + 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"); -- cgit v1.2.3 From 57f6feb102ede868b4692e6a2a8177ec7663c04f Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 14 Sep 2010 23:41:33 -0500 Subject: dns: only write out new DNS config if it really changed Use a pseudo-hash to quickly check whether the DNS config has really changed or not. This is certainly better than the 500 line patch I did then scrapped in favor of this approach... yay. This helps ensure that we don't kill then respawn caching DNS servers more often than we have to. --- src/dns-manager/nm-dns-manager.c | 74 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c index cd39a651e6..af71320b2f 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -43,6 +43,7 @@ #include "nm-system.h" #include "NetworkManagerUtils.h" +#include "nm-dns-plugin.h" #include "nm-dns-dnsmasq.h" #ifdef HAVE_SELINUX @@ -53,8 +54,6 @@ #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), \ @@ -69,6 +68,14 @@ struct NMDnsManagerPrivate { 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 @@ -525,6 +532,37 @@ out: 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 update_dns (NMDnsManager *self, const char *iface, @@ -552,6 +590,9 @@ update_dns (NMDnsManager *self, 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 (); @@ -734,6 +775,23 @@ plugin_failed (NMDnsPlugin *plugin, gpointer user_data) } } +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, @@ -764,6 +822,9 @@ nm_dns_manager_add_ip4_config (NMDnsManager *mgr, if (!g_slist_find (priv->configs, config)) priv->configs = g_slist_append (priv->configs, g_object_ref (config)); + 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, @@ -801,6 +862,9 @@ nm_dns_manager_remove_ip4_config (NMDnsManager *mgr, g_object_unref (config); + 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, @@ -843,6 +907,9 @@ nm_dns_manager_add_ip6_config (NMDnsManager *mgr, if (!g_slist_find (priv->configs, config)) priv->configs = g_slist_append (priv->configs, g_object_ref (config)); + 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, @@ -880,6 +947,9 @@ nm_dns_manager_remove_ip6_config (NMDnsManager *mgr, g_object_unref (config); + 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, -- cgit v1.2.3 From 1da4a11ce55c7bf4ce46adca15c609c61b9d9caf Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 14 Sep 2010 23:55:41 -0500 Subject: dns: (dnsmasq) fix handling of multiple DNS servers in non-split configs --- src/dns-manager/nm-dns-dnsmasq.c | 116 ++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/src/dns-manager/nm-dns-dnsmasq.c b/src/dns-manager/nm-dns-dnsmasq.c index 132579dc6c..254e2ffbb6 100644 --- a/src/dns-manager/nm-dns-dnsmasq.c +++ b/src/dns-manager/nm-dns-dnsmasq.c @@ -71,40 +71,41 @@ 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); + 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%s%s\n", - split ? "/" : "", - split ? nm_ip4_config_get_domain (ip4, i) : "", - split ? "/" : "", - buf); + 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 (n == 0) { + if (!added) { n = nm_ip4_config_get_num_nameservers (ip4); for (i = 0; i < n; i++) { memset (&buf[0], 0, sizeof (buf)); @@ -135,39 +136,40 @@ 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); - 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%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); + 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%s%s\n", - split ? "/" : "", - split ? nm_ip6_config_get_domain (ip6, i) : "", - split ? "/" : "", - buf); + 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 (n == 0) { + if (!added) { n = nm_ip6_config_get_num_nameservers (ip6); for (i = 0; i < n; i++) { addr = nm_ip6_config_get_nameserver (ip6, i); -- cgit v1.2.3 From 1e33d1e906968a851747c2a870e53d422c621ed0 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 15 Sep 2010 10:25:11 -0500 Subject: dns: remove unused Chromium bits This was supposed to hook up to the bits Adam Langley did last year for his local-dns-cache DBus service, but I misunderstood the architecture. It was a separate service, not Chromium itself. But it's unclear what happened to his local-dns-cache since the project doesn't seem to have any commits in a year and I'm unsure if it's actually being used. So remove this stuff for now. --- man/NetworkManager.conf.5.in | 4 ---- src/dns-manager/nm-dns-manager.c | 15 --------------- 2 files changed, 19 deletions(-) diff --git a/man/NetworkManager.conf.5.in b/man/NetworkManager.conf.5.in index fca2a4dbb4..bd9b2cc5a8 100644 --- a/man/NetworkManager.conf.5.in +++ b/man/NetworkManager.conf.5.in @@ -104,10 +104,6 @@ this plugin uses dnsmasq to provide local caching nameserver functionality. .I bind this plugin uses the ISC BIND program to provide local caching nameserver functionality. -.TP -.I chromium -this plugin provides DNS information to the Chromium web browser. It does not -provide local caching nameserver functionality. .RE .SS [keyfile] This section contains keyfile-specific options and thus only has effect when using \fIkeyfile\fP plugin. diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c index af71320b2f..823517fb78 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -1002,12 +1002,6 @@ nm_dns_bind_new (void) return NULL; } -static GObject * -nm_dns_chromium_new (void) -{ - return NULL; -} - static void load_plugins (NMDnsManager *self, const char **plugins) { @@ -1023,8 +1017,6 @@ load_plugins (NMDnsManager *self, const char **plugins) plugin = NM_DNS_PLUGIN (nm_dns_dnsmasq_new ()); else if (!strcasecmp (*iter, "bind")) plugin = NM_DNS_PLUGIN (nm_dns_bind_new ()); - else if (!strcasecmp (*iter, "chromium")) - plugin = NM_DNS_PLUGIN (nm_dns_chromium_new ()); else { nm_log_warn (LOGD_DNS, "Unknown DNS plugin '%s'", *iter);\ continue; @@ -1052,13 +1044,6 @@ load_plugins (NMDnsManager *self, const char **plugins) } } else { /* Create default plugins */ - - /* Chromium support */ -#if 0 - plugin = NM_DNS_PLUGIN (nm_dns_chromium_new ()); - g_assert (plugin); - priv->plugins = g_slist_append (priv->plugins, plugin); -#endif } } -- cgit v1.2.3 From e0a2aeaa7c7ac9b7953565ef20a5349b14305502 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 16 Sep 2010 15:31:47 -0500 Subject: dns: add BIND9 local caching nameserver support For some reason split DNS doesn't work yet (all queries are directed to the VPN nameserver if a VPN is active) but it otherwise works. --- src/dns-manager/Makefile.am | 4 +- src/dns-manager/nm-dns-bind.c | 528 +++++++++++++++++++++++++++++++++++++++ src/dns-manager/nm-dns-bind.h | 47 ++++ src/dns-manager/nm-dns-manager.c | 7 +- 4 files changed, 579 insertions(+), 7 deletions(-) create mode 100644 src/dns-manager/nm-dns-bind.c create mode 100644 src/dns-manager/nm-dns-bind.h diff --git a/src/dns-manager/Makefile.am b/src/dns-manager/Makefile.am index 2a56fb8602..1ffe62dcf0 100644 --- a/src/dns-manager/Makefile.am +++ b/src/dns-manager/Makefile.am @@ -12,7 +12,9 @@ libdns_manager_la_SOURCES = \ nm-dns-plugin.h \ nm-dns-plugin.c \ nm-dns-dnsmasq.h \ - nm-dns-dnsmasq.c + nm-dns-dnsmasq.c \ + nm-dns-bind.h \ + nm-dns-bind.c libdns_manager_la_CPPFLAGS = \ $(DBUS_CFLAGS) \ diff --git a/src/dns-manager/nm-dns-bind.c b/src/dns-manager/nm-dns-bind.c new file mode 100644 index 0000000000..c225eb95d2 --- /dev/null +++ b/src/dns-manager/nm-dns-bind.c @@ -0,0 +1,528 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* + * Copyright (C) 2010 Dan Williams + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#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" + " query-source address * port 53;\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 +#include + +#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-manager.c b/src/dns-manager/nm-dns-manager.c index 823517fb78..a0c6a6c4ce 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -45,6 +45,7 @@ #include "nm-dns-plugin.h" #include "nm-dns-dnsmasq.h" +#include "nm-dns-bind.h" #ifdef HAVE_SELINUX #include @@ -996,12 +997,6 @@ nm_dns_manager_set_hostname (NMDnsManager *mgr, } } -static GObject * -nm_dns_bind_new (void) -{ - return NULL; -} - static void load_plugins (NMDnsManager *self, const char **plugins) { -- cgit v1.2.3 From 20acb482c53beabb59022be258f167233dd14959 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 21 Sep 2010 00:08:01 -0500 Subject: dns: allow random source ports for BIND caching DNS Despite most guides saying that without restricting to port 53 queries won't get through a firewall, I cannot make it work with this option. DNS queries through a WRT54G just time out even when the WRT54G isn't caching anything itself (ie, explicit upstream nameservers are the forwarders in the bind config). --- src/dns-manager/nm-dns-bind.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dns-manager/nm-dns-bind.c b/src/dns-manager/nm-dns-bind.c index c225eb95d2..2e1ec67dec 100644 --- a/src/dns-manager/nm-dns-bind.c +++ b/src/dns-manager/nm-dns-bind.c @@ -349,7 +349,6 @@ update (NMDnsPlugin *plugin, g_string_append (conf, "options {\n" " directory \"" LOCALSTATEDIR "/named\";\n" - " query-source address * port 53;\n" " forward only;\n" " recursion yes;\n" " listen-on-v6 { ::1; };\n" -- cgit v1.2.3 From 06bd99f61779417453a049934ab136c8be9df955 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 21 Sep 2010 00:18:15 -0500 Subject: dns: write only 127.0.0.1 to resolv.conf when caching If all nameservers are listed in resolv.conf, glibc apparently tries them all (even if 'options rotate' isn't specified??). Leading to queries for internet hosts being directed to VPN-specific DNS servers in split-DNS situations. I've verified this with wireshark; I see queries going out over the tunnel to VPN nameservers for non-internal addresses, while BIND itself never logs anything about queries to VPN nameservers for that same address. Thus the only thing left is to blame glibc... --- src/dns-manager/nm-dns-manager.c | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c index a0c6a6c4ce..77ad9d73eb 100644 --- a/src/dns-manager/nm-dns-manager.c +++ b/src/dns-manager/nm-dns-manager.c @@ -253,7 +253,6 @@ dispatch_netconfig (const char *domain, char **nameservers, const char *nis_domain, char **nis_servers, - gboolean caching, const char *iface, GError **error) { @@ -281,8 +280,6 @@ dispatch_netconfig (const char *domain, str = g_strjoinv (" ", searches); if (domain) { - char *tmp; - tmp = g_strconcat (domain, " ", str, NULL); g_free (str); str = tmp; @@ -293,14 +290,9 @@ dispatch_netconfig (const char *domain, } if (nameservers) { - tmp = g_strjoinv (" ", nameservers); - if (caching) - str = g_strdup_printf ("127.0.0.1 %s", tmp); - else - str = g_strdup (tmp); + str = g_strjoinv (" ", nameservers); write_to_netconfig (fd, "DNSSERVERS", str); g_free (str); - g_free (tmp); } if (nis_domain) @@ -335,7 +327,6 @@ static gboolean write_resolv_conf (FILE *f, const char *domain, char **searches, char **nameservers, - gboolean caching, GError **error) { char *domain_str = NULL; @@ -367,14 +358,11 @@ write_resolv_conf (FILE *f, const char *domain, str = g_string_new (""); - if (caching) - g_string_append_printf (str, "nameserver 127.0.0.1\n"); - if (nameservers) { int num = g_strv_length (nameservers); for (i = 0; i < num; i++) { - if (i == 3 && !caching) { + 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# "); @@ -408,7 +396,6 @@ static gboolean dispatch_resolvconf (const char *domain, char **searches, char **nameservers, - gboolean caching, const char *iface, GError **error) { @@ -430,7 +417,7 @@ dispatch_resolvconf (const char *domain, RESOLVCONF_PATH, g_strerror (errno)); else { - retval = write_resolv_conf (f, domain, searches, nameservers, caching, error); + retval = write_resolv_conf (f, domain, searches, nameservers, error); retval &= (pclose (f) == 0); } } else { @@ -450,7 +437,6 @@ static gboolean update_resolv_conf (const char *domain, char **searches, char **nameservers, - gboolean caching, const char *iface, GError **error) { @@ -498,7 +484,7 @@ update_resolv_conf (const char *domain, strcpy (tmp_resolv_conf_realpath, RESOLV_CONF); } - write_resolv_conf (f, domain, searches, nameservers, caching, error); + write_resolv_conf (f, domain, searches, nameservers, error); if (fclose (f) < 0) { if (*error == NULL) { @@ -728,20 +714,31 @@ update_dns (NMDnsManager *self, 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, caching, iface, error); + success = dispatch_resolvconf (domain, searches, nameservers, iface, error); #endif #ifdef TARGET_SUSE if (success == FALSE) { success = dispatch_netconfig (domain, searches, nameservers, nis_domain, nis_servers, - caching, iface, error); + iface, error); } #endif if (success == FALSE) - success = update_resolv_conf (domain, searches, nameservers, caching, iface, error); + success = update_resolv_conf (domain, searches, nameservers, iface, error); if (success) nm_system_update_dns (); -- cgit v1.2.3