diff options
Diffstat (limited to 'src/core/nm-active-connection.c')
| -rw-r--r-- | src/core/nm-active-connection.c | 1848 |
1 files changed, 1848 insertions, 0 deletions
diff --git a/src/core/nm-active-connection.c b/src/core/nm-active-connection.c new file mode 100644 index 00000000..3c8c472e --- /dev/null +++ b/src/core/nm-active-connection.c @@ -0,0 +1,1848 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2014 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.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); +} |