summaryrefslogtreecommitdiff
path: root/src/dns-manager/nm-dns-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dns-manager/nm-dns-manager.c')
-rw-r--r--src/dns-manager/nm-dns-manager.c334
1 files changed, 275 insertions, 59 deletions
diff --git a/src/dns-manager/nm-dns-manager.c b/src/dns-manager/nm-dns-manager.c
index 6b4b67543c..77ad9d73eb 100644
--- a/src/dns-manager/nm-dns-manager.c
+++ b/src/dns-manager/nm-dns-manager.c
@@ -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);
}