dns-manager: prevent DNS plugins from respawning too quickly
authorBeniamino Galvani <bgalvani@redhat.com>
Fri, 15 Jan 2016 14:04:39 +0000 (15:04 +0100)
committerBeniamino Galvani <bgalvani@redhat.com>
Thu, 21 Jan 2016 09:40:08 +0000 (10:40 +0100)
If dnsmasq (or another DNS plugin) exits immediately (for example due
to an already used port), the DNS manager keeps restarting it forever,
wasting system resources and filling logs.

Add a simple rate-limiting mechanism.

https://bugzilla.gnome.org/show_bug.cgi?id=760691

src/dns-manager/nm-dns-manager.c
src/dns-manager/nm-dns-plugin.c

index 01e8bf1..20af6d2 100644 (file)
@@ -82,6 +82,10 @@ G_DEFINE_TYPE (NMDnsManager, nm_dns_manager, G_TYPE_OBJECT)
 #define NETCONFIG_PATH "/sbin/netconfig"
 #endif
 
+#define PLUGIN_RATELIMIT_INTERVAL    30
+#define PLUGIN_RATELIMIT_BURST       5
+#define PLUGIN_RATELIMIT_DELAY       300
+
 NM_DEFINE_SINGLETON_INSTANCE (NMDnsManager);
 
 /*********************************************************************************************/
@@ -130,6 +134,12 @@ typedef struct {
        NMConfig *config;
 
        gboolean dns_touched;
+
+       struct {
+               guint64 ts;
+               guint num_restarts;
+               guint timer;
+       } plugin_ratelimit;
 } NMDnsManagerPrivate;
 
 enum {
@@ -799,6 +809,7 @@ update_dns (NMDnsManager *self,
        g_return_val_if_fail (!error || !*error, FALSE);
 
        priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+       nm_clear_g_source (&priv->plugin_ratelimit.timer);
 
        if (priv->resolv_conf_mode == NM_DNS_MANAGER_RESOLV_CONF_UNMANAGED) {
                update = FALSE;
@@ -1022,20 +1033,47 @@ plugin_failed (NMDnsPlugin *plugin, gpointer user_data)
        }
 }
 
-static void
-plugin_child_quit (NMDnsPlugin *plugin, int exit_status, gpointer user_data)
+static gboolean
+plugin_child_quit_update_dns (gpointer user_data)
 {
-       NMDnsManager *self = NM_DNS_MANAGER (user_data);
        GError *error = NULL;
-
-       _LOGW ("plugin %s child quit unexpectedly; refreshing DNS",
-                    nm_dns_plugin_get_name (plugin));
+       NMDnsManager *self = NM_DNS_MANAGER (user_data);
 
        /* Let the plugin try to spawn the child again */
        if (!update_dns (self, FALSE, &error)) {
                _LOGW ("could not commit DNS changes: %s", error->message);
                g_clear_error (&error);
        }
+
+       return G_SOURCE_REMOVE;
+}
+
+static void
+plugin_child_quit (NMDnsPlugin *plugin, int exit_status, gpointer user_data)
+{
+       NMDnsManager *self = NM_DNS_MANAGER (user_data);
+       NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+       gint64 ts = nm_utils_get_monotonic_timestamp_ms ();
+
+       _LOGW ("plugin %s child quit unexpectedly", nm_dns_plugin_get_name (plugin));
+
+       if (   !priv->plugin_ratelimit.ts
+           || (ts - priv->plugin_ratelimit.ts) / 1000 > PLUGIN_RATELIMIT_INTERVAL) {
+               priv->plugin_ratelimit.ts = ts;
+               priv->plugin_ratelimit.num_restarts = 0;
+       } else {
+               priv->plugin_ratelimit.num_restarts++;
+               if (priv->plugin_ratelimit.num_restarts > PLUGIN_RATELIMIT_BURST) {
+                       _LOGW ("plugin %s child respawning too fast, delaying update for %u seconds",
+                               nm_dns_plugin_get_name (plugin), PLUGIN_RATELIMIT_DELAY);
+                       priv->plugin_ratelimit.timer = g_timeout_add_seconds (PLUGIN_RATELIMIT_DELAY,
+                                                                             plugin_child_quit_update_dns,
+                                                                             self);
+                       return;
+               }
+       }
+
+       plugin_child_quit_update_dns (self);
 }
 
 gboolean
index a823669..d473b9d 100644 (file)
@@ -33,7 +33,7 @@ typedef struct {
        gboolean disposed;
 
        GPid pid;
-       guint32 watch_id;
+       guint watch_id;
        char *progname;
        char *pidfile;
 } NMDnsPluginPrivate;
@@ -130,6 +130,7 @@ watch_cb (GPid pid, gint status, gpointer user_data)
        NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self);
 
        priv->pid = 0;
+       priv->watch_id = 0;
        g_free (priv->progname);
        priv->progname = NULL;