/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2006 - 2010 Red Hat, Inc.
* Copyright (C) 2007 - 2008 Novell, Inc.
*/
#include "nm-default.h"
#include "nm-supplicant-manager.h"
#include "nm-core-internal.h"
#include "nm-dbus-manager.h"
#include "nm-glib-aux/nm-dbus-aux.h"
#include "nm-glib-aux/nm-ref-string.h"
#include "nm-supplicant-interface.h"
#include "nm-supplicant-types.h"
#include "platform/nm-platform.h"
/*****************************************************************************/
#define CREATE_IFACE_TRY_COUNT_MAX 7u
struct _NMSupplMgrCreateIfaceHandle {
NMSupplicantManager * self;
CList create_iface_lst;
GCancellable * cancellable;
NMSupplicantManagerCreateInterfaceCb callback;
gpointer callback_user_data;
NMShutdownWaitObjHandle * shutdown_handle;
NMRefString * name_owner;
GError * fail_on_idle_error;
NMSupplicantDriver driver;
int ifindex;
guint fail_on_idle_id;
guint create_iface_try_count : 5;
};
enum {
AVAILABLE_CHANGED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = {0};
typedef struct {
GDBusConnection *dbus_connection;
NMRefString *name_owner;
GCancellable *get_name_owner_cancellable;
GCancellable *get_capabilities_cancellable;
GCancellable *poke_name_owner_cancellable;
GHashTable *supp_ifaces;
CList supp_lst_head;
CList create_iface_lst_head;
NMSupplCapMask capabilities;
guint name_owner_changed_id;
guint interface_removed_id;
guint poke_name_owner_timeout_id;
guint available_reset_id;
/* see nm_supplicant_manager_get_available(). */
NMTernary available : 2;
} NMSupplicantManagerPrivate;
struct _NMSupplicantManager {
GObject parent;
NMSupplicantManagerPrivate _priv;
};
struct _NMSupplicantManagerClass {
GObjectClass parent;
};
G_DEFINE_TYPE(NMSupplicantManager, nm_supplicant_manager, G_TYPE_OBJECT)
#define NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMSupplicantManager, NM_IS_SUPPLICANT_MANAGER)
NM_DEFINE_SINGLETON_GETTER(NMSupplicantManager,
nm_supplicant_manager_get,
NM_TYPE_SUPPLICANT_MANAGER);
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_SUPPLICANT
#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "supplicant", __VA_ARGS__)
/*****************************************************************************/
NM_CACHED_QUARK_FCN("nm-supplicant-error-quark", nm_supplicant_error_quark);
/*****************************************************************************/
static void _create_iface_proceed_all(NMSupplicantManager *self, GError *error);
static void _supp_iface_add(NMSupplicantManager * self,
NMRefString * iface_path,
NMSupplicantInterface *supp_iface);
static void _supp_iface_remove_one(NMSupplicantManager * self,
NMSupplicantInterface *supp_iface,
gboolean force_remove_from_supplicant,
const char * reason);
static void _create_iface_dbus_call_get_interface(NMSupplicantManager * self,
NMSupplMgrCreateIfaceHandle *handle,
const char * ifname);
static void _create_iface_dbus_call_create_interface(NMSupplicantManager * self,
NMSupplMgrCreateIfaceHandle *handle,
const char * ifname);
static gboolean _create_iface_fail_on_idle_cb(gpointer user_data);
static gboolean _available_reset_cb(gpointer user_data);
/*****************************************************************************/
NM_UTILS_LOOKUP_STR_DEFINE(nm_supplicant_driver_to_string,
NMSupplicantDriver,
NM_UTILS_LOOKUP_DEFAULT_WARN(NULL),
NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_UNKNOWN, "???"),
NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_WIRELESS,
NM_WPAS_DEFAULT_WIFI_DRIVER),
NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_WIRED, "wired"),
NM_UTILS_LOOKUP_ITEM(NM_SUPPLICANT_DRIVER_MACSEC, "macsec_linux"), );
/*****************************************************************************/
NMTernary
nm_supplicant_manager_is_available(NMSupplicantManager *self)
{
g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NM_TERNARY_FALSE);
return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->available;
}
NMRefString *
nm_supplicant_manager_get_dbus_name_owner(NMSupplicantManager *self)
{
g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);
return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->name_owner;
}
GDBusConnection *
nm_supplicant_manager_get_dbus_connection(NMSupplicantManager *self)
{
g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);
return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->dbus_connection;
}
NMSupplCapMask
nm_supplicant_manager_get_global_capabilities(NMSupplicantManager *self)
{
g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NM_SUPPL_CAP_MASK_NONE);
return NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->capabilities;
}
/*****************************************************************************/
static void
_caps_set(NMSupplicantManagerPrivate *priv, NMSupplCapType type, NMTernary value)
{
priv->capabilities = NM_SUPPL_CAP_MASK_SET(priv->capabilities, type, value);
}
static char
_caps_to_char(NMSupplicantManagerPrivate *priv, NMSupplCapType type)
{
NMTernary val;
val = NM_SUPPL_CAP_MASK_GET(priv->capabilities, type);
if (val == NM_TERNARY_TRUE)
return '+';
if (val == NM_TERNARY_FALSE)
return '-';
return '?';
}
/*****************************************************************************/
static void
_dbus_call_remove_interface(GDBusConnection *dbus_connection,
const char * name_owner,
const char * iface_path)
{
nm_assert(G_IS_DBUS_CONNECTION(dbus_connection));
nm_assert(name_owner);
nm_assert(iface_path);
g_dbus_connection_call(dbus_connection,
name_owner,
NM_WPAS_DBUS_PATH,
NM_WPAS_DBUS_INTERFACE,
"RemoveInterface",
g_variant_new("(o)", iface_path),
G_VARIANT_TYPE("()"),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
10000,
NULL,
NULL,
NULL);
}
void
_nm_supplicant_manager_dbus_call_remove_interface(NMSupplicantManager *self,
const char * name_owner,
const char * iface_path)
{
_dbus_call_remove_interface(NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->dbus_connection,
name_owner,
iface_path);
}
/*****************************************************************************/
static void
on_supplicant_wfd_ies_set(GObject *source_object, GAsyncResult *result, gpointer user_data)
{
gs_unref_variant GVariant *res = NULL;
gs_free_error GError *error = NULL;
res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), result, &error);
if (!res)
_LOGD("failed to set WFD IEs on wpa_supplicant: %s", error->message);
}
/**
* nm_supplicant_manager_set_wfd_ies:
* @self: the #NMSupplicantManager
* @wfd_ies: a #GBytes with the WFD IEs or %NULL
*
* This function sets the global WFD IEs on wpa_supplicant. Note that
* it would make more sense if this was per-device, but wpa_supplicant
* simply does not work that way.
* */
void
nm_supplicant_manager_set_wfd_ies(NMSupplicantManager *self, GBytes *wfd_ies)
{
NMSupplicantManagerPrivate *priv;
GVariantBuilder params;
g_return_if_fail(NM_IS_SUPPLICANT_MANAGER(self));
priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
if (!priv->name_owner)
return;
_LOGD("setting WFD IEs for P2P operation on %s", priv->name_owner->str);
g_variant_builder_init(¶ms, G_VARIANT_TYPE("(ssv)"));
g_variant_builder_add(¶ms, "s", NM_WPAS_DBUS_INTERFACE);
g_variant_builder_add(¶ms, "s", "WFDIEs");
g_variant_builder_add_value(¶ms,
g_variant_new_variant(nm_utils_gbytes_to_variant_ay(wfd_ies)));
g_dbus_connection_call(priv->dbus_connection,
priv->name_owner->str,
NM_WPAS_DBUS_PATH,
DBUS_INTERFACE_PROPERTIES,
"Set",
g_variant_builder_end(¶ms),
G_VARIANT_TYPE("()"),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
3000,
NULL,
on_supplicant_wfd_ies_set,
NULL);
}
/*****************************************************************************/
static gboolean
_poke_name_owner_timeout_cb(gpointer user_data)
{
NMSupplicantManager * self = user_data;
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
gs_free_error GError *error = NULL;
gboolean available_changed = FALSE;
nm_assert(!priv->name_owner);
priv->poke_name_owner_timeout_id = 0;
nm_clear_g_cancellable(&priv->poke_name_owner_cancellable);
_LOGT("poke service \"%s\" failed for good with timeout%s",
NM_WPAS_DBUS_SERVICE,
(priv->available == NM_TERNARY_DEFAULT) ? " (set as not available)" : "");
if (priv->available == NM_TERNARY_DEFAULT) {
/* the available flag usually only changes together with the name-owner.
* However, if we tries to poke the service but failed to start it (with
* timeout), was also set it as (hard) not available. */
priv->available = NM_TERNARY_FALSE;
nm_clear_g_source(&priv->available_reset_id);
priv->available_reset_id = g_timeout_add_seconds(60, _available_reset_cb, self);
available_changed = TRUE;
}
nm_utils_error_set(&error,
NM_UTILS_ERROR_UNKNOWN,
"Failed to D-Bus activate wpa_supplicant service");
_create_iface_proceed_all(self, error);
if (available_changed) {
/* We delay the emitting of the notification after aborting all
* create-iface handles. */
g_signal_emit(self, signals[AVAILABLE_CHANGED], 0);
}
return G_SOURCE_REMOVE;
}
static void
_poke_name_owner_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
gs_unref_variant GVariant *res = NULL;
gs_free_error GError *error = NULL;
res = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
if (nm_utils_error_is_cancelled(error))
return;
if (!res)
_LOGT("poke service \"%s\" failed: %s", NM_WPAS_DBUS_SERVICE, error->message);
else
_LOGT("poke service \"%s\" succeeded", NM_WPAS_DBUS_SERVICE);
/* in both cases, we react the same: we wait for the name owner to appear
* or hit the timeout. */
}
static void
_poke_name_owner(NMSupplicantManager *self)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
if (priv->poke_name_owner_cancellable)
return;
_LOGT("poke service \"%s\"...", NM_WPAS_DBUS_SERVICE);
priv->poke_name_owner_cancellable = g_cancellable_new();
priv->poke_name_owner_timeout_id = g_timeout_add(3000, _poke_name_owner_timeout_cb, self);
nm_dbus_connection_call_start_service_by_name(priv->dbus_connection,
NM_WPAS_DBUS_SERVICE,
5000,
priv->poke_name_owner_cancellable,
_poke_name_owner_cb,
self);
}
/*****************************************************************************/
static void
_create_iface_complete(NMSupplMgrCreateIfaceHandle *handle,
NMSupplicantInterface * supp_iface,
GError * error)
{
nm_assert(!supp_iface || NM_IS_SUPPLICANT_INTERFACE(supp_iface));
nm_assert((!!supp_iface) != (!!error));
c_list_unlink(&handle->create_iface_lst);
nm_clear_g_source(&handle->fail_on_idle_id);
if (handle->callback) {
NMSupplicantManagerCreateInterfaceCb callback;
nm_assert(NM_IS_SUPPLICANT_MANAGER(handle->self));
callback = handle->callback;
handle->callback = NULL;
callback(handle->self, handle, supp_iface, error, handle->callback_user_data);
}
g_clear_error(&handle->fail_on_idle_error);
g_clear_object(&handle->self);
if (handle->shutdown_handle) {
/* we have a pending CreateInterface request. We keep the handle
* instance alive. This is to remove the device again, once the
* request completes. */
return;
}
nm_clear_g_cancellable(&handle->cancellable);
nm_ref_string_unref(handle->name_owner);
nm_g_slice_free_fcn(handle);
}
static void
_create_iface_add(NMSupplicantManager * self,
NMSupplMgrCreateIfaceHandle *handle,
const char * iface_path_str,
gboolean created_by_us)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
nm_auto_ref_string NMRefString *iface_path = NULL;
gs_unref_object NMSupplicantInterface *supp_iface = NULL;
iface_path = nm_ref_string_new(iface_path_str);
supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path);
if (supp_iface) {
/* Now this is odd... Reuse the same interface. */
g_object_ref(supp_iface);
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: interface %s on %s created (already existing)",
NM_HASH_OBFUSCATE_PTR(handle),
iface_path_str,
priv->name_owner->str);
_create_iface_complete(handle, supp_iface, NULL);
return;
}
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: interface %s on %s created%s",
NM_HASH_OBFUSCATE_PTR(handle),
iface_path_str,
priv->name_owner->str,
created_by_us ? " (created by us)" : "");
supp_iface = nm_supplicant_interface_new(self, iface_path, handle->ifindex, handle->driver);
_supp_iface_add(self, iface_path, supp_iface);
_create_iface_complete(handle, supp_iface, NULL);
}
static void
_create_iface_dbus_call_get_interface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
GDBusConnection * dbus_connection = G_DBUS_CONNECTION(source);
NMSupplMgrCreateIfaceHandle *handle;
NMSupplicantManager * self;
NMSupplicantManagerPrivate * priv;
gs_unref_variant GVariant *res = NULL;
gs_free_error GError *error = NULL;
const char * iface_path_str;
res = g_dbus_connection_call_finish(dbus_connection, result, &error);
if (nm_utils_error_is_cancelled(error))
return;
handle = user_data;
nm_assert(handle->callback);
self = handle->self;
priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
nm_assert(handle->name_owner == priv->name_owner);
if (!res) {
char ifname[NMP_IFNAMSIZ];
if (handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX
&& _nm_dbus_error_has_name(error, NM_WPAS_ERROR_UNKNOWN_IFACE)
&& nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) {
/* Before, supplicant told us the interface existed. Was there a race?
* Try again. */
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: D-Bus call failed to get interface. Try to create it again (ifname \"%s\")",
NM_HASH_OBFUSCATE_PTR(handle),
ifname);
_create_iface_dbus_call_create_interface(self, handle, ifname);
return;
}
g_clear_object(&handle->cancellable);
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: D-Bus call to get interface failed: %s",
NM_HASH_OBFUSCATE_PTR(handle),
error->message);
_create_iface_complete(handle, NULL, error);
return;
}
g_clear_object(&handle->cancellable);
g_variant_get(res, "(&o)", &iface_path_str);
_create_iface_add(self, handle, iface_path_str, FALSE);
}
static void
_create_iface_dbus_call_create_interface_cb(GObject * source,
GAsyncResult *result,
gpointer user_data)
{
GDBusConnection * dbus_connection = G_DBUS_CONNECTION(source);
NMSupplMgrCreateIfaceHandle *handle = user_data;
NMSupplicantManager * self;
NMSupplicantManagerPrivate * priv;
gs_unref_variant GVariant *res = NULL;
gs_free_error GError *error = NULL;
const char * iface_path_str;
char ifname[NMP_IFNAMSIZ];
res = g_dbus_connection_call_finish(dbus_connection, result, &error);
nm_shutdown_wait_obj_unregister(g_steal_pointer(&handle->shutdown_handle));
if (!res) {
if (handle->callback && ({
nm_assert(handle->self);
TRUE;
})
&& _nm_dbus_error_has_name(error, NM_WPAS_ERROR_EXISTS_ERROR)
&& nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) {
self = handle->self;
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: D-Bus call failed to create interface. Try to get existing interface (ifname "
"\"%s\")",
NM_HASH_OBFUSCATE_PTR(handle),
ifname);
_create_iface_dbus_call_get_interface(self, handle, ifname);
return;
}
g_clear_object(&handle->cancellable);
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: D-Bus call failed: %s",
NM_HASH_OBFUSCATE_PTR(handle),
error->message);
_create_iface_complete(handle, NULL, error);
return;
}
g_clear_object(&handle->cancellable);
self = handle->self;
priv = self ? NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) : NULL;
g_variant_get(res, "(&o)", &iface_path_str);
if (!handle->callback || priv->name_owner != handle->name_owner) {
if (!handle->callback) {
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: request already cancelled but still remove interface %s in %s",
NM_HASH_OBFUSCATE_PTR(handle),
iface_path_str,
handle->name_owner->str);
nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "Request already cancelled");
} else {
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: name owner changed, still remove interface %s in %s",
NM_HASH_OBFUSCATE_PTR(handle),
iface_path_str,
handle->name_owner->str);
nm_utils_error_set(&error,
NM_UTILS_ERROR_UNKNOWN,
"The name owner changed since creating the interface");
}
_dbus_call_remove_interface(dbus_connection, handle->name_owner->str, iface_path_str);
_create_iface_complete(handle, NULL, error);
return;
}
_create_iface_add(self, handle, iface_path_str, TRUE);
}
static void
_create_iface_dbus_call_get_interface(NMSupplicantManager * self,
NMSupplMgrCreateIfaceHandle *handle,
const char * ifname)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
nm_assert(handle->cancellable);
nm_assert(!handle->shutdown_handle);
g_dbus_connection_call(priv->dbus_connection,
priv->name_owner->str,
NM_WPAS_DBUS_PATH,
NM_WPAS_DBUS_INTERFACE,
"GetInterface",
g_variant_new("(s)", ifname),
G_VARIANT_TYPE("(o)"),
G_DBUS_CALL_FLAGS_NONE,
5000,
handle->cancellable,
_create_iface_dbus_call_get_interface_cb,
handle);
}
static void
_create_iface_dbus_call_create_interface(NMSupplicantManager * self,
NMSupplMgrCreateIfaceHandle *handle,
const char * ifname)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
GVariantBuilder builder;
nm_assert(priv->name_owner == handle->name_owner);
nm_assert(handle->cancellable);
nm_assert(!handle->shutdown_handle);
nm_assert(handle->create_iface_try_count <= CREATE_IFACE_TRY_COUNT_MAX);
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder,
"{sv}",
"Driver",
g_variant_new_string(nm_supplicant_driver_to_string(handle->driver)));
g_variant_builder_add(&builder, "{sv}", "Ifname", g_variant_new_string(ifname));
handle->shutdown_handle = nm_shutdown_wait_obj_register_cancellable_full(
handle->cancellable,
g_strdup_printf("wpas-create-" NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(handle)),
TRUE);
handle->create_iface_try_count++;
g_dbus_connection_call(priv->dbus_connection,
handle->name_owner->str,
NM_WPAS_DBUS_PATH,
NM_WPAS_DBUS_INTERFACE,
"CreateInterface",
g_variant_new("(a{sv})", &builder),
G_VARIANT_TYPE("(o)"),
G_DBUS_CALL_FLAGS_NONE,
5000,
handle->cancellable,
_create_iface_dbus_call_create_interface_cb,
handle);
}
static void
_create_iface_dbus_start(NMSupplicantManager *self, NMSupplMgrCreateIfaceHandle *handle)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
char ifname[NMP_IFNAMSIZ];
nm_assert(priv->name_owner);
nm_assert(!handle->cancellable);
if (!nm_platform_if_indextoname(NM_PLATFORM_GET, handle->ifindex, ifname)) {
nm_utils_error_set(&handle->fail_on_idle_error,
NM_UTILS_ERROR_UNKNOWN,
"Cannot find interface %d",
handle->ifindex);
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: creating interface fails to find interface name for ifindex %d",
NM_HASH_OBFUSCATE_PTR(handle),
handle->ifindex);
handle->fail_on_idle_id = g_idle_add(_create_iface_fail_on_idle_cb, handle);
return;
}
/* Our handle keeps @self alive. That means, when NetworkManager shall shut
* down, it's the responsibility of the callers to cancel the handles,
* to initiate coordinated shutdown.
*
* However, we now issue a CreateInterface call. Even if the handle gets cancelled
* (because of shutdown, or because the caller is no longer interested in the
* result), we don't want to cancel this request. Instead, we want to get
* the interface path and remove it right away.
*
* That means, the D-Bus call cannot be cancelled (because we always care about
* the result). Only the @handle can be cancelled, but parts of the handle will
* stick around to complete the task.
*
* See also handle->shutdown_handle.
*/
handle->name_owner = nm_ref_string_ref(priv->name_owner);
handle->cancellable = g_cancellable_new();
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: creating interface (ifname \"%s\")...",
NM_HASH_OBFUSCATE_PTR(handle),
ifname);
_create_iface_dbus_call_create_interface(self, handle, ifname);
}
static gboolean
_create_iface_fail_on_idle_cb(gpointer user_data)
{
NMSupplMgrCreateIfaceHandle *handle = user_data;
handle->fail_on_idle_id = 0;
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: fail with internal error: %s",
NM_HASH_OBFUSCATE_PTR(handle),
handle->fail_on_idle_error->message);
_create_iface_complete(handle, NULL, handle->fail_on_idle_error);
return G_SOURCE_REMOVE;
}
NMSupplMgrCreateIfaceHandle *
nm_supplicant_manager_create_interface(NMSupplicantManager * self,
int ifindex,
NMSupplicantDriver driver,
NMSupplicantManagerCreateInterfaceCb callback,
gpointer user_data)
{
NMSupplicantManagerPrivate * priv;
NMSupplMgrCreateIfaceHandle *handle;
g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);
g_return_val_if_fail(ifindex > 0, NULL);
g_return_val_if_fail(callback, NULL);
nm_assert(nm_supplicant_driver_to_string(driver));
priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
handle = g_slice_new(NMSupplMgrCreateIfaceHandle);
*handle = (NMSupplMgrCreateIfaceHandle){
.self = g_object_ref(self),
.callback = callback,
.callback_user_data = user_data,
.driver = driver,
.ifindex = ifindex,
};
c_list_link_tail(&priv->create_iface_lst_head, &handle->create_iface_lst);
if (!priv->dbus_connection) {
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: new request interface %d (driver %s). Fail because no D-Bus connection to talk "
"to wpa_supplicant...",
NM_HASH_OBFUSCATE_PTR(handle),
ifindex,
nm_supplicant_driver_to_string(driver));
nm_utils_error_set(&handle->fail_on_idle_error,
NM_UTILS_ERROR_UNKNOWN,
"No D-Bus connection to talk to wpa_supplicant");
handle->fail_on_idle_id = g_idle_add(_create_iface_fail_on_idle_cb, handle);
return handle;
}
if (!priv->name_owner) {
_LOGT(
"create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: new request interface %d (driver %s). %s",
NM_HASH_OBFUSCATE_PTR(handle),
ifindex,
nm_supplicant_driver_to_string(driver),
priv->poke_name_owner_cancellable ? "Waiting for supplicant..." : "Poke supplicant...");
_poke_name_owner(self);
return handle;
}
if (priv->get_capabilities_cancellable) {
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: new request interface %d (driver %s). Waiting to fetch capabilities for %s...",
NM_HASH_OBFUSCATE_PTR(handle),
ifindex,
nm_supplicant_driver_to_string(driver),
priv->name_owner->str);
return handle;
}
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT
"]: new request interface %d (driver %s). create interface on %s...",
NM_HASH_OBFUSCATE_PTR(handle),
ifindex,
nm_supplicant_driver_to_string(driver),
priv->name_owner->str);
_create_iface_dbus_start(self, handle);
return handle;
}
static void
_create_iface_proceed_all(NMSupplicantManager *self, GError *error)
{
NMSupplicantManagerPrivate * priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
NMSupplMgrCreateIfaceHandle *handle;
nm_assert(error || priv->name_owner);
nm_assert(error || !priv->get_capabilities_cancellable);
if (c_list_is_empty(&priv->create_iface_lst_head))
return;
if (error) {
CList alt_list;
/* we move the handles we want to proceed to a alternative list.
* That is, because we invoke callbacks to the caller, who might
* create another request right away. We don't want to proceed
* that one. */
c_list_init(&alt_list);
c_list_splice(&alt_list, &priv->create_iface_lst_head);
while ((handle =
c_list_last_entry(&alt_list, NMSupplMgrCreateIfaceHandle, create_iface_lst))) {
/* We don't need to keep @self alive. Every handle holds a reference already. */
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: create interface failed: %s",
NM_HASH_OBFUSCATE_PTR(handle),
error->message);
_create_iface_complete(handle, NULL, error);
}
return;
}
/* start all the handles. This does not invoke callbacks, so the list of handles
* cannot be modified while we iterate it. */
c_list_for_each_entry (handle, &priv->create_iface_lst_head, create_iface_lst) {
_LOGT("create-iface[" NM_HASH_OBFUSCATE_PTR_FMT "]: create interface on %s...",
NM_HASH_OBFUSCATE_PTR(handle),
priv->name_owner->str);
_create_iface_dbus_start(self, handle);
}
}
void
nm_supplicant_manager_create_interface_cancel(NMSupplMgrCreateIfaceHandle *handle)
{
gs_free_error GError *error = NULL;
if (!handle)
return;
g_return_if_fail(NM_IS_SUPPLICANT_MANAGER(handle->self));
g_return_if_fail(handle->callback);
nm_assert(!c_list_is_empty(&handle->create_iface_lst));
nm_utils_error_set_cancelled(&error, FALSE, NULL);
_create_iface_complete(handle, NULL, error);
}
NMSupplicantInterface *
nm_supplicant_manager_create_interface_from_path(NMSupplicantManager *self, const char *object_path)
{
NMSupplicantManagerPrivate *priv;
NMSupplicantInterface * supp_iface;
nm_auto_ref_string NMRefString *iface_path = NULL;
g_return_val_if_fail(NM_IS_SUPPLICANT_MANAGER(self), NULL);
g_return_val_if_fail(object_path, NULL);
priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
iface_path = nm_ref_string_new(object_path);
supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path);
if (supp_iface)
return g_object_ref(supp_iface);
supp_iface = nm_supplicant_interface_new(self, iface_path, 0, NM_SUPPLICANT_DRIVER_UNKNOWN);
_supp_iface_add(self, iface_path, supp_iface);
return supp_iface;
}
/*****************************************************************************/
static void
_dbus_interface_removed_cb(GDBusConnection *connection,
const char * sender_name,
const char * object_path,
const char * signal_interface_name,
const char * signal_name,
GVariant * parameters,
gpointer user_data)
{
NMSupplicantManager * self = user_data;
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
NMSupplicantInterface * supp_iface;
const char * iface_path_str;
nm_auto_ref_string NMRefString *iface_path = NULL;
nm_assert(nm_streq(sender_name, priv->name_owner->str));
if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(o)")))
return;
g_variant_get(parameters, "(&o)", &iface_path_str);
iface_path = nm_ref_string_new(iface_path_str);
supp_iface = g_hash_table_lookup(priv->supp_ifaces, iface_path);
if (!supp_iface)
return;
_supp_iface_remove_one(self, supp_iface, FALSE, "InterfaceRemoved signal from wpa_supplicant");
}
/*****************************************************************************/
static void
_dbus_get_capabilities_cb(GVariant *res, GError *error, gpointer user_data)
{
NMSupplicantManager * self;
NMSupplicantManagerPrivate *priv;
if (nm_utils_error_is_cancelled(error))
return;
self = user_data;
priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
g_clear_object(&priv->get_capabilities_cancellable);
/* The supplicant only advertises global capabilities if the following
* commit has been applied:
*
* commit 1634ac0654eba8d458640a115efc0a6cde3bac4d
* Author: Dan Williams <dcbw@redhat.com>
* Date: Sat Sep 29 19:06:30 2012 +0300
*
* dbus: Add global capabilities property
*/
_caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_DEFAULT);
_caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_DEFAULT);
_caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_DEFAULT);
/* Support for the following is newer than the capabilities property */
_caps_set(priv, NM_SUPPL_CAP_TYPE_P2P, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_FT, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_FALSE);
if (res) {
nm_auto_free_variant_iter GVariantIter *res_iter = NULL;
const char * res_key;
GVariant * res_val;
g_variant_get(res, "(a{sv})", &res_iter);
while (g_variant_iter_loop(res_iter, "{&sv}", &res_key, &res_val)) {
if (nm_streq(res_key, "Capabilities")) {
if (g_variant_is_of_type(res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
gs_free const char **array = NULL;
const char ** a;
array = g_variant_get_strv(res_val, NULL);
_caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_FALSE);
_caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_FALSE);
if (array) {
for (a = array; *a; a++) {
if (nm_streq(*a, "ap")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_TRUE);
continue;
}
if (nm_streq(*a, "pmf")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_TRUE);
continue;
}
if (nm_streq(*a, "fils")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_TRUE);
continue;
}
if (nm_streq(*a, "p2p")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_P2P, NM_TERNARY_TRUE);
continue;
}
if (nm_streq(*a, "ft")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_FT, NM_TERNARY_TRUE);
continue;
}
if (nm_streq(*a, "sha384")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_TRUE);
continue;
}
if (nm_streq(*a, "mesh")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_TRUE);
continue;
}
if (nm_streq(*a, "suiteb192")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_SUITEB192, NM_TERNARY_TRUE);
continue;
}
}
}
}
continue;
}
if (nm_streq(res_key, "EapMethods")) {
if (g_variant_is_of_type(res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
gs_free const char **array = NULL;
const char ** a;
array = g_variant_get_strv(res_val, NULL);
if (array) {
for (a = array; *a; a++) {
if (g_ascii_strcasecmp(*a, "FAST") == 0) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_TRUE);
break;
}
}
}
}
continue;
}
if (nm_streq(res_key, "WFDIEs")) {
_caps_set(priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_TRUE);
continue;
}
}
}
_LOGD("supported features:"
" AP%c"
" PMF%c"
" FILS%c"
" P2P%c"
" FT%c"
" SHA384%c"
" MESH%c"
" FAST%c"
" WFD%c"
"",
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_AP),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_PMF),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_FILS),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_P2P),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_FT),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_SHA384),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_MESH),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_FAST),
_caps_to_char(priv, NM_SUPPL_CAP_TYPE_WFD));
nm_assert(g_hash_table_size(priv->supp_ifaces) == 0);
nm_assert(c_list_is_empty(&priv->supp_lst_head));
_create_iface_proceed_all(self, NULL);
}
/*****************************************************************************/
void
_nm_supplicant_manager_unregister_interface(NMSupplicantManager * self,
NMSupplicantInterface *supp_iface)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
nm_assert(NM_IS_SUPPLICANT_INTERFACE(supp_iface));
nm_assert(c_list_contains(&NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->supp_lst_head,
&supp_iface->supp_lst));
c_list_unlink(&supp_iface->supp_lst);
if (!g_hash_table_remove(priv->supp_ifaces,
nm_supplicant_interface_get_object_path(supp_iface)))
nm_assert_not_reached();
}
static void
_supp_iface_add(NMSupplicantManager * self,
NMRefString * iface_path,
NMSupplicantInterface *supp_iface)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
c_list_link_tail(&priv->supp_lst_head, &supp_iface->supp_lst);
if (!g_hash_table_insert(priv->supp_ifaces, iface_path, supp_iface))
nm_assert_not_reached();
}
static void
_supp_iface_remove_one(NMSupplicantManager * self,
NMSupplicantInterface *supp_iface,
gboolean force_remove_from_supplicant,
const char * reason)
{
#if NM_MORE_ASSERTS
_nm_unused gs_unref_object NMSupplicantInterface *supp_iface_keep_alive =
g_object_ref(supp_iface);
#endif
nm_assert(NM_IS_SUPPLICANT_MANAGER(self));
nm_assert(NM_IS_SUPPLICANT_INTERFACE(supp_iface));
nm_assert(c_list_contains(&NM_SUPPLICANT_MANAGER_GET_PRIVATE(self)->supp_lst_head,
&supp_iface->supp_lst));
_nm_supplicant_interface_set_state_down(supp_iface, force_remove_from_supplicant, reason);
nm_assert(c_list_is_empty(&supp_iface->supp_lst));
}
static void
_supp_iface_remove_all(NMSupplicantManager *self,
gboolean force_remove_from_supplicant,
const char * reason)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
NMSupplicantInterface * supp_iface;
while ((supp_iface = c_list_first_entry(&priv->supp_lst_head, NMSupplicantInterface, supp_lst)))
_supp_iface_remove_one(self, supp_iface, force_remove_from_supplicant, reason);
}
/*****************************************************************************/
static gboolean
_available_reset_cb(gpointer user_data)
{
NMSupplicantManager * self = user_data;
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
priv->available_reset_id = 0;
nm_assert(priv->available == NM_TERNARY_FALSE);
priv->available = NM_TERNARY_DEFAULT;
g_signal_emit(self, signals[AVAILABLE_CHANGED], 0);
return G_SOURCE_REMOVE;
}
/*****************************************************************************/
static void
name_owner_changed(NMSupplicantManager *self, const char *name_owner, gboolean first_time)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
NMTernary available;
gboolean available_changed = FALSE;
nm_assert(!priv->get_name_owner_cancellable);
nm_assert(!name_owner || name_owner[0]);
nm_assert((first_time && !priv->name_owner)
|| (!first_time && (!!priv->name_owner) != (!!name_owner)));
if (first_time) {
_LOGD("wpa_supplicant name owner %s%s%s (%srunning)",
NM_PRINT_FMT_QUOTE_STRING(name_owner),
name_owner ? "" : "not ");
} else {
_LOGD("wpa_supplicant name owner \"%s\" %s (%srunning)",
name_owner ?: priv->name_owner->str,
name_owner ? "disappeared" : "appeared",
name_owner ? "" : "not ");
}
nm_ref_string_unref(priv->name_owner);
priv->name_owner = nm_ref_string_new(name_owner);
nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->interface_removed_id);
if (name_owner) {
if (nm_clear_g_source(&priv->poke_name_owner_timeout_id))
_LOGT("poke service \"%s\" completed with name owner change", NM_WPAS_DBUS_SERVICE);
nm_clear_g_cancellable(&priv->poke_name_owner_cancellable);
}
nm_clear_g_cancellable(&priv->get_capabilities_cancellable);
priv->capabilities = NM_SUPPL_CAP_MASK_NONE;
if (priv->name_owner) {
priv->get_capabilities_cancellable = g_cancellable_new();
nm_dbus_connection_call_get_all(priv->dbus_connection,
priv->name_owner->str,
NM_WPAS_DBUS_PATH,
NM_WPAS_DBUS_INTERFACE,
5000,
priv->get_capabilities_cancellable,
_dbus_get_capabilities_cb,
self);
priv->interface_removed_id = g_dbus_connection_signal_subscribe(priv->dbus_connection,
priv->name_owner->str,
NM_WPAS_DBUS_INTERFACE,
"InterfaceRemoved",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
_dbus_interface_removed_cb,
self,
NULL);
}
/* if supplicant is running (has a name owner), we may use it.
* If this is the first time, and supplicant is not running, we
* may also use it (and assume that we probably could D-Bus activate
* it).
*
* Otherwise, somebody else stopped supplicant. It's no longer useable to
* us and we block auto starting it. The user has to start the service...
*
* Actually, below we reset the hard block after a short timeout. This
* causes the caller to notify that supplicant may now by around and
* retry to D-Bus activate it. */
if (priv->name_owner)
available = NM_TERNARY_TRUE;
else if (first_time)
available = NM_TERNARY_DEFAULT;
else
available = NM_TERNARY_FALSE;
if (priv->available != available) {
priv->available = available;
_LOGD("supplicant is now %savailable",
available == FALSE ? "not " : (available == TRUE ? "" : "maybe "));
available_changed = TRUE;
nm_clear_g_source(&priv->available_reset_id);
if (available == NM_TERNARY_FALSE) {
/* reset the availability from a hard "no" to a "maybe" in a bit. */
priv->available_reset_id = g_timeout_add_seconds(60, _available_reset_cb, self);
}
}
_supp_iface_remove_all(self, TRUE, "name-owner changed");
if (!priv->name_owner) {
if (priv->poke_name_owner_timeout_id) {
/* we are still poking for the service to start. Don't cancel
* the pending create requests just yet. */
} else {
gs_free_error GError *local_error = NULL;
/* When we loose the name owner, we fail all pending creation requests. */
nm_utils_error_set(&local_error, NM_UTILS_ERROR_UNKNOWN, "Name owner lost");
_create_iface_proceed_all(self, local_error);
}
} else {
/* We got a name-owner, but we don't do anything. Instead let
* _dbus_get_capabilities_cb() complete and kick of the create-iface
* handles.
*
* Note that before the first name-owner change, all create-iface
* requests fail right away. So we don't have to handle them here
* (by starting to poke the service). */
}
if (available_changed)
g_signal_emit(self, signals[AVAILABLE_CHANGED], 0);
}
static void
name_owner_changed_cb(GDBusConnection *connection,
const char * sender_name,
const char * object_path,
const char * interface_name,
const char * signal_name,
GVariant * parameters,
gpointer user_data)
{
gs_unref_object NMSupplicantManager *self = g_object_ref(user_data);
NMSupplicantManagerPrivate * priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
const char * name_owner;
if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)")))
return;
if (priv->get_name_owner_cancellable)
return;
g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &name_owner);
name_owner = nm_str_not_empty(name_owner);
if (nm_streq0(name_owner, nm_ref_string_get_str(priv->name_owner)))
return;
if (name_owner && priv->name_owner) {
/* odd, we directly switch from one name owner to the next. Can't allow that.
* First clear the name owner before resetting. */
name_owner_changed(self, NULL, FALSE);
}
name_owner_changed(user_data, name_owner, FALSE);
}
static void
get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data)
{
NMSupplicantManager * self = user_data;
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
if (!name_owner && nm_utils_error_is_cancelled(error))
return;
self = user_data;
priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
g_clear_object(&priv->get_name_owner_cancellable);
name_owner_changed(self, nm_str_not_empty(name_owner), TRUE);
}
/*****************************************************************************/
static void
nm_supplicant_manager_init(NMSupplicantManager *self)
{
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
nm_assert(priv->capabilities == NM_SUPPL_CAP_MASK_NONE);
nm_assert(priv->available == NM_TERNARY_FALSE);
priv->supp_ifaces = g_hash_table_new(nm_direct_hash, NULL);
c_list_init(&priv->supp_lst_head);
c_list_init(&priv->create_iface_lst_head);
priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET);
if (!priv->dbus_connection) {
_LOGI("no D-Bus connection to talk to wpa_supplicant");
return;
}
priv->name_owner_changed_id =
nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection,
NM_WPAS_DBUS_SERVICE,
name_owner_changed_cb,
self,
NULL);
priv->get_name_owner_cancellable = g_cancellable_new();
nm_dbus_connection_call_get_name_owner(priv->dbus_connection,
NM_WPAS_DBUS_SERVICE,
-1,
priv->get_name_owner_cancellable,
get_name_owner_cb,
self);
}
static void
dispose(GObject *object)
{
NMSupplicantManager * self = (NMSupplicantManager *) object;
NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
_supp_iface_remove_all(self, TRUE, "NMSupplicantManager is disposing");
nm_assert(c_list_is_empty(&priv->create_iface_lst_head));
nm_clear_g_source(&priv->available_reset_id);
priv->available = NM_TERNARY_FALSE;
nm_clear_pointer(&priv->name_owner, nm_ref_string_unref);
nm_clear_g_source(&priv->poke_name_owner_timeout_id);
nm_clear_g_cancellable(&priv->poke_name_owner_cancellable);
nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->interface_removed_id);
nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
nm_clear_g_cancellable(&priv->get_name_owner_cancellable);
nm_clear_g_cancellable(&priv->get_capabilities_cancellable);
G_OBJECT_CLASS(nm_supplicant_manager_parent_class)->dispose(object);
g_clear_object(&priv->dbus_connection);
nm_clear_pointer(&priv->supp_ifaces, g_hash_table_destroy);
}
static void
nm_supplicant_manager_class_init(NMSupplicantManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->dispose = dispose;
signals[AVAILABLE_CHANGED] = g_signal_new(NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
}