Merge remote branch 'origin/cachingdns'

Caching DNS with dnsmasq works well enough to merge for now.  THere
are still some issues with the BIND plugin because BIND is god-awful
unecessarily complex so we'll disable that in a further commit.
This commit is contained in:
Dan Williams 2010-09-22 16:19:28 -05:00
commit a211fadce0
14 changed files with 1746 additions and 80 deletions

View file

@ -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:"
@ -104,6 +105,23 @@ Example:
.nf
no-auto-default=00:22:68:5c:5d:c4,00:1e:65:ff:aa:ee
.fi
.TP
.B dns=\fIplugin1\fP,\fIplugin2\fP, ...
List DNS plugin names separated by ','. DNS plugins are used to provide local
caching nameserver functionality (which speeds up DNS queries) and to push
DNS data to applications that use it.
.P
.RS
.B "Available plugins:"
.br
.TP
.I dnsmasq
this plugin uses dnsmasq to provide local caching nameserver functionality.
.TP
.I bind
this plugin uses the ISC BIND program to provide local caching nameserver
functionality.
.RE
.SS [keyfile]
This section contains keyfile-specific options and thus only has effect when using \fIkeyfile\fP plugin.
.TP

View file

@ -6,13 +6,20 @@ 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 \
nm-dns-bind.h \
nm-dns-bind.c
libdns_manager_la_CPPFLAGS = \
$(DBUS_CFLAGS) \
$(GLIB_CFLAGS) \
-DNM_PKGDATADIR=\"$(pkgdatadir)\" \
-DNM_LOCALSTATEDIR=\"$(localstatedir)\"
-DLOCALSTATEDIR=\"$(localstatedir)\"
libdns_manager_la_LIBADD = \
$(top_builddir)/src/logging/libnm-logging.la \

View file

@ -0,0 +1,527 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "nm-dns-bind.h"
#include "nm-logging.h"
#include "nm-ip4-config.h"
#include "nm-ip6-config.h"
G_DEFINE_TYPE (NMDnsBind, nm_dns_bind, NM_TYPE_DNS_PLUGIN)
#define NM_DNS_BIND_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DNS_BIND, NMDnsBindPrivate))
#define PIDFILE LOCALSTATEDIR "/run/nm-dns-named.pid"
#define CONFFILE LOCALSTATEDIR "/run/nm-dns-named.conf"
typedef struct {
GPid pid;
} NMDnsBindPrivate;
/*******************************************/
static inline const char *
find_bind (void)
{
static const char *paths[] = {
"/usr/local/sbin/named",
"/usr/sbin/named",
"/sbin/named",
NULL
};
const char **binary = paths;
while (*binary != NULL) {
if (g_file_test (*binary, G_FILE_TEST_EXISTS))
return *binary;
binary++;
}
return NULL;
}
static gboolean
start_bind (NMDnsBind *self)
{
const char *argv[10];
argv[0] = find_bind ();
argv[1] = "-f"; /* don't daemonize; stay in foreground */
argv[2] = "-c";
argv[3] = CONFFILE;
argv[4] = NULL;
/* And finally spawn bind */
return nm_dns_plugin_child_spawn (NM_DNS_PLUGIN (self), argv, PIDFILE, "bin/named");
}
/*******************************************/
static gboolean
find_address (GPtrArray *array, const char *addr)
{
int n;
for (n = 0; n < array->len; n++) {
if (g_strcmp0 ((const char*) g_ptr_array_index (array, n), addr) == 0)
return TRUE;
}
return FALSE;
}
static void
add_ip4_nameservers (NMIP4Config *ip4, GPtrArray *array)
{
int i;
for (i = 0; i < nm_ip4_config_get_num_nameservers (ip4); i++) {
char buf[INET_ADDRSTRLEN + 1];
struct in_addr addr;
memset (&buf[0], 0, sizeof (buf));
addr.s_addr = nm_ip4_config_get_nameserver (ip4, i);
if (inet_ntop (AF_INET, &addr, buf, sizeof (buf))) {
if (!find_address (array, buf))
g_ptr_array_add (array, g_strdup (buf));
}
}
}
static gboolean
ip6_addr_to_string (const struct in6_addr *addr, char *buf, size_t buflen)
{
/* inet_ntop is probably supposed to do this for us, but it doesn't */
if (IN6_IS_ADDR_V4MAPPED (addr))
return !!inet_ntop (AF_INET, &(addr->s6_addr32[3]), buf, buflen);
return !!inet_ntop (AF_INET6, addr, buf, buflen);
}
static void
add_ip6_nameservers (NMIP6Config *ip6, GPtrArray *array)
{
char buf[INET6_ADDRSTRLEN + 1];
int i;
for (i = 0; i < nm_ip6_config_get_num_nameservers (ip6); i++) {
memset (buf, 0, sizeof (buf));
if (ip6_addr_to_string (nm_ip6_config_get_nameserver (ip6, i), buf, sizeof (buf))) {
if (!find_address (array, buf))
g_ptr_array_add (array, g_strdup (buf));
}
}
}
typedef struct {
guint32 dhash;
char *domain;
GPtrArray *servers;
} ZoneInfo;
static ZoneInfo *
zone_new (const char *domain)
{
ZoneInfo *info;
g_return_val_if_fail (domain != NULL, NULL);
info = g_malloc0 (sizeof (ZoneInfo));
info->domain = g_strdup (domain);
info->dhash = g_str_hash (domain);
info->servers = g_ptr_array_sized_new (4);
return info;
}
static void
zone_add_nameserver (ZoneInfo *info, const char *server)
{
guint32 i;
g_return_if_fail (info != NULL);
g_return_if_fail (server != NULL);
for (i = 0; i < info->servers->len; i++) {
if (g_strcmp0 ((char *) g_ptr_array_index (info->servers, i), server) == 0)
return;
}
g_ptr_array_add (info->servers, g_strdup (server));
}
static void
zone_free (ZoneInfo *info)
{
g_return_if_fail (info != NULL);
g_free (info->domain);
g_ptr_array_foreach (info->servers, (GFunc) g_free, NULL);
g_ptr_array_free (info->servers, TRUE);
memset (info, 0, sizeof (ZoneInfo));
g_free (info);
}
static ZoneInfo *
find_zone (GPtrArray *zones, const char *domain)
{
guint32 dhash, i;
g_return_val_if_fail (domain != NULL, FALSE);
dhash = g_str_hash (domain);
for (i = 0; i < zones->len; i++) {
ZoneInfo *zone = g_ptr_array_index (zones, i);
if (zone->dhash == dhash)
return zone;
}
return NULL;
}
static void
add_zone (GObject *ip, GPtrArray *zones)
{
guint32 i, j, ns, nd, nn;
GPtrArray *to_add;
ZoneInfo *z;
if (NM_IS_IP4_CONFIG (ip)) {
ns = nm_ip4_config_get_num_searches (NM_IP4_CONFIG (ip));
nd = nm_ip4_config_get_num_domains (NM_IP4_CONFIG (ip));
nn = nm_ip4_config_get_num_nameservers (NM_IP4_CONFIG (ip));
} else if (NM_IS_IP6_CONFIG (ip)) {
ns = nm_ip6_config_get_num_searches (NM_IP6_CONFIG (ip));
nd = nm_ip6_config_get_num_domains (NM_IP6_CONFIG (ip));
nn = nm_ip6_config_get_num_nameservers (NM_IP6_CONFIG (ip));
} else
g_assert_not_reached ();
/* If we don't have any domains or searches, or we don't have any
* nameservers, we can't do split DNS for this config.
*/
if ((!nd && !ns) || !nn)
return;
to_add = g_ptr_array_sized_new (MAX (ns, nd));
/* searches are preferred over domains */
for (i = 0; i < ns; i++) {
const char *domain = NULL;
if (NM_IS_IP4_CONFIG (ip))
domain = nm_ip4_config_get_search (NM_IP4_CONFIG (ip), i);
else if (NM_IS_IP6_CONFIG (ip))
domain = nm_ip6_config_get_search (NM_IP6_CONFIG (ip), i);
z = find_zone (zones, domain);
if (!z) {
z = zone_new (domain);
g_ptr_array_add (zones, z);
}
g_ptr_array_add (to_add, z);
}
if (ns == 0) {
/* If no searches, add any domains */
for (i = 0; i < nd; i++) {
const char *domain = NULL;
if (NM_IS_IP4_CONFIG (ip))
domain = nm_ip4_config_get_domain (NM_IP4_CONFIG (ip), i);
else if (NM_IS_IP6_CONFIG (ip))
domain = nm_ip6_config_get_domain (NM_IP6_CONFIG (ip), i);
z = find_zone (zones, domain);
if (!z) {
z = zone_new (domain);
g_ptr_array_add (zones, z);
}
g_ptr_array_add (to_add, z);
}
}
/* Now add the nameservers to every zone for this config */
for (i = 0; i < nn; i++) {
char buf[INET6_ADDRSTRLEN + 1];
struct in_addr addr4;
const struct in6_addr *addr6;
memset (&buf[0], 0, sizeof (buf));
if (NM_IS_IP4_CONFIG (ip)) {
addr4.s_addr = nm_ip4_config_get_nameserver (NM_IP4_CONFIG (ip), i);
if (!inet_ntop (AF_INET, &addr4, buf, sizeof (buf)))
continue;
} else if (NM_IS_IP6_CONFIG (ip)) {
addr6 = nm_ip6_config_get_nameserver (NM_IP6_CONFIG (ip), i);
if (!ip6_addr_to_string (addr6, buf, sizeof (buf)))
continue;
}
/* Add this nameserver to every zone from this IP config */
for (j = 0; j < to_add->len; j++) {
z = g_ptr_array_index (to_add, j);
zone_add_nameserver (z, buf);
}
}
g_ptr_array_free (to_add, TRUE);
}
static gboolean
update (NMDnsPlugin *plugin,
const GSList *vpn_configs,
const GSList *dev_configs,
const GSList *other_configs,
const char *hostname)
{
NMDnsBind *self = NM_DNS_BIND (plugin);
NMDnsBindPrivate *priv = NM_DNS_BIND_GET_PRIVATE (self);
GString *conf;
GPtrArray *globals, *zones;
GSList *iter;
GError *error = NULL;
int ignored, i, j;
gboolean success = FALSE;
/* Build up the new bind config file */
conf = g_string_sized_new (200);
globals = g_ptr_array_sized_new (6);
/* If any of the VPN configs *don't* have domains or searches, then we
* dont' have any split DNS configuration for them, and we add them
* first in the global nameserver lists. Otherwise we add them later as
* split DNS zones.
*/
for (iter = (GSList *) vpn_configs; iter;iter = g_slist_next (iter)) {
if (NM_IS_IP4_CONFIG (iter->data)) {
NMIP4Config *ip4 = NM_IP4_CONFIG (iter->data);
if (!nm_ip4_config_get_num_domains (ip4) && !nm_ip4_config_get_num_searches (ip4))
add_ip4_nameservers (ip4, globals);
} else if (NM_IS_IP6_CONFIG (iter->data)) {
NMIP6Config *ip6 = NM_IP6_CONFIG (iter->data);
if (!nm_ip6_config_get_num_domains (ip6) && !nm_ip6_config_get_num_searches (ip6))
add_ip6_nameservers (ip6, globals);
}
}
/* Get a list of global upstream servers with dupe checking */
for (iter = (GSList *) dev_configs; iter;iter = g_slist_next (iter)) {
if (NM_IS_IP4_CONFIG (iter->data))
add_ip4_nameservers (NM_IP4_CONFIG (iter->data), globals);
else if (NM_IS_IP6_CONFIG (iter->data))
add_ip6_nameservers (NM_IP6_CONFIG (iter->data), globals);
}
/* And any other random configs with dupe checking */
for (iter = (GSList *) other_configs; iter;iter = g_slist_next (iter)) {
if (NM_IS_IP4_CONFIG (iter->data))
add_ip4_nameservers (NM_IP4_CONFIG (iter->data), globals);
else if (NM_IS_IP6_CONFIG (iter->data))
add_ip6_nameservers (NM_IP6_CONFIG (iter->data), globals);
}
g_string_append (conf,
"options {\n"
" directory \"" LOCALSTATEDIR "/named\";\n"
" forward only;\n"
" recursion yes;\n"
" listen-on-v6 { ::1; };\n"
" listen-on { 127.0.0.1; };\n"
" forwarders {\n");
for (i = 0; i < globals->len; i++) {
char *ns = g_ptr_array_index (globals, i);
g_string_append_printf (conf, " %s;\n", ns);
g_free (ns);
}
g_ptr_array_free (globals, TRUE);
g_string_append (conf,
" };\n"
"};\n\n");
/* Build up the list of any split DNS zones, avoiding duplicates */
zones = g_ptr_array_sized_new (4);
for (iter = (GSList *) vpn_configs; iter;iter = g_slist_next (iter)) {
if (NM_IS_IP4_CONFIG (iter->data))
add_zone (G_OBJECT (iter->data), zones);
else if (NM_IS_IP6_CONFIG (iter->data))
add_zone (G_OBJECT (iter->data), zones);
}
/* Add all the zones to the config */
for (i = 0; i < zones->len; i++) {
ZoneInfo *z = g_ptr_array_index (zones, i);
g_string_append_printf (conf,
"zone \"%s\" IN {\n"
" type forward;\n"
" forward only;\n"
" forwarders {\n",
z->domain);
/* Add each nameserver for this zone */
for (j = 0; j < z->servers->len; j++) {
g_string_append_printf (conf,
" %s;\n",
(const char *) g_ptr_array_index (z->servers, j));
}
g_string_append (conf,
" };\n"
"};\n\n");
zone_free (z);
}
g_ptr_array_free (zones, TRUE);
/* Write out the config file */
if (!g_file_set_contents (CONFFILE, conf->str, -1, &error)) {
nm_log_warn (LOGD_DNS, "Failed to write named config file %s: (%d) %s",
CONFFILE,
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
goto out;
}
ignored = chmod (CONFFILE, 0600);
nm_log_dbg (LOGD_DNS, "BIND local caching DNS configuration:");
nm_log_dbg (LOGD_DNS, "%s", conf->str);
if (priv->pid) {
/* Send it SIGHUP to reload the new configuration */
if (kill (priv->pid, SIGHUP) == 0)
success = TRUE;
else {
/* Sigh... some error. Kill it and restart */
nm_dns_plugin_child_kill (NM_DNS_PLUGIN (self));
priv->pid = 0;
}
}
if (!success) {
/* Spawn it */
priv->pid = start_bind (self);
if (priv->pid)
success = TRUE;
}
out:
g_string_free (conf, TRUE);
return success;
}
/****************************************************************/
static void
child_quit (NMDnsPlugin *plugin, gint status)
{
NMDnsBind *self = NM_DNS_BIND (plugin);
gboolean failed = TRUE;
int err;
if (WIFEXITED (status)) {
err = WEXITSTATUS (status);
if (err) {
nm_log_warn (LOGD_DNS, "named exited with error %d", err);
} else
failed = FALSE;
} else if (WIFSTOPPED (status)) {
nm_log_warn (LOGD_DNS, "named stopped unexpectedly with signal %d", WSTOPSIG (status));
} else if (WIFSIGNALED (status)) {
nm_log_warn (LOGD_DNS, "named died with signal %d", WTERMSIG (status));
} else {
nm_log_warn (LOGD_DNS, "named died from an unknown cause");
}
unlink (CONFFILE);
if (failed)
g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
}
/****************************************************************/
static gboolean
init (NMDnsPlugin *plugin)
{
return TRUE;
}
static gboolean
is_caching (NMDnsPlugin *plugin)
{
return TRUE;
}
static const char *
get_name (NMDnsPlugin *plugin)
{
return "bind";
}
/****************************************************************/
NMDnsBind *
nm_dns_bind_new (void)
{
return (NMDnsBind *) g_object_new (NM_TYPE_DNS_BIND, NULL);
}
static void
nm_dns_bind_init (NMDnsBind *self)
{
}
static void
dispose (GObject *object)
{
unlink (CONFFILE);
G_OBJECT_CLASS (nm_dns_bind_parent_class)->dispose (object);
}
static void
nm_dns_bind_class_init (NMDnsBindClass *dns_class)
{
NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS (dns_class);
GObjectClass *object_class = G_OBJECT_CLASS (dns_class);
g_type_class_add_private (dns_class, sizeof (NMDnsBindPrivate));
object_class->dispose = dispose;
plugin_class->init = init;
plugin_class->child_quit = child_quit;
plugin_class->is_caching = is_caching;
plugin_class->update = update;
plugin_class->get_name = get_name;
}

View file

@ -0,0 +1,47 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2010 Red Hat, Inc.
*/
#ifndef NM_DNS_BIND_H
#define NM_DNS_BIND_H
#include <glib.h>
#include <glib-object.h>
#include "nm-dns-plugin.h"
#define NM_TYPE_DNS_BIND (nm_dns_bind_get_type ())
#define NM_DNS_BIND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNS_BIND, NMDnsBind))
#define NM_DNS_BIND_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNS_BIND, NMDnsBindClass))
#define NM_IS_DNS_BIND(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DNS_BIND))
#define NM_IS_DNS_BIND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_DNS_BIND))
#define NM_DNS_BIND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DNS_BIND, NMDnsBindClass))
typedef struct {
NMDnsPlugin parent;
} NMDnsBind;
typedef struct {
NMDnsPluginClass parent;
} NMDnsBindClass;
GType nm_dns_bind_get_type (void);
NMDnsBind *nm_dns_bind_new (void);
#endif /* NM_DNS_BIND_H */

View file

@ -0,0 +1,370 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
* Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "nm-dns-dnsmasq.h"
#include "nm-logging.h"
#include "nm-ip4-config.h"
#include "nm-ip6-config.h"
G_DEFINE_TYPE (NMDnsDnsmasq, nm_dns_dnsmasq, NM_TYPE_DNS_PLUGIN)
#define NM_DNS_DNSMASQ_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqPrivate))
#define PIDFILE LOCALSTATEDIR "/run/nm-dns-dnsmasq.pid"
#define CONFFILE LOCALSTATEDIR "/run/nm-dns-dnsmasq.conf"
typedef struct {
guint32 foo;
} NMDnsDnsmasqPrivate;
/*******************************************/
static inline const char *
find_dnsmasq (void)
{
static const char *paths[] = {
"/usr/local/sbin/dnsmasq",
"/usr/sbin/dnsmasq",
"/sbin/dnsmasq",
NULL
};
const char **binary = paths;
while (*binary != NULL) {
if (g_file_test (*binary, G_FILE_TEST_EXISTS))
return *binary;
binary++;
}
return NULL;
}
static gboolean
add_ip4_config (GString *str, NMIP4Config *ip4, gboolean split)
{
char buf[INET_ADDRSTRLEN + 1];
struct in_addr addr;
int n, i;
gboolean added = FALSE;
if (split) {
/* FIXME: it appears that dnsmasq can only handle one nameserver
* per domain (at the manpage seems to indicate that) so only use
* the first nameserver here.
*/
addr.s_addr = nm_ip4_config_get_nameserver (ip4, 0);
memset (&buf[0], 0, sizeof (buf));
if (!inet_ntop (AF_INET, &addr, buf, sizeof (buf)))
return FALSE;
/* searches are preferred over domains */
n = nm_ip4_config_get_num_searches (ip4);
for (i = 0; i < n; i++) {
g_string_append_printf (str, "server=/%s/%s\n",
nm_ip4_config_get_search (ip4, i),
buf);
added = TRUE;
}
if (n == 0) {
/* If not searches, use any domains */
n = nm_ip4_config_get_num_domains (ip4);
for (i = 0; i < n; i++) {
g_string_append_printf (str, "server=/%s/%s\n",
nm_ip4_config_get_domain (ip4, i),
buf);
added = TRUE;
}
}
}
/* If no searches or domains, just add the namservers */
if (!added) {
n = nm_ip4_config_get_num_nameservers (ip4);
for (i = 0; i < n; i++) {
memset (&buf[0], 0, sizeof (buf));
addr.s_addr = nm_ip4_config_get_nameserver (ip4, i);
if (inet_ntop (AF_INET, &addr, buf, sizeof (buf)))
g_string_append_printf (str, "server=%s\n", buf);
}
}
return TRUE;
}
static gboolean
ip6_addr_to_string (const struct in6_addr *addr, char *buf, size_t buflen)
{
memset (buf, 0, buflen);
/* inet_ntop is probably supposed to do this for us, but it doesn't */
if (IN6_IS_ADDR_V4MAPPED (addr))
return !!inet_ntop (AF_INET, &(addr->s6_addr32[3]), buf, buflen);
return !!inet_ntop (AF_INET6, addr, buf, buflen);
}
static gboolean
add_ip6_config (GString *str, NMIP6Config *ip6, gboolean split)
{
char buf[INET6_ADDRSTRLEN + 1];
const struct in6_addr *addr;
int n, i;
gboolean added = FALSE;
if (split) {
/* FIXME: it appears that dnsmasq can only handle one nameserver
* per domain (at the manpage seems to indicate that) so only use
* the first nameserver here.
*/
addr = nm_ip6_config_get_nameserver (ip6, 0);
if (!ip6_addr_to_string (addr, &buf[0], sizeof (buf)))
return FALSE;
/* searches are preferred over domains */
n = nm_ip6_config_get_num_searches (ip6);
for (i = 0; i < n; i++) {
g_string_append_printf (str, "server=/%s/%s\n",
nm_ip6_config_get_search (ip6, i),
buf);
added = TRUE;
}
if (n == 0) {
/* If not searches, use any domains */
n = nm_ip6_config_get_num_domains (ip6);
for (i = 0; i < n; i++) {
g_string_append_printf (str, "server=/%s/%s\n",
nm_ip6_config_get_domain (ip6, i),
buf);
added = TRUE;
}
}
}
/* If no searches or domains, just add the namservers */
if (!added) {
n = nm_ip6_config_get_num_nameservers (ip6);
for (i = 0; i < n; i++) {
addr = nm_ip6_config_get_nameserver (ip6, i);
if (ip6_addr_to_string (addr, &buf[0], sizeof (buf)))
g_string_append_printf (str, "server=%s\n", buf);
}
}
return TRUE;
}
static gboolean
update (NMDnsPlugin *plugin,
const GSList *vpn_configs,
const GSList *dev_configs,
const GSList *other_configs,
const char *hostname)
{
NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin);
GString *conf;
GSList *iter;
const char *argv[10];
GError *error = NULL;
int ignored;
GPid pid = 0;
/* Kill the old dnsmasq; there doesn't appear to be a way to get dnsmasq
* to reread the config file using SIGHUP or similar. This is a small race
* here when restarting dnsmasq when DNS requests could go to the upstream
* servers instead of to dnsmasq.
*/
nm_dns_plugin_child_kill (plugin);
/* Build up the new dnsmasq config file */
conf = g_string_sized_new (150);
/* Use split DNS for VPN configs */
for (iter = (GSList *) vpn_configs; iter; iter = g_slist_next (iter)) {
if (NM_IS_IP4_CONFIG (iter->data))
add_ip4_config (conf, NM_IP4_CONFIG (iter->data), TRUE);
else if (NM_IS_IP6_CONFIG (iter->data))
add_ip6_config (conf, NM_IP6_CONFIG (iter->data), TRUE);
}
/* Now add interface configs without split DNS */
for (iter = (GSList *) dev_configs; iter;iter = g_slist_next (iter)) {
if (NM_IS_IP4_CONFIG (iter->data))
add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE);
else if (NM_IS_IP6_CONFIG (iter->data))
add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE);
}
/* And any other random configs */
for (iter = (GSList *) other_configs; iter;iter = g_slist_next (iter)) {
if (NM_IS_IP4_CONFIG (iter->data))
add_ip4_config (conf, NM_IP4_CONFIG (iter->data), FALSE);
else if (NM_IS_IP6_CONFIG (iter->data))
add_ip6_config (conf, NM_IP6_CONFIG (iter->data), FALSE);
}
/* Write out the config file */
if (!g_file_set_contents (CONFFILE, conf->str, -1, &error)) {
nm_log_warn (LOGD_DNS, "Failed to write dnsmasq config file %s: (%d) %s",
CONFFILE,
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
goto out;
}
ignored = chmod (CONFFILE, 0600);
nm_log_dbg (LOGD_DNS, "dnsmasq local caching DNS configuration:");
nm_log_dbg (LOGD_DNS, "%s", conf->str);
argv[0] = find_dnsmasq ();
argv[1] = "--no-resolv"; /* Use only commandline */
argv[2] = "--keep-in-foreground";
argv[3] = "--strict-order";
argv[4] = "--bind-interfaces";
argv[5] = "--pid-file=" PIDFILE;
argv[6] = "--listen-address=127.0.0.1"; /* Should work for both 4 and 6 */
argv[7] = "--conf-file=" CONFFILE;
argv[8] = NULL;
/* And finally spawn dnsmasq */
pid = nm_dns_plugin_child_spawn (NM_DNS_PLUGIN (self), argv, PIDFILE, "bin/dnsmasq");
out:
g_string_free (conf, TRUE);
return pid ? TRUE : FALSE;
}
/****************************************************************/
static const char *
dm_exit_code_to_msg (int status)
{
if (status == 1)
return "Configuration problem";
else if (status == 2)
return "Network access problem (address in use; permissions; etc)";
else if (status == 3)
return "Filesystem problem (missing file/directory; permissions; etc)";
else if (status == 4)
return "Memory allocation failure";
else if (status == 5)
return "Other problem";
else if (status >= 11)
return "Lease-script 'init' process failure";
return "Unknown error";
}
static void
child_quit (NMDnsPlugin *plugin, gint status)
{
NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin);
gboolean failed = TRUE;
int err;
if (WIFEXITED (status)) {
err = WEXITSTATUS (status);
if (err) {
nm_log_warn (LOGD_DNS, "dnsmasq exited with error: %s (%d)",
dm_exit_code_to_msg (err),
err);
} else
failed = FALSE;
} else if (WIFSTOPPED (status)) {
nm_log_warn (LOGD_DNS, "dnsmasq stopped unexpectedly with signal %d", WSTOPSIG (status));
} else if (WIFSIGNALED (status)) {
nm_log_warn (LOGD_DNS, "dnsmasq died with signal %d", WTERMSIG (status));
} else {
nm_log_warn (LOGD_DNS, "dnsmasq died from an unknown cause");
}
unlink (CONFFILE);
if (failed)
g_signal_emit_by_name (self, NM_DNS_PLUGIN_FAILED);
}
/****************************************************************/
static gboolean
init (NMDnsPlugin *plugin)
{
return TRUE;
}
static gboolean
is_caching (NMDnsPlugin *plugin)
{
return TRUE;
}
static const char *
get_name (NMDnsPlugin *plugin)
{
return "dnsmasq";
}
/****************************************************************/
NMDnsDnsmasq *
nm_dns_dnsmasq_new (void)
{
return (NMDnsDnsmasq *) g_object_new (NM_TYPE_DNS_DNSMASQ, NULL);
}
static void
nm_dns_dnsmasq_init (NMDnsDnsmasq *self)
{
}
static void
dispose (GObject *object)
{
unlink (CONFFILE);
G_OBJECT_CLASS (nm_dns_dnsmasq_parent_class)->dispose (object);
}
static void
nm_dns_dnsmasq_class_init (NMDnsDnsmasqClass *dns_class)
{
NMDnsPluginClass *plugin_class = NM_DNS_PLUGIN_CLASS (dns_class);
GObjectClass *object_class = G_OBJECT_CLASS (dns_class);
g_type_class_add_private (dns_class, sizeof (NMDnsDnsmasqPrivate));
object_class->dispose = dispose;
plugin_class->init = init;
plugin_class->child_quit = child_quit;
plugin_class->is_caching = is_caching;
plugin_class->update = update;
plugin_class->get_name = get_name;
}

View file

@ -0,0 +1,47 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2010 Red Hat, Inc.
*/
#ifndef NM_DNS_DNSMASQ_H
#define NM_DNS_DNSMASQ_H
#include <glib.h>
#include <glib-object.h>
#include "nm-dns-plugin.h"
#define NM_TYPE_DNS_DNSMASQ (nm_dns_dnsmasq_get_type ())
#define NM_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasq))
#define NM_DNS_DNSMASQ_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqClass))
#define NM_IS_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DNS_DNSMASQ))
#define NM_IS_DNS_DNSMASQ_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_DNS_DNSMASQ))
#define NM_DNS_DNSMASQ_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasqClass))
typedef struct {
NMDnsPlugin parent;
} NMDnsDnsmasq;
typedef struct {
NMDnsPluginClass parent;
} NMDnsDnsmasqClass;
GType nm_dns_dnsmasq_get_type (void);
NMDnsDnsmasq *nm_dns_dnsmasq_new (void);
#endif /* NM_DNS_DNSMASQ_H */

View file

@ -43,6 +43,10 @@
#include "nm-system.h"
#include "NetworkManagerUtils.h"
#include "nm-dns-plugin.h"
#include "nm-dns-dnsmasq.h"
#include "nm-dns-bind.h"
#ifdef HAVE_SELINUX
#include <selinux/selinux.h>
#endif
@ -51,15 +55,12 @@
#define RESOLV_CONF "/etc/resolv.conf"
#endif
#define ADDR_BUF_LEN 50
G_DEFINE_TYPE(NMDnsManager, nm_dns_manager, G_TYPE_OBJECT)
#define NM_DNS_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
NM_TYPE_DNS_MANAGER, \
NMDnsManagerPrivate))
struct NMDnsManagerPrivate {
NMIP4Config *ip4_vpn_config;
NMIP4Config *ip4_device_config;
@ -68,6 +69,16 @@ 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
* associated with a network interface, but sometimes a change isn't
* associated with a network interface (like hostnames).
@ -76,31 +87,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;
@ -270,7 +256,7 @@ dispatch_netconfig (const char *domain,
const char *iface,
GError **error)
{
char *str;
char *str, *tmp;
GPid pid;
gint fd;
int ret;
@ -294,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;
@ -350,6 +334,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,
@ -371,12 +356,10 @@ write_resolv_conf (FILE *f, const char *domain,
g_free (tmp_str);
}
if (nameservers) {
GString *str;
int num;
str = g_string_new ("");
str = g_string_new ("");
num = g_strv_length (nameservers);
if (nameservers) {
int num = g_strv_length (nameservers);
for (i = 0; i < num; i++) {
if (i == 3) {
@ -391,14 +374,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);
@ -536,30 +519,67 @@ 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
rewrite_resolv_conf (NMDnsManager *mgr, const char *iface, GError **error)
update_dns (NMDnsManager *self,
const char *iface,
gboolean no_caching,
GError **error)
{
NMDnsManagerPrivate *priv;
NMResolvConfData rc;
GSList *iter;
GSList *iter, *vpn_configs = NULL, *dev_configs = NULL, *other_configs = NULL;
const char *domain = NULL;
const char *nis_domain = NULL;
char **searches = NULL;
char **nameservers = NULL;
char **nis_servers = NULL;
int num, i, len;
gboolean success = FALSE;
gboolean success = FALSE, caching = FALSE;
g_return_val_if_fail (error != NULL, FALSE);
g_return_val_if_fail (*error == NULL, FALSE);
priv = NM_DNS_MANAGER_GET_PRIVATE (mgr);
priv = NM_DNS_MANAGER_GET_PRIVATE (self);
if (iface) {
g_free (priv->last_iface);
priv->last_iface = g_strdup (iface);
}
/* Update hash with config we're applying */
compute_hash (self, priv->hash);
rc.nameservers = g_ptr_array_new ();
rc.domain = NULL;
rc.searches = g_ptr_array_new ();
@ -640,6 +660,71 @@ 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);
/* If caching was successful, we only send 127.0.0.1 to /etc/resolv.conf
* to ensure that the glibc resolver doesn't try to round-robin nameservers,
* but only uses the local caching nameserver.
*/
if (caching) {
if (nameservers)
g_strfreev (nameservers);
nameservers = g_new0 (char*, 2);
nameservers[0] = g_strdup ("127.0.0.1");
}
#ifdef RESOLVCONF_PATH
success = dispatch_resolvconf (domain, searches, nameservers, iface, error);
#endif
@ -668,6 +753,43 @@ 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);
}
}
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,
@ -698,9 +820,14 @@ 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 (!config_changed (mgr))
return TRUE;
if (!update_dns (mgr, iface, FALSE, &error)) {
nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
return TRUE;
@ -733,10 +860,14 @@ 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 (config_changed (mgr))
return TRUE;
if (!update_dns (mgr, iface, FALSE, &error)) {
nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
return TRUE;
@ -774,9 +905,14 @@ 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 (config_changed (mgr))
return TRUE;
if (!update_dns (mgr, iface, FALSE, &error)) {
nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
return TRUE;
@ -809,10 +945,14 @@ 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 (config_changed (mgr))
return TRUE;
if (!update_dns (mgr, iface, FALSE, &error)) {
nm_log_warn (LOGD_DNS, "could not commit DNS changes: (%d) %s",
error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
return TRUE;
@ -846,12 +986,85 @@ 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);
}
}
static void
load_plugins (NMDnsManager *self, const char **plugins)
{
NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
NMDnsPlugin *plugin;
const char **iter;
gboolean have_caching = FALSE;
if (plugins && *plugins) {
/* Create each configured plugin */
for (iter = plugins; iter && *iter; iter++) {
if (!strcasecmp (*iter, "dnsmasq"))
plugin = NM_DNS_PLUGIN (nm_dns_dnsmasq_new ());
else if (!strcasecmp (*iter, "bind"))
plugin = NM_DNS_PLUGIN (nm_dns_bind_new ());
else {
nm_log_warn (LOGD_DNS, "Unknown DNS plugin '%s'", *iter);\
continue;
}
g_assert (plugin);
/* Only one caching DNS plugin is allowed */
if (nm_dns_plugin_is_caching (plugin)) {
if (have_caching) {
nm_log_warn (LOGD_DNS,
"Ignoring plugin %s; only one caching DNS "
"plugin is allowed.",
*iter);
g_object_unref (plugin);
continue;
}
have_caching = TRUE;
}
nm_log_info (LOGD_DNS, "DNS: loaded plugin %s", nm_dns_plugin_get_name (plugin));
priv->plugins = g_slist_append (priv->plugins, plugin);
g_signal_connect (plugin, NM_DNS_PLUGIN_FAILED,
G_CALLBACK (plugin_failed),
self);
}
} else {
/* Create default plugins */
}
}
/******************************************************************/
NMDnsManager *
nm_dns_manager_get (const char **plugins)
{
static NMDnsManager * singleton = NULL;
if (!singleton) {
singleton = NM_DNS_MANAGER (g_object_new (NM_TYPE_DNS_MANAGER, NULL));
g_assert (singleton);
load_plugins (singleton, plugins);
} else
g_object_ref (singleton);
return singleton;
}
GQuark
nm_dns_manager_error_quark (void)
{
static GQuark quark = 0;
if (!quark)
quark = g_quark_from_static_string ("nm_dns_manager_error");
return quark;
}
static void
nm_dns_manager_init (NMDnsManager *mgr)
@ -868,6 +1081,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);
}

View file

@ -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,

View file

@ -0,0 +1,318 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2010 Red Hat, Inc.
*
*/
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <glib.h>
#include "nm-dns-plugin.h"
#include "nm-logging.h"
typedef struct {
gboolean disposed;
GPid pid;
guint32 watch_id;
char *progname;
char *pidfile;
} NMDnsPluginPrivate;
#define NM_DNS_PLUGIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DNS_PLUGIN, NMDnsPluginPrivate))
G_DEFINE_TYPE_EXTENDED (NMDnsPlugin, nm_dns_plugin, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {})
enum {
FAILED,
CHILD_QUIT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
/********************************************/
gboolean
nm_dns_plugin_update (NMDnsPlugin *self,
const GSList *vpn_configs,
const GSList *dev_configs,
const GSList *other_configs,
const char *hostname)
{
g_return_val_if_fail (NM_DNS_PLUGIN_GET_CLASS (self)->update != NULL, FALSE);
return NM_DNS_PLUGIN_GET_CLASS (self)->update (self,
vpn_configs,
dev_configs,
other_configs,
hostname);
}
static gboolean
is_caching (NMDnsPlugin *self)
{
return FALSE;
}
gboolean
nm_dns_plugin_is_caching (NMDnsPlugin *self)
{
return NM_DNS_PLUGIN_GET_CLASS (self)->is_caching (self);
}
const char *
nm_dns_plugin_get_name (NMDnsPlugin *self)
{
g_assert (NM_DNS_PLUGIN_GET_CLASS (self)->get_name);
return NM_DNS_PLUGIN_GET_CLASS (self)->get_name (self);
}
/********************************************/
static void
kill_existing (const char *progname, const char *pidfile, const char *kill_match)
{
char *contents = NULL;
glong pid;
char *proc_path = NULL;
char *cmdline_contents = NULL;
if (!g_file_get_contents (pidfile, &contents, NULL, NULL))
return;
pid = strtol (contents, NULL, 10);
if (pid < 1 || pid > INT_MAX)
goto out;
proc_path = g_strdup_printf ("/proc/%ld/cmdline", pid);
if (!g_file_get_contents (proc_path, &cmdline_contents, NULL, NULL))
goto out;
if (strstr (cmdline_contents, kill_match)) {
if (kill (pid, 0)) {
nm_log_dbg (LOGD_DNS, "Killing stale %s child process %ld", progname, pid);
kill (pid, SIGKILL);
}
unlink (pidfile);
}
out:
g_free (cmdline_contents);
g_free (proc_path);
g_free (contents);
}
static void
watch_cb (GPid pid, gint status, gpointer user_data)
{
NMDnsPlugin *self = NM_DNS_PLUGIN (user_data);
NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self);
priv->pid = 0;
g_free (priv->progname);
priv->progname = NULL;
g_signal_emit (self, signals[CHILD_QUIT], 0, status);
}
static void
child_setup (gpointer user_data G_GNUC_UNUSED)
{
/* We are in the child process at this point */
pid_t pid = getpid ();
setpgid (pid, pid);
}
GPid
nm_dns_plugin_child_spawn (NMDnsPlugin *self,
const char **argv,
const char *pidfile,
const char *kill_match)
{
NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self);
GError *error = NULL;
char *cmdline;
g_return_val_if_fail (argv != NULL, 0);
g_return_val_if_fail (argv[0] != NULL, 0);
g_warn_if_fail (priv->progname == NULL);
g_free (priv->progname);
priv->progname = g_path_get_basename (argv[0]);
if (pidfile) {
g_return_val_if_fail (kill_match != NULL, 0);
kill_existing (priv->progname, pidfile, kill_match);
g_free (priv->pidfile);
priv->pidfile = g_strdup (pidfile);
}
nm_log_info (LOGD_DNS, "DNS: starting %s...", priv->progname);
cmdline = g_strjoinv (" ", (char **) argv);
nm_log_dbg (LOGD_DNS, "DNS: command line: %s", cmdline);
g_free (cmdline);
priv->pid = 0;
if (g_spawn_async (NULL, (char **) argv, NULL,
G_SPAWN_DO_NOT_REAP_CHILD,
child_setup,
NULL, &priv->pid,
&error)) {
nm_log_dbg (LOGD_DNS, "%s started with pid %d", priv->progname, priv->pid);
priv->watch_id = g_child_watch_add (priv->pid, (GChildWatchFunc) watch_cb, self);
} else {
nm_log_warn (LOGD_DNS, "Failed to spawn %s: (%d) %s",
priv->progname, error ? error->code : -1,
error && error->message ? error->message : "(unknown)");
g_clear_error (&error);
}
return priv->pid;
}
typedef struct {
int pid;
char *progname;
} KillInfo;
static gboolean
ensure_killed (gpointer data)
{
KillInfo *info = data;
if (kill (info->pid, 0) == 0)
kill (info->pid, SIGKILL);
/* ensure the child is reaped */
nm_log_dbg (LOGD_DNS, "waiting for %s pid %d to exit", info->progname, info->pid);
waitpid (info->pid, NULL, 0);
nm_log_dbg (LOGD_DNS, "dnsmasq pid %d cleaned up", info->progname, info->pid);
g_free (info->progname);
g_free (info);
return FALSE;
}
gboolean nm_dns_plugin_child_kill (NMDnsPlugin *self)
{
NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self);
if (priv->watch_id) {
g_source_remove (priv->watch_id);
priv->watch_id = 0;
}
if (priv->pid) {
KillInfo *info;
if (kill (priv->pid, SIGTERM) == 0) {
info = g_malloc0 (sizeof (KillInfo));
info->pid = priv->pid;
info->progname = g_strdup (priv->progname);
g_timeout_add_seconds (2, ensure_killed, info);
} else {
kill (priv->pid, SIGKILL);
/* ensure the child is reaped */
nm_log_dbg (LOGD_DNS, "waiting for %s pid %d to exit", priv->progname, priv->pid);
waitpid (priv->pid, NULL, 0);
nm_log_dbg (LOGD_DNS, "%s pid %d cleaned up", priv->progname, priv->pid);
}
priv->pid = 0;
g_free (priv->progname);
priv->progname = NULL;
}
if (priv->pidfile) {
unlink (priv->pidfile);
g_free (priv->pidfile);
priv->pidfile = NULL;
}
return TRUE;
}
/********************************************/
static void
nm_dns_plugin_init (NMDnsPlugin *self)
{
}
static void
dispose (GObject *object)
{
NMDnsPlugin *self = NM_DNS_PLUGIN (object);
NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self);
if (!priv->disposed) {
priv->disposed = TRUE;
nm_dns_plugin_child_kill (self);
}
G_OBJECT_CLASS (nm_dns_plugin_parent_class)->dispose (object);
}
static void
finalize (GObject *object)
{
NMDnsPlugin *self = NM_DNS_PLUGIN (object);
NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self);
g_free (priv->progname);
g_free (priv->pidfile);
G_OBJECT_CLASS (nm_dns_plugin_parent_class)->finalize (object);
}
static void
nm_dns_plugin_class_init (NMDnsPluginClass *plugin_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (plugin_class);
g_type_class_add_private (plugin_class, sizeof (NMDnsPluginPrivate));
/* virtual methods */
object_class->dispose = dispose;
object_class->finalize = finalize;
plugin_class->is_caching = is_caching;
/* signals */
signals[FAILED] =
g_signal_new (NM_DNS_PLUGIN_FAILED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMDnsPluginClass, failed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[CHILD_QUIT] =
g_signal_new (NM_DNS_PLUGIN_CHILD_QUIT,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMDnsPluginClass, child_quit),
NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1, G_TYPE_INT);
}

View file

@ -0,0 +1,112 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2010 Red Hat, Inc.
*/
#ifndef NM_DNS_PLUGIN_H
#define NM_DNS_PLUGIN_H
#include <glib.h>
#include <glib-object.h>
#define NM_TYPE_DNS_PLUGIN (nm_dns_plugin_get_type ())
#define NM_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DNS_PLUGIN, NMDnsPlugin))
#define NM_DNS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass))
#define NM_IS_DNS_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DNS_PLUGIN))
#define NM_IS_DNS_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), NM_TYPE_DNS_PLUGIN))
#define NM_DNS_PLUGIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DNS_PLUGIN, NMDnsPluginClass))
#define NM_DNS_PLUGIN_FAILED "failed"
#define NM_DNS_PLUGIN_CHILD_QUIT "child-quit"
typedef struct {
GObject parent;
} NMDnsPlugin;
typedef struct {
GObjectClass parent;
/* Methods */
gboolean (*init) (NMDnsPlugin *self);
/* Called when DNS information is changed. 'vpn_configs' is a list of
* NMIP4Config or NMIP6Config objects from VPN connections, while
* 'dev_configs' is a list of NMPI4Config or NMIP6Config objects from
* active devices. 'other_configs' represent other IP configuration that
* may be in-use. Configs of the same IP version are sorted in priority
* order.
*/
gboolean (*update) (NMDnsPlugin *self,
const GSList *vpn_configs,
const GSList *dev_configs,
const GSList *other_configs,
const char *hostname);
/* Subclasses should override and return TRUE if they start a local
* caching nameserver that listens on localhost and would block any
* other local caching nameserver from operating.
*/
gboolean (*is_caching) (NMDnsPlugin *self);
/* Subclasses should override this and return their plugin name */
const char *(*get_name) (NMDnsPlugin *self);
/* Signals */
/* Emitted by the plugin and consumed by NMDnsManager when
* some error happens with the nameserver subprocess. Causes NM to fall
* back to writing out a non-local-caching resolv.conf until the next
* DNS update.
*/
void (*failed) (NMDnsPlugin *self);
/* Emitted by the plugin base class when the nameserver subprocess
* quits. This signal is consumed by the plugin subclasses and not
* by NMDnsManager. If the subclass decides the exit status (as returned
* by waitpid(2)) is fatal it should then emit the 'failed' signal.
*/
void (*child_quit) (NMDnsPlugin *self, gint status);
} NMDnsPluginClass;
GType nm_dns_plugin_get_type (void);
gboolean nm_dns_plugin_is_caching (NMDnsPlugin *self);
const char *nm_dns_plugin_get_name (NMDnsPlugin *self);
gboolean nm_dns_plugin_update (NMDnsPlugin *self,
const GSList *vpn_configs,
const GSList *dev_configs,
const GSList *other_configs,
const char *hostname);
/* For subclasses/plugins */
/* Spawn a child process and watch for it to quit. 'argv' is the NULL-terminated
* argument vector to spawn the child with, where argv[0] is the full path to
* the child's executable. If 'pidfile' is given the process owning the PID
* contained in 'pidfile' will be killed if its command line matches 'kill_match'
* and the pidfile will be deleted.
*/
GPid nm_dns_plugin_child_spawn (NMDnsPlugin *self,
const char **argv,
const char *pidfile,
const char *kill_match);
gboolean nm_dns_plugin_child_kill (NMDnsPlugin *self);
#endif /* NM_DNS_PLUGIN_H */

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);