/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2006 - 2010 Red Hat, Inc. * Copyright (C) 2007 - 2008 Novell, Inc. */ #include "src/core/nm-default-daemon.h" #include "nm-supplicant-manager.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-dbus-manager.h" #include "libnm-glib-aux/nm-dbus-aux.h" #include "libnm-glib-aux/nm-ref-string.h" #include "nm-supplicant-interface.h" #include "nm-supplicant-types.h" #include "libnm-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 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_g_bytes_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(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[NM_IFNAMSIZ]; if (handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX && nm_dbus_error_is(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[NM_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_is(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[NM_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 * 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); _caps_set(priv, NM_SUPPL_CAP_TYPE_WEP, NM_TERNARY_TRUE); 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; } if (nm_streq(*a, "wep_disabled")) { _caps_set(priv, NM_SUPPL_CAP_TYPE_WEP, NM_TERNARY_FALSE); 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" " SUITEB192%c" " WEP%c" "", NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_AP), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_PMF), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FILS), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_P2P), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FT), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_SHA384), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_MESH), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_FAST), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_WFD), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_SUITEB192), NM_SUPPL_CAP_TO_CHAR(priv->capabilities, NM_SUPPL_CAP_TYPE_WEP)); 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); }