summary refs log tree commit diff
path: root/src/core/nm-policy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/nm-policy.c')
-rw-r--r--src/core/nm-policy.c3022
1 files changed, 3022 insertions, 0 deletions
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
new file mode 100644
index 00000000..db4983f8
--- /dev/null
+++ b/src/core/nm-policy.c
@@ -0,0 +1,3022 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2004 - 2013 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-policy.h"
+
+#include <unistd.h>
+#include <netdb.h>
+
+#include "NetworkManagerUtils.h"
+#include "nm-act-request.h"
+#include "nm-keep-alive.h"
+#include "devices/nm-device.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-connection.h"
+#include "platform/nm-platform.h"
+#include "dns/nm-dns-manager.h"
+#include "vpn/nm-vpn-manager.h"
+#include "nm-auth-utils.h"
+#include "nm-firewall-manager.h"
+#include "nm-dispatcher.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-manager.h"
+#include "settings/nm-settings.h"
+#include "settings/nm-settings-connection.h"
+#include "settings/nm-agent-manager.h"
+#include "nm-dhcp-config.h"
+#include "nm-config.h"
+#include "nm-netns.h"
+#include "nm-hostname-manager.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMPolicy,
+                             PROP_MANAGER,
+                             PROP_SETTINGS,
+                             PROP_DEFAULT_IP4_AC,
+                             PROP_DEFAULT_IP6_AC,
+                             PROP_ACTIVATING_IP4_AC,
+                             PROP_ACTIVATING_IP6_AC, );
+
+typedef struct {
+    NMManager *        manager;
+    NMNetns *          netns;
+    NMFirewallManager *firewall_manager;
+    CList              pending_activation_checks;
+
+    NMAgentManager *agent_mgr;
+
+    GHashTable *devices;
+    GHashTable *pending_active_connections;
+
+    GSList *pending_secondaries;
+
+    NMSettings *settings;
+
+    NMHostnameManager *hostname_manager;
+
+    NMActiveConnection *default_ac4, *activating_ac4;
+    NMActiveConnection *default_ac6, *activating_ac6;
+
+    NMDnsManager *dns_manager;
+    gulong        config_changed_id;
+
+    guint reset_retries_id; /* idle handler for resetting the retries count */
+
+    guint schedule_activate_all_id; /* idle handler for schedule_activate_all(). */
+
+    NMPolicyHostnameMode hostname_mode;
+    char *               orig_hostname; /* hostname at NM start time */
+    char *               cur_hostname;  /* hostname we want to assign */
+    char *
+        last_hostname; /* last hostname NM set (to detect if someone else changed it in the meanwhile) */
+
+    bool changing_hostname : 1; /* hostname set operation in progress */
+    bool dhcp_hostname : 1;     /* current hostname was set from dhcp */
+    bool updating_dns : 1;
+
+    GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */
+} NMPolicyPrivate;
+
+struct _NMPolicy {
+    GObject         parent;
+    NMPolicyPrivate _priv;
+};
+
+struct _NMPolicyClass {
+    GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMPolicy, nm_policy, G_TYPE_OBJECT)
+
+#define NM_POLICY_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMPolicy, NM_IS_POLICY)
+
+static NMPolicy *
+_PRIV_TO_SELF(NMPolicyPrivate *priv)
+{
+    NMPolicy *self;
+
+    nm_assert(priv);
+
+    self = (NMPolicy *) (((char *) priv) - G_STRUCT_OFFSET(NMPolicy, _priv));
+
+    nm_assert(NM_IS_POLICY(self));
+    return self;
+}
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME "policy"
+#undef _NMLOG_ENABLED
+#define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain)))
+#define _NMLOG(level, domain, ...)                                         \
+    G_STMT_START                                                           \
+    {                                                                      \
+        nm_log((level),                                                    \
+               (domain),                                                   \
+               NULL,                                                       \
+               NULL,                                                       \
+               "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                    \
+               _NMLOG_PREFIX_NAME ": " _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                      \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static void      update_system_hostname(NMPolicy *self, const char *msg);
+static void      schedule_activate_all(NMPolicy *self);
+static void      schedule_activate_check(NMPolicy *self, NMDevice *device);
+static NMDevice *get_default_device(NMPolicy *self, int addr_family);
+
+/*****************************************************************************/
+
+static void
+_dns_manager_set_ip_config(NMDnsManager *    dns_manager,
+                           NMIPConfig *      ip_config,
+                           NMDnsIPConfigType ip_config_type,
+                           NMDevice *        device)
+{
+    if (device && nm_device_sys_iface_state_is_external(device)) {
+        nm_dns_manager_set_ip_config(dns_manager, ip_config, NM_DNS_IP_CONFIG_TYPE_REMOVED);
+        return;
+    }
+
+    if (NM_IN_SET(ip_config_type, NM_DNS_IP_CONFIG_TYPE_DEFAULT, NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE)
+        && device
+        && nm_device_get_route_metric_default(nm_device_get_device_type(device))
+               == NM_VPN_ROUTE_METRIC_DEFAULT) {
+        /* some device types are inherently VPN. */
+        ip_config_type = NM_DNS_IP_CONFIG_TYPE_VPN;
+    }
+
+    nm_dns_manager_set_ip_config(dns_manager, ip_config, ip_config_type);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    NMPlatformIP6Address prefix;
+    NMDevice *           device;      /* The requesting ("uplink") device */
+    guint64              next_subnet; /* Cache of the next subnet number to be
+                                       * assigned from this prefix */
+    GHashTable *         subnets;     /* ifindex -> NMPlatformIP6Address */
+} IP6PrefixDelegation;
+
+static void
+_clear_ip6_subnet(gpointer key, gpointer value, gpointer user_data)
+{
+    NMPlatformIP6Address *subnet = value;
+    NMDevice *device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, GPOINTER_TO_INT(key));
+
+    if (device) {
+        /* We can not remove a subnet we already started announcing.
+         * Just un-prefer it. */
+        subnet->preferred = 0;
+        nm_device_use_ip6_subnet(device, subnet);
+    }
+    g_slice_free(NMPlatformIP6Address, subnet);
+}
+
+static void
+clear_ip6_prefix_delegation(gpointer data)
+{
+    IP6PrefixDelegation *delegation = data;
+    char                 sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+    _LOGD(LOGD_IP6,
+          "ipv6-pd: undelegating prefix %s/%d",
+          _nm_utils_inet6_ntop(&delegation->prefix.address, sbuf),
+          delegation->prefix.plen);
+
+    g_hash_table_foreach(delegation->subnets, _clear_ip6_subnet, NULL);
+    g_hash_table_destroy(delegation->subnets);
+}
+
+static void
+expire_ip6_delegations(NMPolicy *self)
+{
+    NMPolicyPrivate *    priv       = NM_POLICY_GET_PRIVATE(self);
+    guint32              now        = nm_utils_get_monotonic_timestamp_sec();
+    IP6PrefixDelegation *delegation = NULL;
+    guint                i;
+
+    for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+        delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
+        if (delegation->prefix.timestamp + delegation->prefix.lifetime < now)
+            g_array_remove_index_fast(priv->ip6_prefix_delegations, i);
+    }
+}
+
+/*
+ * Try to obtain a new subnet for a particular active connection from given
+ * delegated prefix, possibly reusing the existing subnet.
+ * Return value of FALSE indicates no more subnets are available from
+ * this prefix (and other prefix should be used -- and requested if necessary).
+ */
+static gboolean
+ip6_subnet_from_delegation(IP6PrefixDelegation *delegation, NMDevice *device)
+{
+    NMPlatformIP6Address *subnet;
+    int                   ifindex = nm_device_get_ifindex(device);
+    char                  sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+    subnet = g_hash_table_lookup(delegation->subnets, GINT_TO_POINTER(ifindex));
+    if (!subnet) {
+        /* Check for out-of-prefixes condition. */
+        if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) {
+            _LOGD(LOGD_IP6,
+                  "ipv6-pd: no more prefixes in %s/%d",
+                  _nm_utils_inet6_ntop(&delegation->prefix.address, sbuf),
+                  delegation->prefix.plen);
+            return FALSE;
+        }
+
+        /* Allocate a new subnet. */
+        subnet = g_slice_new0(NMPlatformIP6Address);
+        g_hash_table_insert(delegation->subnets, GINT_TO_POINTER(ifindex), subnet);
+
+        subnet->plen = 64;
+        subnet->address.s6_addr32[0] =
+            delegation->prefix.address.s6_addr32[0] | htonl(delegation->next_subnet >> 32);
+        subnet->address.s6_addr32[1] =
+            delegation->prefix.address.s6_addr32[1] | htonl(delegation->next_subnet);
+
+        /* Out subnet pool management is pretty unsophisticated. We only add
+         * the subnets and index them by ifindex. That keeps the implementation
+         * simple and the dead entries make it easy to reuse the same subnet on
+         * subsequent activations. On the other hand they may waste the subnet
+         * space. */
+        delegation->next_subnet++;
+    }
+
+    subnet->timestamp = delegation->prefix.timestamp;
+    subnet->lifetime  = delegation->prefix.lifetime;
+    subnet->preferred = delegation->prefix.preferred;
+
+    _LOGD(LOGD_IP6,
+          "ipv6-pd: %s allocated from a /%d prefix on %s",
+          _nm_utils_inet6_ntop(&subnet->address, sbuf),
+          delegation->prefix.plen,
+          nm_device_get_iface(device));
+
+    nm_device_use_ip6_subnet(device, subnet);
+
+    return TRUE;
+}
+
+/*
+ * Try to obtain a subnet from each prefix delegated to given requesting
+ * ("uplink") device and assign it to the downlink device.
+ * Requests a new prefix if no subnet could be found.
+ */
+static void
+ip6_subnet_from_device(NMPolicy *self, NMDevice *from_device, NMDevice *device)
+{
+    NMPolicyPrivate *    priv          = NM_POLICY_GET_PRIVATE(self);
+    IP6PrefixDelegation *delegation    = NULL;
+    gboolean             got_subnet    = FALSE;
+    guint                have_prefixes = 0;
+    guint                i;
+
+    expire_ip6_delegations(self);
+
+    for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+        delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
+
+        if (delegation->device != from_device)
+            continue;
+
+        if (ip6_subnet_from_delegation(delegation, device))
+            got_subnet = TRUE;
+        have_prefixes++;
+    }
+
+    if (!got_subnet) {
+        _LOGI(LOGD_IP6,
+              "ipv6-pd: none of %u prefixes of %s can be shared on %s",
+              have_prefixes,
+              nm_device_get_iface(from_device),
+              nm_device_get_iface(device));
+        nm_device_request_ip6_prefixes(from_device, have_prefixes + 1);
+    }
+}
+
+static void
+ip6_remove_device_prefix_delegations(NMPolicy *self, NMDevice *device)
+{
+    NMPolicyPrivate *    priv       = NM_POLICY_GET_PRIVATE(self);
+    IP6PrefixDelegation *delegation = NULL;
+    guint                i;
+
+    for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+        delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
+        if (delegation->device == device)
+            g_array_remove_index_fast(priv->ip6_prefix_delegations, i);
+    }
+}
+
+static void
+device_ip6_prefix_delegated(NMDevice *device, NMPlatformIP6Address *prefix, gpointer user_data)
+{
+    NMPolicyPrivate *    priv       = user_data;
+    NMPolicy *           self       = _PRIV_TO_SELF(priv);
+    IP6PrefixDelegation *delegation = NULL;
+    guint                i;
+    const CList *        tmp_list;
+    NMActiveConnection * ac;
+    char                 sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
+    _LOGI(LOGD_IP6,
+          "ipv6-pd: received a prefix %s/%d from %s",
+          _nm_utils_inet6_ntop(&prefix->address, sbuf),
+          prefix->plen,
+          nm_device_get_iface(device));
+
+    expire_ip6_delegations(self);
+
+    for (i = 0; i < priv->ip6_prefix_delegations->len; i++) {
+        /* Look for an already known prefix to update. */
+        delegation = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
+        if (IN6_ARE_ADDR_EQUAL(&delegation->prefix.address, &prefix->address))
+            break;
+    }
+
+    if (i == priv->ip6_prefix_delegations->len) {
+        /* Allocate a delegation for new prefix. */
+        g_array_set_size(priv->ip6_prefix_delegations, i + 1);
+        delegation          = &g_array_index(priv->ip6_prefix_delegations, IP6PrefixDelegation, i);
+        delegation->subnets = g_hash_table_new(nm_direct_hash, NULL);
+        delegation->next_subnet = 0;
+    }
+
+    delegation->device = device;
+    delegation->prefix = *prefix;
+
+    /* The newly activated connections are added to the end of the list,
+     * so traversing it from the end makes it likely for newly
+     * activated connections that have no subnet assigned to be served
+     * first. That is a simple yet fair policy, which is good. */
+    nm_manager_for_each_active_connection_prev (priv->manager, ac, tmp_list) {
+        NMDevice *to_device;
+
+        to_device = nm_active_connection_get_device(ac);
+        if (nm_device_needs_ip6_subnet(to_device))
+            ip6_subnet_from_delegation(delegation, to_device);
+    }
+}
+
+static void
+device_ip6_subnet_needed(NMDevice *device, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    _LOGD(LOGD_IP6, "ipv6-pd: %s needs a subnet", nm_device_get_iface(device));
+
+    if (!priv->default_ac6) {
+        /* We request the prefixes when the default IPv6 device is set. */
+        _LOGI(LOGD_IP6,
+              "ipv6-pd: no device to obtain a subnet to share on %s from",
+              nm_device_get_iface(device));
+        return;
+    }
+    ip6_subnet_from_device(self, get_default_device(self, AF_INET6), device);
+    nm_device_copy_ip6_dns_config(device, get_default_device(self, AF_INET6));
+}
+
+/*****************************************************************************/
+
+static NMDevice *
+get_default_device(NMPolicy *self, int addr_family)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    NMActiveConnection *ac;
+
+    nm_assert_addr_family(addr_family);
+
+    ac = (addr_family == AF_INET) ? priv->default_ac4 : priv->default_ac6;
+
+    return ac ? nm_active_connection_get_device(ac) : NULL;
+}
+
+static NMActiveConnection *
+get_best_active_connection(NMPolicy *self, int addr_family, gboolean fully_activated)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    const CList *       tmp_lst;
+    NMDevice *          device;
+    guint32             best_metric             = G_MAXUINT32;
+    gboolean            best_is_fully_activated = FALSE;
+    NMActiveConnection *best_ac, *prev_ac;
+
+    nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
+
+    /* we prefer the current AC in case of identical metric.
+     * Hence, try that one first. */
+    prev_ac = addr_family == AF_INET ? (fully_activated ? priv->default_ac4 : priv->activating_ac4)
+                                     : (fully_activated ? priv->default_ac6 : priv->activating_ac6);
+    best_ac = NULL;
+
+    nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+        NMDeviceState       state;
+        const NMPObject *   r;
+        NMActiveConnection *ac;
+        NMConnection *      connection;
+        guint32             metric;
+        gboolean            is_fully_activated;
+
+        state = nm_device_get_state(device);
+        if (state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_DEACTIVATING)
+            continue;
+
+        if (nm_device_sys_iface_state_is_external(device))
+            continue;
+
+        r = nm_device_get_best_default_route(device, addr_family);
+        if (r) {
+            /* NOTE: the best route might have rt_source NM_IP_CONFIG_SOURCE_VPN,
+             * which means it was injected by a VPN, not added by device.
+             *
+             * In this case, is it really the best device? Why do we even need the best
+             * device?? */
+            metric             = NMP_OBJECT_CAST_IP_ROUTE(r)->metric;
+            is_fully_activated = TRUE;
+        } else if (!fully_activated && (connection = nm_device_get_applied_connection(device))
+                   && nm_utils_connection_has_default_route(connection, addr_family, NULL)) {
+            metric             = nm_device_get_route_metric(device, addr_family);
+            is_fully_activated = FALSE;
+        } else
+            continue;
+
+        ac = (NMActiveConnection *) nm_device_get_act_request(device);
+        nm_assert(ac);
+
+        if (!best_ac || (!best_is_fully_activated && is_fully_activated)
+            || (metric < best_metric || (metric == best_metric && ac == prev_ac))) {
+            best_ac                 = ac;
+            best_metric             = metric;
+            best_is_fully_activated = is_fully_activated;
+        }
+    }
+
+    if (!fully_activated && best_ac && best_is_fully_activated) {
+        /* There's a best activating AC only if the best device
+         * among all activating and already-activated devices is a
+         * still-activating one. */
+        return NULL;
+    }
+
+    return best_ac;
+}
+
+static gboolean
+all_devices_not_active(NMPolicy *self)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    const CList *    tmp_lst;
+    NMDevice *       device;
+
+    nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+        NMDeviceState state;
+
+        state = nm_device_get_state(device);
+        if (state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_DEACTIVATING) {
+            continue;
+        }
+        return FALSE;
+    }
+    return TRUE;
+}
+
+#define FALLBACK_HOSTNAME4 "localhost.localdomain"
+
+static void
+settings_set_hostname_cb(const char *hostname, gboolean result, gpointer user_data)
+{
+    NMPolicy *       self = NM_POLICY(user_data);
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    int              ret  = 0;
+    int              errsv;
+
+    if (!result) {
+        _LOGT(LOGD_DNS, "set-hostname: hostname set via dbus failed, fallback to \"sethostname\"");
+        ret = sethostname(hostname, strlen(hostname));
+        if (ret != 0) {
+            errsv = errno;
+            _LOGW(LOGD_DNS,
+                  "set-hostname: couldn't set the system hostname to '%s': (%d) %s",
+                  hostname,
+                  errsv,
+                  nm_strerror_native(errsv));
+            if (errsv == EPERM)
+                _LOGW(
+                    LOGD_DNS,
+                    "set-hostname: you should use hostnamed when systemd hardening is in effect!");
+        }
+    }
+
+    priv->changing_hostname = FALSE;
+    if (!ret)
+        nm_dispatcher_call_hostname(NULL, NULL, NULL);
+    g_object_unref(self);
+}
+
+#define HOST_NAME_BUFSIZE (HOST_NAME_MAX + 2)
+
+static char *
+_get_hostname(NMPolicy *self)
+{
+    NMPolicyPrivate *priv     = NM_POLICY_GET_PRIVATE(self);
+    char *           hostname = NULL;
+    int              errsv;
+
+    /* If there is an in-progress hostname change, return
+     * the last hostname set as would be set soon...
+     */
+    if (priv->changing_hostname) {
+        _LOGT(LOGD_DNS, "get-hostname: \"%s\" (last on set)", priv->last_hostname);
+        return g_strdup(priv->last_hostname);
+    }
+
+    /* try to get the hostname via dbus... */
+    if (nm_hostname_manager_get_transient_hostname(priv->hostname_manager, &hostname)) {
+        _LOGT(LOGD_DNS, "get-hostname: \"%s\" (from dbus)", hostname);
+        return hostname;
+    }
+
+    /* ...or retrieve it by yourself */
+    hostname = g_malloc(HOST_NAME_BUFSIZE);
+    if (gethostname(hostname, HOST_NAME_BUFSIZE - 1) != 0) {
+        errsv = errno;
+        _LOGT(LOGD_DNS,
+              "get-hostname: couldn't get the system hostname: (%d) %s",
+              errsv,
+              nm_strerror_native(errsv));
+        g_free(hostname);
+        return NULL;
+    }
+
+    /* the name may be truncated... */
+    hostname[HOST_NAME_BUFSIZE - 1] = '\0';
+    if (strlen(hostname) >= HOST_NAME_BUFSIZE - 1) {
+        _LOGT(LOGD_DNS, "get-hostname: system hostname too long: \"%s\"", hostname);
+        g_free(hostname);
+        return NULL;
+    }
+
+    _LOGT(LOGD_DNS, "get-hostname: \"%s\"", hostname);
+    return hostname;
+}
+
+static void
+_set_hostname(NMPolicy *self, const char *new_hostname, const char *msg)
+{
+    NMPolicyPrivate *priv         = NM_POLICY_GET_PRIVATE(self);
+    gs_free char *   old_hostname = NULL;
+    const char *     name;
+
+    /* The incoming hostname *can* be NULL, which will get translated to
+     * 'localhost.localdomain' or such in the hostname policy code, but we
+     * keep cur_hostname = NULL in the case because we need to know that
+     * there was no valid hostname to start with.
+     */
+
+    /* Update the DNS only if the hostname is actually
+     * going to change.
+     */
+    if (!nm_streq0(priv->cur_hostname, new_hostname)) {
+        g_free(priv->cur_hostname);
+        priv->cur_hostname = g_strdup(new_hostname);
+
+        /* Notify the DNS manager of the hostname change so that the domain part, if
+         * present, can be added to the search list. Set the @updating_dns flag
+         * so that dns_config_changed() doesn't try again to restart DNS lookup.
+         */
+        priv->updating_dns = TRUE;
+        nm_dns_manager_set_hostname(priv->dns_manager,
+                                    priv->cur_hostname,
+                                    all_devices_not_active(self));
+        priv->updating_dns = FALSE;
+    }
+
+    /* Finally, set kernel hostname */
+    if (!new_hostname)
+        name = FALLBACK_HOSTNAME4;
+    else if (!new_hostname[0]) {
+        g_warn_if_reached();
+        name = FALLBACK_HOSTNAME4;
+    } else
+        name = new_hostname;
+
+    /* Don't set the hostname if it isn't actually changing */
+    if ((old_hostname = _get_hostname(self)) && (nm_streq(name, old_hostname))) {
+        _LOGT(LOGD_DNS, "set-hostname: hostname already set to '%s' (%s)", name, msg);
+        return;
+    }
+
+    /* Keep track of the last set hostname */
+    g_free(priv->last_hostname);
+    priv->last_hostname     = g_strdup(name);
+    priv->changing_hostname = TRUE;
+
+    _LOGI(LOGD_DNS, "set-hostname: set hostname to '%s' (%s)", name, msg);
+
+    /* Ask NMSettings to update the transient hostname using its
+     * systemd-hostnamed proxy */
+    nm_hostname_manager_set_transient_hostname(priv->hostname_manager,
+                                               name,
+                                               settings_set_hostname_cb,
+                                               g_object_ref(self));
+}
+
+typedef struct {
+    NMDevice *device;
+    int       priority;
+    bool      from_dhcp : 1;
+    bool      from_dns : 1;
+    bool      IS_IPv4 : 1;
+    bool      is_default : 1;
+} DeviceHostnameInfo;
+
+static int
+device_hostname_info_compare(gconstpointer a, gconstpointer b)
+{
+    const DeviceHostnameInfo *info1 = a;
+    const DeviceHostnameInfo *info2 = b;
+
+    NM_CMP_FIELD(info1, info2, priority);
+    NM_CMP_FIELD_UNSAFE(info2, info1, is_default);
+
+    return 0;
+}
+
+NM_CON_DEFAULT_NOP("hostname.from-dhcp");
+NM_CON_DEFAULT_NOP("hostname.from-dns-lookup");
+NM_CON_DEFAULT_NOP("hostname.only-from-default");
+
+static gboolean
+device_get_hostname_property_boolean(NMDevice *device, const char *name)
+{
+    NMSettingHostname *s_hostname;
+    char               buf[128];
+    int                value;
+    NMTernary          default_value;
+
+    nm_assert(NM_IN_STRSET(name,
+                           NM_SETTING_HOSTNAME_FROM_DHCP,
+                           NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP,
+                           NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT));
+
+    s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME);
+
+    if (s_hostname) {
+        g_object_get(s_hostname, name, &value, NULL);
+        if (NM_IN_SET(value, NM_TERNARY_FALSE, NM_TERNARY_TRUE))
+            return value;
+    }
+
+    if (nm_streq(name, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT))
+        default_value = NM_TERNARY_FALSE;
+    else
+        default_value = NM_TERNARY_TRUE;
+
+    return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                       nm_sprintf_buf(buf, "hostname.%s", name),
+                                                       device,
+                                                       NM_TERNARY_FALSE,
+                                                       NM_TERNARY_TRUE,
+                                                       default_value);
+}
+
+static int
+device_get_hostname_priority(NMDevice *device)
+{
+    NMSettingHostname *s_hostname;
+    int                priority;
+
+    s_hostname = nm_device_get_applied_setting(device, NM_TYPE_SETTING_HOSTNAME);
+    if (s_hostname) {
+        priority = nm_setting_hostname_get_priority(s_hostname);
+        if (priority != 0)
+            return priority;
+    }
+
+    return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                       NM_CON_DEFAULT("hostname.priority"),
+                                                       device,
+                                                       G_MININT,
+                                                       G_MAXINT,
+                                                       100);
+}
+
+static GArray *
+build_device_hostname_infos(NMPolicy *self)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    const CList *       tmp_clist;
+    NMActiveConnection *ac;
+    GArray *            array = NULL;
+
+    nm_manager_for_each_active_connection (priv->manager, ac, tmp_clist) {
+        DeviceHostnameInfo *info;
+        NMDevice *          device;
+        gboolean            only_from_default;
+        gboolean            is_default;
+        int                 IS_IPv4;
+
+        device = nm_active_connection_get_device(ac);
+        if (!device)
+            continue;
+
+        only_from_default =
+            device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT);
+
+        for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+            is_default = (ac == (IS_IPv4 ? priv->default_ac4 : priv->default_ac6));
+            if (only_from_default && !is_default)
+                continue;
+
+            if (!array)
+                array = g_array_sized_new(FALSE, FALSE, sizeof(DeviceHostnameInfo), 4);
+
+            info  = nm_g_array_append_new(array, DeviceHostnameInfo);
+            *info = (DeviceHostnameInfo){
+                .device   = device,
+                .priority = device_get_hostname_priority(device),
+                .from_dhcp =
+                    device_get_hostname_property_boolean(device, NM_SETTING_HOSTNAME_FROM_DHCP),
+                .from_dns =
+                    device_get_hostname_property_boolean(device,
+                                                         NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP),
+                .IS_IPv4    = IS_IPv4,
+                .is_default = is_default,
+            };
+        }
+    }
+
+    if (array && array->len > 1) {
+        const DeviceHostnameInfo *info0;
+        guint                     i;
+
+        g_array_sort(array, device_hostname_info_compare);
+
+        info0 = &g_array_index(array, DeviceHostnameInfo, 0);
+        if (info0->priority < 0) {
+            for (i = 1; i < array->len; i++) {
+                const DeviceHostnameInfo *info = &g_array_index(array, DeviceHostnameInfo, i);
+
+                if (info->priority > info0->priority) {
+                    g_array_set_size(array, i);
+                    break;
+                }
+            }
+        }
+    }
+
+    return array;
+}
+
+static void
+device_dns_lookup_done(NMDevice *device, gpointer user_data)
+{
+    NMPolicy *self = user_data;
+
+    g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
+
+    update_system_hostname(self, "lookup finished");
+}
+
+static void
+update_system_hostname(NMPolicy *self, const char *msg)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    const char *     configured_hostname;
+    gs_free char *   temp_hostname = NULL;
+    const char *     dhcp_hostname, *p;
+    gboolean         external_hostname = FALSE;
+    NMDhcpConfig *   dhcp_config;
+    gs_unref_array GArray *infos = NULL;
+    DeviceHostnameInfo *   info;
+    guint                  i;
+    int                    addr_family;
+
+    g_return_if_fail(self != NULL);
+
+    if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_NONE) {
+        _LOGT(LOGD_DNS, "set-hostname: hostname is unmanaged");
+        return;
+    }
+
+    _LOGT(LOGD_DNS, "set-hostname: updating hostname (%s)", msg);
+
+    /* Check if the hostname was set externally to NM, so that in that case
+     * we can avoid to fallback to the one we got when we started.
+     * Consider "not specific" hostnames as equal. */
+    if ((temp_hostname = _get_hostname(self)) && !nm_streq0(temp_hostname, priv->last_hostname)
+        && (nm_utils_is_specific_hostname(temp_hostname)
+            || nm_utils_is_specific_hostname(priv->last_hostname))) {
+        external_hostname = TRUE;
+        _LOGI(LOGD_DNS,
+              "set-hostname: current hostname was changed outside NetworkManager: '%s'",
+              temp_hostname);
+        priv->dhcp_hostname = FALSE;
+
+        if (!nm_utils_is_specific_hostname(temp_hostname))
+            nm_clear_g_free(&temp_hostname);
+        if (!nm_streq0(temp_hostname, priv->orig_hostname)) {
+            /* Update original (fallback) hostname */
+            g_free(priv->orig_hostname);
+            priv->orig_hostname = g_steal_pointer(&temp_hostname);
+            _LOGT(LOGD_DNS,
+                  "hostname-original: update to %s%s%s",
+                  NM_PRINT_FMT_QUOTE_STRING(priv->orig_hostname));
+        }
+    }
+
+    /* Hostname precedence order:
+     *
+     * 1) a configured hostname (from settings)
+     * 2) automatic hostname from DHCP of eligible interfaces
+     * 3) reverse-DNS lookup of the first address on eligible interfaces
+     * 4) the last hostname set outside NM
+     */
+
+    /* Try a persistent hostname first */
+    configured_hostname = nm_hostname_manager_get_hostname(priv->hostname_manager);
+    if (configured_hostname && nm_utils_is_specific_hostname(configured_hostname)) {
+        _set_hostname(self, configured_hostname, "from system configuration");
+        priv->dhcp_hostname = FALSE;
+        return;
+    }
+
+    infos = build_device_hostname_infos(self);
+
+    if (infos && _LOGT_ENABLED(LOGD_DNS)) {
+        _LOGT(LOGD_DNS, "device hostname info:");
+        for (i = 0; i < infos->len; i++) {
+            info = &g_array_index(infos, DeviceHostnameInfo, i);
+            _LOGT(LOGD_DNS,
+                  "  - prio:%5d ipv%c%s %s %s dev:%s",
+                  info->priority,
+                  info->IS_IPv4 ? '4' : '6',
+                  info->is_default ? " (def)" : "      ",
+                  info->from_dhcp ? "dhcp " : "     ",
+                  info->from_dns ? "dns " : "    ",
+                  nm_device_get_iface(info->device));
+        }
+    }
+
+    for (i = 0; infos && i < infos->len; i++) {
+        info        = &g_array_index(infos, DeviceHostnameInfo, i);
+        addr_family = info->IS_IPv4 ? AF_INET : AF_INET6;
+        g_signal_handlers_disconnect_by_func(info->device, device_dns_lookup_done, self);
+
+        if (info->from_dhcp) {
+            dhcp_config = nm_device_get_dhcp_config(info->device, addr_family);
+            if (dhcp_config) {
+                dhcp_hostname =
+                    nm_dhcp_config_get_option(dhcp_config,
+                                              info->IS_IPv4 ? "host_name" : "fqdn_fqdn");
+                if (dhcp_hostname && dhcp_hostname[0]) {
+                    p = nm_str_skip_leading_spaces(dhcp_hostname);
+                    if (p[0]) {
+                        _set_hostname(self, p, info->IS_IPv4 ? "from DHCPv4" : "from DHCPv6");
+                        priv->dhcp_hostname = TRUE;
+                        return;
+                    }
+                    _LOGW(LOGD_DNS,
+                          "set-hostname: DHCPv%c-provided hostname '%s' looks invalid; "
+                          "ignoring it",
+                          nm_utils_addr_family_to_char(addr_family),
+                          dhcp_hostname);
+                }
+            }
+        }
+
+        if (priv->hostname_mode != NM_POLICY_HOSTNAME_MODE_DHCP) {
+            if (info->from_dns) {
+                const char *result;
+                gboolean    wait = FALSE;
+
+                result = nm_device_get_hostname_from_dns_lookup(info->device, addr_family, &wait);
+                if (result) {
+                    _set_hostname(self, result, "from address lookup");
+                    return;
+                }
+                if (wait) {
+                    g_signal_connect(info->device,
+                                     NM_DEVICE_DNS_LOOKUP_DONE,
+                                     (GCallback) device_dns_lookup_done,
+                                     self);
+                    return;
+                }
+            }
+        }
+    }
+
+    /* If an hostname was set outside NetworkManager keep it */
+    if (external_hostname)
+        return;
+
+    if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_DHCP) {
+        /* In dhcp hostname-mode, the hostname is updated only if it comes from
+         * a DHCP host-name option: if last set was from a host-name option and
+         * we are here than that connection is gone (with its host-name option),
+         * so reset the hostname to the previous value
+         */
+        if (priv->dhcp_hostname) {
+            _set_hostname(self, priv->orig_hostname, "reset dhcp hostname");
+            priv->dhcp_hostname = FALSE;
+        }
+        return;
+    }
+
+    priv->dhcp_hostname = FALSE;
+
+    /* If no automatically-configured hostname, try using the last hostname
+     * set externally to NM
+     */
+    if (priv->orig_hostname) {
+        _set_hostname(self, priv->orig_hostname, "from system startup");
+        return;
+    }
+
+    _set_hostname(self, NULL, "no hostname found");
+}
+
+static void
+update_default_ac(NMPolicy *self, int addr_family, NMActiveConnection *best)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    const CList *       tmp_list;
+    NMActiveConnection *ac;
+
+    /* Clear the 'default[6]' flag on all active connections that aren't the new
+     * default active connection.  We'll set the new default after; this ensures
+     * we don't ever have two marked 'default[6]' simultaneously.
+     */
+    nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+        if (ac != best)
+            nm_active_connection_set_default(ac, addr_family, FALSE);
+    }
+
+    /* Mark new default active connection */
+    if (best)
+        nm_active_connection_set_default(best, addr_family, TRUE);
+}
+
+static gpointer
+get_best_ip_config(NMPolicy *           self,
+                   int                  addr_family,
+                   const char **        out_ip_iface,
+                   NMActiveConnection **out_ac,
+                   NMDevice **          out_device,
+                   NMVpnConnection **   out_vpn)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    gpointer            conf, best_conf = NULL;
+    const CList *       tmp_list;
+    NMActiveConnection *ac;
+    guint64             best_metric = G_MAXUINT64;
+    NMVpnConnection *   best_vpn    = NULL;
+
+    nm_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
+
+    nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+        NMVpnConnection *    candidate;
+        NMVpnConnectionState vpn_state;
+        const NMPObject *    obj;
+        guint32              metric;
+
+        if (!NM_IS_VPN_CONNECTION(ac))
+            continue;
+
+        candidate = NM_VPN_CONNECTION(ac);
+
+        vpn_state = nm_vpn_connection_get_vpn_state(candidate);
+        if (vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED)
+            continue;
+
+        if (addr_family == AF_INET)
+            conf = nm_vpn_connection_get_ip4_config(candidate);
+        else
+            conf = nm_vpn_connection_get_ip6_config(candidate);
+        if (!conf)
+            continue;
+
+        if (addr_family == AF_INET)
+            obj = nm_ip4_config_best_default_route_get(conf);
+        else
+            obj = nm_ip6_config_best_default_route_get(conf);
+        if (!obj)
+            continue;
+
+        metric = NMP_OBJECT_CAST_IPX_ROUTE(obj)->rx.metric;
+        if (metric <= best_metric) {
+            best_metric = metric;
+            best_conf   = conf;
+            best_vpn    = candidate;
+        }
+    }
+
+    if (best_metric != G_MAXUINT64) {
+        NM_SET_OUT(out_device, NULL);
+        NM_SET_OUT(out_vpn, best_vpn);
+        NM_SET_OUT(out_ac, NM_ACTIVE_CONNECTION(best_vpn));
+        NM_SET_OUT(out_ip_iface, nm_vpn_connection_get_ip_iface(best_vpn, TRUE));
+        return best_conf;
+    }
+
+    ac = get_best_active_connection(self, addr_family, TRUE);
+    if (ac) {
+        NMDevice *device = nm_active_connection_get_device(ac);
+
+        nm_assert(device);
+
+        if (addr_family == AF_INET)
+            conf = nm_device_get_ip4_config(device);
+        else
+            conf = nm_device_get_ip6_config(device);
+
+        NM_SET_OUT(out_device, device);
+        NM_SET_OUT(out_vpn, NULL);
+        NM_SET_OUT(out_ac, ac);
+        NM_SET_OUT(out_ip_iface, nm_device_get_ip_iface(device));
+        return conf;
+    }
+
+    NM_SET_OUT(out_device, NULL);
+    NM_SET_OUT(out_vpn, NULL);
+    NM_SET_OUT(out_ac, NULL);
+    NM_SET_OUT(out_ip_iface, NULL);
+    return NULL;
+}
+
+static void
+update_ip4_routing(NMPolicy *self, gboolean force_update)
+{
+    NMPolicyPrivate *   priv     = NM_POLICY_GET_PRIVATE(self);
+    NMDevice *          best     = NULL;
+    NMVpnConnection *   vpn      = NULL;
+    NMActiveConnection *best_ac  = NULL;
+    const char *        ip_iface = NULL;
+    const CList *       tmp_list;
+    NMActiveConnection *ac;
+
+    /* Note that we might have an IPv4 VPN tunneled over an IPv6-only device,
+     * so we can get (vpn != NULL && best == NULL).
+     */
+    if (!get_best_ip_config(self, AF_INET, &ip_iface, &best_ac, &best, &vpn)) {
+        if (nm_clear_g_object(&priv->default_ac4)) {
+            _LOGt(LOGD_DNS, "set-default-ac-4: %p", NULL);
+            _notify(self, PROP_DEFAULT_IP4_AC);
+        }
+        return;
+    }
+    g_assert((best || vpn) && best_ac);
+
+    if (!force_update && best_ac && best_ac == priv->default_ac4)
+        return;
+
+    if (best) {
+        nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+            if (NM_IS_VPN_CONNECTION(ac) && nm_vpn_connection_get_ip4_config(NM_VPN_CONNECTION(ac))
+                && !nm_active_connection_get_device(ac))
+                nm_active_connection_set_device(ac, best);
+        }
+    }
+
+    update_default_ac(self, AF_INET, best_ac);
+
+    if (!nm_g_object_ref_set(&priv->default_ac4, best_ac))
+        return;
+    _LOGt(LOGD_DNS, "set-default-ac-4: %p", priv->default_ac4);
+
+    _LOGI(LOGD_CORE,
+          "set '%s' (%s) as default for IPv4 routing and DNS",
+          nm_connection_get_id(nm_active_connection_get_applied_connection(best_ac)),
+          ip_iface);
+    _notify(self, PROP_DEFAULT_IP4_AC);
+}
+
+static void
+update_ip6_prefix_delegation(NMPolicy *self)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    NMDevice *          device;
+    NMActiveConnection *ac;
+    const CList *       tmp_list;
+
+    /* There's new default IPv6 connection, try to get a prefix for everyone. */
+    nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+        device = nm_active_connection_get_device(ac);
+        if (device && nm_device_needs_ip6_subnet(device))
+            ip6_subnet_from_device(self, get_default_device(self, AF_INET6), device);
+    }
+}
+
+static void
+update_ip6_routing(NMPolicy *self, gboolean force_update)
+{
+    NMPolicyPrivate *   priv     = NM_POLICY_GET_PRIVATE(self);
+    NMDevice *          best     = NULL;
+    NMVpnConnection *   vpn      = NULL;
+    NMActiveConnection *best_ac  = NULL;
+    const char *        ip_iface = NULL;
+    NMActiveConnection *ac;
+    const CList *       tmp_list;
+
+    /* Note that we might have an IPv6 VPN tunneled over an IPv4-only device,
+     * so we can get (vpn != NULL && best == NULL).
+     */
+    if (!get_best_ip_config(self, AF_INET6, &ip_iface, &best_ac, &best, &vpn)) {
+        if (nm_clear_g_object(&priv->default_ac6)) {
+            _LOGt(LOGD_DNS, "set-default-ac-6: %p", NULL);
+            _notify(self, PROP_DEFAULT_IP6_AC);
+        }
+        return;
+    }
+    g_assert((best || vpn) && best_ac);
+
+    if (!force_update && best_ac && best_ac == priv->default_ac6)
+        return;
+
+    if (best) {
+        nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+            if (NM_IS_VPN_CONNECTION(ac) && nm_vpn_connection_get_ip6_config(NM_VPN_CONNECTION(ac))
+                && !nm_active_connection_get_device(ac))
+                nm_active_connection_set_device(ac, best);
+        }
+    }
+
+    update_default_ac(self, AF_INET6, best_ac);
+
+    if (!nm_g_object_ref_set(&priv->default_ac6, best_ac))
+        return;
+    _LOGt(LOGD_DNS, "set-default-ac-6: %p", priv->default_ac6);
+
+    update_ip6_prefix_delegation(self);
+
+    _LOGI(LOGD_CORE,
+          "set '%s' (%s) as default for IPv6 routing and DNS",
+          nm_connection_get_id(nm_active_connection_get_applied_connection(best_ac)),
+          ip_iface);
+    _notify(self, PROP_DEFAULT_IP6_AC);
+}
+
+static void
+update_ip_dns(NMPolicy *self, int addr_family, NMDevice *changed_device)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    gpointer         ip_config;
+    const char *     ip_iface = NULL;
+    NMVpnConnection *vpn      = NULL;
+    NMDevice *       device   = NULL;
+
+    nm_assert_addr_family(addr_family);
+
+    ip_config = get_best_ip_config(self, addr_family, &ip_iface, NULL, &device, &vpn);
+    if (ip_config) {
+        /* Tell the DNS manager this config is preferred by re-adding it with
+         * a different IP config type.
+         */
+        _dns_manager_set_ip_config(NM_POLICY_GET_PRIVATE(self)->dns_manager,
+                                   ip_config,
+                                   (vpn || (device && nm_device_is_vpn(device)))
+                                       ? NM_DNS_IP_CONFIG_TYPE_VPN
+                                       : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE,
+                                   device);
+    }
+
+    if (addr_family == AF_INET6) {
+        NMActiveConnection *ac;
+        const CList *       tmp_list;
+
+        /* Tell devices needing a subnet about the new DNS configuration */
+        nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+            device = nm_active_connection_get_device(ac);
+            if (device && device != changed_device && nm_device_needs_ip6_subnet(device))
+                nm_device_copy_ip6_dns_config(device, get_default_device(self, AF_INET6));
+        }
+    }
+}
+
+static void
+update_routing_and_dns(NMPolicy *self, gboolean force_update, NMDevice *changed_device)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    nm_dns_manager_begin_updates(priv->dns_manager, __func__);
+
+    update_ip_dns(self, AF_INET, changed_device);
+    update_ip_dns(self, AF_INET6, changed_device);
+
+    update_ip4_routing(self, force_update);
+    update_ip6_routing(self, force_update);
+
+    /* Update the system hostname */
+    update_system_hostname(self, "routing and dns");
+
+    nm_dns_manager_end_updates(priv->dns_manager, __func__);
+}
+
+static void
+check_activating_active_connections(NMPolicy *self)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    NMActiveConnection *best4, *best6 = NULL;
+
+    best4 = get_best_active_connection(self, AF_INET, FALSE);
+    best6 = get_best_active_connection(self, AF_INET6, FALSE);
+
+    g_object_freeze_notify(G_OBJECT(self));
+
+    if (nm_g_object_ref_set(&priv->activating_ac4, best4)) {
+        _LOGt(LOGD_DNS, "set-activating-ac-4: %p", priv->activating_ac4);
+        _notify(self, PROP_ACTIVATING_IP4_AC);
+    }
+    if (nm_g_object_ref_set(&priv->activating_ac6, best6)) {
+        _LOGt(LOGD_DNS, "set-activating-ac-6: %p", priv->activating_ac6);
+        _notify(self, PROP_ACTIVATING_IP6_AC);
+    }
+
+    g_object_thaw_notify(G_OBJECT(self));
+}
+
+typedef struct {
+    CList     pending_lst;
+    NMPolicy *policy;
+    NMDevice *device;
+    guint     autoactivate_id;
+} ActivateData;
+
+static void
+activate_data_free(ActivateData *data)
+{
+    nm_device_remove_pending_action(data->device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE);
+    c_list_unlink_stale(&data->pending_lst);
+    nm_clear_g_source(&data->autoactivate_id);
+    g_object_unref(data->device);
+    g_slice_free(ActivateData, data);
+}
+
+static void
+pending_ac_gone(gpointer data, GObject *where_the_object_was)
+{
+    NMPolicy *       self = NM_POLICY(data);
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    /* Active connections should reach the DEACTIVATED state
+     * before disappearing. */
+    nm_assert_not_reached();
+
+    if (g_hash_table_remove(priv->pending_active_connections, where_the_object_was))
+        g_object_unref(self);
+}
+
+static void
+pending_ac_state_changed(NMActiveConnection *ac, guint state, guint reason, NMPolicy *self)
+{
+    NMPolicyPrivate *     priv = NM_POLICY_GET_PRIVATE(self);
+    NMSettingsConnection *con;
+
+    if (state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+        /* The AC is being deactivated before the device had a chance
+         * to move to PREPARE. Schedule a new auto-activation on the
+         * device, but block the current connection to avoid an activation
+         * loop.
+         */
+        if (reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED) {
+            con = nm_active_connection_get_settings_connection(ac);
+            nm_settings_connection_autoconnect_blocked_reason_set(
+                con,
+                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                TRUE);
+            schedule_activate_check(self, nm_active_connection_get_device(ac));
+        }
+
+        /* Cleanup */
+        g_signal_handlers_disconnect_by_func(ac, pending_ac_state_changed, self);
+        if (!g_hash_table_remove(priv->pending_active_connections, ac))
+            nm_assert_not_reached();
+        g_object_weak_unref(G_OBJECT(ac), pending_ac_gone, self);
+        g_object_unref(self);
+    }
+}
+
+static void
+auto_activate_device(NMPolicy *self, NMDevice *device)
+{
+    NMPolicyPrivate *     priv;
+    NMSettingsConnection *best_connection;
+    gs_free char *        specific_object      = NULL;
+    gs_free NMSettingsConnection **connections = NULL;
+    guint                          i, len;
+    gs_free_error GError *error            = NULL;
+    gs_unref_object NMAuthSubject *subject = NULL;
+    NMActiveConnection *           ac;
+
+    nm_assert(NM_IS_POLICY(self));
+    nm_assert(NM_IS_DEVICE(device));
+
+    priv = NM_POLICY_GET_PRIVATE(self);
+
+    // FIXME: if a device is already activating (or activated) with a connection
+    // but another connection now overrides the current one for that device,
+    // deactivate the device and activate the new connection instead of just
+    // bailing if the device is already active
+    if (nm_device_get_act_request(device))
+        return;
+
+    if (!nm_device_autoconnect_allowed(device))
+        return;
+
+    connections = nm_manager_get_activatable_connections(priv->manager, TRUE, TRUE, &len);
+    if (!connections[0])
+        return;
+
+    /* Find the first connection that should be auto-activated */
+    best_connection = NULL;
+    for (i = 0; i < len; i++) {
+        NMSettingsConnection *candidate = connections[i];
+        NMConnection *        cand_conn;
+        NMSettingConnection * s_con;
+        const char *          permission;
+
+        if (nm_settings_connection_autoconnect_is_blocked(candidate))
+            continue;
+
+        cand_conn = nm_settings_connection_get_connection(candidate);
+
+        s_con = nm_connection_get_setting_connection(cand_conn);
+        if (!nm_setting_connection_get_autoconnect(s_con))
+            continue;
+
+        permission = nm_utils_get_shared_wifi_permission(cand_conn);
+        if (permission && !nm_settings_connection_check_permission(candidate, permission))
+            continue;
+
+        if (nm_device_can_auto_connect(device, candidate, &specific_object)) {
+            best_connection = candidate;
+            break;
+        }
+    }
+
+    if (!best_connection)
+        return;
+
+    _LOGI(LOGD_DEVICE,
+          "auto-activating connection '%s' (%s)",
+          nm_settings_connection_get_id(best_connection),
+          nm_settings_connection_get_uuid(best_connection));
+
+    subject = nm_auth_subject_new_internal();
+    ac      = nm_manager_activate_connection(
+        priv->manager,
+        best_connection,
+        NULL,
+        specific_object,
+        device,
+        subject,
+        NM_ACTIVATION_TYPE_MANAGED,
+        NM_ACTIVATION_REASON_AUTOCONNECT,
+        NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
+        &error);
+    if (!ac) {
+        _LOGI(LOGD_DEVICE,
+              "connection '%s' auto-activation failed: %s",
+              nm_settings_connection_get_id(best_connection),
+              error->message);
+        nm_settings_connection_autoconnect_blocked_reason_set(
+            best_connection,
+            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+            TRUE);
+        schedule_activate_check(self, device);
+        return;
+    }
+
+    /* Subscribe to AC state-changed signal to detect when the
+     * activation fails in early stages without changing device
+     * state.
+     */
+    if (g_hash_table_add(priv->pending_active_connections, ac)) {
+        g_signal_connect(ac,
+                         NM_ACTIVE_CONNECTION_STATE_CHANGED,
+                         G_CALLBACK(pending_ac_state_changed),
+                         g_object_ref(self));
+        g_object_weak_ref(G_OBJECT(ac), (GWeakNotify) pending_ac_gone, self);
+    }
+}
+
+static gboolean
+auto_activate_device_cb(gpointer user_data)
+{
+    ActivateData *data = user_data;
+
+    g_assert(data);
+    g_assert(NM_IS_POLICY(data->policy));
+    g_assert(NM_IS_DEVICE(data->device));
+
+    data->autoactivate_id = 0;
+    auto_activate_device(data->policy, data->device);
+    activate_data_free(data);
+    return G_SOURCE_REMOVE;
+}
+
+static ActivateData *
+find_pending_activation(NMPolicy *self, NMDevice *device)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    ActivateData *   data;
+
+    c_list_for_each_entry (data, &priv->pending_activation_checks, pending_lst) {
+        if (data->device == device)
+            return data;
+    }
+    return NULL;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    NMDevice *device;
+    GSList *  secondaries;
+} PendingSecondaryData;
+
+static PendingSecondaryData *
+pending_secondary_data_new(NMDevice *device, GSList *secondaries)
+{
+    PendingSecondaryData *data;
+
+    data              = g_slice_new(PendingSecondaryData);
+    data->device      = g_object_ref(device);
+    data->secondaries = secondaries;
+    return data;
+}
+
+static void
+pending_secondary_data_free(PendingSecondaryData *data)
+{
+    g_object_unref(data->device);
+    g_slist_free_full(data->secondaries, g_object_unref);
+    g_slice_free(PendingSecondaryData, data);
+}
+
+static void
+process_secondaries(NMPolicy *self, NMActiveConnection *active, gboolean connected)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    GSList *         iter, *iter2, *next, *next2;
+
+    /* Loop through devices waiting for secondary connections to activate */
+    for (iter = priv->pending_secondaries; iter; iter = next) {
+        PendingSecondaryData *secondary_data = (PendingSecondaryData *) iter->data;
+        NMDevice *            item_device    = secondary_data->device;
+
+        next = g_slist_next(iter);
+
+        /* Look for 'active' in each device's secondary connections list */
+        for (iter2 = secondary_data->secondaries; iter2; iter2 = next2) {
+            NMActiveConnection *secondary_active = NM_ACTIVE_CONNECTION(iter2->data);
+
+            next2 = g_slist_next(iter2);
+
+            if (active != secondary_active)
+                continue;
+
+            if (connected) {
+                _LOGD(LOGD_DEVICE,
+                      "secondary connection '%s' succeeded; active path '%s'",
+                      nm_active_connection_get_settings_connection_id(active),
+                      nm_dbus_object_get_path(NM_DBUS_OBJECT(active)));
+
+                /* Secondary connection activated */
+                secondary_data->secondaries =
+                    g_slist_remove(secondary_data->secondaries, secondary_active);
+                g_object_unref(secondary_active);
+                if (!secondary_data->secondaries) {
+                    /* No secondary UUID remained -> remove the secondary data item */
+                    priv->pending_secondaries =
+                        g_slist_remove(priv->pending_secondaries, secondary_data);
+                    pending_secondary_data_free(secondary_data);
+                    if (nm_device_get_state(item_device) == NM_DEVICE_STATE_SECONDARIES)
+                        nm_device_state_changed(item_device,
+                                                NM_DEVICE_STATE_ACTIVATED,
+                                                NM_DEVICE_STATE_REASON_NONE);
+                    break;
+                }
+            } else {
+                _LOGD(LOGD_DEVICE,
+                      "secondary connection '%s' failed; active path '%s'",
+                      nm_active_connection_get_settings_connection_id(active),
+                      nm_dbus_object_get_path(NM_DBUS_OBJECT(active)));
+
+                /* Secondary connection failed -> do not watch other connections */
+                priv->pending_secondaries =
+                    g_slist_remove(priv->pending_secondaries, secondary_data);
+                pending_secondary_data_free(secondary_data);
+                if (nm_device_get_state(item_device) == NM_DEVICE_STATE_SECONDARIES
+                    || nm_device_get_state(item_device) == NM_DEVICE_STATE_ACTIVATED)
+                    nm_device_state_changed(item_device,
+                                            NM_DEVICE_STATE_FAILED,
+                                            NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED);
+                break;
+            }
+        }
+    }
+}
+
+static void
+hostname_changed(NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    update_system_hostname(self, "hostname changed");
+}
+
+void
+nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self)
+{
+    NMPolicyPrivate *            priv        = NM_POLICY_GET_PRIVATE(self);
+    NMSettingsConnection *const *connections = NULL;
+    guint                        i;
+
+    _LOGT(LOGD_DEVICE, "unblocking failed OVS interfaces");
+
+    connections = nm_settings_get_connections(priv->settings, NULL);
+    for (i = 0; connections[i]; i++) {
+        NMSettingsConnection *sett_conn  = connections[i];
+        NMConnection *        connection = nm_settings_connection_get_connection(sett_conn);
+
+        if (nm_connection_get_setting_ovs_interface(connection)) {
+            nm_settings_connection_autoconnect_retries_reset(sett_conn);
+            nm_settings_connection_autoconnect_blocked_reason_set(
+                sett_conn,
+                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                FALSE);
+        }
+    }
+}
+
+static gboolean
+reset_autoconnect_all(
+    NMPolicy *self,
+    NMDevice *device, /* if present, only reset connections compatible with @device */
+    gboolean  only_no_secrets)
+{
+    NMPolicyPrivate *            priv        = NM_POLICY_GET_PRIVATE(self);
+    NMSettingsConnection *const *connections = NULL;
+    guint                        i;
+    gboolean                     changed = FALSE;
+
+    _LOGD(LOGD_DEVICE,
+          "re-enabling autoconnect for all connections%s%s%s",
+          device ? " on " : "",
+          device ? nm_device_get_iface(device) : "",
+          only_no_secrets ? " (only clear no-secrets flag)" : "");
+
+    connections = nm_settings_get_connections(priv->settings, NULL);
+    for (i = 0; connections[i]; i++) {
+        NMSettingsConnection *sett_conn = connections[i];
+
+        if (device
+            && !nm_device_check_connection_compatible(
+                device,
+                nm_settings_connection_get_connection(sett_conn),
+                NULL))
+            continue;
+
+        if (only_no_secrets) {
+            /* we only reset the no-secrets blocked flag. */
+            if (nm_settings_connection_autoconnect_blocked_reason_set(
+                    sett_conn,
+                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
+                    FALSE)) {
+                /* maybe the connection is still blocked afterwards for other reasons
+                 * and in the larger picture nothing changed. But it's too complicated
+                 * to find out exactly. Just assume, something changed to be sure. */
+                if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
+                    changed = TRUE;
+            }
+        } else {
+            /* we reset the tries-count and any blocked-reason */
+            if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0)
+                changed = TRUE;
+            nm_settings_connection_autoconnect_retries_reset(sett_conn);
+
+            if (nm_settings_connection_autoconnect_blocked_reason_set(
+                    sett_conn,
+                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL
+                        & ~NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+                    FALSE)) {
+                if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
+                    changed = TRUE;
+            }
+        }
+    }
+    return changed;
+}
+
+static void
+sleeping_changed(NMManager *manager, GParamSpec *pspec, gpointer user_data)
+{
+    NMPolicyPrivate *priv     = user_data;
+    NMPolicy *       self     = _PRIV_TO_SELF(priv);
+    gboolean         sleeping = FALSE, enabled = FALSE;
+
+    g_object_get(G_OBJECT(manager), NM_MANAGER_SLEEPING, &sleeping, NULL);
+    g_object_get(G_OBJECT(manager), NM_MANAGER_NETWORKING_ENABLED, &enabled, NULL);
+
+    /* Reset retries on all connections so they'll checked on wakeup */
+    if (sleeping || !enabled)
+        reset_autoconnect_all(self, NULL, FALSE);
+}
+
+static void
+schedule_activate_check(NMPolicy *self, NMDevice *device)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    ActivateData *      data;
+    NMActiveConnection *ac;
+    const CList *       tmp_list;
+
+    if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP)
+        return;
+
+    if (!nm_device_autoconnect_allowed(device))
+        return;
+
+    if (find_pending_activation(self, device))
+        return;
+
+    nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+        if (nm_active_connection_get_device(ac) == device)
+            return;
+    }
+
+    nm_device_add_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE);
+
+    data                  = g_slice_new0(ActivateData);
+    data->policy          = self;
+    data->device          = g_object_ref(device);
+    data->autoactivate_id = g_idle_add(auto_activate_device_cb, data);
+    c_list_link_tail(&priv->pending_activation_checks, &data->pending_lst);
+}
+
+static gboolean
+reset_connections_retries(gpointer user_data)
+{
+    NMPolicy *                   self        = (NMPolicy *) user_data;
+    NMPolicyPrivate *            priv        = NM_POLICY_GET_PRIVATE(self);
+    NMSettingsConnection *const *connections = NULL;
+    guint                        i;
+    gint32                       con_stamp, min_stamp, now;
+    gboolean                     changed = FALSE;
+
+    priv->reset_retries_id = 0;
+
+    min_stamp   = 0;
+    now         = nm_utils_get_monotonic_timestamp_sec();
+    connections = nm_settings_get_connections(priv->settings, NULL);
+    for (i = 0; connections[i]; i++) {
+        NMSettingsConnection *connection = connections[i];
+
+        con_stamp = nm_settings_connection_autoconnect_retries_blocked_until(connection);
+        if (con_stamp == 0)
+            continue;
+
+        if (con_stamp <= now) {
+            nm_settings_connection_autoconnect_retries_reset(connection);
+            changed = TRUE;
+        } else if (min_stamp == 0 || min_stamp > con_stamp)
+            min_stamp = con_stamp;
+    }
+
+    /* Schedule the handler again if there are some stamps left */
+    if (min_stamp != 0)
+        priv->reset_retries_id =
+            g_timeout_add_seconds(min_stamp - now, reset_connections_retries, self);
+
+    /* If anything changed, try to activate the newly re-enabled connections */
+    if (changed)
+        schedule_activate_all(self);
+
+    return FALSE;
+}
+
+static void
+_connection_autoconnect_retries_set(NMPolicy *self, NMSettingsConnection *connection, int tries)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(connection));
+    nm_assert(tries >= 0);
+
+    nm_settings_connection_autoconnect_retries_set(connection, tries);
+
+    if (tries == 0) {
+        /* Schedule a handler to reset retries count */
+        if (!priv->reset_retries_id) {
+            gint32 retry_time =
+                nm_settings_connection_autoconnect_retries_blocked_until(connection);
+
+            g_warn_if_fail(retry_time != 0);
+            priv->reset_retries_id =
+                g_timeout_add_seconds(MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()),
+                                      reset_connections_retries,
+                                      self);
+        }
+    }
+}
+
+static void
+activate_slave_connections(NMPolicy *self, NMDevice *device)
+{
+    NMPolicyPrivate *            priv = NM_POLICY_GET_PRIVATE(self);
+    const char *                 master_device;
+    const char *                 master_uuid_settings = NULL;
+    const char *                 master_uuid_applied  = NULL;
+    guint                        i;
+    NMActRequest *               req;
+    gboolean                     internal_activation = FALSE;
+    NMSettingsConnection *const *connections;
+    gboolean                     changed;
+
+    master_device = nm_device_get_iface(device);
+    g_assert(master_device);
+
+    req = nm_device_get_act_request(device);
+    if (req) {
+        NMConnection *        connection;
+        NMSettingsConnection *sett_conn;
+        NMAuthSubject *       subject;
+
+        connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req));
+        if (connection)
+            master_uuid_applied = nm_connection_get_uuid(connection);
+
+        sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req));
+        if (sett_conn) {
+            master_uuid_settings = nm_settings_connection_get_uuid(sett_conn);
+            if (nm_streq0(master_uuid_settings, master_uuid_applied))
+                master_uuid_settings = NULL;
+        }
+
+        subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req));
+        internal_activation =
+            subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL);
+    }
+
+    changed     = FALSE;
+    connections = nm_settings_get_connections(priv->settings, NULL);
+    for (i = 0; connections[i]; i++) {
+        NMSettingsConnection *sett_conn = connections[i];
+        NMSettingConnection * s_slave_con;
+        const char *          slave_master;
+
+        s_slave_con =
+            nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn));
+        slave_master = nm_setting_connection_get_master(s_slave_con);
+        if (!slave_master)
+            continue;
+        if (!NM_IN_STRSET(slave_master, master_device, master_uuid_applied, master_uuid_settings))
+            continue;
+
+        if (!internal_activation) {
+            if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0)
+                changed = TRUE;
+            nm_settings_connection_autoconnect_retries_reset(sett_conn);
+        }
+        if (nm_settings_connection_autoconnect_blocked_reason_set(
+                sett_conn,
+                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                FALSE)) {
+            if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
+                changed = TRUE;
+        }
+    }
+
+    if (changed)
+        schedule_activate_all(self);
+}
+
+static gboolean
+activate_secondary_connections(NMPolicy *self, NMConnection *connection, NMDevice *device)
+{
+    NMPolicyPrivate *      priv = NM_POLICY_GET_PRIVATE(self);
+    NMSettingConnection *  s_con;
+    NMActiveConnection *   ac;
+    PendingSecondaryData * secondary_data;
+    GSList *               secondary_ac_list = NULL;
+    GError *               error             = NULL;
+    guint32                i;
+    gboolean               success = TRUE;
+    NMActivationStateFlags initial_state_flags;
+
+    s_con = nm_connection_get_setting_connection(connection);
+    nm_assert(NM_IS_SETTING_CONNECTION(s_con));
+
+    /* we propagate the activation's state flags. */
+    initial_state_flags = nm_device_get_activation_state_flags(device)
+                          & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY;
+
+    for (i = 0; i < nm_setting_connection_get_num_secondaries(s_con); i++) {
+        NMSettingsConnection *sett_conn;
+        const char *          sec_uuid = nm_setting_connection_get_secondary(s_con, i);
+        NMActRequest *        req;
+
+        sett_conn = nm_settings_get_connection_by_uuid(priv->settings, sec_uuid);
+        if (!sett_conn) {
+            _LOGW(LOGD_DEVICE,
+                  "secondary connection '%s' auto-activation failed: The connection doesn't exist.",
+                  sec_uuid);
+            success = FALSE;
+            break;
+        }
+
+        if (!nm_connection_is_type(nm_settings_connection_get_connection(sett_conn),
+                                   NM_SETTING_VPN_SETTING_NAME)) {
+            _LOGW(LOGD_DEVICE,
+                  "secondary connection '%s (%s)' auto-activation failed: The connection is not a "
+                  "VPN.",
+                  nm_settings_connection_get_id(sett_conn),
+                  sec_uuid);
+            success = FALSE;
+            break;
+        }
+
+        req = nm_device_get_act_request(device);
+
+        _LOGD(LOGD_DEVICE,
+              "activating secondary connection '%s (%s)' for base connection '%s (%s)'",
+              nm_settings_connection_get_id(sett_conn),
+              sec_uuid,
+              nm_connection_get_id(connection),
+              nm_connection_get_uuid(connection));
+        ac = nm_manager_activate_connection(
+            priv->manager,
+            sett_conn,
+            NULL,
+            nm_dbus_object_get_path(NM_DBUS_OBJECT(req)),
+            device,
+            nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req)),
+            NM_ACTIVATION_TYPE_MANAGED,
+            nm_active_connection_get_activation_reason(NM_ACTIVE_CONNECTION(req)),
+            initial_state_flags,
+            &error);
+        if (ac)
+            secondary_ac_list = g_slist_append(secondary_ac_list, g_object_ref(ac));
+        else {
+            _LOGW(LOGD_DEVICE,
+                  "secondary connection '%s (%s)' auto-activation failed: (%d) %s",
+                  nm_settings_connection_get_id(sett_conn),
+                  sec_uuid,
+                  error->code,
+                  error->message);
+            g_clear_error(&error);
+            success = FALSE;
+            break;
+        }
+    }
+
+    if (success && secondary_ac_list != NULL) {
+        secondary_data            = pending_secondary_data_new(device, secondary_ac_list);
+        priv->pending_secondaries = g_slist_append(priv->pending_secondaries, secondary_data);
+    } else
+        g_slist_free_full(secondary_ac_list, g_object_unref);
+
+    return success;
+}
+
+static void
+device_state_changed(NMDevice *          device,
+                     NMDeviceState       new_state,
+                     NMDeviceState       old_state,
+                     NMDeviceStateReason reason,
+                     gpointer            user_data)
+{
+    NMPolicyPrivate *     priv = user_data;
+    NMPolicy *            self = _PRIV_TO_SELF(priv);
+    NMActiveConnection *  ac;
+    NMSettingsConnection *sett_conn = nm_device_get_settings_connection(device);
+    NMIP4Config *         ip4_config;
+    NMIP6Config *         ip6_config;
+    NMSettingConnection * s_con = NULL;
+
+    switch (nm_device_state_reason_check(reason)) {
+    case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
+    case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
+    case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
+    case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
+        /* Block autoconnection at settings level if there is any settings-specific
+         * error reported by the modem (e.g. wrong SIM-PIN or wrong APN). Do not block
+         * autoconnection at settings level for errors in the device domain (e.g.
+         * a missing SIM or wrong modem initialization).
+         */
+        if (sett_conn) {
+            nm_settings_connection_autoconnect_blocked_reason_set(
+                sett_conn,
+                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                TRUE);
+        }
+        break;
+    default:
+        break;
+    }
+
+    switch (new_state) {
+    case NM_DEVICE_STATE_FAILED:
+        g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
+
+        /* Mark the connection invalid if it failed during activation so that
+         * it doesn't get automatically chosen over and over and over again.
+         */
+        if (sett_conn && old_state >= NM_DEVICE_STATE_PREPARE
+            && old_state <= NM_DEVICE_STATE_ACTIVATED) {
+            gboolean blocked = FALSE;
+            int      tries;
+            guint64  con_v;
+
+            if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_NO_SECRETS) {
+                /* we want to block the connection from auto-connect if it failed due to no-secrets.
+                 * However, if a secret-agent registered, since the connection made the last
+                 * secret-request, we do not block it. The new secret-agent might not yet
+                 * been consulted, and it may be able to provide the secrets.
+                 *
+                 * We detect this by using a version-id of the agent-manager, which increments
+                 * whenever new agents register. Note that the agent-manager's version-id is
+                 * never zero and strictly increasing.
+                 *
+                 * A connection's version-id of zero means that the connection never tried to request secrets.
+                 * That can happen when nm_settings_connection_get_secrets() fails early without actually
+                 * consulting any agents.
+                 */
+                con_v = nm_settings_connection_get_last_secret_agent_version_id(sett_conn);
+                if (con_v == 0 || con_v == nm_agent_manager_get_agent_version_id(priv->agent_mgr)) {
+                    _LOGD(LOGD_DEVICE,
+                          "connection '%s' now blocked from autoconnect due to no secrets",
+                          nm_settings_connection_get_id(sett_conn));
+                    nm_settings_connection_autoconnect_blocked_reason_set(
+                        sett_conn,
+                        NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
+                        TRUE);
+                    blocked = TRUE;
+                }
+            } else if (nm_device_state_reason_check(reason)
+                       == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) {
+                /* A connection that fails due to dependency-failed is not
+                 * able to reconnect until the master connection activates
+                 * again; when this happens, the master clears the blocked
+                 * reason for all its slaves in activate_slave_connections()
+                 * and tries to reconnect them. For this to work, the slave
+                 * should be marked as blocked when it fails with
+                 * dependency-failed.
+                 */
+                _LOGD(LOGD_DEVICE,
+                      "connection '%s' now blocked from autoconnect due to failed dependency",
+                      nm_settings_connection_get_id(sett_conn));
+                nm_settings_connection_autoconnect_blocked_reason_set(
+                    sett_conn,
+                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                    TRUE);
+                blocked = TRUE;
+            }
+
+            if (!blocked) {
+                tries = nm_settings_connection_autoconnect_retries_get(sett_conn);
+                if (tries > 0) {
+                    _LOGD(LOGD_DEVICE,
+                          "connection '%s' failed to autoconnect; %d tries left",
+                          nm_settings_connection_get_id(sett_conn),
+                          tries - 1);
+                    _connection_autoconnect_retries_set(self, sett_conn, tries - 1);
+                } else if (tries != 0) {
+                    _LOGD(LOGD_DEVICE,
+                          "connection '%s' failed to autoconnect; infinite tries left",
+                          nm_settings_connection_get_id(sett_conn));
+                }
+            }
+
+            nm_settings_connection_clear_secrets(sett_conn, FALSE, FALSE);
+        }
+        break;
+    case NM_DEVICE_STATE_ACTIVATED:
+        if (sett_conn) {
+            /* Reset auto retries back to default since connection was successful */
+            nm_settings_connection_autoconnect_retries_reset(sett_conn);
+
+            /* And clear secrets so they will always be requested from the
+             * settings service when the next connection is made.
+             */
+            nm_settings_connection_clear_secrets(sett_conn, FALSE, FALSE);
+        }
+
+        /* Add device's new IPv4 and IPv6 configs to DNS */
+
+        nm_dns_manager_begin_updates(priv->dns_manager, __func__);
+
+        ip4_config = nm_device_get_ip4_config(device);
+        if (ip4_config)
+            _dns_manager_set_ip_config(priv->dns_manager,
+                                       NM_IP_CONFIG_CAST(ip4_config),
+                                       NM_DNS_IP_CONFIG_TYPE_DEFAULT,
+                                       device);
+        ip6_config = nm_device_get_ip6_config(device);
+        if (ip6_config)
+            _dns_manager_set_ip_config(priv->dns_manager,
+                                       NM_IP_CONFIG_CAST(ip6_config),
+                                       NM_DNS_IP_CONFIG_TYPE_DEFAULT,
+                                       device);
+
+        update_routing_and_dns(self, FALSE, device);
+
+        nm_dns_manager_end_updates(priv->dns_manager, __func__);
+        break;
+    case NM_DEVICE_STATE_UNMANAGED:
+    case NM_DEVICE_STATE_UNAVAILABLE:
+        if (old_state > NM_DEVICE_STATE_DISCONNECTED)
+            update_routing_and_dns(self, FALSE, device);
+        break;
+    case NM_DEVICE_STATE_DEACTIVATING:
+        if (sett_conn) {
+            NMSettingsAutoconnectBlockedReason blocked_reason =
+                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE;
+
+            switch (nm_device_state_reason_check(reason)) {
+            case NM_DEVICE_STATE_REASON_USER_REQUESTED:
+                blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST;
+                break;
+            case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
+                blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED;
+                break;
+            default:
+                break;
+            }
+            if (blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) {
+                _LOGD(LOGD_DEVICE,
+                      "blocking autoconnect of connection '%s': %s",
+                      nm_settings_connection_get_id(sett_conn),
+                      NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str,
+                                            nm_device_state_reason_check(reason)));
+                nm_settings_connection_autoconnect_blocked_reason_set(sett_conn,
+                                                                      blocked_reason,
+                                                                      TRUE);
+            }
+        }
+        ip6_remove_device_prefix_delegations(self, device);
+        break;
+    case NM_DEVICE_STATE_DISCONNECTED:
+        g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
+
+        /* Reset retry counts for a device's connections when carrier on; if cable
+         * was unplugged and plugged in again, we should try to reconnect.
+         */
+        if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_CARRIER
+            && old_state == NM_DEVICE_STATE_UNAVAILABLE)
+            reset_autoconnect_all(self, device, FALSE);
+
+        if (old_state > NM_DEVICE_STATE_DISCONNECTED)
+            update_routing_and_dns(self, FALSE, device);
+
+        /* Device is now available for auto-activation */
+        schedule_activate_check(self, device);
+        break;
+
+    case NM_DEVICE_STATE_PREPARE:
+        /* Reset auto-connect retries of all slaves and schedule them for
+         * activation. */
+        activate_slave_connections(self, device);
+
+        /* Now that the device state is progressing, we don't care
+         * anymore for the AC state. */
+        ac = (NMActiveConnection *) nm_device_get_act_request(device);
+        if (ac && g_hash_table_remove(priv->pending_active_connections, ac)) {
+            g_signal_handlers_disconnect_by_func(ac, pending_ac_state_changed, self);
+            g_object_weak_unref(G_OBJECT(ac), pending_ac_gone, self);
+            g_object_unref(self);
+        }
+        break;
+    case NM_DEVICE_STATE_IP_CONFIG:
+        /* We must have secrets if we got here. */
+        if (sett_conn)
+            nm_settings_connection_autoconnect_blocked_reason_set(
+                sett_conn,
+                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL,
+                FALSE);
+        break;
+    case NM_DEVICE_STATE_SECONDARIES:
+        if (sett_conn)
+            s_con = nm_connection_get_setting_connection(
+                nm_settings_connection_get_connection(sett_conn));
+        if (s_con && nm_setting_connection_get_num_secondaries(s_con) > 0) {
+            /* Make routes and DNS up-to-date before activating dependent connections */
+            update_routing_and_dns(self, FALSE, device);
+
+            /* Activate secondary (VPN) connections */
+            if (!activate_secondary_connections(self,
+                                                nm_settings_connection_get_connection(sett_conn),
+                                                device)) {
+                nm_device_queue_state(device,
+                                      NM_DEVICE_STATE_FAILED,
+                                      NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED);
+            }
+        } else
+            nm_device_queue_state(device, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE);
+        break;
+
+    default:
+        break;
+    }
+
+    check_activating_active_connections(self);
+}
+
+static void
+device_ip_config_changed(NMDevice *  device,
+                         NMIPConfig *new_config,
+                         NMIPConfig *old_config,
+                         gpointer    user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+    int              addr_family;
+
+    nm_assert(new_config || old_config);
+    nm_assert(!new_config || NM_IS_IP_CONFIG(new_config));
+    nm_assert(!old_config || NM_IS_IP_CONFIG(old_config));
+
+    if (new_config) {
+        addr_family = nm_ip_config_get_addr_family(new_config);
+        nm_assert(!old_config || addr_family == nm_ip_config_get_addr_family(old_config));
+    } else
+        addr_family = nm_ip_config_get_addr_family(old_config);
+
+    nm_dns_manager_begin_updates(priv->dns_manager, __func__);
+
+    /* We catch already all the IP events registering on the device state changes but
+     * the ones where the IP changes but the device state keep stable (i.e., activated):
+     * ignore IP config changes but when the device is in activated state.
+     * Prevents unnecessary changes to DNS information.
+     */
+    if (nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) {
+        if (old_config != new_config) {
+            if (new_config)
+                _dns_manager_set_ip_config(priv->dns_manager,
+                                           new_config,
+                                           NM_DNS_IP_CONFIG_TYPE_DEFAULT,
+                                           device);
+            if (old_config)
+                nm_dns_manager_set_ip_config(priv->dns_manager,
+                                             old_config,
+                                             NM_DNS_IP_CONFIG_TYPE_REMOVED);
+        }
+        update_ip_dns(self, addr_family, device);
+        if (addr_family == AF_INET)
+            update_ip4_routing(self, TRUE);
+        else
+            update_ip6_routing(self, TRUE);
+        update_system_hostname(self, addr_family == AF_INET ? "ip4 conf" : "ip6 conf");
+    } else {
+        /* Old configs get removed immediately */
+        if (old_config)
+            nm_dns_manager_set_ip_config(priv->dns_manager,
+                                         old_config,
+                                         NM_DNS_IP_CONFIG_TYPE_REMOVED);
+    }
+
+    nm_dns_manager_end_updates(priv->dns_manager, __func__);
+}
+
+/*****************************************************************************/
+
+static void
+device_autoconnect_changed(NMDevice *device, GParamSpec *pspec, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    schedule_activate_check(self, device);
+}
+
+static void
+device_recheck_auto_activate(NMDevice *device, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    schedule_activate_check(self, device);
+}
+
+static void
+devices_list_unregister(NMPolicy *self, NMDevice *device)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    g_signal_handlers_disconnect_by_data((GObject *) device, priv);
+}
+
+static void
+devices_list_register(NMPolicy *self, NMDevice *device)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    /* Connect state-changed with _after, so that the handler is invoked after other handlers. */
+    g_signal_connect_after(device, NM_DEVICE_STATE_CHANGED, (GCallback) device_state_changed, priv);
+    g_signal_connect(device,
+                     NM_DEVICE_IP4_CONFIG_CHANGED,
+                     (GCallback) device_ip_config_changed,
+                     priv);
+    g_signal_connect(device,
+                     NM_DEVICE_IP6_CONFIG_CHANGED,
+                     (GCallback) device_ip_config_changed,
+                     priv);
+    g_signal_connect(device,
+                     NM_DEVICE_IP6_PREFIX_DELEGATED,
+                     (GCallback) device_ip6_prefix_delegated,
+                     priv);
+    g_signal_connect(device,
+                     NM_DEVICE_IP6_SUBNET_NEEDED,
+                     (GCallback) device_ip6_subnet_needed,
+                     priv);
+    g_signal_connect(device,
+                     "notify::" NM_DEVICE_AUTOCONNECT,
+                     (GCallback) device_autoconnect_changed,
+                     priv);
+    g_signal_connect(device,
+                     NM_DEVICE_RECHECK_AUTO_ACTIVATE,
+                     (GCallback) device_recheck_auto_activate,
+                     priv);
+}
+
+static void
+device_added(NMManager *manager, NMDevice *device, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    g_return_if_fail(NM_IS_POLICY(self));
+
+    priv = NM_POLICY_GET_PRIVATE(self);
+
+    if (!g_hash_table_add(priv->devices, device))
+        g_return_if_reached();
+
+    devices_list_register(self, device);
+}
+
+static void
+device_removed(NMManager *manager, NMDevice *device, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+    ActivateData *   data;
+
+    /* TODO: is this needed? The delegations are cleaned up
+     * on transition to deactivated too. */
+    ip6_remove_device_prefix_delegations(self, device);
+
+    /* Clear any idle callbacks for this device */
+    data = find_pending_activation(self, device);
+    if (data && data->autoactivate_id)
+        activate_data_free(data);
+
+    if (g_hash_table_remove(priv->devices, device))
+        devices_list_unregister(self, device);
+
+    /* Don't update routing and DNS here as we've already handled that
+     * for devices that need it when the device's state changed to UNMANAGED.
+     */
+}
+
+/*****************************************************************************/
+
+static void
+vpn_connection_activated(NMPolicy *self, NMVpnConnection *vpn)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    NMIP4Config *    ip4_config;
+    NMIP6Config *    ip6_config;
+
+    nm_dns_manager_begin_updates(priv->dns_manager, __func__);
+
+    ip4_config = nm_vpn_connection_get_ip4_config(vpn);
+    if (ip4_config)
+        nm_dns_manager_set_ip_config(priv->dns_manager,
+                                     NM_IP_CONFIG_CAST(ip4_config),
+                                     NM_DNS_IP_CONFIG_TYPE_VPN);
+
+    ip6_config = nm_vpn_connection_get_ip6_config(vpn);
+    if (ip6_config)
+        nm_dns_manager_set_ip_config(priv->dns_manager,
+                                     NM_IP_CONFIG_CAST(ip6_config),
+                                     NM_DNS_IP_CONFIG_TYPE_VPN);
+
+    update_routing_and_dns(self, TRUE, NULL);
+
+    nm_dns_manager_end_updates(priv->dns_manager, __func__);
+}
+
+static void
+vpn_connection_deactivated(NMPolicy *self, NMVpnConnection *vpn)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    NMIP4Config *    ip4_config;
+    NMIP6Config *    ip6_config;
+
+    nm_dns_manager_begin_updates(priv->dns_manager, __func__);
+
+    ip4_config = nm_vpn_connection_get_ip4_config(vpn);
+    if (ip4_config)
+        nm_dns_manager_set_ip_config(priv->dns_manager,
+                                     NM_IP_CONFIG_CAST(ip4_config),
+                                     NM_DNS_IP_CONFIG_TYPE_REMOVED);
+
+    ip6_config = nm_vpn_connection_get_ip6_config(vpn);
+    if (ip6_config)
+        nm_dns_manager_set_ip_config(priv->dns_manager,
+                                     NM_IP_CONFIG_CAST(ip6_config),
+                                     NM_DNS_IP_CONFIG_TYPE_REMOVED);
+
+    update_routing_and_dns(self, TRUE, NULL);
+
+    nm_dns_manager_end_updates(priv->dns_manager, __func__);
+}
+
+static void
+vpn_connection_state_changed(NMVpnConnection *             vpn,
+                             NMVpnConnectionState          new_state,
+                             NMVpnConnectionState          old_state,
+                             NMActiveConnectionStateReason reason,
+                             NMPolicy *                    self)
+{
+    if (new_state == NM_VPN_CONNECTION_STATE_ACTIVATED)
+        vpn_connection_activated(self, vpn);
+    else if (new_state >= NM_VPN_CONNECTION_STATE_FAILED) {
+        /* Only clean up IP/DNS if the connection ever got past IP_CONFIG */
+        if (old_state >= NM_VPN_CONNECTION_STATE_IP_CONFIG_GET
+            && old_state <= NM_VPN_CONNECTION_STATE_ACTIVATED)
+            vpn_connection_deactivated(self, vpn);
+    }
+}
+
+static void
+vpn_connection_retry_after_failure(NMVpnConnection *vpn, NMPolicy *self)
+{
+    NMPolicyPrivate *     priv       = NM_POLICY_GET_PRIVATE(self);
+    NMActiveConnection *  ac         = NM_ACTIVE_CONNECTION(vpn);
+    NMSettingsConnection *connection = nm_active_connection_get_settings_connection(ac);
+    GError *              error      = NULL;
+
+    /* Attempt to reconnect VPN connections that failed after being connected */
+    if (!nm_manager_activate_connection(
+            priv->manager,
+            connection,
+            NULL,
+            NULL,
+            NULL,
+            nm_active_connection_get_subject(ac),
+            NM_ACTIVATION_TYPE_MANAGED,
+            nm_active_connection_get_activation_reason(ac),
+            (nm_active_connection_get_state_flags(ac)
+             & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY),
+            &error)) {
+        _LOGW(LOGD_DEVICE,
+              "VPN '%s' reconnect failed: %s",
+              nm_settings_connection_get_id(connection),
+              error->message ?: "unknown");
+        g_clear_error(&error);
+    }
+}
+
+static void
+active_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMPolicy *self)
+{
+    NMActiveConnectionState state = nm_active_connection_get_state(active);
+
+    if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
+        process_secondaries(self, active, TRUE);
+    else if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
+        process_secondaries(self, active, FALSE);
+}
+
+static void
+active_connection_keep_alive_changed(NMKeepAlive *keep_alive, GParamSpec *pspec, NMPolicy *self)
+{
+    NMPolicyPrivate *   priv;
+    NMActiveConnection *ac;
+    GError *            error = NULL;
+
+    nm_assert(NM_IS_POLICY(self));
+    nm_assert(NM_IS_KEEP_ALIVE(keep_alive));
+    nm_assert(NM_IS_ACTIVE_CONNECTION(nm_keep_alive_get_owner(keep_alive)));
+
+    if (nm_keep_alive_is_alive(keep_alive))
+        return;
+
+    ac = nm_keep_alive_get_owner(keep_alive);
+
+    if (nm_active_connection_get_state(ac) > NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
+        return;
+
+    priv = NM_POLICY_GET_PRIVATE(self);
+
+    if (!nm_manager_deactivate_connection(priv->manager,
+                                          ac,
+                                          NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
+                                          &error)) {
+        _LOGW(LOGD_DEVICE,
+              "connection '%s' is no longer kept alive, but error deactivating it: %s",
+              nm_active_connection_get_settings_connection_id(ac),
+              error->message);
+        g_clear_error(&error);
+    }
+}
+
+static void
+active_connection_added(NMManager *manager, NMActiveConnection *active, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+    NMKeepAlive *    keep_alive;
+
+    if (NM_IS_VPN_CONNECTION(active)) {
+        g_signal_connect(active,
+                         NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED,
+                         G_CALLBACK(vpn_connection_state_changed),
+                         self);
+        g_signal_connect(active,
+                         NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE,
+                         G_CALLBACK(vpn_connection_retry_after_failure),
+                         self);
+    }
+
+    keep_alive = nm_active_connection_get_keep_alive(active);
+
+    nm_keep_alive_arm(keep_alive);
+
+    g_signal_connect(active,
+                     "notify::" NM_ACTIVE_CONNECTION_STATE,
+                     G_CALLBACK(active_connection_state_changed),
+                     self);
+    g_signal_connect(keep_alive,
+                     "notify::" NM_KEEP_ALIVE_ALIVE,
+                     G_CALLBACK(active_connection_keep_alive_changed),
+                     self);
+    active_connection_keep_alive_changed(keep_alive, NULL, self);
+}
+
+static void
+active_connection_removed(NMManager *manager, NMActiveConnection *active, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    g_signal_handlers_disconnect_by_func(active, vpn_connection_state_changed, self);
+    g_signal_handlers_disconnect_by_func(active, vpn_connection_retry_after_failure, self);
+    g_signal_handlers_disconnect_by_func(active, active_connection_state_changed, self);
+    g_signal_handlers_disconnect_by_func(nm_active_connection_get_keep_alive(active),
+                                         active_connection_keep_alive_changed,
+                                         self);
+}
+
+/*****************************************************************************/
+
+static gboolean
+schedule_activate_all_cb(gpointer user_data)
+{
+    NMPolicy *       self = user_data;
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    const CList *    tmp_lst;
+    NMDevice *       device;
+
+    priv->schedule_activate_all_id = 0;
+
+    nm_manager_for_each_device (priv->manager, device, tmp_lst)
+        schedule_activate_check(self, device);
+
+    return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_activate_all(NMPolicy *self)
+{
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    /* always restart the idle handler. That way, we settle
+     * all other events before restarting to activate them. */
+    nm_clear_g_source(&priv->schedule_activate_all_id);
+    priv->schedule_activate_all_id = g_idle_add(schedule_activate_all_cb, self);
+}
+
+static void
+connection_added(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    schedule_activate_all(self);
+}
+
+static void
+firewall_state_changed(NMFirewallManager *manager, gboolean initialized_now, gpointer user_data)
+{
+    NMPolicy *       self = (NMPolicy *) user_data;
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    const CList *    tmp_lst;
+    NMDevice *       device;
+
+    if (initialized_now) {
+        /* the firewall manager was initializing, but all requests
+         * so fare were queued and are already sent. No need to
+         * re-update the firewall zone of the devices. */
+        return;
+    }
+
+    if (!nm_firewall_manager_get_running(manager))
+        return;
+
+    /* add interface of each device to correct zone */
+    nm_manager_for_each_device (priv->manager, device, tmp_lst)
+        nm_device_update_firewall_zone(device);
+}
+
+static void
+dns_config_changed(NMDnsManager *dns_manager, gpointer user_data)
+{
+    NMPolicy *       self = (NMPolicy *) user_data;
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    NMDevice *       device;
+    const CList *    tmp_lst;
+
+    /* We are currently updating the hostname in the DNS manager.
+     * This doesn't warrant a new DNS lookup.*/
+    if (priv->updating_dns)
+        return;
+
+    nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+        nm_device_clear_dns_lookup_data(device);
+    }
+
+    update_system_hostname(self, "DNS configuration changed");
+}
+
+static void
+connection_updated(NMSettings *          settings,
+                   NMSettingsConnection *connection,
+                   guint                 update_reason_u,
+                   gpointer              user_data)
+{
+    NMPolicyPrivate *                priv          = user_data;
+    NMPolicy *                       self          = _PRIV_TO_SELF(priv);
+    NMSettingsConnectionUpdateReason update_reason = update_reason_u;
+
+    if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)) {
+        const CList *tmp_lst;
+        NMDevice *   device;
+
+        /* find device with given connection */
+        nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+            if (nm_device_get_settings_connection(device) == connection)
+                nm_device_reapply_settings_immediately(device);
+        }
+    }
+
+    schedule_activate_all(self);
+}
+
+static void
+_deactivate_if_active(NMPolicy *self, NMSettingsConnection *connection)
+{
+    NMPolicyPrivate *   priv = NM_POLICY_GET_PRIVATE(self);
+    NMActiveConnection *ac;
+    const CList *       tmp_list, *tmp_safe;
+    GError *            error = NULL;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(connection));
+
+    nm_manager_for_each_active_connection_safe (priv->manager, ac, tmp_list, tmp_safe) {
+        if (nm_active_connection_get_settings_connection(ac) == connection
+            && (nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
+            if (!nm_manager_deactivate_connection(priv->manager,
+                                                  ac,
+                                                  NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
+                                                  &error)) {
+                _LOGW(LOGD_DEVICE,
+                      "connection '%s' disappeared, but error deactivating it: (%d) %s",
+                      nm_settings_connection_get_id(connection),
+                      error ? error->code : -1,
+                      error ? error->message : "(unknown)");
+                g_clear_error(&error);
+            }
+        }
+    }
+}
+
+static void
+connection_removed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    _deactivate_if_active(self, connection);
+}
+
+static void
+connection_flags_changed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data)
+{
+    NMPolicyPrivate *priv = user_data;
+    NMPolicy *       self = _PRIV_TO_SELF(priv);
+
+    if (NM_FLAGS_HAS(nm_settings_connection_get_flags(connection),
+                     NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) {
+        if (!nm_settings_connection_autoconnect_is_blocked(connection))
+            schedule_activate_all(self);
+    }
+}
+
+static void
+secret_agent_registered(NMSettings *settings, NMSecretAgent *agent, gpointer user_data)
+{
+    NMPolicy *self = NM_POLICY(user_data);
+
+    /* The registered secret agent may provide some missing secrets. Thus we
+     * reset retries count here and schedule activation, so that the
+     * connections failed due to missing secrets may re-try auto-connection.
+     */
+    if (reset_autoconnect_all(self, NULL, TRUE))
+        schedule_activate_all(self);
+}
+
+NMActiveConnection *
+nm_policy_get_default_ip4_ac(NMPolicy *self)
+{
+    return NM_POLICY_GET_PRIVATE(self)->default_ac4;
+}
+
+NMActiveConnection *
+nm_policy_get_default_ip6_ac(NMPolicy *self)
+{
+    return NM_POLICY_GET_PRIVATE(self)->default_ac6;
+}
+
+NMActiveConnection *
+nm_policy_get_activating_ip4_ac(NMPolicy *self)
+{
+    return NM_POLICY_GET_PRIVATE(self)->activating_ac4;
+}
+
+NMActiveConnection *
+nm_policy_get_activating_ip6_ac(NMPolicy *self)
+{
+    return NM_POLICY_GET_PRIVATE(self)->activating_ac6;
+}
+
+/*****************************************************************************/
+
+static NM_UTILS_LOOKUP_STR_DEFINE(_hostname_mode_to_string,
+                                  NMPolicyHostnameMode,
+                                  NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_POLICY_HOSTNAME_MODE_NONE, "none"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_POLICY_HOSTNAME_MODE_DHCP, "dhcp"),
+                                  NM_UTILS_LOOKUP_STR_ITEM(NM_POLICY_HOSTNAME_MODE_FULL, "full"), );
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMPolicy *       self = NM_POLICY(object);
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    switch (prop_id) {
+    case PROP_DEFAULT_IP4_AC:
+        g_value_set_object(value, priv->default_ac4);
+        break;
+    case PROP_DEFAULT_IP6_AC:
+        g_value_set_object(value, priv->default_ac6);
+        break;
+    case PROP_ACTIVATING_IP4_AC:
+        g_value_set_object(value, priv->activating_ac4);
+        break;
+    case PROP_ACTIVATING_IP6_AC:
+        g_value_set_object(value, priv->activating_ac6);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NMPolicy *       self = NM_POLICY(object);
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    switch (prop_id) {
+    case PROP_MANAGER:
+        /* construct-only */
+        priv->manager = g_value_get_object(value);
+        g_return_if_fail(NM_IS_MANAGER(priv->manager));
+        break;
+    case PROP_SETTINGS:
+        /* construct-only */
+        priv->settings = g_value_dup_object(value);
+        g_return_if_fail(NM_IS_SETTINGS(priv->settings));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_policy_init(NMPolicy *self)
+{
+    NMPolicyPrivate *priv          = NM_POLICY_GET_PRIVATE(self);
+    gs_free char *   hostname_mode = NULL;
+
+    c_list_init(&priv->pending_activation_checks);
+
+    priv->netns = g_object_ref(nm_netns_get());
+
+    priv->hostname_manager = g_object_ref(nm_hostname_manager_get());
+
+    hostname_mode =
+        nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG,
+                                 NM_CONFIG_KEYFILE_GROUP_MAIN,
+                                 NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE,
+                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
+    if (nm_streq0(hostname_mode, "none"))
+        priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_NONE;
+    else if (nm_streq0(hostname_mode, "dhcp"))
+        priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_DHCP;
+    else /* default - full mode */
+        priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_FULL;
+
+    priv->devices                    = g_hash_table_new(nm_direct_hash, NULL);
+    priv->pending_active_connections = g_hash_table_new(nm_direct_hash, NULL);
+    priv->ip6_prefix_delegations     = g_array_new(FALSE, FALSE, sizeof(IP6PrefixDelegation));
+    g_array_set_clear_func(priv->ip6_prefix_delegations, clear_ip6_prefix_delegation);
+}
+
+static void
+constructed(GObject *object)
+{
+    NMPolicy *       self     = NM_POLICY(object);
+    NMPolicyPrivate *priv     = NM_POLICY_GET_PRIVATE(self);
+    char *           hostname = NULL;
+
+    /* Grab hostname on startup and use that if nothing provides one */
+    if ((hostname = _get_hostname(self))) {
+        /* init last_hostname */
+        priv->last_hostname = hostname;
+
+        /* only cache it if it's a valid hostname */
+        if (nm_utils_is_specific_hostname(hostname))
+            priv->orig_hostname = g_strdup(hostname);
+    }
+    _LOGT(LOGD_DNS,
+          "hostname-original: set to %s%s%s",
+          NM_PRINT_FMT_QUOTE_STRING(priv->orig_hostname));
+
+    priv->agent_mgr = g_object_ref(nm_agent_manager_get());
+
+    priv->firewall_manager = g_object_ref(nm_firewall_manager_get());
+    g_signal_connect(priv->firewall_manager,
+                     NM_FIREWALL_MANAGER_STATE_CHANGED,
+                     G_CALLBACK(firewall_state_changed),
+                     self);
+
+    priv->dns_manager = g_object_ref(nm_dns_manager_get());
+    nm_dns_manager_set_initial_hostname(priv->dns_manager, priv->orig_hostname);
+    priv->config_changed_id = g_signal_connect(priv->dns_manager,
+                                               NM_DNS_MANAGER_CONFIG_CHANGED,
+                                               G_CALLBACK(dns_config_changed),
+                                               self);
+
+    g_signal_connect(priv->hostname_manager,
+                     "notify::" NM_HOSTNAME_MANAGER_HOSTNAME,
+                     (GCallback) hostname_changed,
+                     priv);
+
+    g_signal_connect(priv->manager,
+                     "notify::" NM_MANAGER_SLEEPING,
+                     (GCallback) sleeping_changed,
+                     priv);
+    g_signal_connect(priv->manager,
+                     "notify::" NM_MANAGER_NETWORKING_ENABLED,
+                     (GCallback) sleeping_changed,
+                     priv);
+    g_signal_connect(priv->manager,
+                     NM_MANAGER_INTERNAL_DEVICE_ADDED,
+                     (GCallback) device_added,
+                     priv);
+    g_signal_connect(priv->manager,
+                     NM_MANAGER_INTERNAL_DEVICE_REMOVED,
+                     (GCallback) device_removed,
+                     priv);
+    g_signal_connect(priv->manager,
+                     NM_MANAGER_ACTIVE_CONNECTION_ADDED,
+                     (GCallback) active_connection_added,
+                     priv);
+    g_signal_connect(priv->manager,
+                     NM_MANAGER_ACTIVE_CONNECTION_REMOVED,
+                     (GCallback) active_connection_removed,
+                     priv);
+
+    g_signal_connect(priv->settings,
+                     NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
+                     (GCallback) connection_added,
+                     priv);
+    g_signal_connect(priv->settings,
+                     NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
+                     (GCallback) connection_updated,
+                     priv);
+    g_signal_connect(priv->settings,
+                     NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
+                     (GCallback) connection_removed,
+                     priv);
+    g_signal_connect(priv->settings,
+                     NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED,
+                     (GCallback) connection_flags_changed,
+                     priv);
+
+    g_signal_connect(priv->agent_mgr,
+                     NM_AGENT_MANAGER_AGENT_REGISTERED,
+                     G_CALLBACK(secret_agent_registered),
+                     self);
+
+    G_OBJECT_CLASS(nm_policy_parent_class)->constructed(object);
+
+    _LOGD(LOGD_DNS, "hostname-mode: %s", _hostname_mode_to_string(priv->hostname_mode));
+}
+
+NMPolicy *
+nm_policy_new(NMManager *manager, NMSettings *settings)
+{
+    g_return_val_if_fail(NM_IS_MANAGER(manager), NULL);
+    g_return_val_if_fail(NM_IS_SETTINGS(settings), NULL);
+
+    return g_object_new(NM_TYPE_POLICY,
+                        NM_POLICY_MANAGER,
+                        manager,
+                        NM_POLICY_SETTINGS,
+                        settings,
+                        NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMPolicy *       self = NM_POLICY(object);
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+    GHashTableIter   h_iter;
+    NMDevice *       device;
+    ActivateData *   data, *data_safe;
+
+    nm_clear_g_object(&priv->default_ac4);
+    nm_clear_g_object(&priv->default_ac6);
+    nm_clear_g_object(&priv->activating_ac4);
+    nm_clear_g_object(&priv->activating_ac6);
+    nm_clear_pointer(&priv->pending_active_connections, g_hash_table_unref);
+
+    c_list_for_each_entry_safe (data, data_safe, &priv->pending_activation_checks, pending_lst)
+        activate_data_free(data);
+
+    g_slist_free_full(priv->pending_secondaries, (GDestroyNotify) pending_secondary_data_free);
+    priv->pending_secondaries = NULL;
+
+    if (priv->firewall_manager) {
+        g_signal_handlers_disconnect_by_func(priv->firewall_manager, firewall_state_changed, self);
+        g_clear_object(&priv->firewall_manager);
+    }
+
+    if (priv->agent_mgr) {
+        g_signal_handlers_disconnect_by_func(priv->agent_mgr, secret_agent_registered, self);
+        g_clear_object(&priv->agent_mgr);
+    }
+
+    if (priv->dns_manager) {
+        nm_clear_g_signal_handler(priv->dns_manager, &priv->config_changed_id);
+        g_clear_object(&priv->dns_manager);
+    }
+
+    g_hash_table_iter_init(&h_iter, priv->devices);
+    if (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) {
+        g_hash_table_iter_remove(&h_iter);
+        devices_list_unregister(self, device);
+    }
+
+    /* The manager should have disposed of ActiveConnections already, which
+     * will have called active_connection_removed() and thus we don't need
+     * to clean anything up.  Assert that this is TRUE.
+     */
+    nm_assert(c_list_is_empty(nm_manager_get_active_connections(priv->manager)));
+
+    nm_clear_g_source(&priv->reset_retries_id);
+    nm_clear_g_source(&priv->schedule_activate_all_id);
+
+    nm_clear_g_free(&priv->orig_hostname);
+    nm_clear_g_free(&priv->cur_hostname);
+    nm_clear_g_free(&priv->last_hostname);
+
+    if (priv->hostname_manager) {
+        g_signal_handlers_disconnect_by_data(priv->hostname_manager, priv);
+        g_clear_object(&priv->hostname_manager);
+    }
+
+    if (priv->settings) {
+        g_signal_handlers_disconnect_by_data(priv->settings, priv);
+        g_clear_object(&priv->settings);
+
+        /* we don't clear priv->manager as we don't own a reference to it,
+         * that is, NMManager must outlive NMPolicy anyway.
+         *
+         * Hence, we unsubscribe the signals here together with the signals
+         * for settings. */
+        g_signal_handlers_disconnect_by_data(priv->manager, priv);
+    }
+
+    if (priv->ip6_prefix_delegations) {
+        g_array_free(priv->ip6_prefix_delegations, TRUE);
+        priv->ip6_prefix_delegations = NULL;
+    }
+
+    nm_assert(NM_IS_MANAGER(priv->manager));
+
+    G_OBJECT_CLASS(nm_policy_parent_class)->dispose(object);
+}
+
+static void
+finalize(GObject *object)
+{
+    NMPolicy *       self = NM_POLICY(object);
+    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
+
+    g_hash_table_unref(priv->devices);
+
+    G_OBJECT_CLASS(nm_policy_parent_class)->finalize(object);
+
+    g_object_unref(priv->netns);
+}
+
+static void
+nm_policy_class_init(NMPolicyClass *policy_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(policy_class);
+
+    object_class->get_property = get_property;
+    object_class->set_property = set_property;
+    object_class->constructed  = constructed;
+    object_class->dispose      = dispose;
+    object_class->finalize     = finalize;
+
+    obj_properties[PROP_MANAGER] =
+        g_param_spec_object(NM_POLICY_MANAGER,
+                            "",
+                            "",
+                            NM_TYPE_MANAGER,
+                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_SETTINGS] =
+        g_param_spec_object(NM_POLICY_SETTINGS,
+                            "",
+                            "",
+                            NM_TYPE_SETTINGS,
+                            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_DEFAULT_IP4_AC] =
+        g_param_spec_object(NM_POLICY_DEFAULT_IP4_AC,
+                            "",
+                            "",
+                            NM_TYPE_ACTIVE_CONNECTION,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_DEFAULT_IP6_AC] =
+        g_param_spec_object(NM_POLICY_DEFAULT_IP6_AC,
+                            "",
+                            "",
+                            NM_TYPE_DEVICE,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_ACTIVATING_IP4_AC] =
+        g_param_spec_object(NM_POLICY_ACTIVATING_IP4_AC,
+                            "",
+                            "",
+                            NM_TYPE_DEVICE,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_ACTIVATING_IP6_AC] =
+        g_param_spec_object(NM_POLICY_ACTIVATING_IP6_AC,
+                            "",
+                            "",
+                            NM_TYPE_DEVICE,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}