Files
NetworkManager/src/nm-manager.c
Thomas Haller 332df7a58e core: periodically cleanup unused device state files from /run
Otherwise, we only prune unused files when the service terminates.
Usually, NetworkManager service doesn't get restarted before shutdown
of the system (nor should it be). That means, if you create (and
destroy) a large number of software devices, the state files pile
up.

From time to time, go through the files on disk and delete those that
are no longer relevant.

In this case, "from time to time" means after we write/update state
files 100 times.
2020-03-04 16:53:15 +01:00

8354 lines
295 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2007 - 2009 Novell, Inc.
* Copyright (C) 2007 - 2017 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-manager.h"
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <limits.h>
#include "nm-glib-aux/nm-c-list.h"
#include "nm-libnm-core-intern/nm-common-macros.h"
#include "nm-dbus-manager.h"
#include "vpn/nm-vpn-manager.h"
#include "devices/nm-device.h"
#include "devices/nm-device-generic.h"
#include "platform/nm-platform.h"
#include "platform/nmp-object.h"
#include "nm-hostname-manager.h"
#include "nm-keep-alive.h"
#include "nm-rfkill-manager.h"
#include "dhcp/nm-dhcp-manager.h"
#include "settings/nm-settings.h"
#include "settings/nm-settings-connection.h"
#include "nm-auth-utils.h"
#include "nm-auth-manager.h"
#include "NetworkManagerUtils.h"
#include "devices/nm-device-factory.h"
#include "nm-sleep-monitor.h"
#include "nm-connectivity.h"
#include "nm-policy.h"
#include "nm-session-monitor.h"
#include "nm-act-request.h"
#include "nm-core-internal.h"
#include "nm-config.h"
#include "nm-audit-manager.h"
#include "nm-std-aux/nm-dbus-compat.h"
#include "nm-checkpoint.h"
#include "nm-checkpoint-manager.h"
#include "nm-dbus-object.h"
#include "nm-dispatcher.h"
#include "NetworkManagerUtils.h"
#define DEVICE_STATE_PRUNE_RATELIMIT_MAX 100u
/*****************************************************************************/
typedef struct {
gboolean user_enabled;
gboolean sw_enabled;
gboolean hw_enabled;
RfKillType rtype;
NMConfigRunStatePropertyType key;
const char *desc;
const char *prop;
const char *hw_prop;
} RadioState;
typedef enum {
ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL,
ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER,
ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2,
} AsyncOpType;
typedef struct {
CList async_op_lst;
NMManager *self;
AsyncOpType async_op_type;
union {
struct {
NMActiveConnection *active;
union {
struct {
GDBusMethodInvocation *invocation;
} activate_user;
struct {
GDBusMethodInvocation *invocation;
NMConnection *connection;
NMSettingsConnectionPersistMode persist_mode;
bool is_volatile:1;
} add_and_activate;
};
} ac_auth;
};
} AsyncOpData;
enum {
DEVICE_ADDED,
INTERNAL_DEVICE_ADDED,
DEVICE_REMOVED,
INTERNAL_DEVICE_REMOVED,
ACTIVE_CONNECTION_ADDED,
ACTIVE_CONNECTION_REMOVED,
CONFIGURE_QUIT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
NM_GOBJECT_PROPERTIES_DEFINE (NMManager,
PROP_VERSION,
PROP_CAPABILITIES,
PROP_STATE,
PROP_STARTUP,
PROP_NETWORKING_ENABLED,
PROP_WIRELESS_ENABLED,
PROP_WIRELESS_HARDWARE_ENABLED,
PROP_WWAN_ENABLED,
PROP_WWAN_HARDWARE_ENABLED,
PROP_WIMAX_ENABLED,
PROP_WIMAX_HARDWARE_ENABLED,
PROP_ACTIVE_CONNECTIONS,
PROP_CONNECTIVITY,
PROP_CONNECTIVITY_CHECK_AVAILABLE,
PROP_CONNECTIVITY_CHECK_ENABLED,
PROP_CONNECTIVITY_CHECK_URI,
PROP_PRIMARY_CONNECTION,
PROP_PRIMARY_CONNECTION_TYPE,
PROP_ACTIVATING_CONNECTION,
PROP_DEVICES,
PROP_METERED,
PROP_GLOBAL_DNS_CONFIGURATION,
PROP_ALL_DEVICES,
PROP_CHECKPOINTS,
/* Not exported */
PROP_SLEEPING,
);
typedef struct {
NMPlatform *platform;
GArray *capabilities;
CList active_connections_lst_head;
CList async_op_lst_head;
guint ac_cleanup_id;
NMActiveConnection *primary_connection;
NMActiveConnection *activating_connection;
NMMetered metered;
CList devices_lst_head;
NMState state;
NMConfig *config;
NMConnectivity *concheck_mgr;
NMPolicy *policy;
NMHostnameManager *hostname_manager;
struct {
GDBusConnection *connection;
guint id;
} prop_filter;
NMRfkillManager *rfkill_mgr;
CList link_cb_lst;
NMCheckpointManager *checkpoint_mgr;
NMSettings *settings;
RadioState radio_states[RFKILL_TYPE_MAX];
NMVpnManager *vpn_manager;
NMSleepMonitor *sleep_monitor;
NMAuthManager *auth_mgr;
GHashTable *device_route_metrics;
CList auth_lst_head;
GHashTable *sleep_devices;
/* Firmware dir monitor */
GFileMonitor *fw_monitor;
guint fw_changed_id;
guint timestamp_update_id;
guint devices_inited_id;
NMConnectivityState connectivity_state;
guint8 device_state_prune_ratelimit_count;
bool startup:1;
bool devices_inited:1;
bool sleeping:1;
bool net_enabled:1;
unsigned connectivity_check_enabled_last:2;
guint delete_volatile_connection_idle_id;
CList delete_volatile_connection_lst_head;
} NMManagerPrivate;
struct _NMManager {
NMDBusObject parent;
NMManagerPrivate _priv;
};
typedef struct {
NMDBusObjectClass parent;
} NMManagerClass;
G_DEFINE_TYPE (NMManager, nm_manager, NM_TYPE_DBUS_OBJECT)
#define NM_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMManager, NM_IS_MANAGER)
/*****************************************************************************/
NM_DEFINE_SINGLETON_INSTANCE (NMManager);
/*****************************************************************************/
#define _NMLOG_PREFIX_NAME "manager"
#define _NMLOG(level, domain, ...) \
G_STMT_START { \
const NMLogLevel _level = (level); \
const NMLogDomain _domain = (domain); \
\
if (nm_logging_enabled (_level, _domain)) { \
const NMManager *const _self = (self); \
char _sbuf[32]; \
\
_nm_log (_level, _domain, 0, NULL, NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
((_self && _self != singleton_instance) \
? nm_sprintf_buf (_sbuf, "[%p]", _self) \
: "") \
_NM_UTILS_MACRO_REST (__VA_ARGS__)); \
} \
} G_STMT_END
#define _NMLOG2(level, domain, device, ...) \
G_STMT_START { \
const NMLogLevel _level = (level); \
const NMLogDomain _domain = (domain); \
\
if (nm_logging_enabled (_level, _domain)) { \
const NMManager *const _self = (self); \
const char *const _ifname = _nm_device_get_iface (device); \
char _sbuf[32]; \
\
_nm_log (_level, _domain, 0, \
_ifname, NULL, \
"%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
((_self && _self != singleton_instance) \
? nm_sprintf_buf (_sbuf, "[%p]", _self) \
: ""), \
NM_PRINT_FMT_QUOTED (_ifname, "(", _ifname, "): ", "") \
_NM_UTILS_MACRO_REST (__VA_ARGS__)); \
} \
} G_STMT_END
#define _NMLOG3(level, domain, connection, ...) \
G_STMT_START { \
const NMLogLevel _level = (level); \
const NMLogDomain _domain = (domain); \
\
if (nm_logging_enabled (_level, _domain)) { \
const NMManager *const _self = (self); \
NMConnection *const _connection = (connection); \
const char *const _con_id = _nm_connection_get_id (_connection); \
char _sbuf[32]; \
\
_nm_log (_level, _domain, 0, \
NULL, _nm_connection_get_uuid (_connection), \
"%s%s: %s%s%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
((_self && _self != singleton_instance) \
? nm_sprintf_buf (_sbuf, "[%p]", _self) \
: ""), \
NM_PRINT_FMT_QUOTED (_con_id, "(", _con_id, ") ", "") \
_NM_UTILS_MACRO_REST (__VA_ARGS__)); \
} \
} G_STMT_END
/*****************************************************************************/
static const NMDBusInterfaceInfoExtended interface_info_manager;
static const GDBusSignalInfo signal_info_check_permissions;
static const GDBusSignalInfo signal_info_state_changed;
static const GDBusSignalInfo signal_info_device_added;
static const GDBusSignalInfo signal_info_device_removed;
static void update_connectivity_value (NMManager *self);
static gboolean add_device (NMManager *self, NMDevice *device, GError **error);
static void _emit_device_added_removed (NMManager *self,
NMDevice *device,
gboolean is_added);
static NMActiveConnection *_new_active_connection (NMManager *self,
gboolean is_vpn,
NMSettingsConnection *sett_conn,
NMConnection *incompl_conn,
NMConnection *applied,
const char *specific_object,
NMDevice *device,
NMAuthSubject *subject,
NMActivationType activation_type,
NMActivationReason activation_reason,
NMActivationStateFlags initial_state_flags,
GError **error);
static void policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data);
static gboolean find_master (NMManager *self,
NMConnection *connection,
NMDevice *device,
NMSettingsConnection **out_master_connection,
NMDevice **out_master_device,
NMActiveConnection **out_master_ac,
GError **error);
static void nm_manager_update_state (NMManager *manager);
static void connection_changed (NMManager *self,
NMSettingsConnection *sett_conn);
static void device_sleep_cb (NMDevice *device,
GParamSpec *pspec,
NMManager *self);
static void settings_startup_complete_changed (NMSettings *settings,
GParamSpec *pspec,
NMManager *self);
static void retry_connections_for_parent_device (NMManager *self, NMDevice *device);
static void active_connection_state_changed (NMActiveConnection *active,
GParamSpec *pspec,
NMManager *self);
static void active_connection_default_changed (NMActiveConnection *active,
GParamSpec *pspec,
NMManager *self);
static void active_connection_parent_active (NMActiveConnection *active,
NMActiveConnection *parent_ac,
NMManager *self);
static NMActiveConnection *active_connection_find (NMManager *self,
NMSettingsConnection *sett_conn,
const char *uuid,
NMActiveConnectionState max_state,
GPtrArray **out_all_matching);
static NMConnectivity *concheck_get_mgr (NMManager *self);
static void _internal_activation_auth_done (NMManager *self,
NMActiveConnection *active,
gboolean success,
const char *error_desc);
static void _add_and_activate_auth_done (NMManager *self,
AsyncOpType async_op_type,
NMActiveConnection *active,
NMConnection *connection,
GDBusMethodInvocation *invocation,
NMSettingsConnectionPersistMode persist_mode,
gboolean is_volatile,
gboolean success,
const char *error_desc);
static void _activation_auth_done (NMManager *self,
NMActiveConnection *active,
GDBusMethodInvocation *invocation,
gboolean success,
const char *error_desc);
/*****************************************************************************/
static
NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark)
/*****************************************************************************/
static gboolean
_connection_is_vpn (NMConnection *connection)
{
const char *type;
type = nm_connection_get_connection_type (connection);
if (type)
return nm_streq (type, NM_SETTING_VPN_SETTING_NAME);
/* we have an incomplete (invalid) connection at hand. That can only
* happen during AddAndActivate. Determine whether it's VPN type based
* on the existence of a [vpn] section. */
return !!nm_connection_get_setting_vpn (connection);
}
/*****************************************************************************/
static gboolean
concheck_enabled (NMManager *self, gboolean *out_changed)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
guint check_enabled;
check_enabled = nm_connectivity_check_enabled (concheck_get_mgr (self))
? 1 : 2;
if (priv->connectivity_check_enabled_last == check_enabled)
NM_SET_OUT (out_changed, FALSE);
else {
NM_SET_OUT (out_changed, TRUE);
priv->connectivity_check_enabled_last = check_enabled;
}
return check_enabled == 1;
}
static void
concheck_config_changed_cb (NMConnectivity *connectivity,
NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
gboolean changed;
concheck_enabled (self, &changed);
if (changed)
_notify (self, PROP_CONNECTIVITY_CHECK_ENABLED);
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
nm_device_check_connectivity_update_interval (device);
}
static NMConnectivity *
concheck_get_mgr (NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
if (G_UNLIKELY (!priv->concheck_mgr)) {
priv->concheck_mgr = g_object_ref (nm_connectivity_get ());
g_signal_connect (priv->concheck_mgr,
NM_CONNECTIVITY_CONFIG_CHANGED,
G_CALLBACK (concheck_config_changed_cb),
self);
}
return priv->concheck_mgr;
}
/*****************************************************************************/
static AsyncOpData *
_async_op_data_new_authorize_activate_internal (NMManager *self, NMActiveConnection *active_take)
{
AsyncOpData *async_op_data;
async_op_data = g_slice_new0 (AsyncOpData);
async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL;
async_op_data->self = g_object_ref (self);
async_op_data->ac_auth.active = active_take;
c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
return async_op_data;
}
static AsyncOpData *
_async_op_data_new_ac_auth_activate_user (NMManager *self,
NMActiveConnection *active_take,
GDBusMethodInvocation *invocation_take)
{
AsyncOpData *async_op_data;
async_op_data = g_slice_new0 (AsyncOpData);
async_op_data->async_op_type = ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER;
async_op_data->self = g_object_ref (self);
async_op_data->ac_auth.active = active_take;
async_op_data->ac_auth.activate_user.invocation = invocation_take;
c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
return async_op_data;
}
static AsyncOpData *
_async_op_data_new_ac_auth_add_and_activate (NMManager *self,
AsyncOpType async_op_type,
NMActiveConnection *active_take,
GDBusMethodInvocation *invocation_take,
NMConnection *connection_take,
NMSettingsConnectionPersistMode persist_mode,
gboolean is_volatile)
{
AsyncOpData *async_op_data;
nm_assert (NM_IN_SET (async_op_type, ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2));
async_op_data = g_slice_new0 (AsyncOpData);
async_op_data->async_op_type = async_op_type;
async_op_data->self = g_object_ref (self);
async_op_data->ac_auth.active = active_take;
async_op_data->ac_auth.add_and_activate.invocation = invocation_take;
async_op_data->ac_auth.add_and_activate.connection = connection_take;
async_op_data->ac_auth.add_and_activate.persist_mode = persist_mode;
async_op_data->ac_auth.add_and_activate.is_volatile = is_volatile;
c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
return async_op_data;
}
static void
_async_op_complete_ac_auth_cb (NMActiveConnection *active,
gboolean success,
const char *error_desc,
gpointer user_data)
{
AsyncOpData *async_op_data = user_data;
nm_assert (async_op_data);
nm_assert (NM_IS_MANAGER (async_op_data->self));
nm_assert (nm_c_list_contains_entry (&NM_MANAGER_GET_PRIVATE (async_op_data->self)->async_op_lst_head, async_op_data, async_op_lst));
nm_assert (NM_IS_ACTIVE_CONNECTION (active));
nm_assert (active == async_op_data->ac_auth.active);
c_list_unlink (&async_op_data->async_op_lst);
switch (async_op_data->async_op_type) {
case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL:
_internal_activation_auth_done (async_op_data->self,
async_op_data->ac_auth.active,
success,
error_desc);
break;
case ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER:
_activation_auth_done (async_op_data->self,
async_op_data->ac_auth.active,
async_op_data->ac_auth.activate_user.invocation,
success,
error_desc);
break;
case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE:
case ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2:
_add_and_activate_auth_done (async_op_data->self,
async_op_data->async_op_type,
async_op_data->ac_auth.active,
async_op_data->ac_auth.add_and_activate.connection,
async_op_data->ac_auth.add_and_activate.invocation,
async_op_data->ac_auth.add_and_activate.persist_mode,
async_op_data->ac_auth.add_and_activate.is_volatile,
success,
error_desc);
g_object_unref (async_op_data->ac_auth.add_and_activate.connection);
break;
default:
nm_assert_not_reached ();
break;
}
g_object_unref (async_op_data->ac_auth.active);
g_object_unref (async_op_data->self);
g_slice_free (AsyncOpData, async_op_data);
}
/*****************************************************************************/
typedef struct {
int ifindex;
guint32 aspired_metric;
guint32 effective_metric;
} DeviceRouteMetricData;
static DeviceRouteMetricData *
_device_route_metric_data_new (int ifindex, guint32 aspired_metric, guint32 effective_metric)
{
DeviceRouteMetricData *data;
nm_assert (ifindex > 0);
/* For IPv4, metrics can use the entire uint32 bit range. For IPv6,
* zero is treated like 1024. Since we handle IPv4 and IPv6 identically,
* we cannot allow a zero metric here.
*/
nm_assert (aspired_metric > 0);
nm_assert (effective_metric == 0 || aspired_metric <= effective_metric);
data = g_slice_new0 (DeviceRouteMetricData);
data->ifindex = ifindex;
data->aspired_metric = aspired_metric;
data->effective_metric = effective_metric ?: aspired_metric;
return data;
}
static guint
_device_route_metric_data_by_ifindex_hash (gconstpointer p)
{
const DeviceRouteMetricData *data = p;
NMHashState h;
nm_hash_init (&h, 1030338191);
nm_hash_update_vals (&h, data->ifindex);
return nm_hash_complete (&h);
}
static gboolean
_device_route_metric_data_by_ifindex_equal (gconstpointer pa, gconstpointer pb)
{
const DeviceRouteMetricData *a = pa;
const DeviceRouteMetricData *b = pb;
return a->ifindex == b->ifindex;
}
static guint32
_device_route_metric_get (NMManager *self,
int ifindex,
NMDeviceType device_type,
gboolean lookup_only,
guint32 *out_aspired_metric)
{
NMManagerPrivate *priv;
const DeviceRouteMetricData *d2;
DeviceRouteMetricData *data;
DeviceRouteMetricData data_lookup;
const NMDedupMultiHeadEntry *all_links_head;
NMPObject links_needle;
guint n_links;
gboolean cleaned = FALSE;
GHashTableIter h_iter;
guint32 metric;
g_return_val_if_fail (NM_IS_MANAGER (self), 0);
NM_SET_OUT (out_aspired_metric, 0);
if (ifindex <= 0) {
if (lookup_only)
return 0;
metric = nm_device_get_route_metric_default (device_type);
NM_SET_OUT (out_aspired_metric, metric);
return metric;
}
priv = NM_MANAGER_GET_PRIVATE (self);
if ( lookup_only
&& !priv->device_route_metrics)
return 0;
if (G_UNLIKELY (!priv->device_route_metrics)) {
const GHashTable *h;
const NMConfigDeviceStateData *device_state;
priv->device_route_metrics = g_hash_table_new_full (_device_route_metric_data_by_ifindex_hash,
_device_route_metric_data_by_ifindex_equal,
NULL,
nm_g_slice_free_fcn (DeviceRouteMetricData));
cleaned = TRUE;
/* we need to pre-populate the cache for all (still existing) devices from the state-file */
h = nm_config_device_state_get_all (priv->config);
if (!h)
goto initited;
g_hash_table_iter_init (&h_iter, (GHashTable *) h);
while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &device_state)) {
if (!device_state->route_metric_default_effective)
continue;
if (!nm_platform_link_get (priv->platform, device_state->ifindex)) {
/* we have the entry in the state file, but (currently) no such
* ifindex exists in platform. Most likely the entry is obsolete,
* hence we skip it. */
continue;
}
if (!g_hash_table_add (priv->device_route_metrics,
_device_route_metric_data_new (device_state->ifindex,
device_state->route_metric_default_aspired,
device_state->route_metric_default_effective)))
nm_assert_not_reached ();
}
}
initited:
data_lookup.ifindex = ifindex;
data = g_hash_table_lookup (priv->device_route_metrics, &data_lookup);
if (data)
goto out;
if (lookup_only)
return 0;
if (!cleaned) {
/* get the number of all links in the platform cache. */
all_links_head = nm_platform_lookup_all (priv->platform,
NMP_CACHE_ID_TYPE_OBJECT_TYPE,
nmp_object_stackinit_id_link (&links_needle, 1));
n_links = all_links_head ? all_links_head->len : 0;
/* on systems where a lot of devices are created and go away, the index contains
* a lot of stale entries. We must from time to time clean them up.
*
* Do do this cleanup, whenever we have more enties then 2 times the number of links. */
if (G_UNLIKELY (g_hash_table_size (priv->device_route_metrics) > NM_MAX (20, n_links * 2))) {
/* from time to time, we need to do some house-keeping and prune stale entries.
* Otherwise, on a system where interfaces frequently come and go (docker), we
* keep growing this cache for ifindexes that no longer exist. */
g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
if (!nm_platform_link_get (priv->platform, d2->ifindex))
g_hash_table_iter_remove (&h_iter);
}
cleaned = TRUE;
}
}
data = _device_route_metric_data_new (ifindex, nm_device_get_route_metric_default (device_type), 0);
/* unfortunately, there is no stright forward way to lookup all reserved metrics.
* Note, that we don't only have to know which metrics are currently reserved,
* but also, which metrics are now seemingly un-used but caused another reserved
* metric to be bumped. Hence, the naive O(n^2) search :(
*
* Well, technically, since we limit bumping the metric to 50, this entire
* loop runs at most 50 times, so it's still O(n). Let's just say, it's not
* very efficient. */
again:
g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
if ( data->effective_metric < d2->aspired_metric
|| data->effective_metric > d2->effective_metric) {
/* no overlap. Skip. */
continue;
}
if ( !cleaned
&& !nm_platform_link_get (priv->platform, d2->ifindex)) {
/* the metric seems taken, but there is no such interface. This entry
* is stale, forget about it. */
g_hash_table_iter_remove (&h_iter);
continue;
}
if (d2->effective_metric == G_MAXUINT32) {
/* we cannot bump the metric any further. Done.
*
* Actually, this can currently not happen because the aspired_metric
* are small numbers and we limit the bumping to 50. Still, for
* completeness... */
data->effective_metric = G_MAXUINT32;
break;
}
if (d2->effective_metric - data->aspired_metric >= 50) {
/* as one active interface reserves an entire range of metrics
* (from aspired_metric to effective_metric), that means if you
* alternatingly activate two interfaces, their metric will
* bump each other.
*
* Limit this, bump the metric at most 50 points. */
data->effective_metric = data->aspired_metric + 50;
break;
}
/* bump the metric, and search again. */
data->effective_metric = d2->effective_metric + 1;
goto again;
}
_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d reserves metric %u (aspired %u)",
data->ifindex, data->effective_metric, data->aspired_metric);
if (!g_hash_table_add (priv->device_route_metrics, data))
nm_assert_not_reached ();
out:
NM_SET_OUT (out_aspired_metric, data->aspired_metric);
return data->effective_metric;
}
guint32
nm_manager_device_route_metric_reserve (NMManager *self,
int ifindex,
NMDeviceType device_type)
{
guint32 metric;
metric = _device_route_metric_get (self, ifindex, device_type, FALSE, NULL);
nm_assert (metric != 0);
return metric;
}
void
nm_manager_device_route_metric_clear (NMManager *self,
int ifindex)
{
NMManagerPrivate *priv;
DeviceRouteMetricData data_lookup;
priv = NM_MANAGER_GET_PRIVATE (self);
if (!priv->device_route_metrics)
return;
data_lookup.ifindex = ifindex;
if (g_hash_table_remove (priv->device_route_metrics, &data_lookup)) {
_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d released",
ifindex);
}
}
/*****************************************************************************/
static void
_delete_volatile_connection_do (NMManager *self,
NMSettingsConnection *connection)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
return;
if (!nm_settings_has_connection (priv->settings, connection))
return;
if (active_connection_find (self,
connection,
NULL,
NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
NULL))
return;
_LOGD (LOGD_DEVICE, "volatile connection disconnected. Deleting connection '%s' (%s)",
nm_settings_connection_get_id (connection), nm_settings_connection_get_uuid (connection));
nm_settings_connection_delete (connection, FALSE);
}
/* Returns: whether to notify D-Bus of the removal or not */
static gboolean
active_connection_remove (NMManager *self, NMActiveConnection *active)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
gs_unref_object NMSettingsConnection *connection = NULL;
gboolean notify;
nm_assert (NM_IS_ACTIVE_CONNECTION (active));
nm_assert (c_list_contains (&priv->active_connections_lst_head, &active->active_connections_lst));
notify = nm_dbus_object_is_exported (NM_DBUS_OBJECT (active));
c_list_unlink (&active->active_connections_lst);
g_signal_emit (self, signals[ACTIVE_CONNECTION_REMOVED], 0, active);
g_signal_handlers_disconnect_by_func (active, active_connection_state_changed, self);
g_signal_handlers_disconnect_by_func (active, active_connection_default_changed, self);
g_signal_handlers_disconnect_by_func (active, active_connection_parent_active, self);
connection = nm_g_object_ref (nm_active_connection_get_settings_connection (active));
nm_dbus_object_clear_and_unexport (&active);
if (connection)
_delete_volatile_connection_do (self, connection);
return notify;
}
static gboolean
_active_connection_cleanup (gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMActiveConnection *ac, *ac_safe;
priv->ac_cleanup_id = 0;
g_object_freeze_notify (G_OBJECT (self));
c_list_for_each_entry_safe (ac, ac_safe, &priv->active_connections_lst_head, active_connections_lst) {
if (nm_active_connection_get_state (ac) == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
if (active_connection_remove (self, ac))
_notify (self, PROP_ACTIVE_CONNECTIONS);
}
}
g_object_thaw_notify (G_OBJECT (self));
return FALSE;
}
static void
active_connection_state_changed (NMActiveConnection *active,
GParamSpec *pspec,
NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMActiveConnectionState state;
NMSettingsConnection *con;
state = nm_active_connection_get_state (active);
if (state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
/* Destroy active connections from an idle handler to ensure that
* their last property change notifications go out, which wouldn't
* happen if we destroyed them immediately when their state was set
* to DEACTIVATED.
*/
if (!priv->ac_cleanup_id)
priv->ac_cleanup_id = g_idle_add (_active_connection_cleanup, self);
con = nm_active_connection_get_settings_connection (active);
if (con)
g_object_set_qdata (G_OBJECT (con), autoconnect_root_quark (), NULL);
}
nm_manager_update_state (self);
}
static void
active_connection_default_changed (NMActiveConnection *active,
GParamSpec *pspec,
NMManager *self)
{
nm_manager_update_state (self);
}
/**
* active_connection_add():
* @self: the #NMManager
* @active: the #NMActiveConnection to manage
*
* Begins to track and manage @active. Increases the refcount of @active.
*/
static void
active_connection_add (NMManager *self,
NMActiveConnection *active)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
nm_assert (NM_IS_ACTIVE_CONNECTION (active));
nm_assert (!c_list_is_linked (&active->active_connections_lst));
c_list_link_front (&priv->active_connections_lst_head, &active->active_connections_lst);
g_object_ref (active);
g_signal_connect (active,
"notify::" NM_ACTIVE_CONNECTION_STATE,
G_CALLBACK (active_connection_state_changed),
self);
g_signal_connect (active,
"notify::" NM_ACTIVE_CONNECTION_DEFAULT,
G_CALLBACK (active_connection_default_changed),
self);
g_signal_connect (active,
"notify::" NM_ACTIVE_CONNECTION_DEFAULT6,
G_CALLBACK (active_connection_default_changed),
self);
if (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (active)))
nm_dbus_object_export (NM_DBUS_OBJECT (active));
g_signal_emit (self, signals[ACTIVE_CONNECTION_ADDED], 0, active);
_notify (self, PROP_ACTIVE_CONNECTIONS);
}
const CList *
nm_manager_get_active_connections (NMManager *manager)
{
return &NM_MANAGER_GET_PRIVATE (manager)->active_connections_lst_head;
}
static NMActiveConnection *
active_connection_find (NMManager *self,
NMSettingsConnection *sett_conn,
const char *uuid,
NMActiveConnectionState max_state /* candidates in state @max_state will be found */,
GPtrArray **out_all_matching)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMActiveConnection *ac;
NMActiveConnection *best_ac = NULL;
GPtrArray *all = NULL;
nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
nm_assert (!out_all_matching || !*out_all_matching);
c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
NMSettingsConnection *ac_conn;
ac_conn = nm_active_connection_get_settings_connection (ac);
if ( sett_conn
&& sett_conn != ac_conn)
continue;
if ( uuid
&& !nm_streq0 (uuid, nm_settings_connection_get_uuid (ac_conn)))
continue;
if (nm_active_connection_get_state (ac) > max_state)
continue;
if (!out_all_matching)
return ac;
if (!best_ac) {
best_ac = ac;
continue;
}
if (!all) {
all = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (all, g_object_ref (best_ac));
}
g_ptr_array_add (all, g_object_ref (ac));
}
if (!best_ac)
return NULL;
/* as an optimization, we only allocate out_all_matching, if there are more
* than one result. If there is only one result, we only return the single
* element and don't bother allocating an array. That's the common case.
*
* Also, in case we have multiple results, we return the *first* one
* as @best_ac. */
nm_assert ( !all
|| ( all->len >= 2
&& all->pdata[0] == best_ac));
*out_all_matching = all;
return best_ac;
}
static NMActiveConnection *
active_connection_find_by_connection (NMManager *self,
NMSettingsConnection *sett_conn,
NMConnection *connection,
NMActiveConnectionState max_state,
GPtrArray **out_all_matching)
{
nm_assert (NM_IS_MANAGER (self));
nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
nm_assert (!connection || NM_IS_CONNECTION (connection));
nm_assert (sett_conn || connection);
nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));
/* Depending on whether connection is a settings connection,
* either lookup by object-identity of @connection, or compare the UUID */
return active_connection_find (self,
sett_conn,
sett_conn ? NULL : nm_connection_get_uuid (connection),
max_state,
out_all_matching);
}
typedef struct {
NMManager *self;
gboolean for_auto_activation;
} GetActivatableConnectionsFilterData;
static gboolean
_get_activatable_connections_filter (NMSettings *settings,
NMSettingsConnection *sett_conn,
gpointer user_data)
{
const GetActivatableConnectionsFilterData *d = user_data;
NMConnectionMultiConnect multi_connect;
if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
return FALSE;
multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn));
if ( multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE
|| ( multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE
&& !d->for_auto_activation))
return TRUE;
/* the connection is activatable, if it has no active-connections that are in state
* activated, activating, or waiting to be activated. */
return !active_connection_find (d->self,
sett_conn,
NULL,
NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
NULL);
}
NMSettingsConnection **
nm_manager_get_activatable_connections (NMManager *manager,
gboolean for_auto_activation,
gboolean sort,
guint *out_len)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
const GetActivatableConnectionsFilterData d = {
.self = manager,
.for_auto_activation = for_auto_activation,
};
return nm_settings_get_connections_clone (priv->settings, out_len,
_get_activatable_connections_filter,
(gpointer) &d,
sort ? nm_settings_connection_cmp_autoconnect_priority_p_with_data : NULL,
NULL);
}
static NMActiveConnection *
active_connection_get_by_path (NMManager *self, const char *path)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMActiveConnection *ac;
ac = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
path);
if ( !ac
|| !NM_IS_ACTIVE_CONNECTION (ac)
|| c_list_is_empty (&ac->active_connections_lst))
return NULL;
nm_assert (c_list_contains (&priv->active_connections_lst_head, &ac->active_connections_lst));
return ac;
}
/*****************************************************************************/
static void
_config_changed_cb (NMConfig *config, NMConfigData *config_data, NMConfigChangeFlags changes, NMConfigData *old_data, NMManager *self)
{
g_object_freeze_notify (G_OBJECT (self));
if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG))
_notify (self, PROP_GLOBAL_DNS_CONFIGURATION);
if (!nm_streq0 (nm_config_data_get_connectivity_uri (config_data),
nm_config_data_get_connectivity_uri (old_data))) {
if ((!nm_config_data_get_connectivity_uri (config_data)) != (!nm_config_data_get_connectivity_uri (old_data)))
_notify (self, PROP_CONNECTIVITY_CHECK_AVAILABLE);
_notify (self, PROP_CONNECTIVITY_CHECK_URI);
}
g_object_thaw_notify (G_OBJECT (self));
}
static void
_reload_auth_cb (NMAuthChain *chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
GError *ret_error = NULL;
NMAuthCallResult result;
guint32 flags;
NMAuthSubject *subject;
char s_buf[60];
NMConfigChangeFlags reload_type = NM_CONFIG_CHANGE_NONE;
nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
c_list_unlink (nm_auth_chain_parent_lst_list (chain));
flags = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "flags"));
subject = nm_auth_chain_get_subject (chain);
result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_RELOAD);
if (result != NM_AUTH_CALL_RESULT_YES) {
ret_error = g_error_new_literal (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
"Not authorized to reload configuration");
} else {
if (NM_FLAGS_ANY (flags, ~NM_MANAGER_RELOAD_FLAG_ALL)) {
/* invalid flags */
} else if (flags == 0)
reload_type = NM_CONFIG_CHANGE_CAUSE_SIGHUP;
else {
if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_CONF))
reload_type |= NM_CONFIG_CHANGE_CAUSE_CONF;
if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_DNS_RC))
reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_RC;
if (NM_FLAGS_HAS (flags, NM_MANAGER_RELOAD_FLAG_DNS_FULL))
reload_type |= NM_CONFIG_CHANGE_CAUSE_DNS_FULL;
}
if (reload_type == NM_CONFIG_CHANGE_NONE) {
ret_error = g_error_new_literal (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_INVALID_ARGUMENTS,
"Invalid flags for reload");
}
}
nm_audit_log_control_op (NM_AUDIT_OP_RELOAD,
nm_sprintf_buf (s_buf, "%u", flags),
ret_error == NULL, subject,
ret_error ? ret_error->message : NULL);
if (ret_error) {
g_dbus_method_invocation_take_error (context, ret_error);
return;
}
nm_config_reload (priv->config, reload_type, TRUE);
g_dbus_method_invocation_return_value (context, NULL);
}
static void
impl_manager_reload (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMManager *self = NM_MANAGER (obj);
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMAuthChain *chain;
guint32 flags;
g_variant_get (parameters, "(u)", &flags);
chain = nm_auth_chain_new_context (invocation, _reload_auth_cb, self);
if (!chain) {
g_dbus_method_invocation_return_error_literal (invocation,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
return;
}
c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
nm_auth_chain_set_data (chain, "flags", GUINT_TO_POINTER (flags), NULL);
nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_RELOAD, TRUE);
}
/*****************************************************************************/
NMDevice *
nm_manager_get_device_by_path (NMManager *self, const char *path)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
g_return_val_if_fail (path, NULL);
device = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
path);
if ( !device
|| !NM_IS_DEVICE (device)
|| c_list_is_empty (&device->devices_lst))
return NULL;
nm_assert (c_list_contains (&priv->devices_lst_head, &device->devices_lst));
return device;
}
NMDevice *
nm_manager_get_device_by_ifindex (NMManager *self, int ifindex)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
if (ifindex > 0) {
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
if (nm_device_get_ifindex (device) == ifindex)
return device;
}
}
return NULL;
}
static NMDevice *
find_device_by_permanent_hw_addr (NMManager *self, const char *hwaddr)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
const char *device_addr;
guint8 hwaddr_bin[NM_UTILS_HWADDR_LEN_MAX];
gsize hwaddr_len;
g_return_val_if_fail (hwaddr != NULL, NULL);
if (!_nm_utils_hwaddr_aton (hwaddr, hwaddr_bin, sizeof (hwaddr_bin), &hwaddr_len))
return NULL;
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
device_addr = nm_device_get_permanent_hw_address (device);
if ( device_addr
&& nm_utils_hwaddr_matches (hwaddr_bin, hwaddr_len, device_addr, -1))
return device;
}
return NULL;
}
static NMDevice *
find_device_by_ip_iface (NMManager *self, const char *iface)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
g_return_val_if_fail (iface, NULL);
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
if ( nm_device_is_real (device)
&& nm_streq0 (nm_device_get_ip_iface (device), iface))
return device;
}
return NULL;
}
/**
* find_device_by_iface:
* @self: the #NMManager
* @iface: the device interface to find
* @connection: a connection to ensure the returned device is compatible with
* @slave: a slave connection to ensure a master is compatible with
*
* Finds a device by interface name, preferring realized devices. If @slave
* is given, this function will only return master devices and will ensure
* @slave, when activated, can be a slave of the returned master device. If
* @connection is given, this function will only consider devices that are
* compatible with @connection.
*
* Returns: the matching #NMDevice
*/
static NMDevice *
find_device_by_iface (NMManager *self,
const char *iface,
NMConnection *connection,
NMConnection *slave)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *fallback = NULL;
NMDevice *candidate;
g_return_val_if_fail (iface != NULL, NULL);
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
if (strcmp (nm_device_get_iface (candidate), iface))
continue;
if (connection && !nm_device_check_connection_compatible (candidate, connection, NULL))
continue;
if (slave) {
if (!nm_device_is_master (candidate))
continue;
if (!nm_device_check_slave_connection_compatible (candidate, slave))
continue;
}
if (nm_device_is_real (candidate))
return candidate;
else if (!fallback)
fallback = candidate;
}
return fallback;
}
static gboolean
manager_sleeping (NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
if (priv->sleeping || !priv->net_enabled)
return TRUE;
return FALSE;
}
static const char *
_nm_state_to_string (NMState state)
{
switch (state) {
case NM_STATE_ASLEEP:
return "ASLEEP";
case NM_STATE_DISCONNECTED:
return "DISCONNECTED";
case NM_STATE_DISCONNECTING:
return "DISCONNECTING";
case NM_STATE_CONNECTING:
return "CONNECTING";
case NM_STATE_CONNECTED_LOCAL:
return "CONNECTED_LOCAL";
case NM_STATE_CONNECTED_SITE:
return "CONNECTED_SITE";
case NM_STATE_CONNECTED_GLOBAL:
return "CONNECTED_GLOBAL";
case NM_STATE_UNKNOWN:
default:
return "UNKNOWN";
}
}
static NMState
find_best_device_state (NMManager *manager)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
NMState best_state = NM_STATE_DISCONNECTED;
NMActiveConnection *ac;
c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
NMActiveConnectionState ac_state = nm_active_connection_get_state (ac);
switch (ac_state) {
case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
if (nm_active_connection_get_default (ac, AF_UNSPEC)) {
if (priv->connectivity_state == NM_CONNECTIVITY_FULL)
return NM_STATE_CONNECTED_GLOBAL;
best_state = NM_STATE_CONNECTED_SITE;
} else {
if (best_state < NM_STATE_CONNECTING)
best_state = NM_STATE_CONNECTED_LOCAL;
}
break;
case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
if (!NM_IN_SET (nm_active_connection_get_activation_type (ac),
NM_ACTIVATION_TYPE_EXTERNAL,
NM_ACTIVATION_TYPE_ASSUME)) {
if (best_state != NM_STATE_CONNECTED_GLOBAL)
best_state = NM_STATE_CONNECTING;
}
break;
case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING:
if (!NM_IN_SET (nm_active_connection_get_activation_type (ac),
NM_ACTIVATION_TYPE_EXTERNAL,
NM_ACTIVATION_TYPE_ASSUME)) {
if (best_state < NM_STATE_DISCONNECTING)
best_state = NM_STATE_DISCONNECTING;
}
break;
default:
break;
}
}
return best_state;
}
static void
nm_manager_update_metered (NMManager *self)
{
NMManagerPrivate *priv;
NMDevice *device;
NMMetered value = NM_METERED_UNKNOWN;
g_return_if_fail (NM_IS_MANAGER (self));
priv = NM_MANAGER_GET_PRIVATE (self);
if (priv->primary_connection) {
device = nm_active_connection_get_device (priv->primary_connection);
if (device)
value = nm_device_get_metered (device);
}
if (value != priv->metered) {
priv->metered = value;
_LOGD (LOGD_CORE, "new metered value: %d", (int) priv->metered);
_notify (self, PROP_METERED);
}
}
NMMetered
nm_manager_get_metered (NMManager *self)
{
g_return_val_if_fail (NM_IS_MANAGER (self), NM_METERED_UNKNOWN);
return NM_MANAGER_GET_PRIVATE (self)->metered;
}
static void
nm_manager_update_state (NMManager *self)
{
NMManagerPrivate *priv;
NMState new_state = NM_STATE_DISCONNECTED;
g_return_if_fail (NM_IS_MANAGER (self));
priv = NM_MANAGER_GET_PRIVATE (self);
if (manager_sleeping (self))
new_state = NM_STATE_ASLEEP;
else
new_state = find_best_device_state (self);
if ( new_state >= NM_STATE_CONNECTED_LOCAL
&& priv->connectivity_state == NM_CONNECTIVITY_FULL) {
new_state = NM_STATE_CONNECTED_GLOBAL;
}
if (priv->state == new_state)
return;
priv->state = new_state;
_LOGI (LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string (new_state));
_notify (self, PROP_STATE);
nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
&interface_info_manager,
&signal_info_state_changed,
"(u)",
(guint32) priv->state);
}
static void
manager_device_state_changed (NMDevice *device,
NMDeviceState new_state,
NMDeviceState old_state,
NMDeviceStateReason reason,
gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
if ( old_state == NM_DEVICE_STATE_UNMANAGED
&& new_state > NM_DEVICE_STATE_UNMANAGED)
retry_connections_for_parent_device (self, device);
if (NM_IN_SET (new_state,
NM_DEVICE_STATE_UNMANAGED,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_DISCONNECTED,
NM_DEVICE_STATE_PREPARE,
NM_DEVICE_STATE_FAILED))
_notify (self, PROP_ACTIVE_CONNECTIONS);
if (NM_IN_SET (new_state,
NM_DEVICE_STATE_UNMANAGED,
NM_DEVICE_STATE_DISCONNECTED,
NM_DEVICE_STATE_ACTIVATED)) {
nm_manager_write_device_state (self, device, NULL);
G_STATIC_ASSERT_EXPR (DEVICE_STATE_PRUNE_RATELIMIT_MAX < G_MAXUINT8);
if (priv->device_state_prune_ratelimit_count++ > DEVICE_STATE_PRUNE_RATELIMIT_MAX) {
/* We write the device state to /run. The state files are named after the
* ifindex (which is assumed to be unique and not repeat -- in practice
* it may repeat). So from time to time, we prune device state files
* for interfaces that no longer exist.
*
* Otherwise, the files might pile up if you create (and destroy) a large
* number of software devices. */
priv->device_state_prune_ratelimit_count = 0;
nm_config_device_state_prune_stale (NULL, priv->platform);
}
}
if (NM_IN_SET (new_state,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_DISCONNECTED))
nm_settings_device_added (priv->settings, device);
}
static void device_has_pending_action_changed (NMDevice *device,
GParamSpec *pspec,
NMManager *self);
static void
check_if_startup_complete (NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
const char *reason;
if (!priv->startup)
return;
if (!priv->devices_inited)
return;
reason = nm_settings_get_startup_complete_blocked_reason (priv->settings);
if (reason) {
_LOGD (LOGD_CORE, "startup complete is waiting for connection (%s)",
reason);
return;
}
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
reason = nm_device_has_pending_action_reason (device);
if (reason) {
_LOGD (LOGD_CORE, "startup complete is waiting for device '%s' (%s)",
nm_device_get_iface (device),
reason);
return;
}
}
_LOGI (LOGD_CORE, "startup complete");
priv->startup = FALSE;
/* we no longer care about these signals. Startup-complete only
* happens once. */
g_signal_handlers_disconnect_by_func (priv->settings, G_CALLBACK (settings_startup_complete_changed), self);
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
g_signal_handlers_disconnect_by_func (device,
G_CALLBACK (device_has_pending_action_changed),
self);
}
_notify (self, PROP_STARTUP);
if (nm_config_get_configure_and_quit (priv->config))
g_signal_emit (self, signals[CONFIGURE_QUIT], 0);
}
static void
device_has_pending_action_changed (NMDevice *device,
GParamSpec *pspec,
NMManager *self)
{
check_if_startup_complete (self);
}
static void
settings_startup_complete_changed (NMSettings *settings,
GParamSpec *pspec,
NMManager *self)
{
check_if_startup_complete (self);
}
static void
_parent_notify_changed (NMManager *self,
NMDevice *device,
gboolean device_removed)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *candidate;
nm_assert (NM_IS_DEVICE (device));
again:
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
if (nm_device_parent_notify_changed (candidate, device, device_removed)) {
/* in the unlikely event that this changes anything, we start iterating
* again, to be sure that the device list is up-to-date. */
goto again;
}
}
}
static gboolean
device_is_wake_on_lan (NMPlatform *platform, NMDevice *device)
{
int ifindex;
ifindex = nm_device_get_ip_ifindex (device);
if (ifindex <= 0)
return FALSE;
return nm_platform_link_get_wake_on_lan (platform, ifindex);
}
static void
remove_device (NMManager *self,
NMDevice *device,
gboolean quitting)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
gboolean unmanage = FALSE;
_LOG2D (LOGD_DEVICE, device, "removing device (managed %d, wol %d)",
nm_device_get_managed (device, FALSE),
device_is_wake_on_lan (priv->platform, device));
if (nm_device_get_managed (device, FALSE)) {
if (quitting) {
/* Leave configured if wo(w)lan and quitting */
if (device_is_wake_on_lan (priv->platform, device))
unmanage = FALSE;
else
unmanage = nm_device_unmanage_on_quit (device);
} else {
/* the device is already gone. Unmanage it. */
unmanage = TRUE;
}
if (unmanage) {
if (quitting)
nm_device_set_unmanaged_by_quitting (device);
else {
nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, TRUE, NM_DEVICE_STATE_REASON_REMOVED);
}
} else if (quitting && nm_config_get_configure_and_quit (priv->config) == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) {
nm_device_spawn_iface_helper (device);
}
}
g_signal_handlers_disconnect_matched (device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
nm_settings_device_removed (priv->settings, device, quitting);
c_list_unlink (&device->devices_lst);
_parent_notify_changed (self, device, TRUE);
if (nm_device_is_real (device)) {
gboolean unconfigure_ip_config = !quitting || unmanage;
/* When we don't unmanage the device on shutdown, we want to preserve the DNS
* configuration in resolv.conf. For that, we must leak the configuration
* in NMPolicy/NMDnsManager. We do that, by emitting the device-removed signal
* with device's ip-config object still uncleared. In that case, NMPolicy
* never learns to unconfigure the ip-config objects and does not remove them
* from DNS on shutdown (which is ugly, because we don't cleanup the memory
* properly).
*
* Control that by passing @unconfigure_ip_config. */
nm_device_removed (device, unconfigure_ip_config);
_emit_device_added_removed (self, device, FALSE);
} else {
/* unrealize() does not release a slave device from master and
* clear IP configurations, do it here */
nm_device_removed (device, TRUE);
}
g_signal_emit (self, signals[INTERNAL_DEVICE_REMOVED], 0, device);
_notify (self, PROP_ALL_DEVICES);
update_connectivity_value (self);
nm_dbus_object_clear_and_unexport (&device);
check_if_startup_complete (self);
}
static void
device_removed_cb (NMDevice *device, gpointer user_data)
{
remove_device (NM_MANAGER (user_data), device, FALSE);
}
NMState
nm_manager_get_state (NMManager *manager)
{
g_return_val_if_fail (NM_IS_MANAGER (manager), NM_STATE_UNKNOWN);
return NM_MANAGER_GET_PRIVATE (manager)->state;
}
/*****************************************************************************/
static NMDevice *
find_parent_device_for_connection (NMManager *self,
NMConnection *connection,
NMDeviceFactory *cached_factory,
const char **out_parent_spec)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDeviceFactory *factory;
const char *parent_name = NULL;
NMSettingsConnection *parent_connection;
NMDevice *parent, *first_compatible = NULL;
NMDevice *candidate;
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
NM_SET_OUT (out_parent_spec, NULL);
if (!cached_factory) {
factory = nm_device_factory_manager_find_factory_for_connection (connection);
if (!factory)
return NULL;
} else
factory = cached_factory;
parent_name = nm_device_factory_get_connection_parent (factory, connection);
if (!parent_name)
return NULL;
NM_SET_OUT (out_parent_spec, parent_name);
/* Try as an interface name of a parent device */
parent = find_device_by_iface (self, parent_name, NULL, NULL);
if (parent)
return parent;
/* Maybe a hardware address */
parent = find_device_by_permanent_hw_addr (self, parent_name);
if (parent)
return parent;
/* Maybe a connection UUID */
parent_connection = nm_settings_get_connection_by_uuid (priv->settings, parent_name);
if (!parent_connection)
return NULL;
/* Check if the parent connection is currently activated or is compatible
* with some known device.
*/
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
/* Unmanaged devices are not compatible with any connection */
if (!nm_device_get_managed (candidate, FALSE))
continue;
if (nm_device_get_settings_connection (candidate) == parent_connection)
return candidate;
if ( !first_compatible
&& nm_device_check_connection_compatible (candidate,
nm_settings_connection_get_connection (parent_connection),
NULL))
first_compatible = candidate;
}
return first_compatible;
}
/**
* nm_manager_get_connection_iface:
* @self: the #NMManager
* @connection: the #NMConnection to get the interface for
* @out_parent: on success, the parent device if any
* @out_parent_spec: on return, a string specifying the parent device
* in the connection. This can be a device name, a MAC address or a
* connection UUID.
* @error: an error if determining the virtual interface name failed
*
* Given @connection, returns the interface name that the connection
* would need to use when activated. %NULL is returned if the name
* is not specified in connection or a the name for a virtual device
* could not be generated.
*
* Returns: the expected interface name (caller takes ownership), or %NULL
*/
char *
nm_manager_get_connection_iface (NMManager *self,
NMConnection *connection,
NMDevice **out_parent,
const char **out_parent_spec,
GError **error)
{
NMDeviceFactory *factory;
char *iface = NULL;
NMDevice *parent = NULL;
NM_SET_OUT (out_parent, NULL);
NM_SET_OUT (out_parent_spec, NULL);
factory = nm_device_factory_manager_find_factory_for_connection (connection);
if (!factory) {
if (nm_streq0 (nm_connection_get_connection_type (connection), NM_SETTING_GENERIC_SETTING_NAME)) {
/* the generic type doesn't have a factory. */
goto return_ifname_fom_connection;
}
g_set_error (error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_FAILED,
"NetworkManager plugin for '%s' unavailable",
nm_connection_get_connection_type (connection));
return NULL;
}
if ( !out_parent
&& !NM_DEVICE_FACTORY_GET_CLASS (factory)->get_connection_iface) {
/* optimization. Shortcut lookup of the partent device. */
goto return_ifname_fom_connection;
}
parent = find_parent_device_for_connection (self, connection, factory, out_parent_spec);
iface = nm_device_factory_get_connection_iface (factory,
connection,
parent ? nm_device_get_ip_iface (parent) : NULL,
error);
if (!iface)
return NULL;
if (out_parent)
*out_parent = parent;
return iface;
return_ifname_fom_connection:
iface = g_strdup (nm_connection_get_interface_name (connection));
if (!iface) {
g_set_error (error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_FAILED,
"failed to determine interface name: error determine name for %s",
nm_connection_get_connection_type (connection));
}
return iface;
}
/**
* nm_manager_iface_for_uuid:
* @self: the #NMManager
* @uuid: the connection uuid
*
* Gets a link name for the given UUID. Useful for the settings plugins that
* wish to write configuration files compatible with tooling that can't
* interpret our UUIDs.
*
* Returns: An interface name; %NULL if none matches
*/
const char *
nm_manager_iface_for_uuid (NMManager *self, const char *uuid)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMSettingsConnection *sett_conn;
sett_conn = nm_settings_get_connection_by_uuid (priv->settings, uuid);
if (!sett_conn)
return NULL;
return nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn));
}
NMDevice *
nm_manager_get_device (NMManager *self, const char *ifname, NMDeviceType device_type)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
g_return_val_if_fail (ifname, NULL);
g_return_val_if_fail (device_type != NM_DEVICE_TYPE_UNKNOWN, NULL);
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
if ( nm_device_get_device_type (device) == device_type
&& nm_streq0 (nm_device_get_iface (device), ifname))
return device;
}
return NULL;
}
gboolean
nm_manager_remove_device (NMManager *self, const char *ifname, NMDeviceType device_type)
{
NMDevice *d;
d = nm_manager_get_device (self, ifname, device_type);
if (!d)
return FALSE;
remove_device (self, d, FALSE);
return TRUE;
}
/**
* system_create_virtual_device:
* @self: the #NMManager
* @connection: the connection which might require a virtual device
*
* If @connection requires a virtual device and one does not yet exist for it,
* creates that device.
*
* Returns: A #NMDevice that was just realized; %NULL if none
*/
static NMDevice *
system_create_virtual_device (NMManager *self, NMConnection *connection)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDeviceFactory *factory;
gs_free NMSettingsConnection **connections = NULL;
guint i;
gs_free char *iface = NULL;
const char *parent_spec;
NMDevice *device = NULL, *parent = NULL;
NMDevice *dev_candidate;
GError *error = NULL;
NMLogLevel log_level;
g_return_val_if_fail (NM_IS_MANAGER (self), NULL);
g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
iface = nm_manager_get_connection_iface (self, connection, &parent, &parent_spec, &error);
if (!iface) {
_LOG3D (LOGD_DEVICE, connection, "can't get a name of a virtual device: %s",
error->message);
g_error_free (error);
return NULL;
}
if (parent_spec && !parent) {
/* parent is not ready, wait */
return NULL;
}
/* See if there's a device that is already compatible with this connection */
c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) {
if (nm_device_check_connection_compatible (dev_candidate, connection, NULL)) {
if (nm_device_is_real (dev_candidate)) {
_LOG3D (LOGD_DEVICE, connection, "already created virtual interface name %s",
iface);
return NULL;
}
device = dev_candidate;
break;
}
}
if (!device) {
/* No matching device found. Proceed creating a new one. */
factory = nm_device_factory_manager_find_factory_for_connection (connection);
if (!factory) {
_LOG3E (LOGD_DEVICE, connection, "(%s) NetworkManager plugin for '%s' unavailable",
iface,
nm_connection_get_connection_type (connection));
return NULL;
}
device = nm_device_factory_create_device (factory, iface, NULL, connection, NULL, &error);
if (!device) {
_LOG3W (LOGD_DEVICE, connection, "factory can't create the device: %s",
error->message);
g_error_free (error);
return NULL;
}
_LOG3D (LOGD_DEVICE, connection, "create virtual device %s",
nm_device_get_iface (device));
if (!add_device (self, device, &error)) {
_LOG3W (LOGD_DEVICE, connection, "can't register the device with manager: %s",
error->message);
g_error_free (error);
g_object_unref (device);
return NULL;
}
/* Add device takes a reference that NMManager still owns, so it's
* safe to unref here and still return @device.
*/
g_object_unref (device);
}
if (!nm_device_check_unrealized_device_managed (device)) {
_LOG3D (LOGD_DEVICE, connection,
"skip activation because virtual device '%s' is unmanaged",
nm_device_get_iface (device));
return device;
}
if (!find_master (self,
connection,
device,
NULL,
NULL,
NULL,
&error)) {
_LOG3D (LOGD_DEVICE, connection,
"skip activation: %s",
error->message);
g_error_free (error);
return device;
}
/* Create backing resources if the device has any autoconnect connections */
connections = nm_settings_get_connections_clone (priv->settings, NULL,
NULL, NULL,
nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
for (i = 0; connections[i]; i++) {
NMConnection *candidate = nm_settings_connection_get_connection (connections[i]);
NMSettingConnection *s_con;
if (!nm_device_check_connection_compatible (device, candidate, NULL))
continue;
s_con = nm_connection_get_setting_connection (candidate);
g_assert (s_con);
if ( !nm_setting_connection_get_autoconnect (s_con)
|| nm_settings_connection_autoconnect_is_blocked (connections[i]))
continue;
/* Create any backing resources the device needs */
if (!nm_device_create_and_realize (device, connection, parent, &error)) {
log_level = g_error_matches (error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_MISSING_DEPENDENCIES)
? LOGL_DEBUG
: LOGL_ERR;
_NMLOG3 (log_level, LOGD_DEVICE, connection,
"couldn't create the device: %s",
error->message);
g_error_free (error);
return NULL;
}
retry_connections_for_parent_device (self, device);
break;
}
return device;
}
static void
retry_connections_for_parent_device (NMManager *self, NMDevice *device)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
gs_free NMSettingsConnection **connections = NULL;
guint i;
g_return_if_fail (device);
connections = nm_settings_get_connections_clone (priv->settings, NULL,
NULL, NULL,
nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
for (i = 0; connections[i]; i++) {
NMSettingsConnection *sett_conn = connections[i];
NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
gs_free_error GError *error = NULL;
gs_free char *ifname = NULL;
NMDevice *parent;
parent = find_parent_device_for_connection (self, connection, NULL, NULL);
if (parent == device) {
/* Only try to activate devices that don't already exist */
ifname = nm_manager_get_connection_iface (self, connection, &parent, NULL, &error);
if (ifname) {
if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname))
connection_changed (self, sett_conn);
}
}
}
}
static void
connection_changed (NMManager *self,
NMSettingsConnection *sett_conn)
{
NMConnection *connection;
NMDevice *device;
if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
return;
connection = nm_settings_connection_get_connection (sett_conn);
if (!nm_connection_is_virtual (connection))
return;
device = system_create_virtual_device (self, connection);
if (!device)
return;
/* Maybe the device that was created was needed by some other
* connection's device (parent of a VLAN). Let the connections
* can use the newly created device as a parent know. */
retry_connections_for_parent_device (self, device);
}
static void
connection_added_cb (NMSettings *settings,
NMSettingsConnection *sett_conn,
NMManager *self)
{
connection_changed (self, sett_conn);
}
static void
connection_updated_cb (NMSettings *settings,
NMSettingsConnection *sett_conn,
guint update_reason_u,
NMManager *self)
{
connection_changed (self, sett_conn);
}
/*****************************************************************************/
static void
_delete_volatile_connection_all (NMManager *self, gboolean do_delete)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMCListElem *elem;
while ((elem = c_list_first_entry (&priv->delete_volatile_connection_lst_head, NMCListElem, lst))) {
gs_unref_object NMSettingsConnection *connection = NULL;
connection = nm_c_list_elem_free_steal (elem);
if (do_delete)
_delete_volatile_connection_do (self, connection);
}
}
static gboolean
_delete_volatile_connection_cb (gpointer user_data)
{
NMManager *self = user_data;
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
priv->delete_volatile_connection_idle_id = 0;
_delete_volatile_connection_all (self, TRUE);
return G_SOURCE_REMOVE;
}
static void
connection_flags_changed (NMSettings *settings,
NMSettingsConnection *connection,
gpointer user_data)
{
NMManager *self = user_data;
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
return;
if (active_connection_find (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_DEACTIVATED, NULL)) {
/* the connection still has an active-connection. It will be purged
* when the active connection(s) get(s) removed. */
return;
}
c_list_link_tail (&priv->delete_volatile_connection_lst_head,
&nm_c_list_elem_new_stale (g_object_ref (connection))->lst);
if (!priv->delete_volatile_connection_idle_id)
priv->delete_volatile_connection_idle_id = g_idle_add (_delete_volatile_connection_cb, self);
}
/*****************************************************************************/
static void
system_unmanaged_devices_changed_cb (NMSettings *settings,
GParamSpec *pspec,
gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
nm_device_set_unmanaged_by_user_settings (device);
}
static void
hostname_changed_cb (NMHostnameManager *hostname_manager,
GParamSpec *pspec,
NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
const char *hostname;
hostname = nm_hostname_manager_get_hostname (priv->hostname_manager);
nm_dispatcher_call_hostname (NULL, NULL, NULL);
nm_dhcp_manager_set_default_hostname (nm_dhcp_manager_get (), hostname);
}
/*****************************************************************************/
/* General NMManager stuff */
/*****************************************************************************/
static gboolean
radio_enabled_for_rstate (RadioState *rstate, gboolean check_changeable)
{
gboolean enabled;
enabled = rstate->user_enabled && rstate->hw_enabled;
if (check_changeable)
enabled &= rstate->sw_enabled;
return enabled;
}
static gboolean
radio_enabled_for_type (NMManager *self, RfKillType rtype, gboolean check_changeable)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
return radio_enabled_for_rstate (&priv->radio_states[rtype], check_changeable);
}
static void
manager_update_radio_enabled (NMManager *self,
RadioState *rstate,
gboolean enabled)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
/* Do nothing for radio types not yet implemented */
if (!rstate->prop)
return;
g_object_notify (G_OBJECT (self), rstate->prop);
/* Don't touch devices if asleep/networking disabled */
if (manager_sleeping (self))
return;
/* enable/disable wireless devices as required */
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
if (nm_device_get_rfkill_type (device) == rstate->rtype) {
_LOG2D (LOGD_RFKILL, device, "rfkill: setting radio %s", enabled ? "enabled" : "disabled");
nm_device_set_enabled (device, enabled);
}
}
}
static void
update_rstate_from_rfkill (NMRfkillManager *rfkill_mgr, RadioState *rstate)
{
switch (nm_rfkill_manager_get_rfkill_state (rfkill_mgr, rstate->rtype)) {
case RFKILL_UNBLOCKED:
rstate->sw_enabled = TRUE;
rstate->hw_enabled = TRUE;
break;
case RFKILL_SOFT_BLOCKED:
rstate->sw_enabled = FALSE;
rstate->hw_enabled = TRUE;
break;
case RFKILL_HARD_BLOCKED:
rstate->sw_enabled = FALSE;
rstate->hw_enabled = FALSE;
break;
default:
g_warn_if_reached ();
break;
}
}
static void
manager_rfkill_update_one_type (NMManager *self,
RadioState *rstate,
RfKillType rtype)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
gboolean old_enabled, new_enabled, old_rfkilled, new_rfkilled, old_hwe;
old_enabled = radio_enabled_for_rstate (rstate, TRUE);
old_rfkilled = rstate->hw_enabled && rstate->sw_enabled;
old_hwe = rstate->hw_enabled;
/* recheck kernel rfkill state */
update_rstate_from_rfkill (priv->rfkill_mgr, rstate);
/* Print out all states affecting device enablement */
if (rstate->desc) {
_LOGD (LOGD_RFKILL, "rfkill: %s hw-enabled %d sw-enabled %d",
rstate->desc, rstate->hw_enabled, rstate->sw_enabled);
}
/* Log new killswitch state */
new_rfkilled = rstate->hw_enabled && rstate->sw_enabled;
if (old_rfkilled != new_rfkilled) {
_LOGI (LOGD_RFKILL, "rfkill: %s now %s by radio killswitch",
rstate->desc,
new_rfkilled ? "enabled" : "disabled");
}
/* Send out property changed signal for HW enabled */
if (rstate->hw_enabled != old_hwe) {
if (rstate->hw_prop)
g_object_notify (G_OBJECT (self), rstate->hw_prop);
}
/* And finally update the actual device radio state itself; respect the
* daemon state here because this is never called from user-triggered
* radio changes and we only want to ignore the daemon enabled state when
* handling user radio change requests.
*/
new_enabled = radio_enabled_for_rstate (rstate, TRUE);
if (new_enabled != old_enabled)
manager_update_radio_enabled (self, rstate, new_enabled);
}
static void
nm_manager_rfkill_update (NMManager *self, RfKillType rtype)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
guint i;
if (rtype != RFKILL_TYPE_UNKNOWN)
manager_rfkill_update_one_type (self, &priv->radio_states[rtype], rtype);
else {
/* Otherwise sync all radio types */
for (i = 0; i < RFKILL_TYPE_MAX; i++)
manager_rfkill_update_one_type (self, &priv->radio_states[i], i);
}
}
static void
device_auth_done_cb (NMAuthChain *chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
gs_free_error GError *error = NULL;
NMAuthCallResult result;
NMDevice *device;
const char *permission;
NMDeviceAuthRequestFunc callback;
NMAuthSubject *subject;
nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
c_list_unlink (nm_auth_chain_parent_lst_list (chain));
permission = nm_auth_chain_get_data (chain, "perm");
nm_assert (permission);
callback = nm_auth_chain_get_data (chain, "callback");
nm_assert (callback);
device = nm_auth_chain_get_data (chain, "device");
nm_assert (NM_IS_DEVICE (device));
result = nm_auth_chain_get_result (chain, permission);
subject = nm_auth_chain_get_subject (chain);
if (result != NM_AUTH_CALL_RESULT_YES) {
_LOGD (LOGD_CORE, "%s request failed: not authorized", permission);
error = g_error_new (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
"%s request failed: not authorized",
permission);
}
nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES));
callback (device,
context,
subject,
error,
nm_auth_chain_get_data (chain, "user-data"));
}
static void
device_auth_request_cb (NMDevice *device,
GDBusMethodInvocation *context,
NMConnection *connection,
const char *permission,
gboolean allow_interaction,
NMDeviceAuthRequestFunc callback,
gpointer user_data,
NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
GError *error = NULL;
NMAuthSubject *subject = NULL;
NMAuthChain *chain;
char *permission_dup;
/* Validate the caller */
subject = nm_dbus_manager_new_auth_subject_from_context (context);
if (!subject) {
error = g_error_new_literal (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
goto done;
}
/* Ensure the subject has permissions for this connection */
if ( connection
&& !nm_auth_is_subject_in_acl_set_error (connection,
subject,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
&error))
goto done;
/* Validate the request */
chain = nm_auth_chain_new_subject (subject, context, device_auth_done_cb, self);
if (!chain) {
error = g_error_new_literal (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
goto done;
}
permission_dup = g_strdup (permission);
c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
nm_auth_chain_set_data (chain, "device", g_object_ref (device), g_object_unref);
nm_auth_chain_set_data (chain, "callback", callback, NULL);
nm_auth_chain_set_data (chain, "user-data", user_data, NULL);
nm_auth_chain_set_data (chain, "perm", permission_dup /* transfer ownership */, g_free);
nm_auth_chain_add_call_unsafe (chain, permission_dup, allow_interaction);
done:
if (error)
callback (device, context, subject, error, user_data);
g_clear_object (&subject);
g_clear_error (&error);
}
static gboolean
new_activation_allowed_for_connection (NMManager *self,
NMSettingsConnection *connection)
{
if (NM_IN_SET (_nm_connection_get_multi_connect (nm_settings_connection_get_connection (connection)),
NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE,
NM_CONNECTION_MULTI_CONNECT_MULTIPLE))
return TRUE;
return !active_connection_find (self, connection, NULL,
NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
NULL);
}
/**
* get_existing_connection:
* @manager: #NMManager instance
* @device: #NMDevice instance
* @out_generated: (allow-none): return TRUE, if the connection was generated.
*
* Returns: a #NMSettingsConnection to be assumed by the device, or %NULL if
* the device does not support assuming existing connections.
*/
static NMSettingsConnection *
get_existing_connection (NMManager *self,
NMDevice *device,
gboolean *out_generated)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
gs_unref_object NMConnection *connection = NULL;
NMSettingsConnection *added;
GError *error = NULL;
gs_free_error GError *gen_error = NULL;
NMDevice *master = NULL;
int ifindex = nm_device_get_ifindex (device);
NMSettingsConnection *matched = NULL;
NMSettingsConnection *connection_checked = NULL;
gboolean assume_state_guess_assume = FALSE;
const char *assume_state_connection_uuid = NULL;
gboolean maybe_later, only_by_uuid = FALSE;
if (out_generated)
*out_generated = FALSE;
nm_device_capture_initial_config (device);
if (ifindex) {
int master_ifindex = nm_platform_link_get_master (priv->platform, ifindex);
/* Check that the master is activating before assuming a
* slave connection. However, ignore ovs-system master as
* we never manage it.
*/
if ( master_ifindex
&& nm_platform_link_get_type (priv->platform, master_ifindex) != NM_LINK_TYPE_OPENVSWITCH) {
master = nm_manager_get_device_by_ifindex (self, master_ifindex);
if (!master) {
_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
"cannot generate connection for slave before its master (%s/%d)",
nm_platform_link_get_name (priv->platform, master_ifindex), master_ifindex);
return NULL;
}
if (!nm_device_get_act_request (master)) {
_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
"cannot generate connection for slave before master %s activates",
nm_device_get_iface (master));
return NULL;
}
}
}
/* The core of the API is nm_device_generate_connection() function and
* update_connection() virtual method and the convenient connection_type
* class attribute. Subclasses supporting the new API must have
* update_connection() implemented, otherwise nm_device_generate_connection()
* returns NULL.
*/
connection = nm_device_generate_connection (device, master, &maybe_later, &gen_error);
if (!connection) {
if (maybe_later) {
/* The device can generate a connection, but it failed for now.
* Give it a chance to match a connection from the state file. */
only_by_uuid = TRUE;
} else {
nm_device_assume_state_reset (device);
_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
gen_error->message);
return NULL;
}
}
nm_device_assume_state_get (device,
&assume_state_guess_assume,
&assume_state_connection_uuid);
/* Now we need to compare the generated connection to each configured
* connection. The comparison function is the heart of the connection
* assumption implementation and it must compare the connections very
* carefully to sort out various corner cases. Also, the comparison is
* not entirely symmetric.
*
* When no configured connection matches the generated connection, we keep
* the generated connection instead.
*/
if ( assume_state_connection_uuid
&& (connection_checked = nm_settings_get_connection_by_uuid (priv->settings, assume_state_connection_uuid))
&& new_activation_allowed_for_connection (self, connection_checked)
&& nm_device_check_connection_compatible (device,
nm_settings_connection_get_connection (connection_checked),
NULL)) {
if (connection) {
NMConnection *con = nm_settings_connection_get_connection (connection_checked);
if (nm_utils_match_connection ((NMConnection *[]) { con, NULL },
connection,
TRUE,
nm_device_has_carrier (device),
nm_device_get_route_metric (device, AF_INET),
nm_device_get_route_metric (device, AF_INET6),
NULL, NULL))
matched = connection_checked;
} else
matched = connection_checked;
}
if (!matched && only_by_uuid) {
_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
gen_error->message);
return NULL;
}
if (!matched && assume_state_guess_assume) {
gs_free NMSettingsConnection **sett_conns = NULL;
guint len, i, j;
/* the state file doesn't indicate a connection UUID to assume. Search the
* persistent connections for a matching candidate. */
sett_conns = nm_manager_get_activatable_connections (self, FALSE, FALSE, &len);
if (len > 0) {
for (i = 0, j = 0; i < len; i++) {
NMSettingsConnection *sett_conn = sett_conns[i];
if ( sett_conn != connection_checked
&& nm_device_check_connection_compatible (device,
nm_settings_connection_get_connection (sett_conn),
NULL))
sett_conns[j++] = sett_conn;
}
sett_conns[j] = NULL;
len = j;
if (len > 0) {
gs_free NMConnection **conns = NULL;
NMConnection *con;
g_qsort_with_data (sett_conns, len, sizeof (sett_conns[0]),
nm_settings_connection_cmp_timestamp_p_with_data, NULL);
conns = nm_settings_connections_array_to_connections (sett_conns, len);
con = nm_utils_match_connection (conns,
connection,
FALSE,
nm_device_has_carrier (device),
nm_device_get_route_metric (device, AF_INET),
nm_device_get_route_metric (device, AF_INET6),
NULL,
NULL);
if (con) {
for (i = 0; i < len; i++) {
if (conns[i] == con) {
matched = sett_conns[i];
break;
}
}
nm_assert (matched);
}
}
}
}
if (matched) {
_LOG2I (LOGD_DEVICE, device, "assume: will attempt to assume matching connection '%s' (%s)%s",
nm_settings_connection_get_id (matched),
nm_settings_connection_get_uuid (matched),
assume_state_connection_uuid && nm_streq (assume_state_connection_uuid, nm_settings_connection_get_uuid (matched))
? " (indicated)" : " (guessed)");
nm_device_assume_state_reset (device);
return matched;
}
_LOG2D (LOGD_DEVICE, device, "assume: generated connection '%s' (%s)",
nm_connection_get_id (connection),
nm_connection_get_uuid (connection));
nm_device_assume_state_reset (device);
if (!nm_settings_add_connection (priv->settings,
connection,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
| NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
&added,
&error)) {
_LOG2W (LOGD_SETTINGS, device, "assume: failure to save generated connection '%s': %s",
nm_connection_get_id (connection),
error->message);
g_error_free (error);
return NULL;
}
NM_SET_OUT (out_generated, TRUE);
return added;
}
static gboolean
copy_lease (const char *src, const char *dst)
{
nm_auto_close int src_fd = -1;
int dst_fd;
ssize_t res, size = SSIZE_MAX;
src_fd = open (src, O_RDONLY|O_CLOEXEC);
if (src_fd < 0)
return FALSE;
dst_fd = open (dst, O_CREAT|O_EXCL|O_CLOEXEC|O_WRONLY, 0644);
if (dst_fd < 0)
return FALSE;
while ((res = sendfile (dst_fd, src_fd, NULL, size)) > 0)
size -= res;
nm_close (dst_fd);
if (res != 0) {
unlink (dst);
return FALSE;
}
return TRUE;
}
static gboolean
recheck_assume_connection (NMManager *self,
NMDevice *device)
{
NMSettingsConnection *sett_conn;
gboolean was_unmanaged = FALSE;
gboolean generated = FALSE;
NMDeviceState state;
gboolean activation_type_assume;
g_return_val_if_fail (NM_IS_MANAGER (self), FALSE);
g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
if (!nm_device_get_managed (device, FALSE)) {
nm_device_assume_state_reset (device);
_LOG2D (LOGD_DEVICE, device, "assume: don't assume because %s", "not managed");
return FALSE;
}
state = nm_device_get_state (device);
if (state > NM_DEVICE_STATE_DISCONNECTED) {
nm_device_assume_state_reset (device);
_LOG2D (LOGD_DEVICE, device, "assume: don't assume due to device state %s",
nm_device_state_to_str (state));
return FALSE;
}
sett_conn = get_existing_connection (self, device, &generated);
/* log no reason. get_existing_connection() already does it. */
if (!sett_conn)
return FALSE;
activation_type_assume = !generated;
if (state == NM_DEVICE_STATE_UNMANAGED) {
gs_free char *initramfs_lease = g_strdup_printf (RUNSTATEDIR "/initramfs/net.%s.lease",
nm_device_get_iface (device));
gs_free char *connection_lease = g_strdup_printf (NMRUNDIR "/dhclient-%s-%s.lease",
nm_settings_connection_get_uuid (sett_conn),
nm_device_get_iface (device));
if (copy_lease (initramfs_lease, connection_lease)) {
unlink (initramfs_lease);
/*
* We've managed to steal the lease used by initramfs before it
* killed off the dhclient. We need to take ownership of the configured
* connection and act like the device was configured by us.
* Otherwise the address would just expire.
*/
_LOG2I (LOGD_DEVICE, device, "assume: taking over an initramfs-configured connection");
activation_type_assume = TRUE;
if (generated) {
/* Reset the IPv4 setting to empty method=auto, regardless of what assumption guessed. */
nm_connection_add_setting (nm_settings_connection_get_connection (sett_conn),
g_object_new (NM_TYPE_SETTING_IP4_CONFIG,
NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
NULL));
nm_settings_connection_update (sett_conn,
NULL,
NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
0,
NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
"assume-initrd",
NULL);
}
}
}
nm_device_sys_iface_state_set (device,
activation_type_assume
? NM_DEVICE_SYS_IFACE_STATE_ASSUME
: NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
/* Move device to DISCONNECTED to activate the connection */
if (state == NM_DEVICE_STATE_UNMANAGED) {
was_unmanaged = TRUE;
nm_device_state_changed (device,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
}
if (nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE) {
nm_device_state_changed (device,
NM_DEVICE_STATE_DISCONNECTED,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
}
g_return_val_if_fail (nm_device_get_state (device) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
{
gs_unref_object NMActiveConnection *active = NULL;
gs_unref_object NMAuthSubject *subject = NULL;
NMActiveConnection *master_ac;
GError *error = NULL;
subject = nm_auth_subject_new_internal ();
/* Note: the lifetime of the activation connection is always bound to the profiles visibility
* via NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY.
*
* This only makes a difference, if the profile actually has "connection.permissions"
* set to limit visibility (which is not the case for externally managed, generated profiles).
*
* If we assume a previously active connection whose lifetime was unbound, we now bind it
* after restart. That is not correct, and can mean that the profile becomes subject to
* deactivation after restart (if the user logs out).
*
* This should be improved, but it's unclear how. */
active = _new_active_connection (self,
FALSE,
sett_conn,
NULL,
NULL,
NULL,
device,
subject,
activation_type_assume ? NM_ACTIVATION_TYPE_ASSUME : NM_ACTIVATION_TYPE_EXTERNAL,
activation_type_assume ? NM_ACTIVATION_REASON_ASSUME : NM_ACTIVATION_REASON_EXTERNAL,
NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY,
&error);
if (!active) {
_LOGW (LOGD_DEVICE, "assume: assumed connection %s failed to activate: %s",
nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)),
error->message);
g_error_free (error);
if (was_unmanaged) {
nm_device_state_changed (device,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_REASON_CONFIG_FAILED);
}
if ( generated
&& !activation_type_assume) {
_LOG2D (LOGD_DEVICE, device, "assume: deleting generated connection after assuming failed");
nm_settings_connection_delete (sett_conn, FALSE);
} else {
if (nm_device_sys_iface_state_get (device) == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
}
return FALSE;
}
/* If the device is a slave or VLAN, find the master ActiveConnection */
master_ac = NULL;
if ( find_master (self,
nm_settings_connection_get_connection (sett_conn),
device,
NULL,
NULL,
&master_ac,
NULL)
&& master_ac)
nm_active_connection_set_master (active, master_ac);
active_connection_add (self, active);
nm_device_queue_activation (device, NM_ACT_REQUEST (active));
}
return TRUE;
}
static void
recheck_assume_connection_cb (NMManager *self, NMDevice *device)
{
recheck_assume_connection (self, device);
}
static void
device_ifindex_changed (NMDevice *device,
GParamSpec *pspec,
NMManager *self)
{
_parent_notify_changed (self, device, FALSE);
}
static void
device_ip_iface_changed (NMDevice *device,
GParamSpec *pspec,
NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
const char *ip_iface = nm_device_get_ip_iface (device);
NMDeviceType device_type = nm_device_get_device_type (device);
NMDevice *candidate;
/* Remove NMDevice objects that are actually child devices of others,
* when the other device finally knows its IP interface name. For example,
* remove the PPP interface that's a child of a WWAN device, since it's
* not really a standalone NMDevice.
*/
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
if ( candidate != device
&& g_strcmp0 (nm_device_get_iface (candidate), ip_iface) == 0
&& nm_device_get_device_type (candidate) == device_type
&& nm_device_is_real (candidate)) {
remove_device (self, candidate, FALSE);
break;
}
}
}
static void
device_iface_changed (NMDevice *device,
GParamSpec *pspec,
NMManager *self)
{
/* Virtual connections may refer to the new device name as
* parent device, retry to activate them.
*/
retry_connections_for_parent_device (self, device);
}
static void
_emit_device_added_removed (NMManager *self,
NMDevice *device,
gboolean is_added)
{
nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
&interface_info_manager,
is_added
? &signal_info_device_added
: &signal_info_device_removed,
"(o)",
nm_dbus_object_get_path (NM_DBUS_OBJECT (device)));
g_signal_emit (self,
signals[is_added ? DEVICE_ADDED : DEVICE_REMOVED],
0,
device);
_notify (self, PROP_DEVICES);
}
static void
device_realized (NMDevice *device,
GParamSpec *pspec,
NMManager *self)
{
_emit_device_added_removed (self, device, nm_device_is_real (device));
}
static NMConnectivityState
_get_best_connectivity (NMManager *self, int addr_family)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMConnectivityState best_state;
NMDevice *dev;
gint64 best_metric;
if (addr_family == AF_UNSPEC) {
best_state = _get_best_connectivity (self, AF_INET);
if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
/* already FULL IPv4 connectivity. No need to check IPv6, it doesn't get
* better. */
return best_state;
}
return NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
best_state,
_get_best_connectivity (self, AF_INET6));
}
nm_assert_addr_family (addr_family);
best_state = NM_CONNECTIVITY_UNKNOWN;
best_metric = G_MAXINT64;
c_list_for_each_entry (dev, &priv->devices_lst_head, devices_lst) {
const NMPObject *r;
NMConnectivityState state;
gint64 metric;
r = nm_device_get_best_default_route (dev, addr_family);
if (r) {
metric = nm_utils_ip_route_metric_normalize (addr_family,
NMP_OBJECT_CAST_IP_ROUTE (r)->metric);
} else {
/* if all devices have no default-route, we still include the best
* of all connectivity state of all the devices. */
metric = G_MAXINT64;
}
if (metric > best_metric) {
/* we already have a default route with better metric. The connectivity state
* of this device is irreleavnt. */
continue;
}
state = nm_device_get_connectivity_state (dev, addr_family);
if (metric < best_metric) {
/* this device has a better default route. It wins. */
best_metric = metric;
best_state = state;
} else {
best_state = NM_MAX_WITH_CMP (nm_connectivity_state_cmp,
best_state,
state);
}
if (nm_connectivity_state_cmp (best_state, NM_CONNECTIVITY_FULL) >= 0) {
/* it doesn't get better than FULL. We are done. */
break;
}
}
return best_state;
}
static void
device_connectivity_changed (NMDevice *device,
GParamSpec *pspec,
NMManager *self)
{
update_connectivity_value (self);
}
static void
update_connectivity_value (NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMConnectivityState best_state;
best_state = _get_best_connectivity (self, AF_UNSPEC);
if (best_state == priv->connectivity_state)
return;
priv->connectivity_state = best_state;
_LOGD (LOGD_CORE, "connectivity checking indicates %s",
nm_connectivity_state_to_string (priv->connectivity_state));
nm_manager_update_state (self);
_notify (self, PROP_CONNECTIVITY);
nm_dispatcher_call_connectivity (priv->connectivity_state, NULL, NULL, NULL);
}
static void
_device_realize_finish (NMManager *self,
NMDevice *device,
const NMPlatformLink *plink)
{
g_return_if_fail (NM_IS_MANAGER (self));
g_return_if_fail (NM_IS_DEVICE (device));
nm_device_realize_finish (device, plink);
if (!nm_device_get_managed (device, FALSE)) {
nm_device_assume_state_reset (device);
return;
}
if (recheck_assume_connection (self, device))
return;
/* if we failed to assume a connection for the managed device, but the device
* is still unavailable. Set UNAVAILABLE state again, this time with NOW_MANAGED. */
nm_device_state_changed (device,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_REASON_NOW_MANAGED);
nm_device_emit_recheck_auto_activate (device);
}
/**
* add_device:
* @self: the #NMManager
* @device: the #NMDevice to add
* @error: (out): the #GError
*
* If successful, this function will increase the references count of @device.
* Callers should decrease the reference count.
*/
static gboolean
add_device (NMManager *self, NMDevice *device, GError **error)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
const char *iface, *type_desc;
RfKillType rtype;
GSList *iter, *remove = NULL;
int ifindex;
const char *dbus_path;
NMDevice *candidate;
/* No duplicates */
ifindex = nm_device_get_ifindex (device);
if (ifindex > 0 && nm_manager_get_device_by_ifindex (self, ifindex)) {
g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
"A device with ifindex %d already exists", ifindex);
return FALSE;
}
/* Remove existing devices owned by the new device; eg remove ethernet
* ports that are owned by a WWAN modem, since udev may announce them
* before the modem is fully discovered.
*
* FIXME: use parent/child device relationships instead of removing
* the child NMDevice entirely
*/
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
if ( nm_device_is_real (candidate)
&& (iface = nm_device_get_ip_iface (candidate))
&& nm_device_owns_iface (device, iface))
remove = g_slist_prepend (remove, candidate);
}
for (iter = remove; iter; iter = iter->next)
remove_device (self, NM_DEVICE (iter->data), FALSE);
g_slist_free (remove);
g_object_ref (device);
nm_assert (c_list_is_empty (&device->devices_lst));
c_list_link_tail (&priv->devices_lst_head, &device->devices_lst);
g_signal_connect (device, NM_DEVICE_STATE_CHANGED,
G_CALLBACK (manager_device_state_changed),
self);
g_signal_connect (device, NM_DEVICE_AUTH_REQUEST,
G_CALLBACK (device_auth_request_cb),
self);
g_signal_connect (device, NM_DEVICE_REMOVED,
G_CALLBACK (device_removed_cb),
self);
g_signal_connect_data (device, NM_DEVICE_RECHECK_ASSUME,
G_CALLBACK (recheck_assume_connection_cb),
self, NULL, G_CONNECT_SWAPPED);
g_signal_connect (device, "notify::" NM_DEVICE_IP_IFACE,
G_CALLBACK (device_ip_iface_changed),
self);
g_signal_connect (device, "notify::" NM_DEVICE_IFINDEX,
G_CALLBACK (device_ifindex_changed),
self);
g_signal_connect (device, "notify::" NM_DEVICE_IFACE,
G_CALLBACK (device_iface_changed),
self);
g_signal_connect (device, "notify::" NM_DEVICE_REAL,
G_CALLBACK (device_realized),
self);
g_signal_connect (device, "notify::" NM_DEVICE_IP4_CONNECTIVITY,
G_CALLBACK (device_connectivity_changed),
self);
g_signal_connect (device, "notify::" NM_DEVICE_IP6_CONNECTIVITY,
G_CALLBACK (device_connectivity_changed),
self);
if (priv->startup) {
g_signal_connect (device, "notify::" NM_DEVICE_HAS_PENDING_ACTION,
G_CALLBACK (device_has_pending_action_changed),
self);
}
/* Update global rfkill state for this device type with the device's
* rfkill state, and then set this device's rfkill state based on the
* global state.
*/
rtype = nm_device_get_rfkill_type (device);
if (rtype != RFKILL_TYPE_UNKNOWN) {
nm_manager_rfkill_update (self, rtype);
nm_device_set_enabled (device, radio_enabled_for_type (self, rtype, TRUE));
}
iface = nm_device_get_iface (device);
g_assert (iface);
type_desc = nm_device_get_type_desc (device);
g_assert (type_desc);
nm_device_set_unmanaged_by_user_settings (device);
nm_device_set_unmanaged_flags (device,
NM_UNMANAGED_SLEEPING,
manager_sleeping (self));
dbus_path = nm_dbus_object_export (NM_DBUS_OBJECT (device));
_LOG2I (LOGD_DEVICE, device, "new %s device (%s)", type_desc, dbus_path);
nm_settings_device_added (priv->settings, device);
g_signal_emit (self, signals[INTERNAL_DEVICE_ADDED], 0, device);
_notify (self, PROP_ALL_DEVICES);
_parent_notify_changed (self, device, FALSE);
return TRUE;
}
/*****************************************************************************/
static void
factory_device_added_cb (NMDeviceFactory *factory,
NMDevice *device,
gpointer user_data)
{
NMManager *self = user_data;
GError *error = NULL;
g_return_if_fail (NM_IS_MANAGER (self));
if (nm_device_realize_start (device,
NULL,
FALSE, /* assume_state_guess_assume */
NULL, /* assume_state_connection_uuid */
FALSE, /* set_nm_owned */
NM_UNMAN_FLAG_OP_FORGET,
NULL,
&error)) {
add_device (self, device, NULL);
_device_realize_finish (self, device, NULL);
retry_connections_for_parent_device (self, device);
} else {
_LOG2W (LOGD_DEVICE, device, "failed to realize device: %s", error->message);
g_error_free (error);
}
}
static void
_register_device_factory (NMDeviceFactory *factory, gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
g_signal_connect (factory,
NM_DEVICE_FACTORY_DEVICE_ADDED,
G_CALLBACK (factory_device_added_cb),
self);
}
/*****************************************************************************/
void
nm_manager_notify_device_availibility_maybe_changed (NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDevice *device;
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst)
nm_device_notify_availability_maybe_changed (device);
}
/*****************************************************************************/
static void
platform_link_added (NMManager *self,
int ifindex,
const NMPlatformLink *plink,
gboolean guess_assume,
const NMConfigDeviceStateData *dev_state)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMDeviceFactory *factory;
NMDevice *device = NULL;
NMDevice *candidate;
g_return_if_fail (ifindex > 0);
if (nm_manager_get_device_by_ifindex (self, ifindex))
return;
/* Let unrealized devices try to realize themselves with the link */
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
gboolean compatible = TRUE;
gs_free_error GError *error = NULL;
if (nm_device_get_link_type (candidate) != plink->type)
continue;
if (strcmp (nm_device_get_iface (candidate), plink->name))
continue;
if (nm_device_is_real (candidate)) {
/* There's already a realized device with the link's name
* and a different ifindex.
*/
if (nm_device_get_ifindex (candidate) <= 0)
nm_device_update_from_platform_link (candidate, plink);
else {
/* The ifindex of a device can't be changed after
* initialization because it is used as a key by
* the dns-manager.
*/
_LOGD (LOGD_DEVICE, "(%s): removing old device %p after ifindex change from %d to %d",
plink->name, candidate, nm_device_get_ifindex (candidate), ifindex);
remove_device (self, candidate, FALSE);
goto add;
}
return;
} else if (nm_device_realize_start (candidate,
plink,
FALSE, /* assume_state_guess_assume */
NULL, /* assume_state_connection_uuid */
FALSE, /* set_nm_owned */
NM_UNMAN_FLAG_OP_FORGET,
&compatible,
&error)) {
_device_realize_finish (self, candidate, plink);
return;
}
_LOGD (LOGD_DEVICE, "(%s): failed to realize from plink: '%s'",
plink->name, error->message);
/* Try next unrealized device */
}
add:
/* Try registered device factories */
factory = nm_device_factory_manager_find_factory_for_link_type (plink->type);
if (factory) {
gboolean ignore = FALSE;
gs_free_error GError *error = NULL;
device = nm_device_factory_create_device (factory, plink->name, plink, NULL, &ignore, &error);
if (!device) {
if (!ignore) {
_LOGW (LOGD_PLATFORM, "%s: factory failed to create device: %s",
plink->name, error->message);
} else {
_LOGD (LOGD_PLATFORM, "%s: factory failed to create device: %s",
plink->name, error->message);
}
return;
}
}
if (device == NULL) {
gboolean nm_plugin_missing = FALSE;
switch (plink->type) {
case NM_LINK_TYPE_WWAN_NET:
case NM_LINK_TYPE_BNEP:
case NM_LINK_TYPE_OLPC_MESH:
case NM_LINK_TYPE_TEAM:
case NM_LINK_TYPE_WIFI:
_LOGI (LOGD_PLATFORM, "(%s): '%s' plugin not available; creating generic device",
plink->name, nm_link_type_to_string (plink->type));
nm_plugin_missing = TRUE;
/* fall-through */
default:
device = nm_device_generic_new (plink, nm_plugin_missing);
break;
}
}
if (device) {
gs_free_error GError *error = NULL;
NMUnmanFlagOp unmanaged_user_explicit = NM_UNMAN_FLAG_OP_FORGET;
if (dev_state) {
switch (dev_state->managed) {
case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED:
unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_MANAGED;
break;
case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED:
unmanaged_user_explicit = NM_UNMAN_FLAG_OP_SET_UNMANAGED;
break;
case NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN:
break;
}
}
if (nm_device_realize_start (device,
plink,
guess_assume,
dev_state ? dev_state->connection_uuid : NULL,
dev_state ? (dev_state->nm_owned == 1) : FALSE,
unmanaged_user_explicit,
NULL,
&error)) {
add_device (self, device, NULL);
_device_realize_finish (self, device, plink);
retry_connections_for_parent_device (self, device);
} else {
_LOGW (LOGD_DEVICE, "%s: failed to realize device: %s",
plink->name, error->message);
}
g_object_unref (device);
}
}
typedef struct {
CList lst;
NMManager *self;
int ifindex;
guint idle_id;
} PlatformLinkCbData;
static gboolean
_platform_link_cb_idle (PlatformLinkCbData *data)
{
int ifindex = data->ifindex;
NMManager *self = data->self;
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
const NMPlatformLink *plink;
c_list_unlink_stale (&data->lst);
g_slice_free (PlatformLinkCbData, data);
plink = nm_platform_link_get (priv->platform, ifindex);
if (plink) {
const NMPObject *plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
platform_link_added (self, ifindex, plink, FALSE, NULL);
nmp_object_unref (plink_keep_alive);
} else {
NMDevice *device;
GError *error = NULL;
device = nm_manager_get_device_by_ifindex (self, ifindex);
if (device) {
if (nm_device_is_software (device)) {
nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
/* Our software devices stick around until their connection is removed */
if (!nm_device_unrealize (device, FALSE, &error)) {
_LOG2W (LOGD_DEVICE, device, "failed to unrealize: %s", error->message);
g_clear_error (&error);
remove_device (self, device, FALSE);
} else {
nm_device_update_from_platform_link (device, NULL);
}
} else {
/* Hardware and external devices always get removed when their kernel link is gone */
remove_device (self, device, FALSE);
}
}
}
return G_SOURCE_REMOVE;
}
static void
platform_link_cb (NMPlatform *platform,
int obj_type_i,
int ifindex,
NMPlatformLink *plink,
int change_type_i,
gpointer user_data)
{
NMManager *self;
NMManagerPrivate *priv;
const NMPlatformSignalChangeType change_type = change_type_i;
PlatformLinkCbData *data;
switch (change_type) {
case NM_PLATFORM_SIGNAL_ADDED:
case NM_PLATFORM_SIGNAL_REMOVED:
self = NM_MANAGER (user_data);
priv = NM_MANAGER_GET_PRIVATE (self);
data = g_slice_new (PlatformLinkCbData);
data->self = self;
data->ifindex = ifindex;
c_list_link_tail (&priv->link_cb_lst, &data->lst);
data->idle_id = g_idle_add ((GSourceFunc) _platform_link_cb_idle, data);
break;
default:
break;
}
}
static void
platform_query_devices (NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
gs_unref_ptrarray GPtrArray *links = NULL;
int i;
gboolean guess_assume;
gs_free char *order = NULL;
guess_assume = nm_config_get_first_start (nm_config_get ());
order = nm_config_data_get_value (NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_GROUP_MAIN,
NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
NM_CONFIG_GET_VALUE_STRIP);
links = nm_platform_link_get_all (priv->platform, !nm_streq0 (order, "index"));
if (!links)
return;
for (i = 0; i < links->len; i++) {
const NMPlatformLink *link = NMP_OBJECT_CAST_LINK (links->pdata[i]);
const NMConfigDeviceStateData *dev_state;
dev_state = nm_config_device_state_get (priv->config, link->ifindex);
platform_link_added (self,
link->ifindex,
link,
guess_assume && (!dev_state || !dev_state->connection_uuid),
dev_state);
}
}
static void
rfkill_manager_rfkill_changed_cb (NMRfkillManager *rfkill_mgr,
RfKillType rtype,
RfKillState udev_state,
gpointer user_data)
{
nm_manager_rfkill_update (NM_MANAGER (user_data), rtype);
}
const CList *
nm_manager_get_devices (NMManager *manager)
{
g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);
return &NM_MANAGER_GET_PRIVATE (manager)->devices_lst_head;
}
typedef enum {
DEVICE_ACTIVATION_PRIO_NONE,
DEVICE_ACTIVATION_PRIO_UNMANAGED,
DEVICE_ACTIVATION_PRIO_UNAVAILABLE,
DEVICE_ACTIVATION_PRIO_DEACTIVATING,
DEVICE_ACTIVATION_PRIO_ACTIVATING,
DEVICE_ACTIVATION_PRIO_ACTIVATED,
DEVICE_ACTIVATION_PRIO_DISCONNECTED,
_DEVICE_ACTIVATION_PRIO_BEST = DEVICE_ACTIVATION_PRIO_DISCONNECTED,
} DeviceActivationPrio;
static DeviceActivationPrio
_device_get_activation_prio (NMDevice *device)
{
if (!nm_device_get_managed (device, TRUE))
return DEVICE_ACTIVATION_PRIO_NONE;
switch (nm_device_get_state (device)) {
case NM_DEVICE_STATE_DISCONNECTED:
return DEVICE_ACTIVATION_PRIO_DISCONNECTED;
case NM_DEVICE_STATE_ACTIVATED:
return DEVICE_ACTIVATION_PRIO_ACTIVATED;
case NM_DEVICE_STATE_PREPARE:
case NM_DEVICE_STATE_CONFIG:
case NM_DEVICE_STATE_NEED_AUTH:
case NM_DEVICE_STATE_IP_CONFIG:
case NM_DEVICE_STATE_IP_CHECK:
case NM_DEVICE_STATE_SECONDARIES:
return DEVICE_ACTIVATION_PRIO_ACTIVATING;
case NM_DEVICE_STATE_DEACTIVATING:
case NM_DEVICE_STATE_FAILED:
return DEVICE_ACTIVATION_PRIO_DEACTIVATING;
case NM_DEVICE_STATE_UNAVAILABLE:
return DEVICE_ACTIVATION_PRIO_UNAVAILABLE;
case NM_DEVICE_STATE_UNKNOWN:
case NM_DEVICE_STATE_UNMANAGED:
return DEVICE_ACTIVATION_PRIO_UNMANAGED;
}
g_return_val_if_reached (DEVICE_ACTIVATION_PRIO_UNAVAILABLE);
}
static NMDevice *
nm_manager_get_best_device_for_connection (NMManager *self,
NMSettingsConnection *sett_conn,
NMConnection *connection,
gboolean for_user_request,
GHashTable *unavailable_devices,
GError **error)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMActiveConnectionState ac_state;
NMActiveConnection *ac;
NMDevice *ac_device;
NMDevice *device;
struct {
NMDevice *device;
DeviceActivationPrio prio;
} best = {
.device = NULL,
.prio = DEVICE_ACTIVATION_PRIO_NONE,
};
NMDeviceCheckConAvailableFlags flags;
gs_unref_ptrarray GPtrArray *all_ac_arr = NULL;
gs_free_error GError *local_best = NULL;
NMConnectionMultiConnect multi_connect;
nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
nm_assert (!connection || NM_IS_CONNECTION (connection));
nm_assert (sett_conn || connection);
nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));
if (!connection)
connection = nm_settings_connection_get_connection (sett_conn);
multi_connect = _nm_connection_get_multi_connect (connection);
if (!for_user_request)
flags = NM_DEVICE_CHECK_CON_AVAILABLE_NONE;
else {
/* if the profile is multi-connect=single, we also consider devices which
* are marked as unmanaged. And explicit user-request shows sufficient user
* intent to make the device managed.
* That is also, because we expect that such profile is suitably tied
* to the intended device. So when an unmanaged device matches, the user's
* intent is clear.
*
* For multi-connect != single devices that is different. The profile
* is not restricted to a particular device.
* For that reason, plain `nmcli connection up "$MULIT_PROFILE"` seems
* less suitable for multi-connect profiles, because the target device is
* left unspecified. Anyway, if a user issues
*
* $ nmcli device set "$DEVICE" managed no
* $ nmcli connection up "$MULIT_PROFILE"
*
* then it is reasonable for multi-connect profiles to not consider
* the device a suitable candidate.
*
* This may be seen inconsistent, but I think that it makes a lot of
* sense. Also note that "connection.multi-connect" work quite differently
* in aspects like activation. E.g. `nmcli connection up` of multi-connect
* "single" profile, will deactivate the profile if it is active already.
* That is different from multi-connect profiles, where it will aim to
* activate the profile one more time on an hitherto disconnected device.
*/
if (multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE)
flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST;
else
flags = NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED;
}
if ( multi_connect == NM_CONNECTION_MULTI_CONNECT_SINGLE
&& (ac = active_connection_find_by_connection (self, sett_conn, connection, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, &all_ac_arr))) {
/* if we have a profile which may activate on only one device (multi-connect single), then
* we prefer the device on which the profile is already active. It means to reactivate
* the profile on the same device.
*
* If the profile can be activated on multiple devices, we don't do this. In fact, the
* check below for the DeviceActivationPrio will prefer devices which are not already
* activated (with this or another) profile. */
ac_device = nm_active_connection_get_device (ac);
if ( ac_device
&& ( (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device))
|| !nm_device_check_connection_available (ac_device, connection, flags, NULL, NULL)))
ac_device = NULL;
if (all_ac_arr) {
guint i;
ac_state = nm_active_connection_get_state (ac);
/* we found several active connections. See which one is the most suitable... */
nm_assert (ac == all_ac_arr->pdata[0]);
for (i = 1; i < all_ac_arr->len; i++) {
NMActiveConnection *ac2 = all_ac_arr->pdata[i];
NMDevice *ac_device2 = nm_active_connection_get_device (ac2);
NMActiveConnectionState ac_state2;
if ( !ac_device2
|| (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device2))
|| !nm_device_check_connection_available (ac_device2, connection, flags, NULL, NULL))
continue;
ac_state2 = nm_active_connection_get_state (ac2);
if (!ac_device)
goto found_better;
if (ac_state == ac_state2) {
/* active-connections are in their list in the order in which they are connected.
* If we have two with same state, the later (newer) one is preferred. */
goto found_better;
}
switch (ac_state) {
case NM_ACTIVE_CONNECTION_STATE_UNKNOWN:
if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATING, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING))
goto found_better;
break;
case NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATED))
goto found_better;
break;
case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
break;
case NM_ACTIVE_CONNECTION_STATE_DEACTIVATING:
if (NM_IN_SET (ac_state2, NM_ACTIVE_CONNECTION_STATE_ACTIVATING, NM_ACTIVE_CONNECTION_STATE_ACTIVATED))
goto found_better;
break;
default:
nm_assert_not_reached ();
goto found_better;
}
continue;
found_better:
ac = ac2;
ac_state = ac_state2;
ac_device = ac_device2;
}
}
if (ac_device)
return ac_device;
}
/* Pick the first device that's compatible with the connection. */
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
GError *local = NULL;
DeviceActivationPrio prio;
if ( unavailable_devices
&& g_hash_table_contains (unavailable_devices, device))
continue;
/* determine the priority of this device. Currently this priority is independent
* of the profile (connection) and the device's details (aside the state).
*
* Maybe nm_device_check_connection_available() should instead return a priority,
* as it has more information available.
*
* For example, if you have multiple Wi-Fi devices, currently a user-request would
* also select the device if the AP is not visible. Optimally, if one of the two
* devices sees the AP and the other one doesn't, the former would be preferred.
* For that, the priority would need to be determined by nm_device_check_connection_available(). */
prio = _device_get_activation_prio (device);
if ( prio <= best.prio
&& best.device) {
/* we already have a matching device with a better priority. This candidate
* cannot be better. Skip the check.
*
* Also note, that below we collect the best error message @local_best.
* Since we already have best.device, the error message does not matter
* either, and we can skip nm_device_check_connection_available() altogether. */
continue;
}
if (nm_device_check_connection_available (device,
connection,
flags,
NULL,
error ? &local : NULL)) {
if (prio == _DEVICE_ACTIVATION_PRIO_BEST) {
/* this device already has the best priority. It cannot get better
* and finish the search. */
return device;
}
best.prio = prio;
best.device = device;
continue;
}
if (error) {
gboolean reset_error;
if (!local_best)
reset_error = TRUE;
else if (local_best->domain != NM_UTILS_ERROR)
reset_error = (local->domain == NM_UTILS_ERROR);
else {
reset_error = ( local->domain == NM_UTILS_ERROR
&& local_best->code < local->code);
}
if (reset_error) {
g_clear_error (&local_best);
g_set_error (&local_best,
local->domain,
local->code,
"device %s not available because %s",
nm_device_get_iface (device),
local->message);
}
g_error_free (local);
}
}
if (best.device)
return best.device;
if (error) {
if (local_best)
g_propagate_error (error, g_steal_pointer (&local_best));
else {
nm_utils_error_set_literal (error,
NM_UTILS_ERROR_UNKNOWN,
"no suitable device found");
}
}
return NULL;
}
static const char **
_get_devices_paths (NMManager *self,
gboolean all_devices)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
const char **paths = NULL;
guint i;
NMDevice *device;
paths = g_new (const char *, c_list_length (&priv->devices_lst_head) + 1);
i = 0;
c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
const char *path;
path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device));
if (!path)
continue;
if ( !all_devices
&& !nm_device_is_real (device))
continue;
paths[i++] = path;
}
paths[i++] = NULL;
return paths;
}
static void
impl_manager_get_devices (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMManager *self = NM_MANAGER (obj);
gs_free const char **paths = NULL;
paths = _get_devices_paths (self, FALSE);
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(^ao)", (char **) paths));
}
static void
impl_manager_get_all_devices (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMManager *self = NM_MANAGER (obj);
gs_free const char **paths = NULL;
paths = _get_devices_paths (self, TRUE);
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(^ao)", (char **) paths));
}
static void
impl_manager_get_device_by_ip_iface (NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMManager *self = NM_MANAGER (obj);
NMDevice *device;
const char *path = NULL;
const char *iface;
g_variant_get (parameters, "(&s)", &iface);
device = find_device_by_ip_iface (self, iface);
if (device)
path = nm_dbus_object_get_path (NM_DBUS_OBJECT (device));
if (!path) {
g_dbus_method_invocation_return_error (invocation,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_UNKNOWN_DEVICE,
"No device found for the requested iface.");
return;
}
g_dbus_method_invocation_return_value (invocation,
g_variant_new ("(o)", path));
}
static gboolean
is_compatible_with_slave (NMConnection *master, NMConnection *slave)
{
NMSettingConnection *s_con;
g_return_val_if_fail (master, FALSE);
g_return_val_if_fail (slave, FALSE);
s_con = nm_connection_get_setting_connection (slave);
g_assert (s_con);
return nm_connection_is_type (master, nm_setting_connection_get_slave_type (s_con));
}
/**
* find_master:
* @self: #NMManager object
* @connection: the #NMConnection to find the master connection and device for
* @device: the #NMDevice, if any, which will activate @connection
* @out_master_connection: on success, the master connection of @connection if
* that master connection was found
* @out_master_device: on success, the master device of @connection if that
* master device was found
* @out_master_ac: on success, the master ActiveConnection of @connection if
* there already is one
* @error: the error, if an error occurred
*
* Given an #NMConnection, attempts to find its master. If @connection has
* no master, this will return %TRUE and @out_master_connection and
* @out_master_device will be untouched.
*
* If @connection does have a master, then the outputs depend on what is in its
* #NMSettingConnection:master property:
*
* If "master" is the ifname of an existing #NMDevice, and that device has a
* compatible master connection activated or activating on it, then
* @out_master_device, @out_master_connection, and @out_master_ac will all be
* set. If the device exists and is idle, only @out_master_device will be set.
* If the device exists and has an incompatible connection on it, an error
* will be returned.
*
* If "master" is the ifname of a non-existent device, then @out_master_device
* will be %NULL, and @out_master_connection will be a connection whose
* activation would cause the creation of that device. @out_master_ac MAY be
* set in this case as well (if the connection has started activating, but has
* not yet created its device).
*
* If "master" is the UUID of a compatible master connection, then
* @out_master_connection will be the identified connection, and @out_master_device
* and/or @out_master_ac will be set if the connection is currently activating.
* (@out_master_device will not be set if the device exists but does not have
* @out_master_connection active/activating on it.)
*
* Returns: %TRUE if the master device and/or connection could be found or if
* the connection did not require a master, %FALSE otherwise
**/
static gboolean
find_master (NMManager *self,
NMConnection *connection,
NMDevice *device,
NMSettingsConnection **out_master_connection,
NMDevice **out_master_device,
NMActiveConnection **out_master_ac,
GError **error)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMSettingConnection *s_con;
const char *master;
NMDevice *master_device = NULL;
NMSettingsConnection *master_connection;
s_con = nm_connection_get_setting_connection (connection);
g_assert (s_con);
master = nm_setting_connection_get_master (s_con);
if (master == NULL)
return TRUE; /* success, but no master */
/* Try as an interface name first */
master_device = find_device_by_iface (self, master, NULL, connection);
if (master_device) {
if (master_device == device) {
g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
"Device cannot be its own master");
return FALSE;
}
master_connection = nm_device_get_settings_connection (master_device);
if ( master_connection
&& !is_compatible_with_slave (nm_settings_connection_get_connection (master_connection),
connection)) {
g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
"The active connection on %s is not compatible",
nm_device_get_iface (master_device));
return FALSE;
}
} else {
/* Try master as a connection UUID */
master_connection = nm_settings_get_connection_by_uuid (priv->settings, master);
if (master_connection) {
NMDevice *candidate;
/* Check if the master connection is activated on some device already */
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
if (candidate == device)
continue;
if (nm_device_get_settings_connection (candidate) == master_connection) {
master_device = candidate;
break;
}
}
}
}
if (out_master_connection)
*out_master_connection = master_connection;
if (out_master_device)
*out_master_device = master_device;
if (out_master_ac && master_connection) {
*out_master_ac = active_connection_find (self, master_connection, NULL,
NM_ACTIVE_CONNECTION_STATE_DEACTIVATING,
NULL);
}
if (master_device || master_connection)
return TRUE;
else {
g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE,
"Master connection not found or invalid");
return FALSE;
}
}
/**
* ensure_master_active_connection:
* @self: the #NMManager
* @subject: the #NMAuthSubject representing the requestor of this activation
* @connection: the connection that should depend on @master_connection
* @device: the #NMDevice, if any, which will activate @connection
* @master_connection: the master connection, or %NULL
* @master_device: the master device, or %NULL
* @activation_reason: the reason for activation
* @error: the error, if an error occurred
*
* Determines whether a given #NMConnection depends on another connection to
* be activated, and if so, finds that master connection or creates it.
*
* If @master_device and @master_connection are both set then @master_connection
* MUST already be activated or activating on @master_device, and the function will
* return the existing #NMActiveConnection.
*
* If only @master_device is set, and it has an #NMActiveConnection, then the
* function will return it if it is a compatible master, or an error if not. If it
* doesn't have an AC, then the function will create one if a compatible master
* connection exists, or return an error if not.
*
* If only @master_connection is set, then this will try to find or create a compatible
* #NMDevice, and either activate @master_connection on that device or return an error.
*
* Returns: the master #NMActiveConnection that the caller should depend on, or
* %NULL if an error occurred
*/
static NMActiveConnection *
ensure_master_active_connection (NMManager *self,
NMAuthSubject *subject,
NMConnection *connection,
NMDevice *device,
NMSettingsConnection *master_connection,
NMDevice *master_device,
NMActivationReason activation_reason,
GError **error)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
NMActiveConnection *ac;
NMActiveConnection *master_ac = NULL;
NMDeviceState master_state;
gboolean bind_lifetime_to_profile_visibility;
g_return_val_if_fail (connection, NULL);
g_return_val_if_fail (master_connection || master_device, FALSE);
bind_lifetime_to_profile_visibility = NM_FLAGS_HAS (nm_device_get_activation_state_flags (device),
NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
/* If the master device isn't activated then we need to activate it using
* compatible connection. If it's already activating we can just proceed.
*/
if (master_device) {
NMSettingsConnection *device_connection = nm_device_get_settings_connection (master_device);
/* If we're passed a connection and a device, we require that connection
* be already activated on the device, eg returned from find_master().
*/
g_assert (!master_connection || master_connection == device_connection);
if ( device_connection
&& !is_compatible_with_slave (nm_settings_connection_get_connection (device_connection),
connection)) {
g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
"The active connection %s is not compatible",
nm_connection_get_id (connection));
return NULL;
}
master_state = nm_device_get_state (master_device);
if ( (master_state == NM_DEVICE_STATE_ACTIVATED)
|| nm_device_is_activating (master_device)) {
/* Device already using master_connection */
ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (master_device));
g_return_val_if_fail (device_connection, ac);
if (!bind_lifetime_to_profile_visibility) {
/* unbind the lifetime. */
nm_active_connection_set_state_flags_clear (ac,
NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
}
return ac;
}
/* If the device is disconnected, find a compatible connection and
* activate it on the device.
*/
if (master_state == NM_DEVICE_STATE_DISCONNECTED || !nm_device_is_real (master_device)) {
gs_free NMSettingsConnection **connections = NULL;
guint i;
g_assert (master_connection == NULL);
/* Find a compatible connection and activate this device using it */
connections = nm_manager_get_activatable_connections (self, FALSE, TRUE, NULL);
for (i = 0; connections[i]; i++) {
NMSettingsConnection *candidate = connections[i];
NMConnection *cand_conn = nm_settings_connection_get_connection (candidate);
/* Ensure eg bond/team slave and the candidate master is a
* bond/team master
*/
if (!is_compatible_with_slave (cand_conn, connection))
continue;
if (nm_device_check_connection_available (master_device,
cand_conn,
NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
NULL,
NULL)) {
master_ac = nm_manager_activate_connection (self,
candidate,
NULL,
NULL,
master_device,
subject,
NM_ACTIVATION_TYPE_MANAGED,
activation_reason,
bind_lifetime_to_profile_visibility
? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
: NM_ACTIVATION_STATE_FLAG_NONE,
error);
return master_ac;
}
}
g_set_error (error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
"No compatible connection found.");
return NULL;
}
/* Otherwise, the device is unmanaged, unavailable, or disconnecting */
g_set_error (error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_DEPENDENCY_FAILED,
"Device unmanaged or not available for activation");
} else if (master_connection) {
NMDevice *candidate;
/* Find a compatible device and activate it using this connection */
c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
if (candidate == device) {
/* A device obviously can't be its own master */
continue;
}
if (!nm_device_check_connection_available (candidate,
nm_settings_connection_get_connection (master_connection),
NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
NULL,
NULL))
continue;
if (!nm_device_is_software (candidate)) {
master_state = nm_device_get_state (candidate);
if (nm_device_is_real (candidate) && master_state != NM_DEVICE_STATE_DISCONNECTED)
continue;
}
master_ac = nm_manager_activate_connection (self,
master_connection,
NULL,
NULL,
candidate,
subject,
NM_ACTIVATION_TYPE_MANAGED,
activation_reason,
bind_lifetime_to_profile_visibility
? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
: NM_ACTIVATION_STATE_FLAG_NONE,
error);
return master_ac;
}
g_set_error (error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_UNKNOWN_DEVICE,
"No device available");
} else
g_assert_not_reached ();
return NULL;
}
typedef struct {
NMSettingsConnection *connection;
NMDevice *device;
} SlaveConnectionInfo;
/**
* find_slaves:
* @manager: #NMManager object
* @sett_conn: the master #NMSettingsConnection to find slave connections for
* @device: the master #NMDevice for the @sett_conn
* @out_n_slaves: on return, the number of slaves found
*
* Given an #NMSettingsConnection, attempts to find its slaves. If @sett_conn is not
* master, or has not any slaves, this will return %NULL.
*
* Returns: an array of #SlaveConnectionInfo for given master @sett_conn, or %NULL
**/
static SlaveConnectionInfo *
find_slaves (NMManager *manager,
NMSettingsConnection *sett_conn,
NMDevice *device,
guint *out_n_slaves,
gboolean for_user_request)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
gs_free NMSettingsConnection **all_connections = NULL;
guint n_all_connections;
guint i;
SlaveConnectionInfo *slaves = NULL;
guint n_slaves = 0;
NMSettingConnection *s_con;
gs_unref_hashtable GHashTable *devices = NULL;
nm_assert (out_n_slaves);
s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
g_return_val_if_fail (s_con, NULL);
devices = g_hash_table_new (nm_direct_hash, NULL);
/* Search through all connections, not only inactive ones, because
* even if a slave was already active, it might be deactivated during
* master reactivation.
*/
all_connections = nm_settings_get_connections_clone (priv->settings, &n_all_connections,
NULL, NULL,
nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
for (i = 0; i < n_all_connections; i++) {
NMSettingsConnection *master_connection = NULL;
NMDevice *master_device = NULL, *slave_device;
NMSettingsConnection *candidate = all_connections[i];
find_master (manager,
nm_settings_connection_get_connection (candidate),
NULL,
&master_connection,
&master_device,
NULL,
NULL);
if ( (master_connection && master_connection == sett_conn)
|| (master_device && master_device == device)) {
slave_device = nm_manager_get_best_device_for_connection (manager,
candidate,
NULL,
for_user_request,
devices,
NULL);
if (!slaves) {
/* what we allocate is quite likely much too large. Don't bother, it is only
* a temporary buffer. */
slaves = g_new (SlaveConnectionInfo, n_all_connections);
}
nm_assert (n_slaves < n_all_connections);
slaves[n_slaves].connection = candidate,
slaves[n_slaves].device = slave_device,
n_slaves++;
if (slave_device)
g_hash_table_add (devices, slave_device);
}
}
*out_n_slaves = n_slaves;
/* Warning: returns NULL if n_slaves is zero. */
return slaves;
}
static gboolean
should_connect_slaves (NMConnection *connection, NMDevice *device)
{
NMSettingConnection *s_con;
NMSettingConnectionAutoconnectSlaves val;
s_con = nm_connection_get_setting_connection (connection);
g_assert (s_con);
val = nm_setting_connection_get_autoconnect_slaves (s_con);
if (val != NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT)
goto out;
val = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
NM_CON_DEFAULT ("connection.autoconnect-slaves"),
device,
0, 1, -1);
out:
if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO)
return FALSE;
if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES)
return TRUE;
return FALSE;
}
static int
compare_slaves (gconstpointer a, gconstpointer b, gpointer sort_by_name)
{
const SlaveConnectionInfo *a_info = a;
const SlaveConnectionInfo *b_info = b;
/* Slaves without a device at the end */
if (!a_info->device)
return 1;
if (!b_info->device)
return -1;
if (GPOINTER_TO_INT (sort_by_name)) {
return g_strcmp0 (nm_device_get_iface (a_info->device),
nm_device_get_iface (b_info->device));
}
return nm_device_get_ifindex (a_info->device) - nm_device_get_ifindex (b_info->device);
}
static void
autoconnect_slaves (NMManager *self,
NMSettingsConnection *master_connection,
NMDevice *master_device,
NMAuthSubject *subject,
gboolean for_user_request)
{
GError *local_err = NULL;
if (should_connect_slaves (nm_settings_connection_get_connection (master_connection),
master_device)) {
gs_free SlaveConnectionInfo *slaves = NULL;
guint i, n_slaves = 0;
gboolean bind_lifetime_to_profile_visibility;
slaves = find_slaves (self, master_connection, master_device, &n_slaves, for_user_request);
if (n_slaves > 1) {
gs_free char *value = NULL;
value = nm_config_data_get_value (NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_GROUP_MAIN,
NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
NM_CONFIG_GET_VALUE_STRIP);
g_qsort_with_data (slaves, n_slaves, sizeof (slaves[0]),
compare_slaves,
GINT_TO_POINTER (!nm_streq0 (value, "index")));
}
bind_lifetime_to_profile_visibility = n_slaves > 0
&& NM_FLAGS_HAS (nm_device_get_activation_state_flags (master_device),
NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY);
for (i = 0; i < n_slaves; i++) {
SlaveConnectionInfo *slave = &slaves[i];
const char *uuid;
/* To avoid loops when autoconnecting slaves, we propagate
* the UUID of the initial connection down to slaves until
* the same connection is found.
*/
uuid = g_object_get_qdata (G_OBJECT (master_connection),
autoconnect_root_quark ());
if (nm_streq0 (nm_settings_connection_get_uuid (slave->connection), uuid)) {
_LOGI (LOGD_CORE,
"will NOT activate slave connection '%s' (%s) as a dependency for master '%s' (%s): "
"circular dependency detected",
nm_settings_connection_get_id (slave->connection),
nm_settings_connection_get_uuid (slave->connection),
nm_settings_connection_get_id (master_connection),
nm_settings_connection_get_uuid (master_connection));
continue;
}
if (!uuid)
uuid = nm_settings_connection_get_uuid (master_connection);
g_object_set_qdata_full (G_OBJECT (slave->connection),
autoconnect_root_quark (),
g_strdup (uuid),
g_free);
if (!slave->device) {
_LOGD (LOGD_CORE,
"will NOT activate slave connection '%s' (%s) as a dependency for master '%s' (%s): "
"no compatible device found",
nm_settings_connection_get_id (slave->connection),
nm_settings_connection_get_uuid (slave->connection),
nm_settings_connection_get_id (master_connection),
nm_settings_connection_get_uuid (master_connection));
continue;
}
_LOGD (LOGD_CORE, "will activate slave connection '%s' (%s) as a dependency for master '%s' (%s)",
nm_settings_connection_get_id (slave->connection),
nm_settings_connection_get_uuid (slave->connection),
nm_settings_connection_get_id (master_connection),
nm_settings_connection_get_uuid (master_connection));
/* Schedule slave activation */
nm_manager_activate_connection (self,
slave->connection,
NULL,
NULL,
slave->device,
subject,
NM_ACTIVATION_TYPE_MANAGED,
NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES,
bind_lifetime_to_profile_visibility
? NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY
: NM_ACTIVATION_STATE_FLAG_NONE,
&local_err);
if (local_err) {
_LOGW (LOGD_CORE, "Slave connection activation failed: %s", local_err->message);
g_clear_error (&local_err);
}
}
}
}
static gboolean
_internal_activate_vpn (NMManager *self, NMActiveConnection *active, GError **error)
{
nm_assert (NM_IS_VPN_CONNECTION (active));
nm_dbus_object_export (NM_DBUS_OBJECT (active));
if (!nm_vpn_manager_activate_connection (NM_MANAGER_GET_PRIVATE (self)->vpn_manager,
NM_VPN_CONNECTION (active),
error)) {
nm_dbus_object_unexport (NM_DBUS_OBJECT (active));
return FALSE;
}
active_connection_add (self, active);
return TRUE;
}
/* Traverse the device to disconnected state. This means that the device is ready
* for connection and will proceed activating if there's an activation request
* enqueued.
*/
static void
unmanaged_to_disconnected (NMDevice *device)
{
/* when creating the software device, it can happen that the device is
* still unmanaged by NM_UNMANAGED_PLATFORM_INIT because we didn't yet
* get the udev event. At this point, we can no longer delay the activation
* and force the device to be managed. */
nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_PLATFORM_INIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);
nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_USER_EXPLICIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);
if (!nm_device_get_managed (device, FALSE)) {
/* the device is still marked as unmanaged. Nothing to do. */
return;
}
if (nm_device_get_state (device) == NM_DEVICE_STATE_UNMANAGED) {
nm_device_state_changed (device,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
}
if ( nm_device_get_state (device) == NM_DEVICE_STATE_UNAVAILABLE
&& nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)) {
nm_device_state_changed (device,
NM_DEVICE_STATE_DISCONNECTED,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
}
}
static NMActivationStateFlags
_activation_bind_lifetime_to_profile_visibility (NMAuthSubject *subject)
{
if ( nm_auth_subject_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;
int 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) : -1;
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);
g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_free);
if (priv->concheck_mgr) {
g_signal_handlers_disconnect_by_func (priv->concheck_mgr,
G_CALLBACK (concheck_config_changed_cb),
self);
g_clear_object (&priv->concheck_mgr);
}
if (priv->auth_mgr) {
g_signal_handlers_disconnect_by_func (priv->auth_mgr,
G_CALLBACK (auth_mgr_changed),
self);
g_clear_object (&priv->auth_mgr);
}
nm_assert (c_list_is_empty (&priv->devices_lst_head));
nm_clear_g_source (&priv->ac_cleanup_id);
while ((iter = c_list_first (&priv->active_connections_lst_head)))
active_connection_remove (self, c_list_entry (iter, NMActiveConnection, active_connections_lst));
nm_assert (c_list_is_empty (&priv->active_connections_lst_head));
g_clear_object (&priv->primary_connection);
g_clear_object (&priv->activating_connection);
if (priv->config) {
g_signal_handlers_disconnect_by_func (priv->config, _config_changed_cb, self);
g_clear_object (&priv->config);
}
if (priv->policy) {
g_signal_handlers_disconnect_by_func (priv->policy, policy_default_ac_changed, self);
g_signal_handlers_disconnect_by_func (priv->policy, policy_activating_ac_changed, self);
g_clear_object (&priv->policy);
}
if (priv->settings) {
g_signal_handlers_disconnect_by_func (priv->settings, settings_startup_complete_changed, self);
g_signal_handlers_disconnect_by_func (priv->settings, system_unmanaged_devices_changed_cb, self);
g_signal_handlers_disconnect_by_func (priv->settings, connection_added_cb, self);
g_signal_handlers_disconnect_by_func (priv->settings, connection_updated_cb, self);
g_signal_handlers_disconnect_by_func (priv->settings, connection_flags_changed, self);
g_clear_object (&priv->settings);
}
if (priv->hostname_manager) {
g_signal_handlers_disconnect_by_func (priv->hostname_manager, hostname_changed_cb, self);
g_clear_object (&priv->hostname_manager);
}
g_clear_object (&priv->vpn_manager);
sleep_devices_clear (self);
g_clear_pointer (&priv->sleep_devices, g_hash_table_unref);
if (priv->sleep_monitor) {
g_signal_handlers_disconnect_by_func (priv->sleep_monitor, sleeping_cb, self);
g_clear_object (&priv->sleep_monitor);
}
if (priv->fw_monitor) {
g_signal_handlers_disconnect_by_func (priv->fw_monitor, firmware_dir_changed, self);
nm_clear_g_source (&priv->fw_changed_id);
g_file_monitor_cancel (priv->fw_monitor);
g_clear_object (&priv->fw_monitor);
}
if (priv->rfkill_mgr) {
g_signal_handlers_disconnect_by_func (priv->rfkill_mgr, rfkill_manager_rfkill_changed_cb, self);
g_clear_object (&priv->rfkill_mgr);
}
nm_clear_g_source (&priv->delete_volatile_connection_idle_id);
_delete_volatile_connection_all (self, FALSE);
nm_assert (!priv->delete_volatile_connection_idle_id);
nm_assert (c_list_is_empty (&priv->delete_volatile_connection_lst_head));
nm_device_factory_manager_for_each_factory (_deinit_device_factory, self);
nm_clear_g_source (&priv->timestamp_update_id);
g_clear_pointer (&priv->device_route_metrics, g_hash_table_destroy);
G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object);
}
static void
finalize (GObject *object)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (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);
}