// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2007 - 2009 Novell, Inc. * Copyright (C) 2007 - 2017 Red Hat, Inc. */ #include "nm-default.h" #include "nm-manager.h" #include #include #include #include #include #include #include #include "nm-glib-aux/nm-c-list.h" #include "nm-libnm-core-intern/nm-common-macros.h" #include "nm-dbus-manager.h" #include "vpn/nm-vpn-manager.h" #include "devices/nm-device.h" #include "devices/nm-device-generic.h" #include "platform/nm-platform.h" #include "platform/nmp-object.h" #include "nm-hostname-manager.h" #include "nm-keep-alive.h" #include "nm-rfkill-manager.h" #include "dhcp/nm-dhcp-manager.h" #include "settings/nm-settings.h" #include "settings/nm-settings-connection.h" #include "nm-auth-utils.h" #include "nm-auth-manager.h" #include "NetworkManagerUtils.h" #include "devices/nm-device-factory.h" #include "nm-sleep-monitor.h" #include "nm-connectivity.h" #include "nm-policy.h" #include "nm-session-monitor.h" #include "nm-act-request.h" #include "nm-core-internal.h" #include "nm-config.h" #include "nm-audit-manager.h" #include "nm-std-aux/nm-dbus-compat.h" #include "nm-checkpoint.h" #include "nm-checkpoint-manager.h" #include "nm-dbus-object.h" #include "nm-dispatcher.h" #include "NetworkManagerUtils.h" #define DEVICE_STATE_PRUNE_RATELIMIT_MAX 100u /*****************************************************************************/ typedef struct { gboolean user_enabled; gboolean sw_enabled; gboolean hw_enabled; RfKillType rtype; NMConfigRunStatePropertyType key; const char *desc; const char *prop; const char *hw_prop; } RadioState; typedef enum { ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL, ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2, } AsyncOpType; typedef struct { CList async_op_lst; NMManager *self; AsyncOpType async_op_type; union { struct { NMActiveConnection *active; union { struct { GDBusMethodInvocation *invocation; } activate_user; struct { GDBusMethodInvocation *invocation; NMConnection *connection; NMSettingsConnectionPersistMode persist_mode; bool is_volatile:1; } add_and_activate; }; } ac_auth; }; } AsyncOpData; enum { DEVICE_ADDED, INTERNAL_DEVICE_ADDED, DEVICE_REMOVED, INTERNAL_DEVICE_REMOVED, ACTIVE_CONNECTION_ADDED, ACTIVE_CONNECTION_REMOVED, CONFIGURE_QUIT, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; NM_GOBJECT_PROPERTIES_DEFINE (NMManager, PROP_VERSION, PROP_CAPABILITIES, PROP_STATE, PROP_STARTUP, PROP_NETWORKING_ENABLED, PROP_WIRELESS_ENABLED, PROP_WIRELESS_HARDWARE_ENABLED, PROP_WWAN_ENABLED, PROP_WWAN_HARDWARE_ENABLED, PROP_WIMAX_ENABLED, PROP_WIMAX_HARDWARE_ENABLED, PROP_ACTIVE_CONNECTIONS, PROP_CONNECTIVITY, PROP_CONNECTIVITY_CHECK_AVAILABLE, PROP_CONNECTIVITY_CHECK_ENABLED, PROP_CONNECTIVITY_CHECK_URI, PROP_PRIMARY_CONNECTION, PROP_PRIMARY_CONNECTION_TYPE, PROP_ACTIVATING_CONNECTION, PROP_DEVICES, PROP_METERED, PROP_GLOBAL_DNS_CONFIGURATION, PROP_ALL_DEVICES, PROP_CHECKPOINTS, /* Not exported */ PROP_SLEEPING, ); typedef struct { NMPlatform *platform; GArray *capabilities; CList active_connections_lst_head; CList async_op_lst_head; guint ac_cleanup_id; NMActiveConnection *primary_connection; NMActiveConnection *activating_connection; NMMetered metered; CList devices_lst_head; NMState state; NMConfig *config; NMConnectivity *concheck_mgr; NMPolicy *policy; NMHostnameManager *hostname_manager; struct { GDBusConnection *connection; guint id; } prop_filter; NMRfkillManager *rfkill_mgr; CList link_cb_lst; NMCheckpointManager *checkpoint_mgr; NMSettings *settings; RadioState radio_states[RFKILL_TYPE_MAX]; NMVpnManager *vpn_manager; NMSleepMonitor *sleep_monitor; NMAuthManager *auth_mgr; GHashTable *device_route_metrics; CList auth_lst_head; GHashTable *sleep_devices; /* Firmware dir monitor */ GFileMonitor *fw_monitor; guint fw_changed_id; guint timestamp_update_id; guint devices_inited_id; NMConnectivityState connectivity_state; guint8 device_state_prune_ratelimit_count; bool startup:1; bool devices_inited:1; bool sleeping:1; bool net_enabled:1; unsigned connectivity_check_enabled_last:2; guint delete_volatile_connection_idle_id; CList delete_volatile_connection_lst_head; } NMManagerPrivate; struct _NMManager { NMDBusObject parent; NMManagerPrivate _priv; }; typedef struct { NMDBusObjectClass parent; } NMManagerClass; G_DEFINE_TYPE (NMManager, nm_manager, NM_TYPE_DBUS_OBJECT) #define NM_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMManager, NM_IS_MANAGER) /*****************************************************************************/ NM_DEFINE_SINGLETON_INSTANCE (NMManager); /*****************************************************************************/ #define _NMLOG_PREFIX_NAME "manager" #define _NMLOG(level, domain, ...) \ G_STMT_START { \ const NMLogLevel _level = (level); \ const NMLogDomain _domain = (domain); \ \ if (nm_logging_enabled (_level, _domain)) { \ const NMManager *const _self = (self); \ char _sbuf[32]; \ \ _nm_log (_level, _domain, 0, NULL, NULL, \ "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ _NMLOG_PREFIX_NAME, \ ((_self && _self != singleton_instance) \ ? nm_sprintf_buf (_sbuf, "[%p]", _self) \ : "") \ _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ } \ } G_STMT_END #define _NMLOG2(level, domain, device, ...) \ G_STMT_START { \ const NMLogLevel _level = (level); \ const NMLogDomain _domain = (domain); \ \ if (nm_logging_enabled (_level, _domain)) { \ const NMManager *const _self = (self); \ const char *const _ifname = _nm_device_get_iface (device); \ char _sbuf[32]; \ \ _nm_log (_level, _domain, 0, \ _ifname, NULL, \ "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ _NMLOG_PREFIX_NAME, \ ((_self && _self != singleton_instance) \ ? nm_sprintf_buf (_sbuf, "[%p]", _self) \ : ""), \ NM_PRINT_FMT_QUOTED (_ifname, "(", _ifname, "): ", "") \ _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ } \ } G_STMT_END #define _NMLOG3(level, domain, connection, ...) \ G_STMT_START { \ const NMLogLevel _level = (level); \ const NMLogDomain _domain = (domain); \ \ if (nm_logging_enabled (_level, _domain)) { \ const NMManager *const _self = (self); \ NMConnection *const _connection = (connection); \ const char *const _con_id = _nm_connection_get_id (_connection); \ char _sbuf[32]; \ \ _nm_log (_level, _domain, 0, \ NULL, _nm_connection_get_uuid (_connection), \ "%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ _NMLOG_PREFIX_NAME, \ ((_self && _self != singleton_instance) \ ? nm_sprintf_buf (_sbuf, "[%p]", _self) \ : ""), \ NM_PRINT_FMT_QUOTED (_con_id, "(", _con_id, ") ", "") \ _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ } \ } G_STMT_END /*****************************************************************************/ static const NMDBusInterfaceInfoExtended interface_info_manager; static const GDBusSignalInfo signal_info_check_permissions; static const GDBusSignalInfo signal_info_state_changed; static const GDBusSignalInfo signal_info_device_added; static const GDBusSignalInfo signal_info_device_removed; static void update_connectivity_value (NMManager *self); static gboolean add_device (NMManager *self, NMDevice *device, GError **error); static void _emit_device_added_removed (NMManager *self, NMDevice *device, gboolean is_added); static NMActiveConnection *_new_active_connection (NMManager *self, gboolean is_vpn, NMSettingsConnection *sett_conn, NMConnection *incompl_conn, NMConnection *applied, const char *specific_object, NMDevice *device, NMAuthSubject *subject, NMActivationType activation_type, NMActivationReason activation_reason, NMActivationStateFlags initial_state_flags, GError **error); static void policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data); static gboolean find_master (NMManager *self, NMConnection *connection, NMDevice *device, NMSettingsConnection **out_master_connection, NMDevice **out_master_device, NMActiveConnection **out_master_ac, GError **error); static void nm_manager_update_state (NMManager *manager); static void connection_changed (NMManager *self, NMSettingsConnection *sett_conn); static void device_sleep_cb (NMDevice *device, GParamSpec *pspec, NMManager *self); static void settings_startup_complete_changed (NMSettings *settings, GParamSpec *pspec, NMManager *self); static void retry_connections_for_parent_device (NMManager *self, NMDevice *device); static void active_connection_state_changed (NMActiveConnection *active, GParamSpec *pspec, NMManager *self); static void active_connection_default_changed (NMActiveConnection *active, GParamSpec *pspec, NMManager *self); static void active_connection_parent_active (NMActiveConnection *active, NMActiveConnection *parent_ac, NMManager *self); static NMActiveConnection *active_connection_find (NMManager *self, NMSettingsConnection *sett_conn, const char *uuid, NMActiveConnectionState max_state, GPtrArray **out_all_matching); static NMConnectivity *concheck_get_mgr (NMManager *self); static void _internal_activation_auth_done (NMManager *self, NMActiveConnection *active, gboolean success, const char *error_desc); static void _add_and_activate_auth_done (NMManager *self, AsyncOpType async_op_type, NMActiveConnection *active, NMConnection *connection, GDBusMethodInvocation *invocation, NMSettingsConnectionPersistMode persist_mode, gboolean is_volatile, gboolean success, const char *error_desc); static void _activation_auth_done (NMManager *self, NMActiveConnection *active, GDBusMethodInvocation *invocation, gboolean success, const char *error_desc); /*****************************************************************************/ static NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark) /*****************************************************************************/ static gboolean _connection_is_vpn (NMConnection *connection) { const char *type; type = nm_connection_get_connection_type (connection); if (type) return nm_streq (type, NM_SETTING_VPN_SETTING_NAME); /* we have an incomplete (invalid) connection at hand. That can only * happen during AddAndActivate. Determine whether it's VPN type based * on the existence of a [vpn] section. */ return !!nm_connection_get_setting_vpn (connection); } /*****************************************************************************/ static gboolean concheck_enabled (NMManager *self, gboolean *out_changed) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); guint check_enabled; check_enabled = nm_connectivity_check_enabled (concheck_get_mgr (self)) ? 1 : 2; if (priv->connectivity_check_enabled_last == check_enabled) NM_SET_OUT (out_changed, FALSE); else { NM_SET_OUT (out_changed, TRUE); priv->connectivity_check_enabled_last = check_enabled; } return check_enabled == 1; } static void concheck_config_changed_cb (NMConnectivity *connectivity, NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; gboolean changed; concheck_enabled (self, &changed); if (changed) _notify (self, PROP_CONNECTIVITY_CHECK_ENABLED); c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) nm_device_check_connectivity_update_interval (device); } static NMConnectivity * concheck_get_mgr (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); if (G_UNLIKELY (!priv->concheck_mgr)) { priv->concheck_mgr = g_object_ref (nm_connectivity_get ()); g_signal_connect (priv->concheck_mgr, NM_CONNECTIVITY_CONFIG_CHANGED, G_CALLBACK (concheck_config_changed_cb), self); } return priv->concheck_mgr; } /*****************************************************************************/ static AsyncOpData * _async_op_data_new_authorize_activate_internal (NMManager *self, NMActiveConnection *active_take) { AsyncOpData *async_op_data; async_op_data = g_slice_new0 (AsyncOpData); async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL; async_op_data->self = g_object_ref (self); async_op_data->ac_auth.active = active_take; c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst); return async_op_data; } static AsyncOpData * _async_op_data_new_ac_auth_activate_user (NMManager *self, NMActiveConnection *active_take, GDBusMethodInvocation *invocation_take) { AsyncOpData *async_op_data; async_op_data = g_slice_new0 (AsyncOpData); async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER; async_op_data->self = g_object_ref (self); async_op_data->ac_auth.active = active_take; async_op_data->ac_auth.activate_user.invocation = invocation_take; c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst); return async_op_data; } static AsyncOpData * _async_op_data_new_ac_auth_add_and_activate (NMManager *self, AsyncOpType async_op_type, NMActiveConnection *active_take, GDBusMethodInvocation *invocation_take, NMConnection *connection_take, NMSettingsConnectionPersistMode persist_mode, gboolean is_volatile) { AsyncOpData *async_op_data; nm_assert (NM_IN_SET (async_op_type, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2)); async_op_data = g_slice_new0 (AsyncOpData); async_op_data->async_op_type = async_op_type; async_op_data->self = g_object_ref (self); async_op_data->ac_auth.active = active_take; async_op_data->ac_auth.add_and_activate.invocation = invocation_take; async_op_data->ac_auth.add_and_activate.connection = connection_take; async_op_data->ac_auth.add_and_activate.persist_mode = persist_mode; async_op_data->ac_auth.add_and_activate.is_volatile = is_volatile; c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst); return async_op_data; } static void _async_op_complete_ac_auth_cb (NMActiveConnection *active, gboolean success, const char *error_desc, gpointer user_data) { AsyncOpData *async_op_data = user_data; nm_assert (async_op_data); nm_assert (NM_IS_MANAGER (async_op_data->self)); nm_assert (nm_c_list_contains_entry (&NM_MANAGER_GET_PRIVATE (async_op_data->self)->async_op_lst_head, async_op_data, async_op_lst)); nm_assert (NM_IS_ACTIVE_CONNECTION (active)); nm_assert (active == async_op_data->ac_auth.active); c_list_unlink (&async_op_data->async_op_lst); switch (async_op_data->async_op_type) { case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL: _internal_activation_auth_done (async_op_data->self, async_op_data->ac_auth.active, success, error_desc); break; case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER: _activation_auth_done (async_op_data->self, async_op_data->ac_auth.active, async_op_data->ac_auth.activate_user.invocation, success, error_desc); break; case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE: case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2: _add_and_activate_auth_done (async_op_data->self, async_op_data->async_op_type, async_op_data->ac_auth.active, async_op_data->ac_auth.add_and_activate.connection, async_op_data->ac_auth.add_and_activate.invocation, async_op_data->ac_auth.add_and_activate.persist_mode, async_op_data->ac_auth.add_and_activate.is_volatile, success, error_desc); g_object_unref (async_op_data->ac_auth.add_and_activate.connection); break; default: nm_assert_not_reached (); break; } g_object_unref (async_op_data->ac_auth.active); g_object_unref (async_op_data->self); g_slice_free (AsyncOpData, async_op_data); } /*****************************************************************************/ typedef struct { int ifindex; guint32 aspired_metric; guint32 effective_metric; } DeviceRouteMetricData; static DeviceRouteMetricData * _device_route_metric_data_new (int ifindex, guint32 aspired_metric, guint32 effective_metric) { DeviceRouteMetricData *data; nm_assert (ifindex > 0); /* For IPv4, metrics can use the entire uint32 bit range. For IPv6, * zero is treated like 1024. Since we handle IPv4 and IPv6 identically, * we cannot allow a zero metric here. */ nm_assert (aspired_metric > 0); nm_assert (effective_metric == 0 || aspired_metric <= effective_metric); data = g_slice_new0 (DeviceRouteMetricData); data->ifindex = ifindex; data->aspired_metric = aspired_metric; data->effective_metric = effective_metric ?: aspired_metric; return data; } static guint _device_route_metric_data_by_ifindex_hash (gconstpointer p) { const DeviceRouteMetricData *data = p; NMHashState h; nm_hash_init (&h, 1030338191); nm_hash_update_vals (&h, data->ifindex); return nm_hash_complete (&h); } static gboolean _device_route_metric_data_by_ifindex_equal (gconstpointer pa, gconstpointer pb) { const DeviceRouteMetricData *a = pa; const DeviceRouteMetricData *b = pb; return a->ifindex == b->ifindex; } static guint32 _device_route_metric_get (NMManager *self, int ifindex, NMDeviceType device_type, gboolean lookup_only, guint32 *out_aspired_metric) { NMManagerPrivate *priv; const DeviceRouteMetricData *d2; DeviceRouteMetricData *data; DeviceRouteMetricData data_lookup; const NMDedupMultiHeadEntry *all_links_head; NMPObject links_needle; guint n_links; gboolean cleaned = FALSE; GHashTableIter h_iter; guint32 metric; g_return_val_if_fail (NM_IS_MANAGER (self), 0); NM_SET_OUT (out_aspired_metric, 0); if (ifindex <= 0) { if (lookup_only) return 0; metric = nm_device_get_route_metric_default (device_type); NM_SET_OUT (out_aspired_metric, metric); return metric; } priv = NM_MANAGER_GET_PRIVATE (self); if ( lookup_only && !priv->device_route_metrics) return 0; if (G_UNLIKELY (!priv->device_route_metrics)) { const GHashTable *h; const NMConfigDeviceStateData *device_state; priv->device_route_metrics = g_hash_table_new_full (_device_route_metric_data_by_ifindex_hash, _device_route_metric_data_by_ifindex_equal, NULL, nm_g_slice_free_fcn (DeviceRouteMetricData)); cleaned = TRUE; /* we need to pre-populate the cache for all (still existing) devices from the state-file */ h = nm_config_device_state_get_all (priv->config); if (!h) goto initited; g_hash_table_iter_init (&h_iter, (GHashTable *) h); while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &device_state)) { if (!device_state->route_metric_default_effective) continue; if (!nm_platform_link_get (priv->platform, device_state->ifindex)) { /* we have the entry in the state file, but (currently) no such * ifindex exists in platform. Most likely the entry is obsolete, * hence we skip it. */ continue; } if (!g_hash_table_add (priv->device_route_metrics, _device_route_metric_data_new (device_state->ifindex, device_state->route_metric_default_aspired, device_state->route_metric_default_effective))) nm_assert_not_reached (); } } initited: data_lookup.ifindex = ifindex; data = g_hash_table_lookup (priv->device_route_metrics, &data_lookup); if (data) goto out; if (lookup_only) return 0; if (!cleaned) { /* get the number of all links in the platform cache. */ all_links_head = nm_platform_lookup_all (priv->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, nmp_object_stackinit_id_link (&links_needle, 1)); n_links = all_links_head ? all_links_head->len : 0; /* on systems where a lot of devices are created and go away, the index contains * a lot of stale entries. We must from time to time clean them up. * * Do do this cleanup, whenever we have more enties then 2 times the number of links. */ if (G_UNLIKELY (g_hash_table_size (priv->device_route_metrics) > NM_MAX (20, n_links * 2))) { /* from time to time, we need to do some house-keeping and prune stale entries. * Otherwise, on a system where interfaces frequently come and go (docker), we * keep growing this cache for ifindexes that no longer exist. */ g_hash_table_iter_init (&h_iter, priv->device_route_metrics); while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) { if (!nm_platform_link_get (priv->platform, d2->ifindex)) g_hash_table_iter_remove (&h_iter); } cleaned = TRUE; } } data = _device_route_metric_data_new (ifindex, nm_device_get_route_metric_default (device_type), 0); /* unfortunately, there is no stright forward way to lookup all reserved metrics. * Note, that we don't only have to know which metrics are currently reserved, * but also, which metrics are now seemingly un-used but caused another reserved * metric to be bumped. Hence, the naive O(n^2) search :( * * Well, technically, since we limit bumping the metric to 50, this entire * loop runs at most 50 times, so it's still O(n). Let's just say, it's not * very efficient. */ again: g_hash_table_iter_init (&h_iter, priv->device_route_metrics); while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) { if ( data->effective_metric < d2->aspired_metric || data->effective_metric > d2->effective_metric) { /* no overlap. Skip. */ continue; } if ( !cleaned && !nm_platform_link_get (priv->platform, d2->ifindex)) { /* the metric seems taken, but there is no such interface. This entry * is stale, forget about it. */ g_hash_table_iter_remove (&h_iter); continue; } if (d2->effective_metric == G_MAXUINT32) { /* we cannot bump the metric any further. Done. * * Actually, this can currently not happen because the aspired_metric * are small numbers and we limit the bumping to 50. Still, for * completeness... */ data->effective_metric = G_MAXUINT32; break; } if (d2->effective_metric - data->aspired_metric >= 50) { /* as one active interface reserves an entire range of metrics * (from aspired_metric to effective_metric), that means if you * alternatingly activate two interfaces, their metric will * bump each other. * * Limit this, bump the metric at most 50 points. */ data->effective_metric = data->aspired_metric + 50; break; } /* bump the metric, and search again. */ data->effective_metric = d2->effective_metric + 1; goto again; } _LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d reserves metric %u (aspired %u)", data->ifindex, data->effective_metric, data->aspired_metric); if (!g_hash_table_add (priv->device_route_metrics, data)) nm_assert_not_reached (); out: NM_SET_OUT (out_aspired_metric, data->aspired_metric); return data->effective_metric; } guint32 nm_manager_device_route_metric_reserve (NMManager *self, int ifindex, NMDeviceType device_type) { guint32 metric; metric = _device_route_metric_get (self, ifindex, device_type, FALSE, NULL); nm_assert (metric != 0); return metric; } void nm_manager_device_route_metric_clear (NMManager *self, int ifindex) { NMManagerPrivate *priv; DeviceRouteMetricData data_lookup; priv = NM_MANAGER_GET_PRIVATE (self); if (!priv->device_route_metrics) return; data_lookup.ifindex = ifindex; if (g_hash_table_remove (priv->device_route_metrics, &data_lookup)) { _LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d released", ifindex); } } /*****************************************************************************/ static void _delete_volatile_connection_do (NMManager *self, NMSettingsConnection *connection) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection), NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) return; if (!nm_settings_has_connection (priv->settings, connection)) return; if (active_connection_find (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, NULL)) return; _LOGD (LOGD_DEVICE, "volatile connection disconnected. Deleting connection '%s' (%s)", nm_settings_connection_get_id (connection), nm_settings_connection_get_uuid (connection)); nm_settings_connection_delete (connection, FALSE); } /* Returns: whether to notify D-Bus of the removal or not */ static gboolean active_connection_remove (NMManager *self, NMActiveConnection *active) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_unref_object NMSettingsConnection *connection = NULL; gboolean notify; nm_assert (NM_IS_ACTIVE_CONNECTION (active)); nm_assert (c_list_contains (&priv->active_connections_lst_head, &active->active_connections_lst)); notify = nm_dbus_object_is_exported (NM_DBUS_OBJECT (active)); c_list_unlink (&active->active_connections_lst); g_signal_emit (self, signals[ACTIVE_CONNECTION_REMOVED], 0, active); g_signal_handlers_disconnect_by_func (active, active_connection_state_changed, self); g_signal_handlers_disconnect_by_func (active, active_connection_default_changed, self); g_signal_handlers_disconnect_by_func (active, active_connection_parent_active, self); connection = nm_g_object_ref (nm_active_connection_get_settings_connection (active)); nm_dbus_object_clear_and_unexport (&active); if (connection) _delete_volatile_connection_do (self, connection); return notify; } static gboolean _active_connection_cleanup (gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *ac, *ac_safe; priv->ac_cleanup_id = 0; g_object_freeze_notify (G_OBJECT (self)); c_list_for_each_entry_safe (ac, ac_safe, &priv->active_connections_lst_head, active_connections_lst) { if (nm_active_connection_get_state (ac) == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { if (active_connection_remove (self, ac)) _notify (self, PROP_ACTIVE_CONNECTIONS); } } g_object_thaw_notify (G_OBJECT (self)); return FALSE; } static void active_connection_state_changed (NMActiveConnection *active, GParamSpec *pspec, NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnectionState state; NMSettingsConnection *con; state = nm_active_connection_get_state (active); if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) { /* Destroy active connections from an idle handler to ensure that * their last property change notifications go out, which wouldn't * happen if we destroyed them immediately when their state was set * to DEACTIVATED. */ if (!priv->ac_cleanup_id) priv->ac_cleanup_id = g_idle_add (_active_connection_cleanup, self); con = nm_active_connection_get_settings_connection (active); if (con) g_object_set_qdata (G_OBJECT (con), autoconnect_root_quark (), NULL); } nm_manager_update_state (self); } static void active_connection_default_changed (NMActiveConnection *active, GParamSpec *pspec, NMManager *self) { nm_manager_update_state (self); } /** * active_connection_add(): * @self: the #NMManager * @active: the #NMActiveConnection to manage * * Begins to track and manage @active. Increases the refcount of @active. */ static void active_connection_add (NMManager *self, NMActiveConnection *active) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); nm_assert (NM_IS_ACTIVE_CONNECTION (active)); nm_assert (!c_list_is_linked (&active->active_connections_lst)); c_list_link_front (&priv->active_connections_lst_head, &active->active_connections_lst); g_object_ref (active); g_signal_connect (active, "notify::" NM_ACTIVE_CONNECTION_STATE, G_CALLBACK (active_connection_state_changed), self); g_signal_connect (active, "notify::" NM_ACTIVE_CONNECTION_DEFAULT, G_CALLBACK (active_connection_default_changed), self); g_signal_connect (active, "notify::" NM_ACTIVE_CONNECTION_DEFAULT6, G_CALLBACK (active_connection_default_changed), self); if (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (active))) nm_dbus_object_export (NM_DBUS_OBJECT (active)); g_signal_emit (self, signals[ACTIVE_CONNECTION_ADDED], 0, active); _notify (self, PROP_ACTIVE_CONNECTIONS); } const CList * nm_manager_get_active_connections (NMManager *manager) { return &NM_MANAGER_GET_PRIVATE (manager)->active_connections_lst_head; } static NMActiveConnection * active_connection_find (NMManager *self, NMSettingsConnection *sett_conn, const char *uuid, NMActiveConnectionState max_state /* candidates in state @max_state will be found */, GPtrArray **out_all_matching) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *ac; NMActiveConnection *best_ac = NULL; GPtrArray *all = NULL; nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn)); nm_assert (!out_all_matching || !*out_all_matching); c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { NMSettingsConnection *ac_conn; ac_conn = nm_active_connection_get_settings_connection (ac); if ( sett_conn && sett_conn != ac_conn) continue; if ( uuid && !nm_streq0 (uuid, nm_settings_connection_get_uuid (ac_conn))) continue; if (nm_active_connection_get_state (ac) > max_state) continue; if (!out_all_matching) return ac; if (!best_ac) { best_ac = ac; continue; } if (!all) { all = g_ptr_array_new_with_free_func (g_object_unref); g_ptr_array_add (all, g_object_ref (best_ac)); } g_ptr_array_add (all, g_object_ref (ac)); } if (!best_ac) return NULL; /* as an optimization, we only allocate out_all_matching, if there are more * than one result. If there is only one result, we only return the single * element and don't bother allocating an array. That's the common case. * * Also, in case we have multiple results, we return the *first* one * as @best_ac. */ nm_assert ( !all || ( all->len >= 2 && all->pdata[0] == best_ac)); *out_all_matching = all; return best_ac; } static NMActiveConnection * active_connection_find_by_connection (NMManager *self, NMSettingsConnection *sett_conn, NMConnection *connection, NMActiveConnectionState max_state, GPtrArray **out_all_matching) { nm_assert (NM_IS_MANAGER (self)); nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn)); nm_assert (!connection || NM_IS_CONNECTION (connection)); nm_assert (sett_conn || connection); nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn)); /* Depending on whether connection is a settings connection, * either lookup by object-identity of @connection, or compare the UUID */ return active_connection_find (self, sett_conn, sett_conn ? NULL : nm_connection_get_uuid (connection), max_state, out_all_matching); } typedef struct { NMManager *self; gboolean for_auto_activation; } GetActivatableConnectionsFilterData; static gboolean _get_activatable_connections_filter (NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data) { const GetActivatableConnectionsFilterData *d = user_data; NMConnectionMultiConnect multi_connect; if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn), NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) return FALSE; multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn)); if ( multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE || ( multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE && !d->for_auto_activation)) return TRUE; /* the connection is activatable, if it has no active-connections that are in state * activated, activating, or waiting to be activated. */ return !active_connection_find (d->self, sett_conn, NULL, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NULL); } NMSettingsConnection ** nm_manager_get_activatable_connections (NMManager *manager, gboolean for_auto_activation, gboolean sort, guint *out_len) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); const GetActivatableConnectionsFilterData d = { .self = manager, .for_auto_activation = for_auto_activation, }; return nm_settings_get_connections_clone (priv->settings, out_len, _get_activatable_connections_filter, (gpointer) &d, sort ? nm_settings_connection_cmp_autoconnect_priority_p_with_data : NULL, NULL); } static NMActiveConnection * active_connection_get_by_path (NMManager *self, const char *path) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *ac; ac = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)), path); if ( !ac || !NM_IS_ACTIVE_CONNECTION (ac) || c_list_is_empty (&ac->active_connections_lst)) return NULL; nm_assert (c_list_contains (&priv->active_connections_lst_head, &ac->active_connections_lst)); return ac; } /*****************************************************************************/ static void _config_changed_cb (NMConfig *config, NMConfigData *config_data, NMConfigChangeFlags changes, NMConfigData *old_data, NMManager *self) { g_object_freeze_notify (G_OBJECT (self)); if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) _notify (self, PROP_GLOBAL_DNS_CONFIGURATION); if (!nm_streq0 (nm_config_data_get_connectivity_uri (config_data), nm_config_data_get_connectivity_uri (old_data))) { if ((!nm_config_data_get_connectivity_uri (config_data)) != (!nm_config_data_get_connectivity_uri (old_data))) _notify (self, PROP_CONNECTIVITY_CHECK_AVAILABLE); _notify (self, PROP_CONNECTIVITY_CHECK_URI); } g_object_thaw_notify (G_OBJECT (self)); } static void _reload_auth_cb (NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); GError *ret_error = NULL; NMAuthCallResult result; guint32 flags; NMAuthSubject *subject; char s_buf[60]; NMConfigChangeFlags reload_type = NM_CONFIG_CHANGE_NONE; nm_assert (G_IS_DBUS_METHOD_INVOCATION (context)); c_list_unlink (nm_auth_chain_parent_lst_list (chain)); flags = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "flags")); subject = nm_auth_chain_get_subject (chain); result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_RELOAD); if (result != NM_AUTH_CALL_RESULT_YES) { ret_error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized to reload configuration"); } else { if (NM_FLAGS_ANY (flags, ~NM_MANAGER_RELOAD_FLAG_ALL)) { /* invalid flags */ } else if (flags == 0) reload_type = NM_CONFIG_CHANGE_CAUSE_SIGHUP; else { if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_CONF)) reload_type |= NM_CONFIG_CHANGE_CAUSE_CONF; if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_DNS_RC)) reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_RC; if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_DNS_FULL)) reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_FULL; } if (reload_type == NM_CONFIG_CHANGE_NONE) { ret_error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS, "Invalid flags for reload"); } } nm_audit_log_control_op (NM_AUDIT_OP_RELOAD, nm_sprintf_buf (s_buf, "%u", flags), ret_error == NULL, subject, ret_error ? ret_error->message : NULL); if (ret_error) { g_dbus_method_invocation_take_error (context, ret_error); return; } nm_config_reload (priv->config, reload_type, TRUE); g_dbus_method_invocation_return_value (context, NULL); } static void impl_manager_reload (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; guint32 flags; g_variant_get (parameters, "(u)", &flags); chain = nm_auth_chain_new_context (invocation, _reload_auth_cb, self); if (!chain) { g_dbus_method_invocation_return_error_literal (invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); return; } c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "flags", GUINT_TO_POINTER (flags), NULL); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_RELOAD, TRUE); } /*****************************************************************************/ NMDevice * nm_manager_get_device_by_path (NMManager *self, const char *path) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; g_return_val_if_fail (path, NULL); device = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)), path); if ( !device || !NM_IS_DEVICE (device) || c_list_is_empty (&device->devices_lst)) return NULL; nm_assert (c_list_contains (&priv->devices_lst_head, &device->devices_lst)); return device; } NMDevice * nm_manager_get_device_by_ifindex (NMManager *self, int ifindex) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; if (ifindex > 0) { c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { if (nm_device_get_ifindex (device) == ifindex) return device; } } return NULL; } static NMDevice * find_device_by_permanent_hw_addr (NMManager *self, const char *hwaddr) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; const char *device_addr; guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX]; gsize hwaddr_len; g_return_val_if_fail (hwaddr != NULL, NULL); if (!_nm_utils_hwaddr_aton (hwaddr, hwaddr_bin, sizeof (hwaddr_bin), &hwaddr_len)) return NULL; c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { device_addr = nm_device_get_permanent_hw_address (device); if ( device_addr && nm_utils_hwaddr_matches (hwaddr_bin, hwaddr_len, device_addr, -1)) return device; } return NULL; } static NMDevice * find_device_by_ip_iface (NMManager *self, const char *iface) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; g_return_val_if_fail (iface, NULL); c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { if ( nm_device_is_real (device) && nm_streq0 (nm_device_get_ip_iface (device), iface)) return device; } return NULL; } /** * find_device_by_iface: * @self: the #NMManager * @iface: the device interface to find * @connection: a connection to ensure the returned device is compatible with * @slave: a slave connection to ensure a master is compatible with * * Finds a device by interface name, preferring realized devices. If @slave * is given, this function will only return master devices and will ensure * @slave, when activated, can be a slave of the returned master device. If * @connection is given, this function will only consider devices that are * compatible with @connection. * * Returns: the matching #NMDevice */ static NMDevice * find_device_by_iface (NMManager *self, const char *iface, NMConnection *connection, NMConnection *slave) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *fallback = NULL; NMDevice *candidate; g_return_val_if_fail (iface != NULL, NULL); c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { if (strcmp (nm_device_get_iface (candidate), iface)) continue; if (connection && !nm_device_check_connection_compatible (candidate, connection, NULL)) continue; if (slave) { if (!nm_device_is_master (candidate)) continue; if (!nm_device_check_slave_connection_compatible (candidate, slave)) continue; } if (nm_device_is_real (candidate)) return candidate; else if (!fallback) fallback = candidate; } return fallback; } static gboolean manager_sleeping (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); if (priv->sleeping || !priv->net_enabled) return TRUE; return FALSE; } static const char * _nm_state_to_string (NMState state) { switch (state) { case NM_STATE_ASLEEP: return "ASLEEP"; case NM_STATE_DISCONNECTED: return "DISCONNECTED"; case NM_STATE_DISCONNECTING: return "DISCONNECTING"; case NM_STATE_CONNECTING: return "CONNECTING"; case NM_STATE_CONNECTED_LOCAL: return "CONNECTED_LOCAL"; case NM_STATE_CONNECTED_SITE: return "CONNECTED_SITE"; case NM_STATE_CONNECTED_GLOBAL: return "CONNECTED_GLOBAL"; case NM_STATE_UNKNOWN: default: return "UNKNOWN"; } } static NMState find_best_device_state (NMManager *manager) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); NMState best_state = NM_STATE_DISCONNECTED; NMActiveConnection *ac; c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { NMActiveConnectionState ac_state = nm_active_connection_get_state (ac); switch (ac_state) { case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: if (nm_active_connection_get_default (ac, AF_UNSPEC)) { if (priv->connectivity_state == NM_CONNECTIVITY_FULL) return NM_STATE_CONNECTED_GLOBAL; best_state = NM_STATE_CONNECTED_SITE; } else { if (best_state < NM_STATE_CONNECTING) best_state = NM_STATE_CONNECTED_LOCAL; } break; case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: if (!NM_IN_SET (nm_active_connection_get_activation_type (ac), NM_ACTIVATION_TYPE_EXTERNAL, NM_ACTIVATION_TYPE_ASSUME)) { if (best_state != NM_STATE_CONNECTED_GLOBAL) best_state = NM_STATE_CONNECTING; } break; case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: if (!NM_IN_SET (nm_active_connection_get_activation_type (ac), NM_ACTIVATION_TYPE_EXTERNAL, NM_ACTIVATION_TYPE_ASSUME)) { if (best_state < NM_STATE_DISCONNECTING) best_state = NM_STATE_DISCONNECTING; } break; default: break; } } return best_state; } static void nm_manager_update_metered (NMManager *self) { NMManagerPrivate *priv; NMDevice *device; NMMetered value = NM_METERED_UNKNOWN; g_return_if_fail (NM_IS_MANAGER (self)); priv = NM_MANAGER_GET_PRIVATE (self); if (priv->primary_connection) { device = nm_active_connection_get_device (priv->primary_connection); if (device) value = nm_device_get_metered (device); } if (value != priv->metered) { priv->metered = value; _LOGD (LOGD_CORE, "new metered value: %d", (int) priv->metered); _notify (self, PROP_METERED); } } NMMetered nm_manager_get_metered (NMManager *self) { g_return_val_if_fail (NM_IS_MANAGER (self), NM_METERED_UNKNOWN); return NM_MANAGER_GET_PRIVATE (self)->metered; } static void nm_manager_update_state (NMManager *self) { NMManagerPrivate *priv; NMState new_state = NM_STATE_DISCONNECTED; g_return_if_fail (NM_IS_MANAGER (self)); priv = NM_MANAGER_GET_PRIVATE (self); if (manager_sleeping (self)) new_state = NM_STATE_ASLEEP; else new_state = find_best_device_state (self); if ( new_state >= NM_STATE_CONNECTED_LOCAL && priv->connectivity_state == NM_CONNECTIVITY_FULL) { new_state = NM_STATE_CONNECTED_GLOBAL; } if (priv->state == new_state) return; priv->state = new_state; _LOGI (LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string (new_state)); _notify (self, PROP_STATE); nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self), &interface_info_manager, &signal_info_state_changed, "(u)", (guint32) priv->state); } static void manager_device_state_changed (NMDevice *device, NMDeviceState new_state, NMDeviceState old_state, NMDeviceStateReason reason, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); if ( old_state == NM_DEVICE_STATE_UNMANAGED && new_state > NM_DEVICE_STATE_UNMANAGED) retry_connections_for_parent_device (self, device); if (NM_IN_SET (new_state, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_FAILED)) _notify (self, PROP_ACTIVE_CONNECTIONS); if (NM_IN_SET (new_state, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_ACTIVATED)) { nm_manager_write_device_state (self, device, NULL); G_STATIC_ASSERT_EXPR (DEVICE_STATE_PRUNE_RATELIMIT_MAX < G_MAXUINT8); if (priv->device_state_prune_ratelimit_count++ > DEVICE_STATE_PRUNE_RATELIMIT_MAX) { /* We write the device state to /run. The state files are named after the * ifindex (which is assumed to be unique and not repeat -- in practice * it may repeat). So from time to time, we prune device state files * for interfaces that no longer exist. * * Otherwise, the files might pile up if you create (and destroy) a large * number of software devices. */ priv->device_state_prune_ratelimit_count = 0; nm_config_device_state_prune_stale (NULL, priv->platform); } } if (NM_IN_SET (new_state, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_DISCONNECTED)) nm_settings_device_added (priv->settings, device); } static void device_has_pending_action_changed (NMDevice *device, GParamSpec *pspec, NMManager *self); static void check_if_startup_complete (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; const char *reason; if (!priv->startup) return; if (!priv->devices_inited) return; reason = nm_settings_get_startup_complete_blocked_reason (priv->settings); if (reason) { _LOGD (LOGD_CORE, "startup complete is waiting for connection (%s)", reason); return; } c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { reason = nm_device_has_pending_action_reason (device); if (reason) { _LOGD (LOGD_CORE, "startup complete is waiting for device '%s' (%s)", nm_device_get_iface (device), reason); return; } } _LOGI (LOGD_CORE, "startup complete"); priv->startup = FALSE; /* we no longer care about these signals. Startup-complete only * happens once. */ g_signal_handlers_disconnect_by_func (priv->settings, G_CALLBACK (settings_startup_complete_changed), self); c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { g_signal_handlers_disconnect_by_func (device, G_CALLBACK (device_has_pending_action_changed), self); } _notify (self, PROP_STARTUP); if (nm_config_get_configure_and_quit (priv->config)) g_signal_emit (self, signals[CONFIGURE_QUIT], 0); } static void device_has_pending_action_changed (NMDevice *device, GParamSpec *pspec, NMManager *self) { check_if_startup_complete (self); } static void settings_startup_complete_changed (NMSettings *settings, GParamSpec *pspec, NMManager *self) { check_if_startup_complete (self); } static void _parent_notify_changed (NMManager *self, NMDevice *device, gboolean device_removed) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *candidate; nm_assert (NM_IS_DEVICE (device)); again: c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { if (nm_device_parent_notify_changed (candidate, device, device_removed)) { /* in the unlikely event that this changes anything, we start iterating * again, to be sure that the device list is up-to-date. */ goto again; } } } static gboolean device_is_wake_on_lan (NMPlatform *platform, NMDevice *device) { int ifindex; ifindex = nm_device_get_ip_ifindex (device); if (ifindex <= 0) return FALSE; return nm_platform_link_get_wake_on_lan (platform, ifindex); } static void remove_device (NMManager *self, NMDevice *device, gboolean quitting) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gboolean unmanage = FALSE; _LOG2D (LOGD_DEVICE, device, "removing device (managed %d, wol %d)", nm_device_get_managed (device, FALSE), device_is_wake_on_lan (priv->platform, device)); if (nm_device_get_managed (device, FALSE)) { if (quitting) { /* Leave configured if wo(w)lan and quitting */ if (device_is_wake_on_lan (priv->platform, device)) unmanage = FALSE; else unmanage = nm_device_unmanage_on_quit (device); } else { /* the device is already gone. Unmanage it. */ unmanage = TRUE; } if (unmanage) { if (quitting) nm_device_set_unmanaged_by_quitting (device); else { nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_REMOVED); nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, TRUE, NM_DEVICE_STATE_REASON_REMOVED); } } else if (quitting && nm_config_get_configure_and_quit (priv->config) == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) { nm_device_spawn_iface_helper (device); } } g_signal_handlers_disconnect_matched (device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self); nm_settings_device_removed (priv->settings, device, quitting); c_list_unlink (&device->devices_lst); _parent_notify_changed (self, device, TRUE); if (nm_device_is_real (device)) { gboolean unconfigure_ip_config = !quitting || unmanage; /* When we don't unmanage the device on shutdown, we want to preserve the DNS * configuration in resolv.conf. For that, we must leak the configuration * in NMPolicy/NMDnsManager. We do that, by emitting the device-removed signal * with device's ip-config object still uncleared. In that case, NMPolicy * never learns to unconfigure the ip-config objects and does not remove them * from DNS on shutdown (which is ugly, because we don't cleanup the memory * properly). * * Control that by passing @unconfigure_ip_config. */ nm_device_removed (device, unconfigure_ip_config); _emit_device_added_removed (self, device, FALSE); } else { /* unrealize() does not release a slave device from master and * clear IP configurations, do it here */ nm_device_removed (device, TRUE); } g_signal_emit (self, signals[INTERNAL_DEVICE_REMOVED], 0, device); _notify (self, PROP_ALL_DEVICES); update_connectivity_value (self); nm_dbus_object_clear_and_unexport (&device); check_if_startup_complete (self); } static void device_removed_cb (NMDevice *device, gpointer user_data) { remove_device (NM_MANAGER (user_data), device, FALSE); } NMState nm_manager_get_state (NMManager *manager) { g_return_val_if_fail (NM_IS_MANAGER (manager), NM_STATE_UNKNOWN); return NM_MANAGER_GET_PRIVATE (manager)->state; } /*****************************************************************************/ static NMDevice * find_parent_device_for_connection (NMManager *self, NMConnection *connection, NMDeviceFactory *cached_factory, const char **out_parent_spec) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDeviceFactory *factory; const char *parent_name = NULL; NMSettingsConnection *parent_connection; NMDevice *parent, *first_compatible = NULL; NMDevice *candidate; g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); NM_SET_OUT (out_parent_spec, NULL); if (!cached_factory) { factory = nm_device_factory_manager_find_factory_for_connection (connection); if (!factory) return NULL; } else factory = cached_factory; parent_name = nm_device_factory_get_connection_parent (factory, connection); if (!parent_name) return NULL; NM_SET_OUT (out_parent_spec, parent_name); /* Try as an interface name of a parent device */ parent = find_device_by_iface (self, parent_name, NULL, NULL); if (parent) return parent; /* Maybe a hardware address */ parent = find_device_by_permanent_hw_addr (self, parent_name); if (parent) return parent; /* Maybe a connection UUID */ parent_connection = nm_settings_get_connection_by_uuid (priv->settings, parent_name); if (!parent_connection) return NULL; /* Check if the parent connection is currently activated or is compatible * with some known device. */ c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { /* Unmanaged devices are not compatible with any connection */ if (!nm_device_get_managed (candidate, FALSE)) continue; if (nm_device_get_settings_connection (candidate) == parent_connection) return candidate; if ( !first_compatible && nm_device_check_connection_compatible (candidate, nm_settings_connection_get_connection (parent_connection), NULL)) first_compatible = candidate; } return first_compatible; } /** * nm_manager_get_connection_iface: * @self: the #NMManager * @connection: the #NMConnection to get the interface for * @out_parent: on success, the parent device if any * @out_parent_spec: on return, a string specifying the parent device * in the connection. This can be a device name, a MAC address or a * connection UUID. * @error: an error if determining the virtual interface name failed * * Given @connection, returns the interface name that the connection * would need to use when activated. %NULL is returned if the name * is not specified in connection or a the name for a virtual device * could not be generated. * * Returns: the expected interface name (caller takes ownership), or %NULL */ char * nm_manager_get_connection_iface (NMManager *self, NMConnection *connection, NMDevice **out_parent, const char **out_parent_spec, GError **error) { NMDeviceFactory *factory; char *iface = NULL; NMDevice *parent = NULL; NM_SET_OUT (out_parent, NULL); NM_SET_OUT (out_parent_spec, NULL); factory = nm_device_factory_manager_find_factory_for_connection (connection); if (!factory) { if (nm_streq0 (nm_connection_get_connection_type (connection), NM_SETTING_GENERIC_SETTING_NAME)) { /* the generic type doesn't have a factory. */ goto return_ifname_fom_connection; } g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "NetworkManager plugin for '%s' unavailable", nm_connection_get_connection_type (connection)); return NULL; } if ( !out_parent && !NM_DEVICE_FACTORY_GET_CLASS (factory)->get_connection_iface) { /* optimization. Shortcut lookup of the partent device. */ goto return_ifname_fom_connection; } parent = find_parent_device_for_connection (self, connection, factory, out_parent_spec); iface = nm_device_factory_get_connection_iface (factory, connection, parent ? nm_device_get_ip_iface (parent) : NULL, error); if (!iface) return NULL; if (out_parent) *out_parent = parent; return iface; return_ifname_fom_connection: iface = g_strdup (nm_connection_get_interface_name (connection)); if (!iface) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "failed to determine interface name: error determine name for %s", nm_connection_get_connection_type (connection)); } return iface; } /** * nm_manager_iface_for_uuid: * @self: the #NMManager * @uuid: the connection uuid * * Gets a link name for the given UUID. Useful for the settings plugins that * wish to write configuration files compatible with tooling that can't * interpret our UUIDs. * * Returns: An interface name; %NULL if none matches */ const char * nm_manager_iface_for_uuid (NMManager *self, const char *uuid) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMSettingsConnection *sett_conn; sett_conn = nm_settings_get_connection_by_uuid (priv->settings, uuid); if (!sett_conn) return NULL; return nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn)); } NMDevice * nm_manager_get_device (NMManager *self, const char *ifname, NMDeviceType device_type) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; g_return_val_if_fail (ifname, NULL); g_return_val_if_fail (device_type != NM_DEVICE_TYPE_UNKNOWN, NULL); c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { if ( nm_device_get_device_type (device) == device_type && nm_streq0 (nm_device_get_iface (device), ifname)) return device; } return NULL; } gboolean nm_manager_remove_device (NMManager *self, const char *ifname, NMDeviceType device_type) { NMDevice *d; d = nm_manager_get_device (self, ifname, device_type); if (!d) return FALSE; remove_device (self, d, FALSE); return TRUE; } /** * system_create_virtual_device: * @self: the #NMManager * @connection: the connection which might require a virtual device * * If @connection requires a virtual device and one does not yet exist for it, * creates that device. * * Returns: A #NMDevice that was just realized; %NULL if none */ static NMDevice * system_create_virtual_device (NMManager *self, NMConnection *connection) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDeviceFactory *factory; gs_free NMSettingsConnection **connections = NULL; guint i; gs_free char *iface = NULL; const char *parent_spec; NMDevice *device = NULL, *parent = NULL; NMDevice *dev_candidate; GError *error = NULL; NMLogLevel log_level; g_return_val_if_fail (NM_IS_MANAGER (self), NULL); g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL); iface = nm_manager_get_connection_iface (self, connection, &parent, &parent_spec, &error); if (!iface) { _LOG3D (LOGD_DEVICE, connection, "can't get a name of a virtual device: %s", error->message); g_error_free (error); return NULL; } if (parent_spec && !parent) { /* parent is not ready, wait */ return NULL; } /* See if there's a device that is already compatible with this connection */ c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) { if (nm_device_check_connection_compatible (dev_candidate, connection, NULL)) { if (nm_device_is_real (dev_candidate)) { _LOG3D (LOGD_DEVICE, connection, "already created virtual interface name %s", iface); return NULL; } device = dev_candidate; break; } } if (!device) { /* No matching device found. Proceed creating a new one. */ factory = nm_device_factory_manager_find_factory_for_connection (connection); if (!factory) { _LOG3E (LOGD_DEVICE, connection, "(%s) NetworkManager plugin for '%s' unavailable", iface, nm_connection_get_connection_type (connection)); return NULL; } device = nm_device_factory_create_device (factory, iface, NULL, connection, NULL, &error); if (!device) { _LOG3W (LOGD_DEVICE, connection, "factory can't create the device: %s", error->message); g_error_free (error); return NULL; } _LOG3D (LOGD_DEVICE, connection, "create virtual device %s", nm_device_get_iface (device)); if (!add_device (self, device, &error)) { _LOG3W (LOGD_DEVICE, connection, "can't register the device with manager: %s", error->message); g_error_free (error); g_object_unref (device); return NULL; } /* Add device takes a reference that NMManager still owns, so it's * safe to unref here and still return @device. */ g_object_unref (device); } if (!nm_device_check_unrealized_device_managed (device)) { _LOG3D (LOGD_DEVICE, connection, "skip activation because virtual device '%s' is unmanaged", nm_device_get_iface (device)); return device; } if (!find_master (self, connection, device, NULL, NULL, NULL, &error)) { _LOG3D (LOGD_DEVICE, connection, "skip activation: %s", error->message); g_error_free (error); return device; } /* Create backing resources if the device has any autoconnect connections */ connections = nm_settings_get_connections_clone (priv->settings, NULL, NULL, NULL, nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL); for (i = 0; connections[i]; i++) { NMConnection *candidate = nm_settings_connection_get_connection (connections[i]); NMSettingConnection *s_con; if (!nm_device_check_connection_compatible (device, candidate, NULL)) continue; s_con = nm_connection_get_setting_connection (candidate); g_assert (s_con); if ( !nm_setting_connection_get_autoconnect (s_con) || nm_settings_connection_autoconnect_is_blocked (connections[i])) continue; /* Create any backing resources the device needs */ if (!nm_device_create_and_realize (device, connection, parent, &error)) { log_level = g_error_matches (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES) ? LOGL_DEBUG : LOGL_ERR; _NMLOG3 (log_level, LOGD_DEVICE, connection, "couldn't create the device: %s", error->message); g_error_free (error); return NULL; } retry_connections_for_parent_device (self, device); break; } return device; } static void retry_connections_for_parent_device (NMManager *self, NMDevice *device) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_free NMSettingsConnection **connections = NULL; guint i; g_return_if_fail (device); connections = nm_settings_get_connections_clone (priv->settings, NULL, NULL, NULL, nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL); for (i = 0; connections[i]; i++) { NMSettingsConnection *sett_conn = connections[i]; NMConnection *connection = nm_settings_connection_get_connection (sett_conn); gs_free_error GError *error = NULL; gs_free char *ifname = NULL; NMDevice *parent; parent = find_parent_device_for_connection (self, connection, NULL, NULL); if (parent == device) { /* Only try to activate devices that don't already exist */ ifname = nm_manager_get_connection_iface (self, connection, &parent, NULL, &error); if (ifname) { if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname)) connection_changed (self, sett_conn); } } } } static void connection_changed (NMManager *self, NMSettingsConnection *sett_conn) { NMConnection *connection; NMDevice *device; if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn), NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) return; connection = nm_settings_connection_get_connection (sett_conn); if (!nm_connection_is_virtual (connection)) return; device = system_create_virtual_device (self, connection); if (!device) return; /* Maybe the device that was created was needed by some other * connection's device (parent of a VLAN). Let the connections * can use the newly created device as a parent know. */ retry_connections_for_parent_device (self, device); } static void connection_added_cb (NMSettings *settings, NMSettingsConnection *sett_conn, NMManager *self) { connection_changed (self, sett_conn); } static void connection_updated_cb (NMSettings *settings, NMSettingsConnection *sett_conn, guint update_reason_u, NMManager *self) { connection_changed (self, sett_conn); } /*****************************************************************************/ static void _delete_volatile_connection_all (NMManager *self, gboolean do_delete) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMCListElem *elem; while ((elem = c_list_first_entry (&priv->delete_volatile_connection_lst_head, NMCListElem, lst))) { gs_unref_object NMSettingsConnection *connection = NULL; connection = nm_c_list_elem_free_steal (elem); if (do_delete) _delete_volatile_connection_do (self, connection); } } static gboolean _delete_volatile_connection_cb (gpointer user_data) { NMManager *self = user_data; NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); priv->delete_volatile_connection_idle_id = 0; _delete_volatile_connection_all (self, TRUE); return G_SOURCE_REMOVE; } static void connection_flags_changed (NMSettings *settings, NMSettingsConnection *connection, gpointer user_data) { NMManager *self = user_data; NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection), NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) return; if (active_connection_find (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, NULL)) { /* the connection still has an active-connection. It will be purged * when the active connection(s) get(s) removed. */ return; } c_list_link_tail (&priv->delete_volatile_connection_lst_head, &nm_c_list_elem_new_stale (g_object_ref (connection))->lst); if (!priv->delete_volatile_connection_idle_id) priv->delete_volatile_connection_idle_id = g_idle_add (_delete_volatile_connection_cb, self); } /*****************************************************************************/ static void system_unmanaged_devices_changed_cb (NMSettings *settings, GParamSpec *pspec, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) nm_device_set_unmanaged_by_user_settings (device); } static void hostname_changed_cb (NMHostnameManager *hostname_manager, GParamSpec *pspec, NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); const char *hostname; hostname = nm_hostname_manager_get_hostname (priv->hostname_manager); nm_dispatcher_call_hostname (NULL, NULL, NULL); nm_dhcp_manager_set_default_hostname (nm_dhcp_manager_get (), hostname); } /*****************************************************************************/ /* General NMManager stuff */ /*****************************************************************************/ static gboolean radio_enabled_for_rstate (RadioState *rstate, gboolean check_changeable) { gboolean enabled; enabled = rstate->user_enabled && rstate->hw_enabled; if (check_changeable) enabled &= rstate->sw_enabled; return enabled; } static gboolean radio_enabled_for_type (NMManager *self, RfKillType rtype, gboolean check_changeable) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); return radio_enabled_for_rstate (&priv->radio_states[rtype], check_changeable); } static void manager_update_radio_enabled (NMManager *self, RadioState *rstate, gboolean enabled) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; /* Do nothing for radio types not yet implemented */ if (!rstate->prop) return; g_object_notify (G_OBJECT (self), rstate->prop); /* Don't touch devices if asleep/networking disabled */ if (manager_sleeping (self)) return; /* enable/disable wireless devices as required */ c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { if (nm_device_get_rfkill_type (device) == rstate->rtype) { _LOG2D (LOGD_RFKILL, device, "rfkill: setting radio %s", enabled ? "enabled" : "disabled"); nm_device_set_enabled (device, enabled); } } } static void update_rstate_from_rfkill (NMRfkillManager *rfkill_mgr, RadioState *rstate) { switch (nm_rfkill_manager_get_rfkill_state (rfkill_mgr, rstate->rtype)) { case RFKILL_UNBLOCKED: rstate->sw_enabled = TRUE; rstate->hw_enabled = TRUE; break; case RFKILL_SOFT_BLOCKED: rstate->sw_enabled = FALSE; rstate->hw_enabled = TRUE; break; case RFKILL_HARD_BLOCKED: rstate->sw_enabled = FALSE; rstate->hw_enabled = FALSE; break; default: g_warn_if_reached (); break; } } static void manager_rfkill_update_one_type (NMManager *self, RadioState *rstate, RfKillType rtype) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gboolean old_enabled, new_enabled, old_rfkilled, new_rfkilled, old_hwe; old_enabled = radio_enabled_for_rstate (rstate, TRUE); old_rfkilled = rstate->hw_enabled && rstate->sw_enabled; old_hwe = rstate->hw_enabled; /* recheck kernel rfkill state */ update_rstate_from_rfkill (priv->rfkill_mgr, rstate); /* Print out all states affecting device enablement */ if (rstate->desc) { _LOGD (LOGD_RFKILL, "rfkill: %s hw-enabled %d sw-enabled %d", rstate->desc, rstate->hw_enabled, rstate->sw_enabled); } /* Log new killswitch state */ new_rfkilled = rstate->hw_enabled && rstate->sw_enabled; if (old_rfkilled != new_rfkilled) { _LOGI (LOGD_RFKILL, "rfkill: %s now %s by radio killswitch", rstate->desc, new_rfkilled ? "enabled" : "disabled"); } /* Send out property changed signal for HW enabled */ if (rstate->hw_enabled != old_hwe) { if (rstate->hw_prop) g_object_notify (G_OBJECT (self), rstate->hw_prop); } /* And finally update the actual device radio state itself; respect the * daemon state here because this is never called from user-triggered * radio changes and we only want to ignore the daemon enabled state when * handling user radio change requests. */ new_enabled = radio_enabled_for_rstate (rstate, TRUE); if (new_enabled != old_enabled) manager_update_radio_enabled (self, rstate, new_enabled); } static void nm_manager_rfkill_update (NMManager *self, RfKillType rtype) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); guint i; if (rtype != RFKILL_TYPE_UNKNOWN) manager_rfkill_update_one_type (self, &priv->radio_states[rtype], rtype); else { /* Otherwise sync all radio types */ for (i = 0; i < RFKILL_TYPE_MAX; i++) manager_rfkill_update_one_type (self, &priv->radio_states[i], i); } } static void device_auth_done_cb (NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); gs_free_error GError *error = NULL; NMAuthCallResult result; NMDevice *device; GCancellable *cancellable; const char *permission; NMManagerDeviceAuthRequestFunc callback; NMAuthSubject *subject; nm_assert (G_IS_DBUS_METHOD_INVOCATION (context)); c_list_unlink (nm_auth_chain_parent_lst_list (chain)); permission = nm_auth_chain_get_data (chain, "perm"); nm_assert (permission); callback = nm_auth_chain_get_data (chain, "callback"); nm_assert (callback); device = nm_auth_chain_get_data (chain, "device"); nm_assert (NM_IS_DEVICE (device)); cancellable = nm_auth_chain_get_cancellable (chain); nm_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); result = nm_auth_chain_get_result (chain, permission); subject = nm_auth_chain_get_subject (chain); if ( cancellable && g_cancellable_set_error_if_cancelled (cancellable, &error)) { /* pass. */ } else { if (result != NM_AUTH_CALL_RESULT_YES) { _LOGD (LOGD_CORE, "%s request failed: not authorized", permission); error = g_error_new (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, "%s request failed: not authorized", permission); } nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES)); } callback (device, context, subject, error, nm_auth_chain_get_data (chain, "user-data")); } static void _device_auth_done_fail_on_idle (gpointer user_data, GCancellable *cancellable) { gs_unref_object NMManager *self = NULL; gs_unref_object NMDevice *device = NULL; gs_unref_object GDBusMethodInvocation *context = NULL; gs_unref_object NMAuthSubject *subject = NULL; gs_free_error GError *error_original = NULL; gs_free_error GError *error_cancelled = NULL; NMManagerDeviceAuthRequestFunc callback; gpointer callback_user_data; nm_utils_user_data_unpack (&self, &device, &context, &subject, &error_original, &callback, &callback_user_data); g_cancellable_set_error_if_cancelled (cancellable, &error_cancelled); callback (device, context, subject, error_cancelled ?: error_original, callback_user_data); } void nm_manager_device_auth_request (NMManager *self, NMDevice *device, GDBusMethodInvocation *context, NMConnection *connection, const char *permission, gboolean allow_interaction, GCancellable *cancellable, NMManagerDeviceAuthRequestFunc callback, gpointer user_data) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_free_error GError *error = NULL; gs_unref_object NMAuthSubject *subject = NULL; NMAuthChain *chain; char *permission_dup; /* Validate the caller */ subject = nm_dbus_manager_new_auth_subject_from_context (context); if (!subject) { g_set_error_literal (&error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); goto fail_on_idle; } /* Ensure the subject has permissions for this connection */ if ( connection && !nm_auth_is_subject_in_acl_set_error (connection, subject, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, &error)) goto fail_on_idle; /* Validate the request */ chain = nm_auth_chain_new_subject (subject, context, device_auth_done_cb, self); if (!chain) { g_set_error (&error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); goto fail_on_idle; } if (cancellable) nm_auth_chain_set_cancellable (chain, cancellable); permission_dup = g_strdup (permission); c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "device", g_object_ref (device), g_object_unref); nm_auth_chain_set_data (chain, "callback", callback, NULL); nm_auth_chain_set_data (chain, "user-data", user_data, NULL); nm_auth_chain_set_data (chain, "perm", permission_dup /* transfer ownership */, g_free); nm_auth_chain_add_call_unsafe (chain, permission_dup, allow_interaction); return; fail_on_idle: nm_utils_invoke_on_idle (cancellable, _device_auth_done_fail_on_idle, nm_utils_user_data_pack (g_object_ref (self), g_object_ref (device), g_object_ref (context), g_steal_pointer (&subject), g_steal_pointer (&error), callback, user_data)); } static gboolean new_activation_allowed_for_connection (NMManager *self, NMSettingsConnection *connection) { if (NM_IN_SET (_nm_connection_get_multi_connect (nm_settings_connection_get_connection (connection)), NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE, NM_CONNECTION_MULTI_CONNECT_MULTIPLE)) return TRUE; return !active_connection_find (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NULL); } /** * get_existing_connection: * @manager: #NMManager instance * @device: #NMDevice instance * @out_generated: (allow-none): return TRUE, if the connection was generated. * * Returns: a #NMSettingsConnection to be assumed by the device, or %NULL if * the device does not support assuming existing connections. */ static NMSettingsConnection * get_existing_connection (NMManager *self, NMDevice *device, gboolean *out_generated) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_unref_object NMConnection *connection = NULL; NMSettingsConnection *added; GError *error = NULL; gs_free_error GError *gen_error = NULL; NMDevice *master = NULL; int ifindex = nm_device_get_ifindex (device); NMSettingsConnection *matched = NULL; NMSettingsConnection *connection_checked = NULL; gboolean assume_state_guess_assume = FALSE; const char *assume_state_connection_uuid = NULL; gboolean maybe_later, only_by_uuid = FALSE; if (out_generated) *out_generated = FALSE; nm_device_capture_initial_config (device); if (ifindex) { int master_ifindex = nm_platform_link_get_master (priv->platform, ifindex); /* Check that the master is activating before assuming a * slave connection. However, ignore ovs-system master as * we never manage it. */ if ( master_ifindex && nm_platform_link_get_type (priv->platform, master_ifindex) != NM_LINK_TYPE_OPENVSWITCH) { master = nm_manager_get_device_by_ifindex (self, master_ifindex); if (!master) { _LOG2D (LOGD_DEVICE, device, "assume: don't assume because " "cannot generate connection for slave before its master (%s/%d)", nm_platform_link_get_name (priv->platform, master_ifindex), master_ifindex); return NULL; } if (!nm_device_get_act_request (master)) { _LOG2D (LOGD_DEVICE, device, "assume: don't assume because " "cannot generate connection for slave before master %s activates", nm_device_get_iface (master)); return NULL; } } } /* The core of the API is nm_device_generate_connection() function and * update_connection() virtual method and the convenient connection_type * class attribute. Subclasses supporting the new API must have * update_connection() implemented, otherwise nm_device_generate_connection() * returns NULL. */ connection = nm_device_generate_connection (device, master, &maybe_later, &gen_error); if (!connection) { if (maybe_later) { /* The device can generate a connection, but it failed for now. * Give it a chance to match a connection from the state file. */ only_by_uuid = TRUE; } else { nm_device_assume_state_reset (device); _LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s", gen_error->message); return NULL; } } nm_device_assume_state_get (device, &assume_state_guess_assume, &assume_state_connection_uuid); /* Now we need to compare the generated connection to each configured * connection. The comparison function is the heart of the connection * assumption implementation and it must compare the connections very * carefully to sort out various corner cases. Also, the comparison is * not entirely symmetric. * * When no configured connection matches the generated connection, we keep * the generated connection instead. */ if ( assume_state_connection_uuid && (connection_checked = nm_settings_get_connection_by_uuid (priv->settings, assume_state_connection_uuid)) && new_activation_allowed_for_connection (self, connection_checked) && nm_device_check_connection_compatible (device, nm_settings_connection_get_connection (connection_checked), NULL)) { if (connection) { NMConnection *con = nm_settings_connection_get_connection (connection_checked); if (nm_utils_match_connection ((NMConnection *[]) { con, NULL }, connection, TRUE, nm_device_has_carrier (device), nm_device_get_route_metric (device, AF_INET), nm_device_get_route_metric (device, AF_INET6), NULL, NULL)) matched = connection_checked; } else matched = connection_checked; } if (!matched && only_by_uuid) { _LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s", gen_error->message); return NULL; } if (!matched && assume_state_guess_assume) { gs_free NMSettingsConnection **sett_conns = NULL; guint len, i, j; /* the state file doesn't indicate a connection UUID to assume. Search the * persistent connections for a matching candidate. */ sett_conns = nm_manager_get_activatable_connections (self, FALSE, FALSE, &len); if (len > 0) { for (i = 0, j = 0; i < len; i++) { NMSettingsConnection *sett_conn = sett_conns[i]; if ( sett_conn != connection_checked && nm_device_check_connection_compatible (device, nm_settings_connection_get_connection (sett_conn), NULL)) sett_conns[j++] = sett_conn; } sett_conns[j] = NULL; len = j; if (len > 0) { gs_free NMConnection **conns = NULL; NMConnection *con; g_qsort_with_data (sett_conns, len, sizeof (sett_conns[0]), nm_settings_connection_cmp_timestamp_p_with_data, NULL); conns = nm_settings_connections_array_to_connections (sett_conns, len); con = nm_utils_match_connection (conns, connection, FALSE, nm_device_has_carrier (device), nm_device_get_route_metric (device, AF_INET), nm_device_get_route_metric (device, AF_INET6), NULL, NULL); if (con) { for (i = 0; i < len; i++) { if (conns[i] == con) { matched = sett_conns[i]; break; } } nm_assert (matched); } } } } if (matched) { _LOG2I (LOGD_DEVICE, device, "assume: will attempt to assume matching connection '%s' (%s)%s", nm_settings_connection_get_id (matched), nm_settings_connection_get_uuid (matched), assume_state_connection_uuid && nm_streq (assume_state_connection_uuid, nm_settings_connection_get_uuid (matched)) ? " (indicated)" : " (guessed)"); nm_device_assume_state_reset (device); return matched; } _LOG2D (LOGD_DEVICE, device, "assume: generated connection '%s' (%s)", nm_connection_get_id (connection), nm_connection_get_uuid (connection)); nm_device_assume_state_reset (device); if (!nm_settings_add_connection (priv->settings, connection, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, NM_SETTINGS_CONNECTION_ADD_REASON_NONE, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, &added, &error)) { _LOG2W (LOGD_SETTINGS, device, "assume: failure to save generated connection '%s': %s", nm_connection_get_id (connection), error->message); g_error_free (error); return NULL; } NM_SET_OUT (out_generated, TRUE); return added; } static gboolean copy_lease (const char *src, const char *dst) { nm_auto_close int src_fd = -1; int dst_fd; ssize_t res, size = SSIZE_MAX; src_fd = open (src, O_RDONLY|O_CLOEXEC); if (src_fd < 0) return FALSE; dst_fd = open (dst, O_CREAT|O_EXCL|O_CLOEXEC|O_WRONLY, 0644); if (dst_fd < 0) return FALSE; while ((res = sendfile (dst_fd, src_fd, NULL, size)) > 0) size -= res; nm_close (dst_fd); if (res != 0) { unlink (dst); return FALSE; } return TRUE; } static gboolean recheck_assume_connection (NMManager *self, NMDevice *device) { NMSettingsConnection *sett_conn; gboolean was_unmanaged = FALSE; gboolean generated = FALSE; NMDeviceState state; gboolean activation_type_assume; g_return_val_if_fail (NM_IS_MANAGER (self), FALSE); g_return_val_if_fail (NM_IS_DEVICE (device), FALSE); if (!nm_device_get_managed (device, FALSE)) { nm_device_assume_state_reset (device); _LOG2D (LOGD_DEVICE, device, "assume: don't assume because %s", "not managed"); return FALSE; } state = nm_device_get_state (device); if (state > NM_DEVICE_STATE_DISCONNECTED) { nm_device_assume_state_reset (device); _LOG2D (LOGD_DEVICE, device, "assume: don't assume due to device state %s", nm_device_state_to_str (state)); return FALSE; } sett_conn = get_existing_connection (self, device, &generated); /* log no reason. get_existing_connection() already does it. */ if (!sett_conn) return FALSE; activation_type_assume = !generated; if (state == NM_DEVICE_STATE_UNMANAGED) { gs_free char *initramfs_lease = g_strdup_printf (RUNSTATEDIR "/initramfs/net.%s.lease", nm_device_get_iface (device)); gs_free char *connection_lease = g_strdup_printf (NMRUNDIR "/dhclient-%s-%s.lease", nm_settings_connection_get_uuid (sett_conn), nm_device_get_iface (device)); if (copy_lease (initramfs_lease, connection_lease)) { unlink (initramfs_lease); /* * We've managed to steal the lease used by initramfs before it * killed off the dhclient. We need to take ownership of the configured * connection and act like the device was configured by us. * Otherwise the address would just expire. */ _LOG2I (LOGD_DEVICE, device, "assume: taking over an initramfs-configured connection"); activation_type_assume = TRUE; if (generated) { /* Reset the IPv4 setting to empty method=auto, regardless of what assumption guessed. */ nm_connection_add_setting (nm_settings_connection_get_connection (sett_conn), g_object_new (NM_TYPE_SETTING_IP4_CONFIG, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL)); nm_settings_connection_update (sett_conn, NULL, NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP, 0, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE, NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE, "assume-initrd", NULL); } } } nm_device_sys_iface_state_set (device, activation_type_assume ? NM_DEVICE_SYS_IFACE_STATE_ASSUME : NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); /* Move device to DISCONNECTED to activate the connection */ if (state == NM_DEVICE_STATE_UNMANAGED) { was_unmanaged = TRUE; nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); } if (nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE) { nm_device_state_changed (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED); } g_return_val_if_fail (nm_device_get_state (device) >= NM_DEVICE_STATE_DISCONNECTED, FALSE); { gs_unref_object NMActiveConnection *active = NULL; gs_unref_object NMAuthSubject *subject = NULL; NMActiveConnection *master_ac; GError *error = NULL; subject = nm_auth_subject_new_internal (); /* Note: the lifetime of the activation connection is always bound to the profiles visibility * via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY. * * This only makes a difference, if the profile actually has "connection.permissions" * set to limit visibility (which is not the case for externally managed, generated profiles). * * If we assume a previously active connection whose lifetime was unbound, we now bind it * after restart. That is not correct, and can mean that the profile becomes subject to * deactivation after restart (if the user logs out). * * This should be improved, but it's unclear how. */ active = _new_active_connection (self, FALSE, sett_conn, NULL, NULL, NULL, device, subject, activation_type_assume ? NM_ACTIVATION_TYPE_ASSUME : NM_ACTIVATION_TYPE_EXTERNAL, activation_type_assume ? NM_ACTIVATION_REASON_ASSUME : NM_ACTIVATION_REASON_EXTERNAL, NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, &error); if (!active) { _LOGW (LOGD_DEVICE, "assume: assumed connection %s failed to activate: %s", nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)), error->message); g_error_free (error); if (was_unmanaged) { nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_CONFIG_FAILED); } if ( generated && !activation_type_assume) { _LOG2D (LOGD_DEVICE, device, "assume: deleting generated connection after assuming failed"); nm_settings_connection_delete (sett_conn, FALSE); } else { if (nm_device_sys_iface_state_get (device) == NM_DEVICE_SYS_IFACE_STATE_ASSUME) nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL); } return FALSE; } /* If the device is a slave or VLAN, find the master ActiveConnection */ master_ac = NULL; if ( find_master (self, nm_settings_connection_get_connection (sett_conn), device, NULL, NULL, &master_ac, NULL) && master_ac) nm_active_connection_set_master (active, master_ac); active_connection_add (self, active); nm_device_queue_activation (device, NM_ACT_REQUEST (active)); } return TRUE; } static void recheck_assume_connection_cb (NMManager *self, NMDevice *device) { recheck_assume_connection (self, device); } static void device_ifindex_changed (NMDevice *device, GParamSpec *pspec, NMManager *self) { _parent_notify_changed (self, device, FALSE); } static void device_ip_iface_changed (NMDevice *device, GParamSpec *pspec, NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); const char *ip_iface = nm_device_get_ip_iface (device); NMDeviceType device_type = nm_device_get_device_type (device); NMDevice *candidate; /* Remove NMDevice objects that are actually child devices of others, * when the other device finally knows its IP interface name. For example, * remove the PPP interface that's a child of a WWAN device, since it's * not really a standalone NMDevice. */ c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { if ( candidate != device && g_strcmp0 (nm_device_get_iface (candidate), ip_iface) == 0 && nm_device_get_device_type (candidate) == device_type && nm_device_is_real (candidate)) { remove_device (self, candidate, FALSE); break; } } } static void device_iface_changed (NMDevice *device, GParamSpec *pspec, NMManager *self) { /* Virtual connections may refer to the new device name as * parent device, retry to activate them. */ retry_connections_for_parent_device (self, device); } static void _emit_device_added_removed (NMManager *self, NMDevice *device, gboolean is_added) { nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self), &interface_info_manager, is_added ? &signal_info_device_added : &signal_info_device_removed, "(o)", nm_dbus_object_get_path (NM_DBUS_OBJECT (device))); g_signal_emit (self, signals[is_added ? DEVICE_ADDED : DEVICE_REMOVED], 0, device); _notify (self, PROP_DEVICES); } static void device_realized (NMDevice *device, GParamSpec *pspec, NMManager *self) { _emit_device_added_removed (self, device, nm_device_is_real (device)); } static NMConnectivityState _get_best_connectivity (NMManager *self, int addr_family) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMConnectivityState best_state; NMDevice *dev; gint64 best_metric; if (addr_family == AF_UNSPEC) { best_state = _get_best_connectivity (self, AF_INET); if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) { /* already FULL IPv4 connectivity. No need to check IPv6, it doesn't get * better. */ return best_state; } return NM_MAX_WITH_CMP (nm_connectivity_state_cmp, best_state, _get_best_connectivity (self, AF_INET6)); } nm_assert_addr_family (addr_family); best_state = NM_CONNECTIVITY_UNKNOWN; best_metric = G_MAXINT64; c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) { const NMPObject *r; NMConnectivityState state; gint64 metric; r = nm_device_get_best_default_route (dev, addr_family); if (r) { metric = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (r)->metric); } else { /* if all devices have no default-route, we still include the best * of all connectivity state of all the devices. */ metric = G_MAXINT64; } if (metric > best_metric) { /* we already have a default route with better metric. The connectivity state * of this device is irreleavnt. */ continue; } state = nm_device_get_connectivity_state (dev, addr_family); if (metric < best_metric) { /* this device has a better default route. It wins. */ best_metric = metric; best_state = state; } else { best_state = NM_MAX_WITH_CMP (nm_connectivity_state_cmp, best_state, state); } if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) { /* it doesn't get better than FULL. We are done. */ break; } } return best_state; } static void device_connectivity_changed (NMDevice *device, GParamSpec *pspec, NMManager *self) { update_connectivity_value (self); } static void update_connectivity_value (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMConnectivityState best_state; best_state = _get_best_connectivity (self, AF_UNSPEC); if (best_state == priv->connectivity_state) return; priv->connectivity_state = best_state; _LOGD (LOGD_CORE, "connectivity checking indicates %s", nm_connectivity_state_to_string (priv->connectivity_state)); nm_manager_update_state (self); _notify (self, PROP_CONNECTIVITY); nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL); } static void _device_realize_finish (NMManager *self, NMDevice *device, const NMPlatformLink *plink) { g_return_if_fail (NM_IS_MANAGER (self)); g_return_if_fail (NM_IS_DEVICE (device)); nm_device_realize_finish (device, plink); if (!nm_device_get_managed (device, FALSE)) { nm_device_assume_state_reset (device); return; } if (recheck_assume_connection (self, device)) return; /* if we failed to assume a connection for the managed device, but the device * is still unavailable. Set UNAVAILABLE state again, this time with NOW_MANAGED. */ nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_NOW_MANAGED); nm_device_emit_recheck_auto_activate (device); } /** * add_device: * @self: the #NMManager * @device: the #NMDevice to add * @error: (out): the #GError * * If successful, this function will increase the references count of @device. * Callers should decrease the reference count. */ static gboolean add_device (NMManager *self, NMDevice *device, GError **error) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); const char *iface, *type_desc; RfKillType rtype; GSList *iter, *remove = NULL; int ifindex; const char *dbus_path; NMDevice *candidate; /* No duplicates */ ifindex = nm_device_get_ifindex (device); if (ifindex > 0 && nm_manager_get_device_by_ifindex (self, ifindex)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, "A device with ifindex %d already exists", ifindex); return FALSE; } /* Remove existing devices owned by the new device; eg remove ethernet * ports that are owned by a WWAN modem, since udev may announce them * before the modem is fully discovered. * * FIXME: use parent/child device relationships instead of removing * the child NMDevice entirely */ c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { if ( nm_device_is_real (candidate) && (iface = nm_device_get_ip_iface (candidate)) && nm_device_owns_iface (device, iface)) remove = g_slist_prepend (remove, candidate); } for (iter = remove; iter; iter = iter->next) remove_device (self, NM_DEVICE (iter->data), FALSE); g_slist_free (remove); g_object_ref (device); nm_assert (c_list_is_empty (&device->devices_lst)); c_list_link_tail (&priv->devices_lst_head, &device->devices_lst); g_signal_connect (device, NM_DEVICE_STATE_CHANGED, G_CALLBACK (manager_device_state_changed), self); g_signal_connect (device, NM_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), self); g_signal_connect_data (device, NM_DEVICE_RECHECK_ASSUME, G_CALLBACK (recheck_assume_connection_cb), self, NULL, G_CONNECT_SWAPPED); g_signal_connect (device, "notify::" NM_DEVICE_IP_IFACE, G_CALLBACK (device_ip_iface_changed), self); g_signal_connect (device, "notify::" NM_DEVICE_IFINDEX, G_CALLBACK (device_ifindex_changed), self); g_signal_connect (device, "notify::" NM_DEVICE_IFACE, G_CALLBACK (device_iface_changed), self); g_signal_connect (device, "notify::" NM_DEVICE_REAL, G_CALLBACK (device_realized), self); g_signal_connect (device, "notify::" NM_DEVICE_IP4_CONNECTIVITY, G_CALLBACK (device_connectivity_changed), self); g_signal_connect (device, "notify::" NM_DEVICE_IP6_CONNECTIVITY, G_CALLBACK (device_connectivity_changed), self); if (priv->startup) { g_signal_connect (device, "notify::" NM_DEVICE_HAS_PENDING_ACTION, G_CALLBACK (device_has_pending_action_changed), self); } /* Update global rfkill state for this device type with the device's * rfkill state, and then set this device's rfkill state based on the * global state. */ rtype = nm_device_get_rfkill_type (device); if (rtype != RFKILL_TYPE_UNKNOWN) { nm_manager_rfkill_update (self, rtype); nm_device_set_enabled (device, radio_enabled_for_type (self, rtype, TRUE)); } iface = nm_device_get_iface (device); g_assert (iface); type_desc = nm_device_get_type_desc (device); g_assert (type_desc); nm_device_set_unmanaged_by_user_settings (device); nm_device_set_unmanaged_flags (device, NM_UNMANAGED_SLEEPING, manager_sleeping (self)); dbus_path = nm_dbus_object_export (NM_DBUS_OBJECT (device)); _LOG2I (LOGD_DEVICE, device, "new %s device (%s)", type_desc, dbus_path); nm_settings_device_added (priv->settings, device); g_signal_emit (self, signals[INTERNAL_DEVICE_ADDED], 0, device); _notify (self, PROP_ALL_DEVICES); _parent_notify_changed (self, device, FALSE); return TRUE; } /*****************************************************************************/ static void factory_device_added_cb (NMDeviceFactory *factory, NMDevice *device, gpointer user_data) { NMManager *self = user_data; GError *error = NULL; g_return_if_fail (NM_IS_MANAGER (self)); if (nm_device_realize_start (device, NULL, FALSE, /* assume_state_guess_assume */ NULL, /* assume_state_connection_uuid */ FALSE, /* set_nm_owned */ NM_UNMAN_FLAG_OP_FORGET, NULL, &error)) { add_device (self, device, NULL); _device_realize_finish (self, device, NULL); retry_connections_for_parent_device (self, device); } else { _LOG2W (LOGD_DEVICE, device, "failed to realize device: %s", error->message); g_error_free (error); } } static void _register_device_factory (NMDeviceFactory *factory, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); g_signal_connect (factory, NM_DEVICE_FACTORY_DEVICE_ADDED, G_CALLBACK (factory_device_added_cb), self); } /*****************************************************************************/ void nm_manager_notify_device_availibility_maybe_changed (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) nm_device_notify_availability_maybe_changed (device); } /*****************************************************************************/ static void platform_link_added (NMManager *self, int ifindex, const NMPlatformLink *plink, gboolean guess_assume, const NMConfigDeviceStateData *dev_state) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDeviceFactory *factory; NMDevice *device = NULL; NMDevice *candidate; g_return_if_fail (ifindex > 0); if (nm_manager_get_device_by_ifindex (self, ifindex)) return; /* Let unrealized devices try to realize themselves with the link */ c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { gboolean compatible = TRUE; gs_free_error GError *error = NULL; if (nm_device_get_link_type (candidate) != plink->type) continue; if (strcmp (nm_device_get_iface (candidate), plink->name)) continue; if (nm_device_is_real (candidate)) { /* There's already a realized device with the link's name * and a different ifindex. */ if (nm_device_get_ifindex (candidate) <= 0) nm_device_update_from_platform_link (candidate, plink); else { /* The ifindex of a device can't be changed after * initialization because it is used as a key by * the dns-manager. */ _LOGD (LOGD_DEVICE, "(%s): removing old device %p after ifindex change from %d to %d", plink->name, candidate, nm_device_get_ifindex (candidate), ifindex); remove_device (self, candidate, FALSE); goto add; } return; } else if (nm_device_realize_start (candidate, plink, FALSE, /* assume_state_guess_assume */ NULL, /* assume_state_connection_uuid */ FALSE, /* set_nm_owned */ NM_UNMAN_FLAG_OP_FORGET, &compatible, &error)) { _device_realize_finish (self, candidate, plink); return; } _LOGD (LOGD_DEVICE, "(%s): failed to realize from plink: '%s'", plink->name, error->message); /* Try next unrealized device */ } add: /* Try registered device factories */ factory = nm_device_factory_manager_find_factory_for_link_type (plink->type); if (factory) { gboolean ignore = FALSE; gs_free_error GError *error = NULL; device = nm_device_factory_create_device (factory, plink->name, plink, NULL, &ignore, &error); if (!device) { if (!ignore) { _LOGW (LOGD_PLATFORM, "%s: factory failed to create device: %s", plink->name, error->message); } else { _LOGD (LOGD_PLATFORM, "%s: factory failed to create device: %s", plink->name, error->message); } return; } } if (device == NULL) { gboolean nm_plugin_missing = FALSE; switch (plink->type) { case NM_LINK_TYPE_WWAN_NET: case NM_LINK_TYPE_BNEP: case NM_LINK_TYPE_OLPC_MESH: case NM_LINK_TYPE_TEAM: case NM_LINK_TYPE_WIFI: _LOGI (LOGD_PLATFORM, "(%s): '%s' plugin not available; creating generic device", plink->name, nm_link_type_to_string (plink->type)); nm_plugin_missing = TRUE; /* fall-through */ default: device = nm_device_generic_new (plink, nm_plugin_missing); break; } } if (device) { gs_free_error GError *error = NULL; NMUnmanFlagOp unmanaged_user_explicit = NM_UNMAN_FLAG_OP_FORGET; if (dev_state) { switch (dev_state->managed) { case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED: unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_MANAGED; break; case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED: unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_UNMANAGED; break; case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN: break; } } if (nm_device_realize_start (device, plink, guess_assume, dev_state ? dev_state->connection_uuid : NULL, dev_state ? (dev_state->nm_owned == 1) : FALSE, unmanaged_user_explicit, NULL, &error)) { add_device (self, device, NULL); _device_realize_finish (self, device, plink); retry_connections_for_parent_device (self, device); } else { _LOGW (LOGD_DEVICE, "%s: failed to realize device: %s", plink->name, error->message); } g_object_unref (device); } } typedef struct { CList lst; NMManager *self; int ifindex; guint idle_id; } PlatformLinkCbData; static gboolean _platform_link_cb_idle (PlatformLinkCbData *data) { int ifindex = data->ifindex; NMManager *self = data->self; NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); const NMPlatformLink *plink; c_list_unlink_stale (&data->lst); g_slice_free (PlatformLinkCbData, data); plink = nm_platform_link_get (priv->platform, ifindex); if (plink) { const NMPObject *plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink)); platform_link_added (self, ifindex, plink, FALSE, NULL); nmp_object_unref (plink_keep_alive); } else { NMDevice *device; GError *error = NULL; device = nm_manager_get_device_by_ifindex (self, ifindex); if (device) { if (nm_device_is_software (device)) { nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_REMOVED); /* Our software devices stick around until their connection is removed */ if (!nm_device_unrealize (device, FALSE, &error)) { _LOG2W (LOGD_DEVICE, device, "failed to unrealize: %s", error->message); g_clear_error (&error); remove_device (self, device, FALSE); } else { nm_device_update_from_platform_link (device, NULL); } } else { /* Hardware and external devices always get removed when their kernel link is gone */ remove_device (self, device, FALSE); } } } return G_SOURCE_REMOVE; } static void platform_link_cb (NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink *plink, int change_type_i, gpointer user_data) { NMManager *self; NMManagerPrivate *priv; const NMPlatformSignalChangeType change_type = change_type_i; PlatformLinkCbData *data; switch (change_type) { case NM_PLATFORM_SIGNAL_ADDED: case NM_PLATFORM_SIGNAL_REMOVED: self = NM_MANAGER (user_data); priv = NM_MANAGER_GET_PRIVATE (self); data = g_slice_new (PlatformLinkCbData); data->self = self; data->ifindex = ifindex; c_list_link_tail (&priv->link_cb_lst, &data->lst); data->idle_id = g_idle_add ((GSourceFunc) _platform_link_cb_idle, data); break; default: break; } } static void platform_query_devices (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_unref_ptrarray GPtrArray *links = NULL; int i; gboolean guess_assume; gs_free char *order = NULL; guess_assume = nm_config_get_first_start (nm_config_get ()); order = nm_config_data_get_value (NM_CONFIG_GET_DATA, NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER, NM_CONFIG_GET_VALUE_STRIP); links = nm_platform_link_get_all (priv->platform, !nm_streq0 (order, "index")); if (!links) return; for (i = 0; i < links->len; i++) { const NMPlatformLink *link = NMP_OBJECT_CAST_LINK (links->pdata[i]); const NMConfigDeviceStateData *dev_state; dev_state = nm_config_device_state_get (priv->config, link->ifindex); platform_link_added (self, link->ifindex, link, guess_assume && (!dev_state || !dev_state->connection_uuid), dev_state); } } static void rfkill_manager_rfkill_changed_cb (NMRfkillManager *rfkill_mgr, RfKillType rtype, RfKillState udev_state, gpointer user_data) { nm_manager_rfkill_update (NM_MANAGER (user_data), rtype); } const CList * nm_manager_get_devices (NMManager *manager) { g_return_val_if_fail (NM_IS_MANAGER (manager), NULL); return &NM_MANAGER_GET_PRIVATE (manager)->devices_lst_head; } typedef enum { DEVICE_ACTIVATION_PRIO_NONE, DEVICE_ACTIVATION_PRIO_UNMANAGED, DEVICE_ACTIVATION_PRIO_UNAVAILABLE, DEVICE_ACTIVATION_PRIO_DEACTIVATING, DEVICE_ACTIVATION_PRIO_ACTIVATING, DEVICE_ACTIVATION_PRIO_ACTIVATED, DEVICE_ACTIVATION_PRIO_DISCONNECTED, _DEVICE_ACTIVATION_PRIO_BEST = DEVICE_ACTIVATION_PRIO_DISCONNECTED, } DeviceActivationPrio; static DeviceActivationPrio _device_get_activation_prio (NMDevice *device) { if (!nm_device_get_managed (device, TRUE)) return DEVICE_ACTIVATION_PRIO_NONE; switch (nm_device_get_state (device)) { case NM_DEVICE_STATE_DISCONNECTED: return DEVICE_ACTIVATION_PRIO_DISCONNECTED; case NM_DEVICE_STATE_ACTIVATED: return DEVICE_ACTIVATION_PRIO_ACTIVATED; case NM_DEVICE_STATE_PREPARE: case NM_DEVICE_STATE_CONFIG: case NM_DEVICE_STATE_NEED_AUTH: case NM_DEVICE_STATE_IP_CONFIG: case NM_DEVICE_STATE_IP_CHECK: case NM_DEVICE_STATE_SECONDARIES: return DEVICE_ACTIVATION_PRIO_ACTIVATING; case NM_DEVICE_STATE_DEACTIVATING: case NM_DEVICE_STATE_FAILED: return DEVICE_ACTIVATION_PRIO_DEACTIVATING; case NM_DEVICE_STATE_UNAVAILABLE: return DEVICE_ACTIVATION_PRIO_UNAVAILABLE; case NM_DEVICE_STATE_UNKNOWN: case NM_DEVICE_STATE_UNMANAGED: return DEVICE_ACTIVATION_PRIO_UNMANAGED; } g_return_val_if_reached (DEVICE_ACTIVATION_PRIO_UNAVAILABLE); } static NMDevice * nm_manager_get_best_device_for_connection (NMManager *self, NMSettingsConnection *sett_conn, NMConnection *connection, gboolean for_user_request, GHashTable *unavailable_devices, GError **error) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnectionState ac_state; NMActiveConnection *ac; NMDevice *ac_device; NMDevice *device; struct { NMDevice *device; DeviceActivationPrio prio; } best = { .device = NULL, .prio = DEVICE_ACTIVATION_PRIO_NONE, }; NMDeviceCheckConAvailableFlags flags; gs_unref_ptrarray GPtrArray *all_ac_arr = NULL; gs_free_error GError *local_best = NULL; NMConnectionMultiConnect multi_connect; nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn)); nm_assert (!connection || NM_IS_CONNECTION (connection)); nm_assert (sett_conn || connection); nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn)); if (!connection) connection = nm_settings_connection_get_connection (sett_conn); multi_connect = _nm_connection_get_multi_connect (connection); if (!for_user_request) flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE; else { /* if the profile is multi-connect=single, we also consider devices which * are marked as unmanaged. And explicit user-request shows sufficient user * intent to make the device managed. * That is also, because we expect that such profile is suitably tied * to the intended device. So when an unmanaged device matches, the user's * intent is clear. * * For multi-connect != single devices that is different. The profile * is not restricted to a particular device. * For that reason, plain `nmcli connection up "$MULIT_PROFILE"` seems * less suitable for multi-connect profiles, because the target device is * left unspecified. Anyway, if a user issues * * $ nmcli device set "$DEVICE" managed no * $ nmcli connection up "$MULIT_PROFILE" * * then it is reasonable for multi-connect profiles to not consider * the device a suitable candidate. * * This may be seen inconsistent, but I think that it makes a lot of * sense. Also note that "connection.multi-connect" work quite differently * in aspects like activation. E.g. `nmcli connection up` of multi-connect * "single" profile, will deactivate the profile if it is active already. * That is different from multi-connect profiles, where it will aim to * activate the profile one more time on an hitherto disconnected device. */ if (multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE) flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST; else flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED; } if ( multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE && (ac = active_connection_find_by_connection (self, sett_conn, connection, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, &all_ac_arr))) { /* if we have a profile which may activate on only one device (multi-connect single), then * we prefer the device on which the profile is already active. It means to reactivate * the profile on the same device. * * If the profile can be activated on multiple devices, we don't do this. In fact, the * check below for the DeviceActivationPrio will prefer devices which are not already * activated (with this or another) profile. */ ac_device = nm_active_connection_get_device (ac); if ( ac_device && ( (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device)) || !nm_device_check_connection_available (ac_device, connection, flags, NULL, NULL))) ac_device = NULL; if (all_ac_arr) { guint i; ac_state = nm_active_connection_get_state (ac); /* we found several active connections. See which one is the most suitable... */ nm_assert (ac == all_ac_arr->pdata[0]); for (i = 1; i < all_ac_arr->len; i++) { NMActiveConnection *ac2 = all_ac_arr->pdata[i]; NMDevice *ac_device2 = nm_active_connection_get_device (ac2); NMActiveConnectionState ac_state2; if ( !ac_device2 || (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device2)) || !nm_device_check_connection_available (ac_device2, connection, flags, NULL, NULL)) continue; ac_state2 = nm_active_connection_get_state (ac2); if (!ac_device) goto found_better; if (ac_state == ac_state2) { /* active-connections are in their list in the order in which they are connected. * If we have two with same state, the later (newer) one is preferred. */ goto found_better; } switch (ac_state) { case NM_ACTIVE_CONNECTION_STATE_UNKNOWN: if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATING, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)) goto found_better; break; case NM_ACTIVE_CONNECTION_STATE_ACTIVATING: if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) goto found_better; break; case NM_ACTIVE_CONNECTION_STATE_ACTIVATED: break; case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING: if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATING, NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) goto found_better; break; default: nm_assert_not_reached (); goto found_better; } continue; found_better: ac = ac2; ac_state = ac_state2; ac_device = ac_device2; } } if (ac_device) return ac_device; } /* Pick the first device that's compatible with the connection. */ c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { GError *local = NULL; DeviceActivationPrio prio; if ( unavailable_devices && g_hash_table_contains (unavailable_devices, device)) continue; /* determine the priority of this device. Currently this priority is independent * of the profile (connection) and the device's details (aside the state). * * Maybe nm_device_check_connection_available() should instead return a priority, * as it has more information available. * * For example, if you have multiple Wi-Fi devices, currently a user-request would * also select the device if the AP is not visible. Optimally, if one of the two * devices sees the AP and the other one doesn't, the former would be preferred. * For that, the priority would need to be determined by nm_device_check_connection_available(). */ prio = _device_get_activation_prio (device); if ( prio <= best.prio && best.device) { /* we already have a matching device with a better priority. This candidate * cannot be better. Skip the check. * * Also note, that below we collect the best error message @local_best. * Since we already have best.device, the error message does not matter * either, and we can skip nm_device_check_connection_available() altogether. */ continue; } if (nm_device_check_connection_available (device, connection, flags, NULL, error ? &local : NULL)) { if (prio == _DEVICE_ACTIVATION_PRIO_BEST) { /* this device already has the best priority. It cannot get better * and finish the search. */ return device; } best.prio = prio; best.device = device; continue; } if (error) { gboolean reset_error; if (!local_best) reset_error = TRUE; else if (local_best->domain != NM_UTILS_ERROR) reset_error = (local->domain == NM_UTILS_ERROR); else { reset_error = ( local->domain == NM_UTILS_ERROR && local_best->code < local->code); } if (reset_error) { g_clear_error (&local_best); g_set_error (&local_best, local->domain, local->code, "device %s not available because %s", nm_device_get_iface (device), local->message); } g_error_free (local); } } if (best.device) return best.device; if (error) { if (local_best) g_propagate_error (error, g_steal_pointer (&local_best)); else { nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "no suitable device found"); } } return NULL; } static const char ** _get_devices_paths (NMManager *self, gboolean all_devices) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); const char **paths = NULL; guint i; NMDevice *device; paths = g_new (const char *, c_list_length (&priv->devices_lst_head) + 1); i = 0; c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { const char *path; path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device)); if (!path) continue; if ( !all_devices && !nm_device_is_real (device)) continue; paths[i++] = path; } paths[i++] = NULL; return paths; } static void impl_manager_get_devices (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); gs_free const char **paths = NULL; paths = _get_devices_paths (self, FALSE); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(^ao)", (char **) paths)); } static void impl_manager_get_all_devices (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); gs_free const char **paths = NULL; paths = _get_devices_paths (self, TRUE); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(^ao)", (char **) paths)); } static void impl_manager_get_device_by_ip_iface (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMDevice *device; const char *path = NULL; const char *iface; g_variant_get (parameters, "(&s)", &iface); device = find_device_by_ip_iface (self, iface); if (device) path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device)); if (!path) { g_dbus_method_invocation_return_error (invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "No device found for the requested iface."); return; } g_dbus_method_invocation_return_value (invocation, g_variant_new ("(o)", path)); } static gboolean is_compatible_with_slave (NMConnection *master, NMConnection *slave) { NMSettingConnection *s_con; g_return_val_if_fail (master, FALSE); g_return_val_if_fail (slave, FALSE); s_con = nm_connection_get_setting_connection (slave); g_assert (s_con); return nm_connection_is_type (master, nm_setting_connection_get_slave_type (s_con)); } /** * find_master: * @self: #NMManager object * @connection: the #NMConnection to find the master connection and device for * @device: the #NMDevice, if any, which will activate @connection * @out_master_connection: on success, the master connection of @connection if * that master connection was found * @out_master_device: on success, the master device of @connection if that * master device was found * @out_master_ac: on success, the master ActiveConnection of @connection if * there already is one * @error: the error, if an error occurred * * Given an #NMConnection, attempts to find its master. If @connection has * no master, this will return %TRUE and @out_master_connection and * @out_master_device will be untouched. * * If @connection does have a master, then the outputs depend on what is in its * #NMSettingConnection:master property: * * If "master" is the ifname of an existing #NMDevice, and that device has a * compatible master connection activated or activating on it, then * @out_master_device, @out_master_connection, and @out_master_ac will all be * set. If the device exists and is idle, only @out_master_device will be set. * If the device exists and has an incompatible connection on it, an error * will be returned. * * If "master" is the ifname of a non-existent device, then @out_master_device * will be %NULL, and @out_master_connection will be a connection whose * activation would cause the creation of that device. @out_master_ac MAY be * set in this case as well (if the connection has started activating, but has * not yet created its device). * * If "master" is the UUID of a compatible master connection, then * @out_master_connection will be the identified connection, and @out_master_device * and/or @out_master_ac will be set if the connection is currently activating. * (@out_master_device will not be set if the device exists but does not have * @out_master_connection active/activating on it.) * * Returns: %TRUE if the master device and/or connection could be found or if * the connection did not require a master, %FALSE otherwise **/ static gboolean find_master (NMManager *self, NMConnection *connection, NMDevice *device, NMSettingsConnection **out_master_connection, NMDevice **out_master_device, NMActiveConnection **out_master_ac, GError **error) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMSettingConnection *s_con; const char *master; NMDevice *master_device = NULL; NMSettingsConnection *master_connection; s_con = nm_connection_get_setting_connection (connection); g_assert (s_con); master = nm_setting_connection_get_master (s_con); if (master == NULL) return TRUE; /* success, but no master */ /* Try as an interface name first */ master_device = find_device_by_iface (self, master, NULL, connection); if (master_device) { if (master_device == device) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "Device cannot be its own master"); return FALSE; } master_connection = nm_device_get_settings_connection (master_device); if ( master_connection && !is_compatible_with_slave (nm_settings_connection_get_connection (master_connection), connection)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "The active connection on %s is not compatible", nm_device_get_iface (master_device)); return FALSE; } } else { /* Try master as a connection UUID */ master_connection = nm_settings_get_connection_by_uuid (priv->settings, master); if (master_connection) { NMDevice *candidate; /* Check if the master connection is activated on some device already */ c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { if (candidate == device) continue; if (nm_device_get_settings_connection (candidate) == master_connection) { master_device = candidate; break; } } } } if (out_master_connection) *out_master_connection = master_connection; if (out_master_device) *out_master_device = master_device; if (out_master_ac && master_connection) { *out_master_ac = active_connection_find (self, master_connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, NULL); } if (master_device || master_connection) return TRUE; else { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "Master connection not found or invalid"); return FALSE; } } /** * ensure_master_active_connection: * @self: the #NMManager * @subject: the #NMAuthSubject representing the requestor of this activation * @connection: the connection that should depend on @master_connection * @device: the #NMDevice, if any, which will activate @connection * @master_connection: the master connection, or %NULL * @master_device: the master device, or %NULL * @activation_reason: the reason for activation * @error: the error, if an error occurred * * Determines whether a given #NMConnection depends on another connection to * be activated, and if so, finds that master connection or creates it. * * If @master_device and @master_connection are both set then @master_connection * MUST already be activated or activating on @master_device, and the function will * return the existing #NMActiveConnection. * * If only @master_device is set, and it has an #NMActiveConnection, then the * function will return it if it is a compatible master, or an error if not. If it * doesn't have an AC, then the function will create one if a compatible master * connection exists, or return an error if not. * * If only @master_connection is set, then this will try to find or create a compatible * #NMDevice, and either activate @master_connection on that device or return an error. * * Returns: the master #NMActiveConnection that the caller should depend on, or * %NULL if an error occurred */ static NMActiveConnection * ensure_master_active_connection (NMManager *self, NMAuthSubject *subject, NMConnection *connection, NMDevice *device, NMSettingsConnection *master_connection, NMDevice *master_device, NMActivationReason activation_reason, GError **error) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *ac; NMActiveConnection *master_ac = NULL; NMDeviceState master_state; gboolean bind_lifetime_to_profile_visibility; g_return_val_if_fail (connection, NULL); g_return_val_if_fail (master_connection || master_device, FALSE); bind_lifetime_to_profile_visibility = NM_FLAGS_HAS (nm_device_get_activation_state_flags (device), NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); /* If the master device isn't activated then we need to activate it using * compatible connection. If it's already activating we can just proceed. */ if (master_device) { NMSettingsConnection *device_connection = nm_device_get_settings_connection (master_device); /* If we're passed a connection and a device, we require that connection * be already activated on the device, eg returned from find_master(). */ g_assert (!master_connection || master_connection == device_connection); if ( device_connection && !is_compatible_with_slave (nm_settings_connection_get_connection (device_connection), connection)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "The active connection %s is not compatible", nm_connection_get_id (connection)); return NULL; } master_state = nm_device_get_state (master_device); if ( (master_state == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (master_device)) { /* Device already using master_connection */ ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device)); g_return_val_if_fail (device_connection, ac); if (!bind_lifetime_to_profile_visibility) { /* unbind the lifetime. */ nm_active_connection_set_state_flags_clear (ac, NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); } return ac; } /* If the device is disconnected, find a compatible connection and * activate it on the device. */ if (master_state == NM_DEVICE_STATE_DISCONNECTED || !nm_device_is_real (master_device)) { gs_free NMSettingsConnection **connections = NULL; guint i; g_assert (master_connection == NULL); /* Find a compatible connection and activate this device using it */ connections = nm_manager_get_activatable_connections (self, FALSE, TRUE, NULL); for (i = 0; connections[i]; i++) { NMSettingsConnection *candidate = connections[i]; NMConnection *cand_conn = nm_settings_connection_get_connection (candidate); /* Ensure eg bond/team slave and the candidate master is a * bond/team master */ if (!is_compatible_with_slave (cand_conn, connection)) continue; if (nm_device_check_connection_available (master_device, cand_conn, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL, NULL)) { master_ac = nm_manager_activate_connection (self, candidate, NULL, NULL, master_device, subject, NM_ACTIVATION_TYPE_MANAGED, activation_reason, bind_lifetime_to_profile_visibility ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY : NM_ACTIVATION_STATE_FLAG_NONE, error); return master_ac; } } g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION, "No compatible connection found."); return NULL; } /* Otherwise, the device is unmanaged, unavailable, or disconnecting */ g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "Device unmanaged or not available for activation"); } else if (master_connection) { NMDevice *candidate; /* Find a compatible device and activate it using this connection */ c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) { if (candidate == device) { /* A device obviously can't be its own master */ continue; } if (!nm_device_check_connection_available (candidate, nm_settings_connection_get_connection (master_connection), NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL, NULL)) continue; if (!nm_device_is_software (candidate)) { master_state = nm_device_get_state (candidate); if (nm_device_is_real (candidate) && master_state != NM_DEVICE_STATE_DISCONNECTED) continue; } master_ac = nm_manager_activate_connection (self, master_connection, NULL, NULL, candidate, subject, NM_ACTIVATION_TYPE_MANAGED, activation_reason, bind_lifetime_to_profile_visibility ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY : NM_ACTIVATION_STATE_FLAG_NONE, error); return master_ac; } g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "No device available"); } else g_assert_not_reached (); return NULL; } typedef struct { NMSettingsConnection *connection; NMDevice *device; } SlaveConnectionInfo; /** * find_slaves: * @manager: #NMManager object * @sett_conn: the master #NMSettingsConnection to find slave connections for * @device: the master #NMDevice for the @sett_conn * @out_n_slaves: on return, the number of slaves found * * Given an #NMSettingsConnection, attempts to find its slaves. If @sett_conn is not * master, or has not any slaves, this will return %NULL. * * Returns: an array of #SlaveConnectionInfo for given master @sett_conn, or %NULL **/ static SlaveConnectionInfo * find_slaves (NMManager *manager, NMSettingsConnection *sett_conn, NMDevice *device, guint *out_n_slaves, gboolean for_user_request) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); gs_free NMSettingsConnection **all_connections = NULL; guint n_all_connections; guint i; SlaveConnectionInfo *slaves = NULL; guint n_slaves = 0; NMSettingConnection *s_con; gs_unref_hashtable GHashTable *devices = NULL; nm_assert (out_n_slaves); s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn)); g_return_val_if_fail (s_con, NULL); devices = g_hash_table_new (nm_direct_hash, NULL); /* Search through all connections, not only inactive ones, because * even if a slave was already active, it might be deactivated during * master reactivation. */ all_connections = nm_settings_get_connections_clone (priv->settings, &n_all_connections, NULL, NULL, nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL); for (i = 0; i < n_all_connections; i++) { NMSettingsConnection *master_connection = NULL; NMDevice *master_device = NULL, *slave_device; NMSettingsConnection *candidate = all_connections[i]; find_master (manager, nm_settings_connection_get_connection (candidate), NULL, &master_connection, &master_device, NULL, NULL); if ( (master_connection && master_connection == sett_conn) || (master_device && master_device == device)) { slave_device = nm_manager_get_best_device_for_connection (manager, candidate, NULL, for_user_request, devices, NULL); if (!slaves) { /* what we allocate is quite likely much too large. Don't bother, it is only * a temporary buffer. */ slaves = g_new (SlaveConnectionInfo, n_all_connections); } nm_assert (n_slaves < n_all_connections); slaves[n_slaves].connection = candidate, slaves[n_slaves].device = slave_device, n_slaves++; if (slave_device) g_hash_table_add (devices, slave_device); } } *out_n_slaves = n_slaves; /* Warning: returns NULL if n_slaves is zero. */ return slaves; } static gboolean should_connect_slaves (NMConnection *connection, NMDevice *device) { NMSettingConnection *s_con; NMSettingConnectionAutoconnectSlaves val; s_con = nm_connection_get_setting_connection (connection); g_assert (s_con); val = nm_setting_connection_get_autoconnect_slaves (s_con); if (val != NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT) goto out; val = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA, NM_CON_DEFAULT ("connection.autoconnect-slaves"), device, 0, 1, -1); out: if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO) return FALSE; if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES) return TRUE; return FALSE; } static int compare_slaves (gconstpointer a, gconstpointer b, gpointer sort_by_name) { const SlaveConnectionInfo *a_info = a; const SlaveConnectionInfo *b_info = b; /* Slaves without a device at the end */ if (!a_info->device) return 1; if (!b_info->device) return -1; if (GPOINTER_TO_INT (sort_by_name)) { return g_strcmp0 (nm_device_get_iface (a_info->device), nm_device_get_iface (b_info->device)); } return nm_device_get_ifindex (a_info->device) - nm_device_get_ifindex (b_info->device); } static void autoconnect_slaves (NMManager *self, NMSettingsConnection *master_connection, NMDevice *master_device, NMAuthSubject *subject, gboolean for_user_request) { GError *local_err = NULL; if (should_connect_slaves (nm_settings_connection_get_connection (master_connection), master_device)) { gs_free SlaveConnectionInfo *slaves = NULL; guint i, n_slaves = 0; gboolean bind_lifetime_to_profile_visibility; slaves = find_slaves (self, master_connection, master_device, &n_slaves, for_user_request); if (n_slaves > 1) { gs_free char *value = NULL; value = nm_config_data_get_value (NM_CONFIG_GET_DATA, NM_CONFIG_KEYFILE_GROUP_MAIN, NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER, NM_CONFIG_GET_VALUE_STRIP); g_qsort_with_data (slaves, n_slaves, sizeof (slaves[0]), compare_slaves, GINT_TO_POINTER (!nm_streq0 (value, "index"))); } bind_lifetime_to_profile_visibility = n_slaves > 0 && NM_FLAGS_HAS (nm_device_get_activation_state_flags (master_device), NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY); for (i = 0; i < n_slaves; i++) { SlaveConnectionInfo *slave = &slaves[i]; const char *uuid; /* To avoid loops when autoconnecting slaves, we propagate * the UUID of the initial connection down to slaves until * the same connection is found. */ uuid = g_object_get_qdata (G_OBJECT (master_connection), autoconnect_root_quark ()); if (nm_streq0 (nm_settings_connection_get_uuid (slave->connection), uuid)) { _LOGI (LOGD_CORE, "will NOT activate slave connection '%s' (%s) as a dependency for master '%s' (%s): " "circular dependency detected", nm_settings_connection_get_id (slave->connection), nm_settings_connection_get_uuid (slave->connection), nm_settings_connection_get_id (master_connection), nm_settings_connection_get_uuid (master_connection)); continue; } if (!uuid) uuid = nm_settings_connection_get_uuid (master_connection); g_object_set_qdata_full (G_OBJECT (slave->connection), autoconnect_root_quark (), g_strdup (uuid), g_free); if (!slave->device) { _LOGD (LOGD_CORE, "will NOT activate slave connection '%s' (%s) as a dependency for master '%s' (%s): " "no compatible device found", nm_settings_connection_get_id (slave->connection), nm_settings_connection_get_uuid (slave->connection), nm_settings_connection_get_id (master_connection), nm_settings_connection_get_uuid (master_connection)); continue; } _LOGD (LOGD_CORE, "will activate slave connection '%s' (%s) as a dependency for master '%s' (%s)", nm_settings_connection_get_id (slave->connection), nm_settings_connection_get_uuid (slave->connection), nm_settings_connection_get_id (master_connection), nm_settings_connection_get_uuid (master_connection)); /* Schedule slave activation */ nm_manager_activate_connection (self, slave->connection, NULL, NULL, slave->device, subject, NM_ACTIVATION_TYPE_MANAGED, NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES, bind_lifetime_to_profile_visibility ? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY : NM_ACTIVATION_STATE_FLAG_NONE, &local_err); if (local_err) { _LOGW (LOGD_CORE, "Slave connection activation failed: %s", local_err->message); g_clear_error (&local_err); } } } } static gboolean _internal_activate_vpn (NMManager *self, NMActiveConnection *active, GError **error) { nm_assert (NM_IS_VPN_CONNECTION (active)); nm_dbus_object_export (NM_DBUS_OBJECT (active)); if (!nm_vpn_manager_activate_connection (NM_MANAGER_GET_PRIVATE (self)->vpn_manager, NM_VPN_CONNECTION (active), error)) { nm_dbus_object_unexport (NM_DBUS_OBJECT (active)); return FALSE; } active_connection_add (self, active); return TRUE; } /* Traverse the device to disconnected state. This means that the device is ready * for connection and will proceed activating if there's an activation request * enqueued. */ static void unmanaged_to_disconnected (NMDevice *device) { /* when creating the software device, it can happen that the device is * still unmanaged by NM_UNMANAGED_PLATFORM_INIT because we didn't yet * get the udev event. At this point, we can no longer delay the activation * and force the device to be managed. */ nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED); nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_USER_EXPLICIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED); if (!nm_device_get_managed (device, FALSE)) { /* the device is still marked as unmanaged. Nothing to do. */ return; } if (nm_device_get_state (device) == NM_DEVICE_STATE_UNMANAGED) { nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_USER_REQUESTED); } if ( nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE && nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)) { nm_device_state_changed (device, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_USER_REQUESTED); } } static NMActivationStateFlags _activation_bind_lifetime_to_profile_visibility (NMAuthSubject *subject) { if ( nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL || nm_auth_subject_get_unix_process_uid (subject) == 0) { /* internal requests and requests from root are always unbound. */ return NM_ACTIVATION_STATE_FLAG_NONE; } /* if the activation was not done by internal decision nor root, there * are the following cases: * * - the connection has "connection.permissions" unset and the profile * is not restricted to a user and commonly always visible. It does * not hurt to bind the lifetime, because we expect the profile to be * visible at the moment. If the profile changes (while still being active), * we want to pick-up changes to the visibility and possibly disconnect. * * - the connection has "connection.permissions" set, and the current user * is the owner: * * - Usually, we would expect that the profile is visible at the moment, * and of course we want to bind the lifetime. The moment the user * logs out, the connection becomes invisible and disconnects. * * - the profile at this time could already be invisible (e.g. if the * user didn't create a proper session (sudo) and manually activates * an invisible profile. In this case, we still want to bind the * lifetime, and it will disconnect after the user logs in and logs * out again. NMKeepAlive takes care of that. */ return NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY; } /* The parent connection is ready; we can proceed realizing the device and * progressing the device to disconencted state. */ static void active_connection_parent_active (NMActiveConnection *active, NMActiveConnection *parent_ac, NMManager *self) { NMDevice *device = nm_active_connection_get_device (active); GError *error = NULL; NMSettingsConnection *sett_conn; NMDevice *parent; g_signal_handlers_disconnect_by_func (active, (GCallback) active_connection_parent_active, self); if (!parent_ac) { _LOGW (LOGD_CORE, "The parent connection device '%s' depended on disappeared.", nm_device_get_iface (device)); nm_active_connection_set_state_fail (active, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REMOVED, "parent device disappeared"); return; } sett_conn = nm_active_connection_get_settings_connection (active); parent = nm_active_connection_get_device (parent_ac); if (!nm_device_create_and_realize (device, nm_settings_connection_get_connection (sett_conn), parent, &error)) { _LOGW (LOGD_CORE, "Could not realize device '%s': %s", nm_device_get_iface (device), error->message); nm_active_connection_set_state_fail (active, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_REALIZE_FAILED, "failure to realize device"); return; } /* We can now proceed to disconnected state so that activation proceeds. */ unmanaged_to_disconnected (device); } static gboolean _internal_activate_device (NMManager *self, NMActiveConnection *active, GError **error) { NMDevice *device, *master_device = NULL; NMConnection *applied; NMSettingsConnection *sett_conn; NMSettingsConnection *master_connection = NULL; NMConnection *existing_connection = NULL; NMActiveConnection *master_ac = NULL; NMAuthSubject *subject; GError *local = NULL; NMConnectionMultiConnect multi_connect; const char *parent_spec; g_return_val_if_fail (NM_IS_MANAGER (self), FALSE); g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (active), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_assert (NM_IS_VPN_CONNECTION (active) == FALSE); device = nm_active_connection_get_device (active); g_return_val_if_fail (device != NULL, FALSE); sett_conn = nm_active_connection_get_settings_connection (active); nm_assert (sett_conn); applied = nm_active_connection_get_applied_connection (active); /* If the device is active and its connection is not visible to the * user that's requesting this new activation, fail, since other users * should not be allowed to implicitly deactivate private connections * by activating a connection of their own. */ existing_connection = nm_device_get_applied_connection (device); subject = nm_active_connection_get_subject (active); if ( existing_connection && !nm_auth_is_subject_in_acl_set_error (existing_connection, subject, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error)) { g_prefix_error (error, "Private connection already active on the device: "); return FALSE; } /* Final connection must be available on device */ if (!nm_device_check_connection_available (device, applied, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL, &local)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION, "Connection '%s' is not available on device %s because %s", nm_settings_connection_get_id (sett_conn), nm_device_get_iface (device), local->message); g_error_free (local); return FALSE; } if (nm_active_connection_get_activation_type (active) == NM_ACTIVATION_TYPE_MANAGED) nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_MANAGED); /* Try to find the master connection/device if the connection has a dependency */ if (!find_master (self, applied, device, &master_connection, &master_device, &master_ac, error)) { g_prefix_error (error, "Can not find a master for %s: ", nm_settings_connection_get_id (sett_conn)); return FALSE; } /* Create any backing resources the device needs */ if (!nm_device_is_real (device)) { NMDevice *parent; parent = find_parent_device_for_connection (self, nm_settings_connection_get_connection (sett_conn), NULL, &parent_spec); if (parent_spec && !parent) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "parent device '%s' not found", parent_spec); return FALSE; } if (parent && !nm_device_is_real (parent)) { NMSettingsConnection *parent_con; NMActiveConnection *parent_ac; parent_con = nm_device_get_best_connection (parent, NULL, error); if (!parent_con) { g_prefix_error (error, "%s failed to create parent: ", nm_device_get_iface (device)); return FALSE; } if ( nm_active_connection_get_activation_reason (active) == NM_ACTIVATION_REASON_AUTOCONNECT && nm_settings_connection_autoconnect_blocked_reason_get (parent_con, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "the parent connection of %s cannot autoactivate because it is blocked due to user request", nm_device_get_iface (device)); return FALSE; } parent_ac = nm_manager_activate_connection (self, parent_con, NULL, NULL, parent, subject, NM_ACTIVATION_TYPE_MANAGED, nm_active_connection_get_activation_reason (active), nm_active_connection_get_state_flags (active) & NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, error); if (!parent_ac) { g_prefix_error (error, "%s failed to activate parent: ", nm_device_get_iface (device)); return FALSE; } /* We can't realize now; defer until the parent device is ready. */ g_signal_connect (active, NM_ACTIVE_CONNECTION_PARENT_ACTIVE, (GCallback) active_connection_parent_active, self); nm_active_connection_set_parent (active, parent_ac); } else { /* We can realize now; no need to wait for a parent device. */ if (!nm_device_create_and_realize (device, nm_settings_connection_get_connection (sett_conn), parent, error)) { g_prefix_error (error, "%s failed to create resources: ", nm_device_get_iface (device)); return FALSE; } } } /* Ensure there's a master active connection the new connection we're * activating can depend on. */ if (master_connection || master_device) { if (master_connection) { _LOGD (LOGD_CORE, "Activation of '%s' requires master connection '%s'", nm_settings_connection_get_id (sett_conn), nm_settings_connection_get_id (master_connection)); } if (master_device) { _LOGD (LOGD_CORE, "Activation of '%s' requires master device '%s'", nm_settings_connection_get_id (sett_conn), nm_device_get_ip_iface (master_device)); } /* Ensure eg bond slave and the candidate master is a bond master */ if ( master_connection && !is_compatible_with_slave (nm_settings_connection_get_connection (master_connection), applied)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "The master connection '%s' is not compatible with '%s'", nm_settings_connection_get_id (master_connection), nm_settings_connection_get_id (sett_conn)); return FALSE; } if (!master_ac) { master_ac = ensure_master_active_connection (self, nm_active_connection_get_subject (active), applied, device, master_connection, master_device, nm_active_connection_get_activation_reason (active), error); if (!master_ac) { if (master_device) { g_prefix_error (error, "Master device '%s' can't be activated: ", nm_device_get_ip_iface (device)); } else { g_prefix_error (error, "Master connection '%s' can't be activated: ", nm_settings_connection_get_id (sett_conn)); } return FALSE; } } /* Now that we're activating a slave for that master, make sure the master just * decides to go unmanaged while we're activating (perhaps because other slaves * go away leaving him with no kids). */ if (master_device) { nm_device_set_unmanaged_by_flags (master_device, NM_UNMANAGED_EXTERNAL_DOWN, NM_UNMAN_FLAG_OP_FORGET, NM_DEVICE_STATE_REASON_USER_REQUESTED); } nm_active_connection_set_master (active, master_ac); _LOGD (LOGD_CORE, "Activation of '%s' depends on active connection %p %s", nm_settings_connection_get_id (sett_conn), master_ac, nm_dbus_object_get_path (NM_DBUS_OBJECT (master_ac)) ?: ""); } /* Check slaves for master connection and possibly activate them */ autoconnect_slaves (self, sett_conn, device, nm_active_connection_get_subject (active), nm_active_connection_get_activation_reason (active) == NM_ACTIVATION_REASON_USER_REQUEST); multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn)); if ( multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE || ( multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE && NM_IN_SET (nm_active_connection_get_activation_reason (active), NM_ACTIVATION_REASON_ASSUME, NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES, NM_ACTIVATION_REASON_USER_REQUEST))) { /* the profile can be activated multiple times. Proceed. */ } else { gs_unref_ptrarray GPtrArray *all_ac_arr = NULL; NMActiveConnection *ac; guint i, n_all; /* Disconnect the connection if already connected or queued for activation. * The connection cannot be active multiple times (at the same time). */ ac = active_connection_find (self, sett_conn, NULL, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, &all_ac_arr); if (ac) { n_all = all_ac_arr ? all_ac_arr->len : ((guint) 1); for (i = 0; i < n_all; i++) { nm_device_disconnect_active_connection ( all_ac_arr ? all_ac_arr->pdata[i] : ac, NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); } } } /* If the device is there, we can ready it for the activation. */ if (nm_device_is_real (device)) { unmanaged_to_disconnected (device); if (!nm_device_get_managed (device, FALSE)) { /* Unexpectedly, the device is still unmanaged. That can happen for example, * if the device is forcibly unmanaged due to NM_UNMANAGED_USER_SETTINGS. */ g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "Activation failed because the device is unmanaged"); return FALSE; } } /* Export the new ActiveConnection to clients and start it on the device */ active_connection_add (self, active); nm_device_queue_activation (device, NM_ACT_REQUEST (active)); return TRUE; } static gboolean _internal_activate_generic (NMManager *self, NMActiveConnection *active, GError **error) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gboolean success = FALSE; /* Ensure activation request is still valid, eg that its device hasn't gone * away or that some other dependency has not failed. */ if (nm_active_connection_get_state (active) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED, "Activation failed because dependencies failed."); return FALSE; } if (NM_IS_VPN_CONNECTION (active)) success = _internal_activate_vpn (self, active, error); else success = _internal_activate_device (self, active, error); if (success) { /* Force an update of the Manager's activating-connection property. * The device changes state before the AC gets exported, which causes * the manager's 'activating-connection' property to be NULL since the * AC only gets a D-Bus path when it's exported. So now that the AC * is exported, make sure the manager's activating-connection property * is up-to-date. */ policy_activating_ac_changed (G_OBJECT (priv->policy), NULL, self); } return success; } static NMActiveConnection * _new_active_connection (NMManager *self, gboolean is_vpn, NMSettingsConnection *sett_conn, NMConnection *incompl_conn, NMConnection *applied, const char *specific_object, NMDevice *device, NMAuthSubject *subject, NMActivationType activation_type, NMActivationReason activation_reason, NMActivationStateFlags initial_state_flags, GError **error) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *parent_device; nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn)); nm_assert (!incompl_conn || NM_IS_CONNECTION (incompl_conn)); nm_assert ((!incompl_conn) ^ (!sett_conn)); nm_assert (NM_IS_AUTH_SUBJECT (subject)); nm_assert (is_vpn == _connection_is_vpn (sett_conn ? nm_settings_connection_get_connection (sett_conn) : incompl_conn)); nm_assert (is_vpn || NM_IS_DEVICE (device)); nm_assert (!nm_streq0 (specific_object, "/")); nm_assert (!applied || NM_IS_CONNECTION (applied)); nm_assert (!is_vpn || !applied); if (is_vpn) { NMActiveConnection *parent; /* FIXME: for VPN connections, we don't allow re-activating an * already active connection. It's a bug, and should be fixed together * when reworking VPN handling. */ if (active_connection_find_by_connection (self, sett_conn, incompl_conn, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NULL)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE, "Connection '%s' is already active", sett_conn ? nm_settings_connection_get_id (sett_conn) : nm_connection_get_id (incompl_conn)); return NULL; } if (activation_type != NM_ACTIVATION_TYPE_MANAGED) g_return_val_if_reached (NULL); if (specific_object) { /* Find the specific connection the client requested we use */ parent = active_connection_get_by_path (self, specific_object); if (!parent) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, "Base connection for VPN connection not active."); return NULL; } } else parent = priv->primary_connection; if (!parent) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION, "Could not find source connection."); return NULL; } parent_device = nm_active_connection_get_device (parent); if (!parent_device) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "Source connection had no active device"); return NULL; } if (device && device != parent_device) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "The device doesn't match the active connection."); return NULL; } return (NMActiveConnection *) nm_vpn_connection_new (sett_conn, parent_device, nm_dbus_object_get_path (NM_DBUS_OBJECT (parent)), activation_reason, initial_state_flags, subject); } return (NMActiveConnection *) nm_act_request_new (sett_conn, applied, specific_object, subject, activation_type, activation_reason, initial_state_flags, device); } static void _internal_activation_auth_done (NMManager *self, NMActiveConnection *active, gboolean success, const char *error_desc) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *ac; gs_free_error GError *error = NULL; nm_assert (NM_IS_ACTIVE_CONNECTION (active)); if (!success) goto fail; /* Don't continue with an autoconnect-activation if a more important activation * already exists. * We also check this earlier, but there we may fail to detect a duplicate * if the existing active connection was undergoing authorization. */ if (NM_IN_SET (nm_active_connection_get_activation_reason (active), NM_ACTIVATION_REASON_EXTERNAL, NM_ACTIVATION_REASON_ASSUME, NM_ACTIVATION_REASON_AUTOCONNECT)) { c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { if ( nm_active_connection_get_device (ac) == nm_active_connection_get_device (active) && nm_active_connection_get_settings_connection (ac) == nm_active_connection_get_settings_connection (active) && nm_active_connection_get_state (ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { g_set_error (&error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE, "Connection '%s' is already active", nm_active_connection_get_settings_connection_id (active)); goto fail; } } } if (_internal_activate_generic (self, active, &error)) return; fail: nm_assert (error_desc || error); nm_active_connection_set_state_fail (active, NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, error_desc ?: error->message); } /** * nm_manager_activate_connection(): * @self: the #NMManager * @sett_conn: the #NMSettingsConnection to activate on @device * @applied: (allow-none): the applied connection to activate on @device * @specific_object: the specific object path, if any, for the activation * @device: the #NMDevice to activate @sett_conn on. Can be %NULL for VPNs. * @subject: the subject which requested activation * @activation_type: whether to assume the connection. That is, take over gracefully, * non-destructible. * @activation_reason: the reason for activation * @initial_state_flags: the initial state flags for the activation. * @error: return location for an error * * Begins a new internally-initiated activation of @sett_conn on @device. * @subject should be the subject of the activation that triggered this * one, or if this is an autoconnect request, a new internal subject. * The returned #NMActiveConnection is owned by the Manager and should be * referenced by the caller if the caller continues to use it. If @applied * is supplied, it shall not be modified by the caller afterwards. * * Returns: (transfer none): the new #NMActiveConnection that tracks * activation of @sett_conn on @device */ NMActiveConnection * nm_manager_activate_connection (NMManager *self, NMSettingsConnection *sett_conn, NMConnection *applied, const char *specific_object, NMDevice *device, NMAuthSubject *subject, NMActivationType activation_type, NMActivationReason activation_reason, NMActivationStateFlags initial_state_flags, GError **error) { NMManagerPrivate *priv; NMActiveConnection *active; AsyncOpData *async_op_data; gboolean is_vpn; g_return_val_if_fail (NM_IS_MANAGER (self), NULL); g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), NULL); is_vpn = _connection_is_vpn (nm_settings_connection_get_connection (sett_conn)); g_return_val_if_fail (is_vpn || NM_IS_DEVICE (device), NULL); g_return_val_if_fail (!error || !*error, NULL); nm_assert (!nm_streq0 (specific_object, "/")); priv = NM_MANAGER_GET_PRIVATE (self); if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn), subject, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error)) return NULL; /* Look for a active connection that's equivalent and is already pending authorization * and eventual activation. This is used to de-duplicate concurrent activations which would * otherwise race and cause the device to disconnect and reconnect repeatedly. * In particular, this allows the master and multiple slaves to concurrently auto-activate * while all the slaves would use the same active-connection. */ c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) { if (async_op_data->async_op_type != ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL) continue; active = async_op_data->ac_auth.active; if ( sett_conn == nm_active_connection_get_settings_connection (active) && nm_streq0 (nm_active_connection_get_specific_object (active), specific_object) && (!device || nm_active_connection_get_device (active) == device) && nm_auth_subject_get_subject_type (nm_active_connection_get_subject (active)) == NM_AUTH_SUBJECT_TYPE_INTERNAL && nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL && nm_active_connection_get_activation_reason (active) == activation_reason) return active; } active = _new_active_connection (self, is_vpn, sett_conn, NULL, applied, specific_object, device, subject, activation_type, activation_reason, initial_state_flags, error); if (!active) return NULL; nm_active_connection_authorize (active, NULL, _async_op_complete_ac_auth_cb, _async_op_data_new_authorize_activate_internal (self, active)); return active; } /** * validate_activation_request: * @self: the #NMManager * @context: the D-Bus context of the requestor * @sett_conn: the #NMSettingsConnection to be activated, or %NULL if there * is only a partial activation. * @connection: the partial #NMConnection to be activated (if @sett_conn is unspecified) * @device_path: the object path of the device to be activated, or NULL * @out_device: on successful return, the #NMDevice to be activated with @connection * The caller may pass in a device which shortcuts the lookup by path. * In this case, the passed in device must have the matching @device_path * already. * @out_is_vpn: on successful return, %TRUE if @connection is a VPN connection * @error: location to store an error on failure * * Performs basic validation on an activation request, including ensuring that * the requestor is a valid Unix process, is not disallowed in @connection * permissions, and that a device exists that can activate @connection. * * Returns: on success, the #NMAuthSubject representing the requestor, or * %NULL on error */ static NMAuthSubject * validate_activation_request (NMManager *self, GDBusMethodInvocation *context, NMSettingsConnection *sett_conn, NMConnection *connection, const char *device_path, NMDevice **out_device, gboolean *out_is_vpn, GError **error) { NMDevice *device = NULL; gboolean is_vpn = FALSE; gs_unref_object NMAuthSubject *subject = NULL; nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn)); nm_assert (!connection || NM_IS_CONNECTION (connection)); nm_assert (sett_conn || connection); nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn)); nm_assert (out_device); nm_assert (out_is_vpn); if (!connection) connection = nm_settings_connection_get_connection (sett_conn); /* Validate the caller */ subject = nm_dbus_manager_new_auth_subject_from_context (context); if (!subject) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); return NULL; } if (!nm_auth_is_subject_in_acl_set_error (connection, subject, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error)) return NULL; is_vpn = _connection_is_vpn (connection); if (*out_device) { device = *out_device; nm_assert (NM_IS_DEVICE (device)); nm_assert (device_path); nm_assert (nm_streq0 (device_path, nm_dbus_object_get_path (NM_DBUS_OBJECT (device)))); nm_assert (device == nm_manager_get_device_by_path (self, device_path)); } else if (device_path) { device = nm_manager_get_device_by_path (self, device_path); if (!device) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "Device not found"); return NULL; } } else if (!is_vpn) { gs_free_error GError *local = NULL; device = nm_manager_get_best_device_for_connection (self, sett_conn, connection, TRUE, NULL, &local); if (!device) { gs_free char *iface = NULL; /* VPN and software-device connections don't need a device yet, * but non-virtual connections do ... */ if (!nm_connection_is_virtual (connection)) { g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "No suitable device found for this connection (%s).", local->message); return NULL; } /* Look for an existing device with the connection's interface name */ iface = nm_manager_get_connection_iface (self, connection, NULL, NULL, error); if (!iface) return NULL; device = find_device_by_iface (self, iface, connection, NULL); if (!device) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "Failed to find a compatible device for this connection"); return NULL; } } } nm_assert (is_vpn || NM_IS_DEVICE (device)); *out_device = device; *out_is_vpn = is_vpn; return g_steal_pointer (&subject); } /*****************************************************************************/ static void _activation_auth_done (NMManager *self, NMActiveConnection *active, GDBusMethodInvocation *invocation, gboolean success, const char *error_desc) { GError *error = NULL; NMAuthSubject *subject; NMSettingsConnection *connection; subject = nm_active_connection_get_subject (active); connection = nm_active_connection_get_settings_connection (active); if (!success) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error_desc); goto fail; } if (!_internal_activate_generic (self, active, &error)) goto fail; nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, FALSE); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(o)", nm_dbus_object_get_path (NM_DBUS_OBJECT (active)))); nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, TRUE, NULL, subject, NULL); return; fail: nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, FALSE, NULL, subject, error->message); nm_active_connection_set_state_fail (active, NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, error->message); g_dbus_method_invocation_take_error (invocation, error); } static void impl_manager_activate_connection (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *dbus_connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_unref_object NMActiveConnection *active = NULL; gs_unref_object NMAuthSubject *subject = NULL; NMSettingsConnection *sett_conn = NULL; NMDevice *device = NULL; gboolean is_vpn = FALSE; GError *error = NULL; const char *connection_path; const char *device_path; const char *specific_object_path; g_variant_get (parameters, "(&o&o&o)", &connection_path, &device_path, &specific_object_path); connection_path = nm_dbus_path_not_empty (connection_path); specific_object_path = nm_dbus_path_not_empty (specific_object_path); device_path = nm_dbus_path_not_empty (device_path); /* If the connection path is given and valid, that connection is activated. * Otherwise the "best" connection for the device is chosen and activated, * regardless of whether that connection is autoconnect-enabled or not * (since this is an explicit request, not an auto-activation request). */ if (connection_path) { sett_conn = nm_settings_get_connection_by_path (priv->settings, connection_path); if (!sett_conn) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION, "Connection could not be found."); goto error; } } else { /* If no connection is given, find a suitable connection for the given device path */ if (!device_path) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "Only devices may be activated without a specifying a connection"); goto error; } device = nm_manager_get_device_by_path (self, device_path); if (!device) { error = g_error_new (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, "Can not activate an unknown device '%s'", device_path); goto error; } sett_conn = nm_device_get_best_connection (device, specific_object_path, &error); if (!sett_conn) goto error; } subject = validate_activation_request (self, invocation, sett_conn, NULL, device_path, &device, &is_vpn, &error); if (!subject) goto error; active = _new_active_connection (self, is_vpn, sett_conn, NULL, NULL, specific_object_path, device, subject, NM_ACTIVATION_TYPE_MANAGED, NM_ACTIVATION_REASON_USER_REQUEST, _activation_bind_lifetime_to_profile_visibility (subject), &error); if (!active) goto error; nm_active_connection_authorize (active, NULL, _async_op_complete_ac_auth_cb, _async_op_data_new_ac_auth_activate_user (self, active, invocation)); /* we passed the pointer on to _async_op_data_new_ac_auth_activate_user() */ g_steal_pointer (&active); return; error: if (sett_conn) { nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, sett_conn, FALSE, NULL, subject, error->message); } g_dbus_method_invocation_take_error (invocation, error); } /*****************************************************************************/ static void activation_add_done (NMSettings *settings, NMSettingsConnection *new_connection, GError *error, GDBusMethodInvocation *context, NMAuthSubject *subject, gpointer user_data) { NMManager *self; gs_unref_object NMActiveConnection *active = NULL; gs_free_error GError *local = NULL; gpointer async_op_type_ptr; AsyncOpType async_op_type; GVariant *result_floating; nm_utils_user_data_unpack (user_data, &self, &active, &async_op_type_ptr); async_op_type = GPOINTER_TO_INT (async_op_type_ptr); if (error) goto fail; nm_active_connection_set_settings_connection (active, new_connection); if (!_internal_activate_generic (self, active, &local)) goto fail; if (async_op_type == ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE) { result_floating = g_variant_new ("(oo)", nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)), nm_dbus_object_get_path (NM_DBUS_OBJECT (active))); } else { result_floating = g_variant_new ("(oo@a{sv})", nm_dbus_object_get_path (NM_DBUS_OBJECT (new_connection)), nm_dbus_object_get_path (NM_DBUS_OBJECT (active)), g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0)); } g_dbus_method_invocation_return_value (context, result_floating); nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE, nm_active_connection_get_settings_connection (active), TRUE, NULL, nm_active_connection_get_subject (active), NULL); return; fail: if (local) { nm_assert (!error); error = local; } else nm_assert (error); nm_active_connection_set_state_fail (active, NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN, error->message); if (new_connection) nm_settings_connection_delete (new_connection, FALSE); g_dbus_method_invocation_return_gerror (context, error); nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE, NULL, FALSE, NULL, nm_active_connection_get_subject (active), error->message); } static void _add_and_activate_auth_done (NMManager *self, AsyncOpType async_op_type, NMActiveConnection *active, NMConnection *connection, GDBusMethodInvocation *invocation, NMSettingsConnectionPersistMode persist_mode, gboolean is_volatile, gboolean success, const char *error_desc) { NMManagerPrivate *priv; GError *error = NULL; if (!success) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, error_desc); nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE, NULL, FALSE, NULL, nm_active_connection_get_subject (active), error->message); g_dbus_method_invocation_take_error (invocation, error); return; } priv = NM_MANAGER_GET_PRIVATE (self); /* FIXME(shutdown): nm_settings_add_connection_dbus() cannot be cancelled. It should be made * cancellable and tracked via AsyncOpData to be able to do a clean * shutdown. */ nm_settings_add_connection_dbus (priv->settings, connection, persist_mode, NM_SETTINGS_CONNECTION_ADD_REASON_NONE, ( is_volatile ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE), nm_active_connection_get_subject (active), invocation, activation_add_done, nm_utils_user_data_pack (self, g_object_ref (active), GINT_TO_POINTER (async_op_type))); } static void impl_manager_add_and_activate_connection (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *dbus_connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_unref_object NMConnection *incompl_conn = NULL; gs_unref_object NMActiveConnection *active = NULL; gs_unref_object NMAuthSubject *subject = NULL; GError *error = NULL; NMDevice *device = NULL; gboolean is_vpn = FALSE; gs_unref_variant GVariant *settings = NULL; gs_unref_variant GVariant *options = NULL; const char *device_path; const char *specific_object_path; gs_free NMConnection **conns = NULL; NMSettingsConnectionPersistMode persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; gboolean is_volatile = FALSE; gboolean bind_dbus_client = FALSE; AsyncOpType async_op_type; if (nm_streq (method_info->parent.name, "AddAndActivateConnection2")) { async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2; g_variant_get (parameters, "(@a{sa{sv}}&o&o@a{sv})", &settings, &device_path, &specific_object_path, &options); } else { nm_assert (nm_streq (method_info->parent.name, "AddAndActivateConnection")); async_op_type = ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE; g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path); } if (options) { GVariantIter iter; const char *option_name; GVariant *option_value; g_variant_iter_init (&iter, options); while (g_variant_iter_next (&iter, "{&sv}", &option_name, &option_value)) { gs_unref_variant GVariant *option_value_free = NULL; const char *s; option_value_free = option_value; if ( nm_streq (option_name, "persist") && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) { s = g_variant_get_string (option_value, NULL); is_volatile = FALSE; persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK; if (nm_streq (s, "volatile")) { persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; is_volatile = TRUE; } else if (nm_streq (s, "memory")) persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY; else if (nm_streq (s, "disk")) { /* pass */ } else { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS, "Option \"persist\" must be one of \"volatile\", \"memory\" or \"disk\""); goto error; } } else if ( nm_streq (option_name, "bind-activation") && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) { s = g_variant_get_string (option_value, NULL); if (nm_streq (s, "dbus-client")) bind_dbus_client = TRUE; else if (nm_streq (s, "none")) bind_dbus_client = FALSE; else { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS, "Option \"bind-activation\" must be one of \"dbus-client\" or \"none\""); goto error; } } else { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS, "Unknown extra option passed"); goto error; } } } specific_object_path = nm_dbus_path_not_empty (specific_object_path); device_path = nm_dbus_path_not_empty (device_path); /* Try to create a new connection with the given settings. * We allow empty settings for AddAndActivateConnection(). In that case, * the connection will be completed in nm_utils_complete_generic() or * nm_device_complete_connection() below. Just make sure we don't expect * specific data being in the connection till then (especially in * validate_activation_request()). */ incompl_conn = nm_simple_connection_new (); if (settings && g_variant_n_children (settings)) _nm_connection_replace_settings (incompl_conn, settings, NM_SETTING_PARSE_FLAGS_STRICT, NULL); subject = validate_activation_request (self, invocation, NULL, incompl_conn, device_path, &device, &is_vpn, &error); if (!subject) goto error; if (is_vpn) { /* Try to fill the VPN's connection setting and name at least */ if (!nm_connection_get_setting_vpn (incompl_conn)) { error = g_error_new_literal (NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_SETTING, "VPN connections require a 'vpn' setting"); g_prefix_error (&error, "%s: ", NM_SETTING_VPN_SETTING_NAME); goto error; } conns = nm_settings_connections_array_to_connections (nm_settings_get_connections (priv->settings, NULL), -1); nm_utils_complete_generic (priv->platform, incompl_conn, NM_SETTING_VPN_SETTING_NAME, conns, NULL, _("VPN connection"), NULL, NULL, FALSE); /* No IPv6 by default for now */ } else { conns = nm_settings_connections_array_to_connections (nm_settings_get_connections (priv->settings, NULL), -1); /* Let each device subclass complete the connection */ if (!nm_device_complete_connection (device, incompl_conn, specific_object_path, conns, &error)) goto error; } nm_assert (_nm_connection_verify (incompl_conn, NULL) == NM_SETTING_VERIFY_SUCCESS); active = _new_active_connection (self, is_vpn, NULL, incompl_conn, NULL, specific_object_path, device, subject, NM_ACTIVATION_TYPE_MANAGED, NM_ACTIVATION_REASON_USER_REQUEST, _activation_bind_lifetime_to_profile_visibility (subject), &error); if (!active) goto error; if (bind_dbus_client) { NMKeepAlive *keep_alive; keep_alive = nm_active_connection_get_keep_alive (active); nm_keep_alive_set_dbus_client_watch (keep_alive, dbus_connection, sender); nm_keep_alive_arm (keep_alive); } nm_active_connection_authorize (active, incompl_conn, _async_op_complete_ac_auth_cb, _async_op_data_new_ac_auth_add_and_activate (self, async_op_type, active, invocation, incompl_conn, persist_mode, is_volatile)); /* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */ g_steal_pointer (&incompl_conn); g_steal_pointer (&active); return; error: nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE, NULL, FALSE, NULL, subject, error->message); g_dbus_method_invocation_take_error (invocation, error); } /*****************************************************************************/ gboolean nm_manager_deactivate_connection (NMManager *manager, NMActiveConnection *active, NMDeviceStateReason reason, GError **error) { if (NM_IS_VPN_CONNECTION (active)) { NMActiveConnectionStateReason vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED; if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_CONNECTION_REMOVED) vpn_reason = NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED; if (!nm_vpn_connection_deactivate (NM_VPN_CONNECTION (active), vpn_reason, FALSE)) { g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, "The VPN connection was not active."); return FALSE; } } else { nm_assert (NM_IS_ACT_REQUEST (active)); nm_device_disconnect_active_connection (active, reason, NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); } _notify (manager, PROP_ACTIVE_CONNECTIONS); return TRUE; } static void deactivate_net_auth_done_cb (NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); GError *error = NULL; NMAuthCallResult result; NMActiveConnection *active; char *path; nm_assert (G_IS_DBUS_METHOD_INVOCATION (context)); c_list_unlink (nm_auth_chain_parent_lst_list (chain)); path = nm_auth_chain_get_data (chain, "path"); result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL); active = active_connection_get_by_path (self, path); if (result != NM_AUTH_CALL_RESULT_YES) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized to deactivate connections"); } else if (!active) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, "The connection was not active."); } else { /* success; deactivation allowed */ if (!nm_manager_deactivate_connection (self, active, NM_DEVICE_STATE_REASON_USER_REQUESTED, &error)) nm_assert (error); } if (active) { nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DEACTIVATE, nm_active_connection_get_settings_connection (active), !error, NULL, nm_auth_chain_get_subject (chain), error ? error->message : NULL); } if (error) g_dbus_method_invocation_take_error (context, error); else g_dbus_method_invocation_return_value (context, NULL); } static void impl_manager_deactivate_connection (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *dbus_connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *ac; NMSettingsConnection *sett_conn = NULL; GError *error = NULL; NMAuthSubject *subject = NULL; NMAuthChain *chain; const char *active_path; g_variant_get (parameters, "(&o)", &active_path); /* Find the connection by its object path */ ac = active_connection_get_by_path (self, active_path); if (ac) sett_conn = nm_active_connection_get_settings_connection (ac); if (!sett_conn) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE, "The connection was not active."); goto done; } /* Validate the caller */ subject = nm_dbus_manager_new_auth_subject_from_context (invocation); if (!subject) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN); goto done; } if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn), subject, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, &error)) goto done; /* Validate the user request */ chain = nm_auth_chain_new_subject (subject, invocation, deactivate_net_auth_done_cb, self); if (!chain) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); goto done; } c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "path", g_strdup (active_path), g_free); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE); done: if (error) { if (sett_conn) { nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DEACTIVATE, sett_conn, FALSE, NULL, subject, error->message); } g_dbus_method_invocation_take_error (invocation, error); } g_clear_object (&subject); } static gboolean sleep_devices_add (NMManager *self, NMDevice *device, gboolean suspending) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMSleepMonitorInhibitorHandle *handle = NULL; if (g_hash_table_lookup_extended (priv->sleep_devices, device, NULL, (gpointer *) &handle)) { if (suspending) { /* if we are suspending, always insert a new handle in sleep_devices. * Even if we had an old handle, it might be stale by now. */ g_hash_table_insert (priv->sleep_devices, device, nm_sleep_monitor_inhibit_take (priv->sleep_monitor)); if (handle) nm_sleep_monitor_inhibit_release (priv->sleep_monitor, handle); } return FALSE; } g_hash_table_insert (priv->sleep_devices, g_object_ref (device), suspending ? nm_sleep_monitor_inhibit_take (priv->sleep_monitor) : NULL); g_signal_connect (device, "notify::" NM_DEVICE_STATE, (GCallback) device_sleep_cb, self); return TRUE; } static gboolean sleep_devices_remove (NMManager *self, NMDevice *device) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMSleepMonitorInhibitorHandle *handle; if (!g_hash_table_lookup_extended (priv->sleep_devices, device, NULL, (gpointer *) &handle)) return FALSE; if (handle) nm_sleep_monitor_inhibit_release (priv->sleep_monitor, handle); /* Remove device from hash */ g_signal_handlers_disconnect_by_func (device, device_sleep_cb, self); g_hash_table_remove (priv->sleep_devices, device); g_object_unref (device); return TRUE; } static void sleep_devices_clear (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; NMSleepMonitorInhibitorHandle *handle; GHashTableIter iter; if (!priv->sleep_devices) return; g_hash_table_iter_init (&iter, priv->sleep_devices); while (g_hash_table_iter_next (&iter, (gpointer *) &device, (gpointer *) &handle)) { g_signal_handlers_disconnect_by_func (device, device_sleep_cb, self); if (handle) nm_sleep_monitor_inhibit_release (priv->sleep_monitor, handle); g_object_unref (device); g_hash_table_iter_remove (&iter); } } static void device_sleep_cb (NMDevice *device, GParamSpec *pspec, NMManager *self) { switch (nm_device_get_state (device)) { case NM_DEVICE_STATE_DISCONNECTED: _LOGD (LOGD_SUSPEND, "sleep: unmanaging device %s", nm_device_get_ip_iface (device)); nm_device_set_unmanaged_by_flags_queue (device, NM_UNMANAGED_SLEEPING, TRUE, NM_DEVICE_STATE_REASON_SLEEPING); break; case NM_DEVICE_STATE_UNMANAGED: _LOGD (LOGD_SUSPEND, "sleep: device %s is ready", nm_device_get_ip_iface (device)); if (!sleep_devices_remove (self, device)) g_return_if_reached (); break; default: return; } } static void do_sleep_wake (NMManager *self, gboolean sleeping_changed) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gboolean suspending, waking_from_suspend; NMDevice *device; suspending = sleeping_changed && priv->sleeping; waking_from_suspend = sleeping_changed && !priv->sleeping; if (manager_sleeping (self)) { _LOGD (LOGD_SUSPEND, "sleep: %s...", suspending ? "sleeping" : "disabling"); /* FIXME: are there still hardware devices that need to be disabled around * suspend/resume? */ c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { if (nm_device_is_software (device)) { /* If a user disables networking we consider that as an * indication that also software devices must be disconnected. * But we don't want to destroy them for external events as * a system suspend. */ if (suspending) continue; } /* Wake-on-LAN devices will be taken down post-suspend rather than pre- */ if ( suspending && device_is_wake_on_lan (priv->platform, device)) { _LOGD (LOGD_SUSPEND, "sleep: device %s has wake-on-lan, skipping", nm_device_get_ip_iface (device)); continue; } if (nm_device_is_activating (device) || nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) { _LOGD (LOGD_SUSPEND, "sleep: wait disconnection of device %s", nm_device_get_ip_iface (device)); if (sleep_devices_add (self, device, suspending)) nm_device_queue_state (device, NM_DEVICE_STATE_DEACTIVATING, NM_DEVICE_STATE_REASON_SLEEPING); } else { nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_SLEEPING, TRUE, NM_DEVICE_STATE_REASON_SLEEPING); } } } else { _LOGD (LOGD_SUSPEND, "sleep: %s...", waking_from_suspend ? "waking up" : "re-enabling"); if (waking_from_suspend) { sleep_devices_clear (self); c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { if (nm_device_is_software (device)) continue; /* Belatedly take down Wake-on-LAN devices; ideally we wouldn't have to do this * but for now it's the only way to make sure we re-check their connectivity. */ if (device_is_wake_on_lan (priv->platform, device)) nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_SLEEPING, TRUE, NM_DEVICE_STATE_REASON_SLEEPING); /* Check if the device is unmanaged but the state transition is still pending. * If so, change state now so that later we re-manage the device forcing a * re-check of available connections. */ if ( !nm_device_get_managed (device, FALSE) && nm_device_get_state (device) != NM_DEVICE_STATE_UNMANAGED) { nm_device_state_changed (device, NM_DEVICE_STATE_UNMANAGED, NM_DEVICE_STATE_REASON_SLEEPING); } } } /* Ensure rfkill state is up-to-date since we don't respond to state * changes during sleep. */ nm_manager_rfkill_update (self, RFKILL_TYPE_UNKNOWN); /* Re-manage managed devices */ c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { guint i; if ( nm_device_is_software (device) && !nm_device_get_unmanaged_flags (device, NM_UNMANAGED_SLEEPING)) { /* DHCP leases of software devices could have gone stale * so we need to renew them. */ nm_device_update_dynamic_ip_setup (device); continue; } /* enable/disable wireless devices since that we don't respond * to killswitch changes during sleep. */ for (i = 0; i < RFKILL_TYPE_MAX; i++) { RadioState *rstate = &priv->radio_states[i]; gboolean enabled = radio_enabled_for_rstate (rstate, TRUE); if (rstate->desc) { _LOGD (LOGD_RFKILL, "rfkill: %s %s devices (hw_enabled %d, sw_enabled %d, user_enabled %d)", enabled ? "enabling" : "disabling", rstate->desc, rstate->hw_enabled, rstate->sw_enabled, rstate->user_enabled); } if (nm_device_get_rfkill_type (device) == rstate->rtype) nm_device_set_enabled (device, enabled); } nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_SLEEPING, FALSE, NM_DEVICE_STATE_REASON_NOW_MANAGED); } } nm_manager_update_state (self); } static void _internal_sleep (NMManager *self, gboolean do_sleep) { NMManagerPrivate *priv; g_return_if_fail (NM_IS_MANAGER (self)); priv = NM_MANAGER_GET_PRIVATE (self); if (priv->sleeping == do_sleep) return; _LOGI (LOGD_SUSPEND, "sleep: %s requested (sleeping: %s enabled: %s)", do_sleep ? "sleep" : "wake", priv->sleeping ? "yes" : "no", priv->net_enabled ? "yes" : "no"); priv->sleeping = do_sleep; do_sleep_wake (self, TRUE); _notify (self, PROP_SLEEPING); } static void impl_manager_sleep (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); GError *error = NULL; gs_unref_object NMAuthSubject *subject = NULL; gboolean do_sleep; g_variant_get (parameters, "(b)", &do_sleep); subject = nm_dbus_manager_new_auth_subject_from_context (invocation); if (priv->sleeping == do_sleep) { error = g_error_new (NM_MANAGER_ERROR, NM_MANAGER_ERROR_ALREADY_ASLEEP_OR_AWAKE, "Already %s", do_sleep ? "asleep" : "awake"); nm_audit_log_control_op (NM_AUDIT_OP_SLEEP_CONTROL, do_sleep ? "on" : "off", FALSE, subject, error->message); g_dbus_method_invocation_take_error (invocation, error); return; } /* Unconditionally allow the request. Previously it was polkit protected * but unfortunately that doesn't work for short-lived processes like * pm-utils. It uses dbus-send without --print-reply, which quits * immediately after sending the request, and NM is unable to obtain the * sender's UID as dbus-send has already dropped off the bus. Thus NM * fails the request. Instead, don't validate the request, but rely on * D-Bus permissions to restrict the call to root. */ _internal_sleep (self, do_sleep); nm_audit_log_control_op (NM_AUDIT_OP_SLEEP_CONTROL, do_sleep ? "on" : "off", TRUE, subject, NULL); g_dbus_method_invocation_return_value (invocation, NULL); return; } static void sleeping_cb (NMSleepMonitor *monitor, gboolean is_about_to_suspend, gpointer user_data) { NMManager *self = user_data; _LOGT (LOGD_SUSPEND, "sleep: received %s signal", is_about_to_suspend ? "sleeping" : "resuming"); _internal_sleep (self, is_about_to_suspend); } static void _internal_enable (NMManager *self, gboolean enable) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); nm_config_state_set (priv->config, TRUE, FALSE, NM_CONFIG_STATE_PROPERTY_NETWORKING_ENABLED, enable); _LOGI (LOGD_SUSPEND, "%s requested (sleeping: %s enabled: %s)", enable ? "enable" : "disable", priv->sleeping ? "yes" : "no", priv->net_enabled ? "yes" : "no"); priv->net_enabled = enable; do_sleep_wake (self, FALSE); _notify (self, PROP_NETWORKING_ENABLED); } static void enable_net_done_cb (NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMAuthCallResult result; gboolean enable; NMAuthSubject *subject; nm_assert (G_IS_DBUS_METHOD_INVOCATION (context)); c_list_unlink (nm_auth_chain_parent_lst_list (chain)); enable = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "enable")); subject = nm_auth_chain_get_subject (chain); result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK); if (result != NM_AUTH_CALL_RESULT_YES) { GError *ret_error; ret_error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized to enable/disable networking"); nm_audit_log_control_op (NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", FALSE, subject, ret_error->message); g_dbus_method_invocation_take_error (context, ret_error); return; } _internal_enable (self, enable); g_dbus_method_invocation_return_value (context, NULL); nm_audit_log_control_op (NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", TRUE, subject, NULL); } static void impl_manager_enable (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; GError *error = NULL; gboolean enable; g_variant_get (parameters, "(b)", &enable); if (priv->net_enabled == enable) { error = g_error_new (NM_MANAGER_ERROR, NM_MANAGER_ERROR_ALREADY_ENABLED_OR_DISABLED, "Already %s", enable ? "enabled" : "disabled"); goto done; } chain = nm_auth_chain_new_context (invocation, enable_net_done_cb, self); if (!chain) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); goto done; } c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "enable", GUINT_TO_POINTER (enable), NULL); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, TRUE); done: if (error) g_dbus_method_invocation_take_error (invocation, error); } /* Permissions */ static void get_permissions_done_cb (NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { GVariantBuilder results; int i; nm_assert (G_IS_DBUS_METHOD_INVOCATION (context)); c_list_unlink (nm_auth_chain_parent_lst_list (chain)); g_variant_builder_init (&results, G_VARIANT_TYPE ("a{ss}")); for (i = 0; i < (int) G_N_ELEMENTS (nm_auth_permission_sorted); i++) { const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1]; NMAuthCallResult result; const char *result_str; result = nm_auth_chain_get_result (chain, permission); result_str = nm_client_permission_result_to_string (nm_auth_call_result_to_client (result)); g_variant_builder_add (&results, "{ss}", permission, result_str); } g_dbus_method_invocation_return_value (context, g_variant_new ("(a{ss})", &results)); } static void impl_manager_get_permissions (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; int i; chain = nm_auth_chain_new_context (invocation, get_permissions_done_cb, self); if (!chain) { g_dbus_method_invocation_return_error_literal (invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); return; } c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); for (i = 0; i < (int) G_N_ELEMENTS (nm_auth_permission_sorted); i++) { const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1]; nm_auth_chain_add_call_unsafe (chain, permission, FALSE); } } static void impl_manager_state (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); nm_manager_update_state (self); g_dbus_method_invocation_return_value (invocation, g_variant_new ("(u)", NM_MANAGER_GET_PRIVATE (self)->state)); } static void impl_manager_set_logging (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); GError *error = NULL; const char *level; const char *domains; /* The permission is already enforced by the D-Bus daemon, but we ensure * that the caller is still alive so that clients are forced to wait and * we'll be able to switch to polkit without breaking behavior. */ if (!nm_dbus_manager_ensure_uid (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)), invocation, G_MAXULONG, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED)) return; g_variant_get (parameters, "(&s&s)", &level, &domains); if (nm_logging_setup (level, domains, NULL, &error)) { _LOGI (LOGD_CORE, "logging: level '%s' domains '%s'", nm_logging_level_to_string (), nm_logging_domains_to_string ()); } if (error) g_dbus_method_invocation_take_error (invocation, error); else g_dbus_method_invocation_return_value (invocation, NULL); } static void impl_manager_get_logging (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { g_dbus_method_invocation_return_value (invocation, g_variant_new ("(ss)", nm_logging_level_to_string (), nm_logging_domains_to_string ())); } typedef struct { NMManager *self; GDBusMethodInvocation *context; guint remaining; } ConnectivityCheckData; static void device_connectivity_done (NMDevice *device, NMDeviceConnectivityHandle *handle, NMConnectivityState state, GError *error, gpointer user_data) { ConnectivityCheckData *data = user_data; NMManager *self; NMManagerPrivate *priv; nm_assert (data); nm_assert (data->remaining > 0); nm_assert (NM_IS_MANAGER (data->self)); data->remaining--; self = data->self; priv = NM_MANAGER_GET_PRIVATE (self); if ( data->context && ( data->remaining == 0 || ( state == NM_CONNECTIVITY_FULL && priv->connectivity_state == NM_CONNECTIVITY_FULL))) { /* despite having a @handle and @state returned by the requests, we always * return the current connectivity_state. That is, because the connectivity_state * and the answer to the connectivity check shall agree. * * However, if one of the requests (early) returns full connectivity and agrees with * the accumulated connectivity state, we no longer have to wait. The result is set. * * This also works well, because NMDevice first emits change signals to its own * connectivity state, which is then taken into account for the accumulated global * state. All this happens, before the callback is invoked. */ g_dbus_method_invocation_return_value (g_steal_pointer (&data->context), g_variant_new ("(u)", (guint) priv->connectivity_state)); } if (data->remaining == 0) { g_object_unref (self); g_slice_free (ConnectivityCheckData, data); } } static void check_connectivity_auth_done_cb (NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); GError *error = NULL; NMAuthCallResult result; ConnectivityCheckData *data; NMDevice *device; c_list_unlink (nm_auth_chain_parent_lst_list (chain)); result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL); if (result != NM_AUTH_CALL_RESULT_YES) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized to recheck connectivity"); } if (error) { g_dbus_method_invocation_take_error (context, error); return; } data = g_slice_new (ConnectivityCheckData); data->self = g_object_ref (self); data->context = context; data->remaining = 0; c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { if (nm_device_check_connectivity (device, AF_INET, device_connectivity_done, data)) data->remaining++; if (nm_device_check_connectivity (device, AF_INET6, device_connectivity_done, data)) data->remaining++; } if (data->remaining == 0) { /* call the handler at least once. */ data->remaining = 1; device_connectivity_done (NULL, NULL, NM_CONNECTIVITY_UNKNOWN, NULL, data); /* @data got destroyed. */ } } static void impl_manager_check_connectivity (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; chain = nm_auth_chain_new_context (invocation, check_connectivity_auth_done_cb, self); if (!chain) { g_dbus_method_invocation_return_error_literal(invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); return; } c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE); } static void start_factory (NMDeviceFactory *factory, gpointer user_data) { nm_device_factory_start (factory); } gboolean nm_manager_write_device_state (NMManager *self, NMDevice *device, int *out_ifindex) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); int ifindex; gboolean managed; NMConfigDeviceStateManagedType managed_type; const char *uuid = NULL; const char *perm_hw_addr_fake = NULL; gboolean perm_hw_addr_is_fake; guint32 route_metric_default_aspired; guint32 route_metric_default_effective; NMTernary nm_owned; NMDhcpConfig *dhcp_config; const char *next_server = NULL; const char *root_path = NULL; NM_SET_OUT (out_ifindex, 0); ifindex = nm_device_get_ip_ifindex (device); if (ifindex <= 0) return FALSE; if (ifindex == 1) { /* ignore loopback */ return FALSE; } if (!nm_platform_link_get (priv->platform, ifindex)) return FALSE; managed = nm_device_get_managed (device, FALSE); if (managed) { NMSettingsConnection *sett_conn = NULL; if (nm_device_get_state (device) <= NM_DEVICE_STATE_ACTIVATED) sett_conn = nm_device_get_settings_connection (device); if (sett_conn) uuid = nm_settings_connection_get_uuid (sett_conn); managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED; } else if (nm_device_get_unmanaged_flags (device, NM_UNMANAGED_USER_EXPLICIT)) managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED; else managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN; perm_hw_addr_fake = nm_device_get_permanent_hw_address_full (device, FALSE, &perm_hw_addr_is_fake); if (perm_hw_addr_fake && !perm_hw_addr_is_fake) perm_hw_addr_fake = NULL; nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : NM_TERNARY_DEFAULT; route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN, TRUE, &route_metric_default_aspired); dhcp_config = nm_device_get_dhcp_config (device, AF_INET); if (dhcp_config) { root_path = nm_dhcp_config_get_option (dhcp_config, "root_path"); next_server = nm_dhcp_config_get_option (dhcp_config, "next_server"); } if (!nm_config_device_state_write (ifindex, managed_type, perm_hw_addr_fake, uuid, nm_owned, route_metric_default_aspired, route_metric_default_effective, next_server, root_path)) return FALSE; NM_SET_OUT (out_ifindex, ifindex); return TRUE; } void nm_manager_write_device_state_all (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_unref_hashtable GHashTable *preserve_ifindexes = NULL; NMDevice *device; preserve_ifindexes = g_hash_table_new (nm_direct_hash, NULL); c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { int ifindex; if (nm_manager_write_device_state (self, device, &ifindex)) { g_hash_table_add (preserve_ifindexes, GINT_TO_POINTER (ifindex)); } } nm_config_device_state_prune_stale (preserve_ifindexes, NULL); } static gboolean devices_inited_cb (gpointer user_data) { NMManager *self = user_data; NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); priv->devices_inited_id = 0; priv->devices_inited = TRUE; check_if_startup_complete (self); return G_SOURCE_REMOVE; } gboolean nm_manager_start (NMManager *self, GError **error) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gs_free NMSettingsConnection **connections = NULL; guint i; nm_device_factory_manager_load_factories (_register_device_factory, self); nm_device_factory_manager_for_each_factory (start_factory, NULL); /* Set initial radio enabled/disabled state */ for (i = 0; i < RFKILL_TYPE_MAX; i++) { RadioState *rstate = &priv->radio_states[i]; gboolean enabled; if (!rstate->desc) continue; /* recheck kernel rfkill state */ update_rstate_from_rfkill (priv->rfkill_mgr, rstate); if (rstate->desc) { _LOGI (LOGD_RFKILL, "rfkill: %s %s by radio killswitch; %s by state file", rstate->desc, (rstate->hw_enabled && rstate->sw_enabled) ? "enabled" : "disabled", rstate->user_enabled ? "enabled" : "disabled"); } enabled = radio_enabled_for_rstate (rstate, TRUE); manager_update_radio_enabled (self, rstate, enabled); } _LOGI (LOGD_CORE, "Networking is %s by state file", priv->net_enabled ? "enabled" : "disabled"); system_unmanaged_devices_changed_cb (priv->settings, NULL, self); hostname_changed_cb (priv->hostname_manager, NULL, self); if (!nm_settings_start (priv->settings, error)) return FALSE; nm_platform_process_events (priv->platform); g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (platform_link_cb), self); platform_query_devices (self); /* Load VPN plugins */ priv->vpn_manager = g_object_ref (nm_vpn_manager_get ()); _LOGD (LOGD_CORE, "creating virtual devices..."); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED, G_CALLBACK (connection_added_cb), self); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED, G_CALLBACK (connection_updated_cb), self); connections = nm_settings_get_connections_clone (priv->settings, NULL, NULL, NULL, nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL); for (i = 0; connections[i]; i++) connection_changed (self, connections[i]); nm_clear_g_source (&priv->devices_inited_id); priv->devices_inited_id = g_idle_add_full (G_PRIORITY_LOW + 10, devices_inited_cb, self, NULL); return TRUE; } void nm_manager_stop (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; /* FIXME(shutdown): we don't do a proper shutdown yet: * - need to ensure that all pending async operations are cancelled * - e.g. operations in priv->async_op_lst_head * - need to ensure that no more asynchronous requests are started, * or that they complete quickly, or that they fail quickly. * - note that cancelling some operations is not possible synchronously. * Hence, stop() only prepares shutdown and tells everybody to not * accept new work, and to complete in a timely manner. * We need to still iterate the mainloop for a bit, to give everybody * the chance to complete. * - e.g. see comment at nm_auth_manager_force_shutdown() */ nm_dbus_manager_stop (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self))); while ((device = c_list_first_entry (&priv->devices_lst_head, NMDevice, devices_lst))) remove_device (self, device, TRUE); _active_connection_cleanup (self); nm_clear_g_source (&priv->devices_inited_id); } static gboolean handle_firmware_changed (gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMDevice *device; priv->fw_changed_id = 0; /* Try to re-enable devices with missing firmware */ c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) { NMDeviceState state = nm_device_get_state (device); if ( nm_device_get_firmware_missing (device) && (state == NM_DEVICE_STATE_UNAVAILABLE)) { _LOG2I (LOGD_CORE, device, "firmware may now be available"); /* Re-set unavailable state to try bringing the device up again */ nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_NONE); } } return FALSE; } static void firmware_dir_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); switch (event_type) { case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGED: case G_FILE_MONITOR_EVENT_MOVED: case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: if (!priv->fw_changed_id) { priv->fw_changed_id = g_timeout_add_seconds (4, handle_firmware_changed, self); _LOGI (LOGD_CORE, "kernel firmware directory '%s' changed", KERNEL_FIRMWARE_DIR); } break; default: break; } } static void connection_metered_changed (GObject *object, NMMetered metered, gpointer user_data) { nm_manager_update_metered (NM_MANAGER (user_data)); } static void policy_default_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *ac; /* Note: this assumes that it's not possible for the IP4 default * route to be going over the default-ip6-device. If that changes, * we need something more complicated here. */ ac = nm_policy_get_default_ip4_ac (priv->policy); if (!ac) ac = nm_policy_get_default_ip6_ac (priv->policy); if (ac != priv->primary_connection) { if (priv->primary_connection) { g_signal_handlers_disconnect_by_func (priv->primary_connection, G_CALLBACK (connection_metered_changed), self); g_clear_object (&priv->primary_connection); } priv->primary_connection = ac ? g_object_ref (ac) : NULL; if (priv->primary_connection) { g_signal_connect (priv->primary_connection, NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED, G_CALLBACK (connection_metered_changed), self); } _LOGD (LOGD_CORE, "PrimaryConnection now %s", ac ? nm_active_connection_get_settings_connection_id (ac) : "(none)"); _notify (self, PROP_PRIMARY_CONNECTION); _notify (self, PROP_PRIMARY_CONNECTION_TYPE); nm_manager_update_metered (self); } } static void policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMActiveConnection *activating, *best; /* We only look at activating-ip6-ac if activating-ip4-ac * AND default-ip4-ac are NULL; if default-ip4-ac is * non-NULL, then activating-ip6-ac is irrelevant, since while * that AC might become the new default-ip6-ac, it can't * become primary-connection while default-ip4-ac is set to * something else. */ activating = nm_policy_get_activating_ip4_ac (priv->policy); best = nm_policy_get_default_ip4_ac (priv->policy); if (!activating && !best) activating = nm_policy_get_activating_ip6_ac (priv->policy); if (nm_g_object_ref_set (&priv->activating_connection, activating)) { _LOGD (LOGD_CORE, "ActivatingConnection now %s", activating ? nm_active_connection_get_settings_connection_id (activating) : "(none)"); _notify (self, PROP_ACTIVATING_CONNECTION); } } /*****************************************************************************/ typedef struct { NMManager *self; NMDBusObject *obj; const NMDBusInterfaceInfoExtended *interface_info; const NMDBusPropertyInfoExtended *property_info; GVariant *value; guint64 export_version_id; } DBusSetPropertyHandle; #define NM_PERM_DENIED_ERROR "org.freedesktop.NetworkManager.PermissionDenied" static void _dbus_set_property_auth_cb (NMAuthChain *chain, GDBusMethodInvocation *invocation, gpointer user_data) { DBusSetPropertyHandle *handle_data = user_data; gs_unref_object NMDBusObject *obj = handle_data->obj; const NMDBusInterfaceInfoExtended *interface_info = handle_data->interface_info; const NMDBusPropertyInfoExtended *property_info = handle_data->property_info; gs_unref_variant GVariant *value = handle_data->value; guint64 export_version_id = handle_data->export_version_id; gs_unref_object NMManager *self = handle_data->self; NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthCallResult result; gs_free_error GError *local = NULL; const char *error_name = NULL; const char *error_message = NULL; GValue gvalue; g_slice_free (DBusSetPropertyHandle, handle_data); c_list_unlink (nm_auth_chain_parent_lst_list (chain)); result = nm_auth_chain_get_result (chain, property_info->writable.permission); if (result != NM_AUTH_CALL_RESULT_YES) { error_name = NM_PERM_DENIED_ERROR; error_message = "Not authorized to perform this operation"; goto out; } if (export_version_id != nm_dbus_object_get_export_version_id (obj)) { error_name = "org.freedesktop.DBus.Error.UnknownObject"; error_message = "Object was deleted while authenticating"; goto out; } /* Handle some properties specially *sigh* */ if ( interface_info == &interface_info_manager && nm_streq (property_info->property_name, NM_MANAGER_GLOBAL_DNS_CONFIGURATION)) { const NMGlobalDnsConfig *global_dns; global_dns = nm_config_data_get_global_dns_config (nm_config_get_data (priv->config)); if ( global_dns && !nm_global_dns_config_is_internal (global_dns)) { error_name = NM_PERM_DENIED_ERROR; error_message = "Global DNS configuration already set via configuration file"; goto out; } } g_dbus_gvariant_to_gvalue (value, &gvalue); if (!nm_g_object_set_property (G_OBJECT (obj), property_info->property_name, &gvalue, &local)) { error_name = "org.freedesktop.DBus.Error.InvalidArgs"; error_message = local->message; } g_value_unset (&gvalue); out: nm_audit_log_control_op (property_info->writable.audit_op, property_info->property_name, !error_message, nm_auth_chain_get_subject (chain), error_message); if (error_message) g_dbus_method_invocation_return_dbus_error (invocation, error_name, error_message); else g_dbus_method_invocation_return_value (invocation, NULL); } void nm_manager_dbus_set_property_handle (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusPropertyInfoExtended *property_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *value, gpointer user_data) { NMManager *self = user_data; NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; const char *error_message = NULL; gs_unref_object NMAuthSubject *subject = NULL; DBusSetPropertyHandle *handle_data; subject = nm_dbus_manager_new_auth_subject_from_context (invocation); if (!subject) { error_message = NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN; goto err; } handle_data = g_slice_new0 (DBusSetPropertyHandle); handle_data->self = g_object_ref (self); handle_data->obj = g_object_ref (obj); handle_data->interface_info = interface_info; handle_data->property_info = property_info; handle_data->value = g_variant_ref (value); handle_data->export_version_id = nm_dbus_object_get_export_version_id (obj); chain = nm_auth_chain_new_subject (subject, invocation, _dbus_set_property_auth_cb, handle_data); c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_add_call_unsafe (chain, property_info->writable.permission, TRUE); return; err: nm_audit_log_control_op (property_info->writable.audit_op, property_info->property_name, FALSE, invocation, error_message); g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, G_DBUS_ERROR_AUTH_FAILED, error_message); } /*****************************************************************************/ static NMCheckpointManager * _checkpoint_mgr_get (NMManager *self, gboolean create_as_needed) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); if (G_UNLIKELY (!priv->checkpoint_mgr) && create_as_needed) priv->checkpoint_mgr = nm_checkpoint_manager_new (self, obj_properties[PROP_CHECKPOINTS]); return priv->checkpoint_mgr; } static void checkpoint_auth_done_cb (NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data) { NMManager *self = NM_MANAGER (user_data); char *op; char *checkpoint_path = NULL; char **devices; NMCheckpoint *checkpoint; NMAuthCallResult result; guint32 timeout, flags; GVariant *variant = NULL; GError *error = NULL; const char *arg = NULL; guint32 add_timeout; op = nm_auth_chain_get_data (chain, "audit-op"); c_list_unlink (nm_auth_chain_parent_lst_list (chain)); result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK); if (NM_IN_STRSET (op, NM_AUDIT_OP_CHECKPOINT_DESTROY, NM_AUDIT_OP_CHECKPOINT_ROLLBACK, NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) arg = checkpoint_path = nm_auth_chain_get_data (chain, "checkpoint_path"); if (result != NM_AUTH_CALL_RESULT_YES) { error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, "Not authorized to checkpoint/rollback"); } else { if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_CREATE)) { timeout = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "timeout")); flags = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "flags")); devices = nm_auth_chain_get_data (chain, "devices"); checkpoint = nm_checkpoint_manager_create (_checkpoint_mgr_get (self, TRUE), (const char *const *) devices, timeout, (NMCheckpointCreateFlags) flags, &error); if (checkpoint) { arg = nm_dbus_object_get_path (NM_DBUS_OBJECT (checkpoint)); variant = g_variant_new ("(o)", arg); } } else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_DESTROY)) { nm_checkpoint_manager_destroy (_checkpoint_mgr_get (self, TRUE), checkpoint_path, &error); } else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ROLLBACK)) { nm_checkpoint_manager_rollback (_checkpoint_mgr_get (self, TRUE), checkpoint_path, &variant, &error); } else if (nm_streq0 (op, NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT)) { add_timeout = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "add_timeout")); nm_checkpoint_manager_adjust_rollback_timeout (_checkpoint_mgr_get (self, TRUE), checkpoint_path, add_timeout, &error); } else g_return_if_reached (); } nm_audit_log_checkpoint_op (op, arg ?: "", !error, nm_auth_chain_get_subject (chain), error ? error->message : NULL); if (error) g_dbus_method_invocation_take_error (context, error); else g_dbus_method_invocation_return_value (context, variant); } static void impl_manager_checkpoint_create (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; char **devices; guint32 rollback_timeout; guint32 flags; G_STATIC_ASSERT_EXPR (sizeof (flags) <= sizeof (NMCheckpointCreateFlags)); chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self); if (!chain) { g_dbus_method_invocation_return_error_literal (invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); return; } g_variant_get (parameters, "(^aouu)", &devices, &rollback_timeout, &flags); c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_CREATE, NULL); nm_auth_chain_set_data (chain, "devices", devices, (GDestroyNotify) g_strfreev); nm_auth_chain_set_data (chain, "flags", GUINT_TO_POINTER (flags), NULL); nm_auth_chain_set_data (chain, "timeout", GUINT_TO_POINTER (rollback_timeout), NULL); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); } static void impl_manager_checkpoint_destroy (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; const char *checkpoint_path; chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self); if (!chain) { g_dbus_method_invocation_return_error_literal (invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); return; } g_variant_get (parameters, "(&o)", &checkpoint_path); c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_DESTROY, NULL); nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); } static void impl_manager_checkpoint_rollback (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; const char *checkpoint_path; chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self); if (!chain) { g_dbus_method_invocation_return_error_literal (invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); return; } g_variant_get (parameters, "(&o)", &checkpoint_path); c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ROLLBACK, NULL); nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); } static void impl_manager_checkpoint_adjust_rollback_timeout (NMDBusObject *obj, const NMDBusInterfaceInfoExtended *interface_info, const NMDBusMethodInfoExtended *method_info, GDBusConnection *connection, const char *sender, GDBusMethodInvocation *invocation, GVariant *parameters) { NMManager *self = NM_MANAGER (obj); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMAuthChain *chain; const char *checkpoint_path; guint32 add_timeout; chain = nm_auth_chain_new_context (invocation, checkpoint_auth_done_cb, self); if (!chain) { g_dbus_method_invocation_return_error_literal (invocation, NM_MANAGER_ERROR, NM_MANAGER_ERROR_PERMISSION_DENIED, NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED); return; } g_variant_get (parameters, "(&ou)", &checkpoint_path, &add_timeout); c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain)); nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT, NULL); nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free); nm_auth_chain_set_data (chain, "add_timeout", GUINT_TO_POINTER (add_timeout), NULL); nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE); } /*****************************************************************************/ static void auth_mgr_changed (NMAuthManager *auth_manager, gpointer user_data) { /* Let clients know they should re-check their authorization */ nm_dbus_object_emit_signal (user_data, &interface_info_manager, &signal_info_check_permissions, "()"); } #define KERN_RFKILL_OP_CHANGE_ALL 3 #define KERN_RFKILL_TYPE_WLAN 1 #define KERN_RFKILL_TYPE_WWAN 5 struct rfkill_event { __u32 idx; __u8 type; __u8 op; __u8 soft, hard; } _nm_packed; static void rfkill_change (NMManager *self, const char *desc, RfKillType rtype, gboolean enabled) { int fd; struct rfkill_event event; ssize_t len; int errsv; g_return_if_fail (rtype == RFKILL_TYPE_WLAN || rtype == RFKILL_TYPE_WWAN); fd = open ("/dev/rfkill", O_RDWR | O_CLOEXEC); if (fd < 0) { if (errno == EACCES) _LOGW (LOGD_RFKILL, "rfkill: (%s): failed to open killswitch device", desc); return; } if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0) { _LOGW (LOGD_RFKILL, "rfkill: (%s): failed to set killswitch device for " "non-blocking operation", desc); nm_close (fd); return; } memset (&event, 0, sizeof (event)); event.op = KERN_RFKILL_OP_CHANGE_ALL; switch (rtype) { case RFKILL_TYPE_WLAN: event.type = KERN_RFKILL_TYPE_WLAN; break; case RFKILL_TYPE_WWAN: event.type = KERN_RFKILL_TYPE_WWAN; break; default: g_assert_not_reached (); } event.soft = enabled ? 0 : 1; len = write (fd, &event, sizeof (event)); if (len < 0) { errsv = errno; _LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state: (%d) %s", desc, errsv, nm_strerror_native (errsv)); } else if (len == sizeof (event)) { _LOGI (LOGD_RFKILL, "rfkill: %s hardware radio set %s", desc, enabled ? "enabled" : "disabled"); } else { /* Failed to write full structure */ _LOGW (LOGD_RFKILL, "rfkill: (%s): failed to change Wi-Fi killswitch state", desc); } nm_close (fd); } static void manager_radio_user_toggled (NMManager *self, RadioState *rstate, gboolean enabled) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); gboolean old_enabled, new_enabled; /* Don't touch devices if asleep/networking disabled */ if (manager_sleeping (self)) return; if (rstate->desc) { _LOGD (LOGD_RFKILL, "rfkill: (%s): setting radio %s by user", rstate->desc, enabled ? "enabled" : "disabled"); } /* Update enabled key in state file */ nm_config_state_set (priv->config, TRUE, FALSE, rstate->key, enabled); /* When the user toggles the radio, their request should override any * daemon (like ModemManager) enabled state that can be changed. For WWAN * for example, we want the WwanEnabled property to reflect the daemon state * too so that users can toggle the modem powered, but we don't want that * daemon state to affect whether or not the user *can* turn it on, which is * what the kernel rfkill state does. So we ignore daemon enabled state * when determining what the new state should be since it shouldn't block * the user's request. */ old_enabled = radio_enabled_for_rstate (rstate, TRUE); rstate->user_enabled = enabled; new_enabled = radio_enabled_for_rstate (rstate, FALSE); if (new_enabled != old_enabled) { /* Try to change the kernel rfkill state */ if (rstate->rtype == RFKILL_TYPE_WLAN || rstate->rtype == RFKILL_TYPE_WWAN) rfkill_change (self, rstate->desc, rstate->rtype, new_enabled); manager_update_radio_enabled (self, rstate, new_enabled); } } static gboolean periodic_update_active_connection_timestamps (gpointer user_data) { NMManager *manager = NM_MANAGER (user_data); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager); NMActiveConnection *ac; c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { if (nm_active_connection_get_state (ac) == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) { nm_settings_connection_update_timestamp (nm_active_connection_get_settings_connection (ac), (guint64) time (NULL)); } } return G_SOURCE_CONTINUE; } /*****************************************************************************/ void nm_manager_set_capability (NMManager *self, NMCapability cap) { NMManagerPrivate *priv; guint32 cap_i; gssize idx; g_return_if_fail (NM_IS_MANAGER (self)); if (cap < 1 || cap > _NM_CAPABILITY_MAX) g_return_if_reached (); cap_i = (guint32) cap; priv = NM_MANAGER_GET_PRIVATE (self); idx = nm_utils_array_find_binary_search (&g_array_index (priv->capabilities, guint32, 0), sizeof (guint32), priv->capabilities->len, &cap_i, nm_cmp_uint32_p_with_data, NULL); if (idx >= 0) return; nm_assert ((~idx) <= (gssize) priv->capabilities->len); g_array_insert_val (priv->capabilities, ~idx, cap_i); _notify (self, PROP_CAPABILITIES); } /*****************************************************************************/ NM_DEFINE_SINGLETON_REGISTER (NMManager); NMManager * nm_manager_get (void) { g_return_val_if_fail (singleton_instance, NULL); return singleton_instance; } NMSettings * nm_settings_get (void) { g_return_val_if_fail (singleton_instance, NULL); return NM_MANAGER_GET_PRIVATE (singleton_instance)->settings; } NMManager * nm_manager_setup (void) { NMManager *self; g_return_val_if_fail (!singleton_instance, singleton_instance); self = g_object_new (NM_TYPE_MANAGER, NULL); nm_assert (NM_IS_MANAGER (self)); singleton_instance = self; nm_singleton_instance_register (); nm_log_dbg (LOGD_CORE, "setup %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")", "NMManager", NM_HASH_OBFUSCATE_PTR (singleton_instance)); nm_dbus_object_export (NM_DBUS_OBJECT (self)); return self; } static void constructed (GObject *object) { NMManager *self = NM_MANAGER (object); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); const NMConfigState *state; G_OBJECT_CLASS (nm_manager_parent_class)->constructed (object); priv->settings = nm_settings_new (); nm_dbus_object_export (NM_DBUS_OBJECT (priv->settings)); g_signal_connect (priv->settings, "notify::" NM_SETTINGS_STARTUP_COMPLETE, G_CALLBACK (settings_startup_complete_changed), self); g_signal_connect (priv->settings, "notify::" NM_SETTINGS_UNMANAGED_SPECS, G_CALLBACK (system_unmanaged_devices_changed_cb), self); g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED, G_CALLBACK (connection_flags_changed), self); priv->hostname_manager = g_object_ref (nm_hostname_manager_get ()); g_signal_connect (priv->hostname_manager, "notify::" NM_HOSTNAME_MANAGER_HOSTNAME, G_CALLBACK (hostname_changed_cb), self); /* * Do not delete existing virtual devices to keep connectivity up. * Virtual devices are reused when NetworkManager is restarted. * Hence, don't react on NM_SETTINGS_SIGNAL_CONNECTION_REMOVED. */ priv->policy = nm_policy_new (self, priv->settings); g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP4_AC, G_CALLBACK (policy_default_ac_changed), self); g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP6_AC, G_CALLBACK (policy_default_ac_changed), self); g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP4_AC, G_CALLBACK (policy_activating_ac_changed), self); g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP6_AC, G_CALLBACK (policy_activating_ac_changed), self); priv->config = g_object_ref (nm_config_get ()); g_signal_connect (G_OBJECT (priv->config), NM_CONFIG_SIGNAL_CONFIG_CHANGED, G_CALLBACK (_config_changed_cb), self); state = nm_config_state_get (priv->config); priv->net_enabled = state->net_enabled; priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = state->wifi_enabled; priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = state->wwan_enabled; priv->rfkill_mgr = nm_rfkill_manager_new (); g_signal_connect (priv->rfkill_mgr, NM_RFKILL_MANAGER_SIGNAL_RFKILL_CHANGED, G_CALLBACK (rfkill_manager_rfkill_changed_cb), self); /* Force kernel Wi-Fi/WWAN rfkill state to follow NM saved Wi-Fi/WWAN state * in case the BIOS doesn't save rfkill state, and to be consistent with user * changes to the WirelessEnabled/WWANEnabled properties which toggle kernel * rfkill. */ rfkill_change (self, priv->radio_states[RFKILL_TYPE_WLAN].desc, RFKILL_TYPE_WLAN, priv->radio_states[RFKILL_TYPE_WLAN].user_enabled); rfkill_change (self, priv->radio_states[RFKILL_TYPE_WWAN].desc, RFKILL_TYPE_WWAN, priv->radio_states[RFKILL_TYPE_WWAN].user_enabled); } static void nm_manager_init (NMManager *self) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); guint i; GFile *file; c_list_init (&priv->auth_lst_head); c_list_init (&priv->link_cb_lst); c_list_init (&priv->devices_lst_head); c_list_init (&priv->active_connections_lst_head); c_list_init (&priv->async_op_lst_head); c_list_init (&priv->delete_volatile_connection_lst_head); priv->platform = g_object_ref (NM_PLATFORM_GET); priv->capabilities = g_array_new (FALSE, FALSE, sizeof (guint32)); /* Initialize rfkill structures and states */ memset (priv->radio_states, 0, sizeof (priv->radio_states)); priv->radio_states[RFKILL_TYPE_WLAN].user_enabled = TRUE; priv->radio_states[RFKILL_TYPE_WLAN].key = NM_CONFIG_STATE_PROPERTY_WIFI_ENABLED; priv->radio_states[RFKILL_TYPE_WLAN].prop = NM_MANAGER_WIRELESS_ENABLED; priv->radio_states[RFKILL_TYPE_WLAN].hw_prop = NM_MANAGER_WIRELESS_HARDWARE_ENABLED; priv->radio_states[RFKILL_TYPE_WLAN].desc = "Wi-Fi"; priv->radio_states[RFKILL_TYPE_WLAN].rtype = RFKILL_TYPE_WLAN; priv->radio_states[RFKILL_TYPE_WWAN].user_enabled = TRUE; priv->radio_states[RFKILL_TYPE_WWAN].key = NM_CONFIG_STATE_PROPERTY_WWAN_ENABLED; priv->radio_states[RFKILL_TYPE_WWAN].prop = NM_MANAGER_WWAN_ENABLED; priv->radio_states[RFKILL_TYPE_WWAN].hw_prop = NM_MANAGER_WWAN_HARDWARE_ENABLED; priv->radio_states[RFKILL_TYPE_WWAN].desc = "WWAN"; priv->radio_states[RFKILL_TYPE_WWAN].rtype = RFKILL_TYPE_WWAN; for (i = 0; i < RFKILL_TYPE_MAX; i++) priv->radio_states[i].hw_enabled = TRUE; priv->sleeping = FALSE; priv->state = NM_STATE_DISCONNECTED; priv->startup = TRUE; /* sleep/wake handling */ priv->sleep_monitor = nm_sleep_monitor_new (); g_signal_connect (priv->sleep_monitor, NM_SLEEP_MONITOR_SLEEPING, G_CALLBACK (sleeping_cb), self); /* Listen for authorization changes */ priv->auth_mgr = g_object_ref (nm_auth_manager_get ()); g_signal_connect (priv->auth_mgr, NM_AUTH_MANAGER_SIGNAL_CHANGED, G_CALLBACK (auth_mgr_changed), self); /* Monitor the firmware directory */ if (strlen (KERNEL_FIRMWARE_DIR)) { file = g_file_new_for_path (KERNEL_FIRMWARE_DIR "/"); priv->fw_monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL); g_object_unref (file); } if (priv->fw_monitor) { g_signal_connect (priv->fw_monitor, "changed", G_CALLBACK (firmware_dir_changed), self); _LOGI (LOGD_CORE, "monitoring kernel firmware directory '%s'.", KERNEL_FIRMWARE_DIR); } else { _LOGW (LOGD_CORE, "failed to monitor kernel firmware directory '%s'.", KERNEL_FIRMWARE_DIR); } /* Update timestamps in active connections */ priv->timestamp_update_id = g_timeout_add_seconds (300, (GSourceFunc) periodic_update_active_connection_timestamps, self); priv->metered = NM_METERED_UNKNOWN; priv->sleep_devices = g_hash_table_new (nm_direct_hash, NULL); } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NMManager *self = NM_MANAGER (object); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMConfigData *config_data; const NMGlobalDnsConfig *dns_config; const char *type; const char *path; NMActiveConnection *ac; GPtrArray *ptrarr; switch (prop_id) { case PROP_VERSION: g_value_set_string (value, VERSION); break; case PROP_CAPABILITIES: g_value_set_variant (value, g_variant_new_fixed_array (G_VARIANT_TYPE ("u"), priv->capabilities->data, priv->capabilities->len, sizeof (guint32))); break; case PROP_STATE: g_value_set_uint (value, priv->state); break; case PROP_STARTUP: g_value_set_boolean (value, priv->startup); break; case PROP_NETWORKING_ENABLED: g_value_set_boolean (value, priv->net_enabled); break; case PROP_WIRELESS_ENABLED: g_value_set_boolean (value, radio_enabled_for_type (self, RFKILL_TYPE_WLAN, TRUE)); break; case PROP_WIRELESS_HARDWARE_ENABLED: g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WLAN].hw_enabled); break; case PROP_WWAN_ENABLED: g_value_set_boolean (value, radio_enabled_for_type (self, RFKILL_TYPE_WWAN, TRUE)); break; case PROP_WWAN_HARDWARE_ENABLED: g_value_set_boolean (value, priv->radio_states[RFKILL_TYPE_WWAN].hw_enabled); break; case PROP_WIMAX_ENABLED: g_value_set_boolean (value, FALSE); break; case PROP_WIMAX_HARDWARE_ENABLED: g_value_set_boolean (value, FALSE); break; case PROP_ACTIVE_CONNECTIONS: ptrarr = g_ptr_array_new (); c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) { path = nm_dbus_object_get_path (NM_DBUS_OBJECT (ac)); if (path) g_ptr_array_add (ptrarr, g_strdup (path)); } g_ptr_array_add (ptrarr, NULL); g_value_take_boxed (value, g_ptr_array_free (ptrarr, FALSE)); break; case PROP_CONNECTIVITY: g_value_set_uint (value, priv->connectivity_state); break; case PROP_CONNECTIVITY_CHECK_AVAILABLE: config_data = nm_config_get_data (priv->config); g_value_set_boolean (value, nm_config_data_get_connectivity_uri (config_data) != NULL); break; case PROP_CONNECTIVITY_CHECK_ENABLED: g_value_set_boolean (value, concheck_enabled (self, NULL)); break; case PROP_CONNECTIVITY_CHECK_URI: config_data = nm_config_get_data (priv->config); g_value_set_string (value, nm_config_data_get_connectivity_uri (config_data)); break; case PROP_PRIMARY_CONNECTION: nm_dbus_utils_g_value_set_object_path (value, priv->primary_connection); break; case PROP_PRIMARY_CONNECTION_TYPE: type = NULL; if (priv->primary_connection) { NMConnection *con; con = nm_active_connection_get_applied_connection (priv->primary_connection); if (con) type = nm_connection_get_connection_type (con); } g_value_set_string (value, type ?: ""); break; case PROP_ACTIVATING_CONNECTION: nm_dbus_utils_g_value_set_object_path (value, priv->activating_connection); break; case PROP_SLEEPING: g_value_set_boolean (value, priv->sleeping); break; case PROP_DEVICES: g_value_take_boxed (value, nm_utils_strv_make_deep_copied (_get_devices_paths (self, FALSE))); break; case PROP_METERED: g_value_set_uint (value, priv->metered); break; case PROP_GLOBAL_DNS_CONFIGURATION: config_data = nm_config_get_data (priv->config); dns_config = nm_config_data_get_global_dns_config (config_data); nm_global_dns_config_to_dbus (dns_config, value); break; case PROP_ALL_DEVICES: g_value_take_boxed (value, nm_utils_strv_make_deep_copied (_get_devices_paths (self, TRUE))); break; case PROP_CHECKPOINTS: g_value_take_boxed (value, priv->checkpoint_mgr ? nm_utils_strv_make_deep_copied (nm_checkpoint_manager_get_checkpoint_paths (priv->checkpoint_mgr, NULL)) : NULL); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMManager *self = NM_MANAGER (object); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); NMGlobalDnsConfig *dns_config; GError *error = NULL; switch (prop_id) { case PROP_WIRELESS_ENABLED: manager_radio_user_toggled (NM_MANAGER (object), &priv->radio_states[RFKILL_TYPE_WLAN], g_value_get_boolean (value)); break; case PROP_WWAN_ENABLED: manager_radio_user_toggled (NM_MANAGER (object), &priv->radio_states[RFKILL_TYPE_WWAN], g_value_get_boolean (value)); break; case PROP_WIMAX_ENABLED: /* WIMAX is deprecated. This does nothing. */ break; case PROP_CONNECTIVITY_CHECK_ENABLED: nm_config_set_connectivity_check_enabled (priv->config, g_value_get_boolean (value)); break; case PROP_GLOBAL_DNS_CONFIGURATION: dns_config = nm_global_dns_config_from_dbus (value, &error); if (!error) nm_config_set_global_dns (priv->config, dns_config, &error); nm_global_dns_config_free (dns_config); if (error) { _LOGD (LOGD_CORE, "set global DNS failed with error: %s", error->message); g_error_free (error); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void _deinit_device_factory (NMDeviceFactory *factory, gpointer user_data) { g_signal_handlers_disconnect_matched (factory, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, NM_MANAGER (user_data)); } static void dispose (GObject *object) { NMManager *self = NM_MANAGER (object); NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); CList *iter; nm_assert (c_list_is_empty (&priv->async_op_lst_head)); g_signal_handlers_disconnect_by_func (priv->platform, G_CALLBACK (platform_link_cb), self); while ((iter = c_list_first (&priv->link_cb_lst))) { PlatformLinkCbData *data = c_list_entry (iter, PlatformLinkCbData, lst); g_source_remove (data->idle_id); c_list_unlink_stale (&data->lst); g_slice_free (PlatformLinkCbData, data); } while ((iter = c_list_first (&priv->auth_lst_head))) nm_auth_chain_destroy (nm_auth_chain_parent_lst_entry (iter)); nm_clear_g_source (&priv->devices_inited_id); nm_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_free); if (priv->concheck_mgr) { g_signal_handlers_disconnect_by_func (priv->concheck_mgr, G_CALLBACK (concheck_config_changed_cb), self); g_clear_object (&priv->concheck_mgr); } if (priv->auth_mgr) { g_signal_handlers_disconnect_by_func (priv->auth_mgr, G_CALLBACK (auth_mgr_changed), self); g_clear_object (&priv->auth_mgr); } nm_assert (c_list_is_empty (&priv->devices_lst_head)); nm_clear_g_source (&priv->ac_cleanup_id); while ((iter = c_list_first (&priv->active_connections_lst_head))) active_connection_remove (self, c_list_entry (iter, NMActiveConnection, active_connections_lst)); nm_assert (c_list_is_empty (&priv->active_connections_lst_head)); g_clear_object (&priv->primary_connection); g_clear_object (&priv->activating_connection); if (priv->config) { g_signal_handlers_disconnect_by_func (priv->config, _config_changed_cb, self); g_clear_object (&priv->config); } if (priv->policy) { g_signal_handlers_disconnect_by_func (priv->policy, policy_default_ac_changed, self); g_signal_handlers_disconnect_by_func (priv->policy, policy_activating_ac_changed, self); g_clear_object (&priv->policy); } if (priv->settings) { g_signal_handlers_disconnect_by_func (priv->settings, settings_startup_complete_changed, self); g_signal_handlers_disconnect_by_func (priv->settings, system_unmanaged_devices_changed_cb, self); g_signal_handlers_disconnect_by_func (priv->settings, connection_added_cb, self); g_signal_handlers_disconnect_by_func (priv->settings, connection_updated_cb, self); g_signal_handlers_disconnect_by_func (priv->settings, connection_flags_changed, self); g_clear_object (&priv->settings); } if (priv->hostname_manager) { g_signal_handlers_disconnect_by_func (priv->hostname_manager, hostname_changed_cb, self); g_clear_object (&priv->hostname_manager); } g_clear_object (&priv->vpn_manager); sleep_devices_clear (self); nm_clear_pointer (&priv->sleep_devices, g_hash_table_unref); if (priv->sleep_monitor) { g_signal_handlers_disconnect_by_func (priv->sleep_monitor, sleeping_cb, self); g_clear_object (&priv->sleep_monitor); } if (priv->fw_monitor) { g_signal_handlers_disconnect_by_func (priv->fw_monitor, firmware_dir_changed, self); nm_clear_g_source (&priv->fw_changed_id); g_file_monitor_cancel (priv->fw_monitor); g_clear_object (&priv->fw_monitor); } if (priv->rfkill_mgr) { g_signal_handlers_disconnect_by_func (priv->rfkill_mgr, rfkill_manager_rfkill_changed_cb, self); g_clear_object (&priv->rfkill_mgr); } nm_clear_g_source (&priv->delete_volatile_connection_idle_id); _delete_volatile_connection_all (self, FALSE); nm_assert (!priv->delete_volatile_connection_idle_id); nm_assert (c_list_is_empty (&priv->delete_volatile_connection_lst_head)); nm_device_factory_manager_for_each_factory (_deinit_device_factory, self); nm_clear_g_source (&priv->timestamp_update_id); nm_clear_pointer (&priv->device_route_metrics, g_hash_table_destroy); G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object); } static void finalize (GObject *object) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (object); g_array_free (priv->capabilities, TRUE); G_OBJECT_CLASS (nm_manager_parent_class)->finalize (object); g_object_unref (priv->platform); } static const GDBusSignalInfo signal_info_check_permissions = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT ( "CheckPermissions", ); static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT ( "StateChanged", .args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("state", "u"), ), ); static const GDBusSignalInfo signal_info_device_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT ( "DeviceAdded", .args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("device_path", "o"), ), ); static const GDBusSignalInfo signal_info_device_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT ( "DeviceRemoved", .args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("device_path", "o"), ), ); static const NMDBusInterfaceInfoExtended interface_info_manager = { .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT ( NM_DBUS_INTERFACE, .methods = NM_DEFINE_GDBUS_METHOD_INFOS ( NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "Reload", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("flags", "u"), ), ), .handle = impl_manager_reload, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "GetDevices", .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("devices", "ao"), ), ), .handle = impl_manager_get_devices, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "GetAllDevices", .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("devices", "ao"), ), ), .handle = impl_manager_get_all_devices, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "GetDeviceByIpIface", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("iface", "s"), ), .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("device", "o"), ), ), .handle = impl_manager_get_device_by_ip_iface, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "ActivateConnection", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("connection", "o"), NM_DEFINE_GDBUS_ARG_INFO ("device", "o"), NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"), ), .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"), ), ), .handle = impl_manager_activate_connection, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "AddAndActivateConnection", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"), NM_DEFINE_GDBUS_ARG_INFO ("device", "o"), NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"), ), .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("path", "o"), NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"), ), ), .handle = impl_manager_add_and_activate_connection, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "AddAndActivateConnection2", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("connection", "a{sa{sv}}"), NM_DEFINE_GDBUS_ARG_INFO ("device", "o"), NM_DEFINE_GDBUS_ARG_INFO ("specific_object", "o"), NM_DEFINE_GDBUS_ARG_INFO ("options", "a{sv}"), ), .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("path", "o"), NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"), NM_DEFINE_GDBUS_ARG_INFO ("result", "a{sv}"), ), ), .handle = impl_manager_add_and_activate_connection, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "DeactivateConnection", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("active_connection", "o"), ), ), .handle = impl_manager_deactivate_connection, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "Sleep", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("sleep", "b"), ), ), .handle = impl_manager_sleep, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "Enable", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("enable", "b"), ), ), .handle = impl_manager_enable, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "GetPermissions", .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("permissions", "a{ss}"), ), ), .handle = impl_manager_get_permissions, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "SetLogging", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("level", "s"), NM_DEFINE_GDBUS_ARG_INFO ("domains", "s"), ), ), .handle = impl_manager_set_logging, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "GetLogging", .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("level", "s"), NM_DEFINE_GDBUS_ARG_INFO ("domains", "s"), ), ), .handle = impl_manager_get_logging, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "CheckConnectivity", .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("connectivity", "u"), ), ), .handle = impl_manager_check_connectivity, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "state", .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("state", "u"), ), ), .handle = impl_manager_state, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "CheckpointCreate", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("devices", "ao"), NM_DEFINE_GDBUS_ARG_INFO ("rollback_timeout", "u"), NM_DEFINE_GDBUS_ARG_INFO ("flags", "u"), ), .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"), ), ), .handle = impl_manager_checkpoint_create, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "CheckpointDestroy", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"), ), ), .handle = impl_manager_checkpoint_destroy, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "CheckpointRollback", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"), ), .out_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("result", "a{su}"), ), ), .handle = impl_manager_checkpoint_rollback, ), NM_DEFINE_DBUS_METHOD_INFO_EXTENDED ( NM_DEFINE_GDBUS_METHOD_INFO_INIT ( "CheckpointAdjustRollbackTimeout", .in_args = NM_DEFINE_GDBUS_ARG_INFOS ( NM_DEFINE_GDBUS_ARG_INFO ("checkpoint", "o"), NM_DEFINE_GDBUS_ARG_INFO ("add_timeout", "u"), ), ), .handle = impl_manager_checkpoint_adjust_rollback_timeout, ), ), .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS ( &nm_signal_info_property_changed_legacy, &signal_info_check_permissions, &signal_info_state_changed, &signal_info_device_added, &signal_info_device_removed, ), .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS ( NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Devices", "ao", NM_MANAGER_DEVICES), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AllDevices", "ao", NM_MANAGER_ALL_DEVICES), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Checkpoints", "ao", NM_MANAGER_CHECKPOINTS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("NetworkingEnabled", "b", NM_MANAGER_NETWORKING_ENABLED), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WirelessEnabled", "b", NM_MANAGER_WIRELESS_ENABLED, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, NM_AUDIT_OP_RADIO_CONTROL), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WirelessHardwareEnabled", "b", NM_MANAGER_WIRELESS_HARDWARE_ENABLED), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WwanEnabled", "b", NM_MANAGER_WWAN_ENABLED, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, NM_AUDIT_OP_RADIO_CONTROL), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WwanHardwareEnabled", "b", NM_MANAGER_WWAN_HARDWARE_ENABLED), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("WimaxEnabled", "b", NM_MANAGER_WIMAX_ENABLED, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX, NM_AUDIT_OP_RADIO_CONTROL), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WimaxHardwareEnabled", "b", NM_MANAGER_WIMAX_HARDWARE_ENABLED), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveConnections", "ao", NM_MANAGER_ACTIVE_CONNECTIONS), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PrimaryConnection", "o", NM_MANAGER_PRIMARY_CONNECTION), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PrimaryConnectionType", "s", NM_MANAGER_PRIMARY_CONNECTION_TYPE), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Metered", "u", NM_MANAGER_METERED), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActivatingConnection", "o", NM_MANAGER_ACTIVATING_CONNECTION), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Startup", "b", NM_MANAGER_STARTUP), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Version", "s", NM_MANAGER_VERSION), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Capabilities", "u", NM_MANAGER_CAPABILITIES), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("State", "u", NM_MANAGER_STATE), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Connectivity", "u", NM_MANAGER_CONNECTIVITY), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ConnectivityCheckAvailable", "b", NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("ConnectivityCheckEnabled", "b", NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, NM_AUDIT_OP_NET_CONTROL), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("ConnectivityCheckUri", "s", NM_MANAGER_CONNECTIVITY_CHECK_URI), NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("GlobalDnsConfiguration", "a{sv}", NM_MANAGER_GLOBAL_DNS_CONFIGURATION, NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, NM_AUDIT_OP_NET_CONTROL), ), ), .legacy_property_changed = TRUE, }; static void nm_manager_class_init (NMManagerClass *manager_class) { GObjectClass *object_class = G_OBJECT_CLASS (manager_class); NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (manager_class); dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC (NM_DBUS_PATH); dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_manager); object_class->constructed = constructed; object_class->set_property = set_property; object_class->get_property = get_property; object_class->dispose = dispose; object_class->finalize = finalize; obj_properties[PROP_VERSION] = g_param_spec_string (NM_MANAGER_VERSION, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CAPABILITIES] = g_param_spec_variant (NM_MANAGER_CAPABILITIES, "", "", G_VARIANT_TYPE ("au"), NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_STATE] = g_param_spec_uint (NM_MANAGER_STATE, "", "", 0, NM_STATE_DISCONNECTED, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_STARTUP] = g_param_spec_boolean (NM_MANAGER_STARTUP, "", "", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_NETWORKING_ENABLED] = g_param_spec_boolean (NM_MANAGER_NETWORKING_ENABLED, "", "", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_WIRELESS_ENABLED] = g_param_spec_boolean (NM_MANAGER_WIRELESS_ENABLED, "", "", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_WIRELESS_HARDWARE_ENABLED] = g_param_spec_boolean (NM_MANAGER_WIRELESS_HARDWARE_ENABLED, "", "", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_WWAN_ENABLED] = g_param_spec_boolean (NM_MANAGER_WWAN_ENABLED, "", "", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_WWAN_HARDWARE_ENABLED] = g_param_spec_boolean (NM_MANAGER_WWAN_HARDWARE_ENABLED, "", "", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_WIMAX_ENABLED] = g_param_spec_boolean (NM_MANAGER_WIMAX_ENABLED, "", "", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_WIMAX_HARDWARE_ENABLED] = g_param_spec_boolean (NM_MANAGER_WIMAX_HARDWARE_ENABLED, "", "", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ACTIVE_CONNECTIONS] = g_param_spec_boxed (NM_MANAGER_ACTIVE_CONNECTIONS, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CONNECTIVITY] = g_param_spec_uint (NM_MANAGER_CONNECTIVITY, "", "", NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CONNECTIVITY_CHECK_AVAILABLE] = g_param_spec_boolean (NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE, "", "", TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CONNECTIVITY_CHECK_ENABLED] = g_param_spec_boolean (NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, "", "", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CONNECTIVITY_CHECK_URI] = g_param_spec_string (NM_MANAGER_CONNECTIVITY_CHECK_URI, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_PRIMARY_CONNECTION] = g_param_spec_string (NM_MANAGER_PRIMARY_CONNECTION, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_PRIMARY_CONNECTION_TYPE] = g_param_spec_string (NM_MANAGER_PRIMARY_CONNECTION_TYPE, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_ACTIVATING_CONNECTION] = g_param_spec_string (NM_MANAGER_ACTIVATING_CONNECTION, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /* Sleeping is not exported over D-Bus */ obj_properties[PROP_SLEEPING] = g_param_spec_boolean (NM_MANAGER_SLEEPING, "", "", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_DEVICES] = g_param_spec_boxed (NM_MANAGER_DEVICES, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMManager:metered: * * Whether the connectivity is metered. * * Since: 1.2 **/ obj_properties[PROP_METERED] = g_param_spec_uint (NM_MANAGER_METERED, "", "", 0, G_MAXUINT32, NM_METERED_UNKNOWN, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * NMManager:global-dns-configuration: * * The global DNS configuration. * * Since: 1.2 **/ obj_properties[PROP_GLOBAL_DNS_CONFIGURATION] = g_param_spec_variant (NM_MANAGER_GLOBAL_DNS_CONFIGURATION, "", "", G_VARIANT_TYPE ("a{sv}"), NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * NMManager:all-devices: * * All devices, including those that are not realized. * * Since: 1.2 **/ obj_properties[PROP_ALL_DEVICES] = g_param_spec_boxed (NM_MANAGER_ALL_DEVICES, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); obj_properties[PROP_CHECKPOINTS] = g_param_spec_boxed (NM_MANAGER_CHECKPOINTS, "", "", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); /* signals */ /* emitted only for realized devices */ signals[DEVICE_ADDED] = g_signal_new (NM_MANAGER_DEVICE_ADDED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_DEVICE); /* Emitted for both realized devices and placeholder devices */ signals[INTERNAL_DEVICE_ADDED] = g_signal_new (NM_MANAGER_INTERNAL_DEVICE_ADDED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); /* emitted only for realized devices when a device * becomes unrealized or removed */ signals[DEVICE_REMOVED] = g_signal_new (NM_MANAGER_DEVICE_REMOVED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_DEVICE); /* Emitted for both realized devices and placeholder devices */ signals[INTERNAL_DEVICE_REMOVED] = g_signal_new (NM_MANAGER_INTERNAL_DEVICE_REMOVED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT); signals[ACTIVE_CONNECTION_ADDED] = g_signal_new (NM_MANAGER_ACTIVE_CONNECTION_ADDED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION); signals[ACTIVE_CONNECTION_REMOVED] = g_signal_new (NM_MANAGER_ACTIVE_CONNECTION_REMOVED, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, NM_TYPE_ACTIVE_CONNECTION); signals[CONFIGURE_QUIT] = g_signal_new (NM_MANAGER_CONFIGURE_QUIT, G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); }