Blob Blame History Raw
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2008 - 2014 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nm-active-connection.h"

#include "nm-libnm-core-intern/nm-common-macros.h"
#include "nm-dbus-interface.h"
#include "devices/nm-device.h"
#include "settings/nm-settings-connection.h"
#include "nm-simple-connection.h"
#include "nm-auth-utils.h"
#include "nm-auth-manager.h"
#include "nm-libnm-core-intern/nm-auth-subject.h"
#include "nm-keep-alive.h"
#include "NetworkManagerUtils.h"
#include "nm-core-internal.h"

#define AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED  ((NMAuthManagerCallId *) GINT_TO_POINTER (1))

typedef struct _NMActiveConnectionPrivate {
	NMDBusTrackObjPath settings_connection;
	NMConnection *applied_connection;
	char *specific_object;
	NMDevice *device;

	guint64 version_id;

	char *pending_activation_id;

	NMActivationStateFlags state_flags;

	NMActiveConnectionState state;
	bool is_default:1;
	bool is_default6:1;
	bool state_set:1;
	bool vpn:1;
	bool master_ready:1;

	NMActivationType activation_type:3;

	/* capture the original reason why the connection was activated.
	 * For example with NM_ACTIVATION_REASON_ASSUME, the connection
	 * will later change to become fully managed. But the original
	 * reason never changes. */
	NMActivationReason activation_reason:4;

	NMAuthSubject *subject;
	NMActiveConnection *master;

	NMActiveConnection *parent;

	struct {
		NMAuthManagerCallId *call_id_network_control;
		NMAuthManagerCallId *call_id_wifi_shared_permission;

		NMActiveConnectionAuthResultFunc result_func;
		gpointer user_data;
	} auth;

	NMKeepAlive *keep_alive;
} NMActiveConnectionPrivate;

NM_GOBJECT_PROPERTIES_DEFINE (NMActiveConnection,
	PROP_CONNECTION,
	PROP_ID,
	PROP_UUID,
	PROP_TYPE,
	PROP_SPECIFIC_OBJECT,
	PROP_DEVICES,
	PROP_STATE,
	PROP_STATE_FLAGS,
	PROP_DEFAULT,
	PROP_IP4_CONFIG,
	PROP_DHCP4_CONFIG,
	PROP_DEFAULT6,
	PROP_IP6_CONFIG,
	PROP_DHCP6_CONFIG,
	PROP_VPN,
	PROP_MASTER,

	PROP_INT_SETTINGS_CONNECTION,
	PROP_INT_APPLIED_CONNECTION,
	PROP_INT_DEVICE,
	PROP_INT_SUBJECT,
	PROP_INT_MASTER,
	PROP_INT_MASTER_READY,
	PROP_INT_ACTIVATION_TYPE,
	PROP_INT_ACTIVATION_REASON,
);

enum {
	DEVICE_CHANGED,
	DEVICE_METERED_CHANGED,
	PARENT_ACTIVE,
	STATE_CHANGED,
	LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_ABSTRACT_TYPE (NMActiveConnection, nm_active_connection, NM_TYPE_DBUS_OBJECT)

#define NM_ACTIVE_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMActiveConnection, NM_IS_ACTIVE_CONNECTION)

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

static const NMDBusInterfaceInfoExtended interface_info_active_connection;
static const GDBusSignalInfo signal_info_state_changed;

static void check_master_ready (NMActiveConnection *self);
static void _device_cleanup (NMActiveConnection *self);
static void _settings_connection_flags_changed (NMSettingsConnection *settings_connection,
                                                NMActiveConnection *self);
static void _set_activation_type_managed (NMActiveConnection *self);

static void auth_complete (NMActiveConnection *self, gboolean result, const char *message);

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

#define _NMLOG_DOMAIN         LOGD_DEVICE
#define _NMLOG_PREFIX_NAME    "active-connection"
#define _NMLOG(level, ...) \
    G_STMT_START { \
        char _sbuf[64]; \
        NMActiveConnectionPrivate *_priv = self ? NM_ACTIVE_CONNECTION_GET_PRIVATE (self) : NULL; \
        \
        nm_log ((level), _NMLOG_DOMAIN, \
                (_priv && _priv->device) ? nm_device_get_iface (_priv->device) : NULL, \
                (_priv && _priv->applied_connection) ? nm_connection_get_uuid (_priv->applied_connection) : NULL, \
                "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                _NMLOG_PREFIX_NAME, \
                self ? nm_sprintf_buf (_sbuf, "[%p]", self) : "" \
                _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
    } G_STMT_END

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

static
NM_UTILS_LOOKUP_STR_DEFINE (_state_to_string, NMActiveConnectionState,
	NM_UTILS_LOOKUP_DEFAULT (NULL),
	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_UNKNOWN,      "unknown"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_ACTIVATING,   "activating"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_ACTIVATED,    "activated"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, "deactivating"),
	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,  "deactivated"),
);

#define state_to_string_a(state) NM_UTILS_LOOKUP_STR_A (_state_to_string, state)

/* the maximum required buffer size for _state_flags_to_string(). */
#define _NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE (255)

static
NM_UTILS_FLAGS2STR_DEFINE (_state_flags_to_string, NMActivationStateFlags,
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_NONE,                                 "none"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_MASTER,                            "is-master"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_SLAVE,                             "is-slave"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_LAYER2_READY,                         "layer2-ready"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP4_READY,                            "ip4-ready"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP6_READY,                            "ip6-ready"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES,                    "master-has-slaves"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, "lifetime-bound-to-profile-visibility"),
	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_EXTERNAL,                             "external"),
);

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

static void
_settings_connection_updated (NMSettingsConnection *sett_conn,
                              guint update_reason_u,
                              gpointer user_data)
{
	NMActiveConnection *self = user_data;

	/* we don't know which properties actually changed. Just to be sure,
	 * notify about all possible properties. After all, an update of a
	 * connection is a rare event. */

	_notify (self, PROP_ID);

	/* it's a bit odd to update the TYPE of an active connection. But the alternative
	 * is unexpected too. */
	_notify (self, PROP_TYPE);

	/* currently, the UUID and the exported CONNECTION path cannot change. Later, we might
	 * want to support a re-link operation, which associates an active-connection with a different
	 * settings-connection. */
}

static void
_set_settings_connection (NMActiveConnection *self, NMSettingsConnection *sett_conn)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	if (priv->settings_connection.obj == sett_conn)
		return;

	if (priv->settings_connection.obj) {
		g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_updated, self);
		g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_flags_changed, self);
	}
	if (sett_conn) {
		g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, (GCallback) _settings_connection_updated, self);
		if (nm_active_connection_get_activation_type (self) == NM_ACTIVATION_TYPE_EXTERNAL)
			g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, (GCallback) _settings_connection_flags_changed, self);
	}

	nm_dbus_track_obj_path_set (&priv->settings_connection, sett_conn, TRUE);
}

NMActiveConnectionState
nm_active_connection_get_state (NMActiveConnection *self)
{
	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->state;
}

static void
emit_state_changed (NMActiveConnection *self, guint state, guint reason)
{
	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
	                            &interface_info_active_connection,
	                            &signal_info_state_changed,
	                            "(uu)",
	                            (guint32) state,
	                            (guint32) reason);
	g_signal_emit (self, signals[STATE_CHANGED], 0, state, reason);
}

void
nm_active_connection_set_state (NMActiveConnection *self,
                                NMActiveConnectionState new_state,
                                NMActiveConnectionStateReason reason)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	NMActiveConnectionState old_state;

	/* DEACTIVATED is a terminal state */
	g_return_if_fail (   priv->state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED
	                  || new_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED);

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

	_LOGD ("set state %s (was %s)",
	       state_to_string_a (new_state),
	       state_to_string_a (priv->state));

	if (new_state > NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
		/* once we are about to deactivate, we don't need the keep-alive instance
		 * anymore. Freeze/disarm it. */
		nm_keep_alive_disarm (priv->keep_alive);
	}

	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
	    && priv->activation_type == NM_ACTIVATION_TYPE_ASSUME) {
		/* assuming connections mean to gracefully take over an externally
		 * configured device. Once activation is complete, an assumed
		 * activation *is* the same as a full activation. */
		_set_activation_type_managed (self);
	}

	old_state = priv->state;
	priv->state = new_state;
	priv->state_set = TRUE;
	emit_state_changed (self, new_state, reason);
	_notify (self, PROP_STATE);

	check_master_ready (self);

	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
	    || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
		nm_settings_connection_update_timestamp (priv->settings_connection.obj,
		                                         (guint64) time (NULL));
	}

	if (priv->device) {
		if (   old_state < NM_ACTIVE_CONNECTION_STATE_ACTIVATED
		    && new_state >= NM_ACTIVE_CONNECTION_STATE_ACTIVATED &&
		    priv->pending_activation_id)
		{
			nm_device_remove_pending_action (priv->device, priv->pending_activation_id, TRUE);
			nm_clear_g_free (&priv->pending_activation_id);
		}
	}

	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
	    || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
		_notify (self, PROP_IP4_CONFIG);
		_notify (self, PROP_DHCP4_CONFIG);
		_notify (self, PROP_IP6_CONFIG);
		_notify (self, PROP_DHCP6_CONFIG);
	}

	if (priv->state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
		_nm_unused gs_unref_object NMActiveConnection *self_keep_alive = g_object_ref (self);

		auth_complete (self, FALSE, "Authorization request cancelled");

		/* Device is no longer relevant when deactivated. So remove it and
		 * emit property change notification so clients re-read the value,
		 * which will be NULL due to conditions in get_property().
		 */
		_device_cleanup (self);
		_notify (self, PROP_DEVICES);
	}
}

void
nm_active_connection_set_state_fail (NMActiveConnection *self,
                                     NMActiveConnectionStateReason reason,
                                     const char *error_desc)
{
	NMActiveConnectionState s;

	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));

	if (error_desc) {
		_LOGD ("Failed to activate '%s': %s",
		       nm_active_connection_get_settings_connection_id (self),
		       error_desc);
	}

	s = nm_active_connection_get_state (self);
	if (   s >= NM_ACTIVE_CONNECTION_STATE_ACTIVATING
	    && s < NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
		nm_active_connection_set_state (self,
		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATING,
		                                reason);
		s = nm_active_connection_get_state (self);
	}
	if (s < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
		nm_active_connection_set_state (self,
		                                NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
		                                reason);
	}
}

NMActivationStateFlags
nm_active_connection_get_state_flags (NMActiveConnection *self)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	return   priv->state_flags
	       | (  priv->activation_type == NM_ACTIVATION_TYPE_EXTERNAL
	          ? NM_ACTIVATION_STATE_FLAG_EXTERNAL
	          : NM_ACTIVATION_STATE_FLAG_NONE);
}

void
nm_active_connection_set_state_flags_full (NMActiveConnection *self,
                                           NMActivationStateFlags state_flags,
                                           NMActivationStateFlags mask)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	NMActivationStateFlags f;

	nm_assert (!NM_FLAGS_HAS (mask, NM_ACTIVATION_STATE_FLAG_EXTERNAL));

	f = (priv->state_flags & ~mask) | (state_flags & mask);
	if (f != priv->state_flags) {
		char buf1[_NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE];
		char buf2[_NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE];

		_LOGD ("set state-flags %s (was %s)",
		       _state_flags_to_string (f, buf1, sizeof (buf1)),
		       _state_flags_to_string (priv->state_flags, buf2, sizeof (buf2)));
		priv->state_flags = f;
		_notify (self, PROP_STATE_FLAGS);

		nm_keep_alive_set_settings_connection_watch_visible (priv->keep_alive,
		                                                       NM_FLAGS_HAS (priv->state_flags,
		                                                                     NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY)
		                                                     ? priv->settings_connection.obj
		                                                     : NULL);
	}
}

const char *
nm_active_connection_get_settings_connection_id (NMActiveConnection *self)
{
	NMSettingsConnection *sett_conn;

	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);

	sett_conn = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj;
	return sett_conn
	       ? nm_settings_connection_get_id (sett_conn)
	       : NULL;
}

NMSettingsConnection *
_nm_active_connection_get_settings_connection (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj;
}

NMSettingsConnection *
nm_active_connection_get_settings_connection (NMActiveConnection *self)
{
	NMSettingsConnection *sett_conn;

	sett_conn = _nm_active_connection_get_settings_connection (self);

	/* Only call this function on an active-connection that is already
	 * fully set-up (i.e. that has a settings-connection). Other uses
	 * indicate a bug. */
	g_return_val_if_fail (sett_conn, NULL);
	return sett_conn;
}

NMConnection *
nm_active_connection_get_applied_connection (NMActiveConnection *self)
{
	NMConnection *connection;

	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);

	connection = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->applied_connection;

	/* Only call this function on an active-connection that is already
	 * fully set-up (i.e. that has a settings-connection). Other uses
	 * indicate a bug. */
	g_return_val_if_fail (connection, NULL);
	return connection;
}

static void
_set_applied_connection_take (NMActiveConnection *self,
                              NMConnection *applied_connection)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	NMSettingConnection *s_con;
	NMActivationStateFlags flags_val = 0;

	nm_assert (NM_IS_CONNECTION (applied_connection));
	nm_assert (!priv->applied_connection);

	/* we take ownership of @applied_connection. Ensure to pass in a reference. */
	priv->applied_connection = applied_connection;
	nm_connection_clear_secrets (priv->applied_connection);

	/* we determine whether the connection is a master/slave, based solely
	 * on the connection properties itself. */
	s_con = nm_connection_get_setting_connection (priv->applied_connection);
	if (nm_setting_connection_get_master (s_con))
		flags_val |= NM_ACTIVATION_STATE_FLAG_IS_SLAVE;

	if (_nm_connection_type_is_master (nm_setting_connection_get_connection_type (s_con)))
		flags_val |= NM_ACTIVATION_STATE_FLAG_IS_MASTER;

	nm_active_connection_set_state_flags_full (self,
	                                           flags_val,
	                                             NM_ACTIVATION_STATE_FLAG_IS_MASTER
	                                           | NM_ACTIVATION_STATE_FLAG_IS_SLAVE);
}

void
nm_active_connection_set_settings_connection (NMActiveConnection *self,
                                              NMSettingsConnection *sett_conn)
{
	NMActiveConnectionPrivate *priv;

	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
	g_return_if_fail (!priv->settings_connection.obj);
	g_return_if_fail (!priv->applied_connection);

	/* Can't change connection after the ActiveConnection is exported over D-Bus.
	 *
	 * Later, we want to change the settings-connection of an activated connection.
	 * When doing that, this changes the assumption that the settings-connection
	 * never changes (once it's set). That has effects for NMVpnConnection and
	 * NMActivationRequest.
	 * For example, we'd have to cancel all pending seret requests. */
	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (self)));

	_set_settings_connection (self, sett_conn);

	_set_applied_connection_take (self,
	                              nm_simple_connection_new_clone (nm_settings_connection_get_connection (priv->settings_connection.obj)));
}

gboolean
nm_active_connection_has_unmodified_applied_connection (NMActiveConnection *self, NMSettingCompareFlags compare_flags)
{
	NMActiveConnectionPrivate *priv;

	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	g_return_val_if_fail (priv->settings_connection.obj, FALSE);

	return nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection.obj,
	                                                                 priv->applied_connection,
	                                                                 compare_flags);
}

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

void
nm_active_connection_clear_secrets (NMActiveConnection *self)
{
	NMActiveConnectionPrivate *priv;

	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	if (nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection.obj,
	                                                              priv->applied_connection,
	                                                              NM_SETTING_COMPARE_FLAG_NONE)) {
		nm_settings_connection_clear_secrets (priv->settings_connection.obj,
		                                      FALSE,
		                                      FALSE);
	}
	nm_connection_clear_secrets (priv->applied_connection);
}

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

const char *
nm_active_connection_get_specific_object (NMActiveConnection *self)
{
	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->specific_object;
}

void
nm_active_connection_set_specific_object (NMActiveConnection *self,
                                          const char *specific_object)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	/* Nothing that calls this function should be using paths from D-Bus,
	 * where NM uses "/" to mean NULL.
	 */
	nm_assert (!nm_streq0 (specific_object, "/"));

	if (nm_streq0 (priv->specific_object, specific_object))
		return;

	g_free (priv->specific_object);
	priv->specific_object = g_strdup (specific_object);
	_notify (self, PROP_SPECIFIC_OBJECT);
}

void
nm_active_connection_set_default (NMActiveConnection *self,
                                  int addr_family,
                                  gboolean is_default)
{
	NMActiveConnectionPrivate *priv;

	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));
	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));

	is_default = !!is_default;

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) {
		if (priv->is_default != is_default) {
			priv->is_default = is_default;
			_notify (self, PROP_DEFAULT);
		}
	}
	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) {
		if (priv->is_default6 != is_default) {
			priv->is_default6 = is_default;
			_notify (self, PROP_DEFAULT6);
		}
	}
}

gboolean
nm_active_connection_get_default (NMActiveConnection *self, int addr_family)
{
	NMActiveConnectionPrivate *priv;

	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	switch (addr_family) {
	case AF_INET:   return priv->is_default;
	case AF_INET6:  return priv->is_default6;
	default:        return priv->is_default || priv->is_default6;
	}
}

NMAuthSubject *
nm_active_connection_get_subject (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->subject;
}

gboolean
nm_active_connection_get_user_requested (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);

	return nm_auth_subject_get_subject_type (
	           NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->subject
	           ) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
}

NMDevice *
nm_active_connection_get_device (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->device;
}

static void
device_state_changed (NMDevice *device,
                      NMDeviceState new_state,
                      NMDeviceState old_state,
                      NMDeviceStateReason reason,
                      gpointer user_data)
{
	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	/* When already deactivated or before activation, device state changes are useless */
	if (priv->state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
		return;
	if (old_state < NM_DEVICE_STATE_DISCONNECTED)
		return;

	/* Let subclasses handle the state change */
	if (NM_ACTIVE_CONNECTION_GET_CLASS (self)->device_state_changed)
		NM_ACTIVE_CONNECTION_GET_CLASS (self)->device_state_changed (self, device, new_state, old_state);
}

static void
device_master_changed (GObject *object,
                       GParamSpec *pspec,
                       gpointer user_data)
{
	NMDevice *device = NM_DEVICE (object);
	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
	NMActiveConnection *master;
	NMActiveConnectionState master_state;

	if (NM_ACTIVE_CONNECTION (nm_device_get_act_request (device)) != self)
		return;
	if (!nm_device_get_master (device))
		return;
	if (!nm_active_connection_get_master (self))
		return;
	g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_master_changed), self);

	master = nm_active_connection_get_master (self);
	master_state = nm_active_connection_get_state (master);
	if (master_state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
		/* Master failed before attaching the slave */
		if (NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed)
			NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed (self);
	}
}

static void
device_metered_changed (GObject *object,
                        GParamSpec *pspec,
                        gpointer user_data)
{
	NMActiveConnection *self = (NMActiveConnection *) user_data;
	NMDevice *device = NM_DEVICE (object);

	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));
	g_signal_emit (self, signals[DEVICE_METERED_CHANGED], 0, nm_device_get_metered (device));
}

gboolean
nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device)
{
	NMActiveConnectionPrivate *priv;
	gs_unref_object NMDevice *old_device = NULL;

	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
	g_return_val_if_fail (!device || NM_IS_DEVICE (device), FALSE);

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	if (device == priv->device)
		return TRUE;

	_LOGD ("set device %s%s%s [%p]",
	       NM_PRINT_FMT_QUOTED (device && nm_device_get_iface (device),
	                            "\"",
	                            nm_device_get_iface (device),
	                            "\"",
	                            device ? "(unknown)" : "(null)"),
	       device);

	old_device = priv->device ? g_object_ref (priv->device) : NULL;
	_device_cleanup (self);

	if (device) {
		/* Device obviously can't be its own master */
		g_return_val_if_fail (!priv->master || device != nm_active_connection_get_device (priv->master), FALSE);

		priv->device = g_object_ref (device);

		g_signal_connect (device, NM_DEVICE_STATE_CHANGED,
		                  G_CALLBACK (device_state_changed), self);
		g_signal_connect (device, "notify::" NM_DEVICE_MASTER,
		                  G_CALLBACK (device_master_changed), self);
		g_signal_connect (device, "notify::" NM_DEVICE_METERED,
		                  G_CALLBACK (device_metered_changed), self);

		if (priv->activation_type != NM_ACTIVATION_TYPE_EXTERNAL) {
			priv->pending_activation_id = g_strdup_printf (NM_PENDING_ACTIONPREFIX_ACTIVATION"%"G_GUINT64_FORMAT, priv->version_id);
			nm_device_add_pending_action (device, priv->pending_activation_id, TRUE);
		}
	} else {
		/* The ActiveConnection's device can only be cleared after the
		 * connection is activated.
		 */
		g_warn_if_fail (priv->state > NM_ACTIVE_CONNECTION_STATE_UNKNOWN);
		priv->device = NULL;
	}
	_notify (self, PROP_INT_DEVICE);

	g_signal_emit (self, signals[DEVICE_CHANGED], 0, priv->device, old_device);

	_notify (self, PROP_DEVICES);

	return TRUE;
}

NMActiveConnection *
nm_active_connection_get_master (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->master;
}

/**
 * nm_active_connection_get_master_ready:
 * @self: the #NMActiveConnection
 *
 * Returns: %TRUE if the connection has a master connection, and that
 * master connection is ready to accept slaves.  Otherwise %FALSE.
 */
gboolean
nm_active_connection_get_master_ready (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->master_ready;
}

static void
check_master_ready (NMActiveConnection *self)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	gboolean signalling = FALSE;

	/* ActiveConnetions don't enter the ACTIVATING state until they have a
	 * NMDevice in PREPARE or higher states, so the master active connection's
	 * device will be ready to accept slaves when the master is in ACTIVATING
	 * or higher states.
	 */
	if (   !priv->master_ready
	    && priv->master
	    && priv->state == NM_ACTIVE_CONNECTION_STATE_ACTIVATING
	    && NM_IN_SET (nm_active_connection_get_state (priv->master),
	                  NM_ACTIVE_CONNECTION_STATE_ACTIVATING,
	                  NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
		signalling = TRUE;
	}

	_LOGD ("check-master-ready: %s (state %s, %s)",
	       signalling
	           ? "signal"
	           : (priv->master_ready ? "already signalled" : "not signalling"),
	       state_to_string_a (priv->state),
	       priv->master
	           ? nm_sprintf_bufa (128, "master %p is in state %s",
	                              priv->master,
	                              state_to_string_a (nm_active_connection_get_state (priv->master)))
	           : "no master");

	if (signalling) {
		priv->master_ready = TRUE;
		_notify (self, PROP_INT_MASTER_READY);

		/* Also notify clients to recheck the exported 'master' property to
		 * ensure that if the master connection was created without a device
		 * that we notify clients when the master device is known.
		 */
		_notify (self, PROP_MASTER);
	}
}

static void
master_state_cb (NMActiveConnection *master,
                 GParamSpec *pspec,
                 gpointer user_data)
{
	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	NMActiveConnectionState master_state = nm_active_connection_get_state (master);

	check_master_ready (self);

	if (   master_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATING
	    && !priv->master_ready) {
		/* Master disconnected before the slave was added */
		if (NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed)
			NM_ACTIVE_CONNECTION_GET_CLASS (self)->master_failed (self);
	}
}

/**
 * nm_active_connection_set_master:
 * @self: the #NMActiveConnection
 * @master: if the activation depends on another device (ie, bond or bridge
 * master to which this device will be enslaved) pass the #NMActiveConnection
 * that this activation request is a child of
 *
 * Sets the master active connection of @self.
 */
void
nm_active_connection_set_master (NMActiveConnection *self, NMActiveConnection *master)
{
	NMActiveConnectionPrivate *priv;

	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (self));
	g_return_if_fail (NM_IS_ACTIVE_CONNECTION (master));

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	/* Master is write-once, and must be set before exporting the object */
	g_return_if_fail (priv->master == NULL);
	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (self)));
	if (priv->device) {
		/* Note, the master ActiveConnection may not yet have a device */
		g_return_if_fail (priv->device != nm_active_connection_get_device (master));
	}

	_LOGD ("set master %p, %s, state %s",
	       master,
	       nm_active_connection_get_settings_connection_id (master),
	       state_to_string_a (nm_active_connection_get_state (master)));

	priv->master = g_object_ref (master);
	g_signal_connect (priv->master,
	                  "notify::" NM_ACTIVE_CONNECTION_STATE,
	                  (GCallback) master_state_cb,
	                  self);

	check_master_ready (self);
}

NMActivationType
nm_active_connection_get_activation_type (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NM_ACTIVATION_TYPE_MANAGED);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->activation_type;
}

static void
_set_activation_type (NMActiveConnection *self,
                      NMActivationType activation_type)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	gboolean state_flags_changed;

	if (priv->activation_type == activation_type)
		return;

	state_flags_changed =    (priv->activation_type == NM_ACTIVATION_TYPE_EXTERNAL)
	                      != (activation_type == NM_ACTIVATION_TYPE_EXTERNAL);

	priv->activation_type = activation_type;

	if (priv->settings_connection.obj) {
		if (activation_type == NM_ACTIVATION_TYPE_EXTERNAL)
			g_signal_connect (priv->settings_connection.obj, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, (GCallback) _settings_connection_flags_changed, self);
		else
			g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_flags_changed, self);
	}

	if (state_flags_changed)
		_notify (self, PROP_STATE_FLAGS);
}

static void
_set_activation_type_managed (NMActiveConnection *self)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	if (priv->activation_type == NM_ACTIVATION_TYPE_MANAGED)
		return;

	_LOGD ("update activation type from %s to %s",
	       nm_activation_type_to_string (priv->activation_type),
	       nm_activation_type_to_string (NM_ACTIVATION_TYPE_MANAGED));

	_set_activation_type (self, NM_ACTIVATION_TYPE_MANAGED);

	if (   priv->device
	    && self == NM_ACTIVE_CONNECTION (nm_device_get_act_request (priv->device))
	    && NM_IN_SET (nm_device_sys_iface_state_get (priv->device),
	                  NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
	                  NM_DEVICE_SYS_IFACE_STATE_ASSUME))
		nm_device_sys_iface_state_set (priv->device, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
}

NMActivationReason
nm_active_connection_get_activation_reason (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NM_ACTIVATION_REASON_UNSET);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->activation_reason;
}

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

/**
 * nm_active_connection_get_keep_alive:
 * @self: the #NMActiveConnection instance
 *
 * Gives the #NMKeepAlive instance of the active connection. Note that
 * @self is guaranteed not to swap the keep-alive instance, so it is
 * in particular safe to assume that the keep-alive instance is alive
 * as long as @self, and that nm_active_connection_get_keep_alive()
 * will return always the same instance.
 *
 * In particular this means, that it is safe and encouraged, that you
 * register to the notify:alive property changed signal of the returned
 * instance.
 *
 * Returns: the #NMKeepAlive instance.
 */
NMKeepAlive *
nm_active_connection_get_keep_alive (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->keep_alive;
}

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

static void
_settings_connection_flags_changed (NMSettingsConnection *settings_connection,
                                    NMActiveConnection *self)
{
	NMDevice *device;

	nm_assert (NM_IS_ACTIVE_CONNECTION (self));
	nm_assert (NM_IS_SETTINGS_CONNECTION (settings_connection));
	nm_assert (nm_active_connection_get_activation_type (self) == NM_ACTIVATION_TYPE_EXTERNAL);
	nm_assert (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj == settings_connection);

	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (settings_connection),
	                  NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
		return;

	_set_activation_type_managed (self);

	device = nm_active_connection_get_device (self);
	if (device) {
		gs_free_error GError *error = NULL;

		if (!nm_device_reapply (device,
		                        nm_settings_connection_get_connection (nm_active_connection_get_settings_connection (self)),
		                        &error)) {
			_LOGW ("failed to reapply new device settings on previously externally managed device: %s",
			       error->message);
		}
	}
}

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

static void unwatch_parent (NMActiveConnection *self, gboolean unref);

static void
parent_destroyed (gpointer user_data, GObject *parent)
{
	NMActiveConnection *self = user_data;

	unwatch_parent (self, FALSE);
	g_signal_emit (self, signals[PARENT_ACTIVE], 0, NULL);
}

static void
parent_state_cb (NMActiveConnection *parent_ac,
                 GParamSpec *pspec,
                 gpointer user_data)
{
	NMActiveConnection *self = user_data;
	NMActiveConnectionState parent_state = nm_active_connection_get_state (parent_ac);

	if (parent_state < NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
		return;

	unwatch_parent (self, TRUE);

	if (parent_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED)
		g_signal_emit (self, signals[PARENT_ACTIVE], 0, parent_ac);
}

static void
unwatch_parent (NMActiveConnection *self, gboolean unref)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	g_signal_handlers_disconnect_by_func (priv->parent,
	                                      (GCallback) parent_state_cb,
	                                      self);
	if (unref)
		g_object_weak_unref ((GObject *) priv->parent, parent_destroyed, self);
	priv->parent = NULL;
}

/**
 * nm_active_connection_set_parent:
 * @self: the #NMActiveConnection
 * @parent: The #NMActiveConnection that must be active before the manager
 * can proceed progressing the device to disconnected state for us.
 *
 * Sets the parent connection of @self. A "parent-active" signal will be
 * emitted when the parent connection becomes active.
 */
void
nm_active_connection_set_parent (NMActiveConnection *self, NMActiveConnection *parent)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	g_return_if_fail (priv->parent == NULL);
	priv->parent = parent;
	g_signal_connect (priv->parent,
	                  "notify::" NM_ACTIVE_CONNECTION_STATE,
	                  (GCallback) parent_state_cb,
	                  self);
	g_object_weak_ref ((GObject *) priv->parent, parent_destroyed, self);
}

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

static void
auth_complete (NMActiveConnection *self, gboolean result, const char *message)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	NMActiveConnectionAuthResultFunc result_func;
	gpointer user_data;

	if (priv->auth.call_id_network_control)
		nm_auth_manager_check_authorization_cancel (priv->auth.call_id_network_control);
	if (priv->auth.call_id_wifi_shared_permission) {
		if (priv->auth.call_id_wifi_shared_permission == AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED)
			priv->auth.call_id_wifi_shared_permission = NULL;
		else
			nm_auth_manager_check_authorization_cancel (priv->auth.call_id_wifi_shared_permission);
	}

	nm_assert (!priv->auth.call_id_network_control);
	nm_assert (!priv->auth.call_id_wifi_shared_permission);
	if (priv->auth.result_func) {
		result_func = priv->auth.result_func;
		priv->auth.result_func = NULL;
		user_data = g_steal_pointer (&priv->auth.user_data);

		result_func (self,
		             result,
		             message,
		             user_data);
	}
}

static void
auth_complete_keep_alive (NMActiveConnection *self, gboolean result, const char *message)
{
	_nm_unused gs_unref_object NMActiveConnection *self_keep_alive = g_object_ref (self);

	auth_complete (self, result, message);
}

static void
auth_done (NMAuthManager *auth_mgr,
           NMAuthManagerCallId *auth_call_id,
           gboolean is_authorized,
           gboolean is_challenge,
           GError *error,
           gpointer user_data)

{
	NMActiveConnection *self = NM_ACTIVE_CONNECTION (user_data);
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	NMAuthCallResult result;

	nm_assert (auth_call_id);
	nm_assert (priv->auth.result_func);

	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
		if (auth_call_id == priv->auth.call_id_network_control)
			priv->auth.call_id_network_control = NULL;
		else {
			nm_assert (auth_call_id == priv->auth.call_id_wifi_shared_permission);
			priv->auth.call_id_wifi_shared_permission = NULL;
		}
		return;
	}

	result = nm_auth_call_result_eval (is_authorized, is_challenge, error);

	if (auth_call_id == priv->auth.call_id_network_control) {
		priv->auth.call_id_network_control = NULL;
		if (result != NM_AUTH_CALL_RESULT_YES) {
			auth_complete_keep_alive (self, FALSE, "Not authorized to control networking.");
			return;
		}
	} else {
		nm_assert (auth_call_id == priv->auth.call_id_wifi_shared_permission);
		if (result != NM_AUTH_CALL_RESULT_YES) {
			/* we don't fail right away. Instead, we mark that wifi-shared-permissions
			 * are missing. We prefer to report the failure about network-control.
			 * Below, we will wait longer for call_id_network_control (if it's still
			 * pending). */
			priv->auth.call_id_wifi_shared_permission = AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED;
		} else
			priv->auth.call_id_wifi_shared_permission = NULL;
	}

	if (priv->auth.call_id_network_control)
		return;

	if (priv->auth.call_id_wifi_shared_permission) {
		if (priv->auth.call_id_wifi_shared_permission == AUTH_CALL_ID_SHARED_WIFI_PERMISSION_FAILED)
			auth_complete_keep_alive  (self, FALSE, "Not authorized to share connections via wifi.");
		return;
	}

	auth_complete_keep_alive (self, TRUE, NULL);
}

/**
 * nm_active_connection_authorize:
 * @self: the #NMActiveConnection
 * @initial_connection: (allow-none): for add-and-activate, there
 *   is no @settings_connection available when creating the active connection.
 *   Instead pass an alternative connection.
 * @result_func: function to be called on success or error
 * @user_data: pointer passed to @result_func
 *
 * Checks whether the subject that initiated the active connection (read from
 * the #NMActiveConnection::subject property) is authorized to complete this
 * activation request.
 */
void
nm_active_connection_authorize (NMActiveConnection *self,
                                NMConnection *initial_connection,
                                NMActiveConnectionAuthResultFunc result_func,
                                gpointer user_data)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	const char *wifi_permission = NULL;
	NMConnection *connection;

	g_return_if_fail (result_func);
	g_return_if_fail (!priv->auth.call_id_network_control);
	nm_assert (!priv->auth.call_id_wifi_shared_permission);

	if (initial_connection) {
		g_return_if_fail (NM_IS_CONNECTION (initial_connection));
		g_return_if_fail (!priv->settings_connection.obj);
		g_return_if_fail (!priv->applied_connection);
		connection = initial_connection;
	} else {
		g_return_if_fail (NM_IS_SETTINGS_CONNECTION (priv->settings_connection.obj));
		g_return_if_fail (NM_IS_CONNECTION (priv->applied_connection));
		connection = priv->applied_connection;
	}

	priv->auth.call_id_network_control = nm_auth_manager_check_authorization (nm_auth_manager_get (),
	                                                                          priv->subject,
	                                                                          NM_AUTH_PERMISSION_NETWORK_CONTROL,
	                                                                          TRUE,
	                                                                          auth_done,
	                                                                          self);

	/* Shared wifi connections require special permissions too */
	wifi_permission = nm_utils_get_shared_wifi_permission (connection);
	if (wifi_permission) {
		priv->auth.call_id_wifi_shared_permission = nm_auth_manager_check_authorization (nm_auth_manager_get (),
		                                                                                 priv->subject,
		                                                                                 wifi_permission,
		                                                                                 TRUE,
		                                                                                 auth_done,
		                                                                                 self);
	}

	priv->auth.result_func = result_func;
	priv->auth.user_data = user_data;
}

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

static guint64
_version_id_new (void)
{
	static guint64 id = 0;

	return ++id;
}

guint64
nm_active_connection_version_id_get (NMActiveConnection *self)
{
	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), 0);

	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->version_id;
}

guint64
nm_active_connection_version_id_bump (NMActiveConnection *self)
{
	NMActiveConnectionPrivate *priv;

	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), 0);

	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE  (self);
	priv->version_id = _version_id_new ();
	_LOGT ("new version-id %llu", (unsigned long long) priv->version_id);
	return priv->version_id;
}

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

static void
_device_cleanup (NMActiveConnection *self)
{
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	if (priv->device) {
		g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_state_changed), self);
		g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_master_changed), self);
		g_signal_handlers_disconnect_by_func (priv->device, G_CALLBACK (device_metered_changed), self);
	}

	if (priv->pending_activation_id) {
		nm_device_remove_pending_action (priv->device, priv->pending_activation_id, TRUE);
		nm_clear_g_free (&priv->pending_activation_id);
	}

	g_clear_object (&priv->device);
}

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

static void
get_property (GObject *object, guint prop_id,
              GValue *value, GParamSpec *pspec)
{
	NMActiveConnection *self = NM_ACTIVE_CONNECTION (object);
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	char **strv;
	NMDevice *master_device = NULL;

	switch (prop_id) {

	/* note that while priv->settings_connection.obj might not be set initially,
	 * it will be set before the object is exported on D-Bus. Hence,
	 * nobody is calling these property getters before the object is
	 * exported, at which point we will have a valid settings-connection.
	 *
	 * Therefore, intentionally not check whether priv->settings_connection.obj
	 * is set, to get an assertion failure if somebody tries to access the
	 * getters at the wrong time. */
	case PROP_CONNECTION:
		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->settings_connection));
		break;
	case PROP_ID:
		g_value_set_string (value, nm_settings_connection_get_id (priv->settings_connection.obj));
		break;
	case PROP_UUID:
		g_value_set_string (value, nm_settings_connection_get_uuid (priv->settings_connection.obj));
		break;
	case PROP_TYPE:
		g_value_set_string (value, nm_settings_connection_get_connection_type (priv->settings_connection.obj));
		break;

	case PROP_SPECIFIC_OBJECT:
		g_value_set_string (value, priv->specific_object);
		break;
	case PROP_DEVICES:
		strv = g_new0 (char *, 2);
		if (priv->device && priv->state < NM_ACTIVE_CONNECTION_STATE_DEACTIVATED)
			strv[0] = g_strdup (nm_dbus_object_get_path (NM_DBUS_OBJECT (priv->device)));
		g_value_take_boxed (value, strv);
		break;
	case PROP_STATE:
		if (priv->state_set)
			g_value_set_uint (value, priv->state);
		else {
			/* When the AC has just been created, its externally-visible state should
			 * be "ACTIVATING", even though internally it is "UNKNOWN".
			 */
			g_value_set_uint (value, NM_ACTIVE_CONNECTION_STATE_ACTIVATING);
		}
		break;
	case PROP_STATE_FLAGS:
		g_value_set_uint (value, nm_active_connection_get_state_flags (self));
		break;
	case PROP_DEFAULT:
		g_value_set_boolean (value, priv->is_default);
		break;
	case PROP_IP4_CONFIG:
		/* The IP and DHCP config properties may be overridden by a subclass */
		g_value_set_string (value, NULL);
		break;
	case PROP_DHCP4_CONFIG:
		g_value_set_string (value, NULL);
		break;
	case PROP_DEFAULT6:
		g_value_set_boolean (value, priv->is_default6);
		break;
	case PROP_IP6_CONFIG:
		g_value_set_string (value, NULL);
		break;
	case PROP_DHCP6_CONFIG:
		g_value_set_string (value, NULL);
		break;
	case PROP_VPN:
		g_value_set_boolean (value, priv->vpn);
		break;
	case PROP_MASTER:
		if (priv->master)
			master_device = nm_active_connection_get_device (priv->master);
		nm_dbus_utils_g_value_set_object_path (value, master_device);
		break;
	case PROP_INT_SUBJECT:
		g_value_set_object (value, priv->subject);
		break;
	case PROP_INT_MASTER_READY:
		g_value_set_boolean (value, priv->master_ready);
		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)
{
	NMActiveConnection *self = (NMActiveConnection *) object;
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
	const char *tmp;
	NMSettingsConnection *sett_conn;
	NMConnection *acon;
	int i;

	switch (prop_id) {
	case PROP_INT_SETTINGS_CONNECTION:
		/* construct-only */
		sett_conn = g_value_get_object (value);
		if (sett_conn)
			_set_settings_connection (self, sett_conn);
		break;
	case PROP_INT_APPLIED_CONNECTION:
		/* construct-only */
		acon = g_value_get_object (value);
		if (acon) {
			/* we don't call _set_applied_connection_take() yet, because the instance
			 * is not yet fully initialized. We are currently in the process of setting
			 * the constructor properties.
			 *
			 * For now, just piggyback the connection, but call _set_applied_connection_take()
			 * in constructed(). */
			priv->applied_connection = g_object_ref (acon);
		}
		break;
	case PROP_INT_DEVICE:
		/* construct-only */
		nm_active_connection_set_device (self, g_value_get_object (value));
		break;
	case PROP_INT_SUBJECT:
		/* construct-only */
		priv->subject = g_value_dup_object (value);
		break;
	case PROP_INT_MASTER:
		nm_active_connection_set_master (self, g_value_get_object (value));
		break;
	case PROP_INT_ACTIVATION_TYPE:
		/* construct-only */
		i = g_value_get_int (value);
		if (!NM_IN_SET (i, NM_ACTIVATION_TYPE_MANAGED,
		                   NM_ACTIVATION_TYPE_ASSUME,
		                   NM_ACTIVATION_TYPE_EXTERNAL))
			g_return_if_reached ();
		_set_activation_type (self, (NMActivationType) i);
		break;
	case PROP_STATE_FLAGS:
		/* construct-only */
		priv->state_flags = g_value_get_uint (value);
		nm_assert ((guint) priv->state_flags == g_value_get_uint (value));
		nm_assert (!NM_FLAGS_ANY (priv->state_flags, ~NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY));
		break;
	case PROP_INT_ACTIVATION_REASON:
		/* construct-only */
		i = g_value_get_int (value);
		priv->activation_reason = i;
		nm_assert (priv->activation_reason == ((NMActivationReason) i));
		break;
	case PROP_SPECIFIC_OBJECT:
		/* construct-only */
		tmp = g_value_get_string (value);
		tmp = nm_dbus_path_not_empty (tmp);
		priv->specific_object = g_strdup (tmp);
		break;
	case PROP_DEFAULT:
		priv->is_default = g_value_get_boolean (value);
		break;
	case PROP_DEFAULT6:
		priv->is_default6 = g_value_get_boolean (value);
		break;
	case PROP_VPN:
		/* construct-only */
		priv->vpn = g_value_get_boolean (value);
		break;
	case PROP_MASTER:
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

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

static void
nm_active_connection_init (NMActiveConnection *self)
{
	NMActiveConnectionPrivate *priv;

	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_ACTIVE_CONNECTION, NMActiveConnectionPrivate);
	self->_priv = priv;

	nm_dbus_track_obj_path_init (&priv->settings_connection,
	                             G_OBJECT (self),
	                             obj_properties[PROP_CONNECTION]);

	c_list_init (&self->active_connections_lst);

	_LOGT ("creating");

	priv->activation_type = NM_ACTIVATION_TYPE_MANAGED;
	priv->version_id = _version_id_new ();

	/* the keep-alive instance must never change. Callers rely on that. */
	priv->keep_alive = nm_keep_alive_new ();
	_nm_keep_alive_set_owner (priv->keep_alive, G_OBJECT (self));
}

static void
constructed (GObject *object)
{
	NMActiveConnection *self = (NMActiveConnection *) object;
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	G_OBJECT_CLASS (nm_active_connection_parent_class)->constructed (object);

	if (   !priv->applied_connection
	    && priv->settings_connection.obj)
		priv->applied_connection = nm_simple_connection_new_clone (nm_settings_connection_get_connection (priv->settings_connection.obj));

	_LOGD ("constructed (%s, version-id %llu, type %s)",
	       G_OBJECT_TYPE_NAME (self),
	       (unsigned long long) priv->version_id,
	       nm_activation_type_to_string (priv->activation_type));

	if (priv->applied_connection) {
		/* priv->applied_connection was set during the construction of the object.
		 * It's not yet fully initialized, so do that now.
		 *
		 * We delayed that, because we may log in _set_applied_connection_take(), and the
		 * first logging line should be "constructed" above). */
		_set_applied_connection_take (self,
		                              g_steal_pointer (&priv->applied_connection));
	}

	if (NM_FLAGS_HAS (priv->state_flags,
	                  NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY))
		nm_keep_alive_set_settings_connection_watch_visible (priv->keep_alive, priv->settings_connection.obj);

	g_return_if_fail (priv->subject);
	g_return_if_fail (priv->activation_reason != NM_ACTIVATION_REASON_UNSET);
}

static void
dispose (GObject *object)
{
	NMActiveConnection *self = NM_ACTIVE_CONNECTION (object);
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	nm_assert (!c_list_is_linked (&self->active_connections_lst));

	_LOGD ("disposing");

	auth_complete (self, FALSE, "Authorization aborted");

	nm_clear_g_free (&priv->specific_object);

	_set_settings_connection (self, NULL);
	g_clear_object (&priv->applied_connection);

	_device_cleanup (self);

	if (priv->master) {
		g_signal_handlers_disconnect_by_func (priv->master,
		                                      (GCallback) master_state_cb,
		                                      self);
	}
	g_clear_object (&priv->master);

	if (priv->parent)
		unwatch_parent (self, TRUE);

	g_clear_object (&priv->subject);

	G_OBJECT_CLASS (nm_active_connection_parent_class)->dispose (object);
}

static void
finalize (GObject *object)
{
	NMActiveConnection *self = NM_ACTIVE_CONNECTION (object);
	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);

	nm_dbus_track_obj_path_set (&priv->settings_connection, NULL, FALSE);

	nm_clear_pointer (&priv->keep_alive, nm_keep_alive_destroy);

	G_OBJECT_CLASS (nm_active_connection_parent_class)->finalize (object);
}

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"),
		NM_DEFINE_GDBUS_ARG_INFO ("reason", "u"),
	),
);

static const NMDBusInterfaceInfoExtended interface_info_active_connection = {
	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
		NM_DBUS_INTERFACE_ACTIVE_CONNECTION,
		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
			&nm_signal_info_property_changed_legacy,
			&signal_info_state_changed,
		),
		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Connection",      "o",  NM_ACTIVE_CONNECTION_CONNECTION),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("SpecificObject",  "o",  NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Id",              "s",  NM_ACTIVE_CONNECTION_ID),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Uuid",            "s",  NM_ACTIVE_CONNECTION_UUID),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Type",            "s",  NM_ACTIVE_CONNECTION_TYPE),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Devices",         "ao", NM_ACTIVE_CONNECTION_DEVICES),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("State",           "u",  NM_ACTIVE_CONNECTION_STATE),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("StateFlags",      "u",  NM_ACTIVE_CONNECTION_STATE_FLAGS),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Default",         "b",  NM_ACTIVE_CONNECTION_DEFAULT),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ip4Config",       "o",  NM_ACTIVE_CONNECTION_IP4_CONFIG),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Dhcp4Config",     "o",  NM_ACTIVE_CONNECTION_DHCP4_CONFIG),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Default6",        "b",  NM_ACTIVE_CONNECTION_DEFAULT6),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Ip6Config",       "o",  NM_ACTIVE_CONNECTION_IP6_CONFIG),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Dhcp6Config",     "o",  NM_ACTIVE_CONNECTION_DHCP6_CONFIG),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Vpn",             "b",  NM_ACTIVE_CONNECTION_VPN),
			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Master",          "o",  NM_ACTIVE_CONNECTION_MASTER),
		),
	),
	.legacy_property_changed = TRUE,
};

static void
nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (ac_class);
	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ac_class);

	g_type_class_add_private (ac_class, sizeof (NMActiveConnectionPrivate));

	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/ActiveConnection");
	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_active_connection);

	object_class->get_property = get_property;
	object_class->set_property = set_property;
	object_class->constructed = constructed;
	object_class->dispose = dispose;
	object_class->finalize = finalize;

	obj_properties[PROP_CONNECTION] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_CONNECTION, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_ID] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_ID, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_UUID] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_UUID, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_TYPE] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_TYPE, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_SPECIFIC_OBJECT] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, "", "",
	                          NULL,
	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_DEVICES] =
	     g_param_spec_boxed (NM_ACTIVE_CONNECTION_DEVICES, "", "",
	                         G_TYPE_STRV,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_STATE] =
	     g_param_spec_uint (NM_ACTIVE_CONNECTION_STATE, "", "",
	                        NM_ACTIVE_CONNECTION_STATE_UNKNOWN,
	                        NM_ACTIVE_CONNECTION_STATE_DEACTIVATING,
	                        NM_ACTIVE_CONNECTION_STATE_UNKNOWN,
	                        G_PARAM_READABLE |
	                        G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_STATE_FLAGS] =
	     g_param_spec_uint (NM_ACTIVE_CONNECTION_STATE_FLAGS, "", "",
	                        0, G_MAXUINT32, NM_ACTIVATION_STATE_FLAG_NONE,
	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
	                        G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_DEFAULT] =
	     g_param_spec_boolean (NM_ACTIVE_CONNECTION_DEFAULT, "", "",
	                           FALSE,
	                           G_PARAM_READWRITE |
	                           G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_IP4_CONFIG] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_IP4_CONFIG, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_DHCP4_CONFIG] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_DHCP4_CONFIG, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_DEFAULT6] =
	     g_param_spec_boolean (NM_ACTIVE_CONNECTION_DEFAULT6, "", "",
	                           FALSE,
	                           G_PARAM_READWRITE |
	                           G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_IP6_CONFIG] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_IP6_CONFIG, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_DHCP6_CONFIG] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_DHCP6_CONFIG, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_VPN] =
	     g_param_spec_boolean (NM_ACTIVE_CONNECTION_VPN, "", "",
	                           FALSE,
	                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
	                           G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_MASTER] =
	     g_param_spec_string (NM_ACTIVE_CONNECTION_MASTER, "", "",
	                          NULL,
	                          G_PARAM_READABLE |
	                          G_PARAM_STATIC_STRINGS);

	/* Internal properties */
	obj_properties[PROP_INT_SETTINGS_CONNECTION] =
	     g_param_spec_object (NM_ACTIVE_CONNECTION_INT_SETTINGS_CONNECTION, "", "",
	                          NM_TYPE_SETTINGS_CONNECTION,
	                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_INT_APPLIED_CONNECTION] =
	     g_param_spec_object (NM_ACTIVE_CONNECTION_INT_APPLIED_CONNECTION, "", "",
	                          NM_TYPE_CONNECTION,
	                          G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_INT_DEVICE] =
	     g_param_spec_object (NM_ACTIVE_CONNECTION_INT_DEVICE, "", "",
	                          NM_TYPE_DEVICE,
	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_INT_SUBJECT] =
	     g_param_spec_object (NM_ACTIVE_CONNECTION_INT_SUBJECT, "", "",
	                          NM_TYPE_AUTH_SUBJECT,
	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_INT_MASTER] =
	     g_param_spec_object (NM_ACTIVE_CONNECTION_INT_MASTER, "", "",
	                          NM_TYPE_ACTIVE_CONNECTION,
	                          G_PARAM_READWRITE |
	                          G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_INT_MASTER_READY] =
	     g_param_spec_boolean (NM_ACTIVE_CONNECTION_INT_MASTER_READY, "", "",
	                           FALSE, G_PARAM_READABLE |
	                           G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_INT_ACTIVATION_TYPE] =
	     g_param_spec_int (NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE, "", "",
	                       NM_ACTIVATION_TYPE_MANAGED,
	                       NM_ACTIVATION_TYPE_EXTERNAL,
	                       NM_ACTIVATION_TYPE_MANAGED,
	                       G_PARAM_WRITABLE |
	                       G_PARAM_CONSTRUCT_ONLY |
	                       G_PARAM_STATIC_STRINGS);

	obj_properties[PROP_INT_ACTIVATION_REASON] =
	     g_param_spec_int (NM_ACTIVE_CONNECTION_INT_ACTIVATION_REASON, "", "",
	                       NM_ACTIVATION_REASON_UNSET,
	                       NM_ACTIVATION_REASON_USER_REQUEST,
	                       NM_ACTIVATION_REASON_UNSET,
	                       G_PARAM_WRITABLE |
	                       G_PARAM_CONSTRUCT_ONLY |
	                       G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);

	signals[DEVICE_CHANGED] =
	    g_signal_new (NM_ACTIVE_CONNECTION_DEVICE_CHANGED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  G_STRUCT_OFFSET (NMActiveConnectionClass, device_changed),
	                  NULL, NULL, NULL,
	                  G_TYPE_NONE, 2, NM_TYPE_DEVICE, NM_TYPE_DEVICE);

	signals[DEVICE_METERED_CHANGED] =
	    g_signal_new (NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  G_STRUCT_OFFSET (NMActiveConnectionClass, device_metered_changed),
	                  NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, G_TYPE_UINT);

	signals[PARENT_ACTIVE] =
	    g_signal_new (NM_ACTIVE_CONNECTION_PARENT_ACTIVE,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  G_STRUCT_OFFSET (NMActiveConnectionClass, parent_active),
	                  NULL, NULL, NULL,
	                  G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION);

	signals[STATE_CHANGED] =
	    g_signal_new (NM_ACTIVE_CONNECTION_STATE_CHANGED,
	                  G_OBJECT_CLASS_TYPE (object_class),
	                  G_SIGNAL_RUN_FIRST,
	                  0, NULL, NULL, NULL,
	                  G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
}