/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2007 - 2009 Novell, Inc.
* Copyright (C) 2007 - 2017 Red Hat, Inc.
*/
#include "src/core/nm-default-daemon.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,
DEVICE_IFINDEX_CHANGED,
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; /* Oldest ACs at the beginning */
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 entries 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_tail(&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_prev (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) {
AsyncOpData *async_op_data;
c_list_for_each_entry (async_op_data, &priv->async_op_lst_head, async_op_lst) {
NMSettingsConnection *ac_conn;
ac = async_op_data->ac_auth.active;
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 (!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 (!nm_streq(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;
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;
}
}
/* All NMDevice must be ready. But also NMSettings tracks profiles that wait for
* ready devices via "connection.wait-device-timeout".
*
* Note that we only re-check nm_settings_get_startup_complete_blocked_reason() when
* all of the devices become ready (again).
*
* For example, assume we have device "eth1" and "profile-eth2" which waits for "eth2".
* If "eth1" is ready (no pending action), we only need to re-evaluate "profile-eth2"
* if we have another device ("eth2"), that becomes non-ready (had pending actions)
* and again become ready. We don't need to check "profile-eth2" until "eth2" becomes
* non-ready.
* That is why nm_settings_get_startup_complete_blocked_reason() only has any significance
* if all devices are ready too. It allows us to cut down the number of checks whether
* NMSettings is ready. That's because we don't need to re-evaluate on minor changes of
* a device, only when all devices become managed and ready. */
g_signal_handlers_block_by_func(priv->settings, settings_startup_complete_changed, self);
reason = nm_settings_get_startup_complete_blocked_reason(priv->settings, TRUE);
g_signal_handlers_unblock_by_func(priv->settings, settings_startup_complete_changed, self);
if (reason) {
_LOGD(LOGD_CORE, "startup complete is waiting for connection (%s)", 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(user_data,
&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;
chain = nm_auth_chain_new_subject(subject, context, device_auth_done_cb, self);
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) {
gs_unref_object NMConnection *con2 = NULL;
con2 = nm_simple_connection_new_clone(
nm_settings_connection_get_connection(sett_conn));
/* Reset the IPv4 setting to empty method=auto, regardless of what assumption guessed. */
nm_connection_add_setting(con2,
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,
con2,
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 && nm_streq0(nm_device_get_iface(candidate), ip_iface)
&& 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_availability_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 (!nm_streq(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 nm_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(master_device));
} else {
g_prefix_error(error,
"Master connection '%s' can't be activated: ",
nm_settings_connection_get_id(master_connection));
}
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:
_delete_volatile_connection_do(self, nm_active_connection_get_settings_connection(active));
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:
_delete_volatile_connection_do(self, connection);
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;
chain = nm_auth_chain_new_subject(subject, invocation, deactivate_net_auth_done_cb, self);
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");
sleep_devices_clear(self);
if (waking_from_suspend) {
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 {
uint32_t idx;
uint8_t type;
uint8_t op;
uint8_t soft;
uint8_t 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_unblock_failed_ovs_interfaces(NMManager *self)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self);
nm_policy_unblock_failed_ovs_interfaces(priv->policy);
}
/*****************************************************************************/
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);
}
void
nm_manager_emit_device_ifindex_changed(NMManager *self, NMDevice *device)
{
g_signal_emit(self, signals[DEVICE_IFINDEX_CHANGED], 0, device);
}
/*****************************************************************************/
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(self);
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_prev (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",
"au",
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);
signals[DEVICE_IFINDEX_CHANGED] = g_signal_new(NM_MANAGER_DEVICE_IFINDEX_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
NM_TYPE_DEVICE);
}