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