dns: merge branch 'th/dns-resolv-conf-file-bgo764004'
authorThomas Haller <thaller@redhat.com>
Wed, 23 Mar 2016 08:12:04 +0000 (09:12 +0100)
committerThomas Haller <thaller@redhat.com>
Wed, 23 Mar 2016 08:12:04 +0000 (09:12 +0100)
https://mail.gnome.org/archives/networkmanager-list/2016-March/msg00123.html
https://bugzilla.gnome.org/show_bug.cgi?id=764004

man/NetworkManager.conf.xml.in
src/dns-manager/nm-dns-manager.c
src/dns-manager/nm-dns-manager.h

index cf3421d..2a847f8 100644 (file)
@@ -288,9 +288,13 @@ no-auto-default=*
         <term><varname>rc-manager</varname></term>
         <listitem><para>Set the <filename>resolv.conf</filename>
         management mode. The default value depends on how NetworkManager
-        was built.</para>
-        <para><literal>none</literal>: NetworkManager will directly
-        write changes to <filename>resolv.conf</filename>.</para>
+        was built. Regardless of this setting, NetworkManager will
+        always write resolv.conf to its runtime state directory.</para>
+        <para><literal>none</literal>: NetworkManager will symlink
+        <filename>/etc/resolv.conf</filename> to its private
+        resolv.conf file in the runtime state directory.</para>
+        <para><literal>file</literal>: NetworkManager will write
+        <filename>/etc/resolv.conf</filename> as file.</para>
         <para><literal>resolvconf</literal>: NetworkManager will run
         resolvconf to update the DNS configuration.</para>
         <para><literal>netconfig</literal>: NetworkManager will run
index 79d345b..ec83e60 100644 (file)
@@ -100,14 +100,11 @@ NM_DEFINE_SINGLETON_INSTANCE (NMDnsManager);
             const NMDnsManager *const __self = (self); \
             \
             _nm_log (__level, _NMLOG_DOMAIN, 0, \
-                     "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     ((__self == singleton_instance) \
-                        ? _NMLOG_PREFIX_NAME \
-                        : ({ \
-                                g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-                                __prefix; \
-                           }) \
-                     ) \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     ((!__self || __self == singleton_instance) \
+                        ? "" \
+                        : nm_sprintf_buf (__prefix, "[%p]", __self)) \
                      _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
@@ -128,6 +125,9 @@ typedef struct {
 
        NMDnsManagerResolvConfMode resolv_conf_mode;
        NMDnsManagerResolvConfManager rc_manager;
+       char *last_mode;
+       bool last_immutable:1;
+       bool mode_initialized:1;
        NMDnsPlugin *plugin;
 
        NMConfig *config;
@@ -164,6 +164,15 @@ typedef struct {
        GPtrArray *nis_servers;
 } NMResolvConfData;
 
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConfManager,
+       NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
+       NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE,       "none"),
+       NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE,       "file"),
+       NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF, "resolvconf"),
+       NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,  "netconfig"),
+       NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_DNS_MANAGER_RESOLV_CONF_MAN_INTERNAL_ONLY),
+);
+
 static void
 add_string_item (GPtrArray *array, const char *str)
 {
@@ -406,12 +415,10 @@ dispatch_netconfig (NMDnsManager *self,
        return SR_SUCCESS;
 }
 
-static gboolean
-write_resolv_conf (FILE *f,
-                   char **searches,
-                   char **nameservers,
-                   char **options,
-                   GError **error)
+static char *
+create_resolv_conf (char **searches,
+                    char **nameservers,
+                    char **options)
 {
        gs_free char *searches_str = NULL;
        gs_free char *nameservers_str = NULL;
@@ -439,9 +446,9 @@ write_resolv_conf (FILE *f,
                for (i = 0; i < num; i++) {
                        if (i == 3) {
                                g_string_append (str, "# ");
-                               g_string_append (str, _("NOTE: the libc resolver may not support more than 3 nameservers."));
+                               g_string_append (str, "NOTE: the libc resolver may not support more than 3 nameservers.");
                                g_string_append (str, "\n# ");
-                               g_string_append (str, _("The nameservers listed below may not be recognized."));
+                               g_string_append (str, "The nameservers listed below may not be recognized.");
                                g_string_append_c (str, '\n');
                        }
 
@@ -452,10 +459,18 @@ write_resolv_conf (FILE *f,
                nameservers_str = g_string_free (str, FALSE);
        }
 
-       if (fprintf (f, "# Generated by NetworkManager\n%s%s%s",
-                    searches_str ? searches_str : "",
-                    nameservers_str ? nameservers_str : "",
-                    options_str ? options_str : "") < 0) {
+       return g_strdup_printf ("# Generated by NetworkManager\n%s%s%s",
+                               searches_str ?: "",
+                               nameservers_str ?: "",
+                               options_str ?: "");
+}
+
+static gboolean
+write_resolv_conf_contents (FILE *f,
+                            const char *content,
+                            GError **error)
+{
+       if (fprintf (f, "%s", content) < 0) {
                g_set_error (error,
                             NM_MANAGER_ERROR,
                             NM_MANAGER_ERROR_FAILED,
@@ -467,6 +482,19 @@ write_resolv_conf (FILE *f,
        return TRUE;
 }
 
+static gboolean
+write_resolv_conf (FILE *f,
+                   char **searches,
+                   char **nameservers,
+                   char **options,
+                   GError **error)
+{
+       gs_free char *content = NULL;
+
+       content = create_resolv_conf (searches, nameservers, options);
+       return write_resolv_conf_contents (f, content, error);
+}
+
 static SpawnResult
 dispatch_resolvconf (NMDnsManager *self,
                      char **searches,
@@ -539,18 +567,22 @@ update_resolv_conf (NMDnsManager *self,
                     char **nameservers,
                     char **options,
                     GError **error,
-                    gboolean install_etc)
+                    NMDnsManagerResolvConfManager rc_manager)
 {
        FILE *f;
        struct stat st;
        gboolean success;
+       gs_free char *content = NULL;
+       SpawnResult write_file_result = SR_SUCCESS;
 
        /* If we are not managing /etc/resolv.conf and it points to
         * MY_RESOLV_CONF, don't write the private DNS configuration to
         * MY_RESOLV_CONF otherwise we would overwrite the changes done by
         * some external application.
-        */
-       if (!install_etc) {
+        *
+        * This is the only situation, where we don't try to update our
+        * internal resolv.conf file. */
+       if (rc_manager == _NM_DNS_MANAGER_RESOLV_CONF_MAN_INTERNAL_ONLY) {
                gs_free char *path = g_file_read_link (_PATH_RESCONF, NULL);
 
                if (g_strcmp0 (path, MY_RESOLV_CONF) == 0) {
@@ -560,6 +592,18 @@ update_resolv_conf (NMDnsManager *self,
                }
        }
 
+       content = create_resolv_conf (searches, nameservers, options);
+
+       if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE) {
+               /* we first write to /etc/resolv.conf directly. If that fails,
+                * we still continue to write to runstatedir but remember the
+                * error. */
+               if (!g_file_set_contents (_PATH_RESCONF, content, -1, error)) {
+                       write_file_result = SR_ERROR;
+                       error = NULL;
+               }
+       }
+
        if ((f = fopen (MY_RESOLV_CONF_TMP, "w")) == NULL) {
                g_set_error (error,
                             NM_MANAGER_ERROR,
@@ -570,7 +614,7 @@ update_resolv_conf (NMDnsManager *self,
                return SR_ERROR;
        }
 
-       success = write_resolv_conf (f, searches, nameservers, options, error);
+       success = write_resolv_conf_contents (f, content, error);
 
        if (fclose (f) < 0) {
                if (success) {
@@ -598,7 +642,10 @@ update_resolv_conf (NMDnsManager *self,
                return SR_ERROR;
        }
 
-       if (!install_etc)
+       if (rc_manager == NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE)
+               return write_file_result;
+
+       if (rc_manager != NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE)
                return SR_SUCCESS;
 
        /* A symlink pointing to NM's own resolv.conf (MY_RESOLV_CONF) is always
@@ -920,7 +967,7 @@ update_dns (NMDnsManager *self,
        nis_domain = rc.nis_domain;
 
        /* Let any plugins do their thing first */
-       if (update && priv->plugin) {
+       if (priv->plugin) {
                NMDnsPlugin *plugin = priv->plugin;
                const char *plugin_name = nm_dns_plugin_get_name (plugin);
                GSList *vpn_configs = NULL, *dev_configs = NULL, *other_configs = NULL;
@@ -973,7 +1020,8 @@ update_dns (NMDnsManager *self,
        if (update) {
                switch (priv->rc_manager) {
                case NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE:
-                       result = update_resolv_conf (self, searches, nameservers, options, error, TRUE);
+               case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE:
+                       result = update_resolv_conf (self, searches, nameservers, options, error, priv->rc_manager);
                        resolv_conf_updated = TRUE;
                        break;
                case NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF:
@@ -990,7 +1038,7 @@ update_dns (NMDnsManager *self,
                if (result == SR_NOTFOUND) {
                        _LOGD ("update-dns: program not available, writing to resolv.conf");
                        g_clear_error (error);
-                       result = update_resolv_conf (self, searches, nameservers, options, error, TRUE);
+                       result = update_resolv_conf (self, searches, nameservers, options, error, NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE);
                        resolv_conf_updated = TRUE;
                }
        }
@@ -998,7 +1046,7 @@ update_dns (NMDnsManager *self,
        /* Unless we've already done it, update private resolv.conf in NMRUNDIR
           ignoring any errors */
        if (!resolv_conf_updated)
-               update_resolv_conf (self, searches, nameservers, options, NULL, FALSE);
+               update_resolv_conf (self, searches, nameservers, options, NULL, _NM_DNS_MANAGER_RESOLV_CONF_MAN_INTERNAL_ONLY);
 
        /* signal that resolv.conf was changed */
        if (update && result == SR_SUCCESS)
@@ -1318,70 +1366,105 @@ nm_dns_manager_end_updates (NMDnsManager *self, const char *func)
 
 /******************************************************************/
 
+static bool
+_get_resconf_immutable (int *immutable_cached)
+{
+       int fd, flags;
+       int immutable;
+
+       immutable = *immutable_cached;
+       if (!NM_IN_SET (immutable, FALSE, TRUE)) {
+               immutable = FALSE;
+               fd = open (_PATH_RESCONF, O_RDONLY);
+               if (fd != -1) {
+                       if (ioctl (fd, FS_IOC_GETFLAGS, &flags) != -1)
+                               immutable = NM_FLAGS_HAS (flags, FS_IMMUTABLE_FL);
+                       close (fd);
+               }
+               *immutable_cached = immutable;
+       }
+       return immutable;
+}
+
 NM_DEFINE_SINGLETON_GETTER (NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER);
 
 static void
 init_resolv_conf_mode (NMDnsManager *self)
 {
        NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-       const char *mode;
-       int fd, flags;
-
-       g_clear_object (&priv->plugin);
+       const char *mode, *mode_unknown;
+       int immutable = -1;
 
        mode = nm_config_data_get_dns_mode (nm_config_get_data (priv->config));
-       if (!g_strcmp0 (mode, "none")) {
-               priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_UNMANAGED;
-               goto out;
+
+       if (   priv->mode_initialized
+           && nm_streq0 (mode, priv->last_mode)
+           && (   nm_streq0 (mode, "none")
+               || priv->last_immutable == _get_resconf_immutable (&immutable))) {
+               /* we call init_resolv_conf_mode() on every SIGHUP to possibly reload
+                * when either "mode" or "immutable" changed. However, we don't want to
+                * re-create the plugin, when the paramters didn't actually change. So
+                * detect that we would recreate the same plugin and return early. */
+               return;
        }
 
-       fd = open (_PATH_RESCONF, O_RDONLY);
-       if (fd != -1) {
-               if (ioctl (fd, FS_IOC_GETFLAGS, &flags) == -1)
-                       flags = 0;
-               close (fd);
+       priv->mode_initialized = TRUE;
+       g_free (priv->last_mode);
+       priv->last_mode = g_strdup (mode);
+       priv->last_immutable = FALSE;
+       g_clear_object (&priv->plugin);
+       priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_UNMANAGED;
 
-               if (flags & FS_IMMUTABLE_FL) {
-                       _LOGI ("set resolv-conf-mode: none -- " _PATH_RESCONF " is immutable");
-                       priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_UNMANAGED;
-                       return;
-               }
+       if (nm_streq0 (mode, "none")) {
+               _LOGI ("%s%s", "set resolv-conf-mode: ", "none");
+               return;
        }
 
-       if (!g_strcmp0 (mode, "dnsmasq")) {
-               priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_PROXY;
-               priv->plugin = nm_dns_dnsmasq_new ();
-       } else if (!g_strcmp0 (mode, "unbound")) {
-               priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_PROXY;
-               priv->plugin = nm_dns_unbound_new ();
-       } else {
-               priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_EXPLICIT;
-               if (mode && g_strcmp0 (mode, "default") != 0) {
-                       _LOGW ("set resolve-conf-mode: default -- unknown configuration '%s'", mode);
-                       return;
-               }
-               mode = "default";
-       }
+       priv->last_immutable = _get_resconf_immutable (&immutable);
+
+       if (NM_IN_STRSET (mode, "dnsmasq", "unbound")) {
+               if (!immutable)
+                       priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_PROXY;
+               if (nm_streq (mode, "dnsmasq"))
+                       priv->plugin = nm_dns_dnsmasq_new ();
+               else
+                       priv->plugin = nm_dns_unbound_new ();
 
-       if (priv->plugin) {
                g_signal_connect (priv->plugin, NM_DNS_PLUGIN_FAILED, G_CALLBACK (plugin_failed), self);
                g_signal_connect (priv->plugin, NM_DNS_PLUGIN_CHILD_QUIT, G_CALLBACK (plugin_child_quit), self);
+
+               _NMLOG (immutable ? LOGL_WARN : LOGL_INFO,
+                       "%s%s%s%s%s%s",
+                       "set resolv-conf-mode: ",
+                       immutable ? "none" : mode,
+                       ", plugin=\"", nm_dns_plugin_get_name (priv->plugin), "\"",
+                       immutable ? ", resolv.conf immutable" : "");
+               return;
        }
 
-out:
-       _LOGI ("set resolv-conf-mode: %s%s%s%s", mode,
-              NM_PRINT_FMT_QUOTED (priv->plugin, ", plugin=\"", nm_dns_plugin_get_name (priv->plugin), "\"", ""));
+       if (!immutable)
+               priv->resolv_conf_mode = NM_DNS_MANAGER_RESOLV_CONF_EXPLICIT;
+
+       mode_unknown = mode && !nm_streq (mode, "default") ? mode : NULL;
+       _NMLOG (mode_unknown ? LOGL_WARN : LOGL_INFO,
+               "%s%s%s%s%s%s",
+               "set resolv-conf-mode: ",
+               immutable ? "none" : "default",
+               NM_PRINT_FMT_QUOTED (mode_unknown, " -- unknown configuration '", mode_unknown, "'", ""),
+               immutable ? ", resolv.conf immutable" : "");
 }
 
 static void
 init_resolv_conf_manager (NMDnsManager *self)
 {
        NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-       const char *man, *desc = "";
+       const char *man;
 
        man = nm_config_data_get_rc_manager (nm_config_get_data (priv->config));
        if (!g_strcmp0 (man, "none"))
                priv->rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE;
+       else if (nm_streq0 (man, "file"))
+               priv->rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE;
        else if (!g_strcmp0 (man, "resolvconf"))
                priv->rc_manager = NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF;
        else if (!g_strcmp0 (man, "netconfig"))
@@ -1398,19 +1481,7 @@ init_resolv_conf_manager (NMDnsManager *self)
                        _LOGW ("unknown resolv.conf manager '%s'", man);
        }
 
-       switch (priv->rc_manager) {
-       case NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF:
-               desc = "resolvconf";
-               break;
-       case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG:
-               desc = "netconfig";
-               break;
-       case NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE:
-               desc = "none";
-               break;
-       }
-
-       _LOGI ("using resolv.conf manager '%s'", desc);
+       _LOGI ("using resolv.conf manager '%s'", _rc_manager_to_string (priv->rc_manager));
 }
 
 static void
@@ -1422,8 +1493,15 @@ config_changed_cb (NMConfig *config,
 {
        GError *error = NULL;
 
-       if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_DNS_MODE))
+       if (NM_FLAGS_ANY (changes, NM_CONFIG_CHANGE_DNS_MODE |
+                                  NM_CONFIG_CHANGE_SIGHUP)) {
+               /* reload the resolv-conf mode also on SIGHUP (when DNS_MODE didn't change).
+                * The reason is, that the configuration also depends on whether resolv.conf
+                * is immutable, thus, without the configuration changing, we always want to
+                * re-configure the mode. */
                init_resolv_conf_mode (self);
+       }
+
        if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_RC_MANAGER))
                init_resolv_conf_manager (self);
 
@@ -1474,6 +1552,8 @@ dispose (GObject *object)
                g_clear_object (&priv->plugin);
        }
 
+       g_clear_pointer (&priv->last_mode, g_free);
+
        /* If we're quitting, leave a valid resolv.conf in place, not one
         * pointing to 127.0.0.1 if any plugins were active.  Thus update
         * DNS after disposing of all plugins.  But if we haven't done any
index 7a55f1a..dd5c9e9 100644 (file)
@@ -101,7 +101,13 @@ typedef enum {
 
 /**
  * NMDnsManagerResolvConfManager
- * @NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE: NM directly writes resolv.conf
+ * @_NM_DNS_MANAGER_RESOLV_CONF_MAN_INTERNAL_ONLY: dummy-manager
+ *   to not write resolv.conf at all, only the internal file in
+ *   NM's run state directory.
+ * @NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE: NM writes resolv.conf
+ *   by symlinking it to the run state directory.
+ * @NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE: Like NONE, but instead of symlinking
+ *   resolv.conf, write it as a file.
  * @NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF: NM is managing resolv.conf
      through resolvconf
  * @NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG: NM is managing resolv.conf
@@ -110,7 +116,9 @@ typedef enum {
  * NMDnsManager's management of resolv.conf
  */
 typedef enum {
+       _NM_DNS_MANAGER_RESOLV_CONF_MAN_INTERNAL_ONLY,
        NM_DNS_MANAGER_RESOLV_CONF_MAN_NONE,
+       NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE,
        NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF,
        NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,
 } NMDnsManagerResolvConfManager;