// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2007 - 2009 Novell, Inc.
 * Copyright (C) 2007 - 2017 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nm-manager.h"

#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <limits.h>

#include "nm-glib-aux/nm-c-list.h"

#include "nm-libnm-core-intern/nm-common-macros.h"
#include "nm-dbus-manager.h"
#include "vpn/nm-vpn-manager.h"
#include "devices/nm-device.h"
#include "devices/nm-device-generic.h"
#include "platform/nm-platform.h"
#include "platform/nmp-object.h"
#include "nm-hostname-manager.h"
#include "nm-keep-alive.h"
#include "nm-rfkill-manager.h"
#include "dhcp/nm-dhcp-manager.h"
#include "settings/nm-settings.h"
#include "settings/nm-settings-connection.h"
#include "nm-auth-utils.h"
#include "nm-auth-manager.h"
#include "NetworkManagerUtils.h"
#include "devices/nm-device-factory.h"
#include "nm-sleep-monitor.h"
#include "nm-connectivity.h"
#include "nm-policy.h"
#include "nm-session-monitor.h"
#include "nm-act-request.h"
#include "nm-core-internal.h"
#include "nm-config.h"
#include "nm-audit-manager.h"
#include "nm-std-aux/nm-dbus-compat.h"
#include "nm-checkpoint.h"
#include "nm-checkpoint-manager.h"
#include "nm-dbus-object.h"
#include "nm-dispatcher.h"
#include "NetworkManagerUtils.h"

#define DEVICE_STATE_PRUNE_RATELIMIT_MAX 100u

/*****************************************************************************/

typedef struct {
	gboolean user_enabled;
	gboolean sw_enabled;
	gboolean hw_enabled;
	RfKillType rtype;
	NMConfigRunStatePropertyType key;
	const char *desc;
	const char *prop;
	const char *hw_prop;
} RadioState;

typedef enum {
	ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL,
	ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER,
	ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
	ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2,
} AsyncOpType;

typedef struct {
	CList async_op_lst;
	NMManager *self;
	AsyncOpType async_op_type;
	union {
		struct {
			NMActiveConnection *active;
			union {
				struct {
					GDBusMethodInvocation *invocation;
				} activate_user;
				struct {
					GDBusMethodInvocation *invocation;
					NMConnection *connection;
					NMSettingsConnectionPersistMode persist_mode;
					bool is_volatile:1;
				} add_and_activate;
			};
		} ac_auth;
	};
} AsyncOpData;

enum {
	DEVICE_ADDED,
	INTERNAL_DEVICE_ADDED,
	DEVICE_REMOVED,
	INTERNAL_DEVICE_REMOVED,
	ACTIVE_CONNECTION_ADDED,
	ACTIVE_CONNECTION_REMOVED,
	CONFIGURE_QUIT,

	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

NM_GOBJECT_PROPERTIES_DEFINE (NMManager,
	PROP_VERSION,
	PROP_CAPABILITIES,
	PROP_STATE,
	PROP_STARTUP,
	PROP_NETWORKING_ENABLED,
	PROP_WIRELESS_ENABLED,
	PROP_WIRELESS_HARDWARE_ENABLED,
	PROP_WWAN_ENABLED,
	PROP_WWAN_HARDWARE_ENABLED,
	PROP_WIMAX_ENABLED,
	PROP_WIMAX_HARDWARE_ENABLED,
	PROP_ACTIVE_CONNECTIONS,
	PROP_CONNECTIVITY,
	PROP_CONNECTIVITY_CHECK_AVAILABLE,
	PROP_CONNECTIVITY_CHECK_ENABLED,
	PROP_CONNECTIVITY_CHECK_URI,
	PROP_PRIMARY_CONNECTION,
	PROP_PRIMARY_CONNECTION_TYPE,
	PROP_ACTIVATING_CONNECTION,
	PROP_DEVICES,
	PROP_METERED,
	PROP_GLOBAL_DNS_CONFIGURATION,
	PROP_ALL_DEVICES,
	PROP_CHECKPOINTS,

	/* Not exported */
	PROP_SLEEPING,
);

typedef struct {
	NMPlatform *platform;

	GArray *capabilities;

	CList active_connections_lst_head;
	CList async_op_lst_head;
	guint ac_cleanup_id;
	NMActiveConnection *primary_connection;
	NMActiveConnection *activating_connection;
	NMMetered metered;

	CList devices_lst_head;

	NMState state;
	NMConfig *config;
	NMConnectivity *concheck_mgr;
	NMPolicy *policy;
	NMHostnameManager *hostname_manager;

	struct {
		GDBusConnection *connection;
		guint            id;
	} prop_filter;
	NMRfkillManager *rfkill_mgr;

	CList link_cb_lst;

	NMCheckpointManager *checkpoint_mgr;

	NMSettings *settings;

	RadioState radio_states[RFKILL_TYPE_MAX];
	NMVpnManager *vpn_manager;

	NMSleepMonitor *sleep_monitor;

	NMAuthManager *auth_mgr;

	GHashTable *device_route_metrics;

	CList auth_lst_head;

	GHashTable *sleep_devices;

	/* Firmware dir monitor */
	GFileMonitor *fw_monitor;
	guint fw_changed_id;

	guint timestamp_update_id;

	guint devices_inited_id;

	NMConnectivityState connectivity_state;

	guint8 device_state_prune_ratelimit_count;

	bool startup:1;
	bool devices_inited:1;

	bool sleeping:1;
	bool net_enabled:1;

	unsigned connectivity_check_enabled_last:2;

	guint delete_volatile_connection_idle_id;
	CList delete_volatile_connection_lst_head;
} NMManagerPrivate;

struct _NMManager {
	NMDBusObject parent;
	NMManagerPrivate _priv;
};

typedef struct {
	NMDBusObjectClass parent;
} NMManagerClass;

G_DEFINE_TYPE (NMManager, nm_manager, NM_TYPE_DBUS_OBJECT)

#define NM_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMManager, NM_IS_MANAGER)

/*****************************************************************************/

NM_DEFINE_SINGLETON_INSTANCE (NMManager);

/*****************************************************************************/

#define _NMLOG_PREFIX_NAME      "manager"
#define _NMLOG(level, domain, ...) \
    G_STMT_START { \
        const NMLogLevel _level = (level); \
        const NMLogDomain _domain = (domain); \
        \
        if (nm_logging_enabled (_level, _domain)) { \
            const NMManager *const _self = (self); \
            char _sbuf[32]; \
            \
            _nm_log (_level, _domain, 0, NULL, NULL, \
                     "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     _NMLOG_PREFIX_NAME, \
                     ((_self && _self != singleton_instance) \
                         ? nm_sprintf_buf (_sbuf, "[%p]", _self) \
                         : "") \
                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
        } \
    } G_STMT_END

#define _NMLOG2(level, domain, device, ...) \
    G_STMT_START { \
        const NMLogLevel _level = (level); \
        const NMLogDomain _domain = (domain); \
        \
        if (nm_logging_enabled (_level, _domain)) { \
            const NMManager *const _self = (self); \
            const char *const _ifname = _nm_device_get_iface (device); \
            char _sbuf[32]; \
            \
            _nm_log (_level, _domain, 0, \
                     _ifname, NULL, \
                     "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     _NMLOG_PREFIX_NAME, \
                     ((_self && _self != singleton_instance) \
                         ? nm_sprintf_buf (_sbuf, "[%p]", _self) \
                         : ""), \
                     NM_PRINT_FMT_QUOTED (_ifname, "(", _ifname, "): ", "") \
                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
        } \
    } G_STMT_END

#define _NMLOG3(level, domain, connection, ...) \
    G_STMT_START { \
        const NMLogLevel _level = (level); \
        const NMLogDomain _domain = (domain); \
        \
        if (nm_logging_enabled (_level, _domain)) { \
            const NMManager *const _self = (self); \
            NMConnection *const _connection = (connection); \
            const char *const _con_id = _nm_connection_get_id (_connection); \
            char _sbuf[32]; \
            \
            _nm_log (_level, _domain, 0, \
                     NULL, _nm_connection_get_uuid (_connection), \
                     "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                     _NMLOG_PREFIX_NAME, \
                     ((_self && _self != singleton_instance) \
                         ? nm_sprintf_buf (_sbuf, "[%p]", _self) \
                         : ""), \
                     NM_PRINT_FMT_QUOTED (_con_id, "(", _con_id, ") ", "") \
                     _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
        } \
    } G_STMT_END

/*****************************************************************************/

static const NMDBusInterfaceInfoExtended interface_info_manager;
static const GDBusSignalInfo signal_info_check_permissions;
static const GDBusSignalInfo signal_info_state_changed;
static const GDBusSignalInfo signal_info_device_added;
static const GDBusSignalInfo signal_info_device_removed;

static void update_connectivity_value (NMManager *self);

static gboolean add_device (NMManager *self, NMDevice *device, GError **error);

static void _emit_device_added_removed (NMManager *self,
                                        NMDevice *device,
                                        gboolean is_added);

static NMActiveConnection *_new_active_connection (NMManager *self,
                                                   gboolean is_vpn,
                                                   NMSettingsConnection *sett_conn,
                                                   NMConnection *incompl_conn,
                                                   NMConnection *applied,
                                                   const char *specific_object,
                                                   NMDevice *device,
                                                   NMAuthSubject *subject,
                                                   NMActivationType activation_type,
                                                   NMActivationReason activation_reason,
                                                   NMActivationStateFlags initial_state_flags,
                                                   GError **error);

static void policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data);

static gboolean find_master (NMManager *self,
                             NMConnection *connection,
                             NMDevice *device,
                             NMSettingsConnection **out_master_connection,
                             NMDevice **out_master_device,
                             NMActiveConnection **out_master_ac,
                             GError **error);

static void nm_manager_update_state (NMManager *manager);

static void connection_changed (NMManager *self,
                                NMSettingsConnection *sett_conn);
static void device_sleep_cb (NMDevice *device,
                             GParamSpec *pspec,
                             NMManager *self);

static void settings_startup_complete_changed (NMSettings *settings,
                                               GParamSpec *pspec,
                                               NMManager *self);

static void retry_connections_for_parent_device (NMManager *self, NMDevice *device);

static void active_connection_state_changed (NMActiveConnection *active,
                                             GParamSpec *pspec,
                                             NMManager *self);
static void active_connection_default_changed (NMActiveConnection *active,
                                               GParamSpec *pspec,
                                               NMManager *self);
static void active_connection_parent_active (NMActiveConnection *active,
                                             NMActiveConnection *parent_ac,
                                             NMManager *self);

static NMActiveConnection *active_connection_find (NMManager *self,
                                                   NMSettingsConnection *sett_conn,
                                                   const char *uuid,
                                                   NMActiveConnectionState max_state,
                                                   GPtrArray **out_all_matching);

static NMConnectivity *concheck_get_mgr (NMManager *self);

static void _internal_activation_auth_done (NMManager *self,
                                            NMActiveConnection *active,
                                            gboolean success,
                                            const char *error_desc);
static void _add_and_activate_auth_done (NMManager *self,
                                         AsyncOpType async_op_type,
                                         NMActiveConnection *active,
                                         NMConnection *connection,
                                         GDBusMethodInvocation *invocation,
                                         NMSettingsConnectionPersistMode persist_mode,
                                         gboolean is_volatile,
                                         gboolean success,
                                         const char *error_desc);
static void _activation_auth_done (NMManager *self,
                                   NMActiveConnection *active,
                                   GDBusMethodInvocation *invocation,
                                   gboolean success,
                                   const char *error_desc);

/*****************************************************************************/

static NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark)

/*****************************************************************************/

static gboolean
_connection_is_vpn (NMConnection *connection)
{
	const char *type;

	type = nm_connection_get_connection_type (connection);
	if (type)
		return nm_streq (type, NM_SETTING_VPN_SETTING_NAME);

	/* we have an incomplete (invalid) connection at hand. That can only
	 * happen during AddAndActivate. Determine whether it's VPN type based
	 * on the existence of a [vpn] section. */
	return !!nm_connection_get_setting_vpn (connection);
}

/*****************************************************************************/

static gboolean
concheck_enabled (NMManager *self, gboolean *out_changed)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	guint check_enabled;

	check_enabled = nm_connectivity_check_enabled (concheck_get_mgr (self))
	                ? 1 : 2;
	if (priv->connectivity_check_enabled_last == check_enabled)
		NM_SET_OUT (out_changed, FALSE);
	else {
		NM_SET_OUT (out_changed, TRUE);
		priv->connectivity_check_enabled_last = check_enabled;
	}
	return check_enabled == 1;
}

static void
concheck_config_changed_cb (NMConnectivity *connectivity,
                            NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;
	gboolean changed;

	concheck_enabled (self, &changed);
	if (changed)
		_notify (self, PROP_CONNECTIVITY_CHECK_ENABLED);

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
		nm_device_check_connectivity_update_interval (device);
}

static NMConnectivity *
concheck_get_mgr (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	if (G_UNLIKELY (!priv->concheck_mgr)) {
		priv->concheck_mgr = g_object_ref (nm_connectivity_get ());
		g_signal_connect (priv->concheck_mgr,
		                  NM_CONNECTIVITY_CONFIG_CHANGED,
		                  G_CALLBACK (concheck_config_changed_cb),
		                  self);
	}
	return priv->concheck_mgr;
}

/*****************************************************************************/

static AsyncOpData *
_async_op_data_new_authorize_activate_internal (NMManager *self, NMActiveConnection *active_take)
{
	AsyncOpData *async_op_data;

	async_op_data = g_slice_new0 (AsyncOpData);
	async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL;
	async_op_data->self = g_object_ref (self);
	async_op_data->ac_auth.active = active_take;
	c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
	return async_op_data;
}

static AsyncOpData *
_async_op_data_new_ac_auth_activate_user (NMManager *self,
                                          NMActiveConnection *active_take,
                                          GDBusMethodInvocation *invocation_take)
{
	AsyncOpData *async_op_data;

	async_op_data = g_slice_new0 (AsyncOpData);
	async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER;
	async_op_data->self = g_object_ref (self);
	async_op_data->ac_auth.active = active_take;
	async_op_data->ac_auth.activate_user.invocation = invocation_take;
	c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
	return async_op_data;
}

static AsyncOpData *
_async_op_data_new_ac_auth_add_and_activate (NMManager *self,
                                             AsyncOpType async_op_type,
                                             NMActiveConnection *active_take,
                                             GDBusMethodInvocation *invocation_take,
                                             NMConnection *connection_take,
                                             NMSettingsConnectionPersistMode persist_mode,
                                             gboolean is_volatile)
{
	AsyncOpData *async_op_data;

	nm_assert (NM_IN_SET (async_op_type, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
	                                     ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2));

	async_op_data = g_slice_new0 (AsyncOpData);
	async_op_data->async_op_type = async_op_type;
	async_op_data->self = g_object_ref (self);
	async_op_data->ac_auth.active = active_take;
	async_op_data->ac_auth.add_and_activate.invocation = invocation_take;
	async_op_data->ac_auth.add_and_activate.connection = connection_take;
	async_op_data->ac_auth.add_and_activate.persist_mode = persist_mode;
	async_op_data->ac_auth.add_and_activate.is_volatile = is_volatile;
	c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
	return async_op_data;
}

static void
_async_op_complete_ac_auth_cb (NMActiveConnection *active,
                               gboolean success,
                               const char *error_desc,
                               gpointer user_data)
{
	AsyncOpData *async_op_data = user_data;

	nm_assert (async_op_data);
	nm_assert (NM_IS_MANAGER (async_op_data->self));
	nm_assert (nm_c_list_contains_entry (&NM_MANAGER_GET_PRIVATE (async_op_data->self)->async_op_lst_head, async_op_data, async_op_lst));
	nm_assert (NM_IS_ACTIVE_CONNECTION (active));
	nm_assert (active == async_op_data->ac_auth.active);

	c_list_unlink (&async_op_data->async_op_lst);

	switch (async_op_data->async_op_type) {
	case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL:
		_internal_activation_auth_done (async_op_data->self,
		                                async_op_data->ac_auth.active,
		                                success,
		                                error_desc);
		break;
	case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER:
		_activation_auth_done (async_op_data->self,
		                       async_op_data->ac_auth.active,
		                       async_op_data->ac_auth.activate_user.invocation,
		                       success,
		                       error_desc);
		break;
	case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE:
	case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2:
		_add_and_activate_auth_done (async_op_data->self,
		                             async_op_data->async_op_type,
		                             async_op_data->ac_auth.active,
		                             async_op_data->ac_auth.add_and_activate.connection,
		                             async_op_data->ac_auth.add_and_activate.invocation,
		                             async_op_data->ac_auth.add_and_activate.persist_mode,
		                             async_op_data->ac_auth.add_and_activate.is_volatile,
		                             success,
		                             error_desc);
		g_object_unref (async_op_data->ac_auth.add_and_activate.connection);
		break;
	default:
		nm_assert_not_reached ();
		break;
	}

	g_object_unref (async_op_data->ac_auth.active);
	g_object_unref (async_op_data->self);
	g_slice_free (AsyncOpData, async_op_data);
}

/*****************************************************************************/

typedef struct {
	int ifindex;
	guint32 aspired_metric;
	guint32 effective_metric;
} DeviceRouteMetricData;

static DeviceRouteMetricData *
_device_route_metric_data_new (int ifindex, guint32 aspired_metric, guint32 effective_metric)
{
	DeviceRouteMetricData *data;

	nm_assert (ifindex > 0);

	/* For IPv4, metrics can use the entire uint32 bit range. For IPv6,
	 * zero is treated like 1024. Since we handle IPv4 and IPv6 identically,
	 * we cannot allow a zero metric here.
	 */
	nm_assert (aspired_metric > 0);
	nm_assert (effective_metric == 0 || aspired_metric <= effective_metric);

	data = g_slice_new0 (DeviceRouteMetricData);
	data->ifindex = ifindex;
	data->aspired_metric = aspired_metric;
	data->effective_metric = effective_metric ?: aspired_metric;
	return data;
}

static guint
_device_route_metric_data_by_ifindex_hash (gconstpointer p)
{
	const DeviceRouteMetricData *data = p;
	NMHashState h;

	nm_hash_init (&h, 1030338191);
	nm_hash_update_vals (&h, data->ifindex);
	return nm_hash_complete (&h);
}

static gboolean
_device_route_metric_data_by_ifindex_equal (gconstpointer pa, gconstpointer pb)
{
	const DeviceRouteMetricData *a = pa;
	const DeviceRouteMetricData *b = pb;

	return a->ifindex == b->ifindex;
}

static guint32
_device_route_metric_get (NMManager *self,
                          int ifindex,
                          NMDeviceType device_type,
                          gboolean lookup_only,
                          guint32 *out_aspired_metric)
{
	NMManagerPrivate *priv;
	const DeviceRouteMetricData *d2;
	DeviceRouteMetricData *data;
	DeviceRouteMetricData data_lookup;
	const NMDedupMultiHeadEntry *all_links_head;
	NMPObject links_needle;
	guint n_links;
	gboolean cleaned = FALSE;
	GHashTableIter h_iter;
	guint32 metric;

	g_return_val_if_fail (NM_IS_MANAGER (self), 0);

	NM_SET_OUT (out_aspired_metric, 0);

	if (ifindex <= 0) {
		if (lookup_only)
			return 0;
		metric = nm_device_get_route_metric_default (device_type);
		NM_SET_OUT (out_aspired_metric, metric);
		return metric;
	}

	priv = NM_MANAGER_GET_PRIVATE (self);

	if (   lookup_only
	    && !priv->device_route_metrics)
		return 0;

	if (G_UNLIKELY (!priv->device_route_metrics)) {
		const GHashTable *h;
		const NMConfigDeviceStateData *device_state;

		priv->device_route_metrics = g_hash_table_new_full (_device_route_metric_data_by_ifindex_hash,
		                                                    _device_route_metric_data_by_ifindex_equal,
		                                                    NULL,
		                                                    nm_g_slice_free_fcn (DeviceRouteMetricData));
		cleaned = TRUE;

		/* we need to pre-populate the cache for all (still existing) devices from the state-file */
		h = nm_config_device_state_get_all (priv->config);
		if (!h)
			goto initited;

		g_hash_table_iter_init (&h_iter, (GHashTable *) h);
		while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &device_state)) {
			if (!device_state->route_metric_default_effective)
				continue;
			if (!nm_platform_link_get (priv->platform, device_state->ifindex)) {
				/* we have the entry in the state file, but (currently) no such
				 * ifindex exists in platform. Most likely the entry is obsolete,
				 * hence we skip it. */
				continue;
			}
			if (!g_hash_table_add (priv->device_route_metrics,
			                       _device_route_metric_data_new (device_state->ifindex,
			                                                      device_state->route_metric_default_aspired,
			                                                      device_state->route_metric_default_effective)))
				nm_assert_not_reached ();
		}
	}

initited:
	data_lookup.ifindex = ifindex;

	data = g_hash_table_lookup (priv->device_route_metrics, &data_lookup);
	if (data)
		goto out;
	if (lookup_only)
		return 0;

	if (!cleaned) {
		/* get the number of all links in the platform cache. */
		all_links_head = nm_platform_lookup_all (priv->platform,
		                                         NMP_CACHE_ID_TYPE_OBJECT_TYPE,
		                                         nmp_object_stackinit_id_link (&links_needle, 1));
		n_links = all_links_head ? all_links_head->len : 0;

		/* on systems where a lot of devices are created and go away, the index contains
		 * a lot of stale entries. We must from time to time clean them up.
		 *
		 * Do do this cleanup, whenever we have more enties then 2 times the number of links. */
		if (G_UNLIKELY (g_hash_table_size (priv->device_route_metrics) > NM_MAX (20, n_links * 2))) {
			/* from time to time, we need to do some house-keeping and prune stale entries.
			 * Otherwise, on a system where interfaces frequently come and go (docker), we
			 * keep growing this cache for ifindexes that no longer exist. */
			g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
			while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
				if (!nm_platform_link_get (priv->platform, d2->ifindex))
					g_hash_table_iter_remove (&h_iter);
			}
			cleaned = TRUE;
		}
	}

	data = _device_route_metric_data_new (ifindex, nm_device_get_route_metric_default (device_type), 0);

	/* unfortunately, there is no stright forward way to lookup all reserved metrics.
	 * Note, that we don't only have to know which metrics are currently reserved,
	 * but also, which metrics are now seemingly un-used but caused another reserved
	 * metric to be bumped. Hence, the naive O(n^2) search :(
	 *
	 * Well, technically, since we limit bumping the metric to 50, this entire
	 * loop runs at most 50 times, so it's still O(n). Let's just say, it's not
	 * very efficient. */
again:
	g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
	while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
		if (   data->effective_metric < d2->aspired_metric
		    || data->effective_metric > d2->effective_metric) {
			/* no overlap. Skip. */
			continue;
		}
		if (   !cleaned
		    && !nm_platform_link_get (priv->platform, d2->ifindex)) {
			/* the metric seems taken, but there is no such interface. This entry
			 * is stale, forget about it. */
			g_hash_table_iter_remove (&h_iter);
			continue;
		}

		if (d2->effective_metric == G_MAXUINT32) {
			/* we cannot bump the metric any further. Done.
			 *
			 * Actually, this can currently not happen because the aspired_metric
			 * are small numbers and we limit the bumping to 50. Still, for
			 * completeness... */
			data->effective_metric = G_MAXUINT32;
			break;
		}

		if (d2->effective_metric - data->aspired_metric >= 50) {
			/* as one active interface reserves an entire range of metrics
			 * (from aspired_metric to effective_metric), that means if you
			 * alternatingly activate two interfaces, their metric will
			 * bump each other.
			 *
			 * Limit this, bump the metric at most 50 points. */
			data->effective_metric = data->aspired_metric + 50;
			break;
		}

		/* bump the metric, and search again. */
		data->effective_metric = d2->effective_metric + 1;
		goto again;
	}

	_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d reserves metric %u (aspired %u)",
	       data->ifindex, data->effective_metric, data->aspired_metric);

	if (!g_hash_table_add (priv->device_route_metrics, data))
		nm_assert_not_reached ();

out:
	NM_SET_OUT (out_aspired_metric, data->aspired_metric);
	return data->effective_metric;
}

guint32
nm_manager_device_route_metric_reserve (NMManager *self,
                                        int ifindex,
                                        NMDeviceType device_type)
{
	guint32 metric;

	metric = _device_route_metric_get (self, ifindex, device_type, FALSE, NULL);
	nm_assert (metric != 0);
	return metric;
}

void
nm_manager_device_route_metric_clear (NMManager *self,
                                      int ifindex)
{
	NMManagerPrivate *priv;
	DeviceRouteMetricData data_lookup;

	priv = NM_MANAGER_GET_PRIVATE (self);

	if (!priv->device_route_metrics)
		return;
	data_lookup.ifindex = ifindex;
	if (g_hash_table_remove (priv->device_route_metrics, &data_lookup)) {
		_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d released",
		       ifindex);
	}
}

/*****************************************************************************/

static void
_delete_volatile_connection_do (NMManager *self,
                                NMSettingsConnection *connection)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
		return;
	if (!nm_settings_has_connection (priv->settings, connection))
		return;
	if (active_connection_find (self,
	                            connection,
	                            NULL,
	                            NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
	                            NULL))
		return;

	_LOGD (LOGD_DEVICE, "volatile connection disconnected. Deleting connection '%s' (%s)",
	       nm_settings_connection_get_id (connection), nm_settings_connection_get_uuid (connection));
	nm_settings_connection_delete (connection, FALSE);
}

/* Returns: whether to notify D-Bus of the removal or not */
static gboolean
active_connection_remove (NMManager *self, NMActiveConnection *active)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_unref_object NMSettingsConnection *connection = NULL;
	gboolean notify;

	nm_assert (NM_IS_ACTIVE_CONNECTION (active));
	nm_assert (c_list_contains (&priv->active_connections_lst_head, &active->active_connections_lst));

	notify = nm_dbus_object_is_exported (NM_DBUS_OBJECT (active));

	c_list_unlink (&active->active_connections_lst);
	g_signal_emit (self, signals[ACTIVE_CONNECTION_REMOVED], 0, active);
	g_signal_handlers_disconnect_by_func (active, active_connection_state_changed, self);
	g_signal_handlers_disconnect_by_func (active, active_connection_default_changed, self);
	g_signal_handlers_disconnect_by_func (active, active_connection_parent_active, self);

	connection = nm_g_object_ref (nm_active_connection_get_settings_connection (active));

	nm_dbus_object_clear_and_unexport (&active);

	if (connection)
		_delete_volatile_connection_do (self, connection);

	return notify;
}

static gboolean
_active_connection_cleanup (gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *ac, *ac_safe;

	priv->ac_cleanup_id = 0;

	g_object_freeze_notify (G_OBJECT (self));
	c_list_for_each_entry_safe (ac, ac_safe, &priv->active_connections_lst_head, active_connections_lst) {
		if (nm_active_connection_get_state (ac) == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
			if (active_connection_remove (self, ac))
				_notify (self, PROP_ACTIVE_CONNECTIONS);
		}
	}
	g_object_thaw_notify (G_OBJECT (self));

	return FALSE;
}

static void
active_connection_state_changed (NMActiveConnection *active,
                                 GParamSpec *pspec,
                                 NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnectionState state;
	NMSettingsConnection *con;

	state = nm_active_connection_get_state (active);
	if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
		/* Destroy active connections from an idle handler to ensure that
		 * their last property change notifications go out, which wouldn't
		 * happen if we destroyed them immediately when their state was set
		 * to DEACTIVATED.
		 */
		if (!priv->ac_cleanup_id)
			priv->ac_cleanup_id = g_idle_add (_active_connection_cleanup, self);

		con = nm_active_connection_get_settings_connection (active);
		if (con)
			g_object_set_qdata (G_OBJECT (con), autoconnect_root_quark (), NULL);
	}

	nm_manager_update_state (self);
}

static void
active_connection_default_changed (NMActiveConnection *active,
                                   GParamSpec *pspec,
                                   NMManager *self)
{
	nm_manager_update_state (self);
}

/**
 * active_connection_add():
 * @self: the #NMManager
 * @active: the #NMActiveConnection to manage
 *
 * Begins to track and manage @active.  Increases the refcount of @active.
 */
static void
active_connection_add (NMManager *self,
                       NMActiveConnection *active)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	nm_assert (NM_IS_ACTIVE_CONNECTION (active));
	nm_assert (!c_list_is_linked (&active->active_connections_lst));

	c_list_link_front (&priv->active_connections_lst_head, &active->active_connections_lst);
	g_object_ref (active);

	g_signal_connect (active,
	                  "notify::" NM_ACTIVE_CONNECTION_STATE,
	                  G_CALLBACK (active_connection_state_changed),
	                  self);
	g_signal_connect (active,
	                  "notify::" NM_ACTIVE_CONNECTION_DEFAULT,
	                  G_CALLBACK (active_connection_default_changed),
	                  self);
	g_signal_connect (active,
	                  "notify::" NM_ACTIVE_CONNECTION_DEFAULT6,
	                  G_CALLBACK (active_connection_default_changed),
	                  self);

	if (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (active)))
		nm_dbus_object_export (NM_DBUS_OBJECT (active));

	g_signal_emit (self, signals[ACTIVE_CONNECTION_ADDED], 0, active);

	_notify (self, PROP_ACTIVE_CONNECTIONS);
}

const CList *
nm_manager_get_active_connections (NMManager *manager)
{
	return &NM_MANAGER_GET_PRIVATE (manager)->active_connections_lst_head;
}

static NMActiveConnection *
active_connection_find (NMManager *self,
                        NMSettingsConnection *sett_conn,
                        const char *uuid,
                        NMActiveConnectionState max_state /* candidates in state @max_state will be found */,
                        GPtrArray **out_all_matching)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *ac;
	NMActiveConnection *best_ac = NULL;
	GPtrArray *all = NULL;

	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
	nm_assert (!out_all_matching || !*out_all_matching);

	c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
		NMSettingsConnection *ac_conn;

		ac_conn = nm_active_connection_get_settings_connection (ac);
		if (   sett_conn
		    && sett_conn != ac_conn)
			continue;
		if (   uuid
		    && !nm_streq0 (uuid, nm_settings_connection_get_uuid (ac_conn)))
			continue;
		if (nm_active_connection_get_state (ac) > max_state)
			continue;

		if (!out_all_matching)
			return ac;

		if (!best_ac) {
			best_ac = ac;
			continue;
		}

		if (!all) {
			all = g_ptr_array_new_with_free_func (g_object_unref);
			g_ptr_array_add (all, g_object_ref (best_ac));
		}
		g_ptr_array_add (all, g_object_ref (ac));
	}

	if (!best_ac)
		return NULL;

	/* as an optimization, we only allocate out_all_matching, if there are more
	 * than one result. If there is only one result, we only return the single
	 * element and don't bother allocating an array. That's the common case.
	 *
	 * Also, in case we have multiple results, we return the *first* one
	 * as @best_ac. */
	nm_assert (   !all
	           || (   all->len >= 2
	               && all->pdata[0] == best_ac));

	*out_all_matching = all;
	return best_ac;
}

static NMActiveConnection *
active_connection_find_by_connection (NMManager *self,
                                      NMSettingsConnection *sett_conn,
                                      NMConnection *connection,
                                      NMActiveConnectionState max_state,
                                      GPtrArray **out_all_matching)
{
	nm_assert (NM_IS_MANAGER (self));
	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
	nm_assert (!connection || NM_IS_CONNECTION (connection));
	nm_assert (sett_conn || connection);
	nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));

	/* Depending on whether connection is a settings connection,
	 * either lookup by object-identity of @connection, or compare the UUID */
	return active_connection_find (self,
	                               sett_conn,
	                               sett_conn ? NULL : nm_connection_get_uuid (connection),
	                               max_state,
	                               out_all_matching);
}

typedef struct {
	NMManager *self;
	gboolean for_auto_activation;
} GetActivatableConnectionsFilterData;

static gboolean
_get_activatable_connections_filter (NMSettings *settings,
                                     NMSettingsConnection *sett_conn,
                                     gpointer user_data)
{
	const GetActivatableConnectionsFilterData *d = user_data;
	NMConnectionMultiConnect multi_connect;

	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
		return FALSE;

	multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn));
	if (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE
	    || (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE
	        && !d->for_auto_activation))
		return TRUE;

	/* the connection is activatable, if it has no active-connections that are in state
	 * activated, activating, or waiting to be activated. */
	return !active_connection_find (d->self,
	                                sett_conn,
	                                NULL,
	                                NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
	                                NULL);
}

NMSettingsConnection **
nm_manager_get_activatable_connections (NMManager *manager,
                                        gboolean for_auto_activation,
                                        gboolean sort,
                                        guint *out_len)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
	const GetActivatableConnectionsFilterData d = {
		.self = manager,
		.for_auto_activation = for_auto_activation,
	};

	return nm_settings_get_connections_clone (priv->settings, out_len,
	                                          _get_activatable_connections_filter,
	                                          (gpointer) &d,
	                                          sort ? nm_settings_connection_cmp_autoconnect_priority_p_with_data : NULL,
	                                          NULL);
}

static NMActiveConnection *
active_connection_get_by_path (NMManager *self, const char *path)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *ac;

	ac = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
	                                    path);
	if (   !ac
	    || !NM_IS_ACTIVE_CONNECTION (ac)
	    || c_list_is_empty (&ac->active_connections_lst))
		return NULL;

	nm_assert (c_list_contains (&priv->active_connections_lst_head, &ac->active_connections_lst));
	return ac;
}

/*****************************************************************************/

static void
_config_changed_cb (NMConfig *config, NMConfigData *config_data, NMConfigChangeFlags changes, NMConfigData *old_data, NMManager *self)
{
	g_object_freeze_notify (G_OBJECT (self));

	if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG))
		_notify (self, PROP_GLOBAL_DNS_CONFIGURATION);

	if (!nm_streq0 (nm_config_data_get_connectivity_uri (config_data),
	                nm_config_data_get_connectivity_uri (old_data))) {
		if ((!nm_config_data_get_connectivity_uri (config_data)) != (!nm_config_data_get_connectivity_uri (old_data)))
			_notify (self, PROP_CONNECTIVITY_CHECK_AVAILABLE);
		_notify (self, PROP_CONNECTIVITY_CHECK_URI);
	}

	g_object_thaw_notify (G_OBJECT (self));
}

static void
_reload_auth_cb (NMAuthChain *chain,
                 GDBusMethodInvocation *context,
                 gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	GError *ret_error = NULL;
	NMAuthCallResult result;
	guint32 flags;
	NMAuthSubject *subject;
	char s_buf[60];
	NMConfigChangeFlags reload_type = NM_CONFIG_CHANGE_NONE;

	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));

	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
	flags = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "flags"));

	subject = nm_auth_chain_get_subject (chain);

	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_RELOAD);
	if (result != NM_AUTH_CALL_RESULT_YES) {
		ret_error = g_error_new_literal (NM_MANAGER_ERROR,
		                                 NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                 "Not authorized to reload configuration");
	} else {
		if (NM_FLAGS_ANY (flags, ~NM_MANAGER_RELOAD_FLAG_ALL)) {
			/* invalid flags */
		} else if (flags == 0)
			reload_type = NM_CONFIG_CHANGE_CAUSE_SIGHUP;
		else {
			if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_CONF))
				reload_type |= NM_CONFIG_CHANGE_CAUSE_CONF;
			if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_DNS_RC))
				reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_RC;
			if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_DNS_FULL))
				reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_FULL;
		}

		if (reload_type == NM_CONFIG_CHANGE_NONE) {
			ret_error = g_error_new_literal (NM_MANAGER_ERROR,
			                                 NM_MANAGER_ERROR_INVALID_ARGUMENTS,
			                                 "Invalid flags for reload");
		}
	}

	nm_audit_log_control_op (NM_AUDIT_OP_RELOAD,
	                         nm_sprintf_buf (s_buf, "%u", flags),
	                         ret_error == NULL, subject,
	                         ret_error ? ret_error->message : NULL);

	if (ret_error) {
		g_dbus_method_invocation_take_error (context, ret_error);
		return;
	}

	nm_config_reload (priv->config, reload_type, TRUE);
	g_dbus_method_invocation_return_value (context, NULL);
}

static void
impl_manager_reload (NMDBusObject *obj,
                     const NMDBusInterfaceInfoExtended *interface_info,
                     const NMDBusMethodInfoExtended *method_info,
                     GDBusConnection *connection,
                     const char *sender,
                     GDBusMethodInvocation *invocation,
                     GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;
	guint32 flags;

	g_variant_get (parameters, "(u)", &flags);

	chain = nm_auth_chain_new_context (invocation, _reload_auth_cb, self);
	if (!chain) {
		g_dbus_method_invocation_return_error_literal (invocation,
		                                               NM_MANAGER_ERROR,
		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		return;
	}

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "flags", GUINT_TO_POINTER (flags), NULL);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_RELOAD, TRUE);
}

/*****************************************************************************/

NMDevice *
nm_manager_get_device_by_path (NMManager *self, const char *path)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	g_return_val_if_fail (path, NULL);

	device = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
	                                        path);
	if (   !device
	    || !NM_IS_DEVICE (device)
	    || c_list_is_empty (&device->devices_lst))
		return NULL;

	nm_assert (c_list_contains (&priv->devices_lst_head, &device->devices_lst));
	return device;
}

NMDevice *
nm_manager_get_device_by_ifindex (NMManager *self, int ifindex)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	if (ifindex > 0) {
		c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
			if (nm_device_get_ifindex (device) == ifindex)
				return device;
		}
	}

	return NULL;
}

static NMDevice *
find_device_by_permanent_hw_addr (NMManager *self, const char *hwaddr)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;
	const char *device_addr;
	guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX];
	gsize hwaddr_len;

	g_return_val_if_fail (hwaddr != NULL, NULL);

	if (!_nm_utils_hwaddr_aton (hwaddr, hwaddr_bin, sizeof (hwaddr_bin), &hwaddr_len))
		return NULL;

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		device_addr = nm_device_get_permanent_hw_address (device);
		if (   device_addr
		    && nm_utils_hwaddr_matches (hwaddr_bin, hwaddr_len, device_addr, -1))
			return device;
	}
	return NULL;
}

static NMDevice *
find_device_by_ip_iface (NMManager *self, const char *iface)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	g_return_val_if_fail (iface, NULL);

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		if (   nm_device_is_real (device)
		    && nm_streq0 (nm_device_get_ip_iface (device), iface))
			return device;
	}
	return NULL;
}

/**
 * find_device_by_iface:
 * @self: the #NMManager
 * @iface: the device interface to find
 * @connection: a connection to ensure the returned device is compatible with
 * @slave: a slave connection to ensure a master is compatible with
 *
 * Finds a device by interface name, preferring realized devices.  If @slave
 * is given, this function will only return master devices and will ensure
 * @slave, when activated, can be a slave of the returned master device.  If
 * @connection is given, this function will only consider devices that are
 * compatible with @connection.
 *
 * Returns: the matching #NMDevice
 */
static NMDevice *
find_device_by_iface (NMManager *self,
                      const char *iface,
                      NMConnection *connection,
                      NMConnection *slave)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *fallback = NULL;
	NMDevice *candidate;

	g_return_val_if_fail (iface != NULL, NULL);

	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {

		if (strcmp (nm_device_get_iface (candidate), iface))
			continue;
		if (connection && !nm_device_check_connection_compatible (candidate, connection, NULL))
			continue;
		if (slave) {
			if (!nm_device_is_master (candidate))
				continue;
			if (!nm_device_check_slave_connection_compatible (candidate, slave))
				continue;
		}

		if (nm_device_is_real (candidate))
			return candidate;
		else if (!fallback)
			fallback = candidate;
	}
	return fallback;
}

static gboolean
manager_sleeping (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	if (priv->sleeping || !priv->net_enabled)
		return TRUE;
	return FALSE;
}

static const char *
_nm_state_to_string (NMState state)
{
	switch (state) {
	case NM_STATE_ASLEEP:
		return "ASLEEP";
	case NM_STATE_DISCONNECTED:
		return "DISCONNECTED";
	case NM_STATE_DISCONNECTING:
		return "DISCONNECTING";
	case NM_STATE_CONNECTING:
		return "CONNECTING";
	case NM_STATE_CONNECTED_LOCAL:
		return "CONNECTED_LOCAL";
	case NM_STATE_CONNECTED_SITE:
		return "CONNECTED_SITE";
	case NM_STATE_CONNECTED_GLOBAL:
		return "CONNECTED_GLOBAL";
	case NM_STATE_UNKNOWN:
	default:
		return "UNKNOWN";
	}
}

static NMState
find_best_device_state (NMManager *manager)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
	NMState best_state = NM_STATE_DISCONNECTED;
	NMActiveConnection *ac;

	c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
		NMActiveConnectionState ac_state = nm_active_connection_get_state (ac);

		switch (ac_state) {
		case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
			if (nm_active_connection_get_default (ac, AF_UNSPEC)) {
				if (priv->connectivity_state == NM_CONNECTIVITY_FULL)
					return NM_STATE_CONNECTED_GLOBAL;

				best_state = NM_STATE_CONNECTED_SITE;
			} else {
				if (best_state < NM_STATE_CONNECTING)
					best_state = NM_STATE_CONNECTED_LOCAL;
			}
			break;
		case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
			if (!NM_IN_SET (nm_active_connection_get_activation_type (ac),
			                NM_ACTIVATION_TYPE_EXTERNAL,
			                NM_ACTIVATION_TYPE_ASSUME)) {
				if (best_state != NM_STATE_CONNECTED_GLOBAL)
					best_state = NM_STATE_CONNECTING;
			}
			break;
		case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING:
			if (!NM_IN_SET (nm_active_connection_get_activation_type (ac),
			                NM_ACTIVATION_TYPE_EXTERNAL,
			                NM_ACTIVATION_TYPE_ASSUME)) {
				if (best_state < NM_STATE_DISCONNECTING)
					best_state = NM_STATE_DISCONNECTING;
			}
			break;
		default:
			break;
		}
	}

	return best_state;
}

static void
nm_manager_update_metered (NMManager *self)
{
	NMManagerPrivate *priv;
	NMDevice *device;
	NMMetered value = NM_METERED_UNKNOWN;

	g_return_if_fail (NM_IS_MANAGER (self));
	priv = NM_MANAGER_GET_PRIVATE (self);

	if (priv->primary_connection) {
		device =  nm_active_connection_get_device (priv->primary_connection);
		if (device)
			value = nm_device_get_metered (device);
	}

	if (value != priv->metered) {
		priv->metered = value;
		_LOGD (LOGD_CORE, "new metered value: %d", (int) priv->metered);
		_notify (self, PROP_METERED);
	}
}

NMMetered
nm_manager_get_metered (NMManager *self)
{
	g_return_val_if_fail (NM_IS_MANAGER (self), NM_METERED_UNKNOWN);

	return NM_MANAGER_GET_PRIVATE (self)->metered;
}

static void
nm_manager_update_state (NMManager *self)
{
	NMManagerPrivate *priv;
	NMState new_state = NM_STATE_DISCONNECTED;

	g_return_if_fail (NM_IS_MANAGER (self));

	priv = NM_MANAGER_GET_PRIVATE (self);

	if (manager_sleeping (self))
		new_state = NM_STATE_ASLEEP;
	else
		new_state = find_best_device_state (self);

	if (   new_state >= NM_STATE_CONNECTED_LOCAL
	    && priv->connectivity_state == NM_CONNECTIVITY_FULL) {
		new_state = NM_STATE_CONNECTED_GLOBAL;
	}

	if (priv->state == new_state)
		return;

	priv->state = new_state;

	_LOGI (LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string (new_state));

	_notify (self, PROP_STATE);
	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
	                            &interface_info_manager,
	                            &signal_info_state_changed,
	                            "(u)",
	                            (guint32) priv->state);
}

static void
manager_device_state_changed (NMDevice *device,
                              NMDeviceState new_state,
                              NMDeviceState old_state,
                              NMDeviceStateReason reason,
                              gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	if (   old_state == NM_DEVICE_STATE_UNMANAGED
	    && new_state > NM_DEVICE_STATE_UNMANAGED)
		retry_connections_for_parent_device (self, device);

	if (NM_IN_SET (new_state,
	               NM_DEVICE_STATE_UNMANAGED,
	               NM_DEVICE_STATE_UNAVAILABLE,
	               NM_DEVICE_STATE_DISCONNECTED,
	               NM_DEVICE_STATE_PREPARE,
	               NM_DEVICE_STATE_FAILED))
		_notify (self, PROP_ACTIVE_CONNECTIONS);

	if (NM_IN_SET (new_state,
	               NM_DEVICE_STATE_UNMANAGED,
	               NM_DEVICE_STATE_DISCONNECTED,
	               NM_DEVICE_STATE_ACTIVATED)) {
		nm_manager_write_device_state (self, device, NULL);

		G_STATIC_ASSERT_EXPR (DEVICE_STATE_PRUNE_RATELIMIT_MAX < G_MAXUINT8);
		if (priv->device_state_prune_ratelimit_count++ > DEVICE_STATE_PRUNE_RATELIMIT_MAX) {
			/* We write the device state to /run. The state files are named after the
			 * ifindex (which is assumed to be unique and not repeat -- in practice
			 * it may repeat). So from time to time, we prune device state files
			 * for interfaces that no longer exist.
			 *
			 * Otherwise, the files might pile up if you create (and destroy) a large
			 * number of software devices. */
			priv->device_state_prune_ratelimit_count = 0;
			nm_config_device_state_prune_stale (NULL, priv->platform);
		}
	}

	if (NM_IN_SET (new_state,
	               NM_DEVICE_STATE_UNAVAILABLE,
	               NM_DEVICE_STATE_DISCONNECTED))
		nm_settings_device_added (priv->settings, device);
}

static void device_has_pending_action_changed (NMDevice *device,
                                               GParamSpec *pspec,
                                               NMManager *self);

static void
check_if_startup_complete (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;
	const char *reason;

	if (!priv->startup)
		return;

	if (!priv->devices_inited)
		return;

	reason = nm_settings_get_startup_complete_blocked_reason (priv->settings);
	if (reason) {
		_LOGD (LOGD_CORE, "startup complete is waiting for connection (%s)",
		       reason);
		return;
	}

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		reason = nm_device_has_pending_action_reason (device);
		if (reason) {
			_LOGD (LOGD_CORE, "startup complete is waiting for device '%s' (%s)",
			       nm_device_get_iface (device),
			       reason);
			return;
		}
	}

	_LOGI (LOGD_CORE, "startup complete");

	priv->startup = FALSE;

	/* we no longer care about these signals. Startup-complete only
	 * happens once. */
	g_signal_handlers_disconnect_by_func (priv->settings, G_CALLBACK (settings_startup_complete_changed), self);
	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		g_signal_handlers_disconnect_by_func (device,
		                                      G_CALLBACK (device_has_pending_action_changed),
		                                      self);
	}

	_notify (self, PROP_STARTUP);

	if (nm_config_get_configure_and_quit (priv->config))
		g_signal_emit (self, signals[CONFIGURE_QUIT], 0);
}

static void
device_has_pending_action_changed (NMDevice *device,
                                   GParamSpec *pspec,
                                   NMManager *self)
{
	check_if_startup_complete (self);
}

static void
settings_startup_complete_changed (NMSettings *settings,
                                   GParamSpec *pspec,
                                   NMManager *self)
{
	check_if_startup_complete (self);
}

static void
_parent_notify_changed (NMManager *self,
                        NMDevice *device,
                        gboolean device_removed)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *candidate;

	nm_assert (NM_IS_DEVICE (device));

again:
	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
		if (nm_device_parent_notify_changed (candidate, device, device_removed)) {
			/* in the unlikely event that this changes anything, we start iterating
			 * again, to be sure that the device list is up-to-date. */
			goto again;
		}
	}
}

static gboolean
device_is_wake_on_lan (NMPlatform *platform, NMDevice *device)
{
	int ifindex;

	ifindex = nm_device_get_ip_ifindex (device);
	if (ifindex <= 0)
		return FALSE;
	return nm_platform_link_get_wake_on_lan (platform, ifindex);
}

static void
remove_device (NMManager *self,
               NMDevice *device,
               gboolean quitting)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gboolean unmanage = FALSE;

	_LOG2D (LOGD_DEVICE, device, "removing device (managed %d, wol %d)",
	        nm_device_get_managed (device, FALSE),
	        device_is_wake_on_lan (priv->platform, device));

	if (nm_device_get_managed (device, FALSE)) {

		if (quitting) {
			/* Leave configured if wo(w)lan and quitting */
			if (device_is_wake_on_lan (priv->platform, device))
				unmanage = FALSE;
			else
				unmanage = nm_device_unmanage_on_quit (device);
		} else {
			/* the device is already gone. Unmanage it. */
			unmanage = TRUE;
		}

		if (unmanage) {
			if (quitting)
				nm_device_set_unmanaged_by_quitting (device);
			else {
				nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
				nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, TRUE, NM_DEVICE_STATE_REASON_REMOVED);
			}
		} else if (quitting && nm_config_get_configure_and_quit (priv->config) == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) {
			nm_device_spawn_iface_helper (device);
		}
	}

	g_signal_handlers_disconnect_matched (device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);

	nm_settings_device_removed (priv->settings, device, quitting);

	c_list_unlink (&device->devices_lst);

	_parent_notify_changed (self, device, TRUE);

	if (nm_device_is_real (device)) {
		gboolean unconfigure_ip_config = !quitting || unmanage;

		/* When we don't unmanage the device on shutdown, we want to preserve the DNS
		 * configuration in resolv.conf. For that, we must leak the configuration
		 * in NMPolicy/NMDnsManager. We do that, by emitting the device-removed signal
		 * with device's ip-config object still uncleared. In that case, NMPolicy
		 * never learns to unconfigure the ip-config objects and does not remove them
		 * from DNS on shutdown (which is ugly, because we don't cleanup the memory
		 * properly).
		 *
		 * Control that by passing @unconfigure_ip_config.  */
		nm_device_removed (device, unconfigure_ip_config);

		_emit_device_added_removed (self, device, FALSE);
	} else {
		/* unrealize() does not release a slave device from master and
		 * clear IP configurations, do it here */
		nm_device_removed (device, TRUE);
	}

	g_signal_emit (self, signals[INTERNAL_DEVICE_REMOVED], 0, device);
	_notify (self, PROP_ALL_DEVICES);

	update_connectivity_value (self);

	nm_dbus_object_clear_and_unexport (&device);

	check_if_startup_complete (self);
}

static void
device_removed_cb (NMDevice *device, gpointer user_data)
{
	remove_device (NM_MANAGER (user_data), device, FALSE);
}

NMState
nm_manager_get_state (NMManager *manager)
{
	g_return_val_if_fail (NM_IS_MANAGER (manager), NM_STATE_UNKNOWN);

	return NM_MANAGER_GET_PRIVATE (manager)->state;
}

/*****************************************************************************/

static NMDevice *
find_parent_device_for_connection (NMManager *self,
                                   NMConnection *connection,
                                   NMDeviceFactory *cached_factory,
                                   const char **out_parent_spec)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDeviceFactory *factory;
	const char *parent_name = NULL;
	NMSettingsConnection *parent_connection;
	NMDevice *parent, *first_compatible = NULL;
	NMDevice *candidate;

	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
	NM_SET_OUT (out_parent_spec, NULL);

	if (!cached_factory) {
		factory = nm_device_factory_manager_find_factory_for_connection (connection);
		if (!factory)
			return NULL;
	} else
		factory = cached_factory;

	parent_name = nm_device_factory_get_connection_parent (factory, connection);
	if (!parent_name)
		return NULL;

	NM_SET_OUT (out_parent_spec, parent_name);

	/* Try as an interface name of a parent device */
	parent = find_device_by_iface (self, parent_name, NULL, NULL);
	if (parent)
		return parent;

	/* Maybe a hardware address */
	parent = find_device_by_permanent_hw_addr (self, parent_name);
	if (parent)
		return parent;

	/* Maybe a connection UUID */
	parent_connection = nm_settings_get_connection_by_uuid (priv->settings, parent_name);
	if (!parent_connection)
		return NULL;

	/* Check if the parent connection is currently activated or is compatible
	 * with some known device.
	 */
	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
		/* Unmanaged devices are not compatible with any connection */
		if (!nm_device_get_managed (candidate, FALSE))
			continue;

		if (nm_device_get_settings_connection (candidate) == parent_connection)
			return candidate;

		if (   !first_compatible
		    && nm_device_check_connection_compatible (candidate,
		                                              nm_settings_connection_get_connection (parent_connection),
		                                              NULL))
			first_compatible = candidate;
	}

	return first_compatible;
}

/**
 * nm_manager_get_connection_iface:
 * @self: the #NMManager
 * @connection: the #NMConnection to get the interface for
 * @out_parent: on success, the parent device if any
 * @out_parent_spec: on return, a string specifying the parent device
 *   in the connection. This can be a device name, a MAC address or a
 *   connection UUID.
 * @error: an error if determining the virtual interface name failed
 *
 * Given @connection, returns the interface name that the connection
 * would need to use when activated. %NULL is returned if the name
 * is not specified in connection or a the name for a virtual device
 * could not be generated.
 *
 * Returns: the expected interface name (caller takes ownership), or %NULL
 */
char *
nm_manager_get_connection_iface (NMManager *self,
                                 NMConnection *connection,
                                 NMDevice **out_parent,
                                 const char **out_parent_spec,
                                 GError **error)
{
	NMDeviceFactory *factory;
	char *iface = NULL;
	NMDevice *parent = NULL;

	NM_SET_OUT (out_parent, NULL);
	NM_SET_OUT (out_parent_spec, NULL);

	factory = nm_device_factory_manager_find_factory_for_connection (connection);
	if (!factory) {
		if (nm_streq0 (nm_connection_get_connection_type (connection), NM_SETTING_GENERIC_SETTING_NAME)) {
			/* the generic type doesn't have a factory. */
			goto return_ifname_fom_connection;
		}

		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_FAILED,
		             "NetworkManager plugin for '%s' unavailable",
		             nm_connection_get_connection_type (connection));
		return NULL;
	}

	if (   !out_parent
	    && !NM_DEVICE_FACTORY_GET_CLASS (factory)->get_connection_iface) {
		/* optimization. Shortcut lookup of the partent device. */
		goto return_ifname_fom_connection;
	}

	parent = find_parent_device_for_connection (self, connection, factory, out_parent_spec);
	iface = nm_device_factory_get_connection_iface (factory,
	                                                connection,
	                                                parent ? nm_device_get_ip_iface (parent) : NULL,
	                                                error);
	if (!iface)
		return NULL;

	if (out_parent)
		*out_parent = parent;
	return iface;

return_ifname_fom_connection:
	iface = g_strdup (nm_connection_get_interface_name (connection));
	if (!iface) {
		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_FAILED,
		             "failed to determine interface name: error determine name for %s",
		             nm_connection_get_connection_type (connection));
	}
	return iface;
}

/**
 * nm_manager_iface_for_uuid:
 * @self: the #NMManager
 * @uuid: the connection uuid
 *
 * Gets a link name for the given UUID. Useful for the settings plugins that
 * wish to write configuration files compatible with tooling that can't
 * interpret our UUIDs.
 *
 * Returns: An interface name; %NULL if none matches
 */
const char *
nm_manager_iface_for_uuid (NMManager *self, const char *uuid)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMSettingsConnection *sett_conn;

	sett_conn = nm_settings_get_connection_by_uuid (priv->settings, uuid);
	if (!sett_conn)
		return NULL;

	return nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn));
}

NMDevice *
nm_manager_get_device (NMManager *self, const char *ifname, NMDeviceType device_type)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	g_return_val_if_fail (ifname, NULL);
	g_return_val_if_fail (device_type != NM_DEVICE_TYPE_UNKNOWN, NULL);

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		if (   nm_device_get_device_type (device) == device_type
		    && nm_streq0 (nm_device_get_iface (device), ifname))
			return device;
	}

	return NULL;
}

gboolean
nm_manager_remove_device (NMManager *self, const char *ifname, NMDeviceType device_type)
{
	NMDevice *d;

	d = nm_manager_get_device (self, ifname, device_type);
	if (!d)
		return FALSE;

	remove_device (self, d, FALSE);
	return TRUE;
}

/**
 * system_create_virtual_device:
 * @self: the #NMManager
 * @connection: the connection which might require a virtual device
 *
 * If @connection requires a virtual device and one does not yet exist for it,
 * creates that device.
 *
 * Returns: A #NMDevice that was just realized; %NULL if none
 */
static NMDevice *
system_create_virtual_device (NMManager *self, NMConnection *connection)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDeviceFactory *factory;
	gs_free NMSettingsConnection **connections = NULL;
	guint i;
	gs_free char *iface = NULL;
	const char *parent_spec;
	NMDevice *device = NULL, *parent = NULL;
	NMDevice *dev_candidate;
	GError *error = NULL;
	NMLogLevel log_level;

	g_return_val_if_fail (NM_IS_MANAGER (self), NULL);
	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);

	iface = nm_manager_get_connection_iface (self, connection, &parent, &parent_spec, &error);
	if (!iface) {
		_LOG3D (LOGD_DEVICE, connection, "can't get a name of a virtual device: %s",
		        error->message);
		g_error_free (error);
		return NULL;
	}

	if (parent_spec && !parent) {
		/* parent is not ready, wait */
		return NULL;
	}

	/* See if there's a device that is already compatible with this connection */
	c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) {
		if (nm_device_check_connection_compatible (dev_candidate, connection, NULL)) {
			if (nm_device_is_real (dev_candidate)) {
				_LOG3D (LOGD_DEVICE, connection, "already created virtual interface name %s",
				       iface);
				return NULL;
			}

			device = dev_candidate;
			break;
		}
	}

	if (!device) {
		/* No matching device found. Proceed creating a new one. */

		factory = nm_device_factory_manager_find_factory_for_connection (connection);
		if (!factory) {
			_LOG3E (LOGD_DEVICE, connection, "(%s) NetworkManager plugin for '%s' unavailable",
			       iface,
			       nm_connection_get_connection_type (connection));
			return NULL;
		}

		device = nm_device_factory_create_device (factory, iface, NULL, connection, NULL, &error);
		if (!device) {
			_LOG3W (LOGD_DEVICE, connection, "factory can't create the device: %s",
			        error->message);
			g_error_free (error);
			return NULL;
		}

		_LOG3D (LOGD_DEVICE, connection, "create virtual device %s",
		       nm_device_get_iface (device));

		if (!add_device (self, device, &error)) {
			_LOG3W (LOGD_DEVICE, connection, "can't register the device with manager: %s",
			        error->message);
			g_error_free (error);
			g_object_unref (device);
			return NULL;
		}

		/* Add device takes a reference that NMManager still owns, so it's
		 * safe to unref here and still return @device.
		 */
		g_object_unref (device);
	}

	if (!nm_device_check_unrealized_device_managed (device)) {
		_LOG3D (LOGD_DEVICE, connection,
		        "skip activation because virtual device '%s' is unmanaged",
		        nm_device_get_iface (device));
		return device;
	}

	if (!find_master (self,
	                  connection,
	                  device,
	                  NULL,
	                  NULL,
	                  NULL,
	                  &error)) {
		_LOG3D (LOGD_DEVICE, connection,
		        "skip activation: %s",
		        error->message);
		g_error_free (error);
		return device;
	}

	/* Create backing resources if the device has any autoconnect connections */
	connections = nm_settings_get_connections_clone (priv->settings, NULL,
	                                                 NULL, NULL,
	                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
	for (i = 0; connections[i]; i++) {
		NMConnection *candidate = nm_settings_connection_get_connection (connections[i]);
		NMSettingConnection *s_con;

		if (!nm_device_check_connection_compatible (device, candidate, NULL))
			continue;

		s_con = nm_connection_get_setting_connection (candidate);
		g_assert (s_con);
		if (   !nm_setting_connection_get_autoconnect (s_con)
		    || nm_settings_connection_autoconnect_is_blocked (connections[i]))
			continue;

		/* Create any backing resources the device needs */
		if (!nm_device_create_and_realize (device, connection, parent, &error)) {
			log_level = g_error_matches (error,
			                             NM_DEVICE_ERROR,
			                             NM_DEVICE_ERROR_MISSING_DEPENDENCIES)
			            ? LOGL_DEBUG
			            : LOGL_ERR;
			_NMLOG3 (log_level, LOGD_DEVICE, connection,
			         "couldn't create the device: %s",
			         error->message);
			g_error_free (error);
			return NULL;
		}

		retry_connections_for_parent_device (self, device);
		break;
	}

	return device;
}

static void
retry_connections_for_parent_device (NMManager *self, NMDevice *device)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_free NMSettingsConnection **connections = NULL;
	guint i;

	g_return_if_fail (device);

	connections = nm_settings_get_connections_clone (priv->settings, NULL,
	                                                 NULL, NULL,
	                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
	for (i = 0; connections[i]; i++) {
		NMSettingsConnection *sett_conn = connections[i];
		NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
		gs_free_error GError *error = NULL;
		gs_free char *ifname = NULL;
		NMDevice *parent;

		parent = find_parent_device_for_connection (self, connection, NULL, NULL);
		if (parent == device) {
			/* Only try to activate devices that don't already exist */
			ifname = nm_manager_get_connection_iface (self, connection, &parent, NULL, &error);
			if (ifname) {
				if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname))
					connection_changed (self, sett_conn);
			}
		}
	}
}

static void
connection_changed (NMManager *self,
                    NMSettingsConnection *sett_conn)
{
	NMConnection *connection;
	NMDevice *device;

	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
		return;

	connection = nm_settings_connection_get_connection (sett_conn);

	if (!nm_connection_is_virtual (connection))
		return;

	device = system_create_virtual_device (self, connection);
	if (!device)
		return;

	/* Maybe the device that was created was needed by some other
	 * connection's device (parent of a VLAN). Let the connections
	 * can use the newly created device as a parent know. */
	retry_connections_for_parent_device (self, device);
}

static void
connection_added_cb (NMSettings *settings,
                     NMSettingsConnection *sett_conn,
                     NMManager *self)
{
	connection_changed (self, sett_conn);
}

static void
connection_updated_cb (NMSettings *settings,
                       NMSettingsConnection *sett_conn,
                       guint update_reason_u,
                       NMManager *self)
{
	connection_changed (self, sett_conn);
}

/*****************************************************************************/

static void
_delete_volatile_connection_all (NMManager *self, gboolean do_delete)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMCListElem *elem;

	while ((elem = c_list_first_entry (&priv->delete_volatile_connection_lst_head, NMCListElem, lst))) {
		gs_unref_object NMSettingsConnection *connection = NULL;

		connection = nm_c_list_elem_free_steal (elem);
		if (do_delete)
			_delete_volatile_connection_do (self, connection);
	}
}

static gboolean
_delete_volatile_connection_cb (gpointer user_data)
{
	NMManager *self = user_data;
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	priv->delete_volatile_connection_idle_id = 0;
	_delete_volatile_connection_all (self, TRUE);
	return G_SOURCE_REMOVE;
}

static void
connection_flags_changed (NMSettings *settings,
                          NMSettingsConnection *connection,
                          gpointer user_data)
{
	NMManager *self = user_data;
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
		return;

	if (active_connection_find (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, NULL)) {
		/* the connection still has an active-connection. It will be purged
		 * when the active connection(s) get(s) removed. */
		return;
	}

	c_list_link_tail (&priv->delete_volatile_connection_lst_head,
	                  &nm_c_list_elem_new_stale (g_object_ref (connection))->lst);
	if (!priv->delete_volatile_connection_idle_id)
		priv->delete_volatile_connection_idle_id = g_idle_add (_delete_volatile_connection_cb, self);
}

/*****************************************************************************/

static void
system_unmanaged_devices_changed_cb (NMSettings *settings,
                                     GParamSpec *pspec,
                                     gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
		nm_device_set_unmanaged_by_user_settings (device);
}

static void
hostname_changed_cb (NMHostnameManager *hostname_manager,
                     GParamSpec *pspec,
                     NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	const char *hostname;

	hostname = nm_hostname_manager_get_hostname (priv->hostname_manager);

	nm_dispatcher_call_hostname (NULL, NULL, NULL);
	nm_dhcp_manager_set_default_hostname (nm_dhcp_manager_get (), hostname);
}

/*****************************************************************************/
/* General NMManager stuff                                         */
/*****************************************************************************/

static gboolean
radio_enabled_for_rstate (RadioState *rstate, gboolean check_changeable)
{
	gboolean enabled;

	enabled = rstate->user_enabled && rstate->hw_enabled;
	if (check_changeable)
		enabled &= rstate->sw_enabled;
	return enabled;
}

static gboolean
radio_enabled_for_type (NMManager *self, RfKillType rtype, gboolean check_changeable)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	return radio_enabled_for_rstate (&priv->radio_states[rtype], check_changeable);
}

static void
manager_update_radio_enabled (NMManager *self,
                              RadioState *rstate,
                              gboolean enabled)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	/* Do nothing for radio types not yet implemented */
	if (!rstate->prop)
		return;

	g_object_notify (G_OBJECT (self), rstate->prop);

	/* Don't touch devices if asleep/networking disabled */
	if (manager_sleeping (self))
		return;

	/* enable/disable wireless devices as required */
	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		if (nm_device_get_rfkill_type (device) == rstate->rtype) {
			_LOG2D (LOGD_RFKILL, device, "rfkill: setting radio %s", enabled ? "enabled" : "disabled");
			nm_device_set_enabled (device, enabled);
		}
	}
}

static void
update_rstate_from_rfkill (NMRfkillManager *rfkill_mgr, RadioState *rstate)
{
	switch (nm_rfkill_manager_get_rfkill_state (rfkill_mgr, rstate->rtype)) {
	case RFKILL_UNBLOCKED:
		rstate->sw_enabled = TRUE;
		rstate->hw_enabled = TRUE;
		break;
	case RFKILL_SOFT_BLOCKED:
		rstate->sw_enabled = FALSE;
		rstate->hw_enabled = TRUE;
		break;
	case RFKILL_HARD_BLOCKED:
		rstate->sw_enabled = FALSE;
		rstate->hw_enabled = FALSE;
		break;
	default:
		g_warn_if_reached ();
		break;
	}
}

static void
manager_rfkill_update_one_type (NMManager *self,
                                RadioState *rstate,
                                RfKillType rtype)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gboolean old_enabled, new_enabled, old_rfkilled, new_rfkilled, old_hwe;

	old_enabled = radio_enabled_for_rstate (rstate, TRUE);
	old_rfkilled = rstate->hw_enabled && rstate->sw_enabled;
	old_hwe = rstate->hw_enabled;

	/* recheck kernel rfkill state */
	update_rstate_from_rfkill (priv->rfkill_mgr, rstate);

	/* Print out all states affecting device enablement */
	if (rstate->desc) {
		_LOGD (LOGD_RFKILL, "rfkill: %s hw-enabled %d sw-enabled %d",
		       rstate->desc, rstate->hw_enabled, rstate->sw_enabled);
	}

	/* Log new killswitch state */
	new_rfkilled = rstate->hw_enabled && rstate->sw_enabled;
	if (old_rfkilled != new_rfkilled) {
		_LOGI (LOGD_RFKILL, "rfkill: %s now %s by radio killswitch",
		       rstate->desc,
		       new_rfkilled ? "enabled" : "disabled");
	}

	/* Send out property changed signal for HW enabled */
	if (rstate->hw_enabled != old_hwe) {
		if (rstate->hw_prop)
			g_object_notify (G_OBJECT (self), rstate->hw_prop);
	}

	/* And finally update the actual device radio state itself; respect the
	 * daemon state here because this is never called from user-triggered
	 * radio changes and we only want to ignore the daemon enabled state when
	 * handling user radio change requests.
	 */
	new_enabled = radio_enabled_for_rstate (rstate, TRUE);
	if (new_enabled != old_enabled)
		manager_update_radio_enabled (self, rstate, new_enabled);
}

static void
nm_manager_rfkill_update (NMManager *self, RfKillType rtype)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	guint i;

	if (rtype != RFKILL_TYPE_UNKNOWN)
		manager_rfkill_update_one_type (self, &priv->radio_states[rtype], rtype);
	else {
		/* Otherwise sync all radio types */
		for (i = 0; i < RFKILL_TYPE_MAX; i++)
			manager_rfkill_update_one_type (self, &priv->radio_states[i], i);
	}
}

static void
device_auth_done_cb (NMAuthChain *chain,
                     GDBusMethodInvocation *context,
                     gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	gs_free_error GError *error = NULL;
	NMAuthCallResult result;
	NMDevice *device;
	const char *permission;
	NMDeviceAuthRequestFunc callback;
	NMAuthSubject *subject;

	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));

	c_list_unlink (nm_auth_chain_parent_lst_list (chain));

	permission = nm_auth_chain_get_data (chain, "perm");
	nm_assert (permission);
	callback = nm_auth_chain_get_data (chain, "callback");
	nm_assert (callback);
	device = nm_auth_chain_get_data (chain, "device");
	nm_assert (NM_IS_DEVICE (device));

	result = nm_auth_chain_get_result (chain, permission);
	subject = nm_auth_chain_get_subject (chain);

	if (result != NM_AUTH_CALL_RESULT_YES) {
		_LOGD (LOGD_CORE, "%s request failed: not authorized", permission);
		error = g_error_new (NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
		                     "%s request failed: not authorized",
		                     permission);
	}

	nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES));

	callback (device,
	          context,
	          subject,
	          error,
	          nm_auth_chain_get_data (chain, "user-data"));
}

static void
device_auth_request_cb (NMDevice *device,
                        GDBusMethodInvocation *context,
                        NMConnection *connection,
                        const char *permission,
                        gboolean allow_interaction,
                        NMDeviceAuthRequestFunc callback,
                        gpointer user_data,
                        NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	GError *error = NULL;
	NMAuthSubject *subject = NULL;
	NMAuthChain *chain;
	char *permission_dup;

	/* Validate the caller */
	subject = nm_auth_subject_new_unix_process_from_context (context);
	if (!subject) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
		goto done;
	}

	/* Ensure the subject has permissions for this connection */
	if (   connection
	    && !nm_auth_is_subject_in_acl_set_error (connection,
	                                             subject,
	                                             NM_MANAGER_ERROR,
	                                             NM_MANAGER_ERROR_PERMISSION_DENIED,
	                                             &error))
		goto done;

	/* Validate the request */
	chain = nm_auth_chain_new_subject (subject, context, device_auth_done_cb, self);
	if (!chain) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		goto done;
	}

	permission_dup = g_strdup (permission);

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "device", g_object_ref (device), g_object_unref);
	nm_auth_chain_set_data (chain, "callback", callback, NULL);
	nm_auth_chain_set_data (chain, "user-data", user_data, NULL);
	nm_auth_chain_set_data (chain, "perm", permission_dup /* transfer ownership */, g_free);
	nm_auth_chain_add_call_unsafe (chain, permission_dup, allow_interaction);

done:
	if (error)
		callback (device, context, subject, error, user_data);

	g_clear_object (&subject);
	g_clear_error (&error);
}

static gboolean
new_activation_allowed_for_connection (NMManager *self,
                                       NMSettingsConnection *connection)
{
	if (NM_IN_SET (_nm_connection_get_multi_connect (nm_settings_connection_get_connection (connection)),
	               NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE,
	               NM_CONNECTION_MULTI_CONNECT_MULTIPLE))
		return TRUE;

	return !active_connection_find (self, connection, NULL,
	                                NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
	                                NULL);
}

/**
 * get_existing_connection:
 * @manager: #NMManager instance
 * @device: #NMDevice instance
 * @out_generated: (allow-none): return TRUE, if the connection was generated.
 *
 * Returns: a #NMSettingsConnection to be assumed by the device, or %NULL if
 *   the device does not support assuming existing connections.
 */
static NMSettingsConnection *
get_existing_connection (NMManager *self,
                         NMDevice *device,
                         gboolean *out_generated)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_unref_object NMConnection *connection = NULL;
	NMSettingsConnection *added;
	GError *error = NULL;
	gs_free_error GError *gen_error = NULL;
	NMDevice *master = NULL;
	int ifindex = nm_device_get_ifindex (device);
	NMSettingsConnection *matched = NULL;
	NMSettingsConnection *connection_checked = NULL;
	gboolean assume_state_guess_assume = FALSE;
	const char *assume_state_connection_uuid = NULL;
	gboolean maybe_later, only_by_uuid = FALSE;

	if (out_generated)
		*out_generated = FALSE;

	nm_device_capture_initial_config (device);

	if (ifindex) {
		int master_ifindex = nm_platform_link_get_master (priv->platform, ifindex);

		/* Check that the master is activating before assuming a
		 * slave connection. However, ignore ovs-system master as
		 * we never manage it.
		 */
		if (   master_ifindex
		    && nm_platform_link_get_type (priv->platform, master_ifindex) != NM_LINK_TYPE_OPENVSWITCH) {
			master = nm_manager_get_device_by_ifindex (self, master_ifindex);
			if (!master) {
				_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
				        "cannot generate connection for slave before its master (%s/%d)",
				        nm_platform_link_get_name (priv->platform, master_ifindex), master_ifindex);
				return NULL;
			}
			if (!nm_device_get_act_request (master)) {
				_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
				        "cannot generate connection for slave before master %s activates",
				        nm_device_get_iface (master));
				return NULL;
			}
		}
	}

	/* The core of the API is nm_device_generate_connection() function and
	 * update_connection() virtual method and the convenient connection_type
	 * class attribute. Subclasses supporting the new API must have
	 * update_connection() implemented, otherwise nm_device_generate_connection()
	 * returns NULL.
	 */
	connection = nm_device_generate_connection (device, master, &maybe_later, &gen_error);
	if (!connection) {
		if (maybe_later) {
			/* The device can generate a connection, but it failed for now.
			 * Give it a chance to match a connection from the state file. */
			only_by_uuid = TRUE;
		} else {
			nm_device_assume_state_reset (device);
			_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
			        gen_error->message);
			return NULL;
		}
	}

	nm_device_assume_state_get (device,
	                            &assume_state_guess_assume,
	                            &assume_state_connection_uuid);

	/* Now we need to compare the generated connection to each configured
	 * connection. The comparison function is the heart of the connection
	 * assumption implementation and it must compare the connections very
	 * carefully to sort out various corner cases. Also, the comparison is
	 * not entirely symmetric.
	 *
	 * When no configured connection matches the generated connection, we keep
	 * the generated connection instead.
	 */
	if (   assume_state_connection_uuid
	    && (connection_checked = nm_settings_get_connection_by_uuid (priv->settings, assume_state_connection_uuid))
	    && new_activation_allowed_for_connection (self, connection_checked)
	    && nm_device_check_connection_compatible (device,
	                                              nm_settings_connection_get_connection (connection_checked),
	                                              NULL)) {

		if (connection) {
			NMConnection *con = nm_settings_connection_get_connection (connection_checked);

			if (nm_utils_match_connection ((NMConnection *[]) { con, NULL },
			                               connection,
			                               TRUE,
			                               nm_device_has_carrier (device),
			                               nm_device_get_route_metric (device, AF_INET),
			                               nm_device_get_route_metric (device, AF_INET6),
			                               NULL, NULL))
				matched = connection_checked;
		} else
			matched = connection_checked;
	}

	if (!matched && only_by_uuid) {
		_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
		        gen_error->message);
		return NULL;
	}

	if (!matched && assume_state_guess_assume) {
		gs_free NMSettingsConnection **sett_conns = NULL;
		guint len, i, j;

		/* the state file doesn't indicate a connection UUID to assume. Search the
		 * persistent connections for a matching candidate. */
		sett_conns = nm_manager_get_activatable_connections (self, FALSE, FALSE, &len);
		if (len > 0) {
			for (i = 0, j = 0; i < len; i++) {
				NMSettingsConnection *sett_conn = sett_conns[i];

				if (   sett_conn != connection_checked
				    && nm_device_check_connection_compatible (device,
				                                              nm_settings_connection_get_connection (sett_conn),
				                                              NULL))
					sett_conns[j++] = sett_conn;
			}
			sett_conns[j] = NULL;
			len = j;
			if (len > 0) {
				gs_free NMConnection **conns = NULL;
				NMConnection *con;

				g_qsort_with_data (sett_conns, len, sizeof (sett_conns[0]),
				                   nm_settings_connection_cmp_timestamp_p_with_data, NULL);

				conns = nm_settings_connections_array_to_connections (sett_conns, len);

				con = nm_utils_match_connection (conns,
				                                 connection,
				                                 FALSE,
				                                 nm_device_has_carrier (device),
				                                 nm_device_get_route_metric (device, AF_INET),
				                                 nm_device_get_route_metric (device, AF_INET6),
				                                 NULL,
				                                 NULL);
				if (con) {
					for (i = 0; i < len; i++) {
						if (conns[i] == con) {
							matched = sett_conns[i];
							break;
						}
					}
					nm_assert (matched);
				}
			}
		}
	}

	if (matched) {
		_LOG2I (LOGD_DEVICE, device, "assume: will attempt to assume matching connection '%s' (%s)%s",
		        nm_settings_connection_get_id (matched),
		        nm_settings_connection_get_uuid (matched),
		        assume_state_connection_uuid && nm_streq (assume_state_connection_uuid, nm_settings_connection_get_uuid (matched))
		            ? " (indicated)" : " (guessed)");
		nm_device_assume_state_reset (device);
		return matched;
	}

	_LOG2D (LOGD_DEVICE, device, "assume: generated connection '%s' (%s)",
	        nm_connection_get_id (connection),
	        nm_connection_get_uuid (connection));

	nm_device_assume_state_reset (device);

	if (!nm_settings_add_connection (priv->settings,
	                                 connection,
	                                 NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
	                                 NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
	                                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
	                                 | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
	                                 &added,
	                                 &error)) {
		_LOG2W (LOGD_SETTINGS, device, "assume: failure to save generated connection '%s': %s",
		       nm_connection_get_id (connection),
		       error->message);
		g_error_free (error);
		return NULL;
	}

	NM_SET_OUT (out_generated, TRUE);
	return added;
}

static gboolean
copy_lease (const char *src, const char *dst)
{
	nm_auto_close int src_fd = -1;
	int dst_fd;
	ssize_t res, size = SSIZE_MAX;

	src_fd = open (src, O_RDONLY|O_CLOEXEC);
	if (src_fd < 0)
		return FALSE;

	dst_fd = open (dst, O_CREAT|O_EXCL|O_CLOEXEC|O_WRONLY, 0644);
	if (dst_fd < 0)
		return FALSE;

	while ((res = sendfile (dst_fd, src_fd, NULL, size)) > 0)
		size -= res;

	nm_close (dst_fd);

	if (res != 0) {
		unlink (dst);
		return FALSE;
	}

	return TRUE;
}

static gboolean
recheck_assume_connection (NMManager *self,
                           NMDevice *device)
{
	NMSettingsConnection *sett_conn;
	gboolean was_unmanaged = FALSE;
	gboolean generated = FALSE;
	NMDeviceState state;
	gboolean activation_type_assume;

	g_return_val_if_fail (NM_IS_MANAGER (self), FALSE);
	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);

	if (!nm_device_get_managed (device, FALSE)) {
		nm_device_assume_state_reset (device);
		_LOG2D (LOGD_DEVICE, device, "assume: don't assume because %s", "not managed");
		return FALSE;
	}

	state = nm_device_get_state (device);
	if (state > NM_DEVICE_STATE_DISCONNECTED) {
		nm_device_assume_state_reset (device);
		_LOG2D (LOGD_DEVICE, device, "assume: don't assume due to device state %s",
		        nm_device_state_to_str (state));
		return FALSE;
	}

	sett_conn = get_existing_connection (self, device, &generated);
	/* log  no reason. get_existing_connection() already does it. */
	if (!sett_conn)
		return FALSE;

	activation_type_assume = !generated;

	if (state == NM_DEVICE_STATE_UNMANAGED) {
		gs_free char *initramfs_lease = g_strdup_printf (RUNSTATEDIR "/initramfs/net.%s.lease",
		                                                 nm_device_get_iface (device));
		gs_free char *connection_lease = g_strdup_printf (NMRUNDIR "/dhclient-%s-%s.lease",
		                                                  nm_settings_connection_get_uuid (sett_conn),
		                                                  nm_device_get_iface (device));

		if (copy_lease (initramfs_lease, connection_lease)) {
			unlink (initramfs_lease);
			/*
			 * We've managed to steal the lease used by initramfs before it
			 * killed off the dhclient. We need to take ownership of the configured
			 * connection and act like the device was configured by us.
			 * Otherwise the address would just expire.
			 */
			_LOG2I (LOGD_DEVICE, device, "assume: taking over an initramfs-configured connection");
			activation_type_assume = TRUE;

			if (generated) {
				/* Reset the IPv4 setting to empty method=auto, regardless of what assumption guessed. */
				nm_connection_add_setting (nm_settings_connection_get_connection (sett_conn),
				                           g_object_new (NM_TYPE_SETTING_IP4_CONFIG,
				                                         NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
				                                         NULL));

				nm_settings_connection_update (sett_conn,
				                               NULL,
				                               NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
				                               0,
				                               NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
				                               NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
				                               "assume-initrd",
				                               NULL);
			}
		}
	}

	nm_device_sys_iface_state_set (device,
	                                 activation_type_assume
	                               ? NM_DEVICE_SYS_IFACE_STATE_ASSUME
	                               : NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);

	/* Move device to DISCONNECTED to activate the connection */
	if (state == NM_DEVICE_STATE_UNMANAGED) {
		was_unmanaged = TRUE;
		nm_device_state_changed (device,
		                         NM_DEVICE_STATE_UNAVAILABLE,
		                         NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
	}
	if (nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE) {
		nm_device_state_changed (device,
		                         NM_DEVICE_STATE_DISCONNECTED,
		                         NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
	}

	g_return_val_if_fail (nm_device_get_state (device) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);

	{
		gs_unref_object NMActiveConnection *active = NULL;
		gs_unref_object NMAuthSubject *subject = NULL;
		NMActiveConnection *master_ac;
		GError *error = NULL;

		subject = nm_auth_subject_new_internal ();

		/* Note: the lifetime of the activation connection is always bound to the profiles visibility
		 * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY.
		 *
		 * This only makes a difference, if the profile actually has "connection.permissions"
		 * set to limit visibility (which is not the case for externally managed, generated profiles).
		 *
		 * If we assume a previously active connection whose lifetime was unbound, we now bind it
		 * after restart. That is not correct, and can mean that the profile becomes subject to
		 * deactivation after restart (if the user logs out).
		 *
		 * This should be improved, but it's unclear how. */
		active = _new_active_connection (self,
		                                 FALSE,
		                                 sett_conn,
		                                 NULL,
		                                 NULL,
		                                 NULL,
		                                 device,
		                                 subject,
		                                 activation_type_assume ? NM_ACTIVATION_TYPE_ASSUME : NM_ACTIVATION_TYPE_EXTERNAL,
		                                 activation_type_assume ? NM_ACTIVATION_REASON_ASSUME : NM_ACTIVATION_REASON_EXTERNAL,
		                                 NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
		                                 &error);

		if (!active) {
			_LOGW (LOGD_DEVICE, "assume: assumed connection %s failed to activate: %s",
			       nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)),
			       error->message);
			g_error_free (error);

			if (was_unmanaged) {
				nm_device_state_changed (device,
				                         NM_DEVICE_STATE_UNAVAILABLE,
				                         NM_DEVICE_STATE_REASON_CONFIG_FAILED);
			}

			if (   generated
			    && !activation_type_assume) {
				_LOG2D (LOGD_DEVICE, device, "assume: deleting generated connection after assuming failed");
				nm_settings_connection_delete (sett_conn, FALSE);
			} else {
				if (nm_device_sys_iface_state_get (device) == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
					nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
			}
			return FALSE;
		}

		/* If the device is a slave or VLAN, find the master ActiveConnection */
		master_ac = NULL;
		if (   find_master (self,
		                    nm_settings_connection_get_connection (sett_conn),
		                    device,
		                    NULL,
		                    NULL,
		                    &master_ac,
		                    NULL)
		    && master_ac)
			nm_active_connection_set_master (active, master_ac);

		active_connection_add (self, active);
		nm_device_queue_activation (device, NM_ACT_REQUEST (active));
	}

	return TRUE;
}

static void
recheck_assume_connection_cb (NMManager *self, NMDevice *device)
{
	recheck_assume_connection (self, device);
}

static void
device_ifindex_changed (NMDevice *device,
                        GParamSpec *pspec,
                        NMManager *self)
{
	_parent_notify_changed (self, device, FALSE);
}

static void
device_ip_iface_changed (NMDevice *device,
                         GParamSpec *pspec,
                         NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	const char *ip_iface = nm_device_get_ip_iface (device);
	NMDeviceType device_type = nm_device_get_device_type (device);
	NMDevice *candidate;

	/* Remove NMDevice objects that are actually child devices of others,
	 * when the other device finally knows its IP interface name.  For example,
	 * remove the PPP interface that's a child of a WWAN device, since it's
	 * not really a standalone NMDevice.
	 */
	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
		if (   candidate != device
		    && g_strcmp0 (nm_device_get_iface (candidate), ip_iface) == 0
		    && nm_device_get_device_type (candidate) == device_type
		    && nm_device_is_real (candidate)) {
			remove_device (self, candidate, FALSE);
			break;
		}
	}
}

static void
device_iface_changed (NMDevice *device,
                      GParamSpec *pspec,
                      NMManager *self)
{
	/* Virtual connections may refer to the new device name as
	 * parent device, retry to activate them.
	 */
	retry_connections_for_parent_device (self, device);
}

static void
_emit_device_added_removed (NMManager *self,
                            NMDevice *device,
                            gboolean is_added)
{
	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
	                            &interface_info_manager,
	                            is_added
	                              ? &signal_info_device_added
	                              : &signal_info_device_removed,
	                            "(o)",
	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (device)));
	g_signal_emit (self,
	               signals[is_added ? DEVICE_ADDED : DEVICE_REMOVED],
	               0,
	               device);
	_notify (self, PROP_DEVICES);
}

static void
device_realized (NMDevice *device,
                 GParamSpec *pspec,
                 NMManager *self)
{
	_emit_device_added_removed (self, device, nm_device_is_real (device));
}

static NMConnectivityState
_get_best_connectivity (NMManager *self, int addr_family)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMConnectivityState best_state;
	NMDevice *dev;
	gint64 best_metric;

	if (addr_family == AF_UNSPEC) {
		best_state = _get_best_connectivity (self, AF_INET);
		if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
			/* already FULL IPv4 connectivity. No need to check IPv6, it doesn't get
			 * better. */
			return best_state;
		}
		return NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
		                        best_state,
		                        _get_best_connectivity (self, AF_INET6));
	}

	nm_assert_addr_family (addr_family);

	best_state = NM_CONNECTIVITY_UNKNOWN;
	best_metric = G_MAXINT64;
	c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
		const NMPObject *r;
		NMConnectivityState state;
		gint64 metric;

		r = nm_device_get_best_default_route (dev, addr_family);
		if (r) {
			metric = nm_utils_ip_route_metric_normalize (addr_family,
			                                             NMP_OBJECT_CAST_IP_ROUTE (r)->metric);
		} else {
			/* if all devices have no default-route, we still include the best
			 * of all connectivity state of all the devices. */
			metric = G_MAXINT64;
		}

		if (metric > best_metric) {
			/* we already have a default route with better metric. The connectivity state
			 * of this device is irreleavnt. */
			continue;
		}

		state = nm_device_get_connectivity_state (dev, addr_family);
		if (metric < best_metric) {
			/* this device has a better default route. It wins. */
			best_metric = metric;
			best_state = state;
		} else {
			best_state = NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
			                              best_state,
			                              state);
		}

		if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
			/* it doesn't get better than FULL. We are done. */
			break;
		}
	}

	return best_state;
}

static void
device_connectivity_changed (NMDevice *device,
                             GParamSpec *pspec,
                             NMManager *self)
{
	update_connectivity_value (self);
}

static void
update_connectivity_value (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMConnectivityState best_state;

	best_state = _get_best_connectivity (self, AF_UNSPEC);
	if (best_state == priv->connectivity_state)
		return;

	priv->connectivity_state = best_state;

	_LOGD (LOGD_CORE, "connectivity checking indicates %s",
	       nm_connectivity_state_to_string (priv->connectivity_state));

	nm_manager_update_state (self);
	_notify (self, PROP_CONNECTIVITY);
	nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL);
}

static void
_device_realize_finish (NMManager *self,
                        NMDevice *device,
                        const NMPlatformLink *plink)
{
	g_return_if_fail (NM_IS_MANAGER (self));
	g_return_if_fail (NM_IS_DEVICE (device));

	nm_device_realize_finish (device, plink);

	if (!nm_device_get_managed (device, FALSE)) {
		nm_device_assume_state_reset (device);
		return;
	}

	if (recheck_assume_connection (self, device))
		return;

	/* if we failed to assume a connection for the managed device, but the device
	 * is still unavailable. Set UNAVAILABLE state again, this time with NOW_MANAGED. */
	nm_device_state_changed (device,
	                         NM_DEVICE_STATE_UNAVAILABLE,
	                         NM_DEVICE_STATE_REASON_NOW_MANAGED);
	nm_device_emit_recheck_auto_activate (device);
}

/**
 * add_device:
 * @self: the #NMManager
 * @device: the #NMDevice to add
 * @error: (out): the #GError
 *
 * If successful, this function will increase the references count of @device.
 * Callers should decrease the reference count.
 */
static gboolean
add_device (NMManager *self, NMDevice *device, GError **error)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	const char *iface, *type_desc;
	RfKillType rtype;
	GSList *iter, *remove = NULL;
	int ifindex;
	const char *dbus_path;
	NMDevice *candidate;

	/* No duplicates */
	ifindex = nm_device_get_ifindex (device);
	if (ifindex > 0 && nm_manager_get_device_by_ifindex (self, ifindex)) {
		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
		             "A device with ifindex %d already exists", ifindex);
		return FALSE;
	}

	/* Remove existing devices owned by the new device; eg remove ethernet
	 * ports that are owned by a WWAN modem, since udev may announce them
	 * before the modem is fully discovered.
	 *
	 * FIXME: use parent/child device relationships instead of removing
	 * the child NMDevice entirely
	 */
	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
		if (   nm_device_is_real (candidate)
		    && (iface = nm_device_get_ip_iface (candidate))
		    && nm_device_owns_iface (device, iface))
			remove = g_slist_prepend (remove, candidate);
	}
	for (iter = remove; iter; iter = iter->next)
		remove_device (self, NM_DEVICE (iter->data), FALSE);
	g_slist_free (remove);

	g_object_ref (device);

	nm_assert (c_list_is_empty (&device->devices_lst));
	c_list_link_tail (&priv->devices_lst_head, &device->devices_lst);

	g_signal_connect (device, NM_DEVICE_STATE_CHANGED,
	                  G_CALLBACK (manager_device_state_changed),
	                  self);

	g_signal_connect (device, NM_DEVICE_AUTH_REQUEST,
	                  G_CALLBACK (device_auth_request_cb),
	                  self);

	g_signal_connect (device, NM_DEVICE_REMOVED,
	                  G_CALLBACK (device_removed_cb),
	                  self);

	g_signal_connect_data (device, NM_DEVICE_RECHECK_ASSUME,
	                       G_CALLBACK (recheck_assume_connection_cb),
	                       self, NULL, G_CONNECT_SWAPPED);

	g_signal_connect (device, "notify::" NM_DEVICE_IP_IFACE,
	                  G_CALLBACK (device_ip_iface_changed),
	                  self);

	g_signal_connect (device, "notify::" NM_DEVICE_IFINDEX,
	                  G_CALLBACK (device_ifindex_changed),
	                  self);

	g_signal_connect (device, "notify::" NM_DEVICE_IFACE,
	                  G_CALLBACK (device_iface_changed),
	                  self);

	g_signal_connect (device, "notify::" NM_DEVICE_REAL,
	                  G_CALLBACK (device_realized),
	                  self);

	g_signal_connect (device, "notify::" NM_DEVICE_IP4_CONNECTIVITY,
	                  G_CALLBACK (device_connectivity_changed),
	                  self);
	g_signal_connect (device, "notify::" NM_DEVICE_IP6_CONNECTIVITY,
	                  G_CALLBACK (device_connectivity_changed),
	                  self);

	if (priv->startup) {
		g_signal_connect (device, "notify::" NM_DEVICE_HAS_PENDING_ACTION,
		                  G_CALLBACK (device_has_pending_action_changed),
		                  self);
	}

	/* Update global rfkill state for this device type with the device's
	 * rfkill state, and then set this device's rfkill state based on the
	 * global state.
	 */
	rtype = nm_device_get_rfkill_type (device);
	if (rtype != RFKILL_TYPE_UNKNOWN) {
		nm_manager_rfkill_update (self, rtype);
		nm_device_set_enabled (device, radio_enabled_for_type (self, rtype, TRUE));
	}

	iface = nm_device_get_iface (device);
	g_assert (iface);
	type_desc = nm_device_get_type_desc (device);
	g_assert (type_desc);

	nm_device_set_unmanaged_by_user_settings (device);

	nm_device_set_unmanaged_flags (device,
	                               NM_UNMANAGED_SLEEPING,
	                               manager_sleeping (self));

	dbus_path = nm_dbus_object_export (NM_DBUS_OBJECT (device));
	_LOG2I (LOGD_DEVICE, device, "new %s device (%s)", type_desc, dbus_path);

	nm_settings_device_added (priv->settings, device);
	g_signal_emit (self, signals[INTERNAL_DEVICE_ADDED], 0, device);
	_notify (self, PROP_ALL_DEVICES);

	_parent_notify_changed (self, device, FALSE);

	return TRUE;
}

/*****************************************************************************/

static void
factory_device_added_cb (NMDeviceFactory *factory,
                         NMDevice *device,
                         gpointer user_data)
{
	NMManager *self = user_data;
	GError *error = NULL;

	g_return_if_fail (NM_IS_MANAGER (self));

	if (nm_device_realize_start (device,
	                             NULL,
	                             FALSE, /* assume_state_guess_assume */
	                             NULL,  /* assume_state_connection_uuid */
	                             FALSE, /* set_nm_owned */
	                             NM_UNMAN_FLAG_OP_FORGET,
	                             NULL,
	                             &error)) {
		add_device (self, device, NULL);
		_device_realize_finish (self, device, NULL);
		retry_connections_for_parent_device (self, device);
	} else {
		_LOG2W (LOGD_DEVICE, device, "failed to realize device: %s", error->message);
		g_error_free (error);
	}
}

static void
_register_device_factory (NMDeviceFactory *factory, gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);

	g_signal_connect (factory,
	                  NM_DEVICE_FACTORY_DEVICE_ADDED,
	                  G_CALLBACK (factory_device_added_cb),
	                  self);
}

/*****************************************************************************/

void
nm_manager_notify_device_availibility_maybe_changed (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
		nm_device_notify_availability_maybe_changed (device);
}

/*****************************************************************************/

static void
platform_link_added (NMManager *self,
                     int ifindex,
                     const NMPlatformLink *plink,
                     gboolean guess_assume,
                     const NMConfigDeviceStateData *dev_state)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDeviceFactory *factory;
	NMDevice *device = NULL;
	NMDevice *candidate;

	g_return_if_fail (ifindex > 0);

	if (nm_manager_get_device_by_ifindex (self, ifindex))
		return;

	/* Let unrealized devices try to realize themselves with the link */
	c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
		gboolean compatible = TRUE;
		gs_free_error GError *error = NULL;

		if (nm_device_get_link_type (candidate) != plink->type)
			continue;

		if (strcmp (nm_device_get_iface (candidate), plink->name))
			continue;

		if (nm_device_is_real (candidate)) {
			/* There's already a realized device with the link's name
			 * and a different ifindex.
			 */
			if (nm_device_get_ifindex (candidate) <= 0)
				nm_device_update_from_platform_link (candidate, plink);
			else {
				/* The ifindex of a device can't be changed after
				 * initialization because it is used as a key by
				 * the dns-manager.
				 */
				_LOGD (LOGD_DEVICE, "(%s): removing old device %p after ifindex change from %d to %d",
				       plink->name, candidate, nm_device_get_ifindex (candidate), ifindex);
				remove_device (self, candidate, FALSE);
				goto add;
			}
			return;
		} else if (nm_device_realize_start (candidate,
		                                    plink,
		                                    FALSE, /* assume_state_guess_assume */
		                                    NULL,  /* assume_state_connection_uuid */
		                                    FALSE, /* set_nm_owned */
		                                    NM_UNMAN_FLAG_OP_FORGET,
		                                    &compatible,
		                                    &error)) {
			_device_realize_finish (self, candidate, plink);
			return;
		}

		_LOGD (LOGD_DEVICE, "(%s): failed to realize from plink: '%s'",
		       plink->name, error->message);

		/* Try next unrealized device */
	}

add:
	/* Try registered device factories */
	factory = nm_device_factory_manager_find_factory_for_link_type (plink->type);
	if (factory) {
		gboolean ignore = FALSE;
		gs_free_error GError *error = NULL;

		device = nm_device_factory_create_device (factory, plink->name, plink, NULL, &ignore, &error);
		if (!device) {
			if (!ignore) {
				_LOGW (LOGD_PLATFORM, "%s: factory failed to create device: %s",
				       plink->name, error->message);
			} else {
				_LOGD (LOGD_PLATFORM, "%s: factory failed to create device: %s",
				       plink->name, error->message);
			}
			return;
		}
	}

	if (device == NULL) {
		gboolean nm_plugin_missing = FALSE;

		switch (plink->type) {
		case NM_LINK_TYPE_WWAN_NET:
		case NM_LINK_TYPE_BNEP:
		case NM_LINK_TYPE_OLPC_MESH:
		case NM_LINK_TYPE_TEAM:
		case NM_LINK_TYPE_WIFI:
			_LOGI (LOGD_PLATFORM, "(%s): '%s' plugin not available; creating generic device",
			       plink->name, nm_link_type_to_string (plink->type));
			nm_plugin_missing = TRUE;
			/* fall-through */
		default:
			device = nm_device_generic_new (plink, nm_plugin_missing);
			break;
		}
	}

	if (device) {
		gs_free_error GError *error = NULL;
		NMUnmanFlagOp unmanaged_user_explicit = NM_UNMAN_FLAG_OP_FORGET;

		if (dev_state) {
			switch (dev_state->managed) {
			case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED:
				unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_MANAGED;
				break;
			case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED:
				unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_UNMANAGED;
				break;
			case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN:
				break;
			}
		}

		if (nm_device_realize_start (device,
		                             plink,
		                             guess_assume,
		                             dev_state ? dev_state->connection_uuid : NULL,
		                             dev_state ? (dev_state->nm_owned == 1) : FALSE,
		                             unmanaged_user_explicit,
		                             NULL,
		                             &error)) {
			add_device (self, device, NULL);
			_device_realize_finish (self, device, plink);
			retry_connections_for_parent_device (self, device);
		} else {
			_LOGW (LOGD_DEVICE, "%s: failed to realize device: %s",
			       plink->name, error->message);
		}
		g_object_unref (device);
	}
}

typedef struct {
	CList lst;
	NMManager *self;
	int ifindex;
	guint idle_id;
} PlatformLinkCbData;

static gboolean
_platform_link_cb_idle (PlatformLinkCbData *data)
{
	int ifindex = data->ifindex;
	NMManager *self = data->self;
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	const NMPlatformLink *plink;

	c_list_unlink_stale (&data->lst);
	g_slice_free (PlatformLinkCbData, data);

	plink = nm_platform_link_get (priv->platform, ifindex);
	if (plink) {
		const NMPObject *plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));

		platform_link_added (self, ifindex, plink, FALSE, NULL);
		nmp_object_unref (plink_keep_alive);
	} else {
		NMDevice *device;
		GError *error = NULL;

		device = nm_manager_get_device_by_ifindex (self, ifindex);
		if (device) {
			if (nm_device_is_software (device)) {
				nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
				/* Our software devices stick around until their connection is removed */
				if (!nm_device_unrealize (device, FALSE, &error)) {
					_LOG2W (LOGD_DEVICE, device, "failed to unrealize: %s", error->message);
					g_clear_error (&error);
					remove_device (self, device, FALSE);
				} else {
					nm_device_update_from_platform_link (device, NULL);
				}
			} else {
				/* Hardware and external devices always get removed when their kernel link is gone */
				remove_device (self, device, FALSE);
			}
		}
	}

	return G_SOURCE_REMOVE;
}

static void
platform_link_cb (NMPlatform *platform,
                  int obj_type_i,
                  int ifindex,
                  NMPlatformLink *plink,
                  int change_type_i,
                  gpointer user_data)
{
	NMManager *self;
	NMManagerPrivate *priv;
	const NMPlatformSignalChangeType change_type = change_type_i;
	PlatformLinkCbData *data;

	switch (change_type) {
	case NM_PLATFORM_SIGNAL_ADDED:
	case NM_PLATFORM_SIGNAL_REMOVED:
		self = NM_MANAGER (user_data);
		priv = NM_MANAGER_GET_PRIVATE (self);

		data = g_slice_new (PlatformLinkCbData);
		data->self = self;
		data->ifindex = ifindex;
		c_list_link_tail (&priv->link_cb_lst, &data->lst);
		data->idle_id = g_idle_add ((GSourceFunc) _platform_link_cb_idle, data);
		break;
	default:
		break;
	}
}

static void
platform_query_devices (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_unref_ptrarray GPtrArray *links = NULL;
	int i;
	gboolean guess_assume;
	gs_free char *order = NULL;

	guess_assume = nm_config_get_first_start (nm_config_get ());
	order = nm_config_data_get_value (NM_CONFIG_GET_DATA,
	                                  NM_CONFIG_KEYFILE_GROUP_MAIN,
	                                  NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
	                                  NM_CONFIG_GET_VALUE_STRIP);
	links = nm_platform_link_get_all (priv->platform, !nm_streq0 (order, "index"));
	if (!links)
		return;
	for (i = 0; i < links->len; i++) {
		const NMPlatformLink *link = NMP_OBJECT_CAST_LINK (links->pdata[i]);
		const NMConfigDeviceStateData *dev_state;

		dev_state = nm_config_device_state_get (priv->config, link->ifindex);
		platform_link_added (self,
		                     link->ifindex,
		                     link,
		                     guess_assume && (!dev_state || !dev_state->connection_uuid),
		                     dev_state);
	}
}

static void
rfkill_manager_rfkill_changed_cb (NMRfkillManager *rfkill_mgr,
                                  RfKillType rtype,
                                  RfKillState udev_state,
                                  gpointer user_data)
{
	nm_manager_rfkill_update (NM_MANAGER (user_data), rtype);
}

const CList *
nm_manager_get_devices (NMManager *manager)
{
	g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);

	return &NM_MANAGER_GET_PRIVATE (manager)->devices_lst_head;
}

typedef enum {
	DEVICE_ACTIVATION_PRIO_NONE,
	DEVICE_ACTIVATION_PRIO_UNMANAGED,
	DEVICE_ACTIVATION_PRIO_UNAVAILABLE,
	DEVICE_ACTIVATION_PRIO_DEACTIVATING,
	DEVICE_ACTIVATION_PRIO_ACTIVATING,
	DEVICE_ACTIVATION_PRIO_ACTIVATED,
	DEVICE_ACTIVATION_PRIO_DISCONNECTED,

	_DEVICE_ACTIVATION_PRIO_BEST = DEVICE_ACTIVATION_PRIO_DISCONNECTED,
} DeviceActivationPrio;

static DeviceActivationPrio
_device_get_activation_prio (NMDevice *device)
{
	if (!nm_device_get_managed (device, TRUE))
		return DEVICE_ACTIVATION_PRIO_NONE;

	switch (nm_device_get_state (device)) {
	case NM_DEVICE_STATE_DISCONNECTED:
		return DEVICE_ACTIVATION_PRIO_DISCONNECTED;
	case NM_DEVICE_STATE_ACTIVATED:
		return DEVICE_ACTIVATION_PRIO_ACTIVATED;
	case NM_DEVICE_STATE_PREPARE:
	case NM_DEVICE_STATE_CONFIG:
	case NM_DEVICE_STATE_NEED_AUTH:
	case NM_DEVICE_STATE_IP_CONFIG:
	case NM_DEVICE_STATE_IP_CHECK:
	case NM_DEVICE_STATE_SECONDARIES:
		return DEVICE_ACTIVATION_PRIO_ACTIVATING;
	case NM_DEVICE_STATE_DEACTIVATING:
	case NM_DEVICE_STATE_FAILED:
		return DEVICE_ACTIVATION_PRIO_DEACTIVATING;
	case NM_DEVICE_STATE_UNAVAILABLE:
		return DEVICE_ACTIVATION_PRIO_UNAVAILABLE;
	case NM_DEVICE_STATE_UNKNOWN:
	case NM_DEVICE_STATE_UNMANAGED:
		return DEVICE_ACTIVATION_PRIO_UNMANAGED;
	}

	g_return_val_if_reached (DEVICE_ACTIVATION_PRIO_UNAVAILABLE);
}

static NMDevice *
nm_manager_get_best_device_for_connection (NMManager *self,
                                           NMSettingsConnection *sett_conn,
                                           NMConnection *connection,
                                           gboolean for_user_request,
                                           GHashTable *unavailable_devices,
                                           GError **error)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnectionState ac_state;
	NMActiveConnection *ac;
	NMDevice *ac_device;
	NMDevice *device;
	struct {
		NMDevice *device;
		DeviceActivationPrio prio;
	} best = {
		.device = NULL,
		.prio = DEVICE_ACTIVATION_PRIO_NONE,
	};
	NMDeviceCheckConAvailableFlags flags;
	gs_unref_ptrarray GPtrArray *all_ac_arr = NULL;
	gs_free_error GError *local_best = NULL;
	NMConnectionMultiConnect multi_connect;

	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
	nm_assert (!connection || NM_IS_CONNECTION (connection));
	nm_assert (sett_conn || connection);
	nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));

	if (!connection)
		connection = nm_settings_connection_get_connection (sett_conn);

	multi_connect =  _nm_connection_get_multi_connect (connection);

	if (!for_user_request)
		flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE;
	else {
		/* if the profile is multi-connect=single, we also consider devices which
		 * are marked as unmanaged. And explicit user-request shows sufficient user
		 * intent to make the device managed.
		 * That is also, because we expect that such profile is suitably tied
		 * to the intended device. So when an unmanaged device matches, the user's
		 * intent is clear.
		 *
		 * For multi-connect != single devices that is different. The profile
		 * is not restricted to a particular device.
		 * For that reason, plain `nmcli connection up "$MULIT_PROFILE"` seems
		 * less suitable for multi-connect profiles, because the target device is
		 * left unspecified. Anyway, if a user issues
		 *
		 *   $ nmcli device set "$DEVICE" managed no
		 *   $ nmcli connection up "$MULIT_PROFILE"
		 *
		 * then it is reasonable for multi-connect profiles to not consider
		 * the device a suitable candidate.
		 *
		 * This may be seen inconsistent, but I think that it makes a lot of
		 * sense. Also note that "connection.multi-connect" work quite differently
		 * in aspects like activation. E.g. `nmcli connection up` of multi-connect
		 * "single" profile, will deactivate the profile if it is active already.
		 * That is different from multi-connect profiles, where it will aim to
		 * activate the profile one more time on an hitherto disconnected device.
		 */
		if (multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE)
			flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST;
		else
			flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED;
	}

	if (   multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE
	    && (ac = active_connection_find_by_connection (self, sett_conn, connection, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, &all_ac_arr))) {
		/* if we have a profile which may activate on only one device (multi-connect single), then
		 * we prefer the device on which the profile is already active. It means to reactivate
		 * the profile on the same device.
		 *
		 * If the profile can be activated on multiple devices, we don't do this. In fact, the
		 * check below for the DeviceActivationPrio will prefer devices which are not already
		 * activated (with this or another) profile. */

		ac_device = nm_active_connection_get_device (ac);
		if (   ac_device
		    && (   (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device))
		        || !nm_device_check_connection_available (ac_device, connection, flags, NULL, NULL)))
			ac_device = NULL;

		if (all_ac_arr) {
			guint i;

			ac_state = nm_active_connection_get_state (ac);

			/* we found several active connections. See which one is the most suitable... */
			nm_assert (ac == all_ac_arr->pdata[0]);
			for (i = 1; i < all_ac_arr->len; i++) {
				NMActiveConnection *ac2 = all_ac_arr->pdata[i];
				NMDevice *ac_device2 = nm_active_connection_get_device (ac2);
				NMActiveConnectionState ac_state2;

				if (   !ac_device2
				    || (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device2))
				    || !nm_device_check_connection_available (ac_device2, connection, flags, NULL, NULL))
					continue;

				ac_state2 = nm_active_connection_get_state (ac2);

				if (!ac_device)
					goto found_better;

				if (ac_state == ac_state2) {
					/* active-connections are in their list in the order in which they are connected.
					 * If we have two with same state, the later (newer) one is preferred. */
					goto found_better;
				}

				switch (ac_state) {
				case NM_ACTIVE_CONNECTION_STATE_UNKNOWN:
					if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATING, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING))
						goto found_better;
					break;
				case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
					if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATED))
						goto found_better;
					break;
				case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
					break;
				case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING:
					if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATING, NM_ACTIVE_CONNECTION_STATE_ACTIVATED))
						goto found_better;
					break;
				default:
					nm_assert_not_reached ();
					goto found_better;
				}

				continue;
found_better:
				ac = ac2;
				ac_state = ac_state2;
				ac_device = ac_device2;
			}
		}

		if (ac_device)
			return ac_device;
	}

	/* Pick the first device that's compatible with the connection. */
	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		GError *local = NULL;
		DeviceActivationPrio prio;

		if (   unavailable_devices
		    && g_hash_table_contains (unavailable_devices, device))
			continue;

		/* determine the priority of this device. Currently this priority is independent
		 * of the profile (connection) and the device's details (aside the state).
		 *
		 * Maybe nm_device_check_connection_available() should instead return a priority,
		 * as it has more information available.
		 *
		 * For example, if you have multiple Wi-Fi devices, currently a user-request would
		 * also select the device if the AP is not visible. Optimally, if one of the two
		 * devices sees the AP and the other one doesn't, the former would be preferred.
		 * For that, the priority would need to be determined by nm_device_check_connection_available(). */
		prio = _device_get_activation_prio (device);
		if (   prio <= best.prio
		    && best.device) {
			/* we already have a matching device with a better priority. This candidate
			 * cannot be better. Skip the check.
			 *
			 * Also note, that below we collect the best error message @local_best.
			 * Since we already have best.device, the error message does not matter
			 * either, and we can skip nm_device_check_connection_available() altogether. */
			continue;
		}

		if (nm_device_check_connection_available (device,
		                                          connection,
		                                          flags,
		                                          NULL,
		                                          error ? &local : NULL)) {
			if (prio == _DEVICE_ACTIVATION_PRIO_BEST) {
				/* this device already has the best priority. It cannot get better
				 * and finish the search. */
				return device;
			}
			best.prio = prio;
			best.device = device;
			continue;
		}

		if (error) {
			gboolean reset_error;

			if (!local_best)
				reset_error = TRUE;
			else if (local_best->domain != NM_UTILS_ERROR)
				reset_error = (local->domain == NM_UTILS_ERROR);
			else {
				reset_error = (   local->domain == NM_UTILS_ERROR
			                   && local_best->code < local->code);
			}

			if (reset_error) {
				g_clear_error (&local_best);
				g_set_error (&local_best,
				             local->domain,
				             local->code,
				             "device %s not available because %s",
				             nm_device_get_iface (device),
				             local->message);
			}
			g_error_free (local);
		}
	}

	if (best.device)
		return best.device;

	if (error) {
		if (local_best)
			g_propagate_error (error, g_steal_pointer (&local_best));
		else {
			nm_utils_error_set_literal (error,
			                            NM_UTILS_ERROR_UNKNOWN,
			                            "no suitable device found");
		}
	}
	return NULL;
}

static const char **
_get_devices_paths (NMManager *self,
                    gboolean all_devices)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	const char **paths = NULL;
	guint i;
	NMDevice *device;

	paths = g_new (const char *, c_list_length (&priv->devices_lst_head) + 1);

	i = 0;
	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		const char *path;

		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device));
		if (!path)
			continue;

		if (   !all_devices
		    && !nm_device_is_real (device))
			continue;

		paths[i++] = path;
	}
	paths[i++] = NULL;

	return paths;
}

static void
impl_manager_get_devices (NMDBusObject *obj,
                          const NMDBusInterfaceInfoExtended *interface_info,
                          const NMDBusMethodInfoExtended *method_info,
                          GDBusConnection *connection,
                          const char *sender,
                          GDBusMethodInvocation *invocation,
                          GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	gs_free const char **paths = NULL;

	paths = _get_devices_paths (self, FALSE);
	g_dbus_method_invocation_return_value (invocation,
	                                       g_variant_new ("(^ao)", (char **) paths));
}

static void
impl_manager_get_all_devices (NMDBusObject *obj,
                              const NMDBusInterfaceInfoExtended *interface_info,
                              const NMDBusMethodInfoExtended *method_info,
                              GDBusConnection *connection,
                              const char *sender,
                              GDBusMethodInvocation *invocation,
                              GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	gs_free const char **paths = NULL;

	paths = _get_devices_paths (self, TRUE);
	g_dbus_method_invocation_return_value (invocation,
	                                       g_variant_new ("(^ao)", (char **) paths));
}

static void
impl_manager_get_device_by_ip_iface (NMDBusObject *obj,
                                     const NMDBusInterfaceInfoExtended *interface_info,
                                     const NMDBusMethodInfoExtended *method_info,
                                     GDBusConnection *connection,
                                     const char *sender,
                                     GDBusMethodInvocation *invocation,
                                     GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMDevice *device;
	const char *path = NULL;
	const char *iface;

	g_variant_get (parameters, "(&s)", &iface);

	device = find_device_by_ip_iface (self, iface);
	if (device)
		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device));

	if (!path) {
		g_dbus_method_invocation_return_error (invocation,
		                                       NM_MANAGER_ERROR,
		                                       NM_MANAGER_ERROR_UNKNOWN_DEVICE,
		                                       "No device found for the requested iface.");
		return;
	}

	g_dbus_method_invocation_return_value (invocation,
	                                       g_variant_new ("(o)", path));
}

static gboolean
is_compatible_with_slave (NMConnection *master, NMConnection *slave)
{
	NMSettingConnection *s_con;

	g_return_val_if_fail (master, FALSE);
	g_return_val_if_fail (slave, FALSE);

	s_con = nm_connection_get_setting_connection (slave);
	g_assert (s_con);

	return nm_connection_is_type (master, nm_setting_connection_get_slave_type (s_con));
}

/**
 * find_master:
 * @self: #NMManager object
 * @connection: the #NMConnection to find the master connection and device for
 * @device: the #NMDevice, if any, which will activate @connection
 * @out_master_connection: on success, the master connection of @connection if
 *   that master connection was found
 * @out_master_device: on success, the master device of @connection if that
 *   master device was found
 * @out_master_ac: on success, the master ActiveConnection of @connection if
 *   there already is one
 * @error: the error, if an error occurred
 *
 * Given an #NMConnection, attempts to find its master. If @connection has
 * no master, this will return %TRUE and @out_master_connection and
 * @out_master_device will be untouched.
 *
 * If @connection does have a master, then the outputs depend on what is in its
 * #NMSettingConnection:master property:
 *
 * If "master" is the ifname of an existing #NMDevice, and that device has a
 * compatible master connection activated or activating on it, then
 * @out_master_device, @out_master_connection, and @out_master_ac will all be
 * set. If the device exists and is idle, only @out_master_device will be set.
 * If the device exists and has an incompatible connection on it, an error
 * will be returned.
 *
 * If "master" is the ifname of a non-existent device, then @out_master_device
 * will be %NULL, and @out_master_connection will be a connection whose
 * activation would cause the creation of that device. @out_master_ac MAY be
 * set in this case as well (if the connection has started activating, but has
 * not yet created its device).
 *
 * If "master" is the UUID of a compatible master connection, then
 * @out_master_connection will be the identified connection, and @out_master_device
 * and/or @out_master_ac will be set if the connection is currently activating.
 * (@out_master_device will not be set if the device exists but does not have
 * @out_master_connection active/activating on it.)
 *
 * Returns: %TRUE if the master device and/or connection could be found or if
 *  the connection did not require a master, %FALSE otherwise
 **/
static gboolean
find_master (NMManager *self,
             NMConnection *connection,
             NMDevice *device,
             NMSettingsConnection **out_master_connection,
             NMDevice **out_master_device,
             NMActiveConnection **out_master_ac,
             GError **error)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMSettingConnection *s_con;
	const char *master;
	NMDevice *master_device = NULL;
	NMSettingsConnection *master_connection;

	s_con = nm_connection_get_setting_connection (connection);
	g_assert (s_con);
	master = nm_setting_connection_get_master (s_con);

	if (master == NULL)
		return TRUE;  /* success, but no master */

	/* Try as an interface name first */
	master_device = find_device_by_iface (self, master, NULL, connection);
	if (master_device) {
		if (master_device == device) {
			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
			                     "Device cannot be its own master");
			return FALSE;
		}

		master_connection = nm_device_get_settings_connection (master_device);
		if (   master_connection
		    && !is_compatible_with_slave (nm_settings_connection_get_connection (master_connection),
		                                  connection)) {
			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
			             "The active connection on %s is not compatible",
			             nm_device_get_iface (master_device));
			return FALSE;
		}
	} else {
		/* Try master as a connection UUID */
		master_connection = nm_settings_get_connection_by_uuid (priv->settings, master);
		if (master_connection) {
			NMDevice *candidate;

			/* Check if the master connection is activated on some device already */
			c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
				if (candidate == device)
					continue;

				if (nm_device_get_settings_connection (candidate) == master_connection) {
					master_device = candidate;
					break;
				}
			}
		}
	}

	if (out_master_connection)
		*out_master_connection = master_connection;
	if (out_master_device)
		*out_master_device = master_device;
	if (out_master_ac && master_connection) {
		*out_master_ac = active_connection_find (self, master_connection, NULL,
		                                         NM_ACTIVE_CONNECTION_STATE_DEACTIVATING,
		                                         NULL);
	}

	if (master_device || master_connection)
		return TRUE;
	else {
		g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
		                     "Master connection not found or invalid");
		return FALSE;
	}
}

/**
 * ensure_master_active_connection:
 * @self: the #NMManager
 * @subject: the #NMAuthSubject representing the requestor of this activation
 * @connection: the connection that should depend on @master_connection
 * @device: the #NMDevice, if any, which will activate @connection
 * @master_connection: the master connection, or %NULL
 * @master_device: the master device, or %NULL
 * @activation_reason: the reason for activation
 * @error: the error, if an error occurred
 *
 * Determines whether a given #NMConnection depends on another connection to
 * be activated, and if so, finds that master connection or creates it.
 *
 * If @master_device and @master_connection are both set then @master_connection
 * MUST already be activated or activating on @master_device, and the function will
 * return the existing #NMActiveConnection.
 *
 * If only @master_device is set, and it has an #NMActiveConnection, then the
 * function will return it if it is a compatible master, or an error if not. If it
 * doesn't have an AC, then the function will create one if a compatible master
 * connection exists, or return an error if not.
 *
 * If only @master_connection is set, then this will try to find or create a compatible
 * #NMDevice, and either activate @master_connection on that device or return an error.
 *
 * Returns: the master #NMActiveConnection that the caller should depend on, or
 * %NULL if an error occurred
 */
static NMActiveConnection *
ensure_master_active_connection (NMManager *self,
                                 NMAuthSubject *subject,
                                 NMConnection *connection,
                                 NMDevice *device,
                                 NMSettingsConnection *master_connection,
                                 NMDevice *master_device,
                                 NMActivationReason activation_reason,
                                 GError **error)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *ac;
	NMActiveConnection *master_ac = NULL;
	NMDeviceState master_state;
	gboolean bind_lifetime_to_profile_visibility;

	g_return_val_if_fail (connection, NULL);
	g_return_val_if_fail (master_connection || master_device, FALSE);

	bind_lifetime_to_profile_visibility = NM_FLAGS_HAS (nm_device_get_activation_state_flags (device),
	                                                    NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);

	/* If the master device isn't activated then we need to activate it using
	 * compatible connection.  If it's already activating we can just proceed.
	 */
	if (master_device) {
		NMSettingsConnection *device_connection = nm_device_get_settings_connection (master_device);

		/* If we're passed a connection and a device, we require that connection
		 * be already activated on the device, eg returned from find_master().
		 */
		g_assert (!master_connection || master_connection == device_connection);
		if (   device_connection
		    && !is_compatible_with_slave (nm_settings_connection_get_connection (device_connection),
		                                  connection)) {
			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
			             "The active connection %s is not compatible",
			             nm_connection_get_id (connection));
			return NULL;
		}

		master_state = nm_device_get_state (master_device);
		if (   (master_state == NM_DEVICE_STATE_ACTIVATED)
		    || nm_device_is_activating (master_device)) {
			/* Device already using master_connection */
			ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device));
			g_return_val_if_fail (device_connection, ac);

			if (!bind_lifetime_to_profile_visibility) {
				/* unbind the lifetime. */
				nm_active_connection_set_state_flags_clear (ac,
				                                            NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
			}

			return ac;
		}

		/* If the device is disconnected, find a compatible connection and
		 * activate it on the device.
		 */
		if (master_state == NM_DEVICE_STATE_DISCONNECTED || !nm_device_is_real (master_device)) {
			gs_free NMSettingsConnection **connections = NULL;
			guint i;

			g_assert (master_connection == NULL);

			/* Find a compatible connection and activate this device using it */
			connections = nm_manager_get_activatable_connections (self, FALSE, TRUE, NULL);
			for (i = 0; connections[i]; i++) {
				NMSettingsConnection *candidate = connections[i];
				NMConnection *cand_conn = nm_settings_connection_get_connection (candidate);

				/* Ensure eg bond/team slave and the candidate master is a
				 * bond/team master
				 */
				if (!is_compatible_with_slave (cand_conn, connection))
					continue;

				if (nm_device_check_connection_available (master_device,
				                                          cand_conn,
				                                          NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
				                                          NULL,
				                                          NULL)) {
					master_ac = nm_manager_activate_connection (self,
					                                            candidate,
					                                            NULL,
					                                            NULL,
					                                            master_device,
					                                            subject,
					                                            NM_ACTIVATION_TYPE_MANAGED,
					                                            activation_reason,
					                                              bind_lifetime_to_profile_visibility
					                                            ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
					                                            : NM_ACTIVATION_STATE_FLAG_NONE,
					                                            error);
					return master_ac;
				}
			}

			g_set_error (error,
			             NM_MANAGER_ERROR,
			             NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
			             "No compatible connection found.");
			return NULL;
		}

		/* Otherwise, the device is unmanaged, unavailable, or disconnecting */
		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_DEPENDENCY_FAILED,
		             "Device unmanaged or not available for activation");
	} else if (master_connection) {
		NMDevice *candidate;

		/* Find a compatible device and activate it using this connection */
		c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
			if (candidate == device) {
				/* A device obviously can't be its own master */
				continue;
			}

			if (!nm_device_check_connection_available (candidate,
			                                           nm_settings_connection_get_connection (master_connection),
			                                           NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
			                                           NULL,
			                                           NULL))
				continue;

			if (!nm_device_is_software (candidate)) {
				master_state = nm_device_get_state (candidate);
				if (nm_device_is_real (candidate) && master_state != NM_DEVICE_STATE_DISCONNECTED)
					continue;
			}

			master_ac = nm_manager_activate_connection (self,
			                                            master_connection,
			                                            NULL,
			                                            NULL,
			                                            candidate,
			                                            subject,
			                                            NM_ACTIVATION_TYPE_MANAGED,
			                                            activation_reason,
			                                              bind_lifetime_to_profile_visibility
			                                            ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
			                                            : NM_ACTIVATION_STATE_FLAG_NONE,
			                                            error);
			return master_ac;
		}

		g_set_error (error,
		             NM_MANAGER_ERROR,
		             NM_MANAGER_ERROR_UNKNOWN_DEVICE,
		             "No device available");
	} else
		g_assert_not_reached ();

	return NULL;
}

typedef struct {
	NMSettingsConnection *connection;
	NMDevice *device;
} SlaveConnectionInfo;

/**
 * find_slaves:
 * @manager: #NMManager object
 * @sett_conn: the master #NMSettingsConnection to find slave connections for
 * @device: the master #NMDevice for the @sett_conn
 * @out_n_slaves: on return, the number of slaves found
 *
 * Given an #NMSettingsConnection, attempts to find its slaves. If @sett_conn is not
 * master, or has not any slaves, this will return %NULL.
 *
 * Returns: an array of #SlaveConnectionInfo for given master @sett_conn, or %NULL
 **/
static SlaveConnectionInfo *
find_slaves (NMManager *manager,
             NMSettingsConnection *sett_conn,
             NMDevice *device,
             guint *out_n_slaves,
             gboolean for_user_request)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
	gs_free NMSettingsConnection **all_connections = NULL;
	guint n_all_connections;
	guint i;
	SlaveConnectionInfo *slaves = NULL;
	guint n_slaves = 0;
	NMSettingConnection *s_con;
	gs_unref_hashtable GHashTable *devices = NULL;

	nm_assert (out_n_slaves);

	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
	g_return_val_if_fail (s_con, NULL);

	devices = g_hash_table_new (nm_direct_hash, NULL);

	/* Search through all connections, not only inactive ones, because
	 * even if a slave was already active, it might be deactivated during
	 * master reactivation.
	 */
	all_connections = nm_settings_get_connections_clone (priv->settings, &n_all_connections,
	                                                     NULL, NULL,
	                                                     nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
	for (i = 0; i < n_all_connections; i++) {
		NMSettingsConnection *master_connection = NULL;
		NMDevice *master_device = NULL, *slave_device;
		NMSettingsConnection *candidate = all_connections[i];

		find_master (manager,
		             nm_settings_connection_get_connection (candidate),
		             NULL,
		             &master_connection,
		             &master_device,
		             NULL,
		             NULL);
		if (   (master_connection && master_connection == sett_conn)
		    || (master_device && master_device == device)) {
			slave_device = nm_manager_get_best_device_for_connection (manager,
			                                                          candidate,
			                                                          NULL,
			                                                          for_user_request,
			                                                          devices,
			                                                          NULL);

			if (!slaves) {
				/* what we allocate is quite likely much too large. Don't bother, it is only
				 * a temporary buffer. */
				slaves = g_new (SlaveConnectionInfo, n_all_connections);
			}

			nm_assert (n_slaves < n_all_connections);
			slaves[n_slaves].connection = candidate,
			slaves[n_slaves].device = slave_device,
			n_slaves++;

			if (slave_device)
				g_hash_table_add (devices, slave_device);
		}
	}

	*out_n_slaves = n_slaves;

	/* Warning: returns NULL if n_slaves is zero. */
	return slaves;
}

static gboolean
should_connect_slaves (NMConnection *connection, NMDevice *device)
{
	NMSettingConnection *s_con;
	NMSettingConnectionAutoconnectSlaves val;

	s_con = nm_connection_get_setting_connection (connection);
	g_assert (s_con);

	val = nm_setting_connection_get_autoconnect_slaves (s_con);
	if (val != NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT)
		goto out;

	val = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
	                                                   NM_CON_DEFAULT ("connection.autoconnect-slaves"),
	                                                   device,
	                                                   0, 1, -1);

out:
	if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO)
		return FALSE;
	if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES)
		return TRUE;
	return FALSE;
}

static int
compare_slaves (gconstpointer a, gconstpointer b, gpointer sort_by_name)
{
	const SlaveConnectionInfo *a_info = a;
	const SlaveConnectionInfo *b_info = b;

	/* Slaves without a device at the end */
	if (!a_info->device)
		return 1;
	if (!b_info->device)
		return -1;

	if (GPOINTER_TO_INT (sort_by_name)) {
		return g_strcmp0 (nm_device_get_iface (a_info->device),
		                  nm_device_get_iface (b_info->device));
	}

	return nm_device_get_ifindex (a_info->device) - nm_device_get_ifindex (b_info->device);
}

static void
autoconnect_slaves (NMManager *self,
                    NMSettingsConnection *master_connection,
                    NMDevice *master_device,
                    NMAuthSubject *subject,
                    gboolean for_user_request)
{
	GError *local_err = NULL;

	if (should_connect_slaves (nm_settings_connection_get_connection (master_connection),
	                           master_device)) {
		gs_free SlaveConnectionInfo *slaves = NULL;
		guint i, n_slaves = 0;
		gboolean bind_lifetime_to_profile_visibility;

		slaves = find_slaves (self, master_connection, master_device, &n_slaves, for_user_request);
		if (n_slaves > 1) {
			gs_free char *value = NULL;

			value = nm_config_data_get_value (NM_CONFIG_GET_DATA,
			                                  NM_CONFIG_KEYFILE_GROUP_MAIN,
			                                  NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
			                                  NM_CONFIG_GET_VALUE_STRIP);
			g_qsort_with_data (slaves, n_slaves, sizeof (slaves[0]),
			                   compare_slaves,
			                   GINT_TO_POINTER (!nm_streq0 (value, "index")));
		}

		bind_lifetime_to_profile_visibility =    n_slaves > 0
		                                      && NM_FLAGS_HAS (nm_device_get_activation_state_flags (master_device),
		                                                       NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);

		for (i = 0; i < n_slaves; i++) {
			SlaveConnectionInfo *slave = &slaves[i];
			const char *uuid;

			/* To avoid loops when autoconnecting slaves, we propagate
			 * the UUID of the initial connection down to slaves until
			 * the same connection is found.
			 */
			uuid = g_object_get_qdata (G_OBJECT (master_connection),
			                           autoconnect_root_quark ());
			if (nm_streq0 (nm_settings_connection_get_uuid (slave->connection), uuid)) {
				_LOGI (LOGD_CORE,
				       "will NOT activate slave connection '%s' (%s) as a dependency for master '%s' (%s): "
				       "circular dependency detected",
				       nm_settings_connection_get_id (slave->connection),
				       nm_settings_connection_get_uuid (slave->connection),
				       nm_settings_connection_get_id (master_connection),
				       nm_settings_connection_get_uuid (master_connection));
				continue;
			}

			if (!uuid)
				uuid = nm_settings_connection_get_uuid (master_connection);
			g_object_set_qdata_full (G_OBJECT (slave->connection),
			                         autoconnect_root_quark (),
			                         g_strdup (uuid),
			                         g_free);

			if (!slave->device) {
				_LOGD (LOGD_CORE,
				       "will NOT activate slave connection '%s' (%s) as a dependency for master '%s' (%s): "
				       "no compatible device found",
				       nm_settings_connection_get_id (slave->connection),
				       nm_settings_connection_get_uuid (slave->connection),
				       nm_settings_connection_get_id (master_connection),
				       nm_settings_connection_get_uuid (master_connection));
				continue;
			}

			_LOGD (LOGD_CORE, "will activate slave connection '%s' (%s) as a dependency for master '%s' (%s)",
			       nm_settings_connection_get_id (slave->connection),
			       nm_settings_connection_get_uuid (slave->connection),
			       nm_settings_connection_get_id (master_connection),
			       nm_settings_connection_get_uuid (master_connection));

			/* Schedule slave activation */
			nm_manager_activate_connection (self,
			                                slave->connection,
			                                NULL,
			                                NULL,
			                                slave->device,
			                                subject,
			                                NM_ACTIVATION_TYPE_MANAGED,
			                                NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES,
			                                  bind_lifetime_to_profile_visibility
			                                ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
			                                : NM_ACTIVATION_STATE_FLAG_NONE,
			                                &local_err);
			if (local_err) {
				_LOGW (LOGD_CORE, "Slave connection activation failed: %s", local_err->message);
				g_clear_error (&local_err);
			}
		}
	}
}

static gboolean
_internal_activate_vpn (NMManager *self, NMActiveConnection *active, GError **error)
{
	nm_assert (NM_IS_VPN_CONNECTION (active));

	nm_dbus_object_export (NM_DBUS_OBJECT (active));
	if (!nm_vpn_manager_activate_connection (NM_MANAGER_GET_PRIVATE (self)->vpn_manager,
	                                         NM_VPN_CONNECTION (active),
	                                         error)) {
		nm_dbus_object_unexport (NM_DBUS_OBJECT (active));
		return FALSE;
	}

	active_connection_add (self, active);
	return TRUE;
}

/* Traverse the device to disconnected state. This means that the device is ready
 * for connection and will proceed activating if there's an activation request
 * enqueued.
 */
static void
unmanaged_to_disconnected (NMDevice *device)
{
	/* when creating the software device, it can happen that the device is
	 * still unmanaged by NM_UNMANAGED_PLATFORM_INIT because we didn't yet
	 * get the udev event. At this point, we can no longer delay the activation
	 * and force the device to be managed. */
	nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);

	nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_USER_EXPLICIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);

	if (!nm_device_get_managed (device, FALSE)) {
		/* the device is still marked as unmanaged. Nothing to do. */
		return;
	}

	if (nm_device_get_state (device) == NM_DEVICE_STATE_UNMANAGED) {
		nm_device_state_changed (device,
		                         NM_DEVICE_STATE_UNAVAILABLE,
		                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
	}

	if (   nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE
	    && nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)) {
		nm_device_state_changed (device,
		                         NM_DEVICE_STATE_DISCONNECTED,
		                         NM_DEVICE_STATE_REASON_USER_REQUESTED);
	}
}

static NMActivationStateFlags
_activation_bind_lifetime_to_profile_visibility (NMAuthSubject *subject)
{
	if (   nm_auth_subject_is_internal (subject)
	    || nm_auth_subject_get_unix_process_uid (subject) == 0) {
		/* internal requests and requests from root are always unbound. */
		return NM_ACTIVATION_STATE_FLAG_NONE;
	}

	/* if the activation was not done by internal decision nor root, there
	 * are the following cases:
	 *
	 * - the connection has "connection.permissions" unset and the profile
	 *   is not restricted to a user and commonly always visible. It does
	 *   not hurt to bind the lifetime, because we expect the profile to be
	 *   visible at the moment. If the profile changes (while still being active),
	 *   we want to pick-up changes to the visibility and possibly disconnect.
	 *
	 * - the connection has "connection.permissions" set, and the current user
	 *   is the owner:
	 *
	 *      - Usually, we would expect that the profile is visible at the moment,
	 *        and of course we want to bind the lifetime. The moment the user
	 *        logs out, the connection becomes invisible and disconnects.
	 *
	 *      - the profile at this time could already be invisible (e.g. if the
	 *        user didn't create a proper session (sudo) and manually activates
	 *        an invisible profile. In this case, we still want to bind the
	 *        lifetime, and it will disconnect after the user logs in and logs
	 *        out again. NMKeepAlive takes care of that.
	 */
	return NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY;
}

/* The parent connection is ready; we can proceed realizing the device and
 * progressing the device to disconencted state.
 */
static void
active_connection_parent_active (NMActiveConnection *active,
                                 NMActiveConnection *parent_ac,
                                 NMManager *self)
{
	NMDevice *device = nm_active_connection_get_device (active);
	GError *error = NULL;
	NMSettingsConnection *sett_conn;
	NMDevice *parent;

	g_signal_handlers_disconnect_by_func (active,
	                                      (GCallback) active_connection_parent_active,
	                                      self);

	if (!parent_ac) {
		_LOGW (LOGD_CORE, "The parent connection device '%s' depended on disappeared.",
		       nm_device_get_iface (device));
		nm_active_connection_set_state_fail (active,
		                                     NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED,
		                                     "parent device disappeared");
		return;
	}

	sett_conn = nm_active_connection_get_settings_connection (active);
	parent = nm_active_connection_get_device (parent_ac);

	if (!nm_device_create_and_realize (device,
	                                   nm_settings_connection_get_connection (sett_conn),
	                                   parent,
	                                   &error)) {
		_LOGW (LOGD_CORE, "Could not realize device '%s': %s",
		       nm_device_get_iface (device), error->message);
		nm_active_connection_set_state_fail (active,
		                                     NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED,
		                                     "failure to realize device");
		return;
	}

	/* We can now proceed to disconnected state so that activation proceeds. */
	unmanaged_to_disconnected (device);
}

static gboolean
_internal_activate_device (NMManager *self, NMActiveConnection *active, GError **error)
{
	NMDevice *device, *master_device = NULL;
	NMConnection *applied;
	NMSettingsConnection *sett_conn;
	NMSettingsConnection *master_connection = NULL;
	NMConnection *existing_connection = NULL;
	NMActiveConnection *master_ac = NULL;
	NMAuthSubject *subject;
	GError *local = NULL;
	NMConnectionMultiConnect multi_connect;
	const char *parent_spec;

	g_return_val_if_fail (NM_IS_MANAGER (self), FALSE);
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (active), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	g_assert (NM_IS_VPN_CONNECTION (active) == FALSE);

	device = nm_active_connection_get_device (active);
	g_return_val_if_fail (device != NULL, FALSE);

	sett_conn = nm_active_connection_get_settings_connection (active);
	nm_assert (sett_conn);

	applied = nm_active_connection_get_applied_connection (active);

	/* If the device is active and its connection is not visible to the
	 * user that's requesting this new activation, fail, since other users
	 * should not be allowed to implicitly deactivate private connections
	 * by activating a connection of their own.
	 */
	existing_connection = nm_device_get_applied_connection (device);
	subject = nm_active_connection_get_subject (active);
	if (   existing_connection
	    && !nm_auth_is_subject_in_acl_set_error (existing_connection,
	                                             subject,
	                                             NM_MANAGER_ERROR,
	                                             NM_MANAGER_ERROR_PERMISSION_DENIED,
	                                             error)) {
		g_prefix_error (error, "Private connection already active on the device: ");
		return FALSE;
	}

	/* Final connection must be available on device */
	if (!nm_device_check_connection_available (device, applied, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL, &local)) {
		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
		             "Connection '%s' is not available on device %s because %s",
		             nm_settings_connection_get_id (sett_conn),
		             nm_device_get_iface (device),
		             local->message);
		g_error_free (local);
		return FALSE;
	}

	if (nm_active_connection_get_activation_type (active) == NM_ACTIVATION_TYPE_MANAGED)
		nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_MANAGED);

	/* Try to find the master connection/device if the connection has a dependency */
	if (!find_master (self,
	                  applied,
	                  device,
	                  &master_connection,
	                  &master_device,
	                  &master_ac,
	                  error)) {
		g_prefix_error (error, "Can not find a master for %s: ",
		                nm_settings_connection_get_id (sett_conn));
		return FALSE;
	}

	/* Create any backing resources the device needs */
	if (!nm_device_is_real (device)) {
		NMDevice *parent;

		parent = find_parent_device_for_connection (self,
		                                            nm_settings_connection_get_connection (sett_conn),
		                                            NULL,
		                                            &parent_spec);

		if (parent_spec && !parent) {
			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
			             "parent device '%s' not found", parent_spec);
			return FALSE;
		}

		if (parent && !nm_device_is_real (parent)) {
			NMSettingsConnection *parent_con;
			NMActiveConnection *parent_ac;

			parent_con = nm_device_get_best_connection (parent, NULL, error);
			if (!parent_con) {
				g_prefix_error (error, "%s failed to create parent: ", nm_device_get_iface (device));
				return FALSE;
			}

			if (   nm_active_connection_get_activation_reason (active) == NM_ACTIVATION_REASON_AUTOCONNECT
			    && nm_settings_connection_autoconnect_blocked_reason_get (parent_con,
			                                                              NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) {
				g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
				             "the parent connection of %s cannot autoactivate because it is blocked due to user request",
				             nm_device_get_iface (device));
				return FALSE;
			}

			parent_ac = nm_manager_activate_connection (self,
			                                            parent_con,
			                                            NULL,
			                                            NULL,
			                                            parent,
			                                            subject,
			                                            NM_ACTIVATION_TYPE_MANAGED,
			                                            nm_active_connection_get_activation_reason (active),
			                                              nm_active_connection_get_state_flags (active)
			                                            & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
			                                            error);
			if (!parent_ac) {
				g_prefix_error (error, "%s failed to activate parent: ", nm_device_get_iface (device));
				return FALSE;
			}

			/* We can't realize now; defer until the parent device is ready. */
			g_signal_connect (active,
			                  NM_ACTIVE_CONNECTION_PARENT_ACTIVE,
			                  (GCallback) active_connection_parent_active,
			                  self);
			nm_active_connection_set_parent (active, parent_ac);
		} else {
			/* We can realize now; no need to wait for a parent device. */
			if (!nm_device_create_and_realize (device,
			                                   nm_settings_connection_get_connection (sett_conn),
			                                   parent,
			                                   error)) {
				g_prefix_error (error, "%s failed to create resources: ", nm_device_get_iface (device));
				return FALSE;
			}
		}
	}

	/* Ensure there's a master active connection the new connection we're
	 * activating can depend on.
	 */
	if (master_connection || master_device) {
		if (master_connection) {
			_LOGD (LOGD_CORE, "Activation of '%s' requires master connection '%s'",
			       nm_settings_connection_get_id (sett_conn),
			       nm_settings_connection_get_id (master_connection));
		}
		if (master_device) {
			_LOGD (LOGD_CORE, "Activation of '%s' requires master device '%s'",
			       nm_settings_connection_get_id (sett_conn),
			       nm_device_get_ip_iface (master_device));
		}

		/* Ensure eg bond slave and the candidate master is a bond master */
		if (   master_connection
		    && !is_compatible_with_slave (nm_settings_connection_get_connection (master_connection),
		                                  applied)) {
			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
			             "The master connection '%s' is not compatible with '%s'",
			             nm_settings_connection_get_id (master_connection),
			             nm_settings_connection_get_id (sett_conn));
			return FALSE;
		}

		if (!master_ac) {
			master_ac = ensure_master_active_connection (self,
			                                             nm_active_connection_get_subject (active),
			                                             applied,
			                                             device,
			                                             master_connection,
			                                             master_device,
			                                             nm_active_connection_get_activation_reason (active),
			                                             error);
			if (!master_ac) {
				if (master_device) {
					g_prefix_error (error, "Master device '%s' can't be activated: ",
					                nm_device_get_ip_iface (device));
				} else {
					g_prefix_error (error, "Master connection '%s' can't be activated: ",
					                nm_settings_connection_get_id (sett_conn));
				}
				return FALSE;
			}
		}

		/* Now that we're activating a slave for that master, make sure the master just
		 * decides to go unmanaged while we're activating (perhaps because other slaves
		 * go away leaving him with no kids).
		 */
		if (master_device) {
			nm_device_set_unmanaged_by_flags (master_device, NM_UNMANAGED_EXTERNAL_DOWN,
			                                  NM_UNMAN_FLAG_OP_FORGET, NM_DEVICE_STATE_REASON_USER_REQUESTED);
		}

		nm_active_connection_set_master (active, master_ac);
		_LOGD (LOGD_CORE, "Activation of '%s' depends on active connection %p %s",
		       nm_settings_connection_get_id (sett_conn),
		       master_ac,
		       nm_dbus_object_get_path (NM_DBUS_OBJECT  (master_ac)) ?: "");
	}

	/* Check slaves for master connection and possibly activate them */
	autoconnect_slaves (self, sett_conn, device, nm_active_connection_get_subject (active),
	                    nm_active_connection_get_activation_reason (active) == NM_ACTIVATION_REASON_USER_REQUEST);

	multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn));
	if (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE
	    || (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE
	        && NM_IN_SET (nm_active_connection_get_activation_reason (active),
	                      NM_ACTIVATION_REASON_ASSUME,
	                      NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES,
	                      NM_ACTIVATION_REASON_USER_REQUEST))) {
		/* the profile can be activated multiple times. Proceed. */
	} else {
		gs_unref_ptrarray GPtrArray *all_ac_arr = NULL;
		NMActiveConnection *ac;
		guint i, n_all;

		/* Disconnect the connection if already connected or queued for activation.
		 * The connection cannot be active multiple times (at the same time).  */
		ac = active_connection_find (self, sett_conn, NULL, NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
		                             &all_ac_arr);
		if (ac) {
			n_all = all_ac_arr ? all_ac_arr->len : ((guint) 1);
			for (i = 0; i < n_all; i++) {
				nm_device_disconnect_active_connection (  all_ac_arr
				                                        ? all_ac_arr->pdata[i]
				                                        : ac,
				                                        NM_DEVICE_STATE_REASON_NEW_ACTIVATION,
				                                        NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
			}
		}
	}

	/* If the device is there, we can ready it for the activation. */
	if (nm_device_is_real (device)) {
		unmanaged_to_disconnected (device);

		if (!nm_device_get_managed (device, FALSE)) {
			/* Unexpectedly, the device is still unmanaged. That can happen for example,
			 * if the device is forcibly unmanaged due to NM_UNMANAGED_USER_SETTINGS. */
			g_set_error_literal (error,
			                     NM_MANAGER_ERROR,
			                     NM_MANAGER_ERROR_DEPENDENCY_FAILED,
			                     "Activation failed because the device is unmanaged");
			return FALSE;
		}
	}

	/* Export the new ActiveConnection to clients and start it on the device */
	active_connection_add (self, active);
	nm_device_queue_activation (device, NM_ACT_REQUEST (active));
	return TRUE;
}

static gboolean
_internal_activate_generic (NMManager *self, NMActiveConnection *active, GError **error)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gboolean success = FALSE;

	/* Ensure activation request is still valid, eg that its device hasn't gone
	 * away or that some other dependency has not failed.
	 */
	if (nm_active_connection_get_state (active) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
		g_set_error_literal (error,
		                     NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_DEPENDENCY_FAILED,
		                     "Activation failed because dependencies failed.");
		return FALSE;
	}

	if (NM_IS_VPN_CONNECTION (active))
		success = _internal_activate_vpn (self, active, error);
	else
		success = _internal_activate_device (self, active, error);

	if (success) {
		/* Force an update of the Manager's activating-connection property.
		 * The device changes state before the AC gets exported, which causes
		 * the manager's 'activating-connection' property to be NULL since the
		 * AC only gets a D-Bus path when it's exported.  So now that the AC
		 * is exported, make sure the manager's activating-connection property
		 * is up-to-date.
		 */
		policy_activating_ac_changed (G_OBJECT (priv->policy), NULL, self);
	}

	return success;
}

static NMActiveConnection *
_new_active_connection (NMManager *self,
                        gboolean is_vpn,
                        NMSettingsConnection *sett_conn,
                        NMConnection *incompl_conn,
                        NMConnection *applied,
                        const char *specific_object,
                        NMDevice *device,
                        NMAuthSubject *subject,
                        NMActivationType activation_type,
                        NMActivationReason activation_reason,
                        NMActivationStateFlags initial_state_flags,
                        GError **error)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *parent_device;

	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
	nm_assert (!incompl_conn || NM_IS_CONNECTION (incompl_conn));
	nm_assert ((!incompl_conn) ^ (!sett_conn));
	nm_assert (NM_IS_AUTH_SUBJECT (subject));
	nm_assert (is_vpn == _connection_is_vpn (sett_conn
	                                         ? nm_settings_connection_get_connection (sett_conn)
	                                         : incompl_conn));
	nm_assert (is_vpn || NM_IS_DEVICE (device));
	nm_assert (!nm_streq0 (specific_object, "/"));
	nm_assert (!applied || NM_IS_CONNECTION (applied));
	nm_assert (!is_vpn || !applied);

	if (is_vpn) {
		NMActiveConnection *parent;

		/* FIXME: for VPN connections, we don't allow re-activating an
		 * already active connection. It's a bug, and should be fixed together
		 * when reworking VPN handling. */
		if (active_connection_find_by_connection (self,
		                                          sett_conn,
		                                          incompl_conn,
		                                          NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
		                                          NULL)) {
			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE,
			             "Connection '%s' is already active",
			             sett_conn ? nm_settings_connection_get_id (sett_conn) : nm_connection_get_id (incompl_conn));
			return NULL;
		}

		if (activation_type != NM_ACTIVATION_TYPE_MANAGED)
			g_return_val_if_reached (NULL);

		if (specific_object) {
			/* Find the specific connection the client requested we use */
			parent = active_connection_get_by_path (self, specific_object);
			if (!parent) {
				g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
				                     "Base connection for VPN connection not active.");
				return NULL;
			}
		} else
			parent = priv->primary_connection;

		if (!parent) {
			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
			                     "Could not find source connection.");
			return NULL;
		}

		parent_device = nm_active_connection_get_device (parent);
		if (!parent_device) {
			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
			                     "Source connection had no active device");
			return NULL;
		}

		if (device && device != parent_device) {
			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
			                     "The device doesn't match the active connection.");
			return NULL;
		}

		return (NMActiveConnection *) nm_vpn_connection_new (sett_conn,
		                                                     parent_device,
		                                                     nm_dbus_object_get_path (NM_DBUS_OBJECT (parent)),
		                                                     activation_reason,
		                                                     initial_state_flags,
		                                                     subject);
	}

	return (NMActiveConnection *) nm_act_request_new (sett_conn,
	                                                  applied,
	                                                  specific_object,
	                                                  subject,
	                                                  activation_type,
	                                                  activation_reason,
	                                                  initial_state_flags,
	                                                  device);
}

static void
_internal_activation_auth_done (NMManager *self,
                                NMActiveConnection *active,
                                gboolean success,
                                const char *error_desc)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *ac;
	gs_free_error GError *error = NULL;

	nm_assert (NM_IS_ACTIVE_CONNECTION (active));

	if (!success)
		goto fail;

	/* Don't continue with an autoconnect-activation if a more important activation
	 * already exists.
	 * We also check this earlier, but there we may fail to detect a duplicate
	 * if the existing active connection was undergoing authorization.
	 */
	if (NM_IN_SET (nm_active_connection_get_activation_reason (active), NM_ACTIVATION_REASON_EXTERNAL,
	                                                                    NM_ACTIVATION_REASON_ASSUME,
	                                                                    NM_ACTIVATION_REASON_AUTOCONNECT)) {
		c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
			if (   nm_active_connection_get_device (ac) == nm_active_connection_get_device (active)
			    && nm_active_connection_get_settings_connection (ac) == nm_active_connection_get_settings_connection (active)
			    && nm_active_connection_get_state (ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
				g_set_error (&error,
				             NM_MANAGER_ERROR,
				             NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE,
				             "Connection '%s' is already active",
				             nm_active_connection_get_settings_connection_id (active));
				goto fail;
			}
		}
	}

	if (_internal_activate_generic (self, active, &error))
		return;

fail:
	nm_assert (error_desc || error);
	nm_active_connection_set_state_fail (active,
	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
	                                     error_desc ?: error->message);
}

/**
 * nm_manager_activate_connection():
 * @self: the #NMManager
 * @sett_conn: the #NMSettingsConnection to activate on @device
 * @applied: (allow-none): the applied connection to activate on @device
 * @specific_object: the specific object path, if any, for the activation
 * @device: the #NMDevice to activate @sett_conn on. Can be %NULL for VPNs.
 * @subject: the subject which requested activation
 * @activation_type: whether to assume the connection. That is, take over gracefully,
 *   non-destructible.
 * @activation_reason: the reason for activation
 * @initial_state_flags: the initial state flags for the activation.
 * @error: return location for an error
 *
 * Begins a new internally-initiated activation of @sett_conn on @device.
 * @subject should be the subject of the activation that triggered this
 * one, or if this is an autoconnect request, a new internal subject.
 * The returned #NMActiveConnection is owned by the Manager and should be
 * referenced by the caller if the caller continues to use it. If @applied
 * is supplied, it shall not be modified by the caller afterwards.
 *
 * Returns: (transfer none): the new #NMActiveConnection that tracks
 * activation of @sett_conn on @device
 */
NMActiveConnection *
nm_manager_activate_connection (NMManager *self,
                                NMSettingsConnection *sett_conn,
                                NMConnection *applied,
                                const char *specific_object,
                                NMDevice *device,
                                NMAuthSubject *subject,
                                NMActivationType activation_type,
                                NMActivationReason activation_reason,
                                NMActivationStateFlags initial_state_flags,
                                GError **error)
{
	NMManagerPrivate *priv;
	NMActiveConnection *active;
	AsyncOpData *async_op_data;
	gboolean is_vpn;

	g_return_val_if_fail (NM_IS_MANAGER (self), NULL);
	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), NULL);
	is_vpn = _connection_is_vpn (nm_settings_connection_get_connection (sett_conn));
	g_return_val_if_fail (is_vpn || NM_IS_DEVICE (device), NULL);
	g_return_val_if_fail (!error || !*error, NULL);
	nm_assert (!nm_streq0 (specific_object, "/"));

	priv = NM_MANAGER_GET_PRIVATE (self);

	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
	                                          subject,
	                                          NM_MANAGER_ERROR,
	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
	                                          error))
		return NULL;

	/* Look for a active connection that's equivalent and is already pending authorization
	 * and eventual activation. This is used to de-duplicate concurrent activations which would
	 * otherwise race and cause the device to disconnect and reconnect repeatedly.
	 * In particular, this allows the master and multiple slaves to concurrently auto-activate
	 * while all the slaves would use the same active-connection. */
	c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) {

		if (async_op_data->async_op_type != ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL)
			continue;

		active = async_op_data->ac_auth.active;
		if (   sett_conn == nm_active_connection_get_settings_connection (active)
		    && nm_streq0 (nm_active_connection_get_specific_object (active), specific_object)
		    && (!device || nm_active_connection_get_device (active) == device)
		    && nm_auth_subject_is_internal (nm_active_connection_get_subject (active))
		    && nm_auth_subject_is_internal (subject)
		    && nm_active_connection_get_activation_reason (active) == activation_reason)
			return active;
	}

	active = _new_active_connection (self,
	                                 is_vpn,
	                                 sett_conn,
	                                 NULL,
	                                 applied,
	                                 specific_object,
	                                 device,
	                                 subject,
	                                 activation_type,
	                                 activation_reason,
	                                 initial_state_flags,
	                                 error);
	if (!active)
		return NULL;

	nm_active_connection_authorize (active,
	                                NULL,
	                                _async_op_complete_ac_auth_cb,
	                                _async_op_data_new_authorize_activate_internal (self,
	                                                                                active));
	return active;
}

/**
 * validate_activation_request:
 * @self: the #NMManager
 * @context: the D-Bus context of the requestor
 * @sett_conn: the #NMSettingsConnection to be activated, or %NULL if there
 *   is only a partial activation.
 * @connection: the partial #NMConnection to be activated (if @sett_conn is unspecified)
 * @device_path: the object path of the device to be activated, or NULL
 * @out_device: on successful return, the #NMDevice to be activated with @connection
 *   The caller may pass in a device which shortcuts the lookup by path.
 *   In this case, the passed in device must have the matching @device_path
 *   already.
 * @out_is_vpn: on successful return, %TRUE if @connection is a VPN connection
 * @error: location to store an error on failure
 *
 * Performs basic validation on an activation request, including ensuring that
 * the requestor is a valid Unix process, is not disallowed in @connection
 * permissions, and that a device exists that can activate @connection.
 *
 * Returns: on success, the #NMAuthSubject representing the requestor, or
 *   %NULL on error
 */
static NMAuthSubject *
validate_activation_request (NMManager *self,
                             GDBusMethodInvocation *context,
                             NMSettingsConnection *sett_conn,
                             NMConnection *connection,
                             const char *device_path,
                             NMDevice **out_device,
                             gboolean *out_is_vpn,
                             GError **error)
{
	NMDevice *device = NULL;
	gboolean is_vpn = FALSE;
	gs_unref_object NMAuthSubject *subject = NULL;

	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
	nm_assert (!connection || NM_IS_CONNECTION (connection));
	nm_assert (sett_conn || connection);
	nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));
	nm_assert (out_device);
	nm_assert (out_is_vpn);

	if (!connection)
		connection = nm_settings_connection_get_connection (sett_conn);

	/* Validate the caller */
	subject = nm_auth_subject_new_unix_process_from_context (context);
	if (!subject) {
		g_set_error_literal (error,
		                     NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
		                     NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
		return NULL;
	}

	if (!nm_auth_is_subject_in_acl_set_error (connection,
	                                          subject,
	                                          NM_MANAGER_ERROR,
	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
	                                          error))
		return NULL;

	is_vpn = _connection_is_vpn (connection);

	if (*out_device) {
		device = *out_device;
		nm_assert (NM_IS_DEVICE (device));
		nm_assert (device_path);
		nm_assert (nm_streq0 (device_path, nm_dbus_object_get_path (NM_DBUS_OBJECT (device))));
		nm_assert (device == nm_manager_get_device_by_path (self, device_path));
	} else if (device_path) {
		device = nm_manager_get_device_by_path (self, device_path);
		if (!device) {
			g_set_error_literal (error,
			                     NM_MANAGER_ERROR,
			                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
			                     "Device not found");
			return NULL;
		}
	} else if (!is_vpn) {
		gs_free_error GError *local = NULL;

		device = nm_manager_get_best_device_for_connection (self, sett_conn, connection, TRUE, NULL, &local);
		if (!device) {
			gs_free char *iface = NULL;

			/* VPN and software-device connections don't need a device yet,
			 * but non-virtual connections do ... */
			if (!nm_connection_is_virtual (connection)) {
				g_set_error (error,
				             NM_MANAGER_ERROR,
				             NM_MANAGER_ERROR_UNKNOWN_DEVICE,
				             "No suitable device found for this connection (%s).",
				             local->message);
				return NULL;
			}

			/* Look for an existing device with the connection's interface name */
			iface = nm_manager_get_connection_iface (self, connection, NULL, NULL, error);
			if (!iface)
				return NULL;

			device = find_device_by_iface (self, iface, connection, NULL);
			if (!device) {
				g_set_error_literal (error,
				                     NM_MANAGER_ERROR,
				                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
				                     "Failed to find a compatible device for this connection");
				return NULL;
			}
		}
	}

	nm_assert (is_vpn || NM_IS_DEVICE (device));

	*out_device = device;
	*out_is_vpn = is_vpn;
	return g_steal_pointer (&subject);
}

/*****************************************************************************/

static void
_activation_auth_done (NMManager *self,
                       NMActiveConnection *active,
                       GDBusMethodInvocation *invocation,
                       gboolean success,
                       const char *error_desc)
{
	GError *error = NULL;
	NMAuthSubject *subject;
	NMSettingsConnection *connection;

	subject = nm_active_connection_get_subject (active);
	connection = nm_active_connection_get_settings_connection (active);

	if (!success) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             error_desc);
		goto fail;
	}

	if (!_internal_activate_generic (self, active, &error))
		goto fail;

	nm_settings_connection_autoconnect_blocked_reason_set (connection,
	                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
	                                                       FALSE);
	g_dbus_method_invocation_return_value (invocation,
	                                       g_variant_new ("(o)",
	                                       nm_dbus_object_get_path (NM_DBUS_OBJECT (active))));
	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, TRUE, NULL,
	                            subject, NULL);
	return;

fail:
	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, FALSE, NULL,
	                            subject, error->message);
	nm_active_connection_set_state_fail (active,
	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
	                                     error->message);

	g_dbus_method_invocation_take_error (invocation, error);
}

static void
impl_manager_activate_connection (NMDBusObject *obj,
                                  const NMDBusInterfaceInfoExtended *interface_info,
                                  const NMDBusMethodInfoExtended *method_info,
                                  GDBusConnection *dbus_connection,
                                  const char *sender,
                                  GDBusMethodInvocation *invocation,
                                  GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_unref_object NMActiveConnection *active = NULL;
	gs_unref_object NMAuthSubject *subject = NULL;
	NMSettingsConnection *sett_conn = NULL;
	NMDevice *device = NULL;
	gboolean is_vpn = FALSE;
	GError *error = NULL;
	const char *connection_path;
	const char *device_path;
	const char *specific_object_path;

	g_variant_get (parameters, "(&o&o&o)", &connection_path, &device_path, &specific_object_path);

	connection_path = nm_utils_dbus_normalize_object_path (connection_path);
	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
	device_path = nm_utils_dbus_normalize_object_path (device_path);

	/* If the connection path is given and valid, that connection is activated.
	 * Otherwise the "best" connection for the device is chosen and activated,
	 * regardless of whether that connection is autoconnect-enabled or not
	 * (since this is an explicit request, not an auto-activation request).
	 */
	if (connection_path) {
		sett_conn = nm_settings_get_connection_by_path (priv->settings, connection_path);
		if (!sett_conn) {
			error = g_error_new_literal (NM_MANAGER_ERROR,
			                             NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
			                             "Connection could not be found.");
			goto error;
		}
	} else {
		/* If no connection is given, find a suitable connection for the given device path */
		if (!device_path) {
			error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
			                             "Only devices may be activated without a specifying a connection");
			goto error;
		}
		device = nm_manager_get_device_by_path (self, device_path);
		if (!device) {
			error = g_error_new (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
			                     "Can not activate an unknown device '%s'", device_path);
			goto error;
		}

		sett_conn = nm_device_get_best_connection (device, specific_object_path, &error);
		if (!sett_conn)
			goto error;
	}

	subject = validate_activation_request (self,
	                                       invocation,
	                                       sett_conn,
	                                       NULL,
	                                       device_path,
	                                       &device,
	                                       &is_vpn,
	                                       &error);
	if (!subject)
		goto error;

	active = _new_active_connection (self,
	                                 is_vpn,
	                                 sett_conn,
	                                 NULL,
	                                 NULL,
	                                 specific_object_path,
	                                 device,
	                                 subject,
	                                 NM_ACTIVATION_TYPE_MANAGED,
	                                 NM_ACTIVATION_REASON_USER_REQUEST,
	                                 _activation_bind_lifetime_to_profile_visibility (subject),
	                                 &error);
	if (!active)
		goto error;

	nm_active_connection_authorize (active,
	                                NULL,
	                                _async_op_complete_ac_auth_cb,
	                                _async_op_data_new_ac_auth_activate_user (self,
	                                                                          active,
	                                                                          invocation));

	/* we passed the pointer on to _async_op_data_new_ac_auth_activate_user() */
	g_steal_pointer (&active);

	return;

error:
	if (sett_conn) {
		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, sett_conn, FALSE, NULL,
		                            subject, error->message);
	}
	g_dbus_method_invocation_take_error (invocation, error);
}

/*****************************************************************************/

static void
activation_add_done (NMSettings *settings,
                     NMSettingsConnection *new_connection,
                     GError *error,
                     GDBusMethodInvocation *context,
                     NMAuthSubject *subject,
                     gpointer user_data)
{
	NMManager *self;
	gs_unref_object NMActiveConnection *active = NULL;
	gs_free_error GError *local = NULL;
	gpointer async_op_type_ptr;
	AsyncOpType async_op_type;
	GVariant *result_floating;

	nm_utils_user_data_unpack (user_data, &self, &active, &async_op_type_ptr);
	async_op_type = GPOINTER_TO_INT (async_op_type_ptr);

	if (error)
		goto fail;

	nm_active_connection_set_settings_connection (active, new_connection);

	if (!_internal_activate_generic (self, active, &local))
		goto fail;

	if (async_op_type == ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE) {
		result_floating = g_variant_new ("(oo)",
		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)));
	} else {
		result_floating = g_variant_new ("(oo@a{sv})",
		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)),
		                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (active)),
		                                 g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
	}
	g_dbus_method_invocation_return_value (context, result_floating);

	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
	                            nm_active_connection_get_settings_connection (active),
	                            TRUE,
	                            NULL,
	                            nm_active_connection_get_subject (active),
	                            NULL);
	return;

fail:
	if (local) {
		nm_assert (!error);
		error = local;
	} else
		nm_assert (error);

	nm_active_connection_set_state_fail (active,
	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
	                                     error->message);
	if (new_connection)
		nm_settings_connection_delete (new_connection, FALSE);
	g_dbus_method_invocation_return_gerror (context, error);
	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
	                            NULL,
	                            FALSE,
	                            NULL,
	                            nm_active_connection_get_subject (active),
	                            error->message);
}

static void
_add_and_activate_auth_done (NMManager *self,
                             AsyncOpType async_op_type,
                             NMActiveConnection *active,
                             NMConnection *connection,
                             GDBusMethodInvocation *invocation,
                             NMSettingsConnectionPersistMode persist_mode,
                             gboolean is_volatile,
                             gboolean success,
                             const char *error_desc)
{
	NMManagerPrivate *priv;
	GError *error = NULL;

	if (!success) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             error_desc);
		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
		                            NULL,
		                            FALSE,
		                            NULL,
		                            nm_active_connection_get_subject (active),
		                            error->message);
		g_dbus_method_invocation_take_error (invocation, error);
		return;
	}

	priv = NM_MANAGER_GET_PRIVATE (self);

	/* FIXME(shutdown): nm_settings_add_connection_dbus() cannot be cancelled. It should be made
	 * cancellable and tracked via AsyncOpData to be able to do a clean
	 * shutdown. */
	nm_settings_add_connection_dbus (priv->settings,
	                                 connection,
	                                 persist_mode,
	                                 NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
	                                 (  is_volatile
	                                  ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
	                                  : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE),
	                                 nm_active_connection_get_subject (active),
	                                 invocation,
	                                 activation_add_done,
	                                 nm_utils_user_data_pack (self,
	                                                          g_object_ref (active),
	                                                          GINT_TO_POINTER (async_op_type)));
}

static void
impl_manager_add_and_activate_connection (NMDBusObject *obj,
                                          const NMDBusInterfaceInfoExtended *interface_info,
                                          const NMDBusMethodInfoExtended *method_info,
                                          GDBusConnection *dbus_connection,
                                          const char *sender,
                                          GDBusMethodInvocation *invocation,
                                          GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_unref_object NMConnection *incompl_conn = NULL;
	gs_unref_object NMActiveConnection *active = NULL;
	gs_unref_object NMAuthSubject *subject = NULL;
	GError *error = NULL;
	NMDevice *device = NULL;
	gboolean is_vpn = FALSE;
	gs_unref_variant GVariant *settings = NULL;
	gs_unref_variant GVariant *options = NULL;
	const char *device_path;
	const char *specific_object_path;
	gs_free NMConnection **conns = NULL;
	NMSettingsConnectionPersistMode persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
	gboolean is_volatile = FALSE;
	gboolean bind_dbus_client = FALSE;
	AsyncOpType async_op_type;

	if (nm_streq (method_info->parent.name, "AddAndActivateConnection2")) {
		async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2;
		g_variant_get (parameters, "(@a{sa{sv}}&o&o@a{sv})", &settings, &device_path, &specific_object_path, &options);
	} else {
		nm_assert (nm_streq (method_info->parent.name, "AddAndActivateConnection"));
		async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE;
		g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path);
	}

	if (options) {
		GVariantIter iter;
		const char *option_name;
		GVariant *option_value;

		g_variant_iter_init (&iter, options);
		while (g_variant_iter_next (&iter, "{&sv}", &option_name, &option_value)) {
			gs_unref_variant GVariant *option_value_free = NULL;
			const char *s;

			option_value_free = option_value;

			if (   nm_streq (option_name, "persist")
			    && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) {
				s = g_variant_get_string (option_value, NULL);

				is_volatile = FALSE;
				persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;

				if (nm_streq (s, "volatile")) {
					persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
					is_volatile = TRUE;
				} else if (nm_streq (s, "memory"))
					persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
				else if (nm_streq (s, "disk")) {
					/* pass */
				} else {
					error = g_error_new_literal (NM_MANAGER_ERROR,
					                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
					                             "Option \"persist\" must be one of \"volatile\", \"memory\" or \"disk\"");
					goto error;
				}
			} else if (   nm_streq (option_name, "bind-activation")
			           && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) {
				s = g_variant_get_string (option_value, NULL);

				if (nm_streq (s, "dbus-client"))
					bind_dbus_client = TRUE;
				else if (nm_streq (s, "none"))
					bind_dbus_client = FALSE;
				else {
					error = g_error_new_literal (NM_MANAGER_ERROR,
					                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
					                             "Option \"bind-activation\" must be one of \"dbus-client\" or \"none\"");
					goto error;
				}
			} else {
				error = g_error_new_literal (NM_MANAGER_ERROR,
				                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
				                             "Unknown extra option passed");
				goto error;
			}
		}
	}

	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
	device_path = nm_utils_dbus_normalize_object_path (device_path);

	/* Try to create a new connection with the given settings.
	 * We allow empty settings for AddAndActivateConnection(). In that case,
	 * the connection will be completed in nm_utils_complete_generic() or
	 * nm_device_complete_connection() below. Just make sure we don't expect
	 * specific data being in the connection till then (especially in
	 * validate_activation_request()).
	 */
	incompl_conn = nm_simple_connection_new ();
	if (settings && g_variant_n_children (settings))
		_nm_connection_replace_settings (incompl_conn, settings, NM_SETTING_PARSE_FLAGS_STRICT, NULL);

	subject = validate_activation_request (self,
	                                       invocation,
	                                       NULL,
	                                       incompl_conn,
	                                       device_path,
	                                       &device,
	                                       &is_vpn,
	                                       &error);
	if (!subject)
		goto error;

	if (is_vpn) {
		/* Try to fill the VPN's connection setting and name at least */
		if (!nm_connection_get_setting_vpn (incompl_conn)) {
			error = g_error_new_literal (NM_CONNECTION_ERROR,
			                             NM_CONNECTION_ERROR_MISSING_SETTING,
			                             "VPN connections require a 'vpn' setting");
			g_prefix_error (&error, "%s: ", NM_SETTING_VPN_SETTING_NAME);
			goto error;
		}

		conns = nm_settings_connections_array_to_connections (nm_settings_get_connections (priv->settings, NULL), -1);

		nm_utils_complete_generic (priv->platform,
		                           incompl_conn,
		                           NM_SETTING_VPN_SETTING_NAME,
		                           conns,
		                           NULL,
		                           _("VPN connection"),
		                           NULL,
		                           NULL,
		                           FALSE); /* No IPv6 by default for now */
	} else {
		conns = nm_settings_connections_array_to_connections (nm_settings_get_connections (priv->settings, NULL), -1);
		/* Let each device subclass complete the connection */
		if (!nm_device_complete_connection (device,
		                                    incompl_conn,
		                                    specific_object_path,
		                                    conns,
		                                    &error))
			goto error;
	}

	nm_assert (_nm_connection_verify (incompl_conn, NULL) == NM_SETTING_VERIFY_SUCCESS);

	active = _new_active_connection (self,
	                                 is_vpn,
	                                 NULL,
	                                 incompl_conn,
	                                 NULL,
	                                 specific_object_path,
	                                 device,
	                                 subject,
	                                 NM_ACTIVATION_TYPE_MANAGED,
	                                 NM_ACTIVATION_REASON_USER_REQUEST,
	                                 _activation_bind_lifetime_to_profile_visibility (subject),
	                                 &error);
	if (!active)
		goto error;

	if (bind_dbus_client) {
		NMKeepAlive *keep_alive;

		keep_alive = nm_active_connection_get_keep_alive (active);
		nm_keep_alive_set_dbus_client_watch (keep_alive, dbus_connection, sender);
		nm_keep_alive_arm (keep_alive);
	}

	nm_active_connection_authorize (active,
	                                incompl_conn,
	                                _async_op_complete_ac_auth_cb,
	                                _async_op_data_new_ac_auth_add_and_activate (self,
	                                                                             async_op_type,
	                                                                             active,
	                                                                             invocation,
	                                                                             incompl_conn,
	                                                                             persist_mode,
	                                                                             is_volatile));

	/* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */
	g_steal_pointer (&incompl_conn);
	g_steal_pointer (&active);
	return;

error:
	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE, NULL, FALSE, NULL, subject, error->message);
	g_dbus_method_invocation_take_error (invocation, error);
}

/*****************************************************************************/

gboolean
nm_manager_deactivate_connection (NMManager *manager,
                                  NMActiveConnection *active,
                                  NMDeviceStateReason reason,
                                  GError **error)
{
	if (NM_IS_VPN_CONNECTION (active)) {
		NMActiveConnectionStateReason vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED;

		if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_CONNECTION_REMOVED)
			vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED;

		if (!nm_vpn_connection_deactivate (NM_VPN_CONNECTION (active), vpn_reason, FALSE)) {
			g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
			                     "The VPN connection was not active.");
			return FALSE;
		}
	} else {
		nm_assert (NM_IS_ACT_REQUEST (active));
		nm_device_disconnect_active_connection (active,
		                                        reason,
		                                        NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN);
	}

	_notify (manager, PROP_ACTIVE_CONNECTIONS);
	return TRUE;
}

static void
deactivate_net_auth_done_cb (NMAuthChain *chain,
                             GDBusMethodInvocation *context,
                             gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	GError *error = NULL;
	NMAuthCallResult result;
	NMActiveConnection *active;
	char *path;

	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));

	c_list_unlink (nm_auth_chain_parent_lst_list (chain));

	path = nm_auth_chain_get_data (chain, "path");
	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL);
	active = active_connection_get_by_path (self, path);

	if (result != NM_AUTH_CALL_RESULT_YES) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             "Not authorized to deactivate connections");
	} else if (!active) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
		                             "The connection was not active.");
	} else {
		/* success; deactivation allowed */
		if (!nm_manager_deactivate_connection (self,
		                                       active,
		                                       NM_DEVICE_STATE_REASON_USER_REQUESTED,
		                                       &error))
			nm_assert (error);
	}

	if (active) {
		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DEACTIVATE,
		                            nm_active_connection_get_settings_connection (active),
		                            !error,
		                            NULL,
		                            nm_auth_chain_get_subject (chain),
		                            error ? error->message : NULL);
	}

	if (error)
		g_dbus_method_invocation_take_error (context, error);
	else
		g_dbus_method_invocation_return_value (context, NULL);
}

static void
impl_manager_deactivate_connection (NMDBusObject *obj,
                                    const NMDBusInterfaceInfoExtended *interface_info,
                                    const NMDBusMethodInfoExtended *method_info,
                                    GDBusConnection *dbus_connection,
                                    const char *sender,
                                    GDBusMethodInvocation *invocation,
                                    GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *ac;
	NMSettingsConnection *sett_conn = NULL;
	GError *error = NULL;
	NMAuthSubject *subject = NULL;
	NMAuthChain *chain;
	const char *active_path;

	g_variant_get (parameters, "(&o)", &active_path);

	/* Find the connection by its object path */
	ac = active_connection_get_by_path (self, active_path);
	if (ac)
		sett_conn = nm_active_connection_get_settings_connection (ac);

	if (!sett_conn) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
		                             "The connection was not active.");
		goto done;
	}

	/* Validate the caller */
	subject = nm_auth_subject_new_unix_process_from_context (invocation);
	if (!subject) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
		goto done;
	}

	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
	                                          subject,
	                                          NM_MANAGER_ERROR,
	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
	                                          &error))
		goto done;

	/* Validate the user request */
	chain = nm_auth_chain_new_subject (subject, invocation, deactivate_net_auth_done_cb, self);
	if (!chain) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		goto done;
	}

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "path", g_strdup (active_path), g_free);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);

done:
	if (error) {
		if (sett_conn) {
			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DEACTIVATE,
			                            sett_conn, FALSE, NULL,
			                            subject, error->message);
		}
		g_dbus_method_invocation_take_error (invocation, error);
	}
	g_clear_object (&subject);
}

static gboolean
sleep_devices_add (NMManager *self, NMDevice *device, gboolean suspending)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMSleepMonitorInhibitorHandle *handle = NULL;

	if (g_hash_table_lookup_extended (priv->sleep_devices, device, NULL, (gpointer *) &handle)) {
		if (suspending) {
			/* if we are suspending, always insert a new handle in sleep_devices.
			 * Even if we had an old handle, it might be stale by now. */
			g_hash_table_insert (priv->sleep_devices, device,
			                     nm_sleep_monitor_inhibit_take (priv->sleep_monitor));
			if (handle)
				nm_sleep_monitor_inhibit_release (priv->sleep_monitor, handle);
		}
		return FALSE;
	}

	g_hash_table_insert (priv->sleep_devices,
	                     g_object_ref (device),
	                     suspending
	                         ? nm_sleep_monitor_inhibit_take (priv->sleep_monitor)
	                         : NULL);
	g_signal_connect (device, "notify::" NM_DEVICE_STATE, (GCallback) device_sleep_cb, self);
	return TRUE;
}

static gboolean
sleep_devices_remove (NMManager *self, NMDevice *device)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMSleepMonitorInhibitorHandle *handle;

	if (!g_hash_table_lookup_extended (priv->sleep_devices, device, NULL, (gpointer *) &handle))
		return FALSE;

	if (handle)
		nm_sleep_monitor_inhibit_release (priv->sleep_monitor, handle);

	/* Remove device from hash */
	g_signal_handlers_disconnect_by_func (device, device_sleep_cb, self);
	g_hash_table_remove (priv->sleep_devices, device);
	g_object_unref (device);
	return TRUE;
}

static void
sleep_devices_clear (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;
	NMSleepMonitorInhibitorHandle *handle;
	GHashTableIter iter;

	if (!priv->sleep_devices)
		return;

	g_hash_table_iter_init (&iter, priv->sleep_devices);
	while (g_hash_table_iter_next (&iter, (gpointer *) &device, (gpointer *) &handle)) {
		g_signal_handlers_disconnect_by_func (device, device_sleep_cb, self);
		if (handle)
			nm_sleep_monitor_inhibit_release (priv->sleep_monitor, handle);
		g_object_unref (device);
		g_hash_table_iter_remove (&iter);
	}
}

static void
device_sleep_cb (NMDevice *device,
                 GParamSpec *pspec,
                 NMManager *self)
{
	switch (nm_device_get_state (device)) {
	case NM_DEVICE_STATE_DISCONNECTED:
		_LOGD (LOGD_SUSPEND, "sleep: unmanaging device %s", nm_device_get_ip_iface (device));
		nm_device_set_unmanaged_by_flags_queue (device,
		                                        NM_UNMANAGED_SLEEPING,
		                                        TRUE,
		                                        NM_DEVICE_STATE_REASON_SLEEPING);
		break;
	case NM_DEVICE_STATE_UNMANAGED:
		_LOGD (LOGD_SUSPEND, "sleep: device %s is ready", nm_device_get_ip_iface (device));

		if (!sleep_devices_remove (self, device))
			g_return_if_reached ();

		break;
	default:
		return;
	}
}

static void
do_sleep_wake (NMManager *self, gboolean sleeping_changed)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gboolean suspending, waking_from_suspend;
	NMDevice *device;

	suspending = sleeping_changed && priv->sleeping;
	waking_from_suspend = sleeping_changed && !priv->sleeping;

	if (manager_sleeping (self)) {
		_LOGD (LOGD_SUSPEND, "sleep: %s...", suspending ? "sleeping" : "disabling");

		/* FIXME: are there still hardware devices that need to be disabled around
		 * suspend/resume?
		 */
		c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
			if (nm_device_is_software (device)) {
				/* If a user disables networking we consider that as an
				 * indication that also software devices must be disconnected.
				 * But we don't want to destroy them for external events as
				 * a system suspend.
				 */
				if (suspending)
					continue;
			}
			/* Wake-on-LAN devices will be taken down post-suspend rather than pre- */
			if (   suspending
			    && device_is_wake_on_lan (priv->platform, device)) {
				_LOGD (LOGD_SUSPEND, "sleep: device %s has wake-on-lan, skipping",
				       nm_device_get_ip_iface (device));
				continue;
			}

			if (nm_device_is_activating (device) ||
			    nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
				_LOGD (LOGD_SUSPEND, "sleep: wait disconnection of device %s",
				       nm_device_get_ip_iface (device));

				if (sleep_devices_add (self, device, suspending))
					nm_device_queue_state (device, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_SLEEPING);
			} else {
				nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_SLEEPING, TRUE, NM_DEVICE_STATE_REASON_SLEEPING);
			}
		}
	} else {
		_LOGD (LOGD_SUSPEND, "sleep: %s...", waking_from_suspend ? "waking up" : "re-enabling");

		if (waking_from_suspend) {
			sleep_devices_clear (self);
			c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
				if (nm_device_is_software (device))
					continue;

				/* Belatedly take down Wake-on-LAN devices; ideally we wouldn't have to do this
				 * but for now it's the only way to make sure we re-check their connectivity.
				 */
				if (device_is_wake_on_lan (priv->platform, device))
					nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_SLEEPING, TRUE, NM_DEVICE_STATE_REASON_SLEEPING);

				/* Check if the device is unmanaged but the state transition is still pending.
				 * If so, change state now so that later we re-manage the device forcing a
				 * re-check of available connections.
				 */
				if (   !nm_device_get_managed (device, FALSE)
				    && nm_device_get_state (device) != NM_DEVICE_STATE_UNMANAGED) {
					nm_device_state_changed (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_SLEEPING);
				}
			}
		}

		/* Ensure rfkill state is up-to-date since we don't respond to state
		 * changes during sleep.
		 */
		nm_manager_rfkill_update (self, RFKILL_TYPE_UNKNOWN);

		/* Re-manage managed devices */
		c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
			guint i;

			if (   nm_device_is_software (device)
			    && !nm_device_get_unmanaged_flags (device, NM_UNMANAGED_SLEEPING)) {
				/* DHCP leases of software devices could have gone stale
				 * so we need to renew them. */
				nm_device_update_dynamic_ip_setup (device);
				continue;
			}

			/* enable/disable wireless devices since that we don't respond
			 * to killswitch changes during sleep.
			 */
			for (i = 0; i < RFKILL_TYPE_MAX; i++) {
				RadioState *rstate = &priv->radio_states[i];
				gboolean enabled = radio_enabled_for_rstate (rstate, TRUE);

				if (rstate->desc) {
					_LOGD (LOGD_RFKILL, "rfkill: %s %s devices (hw_enabled %d, sw_enabled %d, user_enabled %d)",
					       enabled ? "enabling" : "disabling",
					       rstate->desc, rstate->hw_enabled, rstate->sw_enabled, rstate->user_enabled);
				}
				if (nm_device_get_rfkill_type (device) == rstate->rtype)
					nm_device_set_enabled (device, enabled);
			}

			nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_SLEEPING, FALSE, NM_DEVICE_STATE_REASON_NOW_MANAGED);
		}
	}

	nm_manager_update_state (self);
}

static void
_internal_sleep (NMManager *self, gboolean do_sleep)
{
	NMManagerPrivate *priv;

	g_return_if_fail (NM_IS_MANAGER (self));

	priv = NM_MANAGER_GET_PRIVATE (self);

	if (priv->sleeping == do_sleep)
		return;

	_LOGI (LOGD_SUSPEND, "sleep: %s requested (sleeping: %s  enabled: %s)",
	       do_sleep ? "sleep" : "wake",
	       priv->sleeping ? "yes" : "no",
	       priv->net_enabled ? "yes" : "no");

	priv->sleeping = do_sleep;

	do_sleep_wake (self, TRUE);

	_notify (self, PROP_SLEEPING);
}

static void
impl_manager_sleep (NMDBusObject *obj,
                    const NMDBusInterfaceInfoExtended *interface_info,
                    const NMDBusMethodInfoExtended *method_info,
                    GDBusConnection *connection,
                    const char *sender,
                    GDBusMethodInvocation *invocation,
                    GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	GError *error = NULL;
	gs_unref_object NMAuthSubject *subject = NULL;
	gboolean do_sleep;

	g_variant_get (parameters, "(b)", &do_sleep);

	subject = nm_auth_subject_new_unix_process_from_context (invocation);

	if (priv->sleeping == do_sleep) {
		error = g_error_new (NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE,
		                     "Already %s", do_sleep ? "asleep" : "awake");
		nm_audit_log_control_op (NM_AUDIT_OP_SLEEP_CONTROL, do_sleep ? "on" : "off", FALSE, subject,
		                         error->message);
		g_dbus_method_invocation_take_error (invocation, error);
		return;
	}

	/* Unconditionally allow the request.  Previously it was polkit protected
	 * but unfortunately that doesn't work for short-lived processes like
	 * pm-utils.  It uses dbus-send without --print-reply, which quits
	 * immediately after sending the request, and NM is unable to obtain the
	 * sender's UID as dbus-send has already dropped off the bus.  Thus NM
	 * fails the request.  Instead, don't validate the request, but rely on
	 * D-Bus permissions to restrict the call to root.
	 */
	_internal_sleep (self, do_sleep);
	nm_audit_log_control_op (NM_AUDIT_OP_SLEEP_CONTROL, do_sleep ? "on" : "off", TRUE, subject, NULL);
	g_dbus_method_invocation_return_value (invocation, NULL);
	return;
}

static void
sleeping_cb (NMSleepMonitor *monitor, gboolean is_about_to_suspend, gpointer user_data)
{
	NMManager *self = user_data;

	_LOGT (LOGD_SUSPEND, "sleep: received %s signal", is_about_to_suspend ? "sleeping" : "resuming");
	_internal_sleep (self, is_about_to_suspend);
}

static void
_internal_enable (NMManager *self, gboolean enable)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	nm_config_state_set (priv->config, TRUE, FALSE,
	                     NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED, enable);

	_LOGI (LOGD_SUSPEND, "%s requested (sleeping: %s  enabled: %s)",
	       enable ? "enable" : "disable",
	       priv->sleeping ? "yes" : "no",
	       priv->net_enabled ? "yes" : "no");

	priv->net_enabled = enable;

	do_sleep_wake (self, FALSE);

	_notify (self, PROP_NETWORKING_ENABLED);
}

static void
enable_net_done_cb (NMAuthChain *chain,
                    GDBusMethodInvocation *context,
                    gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMAuthCallResult result;
	gboolean enable;
	NMAuthSubject *subject;

	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));

	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
	enable = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "enable"));
	subject = nm_auth_chain_get_subject (chain);

	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK);
	if (result != NM_AUTH_CALL_RESULT_YES) {
		GError *ret_error;

		ret_error = g_error_new_literal (NM_MANAGER_ERROR,
		                                 NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                 "Not authorized to enable/disable networking");
		nm_audit_log_control_op (NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", FALSE,
		                         subject, ret_error->message);
		g_dbus_method_invocation_take_error (context, ret_error);
		return;
	}

	_internal_enable (self, enable);
	g_dbus_method_invocation_return_value (context, NULL);
	nm_audit_log_control_op (NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", TRUE,
	                         subject, NULL);
}

static void
impl_manager_enable (NMDBusObject *obj,
                     const NMDBusInterfaceInfoExtended *interface_info,
                     const NMDBusMethodInfoExtended *method_info,
                     GDBusConnection *connection,
                     const char *sender,
                     GDBusMethodInvocation *invocation,
                     GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;
	GError *error = NULL;
	gboolean enable;

	g_variant_get (parameters, "(b)", &enable);

	if (priv->net_enabled == enable) {
		error = g_error_new (NM_MANAGER_ERROR,
		                     NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED,
		                     "Already %s", enable ? "enabled" : "disabled");
		goto done;
	}

	chain = nm_auth_chain_new_context (invocation, enable_net_done_cb, self);
	if (!chain) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		goto done;
	}

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "enable", GUINT_TO_POINTER (enable), NULL);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, TRUE);

done:
	if (error)
		g_dbus_method_invocation_take_error (invocation, error);
}

/* Permissions */

static void
get_perm_add_result (NMManager *self, NMAuthChain *chain, GVariantBuilder *results, const char *permission)
{
	NMAuthCallResult result;

	result = nm_auth_chain_get_result (chain, permission);
	if (result == NM_AUTH_CALL_RESULT_YES)
		g_variant_builder_add (results, "{ss}", permission, "yes");
	else if (result == NM_AUTH_CALL_RESULT_NO)
		g_variant_builder_add (results, "{ss}", permission, "no");
	else if (result == NM_AUTH_CALL_RESULT_AUTH)
		g_variant_builder_add (results, "{ss}", permission, "auth");
	else {
		_LOGD (LOGD_CORE, "unknown auth chain result %d", result);
	}
}

static void
get_permissions_done_cb (NMAuthChain *chain,
                         GDBusMethodInvocation *context,
                         gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	GVariantBuilder results;

	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));

	c_list_unlink (nm_auth_chain_parent_lst_list (chain));

	g_variant_builder_init (&results, G_VARIANT_TYPE ("a{ss}"));

	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SLEEP_WAKE);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_NETWORK_CONTROL);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_RELOAD);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS);
	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK);

	g_dbus_method_invocation_return_value (context,
	                                       g_variant_new ("(a{ss})", &results));
}

static void
impl_manager_get_permissions (NMDBusObject *obj,
                              const NMDBusInterfaceInfoExtended *interface_info,
                              const NMDBusMethodInfoExtended *method_info,
                              GDBusConnection *connection,
                              const char *sender,
                              GDBusMethodInvocation *invocation,
                              GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;

	chain = nm_auth_chain_new_context (invocation, get_permissions_done_cb, self);
	if (!chain) {
		g_dbus_method_invocation_return_error_literal (invocation,
		                                               NM_MANAGER_ERROR,
		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		return;
	}

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SLEEP_WAKE, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_RELOAD, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, FALSE);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, FALSE);
}

static void
impl_manager_state (NMDBusObject *obj,
                    const NMDBusInterfaceInfoExtended *interface_info,
                    const NMDBusMethodInfoExtended *method_info,
                    GDBusConnection *connection,
                    const char *sender,
                    GDBusMethodInvocation *invocation,
                    GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);

	nm_manager_update_state (self);
	g_dbus_method_invocation_return_value (invocation,
	                                       g_variant_new ("(u)", NM_MANAGER_GET_PRIVATE (self)->state));
}

static void
impl_manager_set_logging (NMDBusObject *obj,
                          const NMDBusInterfaceInfoExtended *interface_info,
                          const NMDBusMethodInfoExtended *method_info,
                          GDBusConnection *connection,
                          const char *sender,
                          GDBusMethodInvocation *invocation,
                          GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	GError *error = NULL;
	const char *level;
	const char *domains;

	/* The permission is already enforced by the D-Bus daemon, but we ensure
	 * that the caller is still alive so that clients are forced to wait and
	 * we'll be able to switch to polkit without breaking behavior.
	 */
	if (!nm_dbus_manager_ensure_uid (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
	                                 invocation,
	                                 G_MAXULONG,
	                                 NM_MANAGER_ERROR,
	                                 NM_MANAGER_ERROR_PERMISSION_DENIED))
		return;

	g_variant_get (parameters, "(&s&s)", &level, &domains);

	if (nm_logging_setup (level, domains, NULL, &error)) {
		_LOGI (LOGD_CORE, "logging: level '%s' domains '%s'",
		       nm_logging_level_to_string (), nm_logging_domains_to_string ());
	}

	if (error)
		g_dbus_method_invocation_take_error (invocation, error);
	else
		g_dbus_method_invocation_return_value (invocation, NULL);
}

static void
impl_manager_get_logging (NMDBusObject *obj,
                          const NMDBusInterfaceInfoExtended *interface_info,
                          const NMDBusMethodInfoExtended *method_info,
                          GDBusConnection *connection,
                          const char *sender,
                          GDBusMethodInvocation *invocation,
                          GVariant *parameters)
{
	g_dbus_method_invocation_return_value (invocation,
	                                       g_variant_new ("(ss)",
	                                                      nm_logging_level_to_string (),
	                                                      nm_logging_domains_to_string ()));
}

typedef struct {
	NMManager *self;
	GDBusMethodInvocation *context;
	guint remaining;
} ConnectivityCheckData;

static void
device_connectivity_done (NMDevice *device,
                          NMDeviceConnectivityHandle *handle,
                          NMConnectivityState state,
                          GError *error,
                          gpointer user_data)
{
	ConnectivityCheckData *data = user_data;
	NMManager *self;
	NMManagerPrivate *priv;

	nm_assert (data);
	nm_assert (data->remaining > 0);
	nm_assert (NM_IS_MANAGER (data->self));

	data->remaining--;

	self = data->self;
	priv = NM_MANAGER_GET_PRIVATE (self);

	if (   data->context
	    && (   data->remaining == 0
	        || (   state == NM_CONNECTIVITY_FULL
	            && priv->connectivity_state == NM_CONNECTIVITY_FULL))) {
		/* despite having a @handle and @state returned by the requests, we always
		 * return the current connectivity_state. That is, because the connectivity_state
		 * and the answer to the connectivity check shall agree.
		 *
		 * However, if one of the requests (early) returns full connectivity and agrees with
		 * the accumulated connectivity state, we no longer have to wait. The result is set.
		 *
		 * This also works well, because NMDevice first emits change signals to its own
		 * connectivity state, which is then taken into account for the accumulated global
		 * state. All this happens, before the callback is invoked. */
		g_dbus_method_invocation_return_value (g_steal_pointer (&data->context),
		                                       g_variant_new ("(u)",
		                                                      (guint) priv->connectivity_state));
	}

	if (data->remaining == 0) {
		g_object_unref (self);
		g_slice_free (ConnectivityCheckData, data);
	}
}

static void
check_connectivity_auth_done_cb (NMAuthChain *chain,
                                 GDBusMethodInvocation *context,
                                 gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	GError *error = NULL;
	NMAuthCallResult result;
	ConnectivityCheckData *data;
	NMDevice *device;

	c_list_unlink (nm_auth_chain_parent_lst_list (chain));

	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL);

	if (result != NM_AUTH_CALL_RESULT_YES) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             "Not authorized to recheck connectivity");
	}
	if (error) {
		g_dbus_method_invocation_take_error (context, error);
		return;
	}

	data = g_slice_new (ConnectivityCheckData);
	data->self = g_object_ref (self);
	data->context = context;
	data->remaining = 0;

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		if (nm_device_check_connectivity (device,
		                                  AF_INET,
		                                  device_connectivity_done,
		                                  data))
			data->remaining++;
		if (nm_device_check_connectivity (device,
		                                  AF_INET6,
		                                  device_connectivity_done,
		                                  data))
			data->remaining++;
	}

	if (data->remaining == 0) {
		/* call the handler at least once. */
		data->remaining = 1;
		device_connectivity_done (NULL,
		                          NULL,
		                          NM_CONNECTIVITY_UNKNOWN,
		                          NULL,
		                          data);
		/* @data got destroyed. */
	}
}

static void
impl_manager_check_connectivity (NMDBusObject *obj,
                                 const NMDBusInterfaceInfoExtended *interface_info,
                                 const NMDBusMethodInfoExtended *method_info,
                                 GDBusConnection *connection,
                                 const char *sender,
                                 GDBusMethodInvocation *invocation,
                                 GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;

	chain = nm_auth_chain_new_context (invocation, check_connectivity_auth_done_cb, self);
	if (!chain) {
		g_dbus_method_invocation_return_error_literal(invocation,
		                                              NM_MANAGER_ERROR,
		                                              NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                              NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		return;
	}

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
}

static void
start_factory (NMDeviceFactory *factory, gpointer user_data)
{
	nm_device_factory_start (factory);
}

gboolean
nm_manager_write_device_state (NMManager *self, NMDevice *device, int *out_ifindex)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	int ifindex;
	gboolean managed;
	NMConfigDeviceStateManagedType managed_type;
	const char *uuid = NULL;
	const char *perm_hw_addr_fake = NULL;
	gboolean perm_hw_addr_is_fake;
	guint32 route_metric_default_aspired;
	guint32 route_metric_default_effective;
	int nm_owned;
	NMDhcp4Config *dhcp4_config;
	const char *next_server = NULL;
	const char *root_path = NULL;

	NM_SET_OUT (out_ifindex, 0);

	ifindex = nm_device_get_ip_ifindex (device);
	if (ifindex <= 0)
		return FALSE;
	if (ifindex == 1) {
		/* ignore loopback */
		return FALSE;
	}

	if (!nm_platform_link_get (priv->platform, ifindex))
		return FALSE;

	managed = nm_device_get_managed (device, FALSE);
	if (managed) {
		NMSettingsConnection *sett_conn = NULL;

		if (nm_device_get_state (device) <= NM_DEVICE_STATE_ACTIVATED)
			sett_conn = nm_device_get_settings_connection (device);
		if (sett_conn)
			uuid = nm_settings_connection_get_uuid (sett_conn);
		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
	} else if (nm_device_get_unmanaged_flags (device, NM_UNMANAGED_USER_EXPLICIT))
		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
	else
		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN;

	perm_hw_addr_fake = nm_device_get_permanent_hw_address_full (device, FALSE, &perm_hw_addr_is_fake);
	if (perm_hw_addr_fake && !perm_hw_addr_is_fake)
		perm_hw_addr_fake = NULL;

	nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;

	route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN,
	                                                           TRUE, &route_metric_default_aspired);

	dhcp4_config = nm_device_get_dhcp4_config (device);
	if (dhcp4_config) {
		root_path = nm_dhcp4_config_get_option (dhcp4_config, "root_path");
		next_server = nm_dhcp4_config_get_option (dhcp4_config, "next_server");
	}

	if (!nm_config_device_state_write (ifindex,
	                                   managed_type,
	                                   perm_hw_addr_fake,
	                                   uuid,
	                                   nm_owned,
	                                   route_metric_default_aspired,
	                                   route_metric_default_effective,
	                                   next_server,
	                                   root_path))
		return FALSE;

	NM_SET_OUT (out_ifindex, ifindex);
	return TRUE;
}

void
nm_manager_write_device_state_all (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_unref_hashtable GHashTable *preserve_ifindexes = NULL;
	NMDevice *device;

	preserve_ifindexes = g_hash_table_new (nm_direct_hash, NULL);

	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		int ifindex;

		if (nm_manager_write_device_state (self, device, &ifindex)) {
			g_hash_table_add (preserve_ifindexes,
			                  GINT_TO_POINTER (ifindex));
		}
	}

	nm_config_device_state_prune_stale (preserve_ifindexes, NULL);
}

static gboolean
devices_inited_cb (gpointer user_data)
{
	NMManager *self = user_data;
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	priv->devices_inited_id = 0;
	priv->devices_inited = TRUE;
	check_if_startup_complete (self);
	return G_SOURCE_REMOVE;
}

gboolean
nm_manager_start (NMManager *self, GError **error)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gs_free NMSettingsConnection **connections = NULL;
	guint i;

	nm_device_factory_manager_load_factories (_register_device_factory, self);

	nm_device_factory_manager_for_each_factory (start_factory, NULL);

	/* Set initial radio enabled/disabled state */
	for (i = 0; i < RFKILL_TYPE_MAX; i++) {
		RadioState *rstate = &priv->radio_states[i];
		gboolean enabled;

		if (!rstate->desc)
			continue;

		/* recheck kernel rfkill state */
		update_rstate_from_rfkill (priv->rfkill_mgr, rstate);

		if (rstate->desc) {
			_LOGI (LOGD_RFKILL, "rfkill: %s %s by radio killswitch; %s by state file",
			       rstate->desc,
			       (rstate->hw_enabled && rstate->sw_enabled) ? "enabled" : "disabled",
			       rstate->user_enabled ? "enabled" : "disabled");
		}
		enabled = radio_enabled_for_rstate (rstate, TRUE);
		manager_update_radio_enabled (self, rstate, enabled);
	}

	_LOGI (LOGD_CORE, "Networking is %s by state file",
	       priv->net_enabled ? "enabled" : "disabled");

	system_unmanaged_devices_changed_cb (priv->settings, NULL, self);

	hostname_changed_cb (priv->hostname_manager, NULL, self);

	if (!nm_settings_start (priv->settings, error))
		return FALSE;

	nm_platform_process_events (priv->platform);

	g_signal_connect (priv->platform,
	                  NM_PLATFORM_SIGNAL_LINK_CHANGED,
	                  G_CALLBACK (platform_link_cb),
	                  self);

	platform_query_devices (self);

	/* Load VPN plugins */
	priv->vpn_manager = g_object_ref (nm_vpn_manager_get ());

	_LOGD (LOGD_CORE, "creating virtual devices...");
	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
	                  G_CALLBACK (connection_added_cb), self);
	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
	                  G_CALLBACK (connection_updated_cb), self);
	connections = nm_settings_get_connections_clone (priv->settings, NULL,
	                                                 NULL, NULL,
	                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
	for (i = 0; connections[i]; i++)
		connection_changed (self, connections[i]);

	nm_clear_g_source (&priv->devices_inited_id);
	priv->devices_inited_id = g_idle_add_full (G_PRIORITY_LOW + 10, devices_inited_cb, self, NULL);

	return TRUE;
}

void
nm_manager_stop (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	/* FIXME(shutdown): we don't do a proper shutdown yet:
	 *  - need to ensure that all pending async operations are cancelled
	 *    - e.g. operations in priv->async_op_lst_head
	 *  - need to ensure that no more asynchronous requests are started,
	 *    or that they complete quickly, or that they fail quickly.
	 *  - note that cancelling some operations is not possible synchronously.
	 *    Hence, stop() only prepares shutdown and tells everybody to not
	 *    accept new work, and to complete in a timely manner.
	 *    We need to still iterate the mainloop for a bit, to give everybody
	 *    the chance to complete.
	 *    - e.g. see comment at nm_auth_manager_force_shutdown()
	 */

	nm_dbus_manager_stop (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)));

	while ((device = c_list_first_entry (&priv->devices_lst_head, NMDevice, devices_lst)))
		remove_device (self, device, TRUE);

	_active_connection_cleanup (self);

	nm_clear_g_source (&priv->devices_inited_id);
}

static gboolean
handle_firmware_changed (gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMDevice *device;

	priv->fw_changed_id = 0;

	/* Try to re-enable devices with missing firmware */
	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
		NMDeviceState state = nm_device_get_state (device);

		if (   nm_device_get_firmware_missing (device)
		    && (state == NM_DEVICE_STATE_UNAVAILABLE)) {
			_LOG2I (LOGD_CORE, device, "firmware may now be available");

			/* Re-set unavailable state to try bringing the device up again */
			nm_device_state_changed (device,
			                         NM_DEVICE_STATE_UNAVAILABLE,
			                         NM_DEVICE_STATE_REASON_NONE);
		}
	}

	return FALSE;
}

static void
firmware_dir_changed (GFileMonitor *monitor,
                      GFile *file,
                      GFile *other_file,
                      GFileMonitorEvent event_type,
                      gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	switch (event_type) {
	case G_FILE_MONITOR_EVENT_CREATED:
	case G_FILE_MONITOR_EVENT_CHANGED:
	case G_FILE_MONITOR_EVENT_MOVED:
	case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
		if (!priv->fw_changed_id) {
			priv->fw_changed_id = g_timeout_add_seconds (4, handle_firmware_changed, self);
			_LOGI (LOGD_CORE, "kernel firmware directory '%s' changed",
			       KERNEL_FIRMWARE_DIR);
		}
		break;
	default:
		break;
	}
}

static void
connection_metered_changed (GObject *object,
                            NMMetered metered,
                            gpointer user_data)
{
	nm_manager_update_metered (NM_MANAGER (user_data));
}

static void
policy_default_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *ac;

	/* Note: this assumes that it's not possible for the IP4 default
	 * route to be going over the default-ip6-device. If that changes,
	 * we need something more complicated here.
	 */
	ac = nm_policy_get_default_ip4_ac (priv->policy);
	if (!ac)
		ac = nm_policy_get_default_ip6_ac (priv->policy);

	if (ac != priv->primary_connection) {
		if (priv->primary_connection) {
			g_signal_handlers_disconnect_by_func (priv->primary_connection,
			                                      G_CALLBACK (connection_metered_changed),
			                                      self);
			g_clear_object (&priv->primary_connection);
		}

		priv->primary_connection = ac ? g_object_ref (ac) : NULL;

		if (priv->primary_connection) {
			g_signal_connect (priv->primary_connection,
			                  NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED,
			                  G_CALLBACK (connection_metered_changed), self);
		}
		_LOGD (LOGD_CORE, "PrimaryConnection now %s",
		       ac ? nm_active_connection_get_settings_connection_id (ac) : "(none)");
		_notify (self, PROP_PRIMARY_CONNECTION);
		_notify (self, PROP_PRIMARY_CONNECTION_TYPE);
		nm_manager_update_metered (self);
	}
}

static void
policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMActiveConnection *activating, *best;

	/* We only look at activating-ip6-ac if activating-ip4-ac
	 * AND default-ip4-ac are NULL; if default-ip4-ac is
	 * non-NULL, then activating-ip6-ac is irrelevant, since while
	 * that AC might become the new default-ip6-ac, it can't
	 * become primary-connection while default-ip4-ac is set to
	 * something else.
	 */
	activating = nm_policy_get_activating_ip4_ac (priv->policy);
	best = nm_policy_get_default_ip4_ac (priv->policy);
	if (!activating && !best)
		activating = nm_policy_get_activating_ip6_ac (priv->policy);

	if (nm_g_object_ref_set (&priv->activating_connection, activating)) {
		_LOGD (LOGD_CORE, "ActivatingConnection now %s",
		       activating
		           ? nm_active_connection_get_settings_connection_id (activating)
		           : "(none)");
		_notify (self, PROP_ACTIVATING_CONNECTION);
	}
}

/*****************************************************************************/

typedef struct {
	NMManager *self;
	NMDBusObject *obj;
	const NMDBusInterfaceInfoExtended *interface_info;
	const NMDBusPropertyInfoExtended *property_info;
	GVariant *value;
	guint64 export_version_id;
} DBusSetPropertyHandle;

#define NM_PERM_DENIED_ERROR "org.freedesktop.NetworkManager.PermissionDenied"

static void
_dbus_set_property_auth_cb (NMAuthChain *chain,
                            GDBusMethodInvocation *invocation,
                            gpointer user_data)
{
	DBusSetPropertyHandle *handle_data = user_data;
	gs_unref_object NMDBusObject *obj = handle_data->obj;
	const NMDBusInterfaceInfoExtended *interface_info = handle_data->interface_info;
	const NMDBusPropertyInfoExtended *property_info = handle_data->property_info;
	gs_unref_variant GVariant *value = handle_data->value;
	guint64 export_version_id = handle_data->export_version_id;
	gs_unref_object NMManager *self = handle_data->self;
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthCallResult result;
	gs_free_error GError *local = NULL;
	const char *error_name = NULL;
	const char *error_message = NULL;
	GValue gvalue;

	g_slice_free (DBusSetPropertyHandle, handle_data);

	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
	result = nm_auth_chain_get_result (chain, property_info->writable.permission);

	if (result != NM_AUTH_CALL_RESULT_YES) {
		error_name = NM_PERM_DENIED_ERROR;
		error_message = "Not authorized to perform this operation";
		goto out;
	}

	if (export_version_id != nm_dbus_object_get_export_version_id (obj)) {
		error_name = "org.freedesktop.DBus.Error.UnknownObject";
		error_message = "Object was deleted while authenticating";
		goto out;
	}

	/* Handle some properties specially *sigh* */
	if (   interface_info == &interface_info_manager
	    && nm_streq (property_info->property_name, NM_MANAGER_GLOBAL_DNS_CONFIGURATION)) {
		const NMGlobalDnsConfig *global_dns;

		global_dns = nm_config_data_get_global_dns_config (nm_config_get_data (priv->config));
		if (   global_dns
		    && !nm_global_dns_config_is_internal (global_dns)) {
			error_name = NM_PERM_DENIED_ERROR;
			error_message = "Global DNS configuration already set via configuration file";
			goto out;
		}
	}

	g_dbus_gvariant_to_gvalue (value, &gvalue);
	if (!nm_g_object_set_property (G_OBJECT (obj), property_info->property_name, &gvalue, &local)) {
		error_name = "org.freedesktop.DBus.Error.InvalidArgs";
		error_message = local->message;
	}
	g_value_unset (&gvalue);

out:
	nm_audit_log_control_op (property_info->writable.audit_op,
	                         property_info->property_name,
	                         !error_message,
	                         nm_auth_chain_get_subject (chain),
	                         error_message);
	if (error_message)
		g_dbus_method_invocation_return_dbus_error (invocation, error_name, error_message);
	else
		g_dbus_method_invocation_return_value (invocation, NULL);
}

void
nm_manager_dbus_set_property_handle (NMDBusObject *obj,
                                     const NMDBusInterfaceInfoExtended *interface_info,
                                     const NMDBusPropertyInfoExtended *property_info,
                                     GDBusConnection *connection,
                                     const char *sender,
                                     GDBusMethodInvocation *invocation,
                                     GVariant *value,
                                     gpointer user_data)
{
	NMManager *self = user_data;
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;
	const char *error_message = NULL;
	gs_unref_object NMAuthSubject *subject = NULL;
	DBusSetPropertyHandle *handle_data;

	subject = nm_auth_subject_new_unix_process_from_context (invocation);
	if (!subject) {
		error_message = NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN;
		goto err;
	}

	handle_data = g_slice_new0 (DBusSetPropertyHandle);
	handle_data->self = g_object_ref (self);
	handle_data->obj = g_object_ref (obj);
	handle_data->interface_info = interface_info;
	handle_data->property_info = property_info;
	handle_data->value = g_variant_ref (value);
	handle_data->export_version_id = nm_dbus_object_get_export_version_id (obj);

	chain = nm_auth_chain_new_subject (subject, invocation, _dbus_set_property_auth_cb, handle_data);
	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_add_call_unsafe (chain, property_info->writable.permission, TRUE);
	return;

err:
	nm_audit_log_control_op (property_info->writable.audit_op,
	                         property_info->property_name,
	                         FALSE,
	                         invocation,
	                         error_message);
	g_dbus_method_invocation_return_error_literal (invocation,
	                                               G_DBUS_ERROR,
	                                               G_DBUS_ERROR_AUTH_FAILED,
	                                               error_message);
}

/*****************************************************************************/

static NMCheckpointManager *
_checkpoint_mgr_get (NMManager *self, gboolean create_as_needed)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);

	if (G_UNLIKELY (!priv->checkpoint_mgr) && create_as_needed)
		priv->checkpoint_mgr = nm_checkpoint_manager_new (self, obj_properties[PROP_CHECKPOINTS]);
	return priv->checkpoint_mgr;
}

static void
checkpoint_auth_done_cb (NMAuthChain *chain,
                         GDBusMethodInvocation *context,
                         gpointer user_data)
{
	NMManager *self = NM_MANAGER (user_data);
	char *op;
	char *checkpoint_path = NULL;
	char **devices;
	NMCheckpoint *checkpoint;
	NMAuthCallResult result;
	guint32 timeout, flags;
	GVariant *variant = NULL;
	GError *error = NULL;
	const char *arg = NULL;
	guint32 add_timeout;

	op = nm_auth_chain_get_data (chain, "audit-op");
	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);

	if (NM_IN_STRSET (op, NM_AUDIT_OP_CHECKPOINT_DESTROY,
	                      NM_AUDIT_OP_CHECKPOINT_ROLLBACK,
	                      NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT))
		arg = checkpoint_path = nm_auth_chain_get_data (chain, "checkpoint_path");

	if (result != NM_AUTH_CALL_RESULT_YES) {
		error = g_error_new_literal (NM_MANAGER_ERROR,
		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
		                             "Not authorized to checkpoint/rollback");
	} else {
		if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_CREATE)) {
			timeout = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "timeout"));
			flags = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "flags"));
			devices = nm_auth_chain_get_data (chain, "devices");

			checkpoint = nm_checkpoint_manager_create (_checkpoint_mgr_get (self, TRUE),
			                                           (const char *const *) devices,
			                                           timeout,
			                                           (NMCheckpointCreateFlags) flags,
			                                           &error);
			if (checkpoint) {
				arg = nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint));
				variant = g_variant_new ("(o)", arg);
			}
		} else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_DESTROY)) {
			nm_checkpoint_manager_destroy (_checkpoint_mgr_get (self, TRUE),
			                               checkpoint_path, &error);
		} else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK)) {
			nm_checkpoint_manager_rollback (_checkpoint_mgr_get (self, TRUE),
			                                checkpoint_path, &variant, &error);
		} else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) {
			add_timeout = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "add_timeout"));
			nm_checkpoint_manager_adjust_rollback_timeout (_checkpoint_mgr_get (self, TRUE),
			                                               checkpoint_path, add_timeout, &error);
		} else
			g_return_if_reached ();
	}

	nm_audit_log_checkpoint_op (op, arg ?: "", !error, nm_auth_chain_get_subject (chain),
	                            error ? error->message : NULL);

	if (error)
		g_dbus_method_invocation_take_error (context, error);
	else
		g_dbus_method_invocation_return_value (context, variant);
}

static void
impl_manager_checkpoint_create (NMDBusObject *obj,
                                const NMDBusInterfaceInfoExtended *interface_info,
                                const NMDBusMethodInfoExtended *method_info,
                                GDBusConnection *connection,
                                const char *sender,
                                GDBusMethodInvocation *invocation,
                                GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;
	char **devices;
	guint32 rollback_timeout;
	guint32 flags;

	G_STATIC_ASSERT_EXPR (sizeof (flags) <= sizeof (NMCheckpointCreateFlags));

	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
	if (!chain) {
		g_dbus_method_invocation_return_error_literal (invocation,
		                                               NM_MANAGER_ERROR,
		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		return;
	}

	g_variant_get (parameters, "(^aouu)", &devices, &rollback_timeout, &flags);

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_CREATE, NULL);
	nm_auth_chain_set_data (chain, "devices", devices, (GDestroyNotify) g_strfreev);
	nm_auth_chain_set_data (chain, "flags",  GUINT_TO_POINTER (flags), NULL);
	nm_auth_chain_set_data (chain, "timeout", GUINT_TO_POINTER (rollback_timeout), NULL);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
}

static void
impl_manager_checkpoint_destroy (NMDBusObject *obj,
                                 const NMDBusInterfaceInfoExtended *interface_info,
                                 const NMDBusMethodInfoExtended *method_info,
                                 GDBusConnection *connection,
                                 const char *sender,
                                 GDBusMethodInvocation *invocation,
                                 GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;
	const char *checkpoint_path;

	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
	if (!chain) {
		g_dbus_method_invocation_return_error_literal (invocation,
		                                               NM_MANAGER_ERROR,
		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		return;
	}

	g_variant_get (parameters, "(&o)", &checkpoint_path);

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_DESTROY, NULL);
	nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
}

static void
impl_manager_checkpoint_rollback (NMDBusObject *obj,
                                  const NMDBusInterfaceInfoExtended *interface_info,
                                  const NMDBusMethodInfoExtended *method_info,
                                  GDBusConnection *connection,
                                  const char *sender,
                                  GDBusMethodInvocation *invocation,
                                  GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;
	const char *checkpoint_path;

	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
	if (!chain) {
		g_dbus_method_invocation_return_error_literal (invocation,
		                                               NM_MANAGER_ERROR,
		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		return;
	}

	g_variant_get (parameters, "(&o)", &checkpoint_path);

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ROLLBACK, NULL);
	nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
}

static void
impl_manager_checkpoint_adjust_rollback_timeout (NMDBusObject *obj,
                                                 const NMDBusInterfaceInfoExtended *interface_info,
                                                 const NMDBusMethodInfoExtended *method_info,
                                                 GDBusConnection *connection,
                                                 const char *sender,
                                                 GDBusMethodInvocation *invocation,
                                                 GVariant *parameters)
{
	NMManager *self = NM_MANAGER (obj);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMAuthChain *chain;
	const char *checkpoint_path;
	guint32 add_timeout;

	chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self);
	if (!chain) {
		g_dbus_method_invocation_return_error_literal (invocation,
		                                               NM_MANAGER_ERROR,
		                                               NM_MANAGER_ERROR_PERMISSION_DENIED,
		                                               NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
		return;
	}

	g_variant_get (parameters, "(&ou)", &checkpoint_path, &add_timeout);

	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT, NULL);
	nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free);
	nm_auth_chain_set_data (chain, "add_timeout", GUINT_TO_POINTER (add_timeout), NULL);
	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
}

/*****************************************************************************/

static void
auth_mgr_changed (NMAuthManager *auth_manager, gpointer user_data)
{
	/* Let clients know they should re-check their authorization */
	nm_dbus_object_emit_signal (user_data,
	                            &interface_info_manager,
	                            &signal_info_check_permissions,
	                            "()");
}

#define KERN_RFKILL_OP_CHANGE_ALL 3
#define KERN_RFKILL_TYPE_WLAN     1
#define KERN_RFKILL_TYPE_WWAN     5
struct rfkill_event {
	__u32 idx;
	__u8  type;
	__u8  op;
	__u8  soft, hard;
} _nm_packed;

static void
rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean enabled)
{
	int fd;
	struct rfkill_event event;
	ssize_t len;
	int errsv;

	g_return_if_fail (rtype == RFKILL_TYPE_WLAN || rtype == RFKILL_TYPE_WWAN);

	fd = open ("/dev/rfkill", O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		if (errno == EACCES)
			_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to open killswitch device", desc);
		return;
	}

	if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0) {
		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to set killswitch device for "
		       "non-blocking operation", desc);
		nm_close (fd);
		return;
	}

	memset (&event, 0, sizeof (event));
	event.op = KERN_RFKILL_OP_CHANGE_ALL;
	switch (rtype) {
	case RFKILL_TYPE_WLAN:
		event.type = KERN_RFKILL_TYPE_WLAN;
		break;
	case RFKILL_TYPE_WWAN:
		event.type = KERN_RFKILL_TYPE_WWAN;
		break;
	default:
		g_assert_not_reached ();
	}
	event.soft = enabled ? 0 : 1;

	len = write (fd, &event, sizeof (event));
	if (len < 0) {
		errsv = errno;
		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state: (%d) %s",
		       desc, errsv, nm_strerror_native (errsv));
	} else if (len == sizeof (event)) {
		_LOGI (LOGD_RFKILL, "rfkill: %s hardware radio set %s",
		       desc, enabled ? "enabled" : "disabled");
	} else {
		/* Failed to write full structure */
		_LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state", desc);
	}

	nm_close (fd);
}

static void
manager_radio_user_toggled (NMManager *self,
                            RadioState *rstate,
                            gboolean enabled)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	gboolean old_enabled, new_enabled;

	/* Don't touch devices if asleep/networking disabled */
	if (manager_sleeping (self))
		return;

	if (rstate->desc) {
		_LOGD (LOGD_RFKILL, "rfkill: (%s): setting radio %s by user",
		       rstate->desc,
		       enabled ? "enabled" : "disabled");
	}

	/* Update enabled key in state file */
	nm_config_state_set (priv->config, TRUE, FALSE,
	                     rstate->key, enabled);

	/* When the user toggles the radio, their request should override any
	 * daemon (like ModemManager) enabled state that can be changed.  For WWAN
	 * for example, we want the WwanEnabled property to reflect the daemon state
	 * too so that users can toggle the modem powered, but we don't want that
	 * daemon state to affect whether or not the user *can* turn it on, which is
	 * what the kernel rfkill state does.  So we ignore daemon enabled state
	 * when determining what the new state should be since it shouldn't block
	 * the user's request.
	 */
	old_enabled = radio_enabled_for_rstate (rstate, TRUE);
	rstate->user_enabled = enabled;
	new_enabled = radio_enabled_for_rstate (rstate, FALSE);
	if (new_enabled != old_enabled) {
		/* Try to change the kernel rfkill state */
		if (rstate->rtype == RFKILL_TYPE_WLAN || rstate->rtype == RFKILL_TYPE_WWAN)
			rfkill_change (self, rstate->desc, rstate->rtype, new_enabled);

		manager_update_radio_enabled (self, rstate, new_enabled);
	}
}

static gboolean
periodic_update_active_connection_timestamps (gpointer user_data)
{
	NMManager *manager = NM_MANAGER (user_data);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
	NMActiveConnection *ac;

	c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
		if (nm_active_connection_get_state (ac) == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
			nm_settings_connection_update_timestamp (nm_active_connection_get_settings_connection (ac),
			                                         (guint64) time (NULL));
		}
	}
	return G_SOURCE_CONTINUE;
}

/*****************************************************************************/

void
nm_manager_set_capability (NMManager *self,
                           NMCapability cap)
{
	NMManagerPrivate *priv;
	guint32 cap_i;
	gssize idx;

	g_return_if_fail (NM_IS_MANAGER (self));
	if (cap < 1 || cap > _NM_CAPABILITY_MAX)
		g_return_if_reached ();

	cap_i = (guint32) cap;

	priv = NM_MANAGER_GET_PRIVATE (self);

	idx = nm_utils_array_find_binary_search (&g_array_index (priv->capabilities, guint32, 0),
	                                         sizeof (guint32),
	                                         priv->capabilities->len,
	                                         &cap_i,
	                                         nm_cmp_uint32_p_with_data,
	                                         NULL);
	if (idx >= 0)
		return;

	nm_assert ((~idx) <= (gssize) priv->capabilities->len);

	g_array_insert_val (priv->capabilities, ~idx, cap_i);
	_notify (self, PROP_CAPABILITIES);
}

/*****************************************************************************/

NM_DEFINE_SINGLETON_REGISTER (NMManager);

NMManager *
nm_manager_get (void)
{
	g_return_val_if_fail (singleton_instance, NULL);
	return singleton_instance;
}

NMSettings *
nm_settings_get (void)
{
	g_return_val_if_fail (singleton_instance, NULL);

	return NM_MANAGER_GET_PRIVATE (singleton_instance)->settings;
}

NMManager *
nm_manager_setup (void)
{
	NMManager *self;

	g_return_val_if_fail (!singleton_instance, singleton_instance);

	self = g_object_new (NM_TYPE_MANAGER, NULL);
	nm_assert (NM_IS_MANAGER (self));
	singleton_instance = self;

	nm_singleton_instance_register ();
	nm_log_dbg (LOGD_CORE, "setup %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")",
	            "NMManager", NM_HASH_OBFUSCATE_PTR (singleton_instance));

	nm_dbus_object_export (NM_DBUS_OBJECT (self));
	return self;
}

static void
constructed (GObject *object)
{
	NMManager *self = NM_MANAGER (object);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	const NMConfigState *state;

	G_OBJECT_CLASS (nm_manager_parent_class)->constructed (object);

	priv->settings = nm_settings_new ();

	nm_dbus_object_export (NM_DBUS_OBJECT (priv->settings));

	g_signal_connect (priv->settings, "notify::" NM_SETTINGS_STARTUP_COMPLETE,
	                  G_CALLBACK (settings_startup_complete_changed), self);
	g_signal_connect (priv->settings, "notify::" NM_SETTINGS_UNMANAGED_SPECS,
	                  G_CALLBACK (system_unmanaged_devices_changed_cb), self);
	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED, G_CALLBACK (connection_flags_changed), self);

	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
	g_signal_connect (priv->hostname_manager, "notify::" NM_HOSTNAME_MANAGER_HOSTNAME,
	                  G_CALLBACK (hostname_changed_cb), self);

	/*
	 * Do not delete existing virtual devices to keep connectivity up.
	 * Virtual devices are reused when NetworkManager is restarted.
	 * Hence, don't react on NM_SETTINGS_SIGNAL_CONNECTION_REMOVED.
	 */

	priv->policy = nm_policy_new (self, priv->settings);
	g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP4_AC,
	                  G_CALLBACK (policy_default_ac_changed), self);
	g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP6_AC,
	                  G_CALLBACK (policy_default_ac_changed), self);
	g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP4_AC,
	                  G_CALLBACK (policy_activating_ac_changed), self);
	g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP6_AC,
	                  G_CALLBACK (policy_activating_ac_changed), self);

	priv->config = g_object_ref (nm_config_get ());
	g_signal_connect (G_OBJECT (priv->config),
	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
	                  G_CALLBACK (_config_changed_cb),
	                  self);

	state = nm_config_state_get (priv->config);

	priv->net_enabled = state->net_enabled;

	priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = state->wifi_enabled;
	priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = state->wwan_enabled;

	priv->rfkill_mgr = nm_rfkill_manager_new ();
	g_signal_connect (priv->rfkill_mgr,
	                  NM_RFKILL_MANAGER_SIGNAL_RFKILL_CHANGED,
	                  G_CALLBACK (rfkill_manager_rfkill_changed_cb),
	                  self);

	/* Force kernel Wi-Fi/WWAN rfkill state to follow NM saved Wi-Fi/WWAN state
	 * in case the BIOS doesn't save rfkill state, and to be consistent with user
	 * changes to the WirelessEnabled/WWANEnabled properties which toggle kernel
	 * rfkill.
	 */
	rfkill_change (self, priv->radio_states[RFKILL_TYPE_WLAN].desc, RFKILL_TYPE_WLAN, priv->radio_states[RFKILL_TYPE_WLAN].user_enabled);
	rfkill_change (self, priv->radio_states[RFKILL_TYPE_WWAN].desc, RFKILL_TYPE_WWAN, priv->radio_states[RFKILL_TYPE_WWAN].user_enabled);
}

static void
nm_manager_init (NMManager *self)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	guint i;
	GFile *file;

	c_list_init (&priv->auth_lst_head);
	c_list_init (&priv->link_cb_lst);
	c_list_init (&priv->devices_lst_head);
	c_list_init (&priv->active_connections_lst_head);
	c_list_init (&priv->async_op_lst_head);
	c_list_init (&priv->delete_volatile_connection_lst_head);

	priv->platform = g_object_ref (NM_PLATFORM_GET);

	priv->capabilities = g_array_new (FALSE, FALSE, sizeof (guint32));

	/* Initialize rfkill structures and states */
	memset (priv->radio_states, 0, sizeof (priv->radio_states));

	priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = TRUE;
	priv->radio_states[RFKILL_TYPE_WLAN].key = NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED;
	priv->radio_states[RFKILL_TYPE_WLAN].prop = NM_MANAGER_WIRELESS_ENABLED;
	priv->radio_states[RFKILL_TYPE_WLAN].hw_prop = NM_MANAGER_WIRELESS_HARDWARE_ENABLED;
	priv->radio_states[RFKILL_TYPE_WLAN].desc = "Wi-Fi";
	priv->radio_states[RFKILL_TYPE_WLAN].rtype = RFKILL_TYPE_WLAN;

	priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = TRUE;
	priv->radio_states[RFKILL_TYPE_WWAN].key = NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED;
	priv->radio_states[RFKILL_TYPE_WWAN].prop = NM_MANAGER_WWAN_ENABLED;
	priv->radio_states[RFKILL_TYPE_WWAN].hw_prop = NM_MANAGER_WWAN_HARDWARE_ENABLED;
	priv->radio_states[RFKILL_TYPE_WWAN].desc = "WWAN";
	priv->radio_states[RFKILL_TYPE_WWAN].rtype = RFKILL_TYPE_WWAN;

	for (i = 0; i < RFKILL_TYPE_MAX; i++)
		priv->radio_states[i].hw_enabled = TRUE;

	priv->sleeping = FALSE;
	priv->state = NM_STATE_DISCONNECTED;
	priv->startup = TRUE;

	/* sleep/wake handling */
	priv->sleep_monitor = nm_sleep_monitor_new ();
	g_signal_connect (priv->sleep_monitor, NM_SLEEP_MONITOR_SLEEPING,
	                  G_CALLBACK (sleeping_cb), self);

	/* Listen for authorization changes */
	priv->auth_mgr = g_object_ref (nm_auth_manager_get ());
	g_signal_connect (priv->auth_mgr,
	                  NM_AUTH_MANAGER_SIGNAL_CHANGED,
	                  G_CALLBACK (auth_mgr_changed),
	                  self);

	/* Monitor the firmware directory */
	if (strlen (KERNEL_FIRMWARE_DIR)) {
		file = g_file_new_for_path (KERNEL_FIRMWARE_DIR "/");
		priv->fw_monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
		g_object_unref (file);
	}

	if (priv->fw_monitor) {
		g_signal_connect (priv->fw_monitor, "changed",
		                  G_CALLBACK (firmware_dir_changed),
		                  self);
		_LOGI (LOGD_CORE, "monitoring kernel firmware directory '%s'.",
		             KERNEL_FIRMWARE_DIR);
	} else {
		_LOGW (LOGD_CORE, "failed to monitor kernel firmware directory '%s'.",
		       KERNEL_FIRMWARE_DIR);
	}

	/* Update timestamps in active connections */
	priv->timestamp_update_id = g_timeout_add_seconds (300, (GSourceFunc) periodic_update_active_connection_timestamps, self);

	priv->metered = NM_METERED_UNKNOWN;
	priv->sleep_devices = g_hash_table_new (nm_direct_hash, NULL);
}

static void
get_property (GObject *object, guint prop_id,
              GValue *value, GParamSpec *pspec)
{
	NMManager *self = NM_MANAGER (object);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMConfigData *config_data;
	const NMGlobalDnsConfig *dns_config;
	const char *type;
	const char *path;
	NMActiveConnection *ac;
	GPtrArray *ptrarr;

	switch (prop_id) {
	case PROP_VERSION:
		g_value_set_string (value, VERSION);
		break;
	case PROP_CAPABILITIES:
		g_value_set_variant (value, g_variant_new_fixed_array (G_VARIANT_TYPE ("u"),
		                                                       priv->capabilities->data,
		                                                       priv->capabilities->len,
		                                                       sizeof (guint32)));
		break;
	case PROP_STATE:
		g_value_set_uint (value, priv->state);
		break;
	case PROP_STARTUP:
		g_value_set_boolean (value, priv->startup);
		break;
	case PROP_NETWORKING_ENABLED:
		g_value_set_boolean (value, priv->net_enabled);
		break;
	case PROP_WIRELESS_ENABLED:
		g_value_set_boolean (value, radio_enabled_for_type (self, RFKILL_TYPE_WLAN, TRUE));
		break;
	case PROP_WIRELESS_HARDWARE_ENABLED:
		g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WLAN].hw_enabled);
		break;
	case PROP_WWAN_ENABLED:
		g_value_set_boolean (value, radio_enabled_for_type (self, RFKILL_TYPE_WWAN, TRUE));
		break;
	case PROP_WWAN_HARDWARE_ENABLED:
		g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WWAN].hw_enabled);
		break;
	case PROP_WIMAX_ENABLED:
		g_value_set_boolean (value, FALSE);
		break;
	case PROP_WIMAX_HARDWARE_ENABLED:
		g_value_set_boolean (value, FALSE);
		break;
	case PROP_ACTIVE_CONNECTIONS:
		ptrarr = g_ptr_array_new ();
		c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (ac));
			if (path)
				g_ptr_array_add (ptrarr, g_strdup (path));
		}
		g_ptr_array_add (ptrarr, NULL);
		g_value_take_boxed (value, g_ptr_array_free (ptrarr, FALSE));
		break;
	case PROP_CONNECTIVITY:
		g_value_set_uint (value, priv->connectivity_state);
		break;
	case PROP_CONNECTIVITY_CHECK_AVAILABLE:
		config_data = nm_config_get_data (priv->config);
		g_value_set_boolean (value, nm_config_data_get_connectivity_uri (config_data) != NULL);
		break;
	case PROP_CONNECTIVITY_CHECK_ENABLED:
		g_value_set_boolean (value, concheck_enabled (self, NULL));
		break;
	case PROP_CONNECTIVITY_CHECK_URI:
		config_data = nm_config_get_data (priv->config);
		g_value_set_string (value, nm_config_data_get_connectivity_uri (config_data));
		break;
	case PROP_PRIMARY_CONNECTION:
		nm_dbus_utils_g_value_set_object_path (value, priv->primary_connection);
		break;
	case PROP_PRIMARY_CONNECTION_TYPE:
		type = NULL;
		if (priv->primary_connection) {
			NMConnection *con;

			con = nm_active_connection_get_applied_connection (priv->primary_connection);
			if (con)
				type = nm_connection_get_connection_type (con);
		}
		g_value_set_string (value, type ?: "");
		break;
	case PROP_ACTIVATING_CONNECTION:
		nm_dbus_utils_g_value_set_object_path (value, priv->activating_connection);
		break;
	case PROP_SLEEPING:
		g_value_set_boolean (value, priv->sleeping);
		break;
	case PROP_DEVICES:
		g_value_take_boxed (value,
		                    nm_utils_strv_make_deep_copied (_get_devices_paths (self,
		                                                                        FALSE)));
		break;
	case PROP_METERED:
		g_value_set_uint (value, priv->metered);
		break;
	case PROP_GLOBAL_DNS_CONFIGURATION:
		config_data = nm_config_get_data (priv->config);
		dns_config = nm_config_data_get_global_dns_config (config_data);
		nm_global_dns_config_to_dbus (dns_config, value);
		break;
	case PROP_ALL_DEVICES:
		g_value_take_boxed (value,
		                    nm_utils_strv_make_deep_copied (_get_devices_paths (self,
		                                                                        TRUE)));
		break;
	case PROP_CHECKPOINTS:
		g_value_take_boxed (value,
		                    priv->checkpoint_mgr
		                    ? nm_utils_strv_make_deep_copied (nm_checkpoint_manager_get_checkpoint_paths (priv->checkpoint_mgr,
		                                                                                                  NULL))
		                    : NULL);
		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)
{
	NMManager *self = NM_MANAGER (object);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	NMGlobalDnsConfig *dns_config;
	GError *error = NULL;

	switch (prop_id) {
	case PROP_WIRELESS_ENABLED:
		manager_radio_user_toggled (NM_MANAGER (object),
		                            &priv->radio_states[RFKILL_TYPE_WLAN],
		                            g_value_get_boolean (value));
		break;
	case PROP_WWAN_ENABLED:
		manager_radio_user_toggled (NM_MANAGER (object),
		                            &priv->radio_states[RFKILL_TYPE_WWAN],
		                            g_value_get_boolean (value));
		break;
	case PROP_WIMAX_ENABLED:
		/* WIMAX is deprecated. This does nothing. */
		break;
	case PROP_CONNECTIVITY_CHECK_ENABLED:
		nm_config_set_connectivity_check_enabled (priv->config,
		                                          g_value_get_boolean (value));
		break;
	case PROP_GLOBAL_DNS_CONFIGURATION:
		dns_config = nm_global_dns_config_from_dbus (value, &error);
		if (!error)
			nm_config_set_global_dns (priv->config, dns_config, &error);

		nm_global_dns_config_free (dns_config);

		if (error) {
			_LOGD (LOGD_CORE, "set global DNS failed with error: %s", error->message);
			g_error_free (error);
		}
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
_deinit_device_factory (NMDeviceFactory *factory, gpointer user_data)
{
	g_signal_handlers_disconnect_matched (factory, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NM_MANAGER (user_data));
}

static void
dispose (GObject *object)
{
	NMManager *self = NM_MANAGER (object);
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
	CList *iter;

	nm_assert (c_list_is_empty (&priv->async_op_lst_head));

	g_signal_handlers_disconnect_by_func (priv->platform,
	                                      G_CALLBACK (platform_link_cb),
	                                      self);
	while ((iter = c_list_first (&priv->link_cb_lst))) {
		PlatformLinkCbData *data = c_list_entry (iter, PlatformLinkCbData, lst);

		g_source_remove (data->idle_id);
		c_list_unlink_stale (&data->lst);
		g_slice_free (PlatformLinkCbData, data);
	}

	while ((iter = c_list_first (&priv->auth_lst_head)))
		nm_auth_chain_destroy (nm_auth_chain_parent_lst_entry (iter));

	nm_clear_g_source (&priv->devices_inited_id);

	g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_free);

	if (priv->concheck_mgr) {
		g_signal_handlers_disconnect_by_func (priv->concheck_mgr,
		                                      G_CALLBACK (concheck_config_changed_cb),
		                                      self);
		g_clear_object (&priv->concheck_mgr);
	}

	if (priv->auth_mgr) {
		g_signal_handlers_disconnect_by_func (priv->auth_mgr,
		                                      G_CALLBACK (auth_mgr_changed),
		                                      self);
		g_clear_object (&priv->auth_mgr);
	}

	nm_assert (c_list_is_empty (&priv->devices_lst_head));

	nm_clear_g_source (&priv->ac_cleanup_id);

	while ((iter = c_list_first (&priv->active_connections_lst_head)))
		active_connection_remove (self, c_list_entry (iter, NMActiveConnection, active_connections_lst));

	nm_assert (c_list_is_empty (&priv->active_connections_lst_head));
	g_clear_object (&priv->primary_connection);
	g_clear_object (&priv->activating_connection);

	if (priv->config) {
		g_signal_handlers_disconnect_by_func (priv->config, _config_changed_cb, self);
		g_clear_object (&priv->config);
	}

	if (priv->policy) {
		g_signal_handlers_disconnect_by_func (priv->policy, policy_default_ac_changed, self);
		g_signal_handlers_disconnect_by_func (priv->policy, policy_activating_ac_changed, self);
		g_clear_object (&priv->policy);
	}

	if (priv->settings) {
		g_signal_handlers_disconnect_by_func (priv->settings, settings_startup_complete_changed, self);
		g_signal_handlers_disconnect_by_func (priv->settings, system_unmanaged_devices_changed_cb, self);
		g_signal_handlers_disconnect_by_func (priv->settings, connection_added_cb, self);
		g_signal_handlers_disconnect_by_func (priv->settings, connection_updated_cb, self);
		g_signal_handlers_disconnect_by_func (priv->settings, connection_flags_changed, self);
		g_clear_object (&priv->settings);
	}

	if (priv->hostname_manager) {
		g_signal_handlers_disconnect_by_func (priv->hostname_manager, hostname_changed_cb, self);
		g_clear_object (&priv->hostname_manager);
	}

	g_clear_object (&priv->vpn_manager);

	sleep_devices_clear (self);
	g_clear_pointer (&priv->sleep_devices, g_hash_table_unref);

	if (priv->sleep_monitor) {
		g_signal_handlers_disconnect_by_func (priv->sleep_monitor, sleeping_cb, self);
		g_clear_object (&priv->sleep_monitor);
	}

	if (priv->fw_monitor) {
		g_signal_handlers_disconnect_by_func (priv->fw_monitor, firmware_dir_changed, self);

		nm_clear_g_source (&priv->fw_changed_id);

		g_file_monitor_cancel (priv->fw_monitor);
		g_clear_object (&priv->fw_monitor);
	}

	if (priv->rfkill_mgr) {
		g_signal_handlers_disconnect_by_func (priv->rfkill_mgr, rfkill_manager_rfkill_changed_cb, self);
		g_clear_object (&priv->rfkill_mgr);
	}

	nm_clear_g_source (&priv->delete_volatile_connection_idle_id);
	_delete_volatile_connection_all (self, FALSE);
	nm_assert (!priv->delete_volatile_connection_idle_id);
	nm_assert (c_list_is_empty (&priv->delete_volatile_connection_lst_head));

	nm_device_factory_manager_for_each_factory (_deinit_device_factory, self);

	nm_clear_g_source (&priv->timestamp_update_id);

	g_clear_pointer (&priv->device_route_metrics, g_hash_table_destroy);

	G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object);
}

static void
finalize (GObject *object)
{
	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE ((NMManager *) object);

	g_array_free (priv->capabilities, TRUE);

	G_OBJECT_CLASS (nm_manager_parent_class)->finalize (object);

	g_object_unref (priv->platform);
}

static const GDBusSignalInfo signal_info_check_permissions = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
	"CheckPermissions",
);

static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
	"StateChanged",
	.args = NM_DEFINE_GDBUS_ARG_INFOS (
		NM_DEFINE_GDBUS_ARG_INFO ("state", "u"),
	),
);

static const GDBusSignalInfo signal_info_device_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
	"DeviceAdded",
	.args = NM_DEFINE_GDBUS_ARG_INFOS (
		NM_DEFINE_GDBUS_ARG_INFO ("device_path", "o"),
	),
);

static const GDBusSignalInfo signal_info_device_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
	"DeviceRemoved",
	.args = NM_DEFINE_GDBUS_ARG_INFOS (
		NM_DEFINE_GDBUS_ARG_INFO ("device_path", "o"),
	),
);

static const NMDBusInterfaceInfoExtended interface_info_manager = {
	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
		NM_DBUS_INTERFACE,
		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"Reload",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("flags", "u"),
					),
				),
				.handle = impl_manager_reload,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"GetDevices",
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("devices", "ao"),
					),
				),
				.handle = impl_manager_get_devices,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"GetAllDevices",
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("devices", "ao"),
					),
				),
				.handle = impl_manager_get_all_devices,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"GetDeviceByIpIface",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("iface", "s"),
					),
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("device", "o"),
					),
				),
				.handle = impl_manager_get_device_by_ip_iface,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"ActivateConnection",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("connection",      "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("device",          "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"),
					),
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
					),
				),
				.handle = impl_manager_activate_connection,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"AddAndActivateConnection",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("connection",      "a{sa{sv}}"),
						NM_DEFINE_GDBUS_ARG_INFO ("device",          "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"),
					),
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("path",              "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
					),
				),
				.handle = impl_manager_add_and_activate_connection,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"AddAndActivateConnection2",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("connection",      "a{sa{sv}}"),
						NM_DEFINE_GDBUS_ARG_INFO ("device",          "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("options",         "a{sv}"),
					),
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("path",              "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("result",            "a{sv}"),
					),
				),
				.handle = impl_manager_add_and_activate_connection,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"DeactivateConnection",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"),
					),
				),
				.handle = impl_manager_deactivate_connection,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"Sleep",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("sleep", "b"),
					),
				),
				.handle = impl_manager_sleep,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"Enable",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("enable", "b"),
					),
				),
				.handle = impl_manager_enable,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"GetPermissions",
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("permissions", "a{ss}"),
					),
				),
				.handle = impl_manager_get_permissions,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"SetLogging",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("level",   "s"),
						NM_DEFINE_GDBUS_ARG_INFO ("domains", "s"),
					),
				),
				.handle = impl_manager_set_logging,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"GetLogging",
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("level",   "s"),
						NM_DEFINE_GDBUS_ARG_INFO ("domains", "s"),
					),
				),
				.handle = impl_manager_get_logging,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"CheckConnectivity",
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("connectivity", "u"),
					),
				),
				.handle = impl_manager_check_connectivity,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"state",
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("state", "u"),
					),
				),
				.handle = impl_manager_state,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"CheckpointCreate",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("devices",          "ao"),
						NM_DEFINE_GDBUS_ARG_INFO ("rollback_timeout", "u"),
						NM_DEFINE_GDBUS_ARG_INFO ("flags",            "u"),
					),
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
					),
				),
				.handle = impl_manager_checkpoint_create,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"CheckpointDestroy",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
					),
				),
				.handle = impl_manager_checkpoint_destroy,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"CheckpointRollback",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
					),
					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("result", "a{su}"),
					),
				),
				.handle = impl_manager_checkpoint_rollback,
			),
			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
					"CheckpointAdjustRollbackTimeout",
					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
						NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"),
						NM_DEFINE_GDBUS_ARG_INFO ("add_timeout", "u"),
					),
				),
				.handle = impl_manager_checkpoint_adjust_rollback_timeout,
			),
		),
		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
			&nm_signal_info_property_changed_legacy,
			&signal_info_check_permissions,
			&signal_info_state_changed,
			&signal_info_device_added,
			&signal_info_device_removed,
		),
		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Devices",                    "ao",    NM_MANAGER_DEVICES),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("AllDevices",                 "ao",    NM_MANAGER_ALL_DEVICES),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Checkpoints",                "ao",    NM_MANAGER_CHECKPOINTS),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("NetworkingEnabled",          "b",     NM_MANAGER_NETWORKING_ENABLED),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WirelessEnabled",            "b",     NM_MANAGER_WIRELESS_ENABLED,              NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI,               NM_AUDIT_OP_RADIO_CONTROL),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("WirelessHardwareEnabled",    "b",     NM_MANAGER_WIRELESS_HARDWARE_ENABLED),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WwanEnabled",                "b",     NM_MANAGER_WWAN_ENABLED,                  NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN,               NM_AUDIT_OP_RADIO_CONTROL),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("WwanHardwareEnabled",        "b",     NM_MANAGER_WWAN_HARDWARE_ENABLED),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WimaxEnabled",               "b",     NM_MANAGER_WIMAX_ENABLED,                 NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX,              NM_AUDIT_OP_RADIO_CONTROL),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("WimaxHardwareEnabled",       "b",     NM_MANAGER_WIMAX_HARDWARE_ENABLED),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ActiveConnections",          "ao",    NM_MANAGER_ACTIVE_CONNECTIONS),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("PrimaryConnection",          "o",     NM_MANAGER_PRIMARY_CONNECTION),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("PrimaryConnectionType",      "s",     NM_MANAGER_PRIMARY_CONNECTION_TYPE),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Metered",                    "u",     NM_MANAGER_METERED),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ActivatingConnection",       "o",     NM_MANAGER_ACTIVATING_CONNECTION),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Startup",                    "b",     NM_MANAGER_STARTUP),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Version",                    "s",     NM_MANAGER_VERSION),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Capabilities",               "u",     NM_MANAGER_CAPABILITIES),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("State",                      "u",     NM_MANAGER_STATE),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Connectivity",               "u",     NM_MANAGER_CONNECTIVITY),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ConnectivityCheckAvailable", "b",     NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("ConnectivityCheckEnabled",   "b",     NM_MANAGER_CONNECTIVITY_CHECK_ENABLED,    NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, NM_AUDIT_OP_NET_CONTROL),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("ConnectivityCheckUri",       "s",     NM_MANAGER_CONNECTIVITY_CHECK_URI),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("GlobalDnsConfiguration",     "a{sv}", NM_MANAGER_GLOBAL_DNS_CONFIGURATION,      NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,        NM_AUDIT_OP_NET_CONTROL),
		),
	),
	.legacy_property_changed = TRUE,
};

static void
nm_manager_class_init (NMManagerClass *manager_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (manager_class);

	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH);
	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_manager);

	object_class->constructed = constructed;
	object_class->set_property = set_property;
	object_class->get_property = get_property;
	object_class->dispose = dispose;
	object_class->finalize = finalize;

	obj_properties[PROP_VERSION] =
	    g_param_spec_string (NM_MANAGER_VERSION, "", "",
	                         NULL,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_CAPABILITIES] =
	    g_param_spec_variant (NM_MANAGER_CAPABILITIES, "", "",
	                          G_VARIANT_TYPE ("au"),
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_STATE] =
	    g_param_spec_uint (NM_MANAGER_STATE, "", "",
	                       0, NM_STATE_DISCONNECTED, 0,
	                       G_PARAM_READABLE |
	                       G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_STARTUP] =
	    g_param_spec_boolean (NM_MANAGER_STARTUP, "", "",
	                          TRUE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_NETWORKING_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_NETWORKING_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_WIRELESS_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_WIRELESS_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READWRITE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_WIRELESS_HARDWARE_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_WIRELESS_HARDWARE_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_WWAN_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_WWAN_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READWRITE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_WWAN_HARDWARE_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_WWAN_HARDWARE_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_WIMAX_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_WIMAX_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READWRITE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_WIMAX_HARDWARE_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_WIMAX_HARDWARE_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_ACTIVE_CONNECTIONS] =
	    g_param_spec_boxed (NM_MANAGER_ACTIVE_CONNECTIONS, "", "",
	                        G_TYPE_STRV,
	                        G_PARAM_READABLE |
	                        G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_CONNECTIVITY] =
	    g_param_spec_uint (NM_MANAGER_CONNECTIVITY, "", "",
	                       NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
	                       G_PARAM_READABLE |
	                       G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_CONNECTIVITY_CHECK_AVAILABLE] =
	    g_param_spec_boolean (NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE, "", "",
	                          TRUE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_CONNECTIVITY_CHECK_ENABLED] =
	    g_param_spec_boolean (NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, "", "",
	                          TRUE,
	                          G_PARAM_READWRITE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_CONNECTIVITY_CHECK_URI] =
	    g_param_spec_string (NM_MANAGER_CONNECTIVITY_CHECK_URI, "", "",
	                         NULL,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_PRIMARY_CONNECTION] =
	    g_param_spec_string (NM_MANAGER_PRIMARY_CONNECTION, "", "",
	                         NULL,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_PRIMARY_CONNECTION_TYPE] =
	    g_param_spec_string (NM_MANAGER_PRIMARY_CONNECTION_TYPE, "", "",
	                         NULL,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_ACTIVATING_CONNECTION] =
	    g_param_spec_string (NM_MANAGER_ACTIVATING_CONNECTION, "", "",
	                         NULL,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);

	/* Sleeping is not exported over D-Bus */
	obj_properties[PROP_SLEEPING] =
	    g_param_spec_boolean (NM_MANAGER_SLEEPING, "", "",
	                          FALSE,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_DEVICES] =
	    g_param_spec_boxed (NM_MANAGER_DEVICES, "", "",
	                        G_TYPE_STRV,
	                        G_PARAM_READABLE |
	                        G_PARAM_STATIC_STRINGS);

	/**
	 * NMManager:metered:
	 *
	 * Whether the connectivity is metered.
	 *
	 * Since: 1.2
	 **/
	obj_properties[PROP_METERED] =
	    g_param_spec_uint (NM_MANAGER_METERED, "", "",
	                       0, G_MAXUINT32, NM_METERED_UNKNOWN,
	                       G_PARAM_READABLE |
	                       G_PARAM_STATIC_STRINGS);

	/**
	 * NMManager:global-dns-configuration:
	 *
	 * The global DNS configuration.
	 *
	 * Since: 1.2
	 **/
	obj_properties[PROP_GLOBAL_DNS_CONFIGURATION] =
	    g_param_spec_variant (NM_MANAGER_GLOBAL_DNS_CONFIGURATION, "", "",
	                          G_VARIANT_TYPE ("a{sv}"),
	                          NULL,
	                          G_PARAM_READWRITE |
	                          G_PARAM_STATIC_STRINGS);

	/**
	 * NMManager:all-devices:
	 *
	 * All devices, including those that are not realized.
	 *
	 * Since: 1.2
	 **/
	obj_properties[PROP_ALL_DEVICES] =
	    g_param_spec_boxed (NM_MANAGER_ALL_DEVICES, "", "",
	                        G_TYPE_STRV,
	                        G_PARAM_READABLE |
	                        G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_CHECKPOINTS] =
	    g_param_spec_boxed (NM_MANAGER_CHECKPOINTS, "", "",
	                        G_TYPE_STRV,
	                        G_PARAM_READABLE |
	                        G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);

	/* signals */

	/* emitted only for realized devices */
	signals[DEVICE_ADDED] =
	    g_signal_new (NM_MANAGER_DEVICE_ADDED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, NM_TYPE_DEVICE);

	/* Emitted for both realized devices and placeholder devices */
	signals[INTERNAL_DEVICE_ADDED] =
	    g_signal_new (NM_MANAGER_INTERNAL_DEVICE_ADDED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, G_TYPE_OBJECT);

	/* emitted only for realized devices when a device
	 * becomes unrealized or removed */
	signals[DEVICE_REMOVED] =
	    g_signal_new (NM_MANAGER_DEVICE_REMOVED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, NM_TYPE_DEVICE);

	/* Emitted for both realized devices and placeholder devices */
	signals[INTERNAL_DEVICE_REMOVED] =
	    g_signal_new (NM_MANAGER_INTERNAL_DEVICE_REMOVED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, G_TYPE_OBJECT);

	signals[ACTIVE_CONNECTION_ADDED] =
	    g_signal_new (NM_MANAGER_ACTIVE_CONNECTION_ADDED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION);

	signals[ACTIVE_CONNECTION_REMOVED] =
	    g_signal_new (NM_MANAGER_ACTIVE_CONNECTION_REMOVED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION);

	signals[CONFIGURE_QUIT] =
	    g_signal_new (NM_MANAGER_CONFIGURE_QUIT,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 0);
}
