diff options
Diffstat (limited to 'src/supplicant/nm-supplicant-manager.c')
-rw-r--r-- | src/supplicant/nm-supplicant-manager.c | 1403 |
1 files changed, 1105 insertions, 298 deletions
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c index 08bed98cf3..cf5cf11163 100644 --- a/src/supplicant/nm-supplicant-manager.c +++ b/src/supplicant/nm-supplicant-manager.c @@ -8,20 +8,64 @@ #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 "nm-core-internal.h" +#include "platform/nm-platform.h" /*****************************************************************************/ -typedef struct { - GDBusProxy *proxy; +#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 die_count_reset_id; - guint die_count; - bool running:1; + + 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 { @@ -37,6 +81,8 @@ 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 @@ -48,6 +94,27 @@ 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, "???"), @@ -58,6 +125,39 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_supplicant_driver_to_string, NMSupplicantDriver, /*****************************************************************************/ +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, @@ -66,82 +166,68 @@ _caps_set (NMSupplicantManagerPrivate *priv, priv->capabilities = NM_SUPPL_CAP_MASK_SET (priv->capabilities, type, value); } -static const char * -_caps_to_str (NMSupplicantManagerPrivate *priv, - NMSupplCapType type) +static char +_caps_to_char (NMSupplicantManagerPrivate *priv, + NMSupplCapType type) { NMTernary val; - val = NM_SUPPL_CAP_MASK_GET (priv->capabilities, type);; + val = NM_SUPPL_CAP_MASK_GET (priv->capabilities, type); if (val == NM_TERNARY_TRUE) - return "supported"; + return '+'; if (val == NM_TERNARY_FALSE) - return "not supported"; - return "possibly supported"; + return '-'; + return '?'; } /*****************************************************************************/ -static gboolean -die_count_exceeded (guint32 count) +static void +_dbus_call_remove_interface (GDBusConnection *dbus_connection, + const char *name_owner, + const char *iface_path) { - return count > 2; + 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); } -static gboolean -is_available (NMSupplicantManager *self) +void +_nm_supplicant_manager_dbus_call_remove_interface (NMSupplicantManager *self, + const char *name_owner, + const char *iface_path) { - NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - - return priv->running - && !die_count_exceeded (priv->die_count); + _dbus_call_remove_interface (NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->dbus_connection, + name_owner, + iface_path); } /*****************************************************************************/ static void -_sup_iface_last_ref (gpointer data, - GObject *object, - gboolean is_last_ref) -{ - NMSupplicantManager *self = data; - NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - NMSupplicantInterface *sup_iface = NM_SUPPLICANT_INTERFACE (object); - const char *op; - - nm_assert (is_last_ref); - nm_assert (c_list_contains (&priv->supp_lst_head, &sup_iface->supp_lst)); - - c_list_unlink (&sup_iface->supp_lst); - - if ( priv->running - && priv->proxy - && (op = nm_supplicant_interface_get_object_path (sup_iface))) { - g_dbus_proxy_call (priv->proxy, - "RemoveInterface", - g_variant_new ("(o)", op), - G_DBUS_CALL_FLAGS_NONE, - 3000, - NULL, - NULL, - NULL); - } - - g_object_remove_toggle_ref (G_OBJECT (sup_iface), _sup_iface_last_ref, self); -} - -static void on_supplicant_wfd_ies_set (GObject *source_object, - GAsyncResult *res, + GAsyncResult *result, gpointer user_data) { - gs_unref_variant GVariant *result = NULL; + gs_unref_variant GVariant *res = NULL; gs_free_error GError *error = NULL; - result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error); - - if (!result) - _LOGW ("failed to set WFD IEs on wpa_supplicant: %s", error->message); + 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); } /** @@ -164,138 +250,672 @@ nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self, priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - _LOGD ("setting WFD IEs for P2P operation"); + 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", g_dbus_proxy_get_interface_name (priv->proxy)); + 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 (g_dbus_proxy_get_connection (priv->proxy), - g_dbus_proxy_get_name (priv->proxy), - g_dbus_proxy_get_object_path (priv->proxy), - "org.freedesktop.DBus.Properties", + 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_UNIT, + G_VARIANT_TYPE ("()"), G_DBUS_CALL_FLAGS_NO_AUTO_START, - 1000, + 3000, NULL, on_supplicant_wfd_ies_set, NULL); } -/** - * nm_supplicant_manager_create_interface: - * @self: the #NMSupplicantManager - * @ifname: the interface for which to obtain the supplicant interface - * @is_wireless: whether the interface is supposed to be wireless. - * - * Note: the manager owns a reference to the instance and the only way to - * get the manager to release it, is by dropping all other references - * to the supplicant-interface (or destroying the manager). - * - * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL. - * Must be unrefed at the end. - * */ -NMSupplicantInterface * +/*****************************************************************************/ + +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); + } 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, - const char *ifname, - NMSupplicantDriver driver) + int ifindex, + NMSupplicantDriver driver, + NMSupplicantManagerCreateInterfaceCb callback, + gpointer user_data) { NMSupplicantManagerPrivate *priv; - NMSupplicantInterface *sup_iface; + NMSupplMgrCreateIfaceHandle *handle; g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL); - g_return_val_if_fail (ifname != NULL, 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); - _LOGD ("(%s): creating new supplicant interface", ifname); + 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 bacause 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; + } - c_list_for_each_entry (sup_iface, &priv->supp_lst_head, supp_lst) { - if (nm_streq0 (nm_supplicant_interface_get_ifname (sup_iface), ifname)) - g_return_val_if_reached (NULL); + 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; } - sup_iface = nm_supplicant_interface_new (ifname, - NULL, - driver, - priv->capabilities); + _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); - c_list_link_tail (&priv->supp_lst_head, &sup_iface->supp_lst); - g_object_add_toggle_ref (G_OBJECT (sup_iface), _sup_iface_last_ref, self); + _create_iface_dbus_start (self, handle); + return handle; +} - /* If we're making the supplicant take a time out for a bit, don't - * let the supplicant interface start immediately, just let it hang - * around in INIT state until we're ready to talk to the supplicant - * again. - */ - if (is_available (self)) - nm_supplicant_interface_set_supplicant_available (sup_iface, TRUE); +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); - return sup_iface; + 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); } -/** - * nm_supplicant_manager_create_interface_from_path: - * @self: the #NMSupplicantManager - * @object_path: the DBus object path for which to obtain the supplicant interface - * - * Note: the manager owns a reference to the instance and the only way to - * get the manager to release it, is by dropping all other references - * to the supplicant-interface (or destroying the manager). - * - * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL. - * Must be unrefed at the end. - * */ NMSupplicantInterface * nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self, const char *object_path) { NMSupplicantManagerPrivate *priv; - NMSupplicantInterface *sup_iface; + 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, NULL); + g_return_val_if_fail (object_path, NULL); priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - _LOGD ("creating new supplicant interface for dbus path %s", object_path); + iface_path = nm_ref_string_new (object_path); - c_list_for_each_entry (sup_iface, &priv->supp_lst_head, supp_lst) { - if (nm_streq0 (nm_supplicant_interface_get_object_path (sup_iface), object_path)) - g_return_val_if_reached (NULL); - } + supp_iface = g_hash_table_lookup (priv->supp_ifaces, iface_path); - sup_iface = nm_supplicant_interface_new (NULL, - object_path, - NM_SUPPLICANT_DRIVER_WIRELESS, - priv->capabilities); + if (supp_iface) + return g_object_ref (supp_iface); - c_list_link_tail (&priv->supp_lst_head, &sup_iface->supp_lst); - g_object_add_toggle_ref (G_OBJECT (sup_iface), _sup_iface_last_ref, self); + supp_iface = nm_supplicant_interface_new (self, + iface_path, + 0, + NM_SUPPLICANT_DRIVER_UNKNOWN); - /* If we're making the supplicant take a time out for a bit, don't - * let the supplicant interface start immediately, just let it hang - * around in INIT state until we're ready to talk to the supplicant - * again. - */ - if (is_available (self)) - nm_supplicant_interface_set_supplicant_available (sup_iface, TRUE); + _supp_iface_add (self, iface_path, supp_iface); - return sup_iface; + return supp_iface; } +/*****************************************************************************/ + static void -update_capabilities (NMSupplicantManager *self) +_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 *sup_iface; - const char **array; - GVariant *value; + 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: @@ -315,220 +935,389 @@ update_capabilities (NMSupplicantManager *self) _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); - - value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities"); - if (value) { - if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) { - array = g_variant_get_strv (value, 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); - if (array) { - if (g_strv_contains (array, "ap")) _caps_set (priv, NM_SUPPL_CAP_TYPE_AP, NM_TERNARY_TRUE); - if (g_strv_contains (array, "pmf")) _caps_set (priv, NM_SUPPL_CAP_TYPE_PMF, NM_TERNARY_TRUE); - if (g_strv_contains (array, "fils")) _caps_set (priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_TRUE); - if (g_strv_contains (array, "p2p")) _caps_set (priv, NM_SUPPL_CAP_TYPE_P2P, NM_TERNARY_TRUE); - if (g_strv_contains (array, "ft")) _caps_set (priv, NM_SUPPL_CAP_TYPE_FT, NM_TERNARY_TRUE); - if (g_strv_contains (array, "sha384")) _caps_set (priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_TRUE); - if (g_strv_contains (array, "mesh")) _caps_set (priv, NM_SUPPL_CAP_TYPE_MESH, NM_TERNARY_TRUE); - g_free (array); + _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); + 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; } + } + } + } + continue; } - } - g_variant_unref (value); - } - - _caps_set (priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_FALSE); - value = g_dbus_proxy_get_cached_property (priv->proxy, "EapMethods"); - if (value) { - if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) { - array = g_variant_get_strv (value, NULL); - if (array) { - const char **a; - - for (a = array; *a; a++) { - if (g_ascii_strcasecmp (*a, "FAST") == 0) { - _caps_set (priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_TRUE); - break; + 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; + } + } } } - g_free (array); + continue; + } + if (nm_streq (res_key, "WFDIEs")) { + _caps_set (priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_TRUE); + continue; } } - g_variant_unref (value); } - _caps_set (priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_FALSE); - value = g_dbus_proxy_get_cached_property (priv->proxy, "WFDIEs"); - if (value) { - _caps_set (priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_TRUE); - g_variant_unref (value); - } - - _LOGD ("AP mode is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_AP)); - _LOGD ("PMF is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_PMF)); - _LOGD ("FILS is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_FILS)); - _LOGD ("P2P is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_P2P)); - _LOGD ("FT is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_FT)); - _LOGD ("SHA384 is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_SHA384)); - _LOGD ("Mesh is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_MESH)); - _LOGD ("EAP-FAST is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_FAST)); - _LOGD ("WFD is %s", _caps_to_str (priv, NM_SUPPL_CAP_TYPE_WFD)); - - c_list_for_each_entry (sup_iface, &priv->supp_lst_head, supp_lst) { - nm_supplicant_interface_set_global_capabilities (sup_iface, - priv->capabilities); - } + _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); } -static void -availability_changed (NMSupplicantManager *self, gboolean available) +/*****************************************************************************/ + +void +_nm_supplicant_manager_unregister_interface (NMSupplicantManager *self, + NMSupplicantInterface *supp_iface) { NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - gs_unref_ptrarray GPtrArray *sup_ifaces = NULL; - NMSupplicantInterface *sup_iface; - gsize i, n; - n = c_list_length (&priv->supp_lst_head); - if (n == 0) - return; + 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)); - /* setting the supplicant as unavailable might cause the caller to unref - * the supplicant (and thus remove the instance from the list of interfaces. - * Delay that by taking an additional reference first. */ + 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 (); +} - sup_ifaces = g_ptr_array_new_full (n, g_object_unref); - c_list_for_each_entry (sup_iface, &priv->supp_lst_head, supp_lst) - g_ptr_array_add (sup_ifaces, g_object_ref (sup_iface)); +static void +_supp_iface_add (NMSupplicantManager *self, + NMRefString *iface_path, + NMSupplicantInterface *supp_iface) +{ + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - for (i = 0; i < n; i++) - nm_supplicant_interface_set_supplicant_available (sup_ifaces->pdata[i], available); + 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 -set_running (NMSupplicantManager *self, gboolean now_running) +_supp_iface_remove_one (NMSupplicantManager *self, + NMSupplicantInterface *supp_iface, + gboolean force_remove_from_supplicant, + const char *reason) { - NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - gboolean old_available = is_available (self); - gboolean new_available; +#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); - priv->running = now_running; - new_available = is_available (self); - if (old_available != new_available) - availability_changed (self, new_available); + nm_assert (c_list_is_empty (&supp_iface->supp_lst)); } static void -set_die_count (NMSupplicantManager *self, guint new_die_count) +_supp_iface_remove_all (NMSupplicantManager *self, + gboolean force_remove_from_supplicant, + const char *reason) { NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - gboolean old_available = is_available (self); - gboolean new_available; + NMSupplicantInterface *supp_iface; - priv->die_count = new_die_count; - new_available = is_available (self); - if (old_available != new_available) - availability_changed (self, new_available); + 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 -wpas_die_count_reset_cb (gpointer user_data) +_available_reset_cb (gpointer user_data) { - NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data); + NMSupplicantManager *self = user_data; NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - /* Reset the die count back to zero, which allows use of the supplicant again */ - priv->die_count_reset_id = 0; - set_die_count (self, 0); - _LOGI ("wpa_supplicant die count reset"); - return FALSE; + 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_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data) +name_owner_changed (NMSupplicantManager *self, + const char *name_owner, + gboolean first_time) { - NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data); NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - char *owner; - - g_return_if_fail (proxy == priv->proxy); - - owner = g_dbus_proxy_get_name_owner (proxy); - _LOGI ("wpa_supplicant %s", owner ? "running" : "stopped"); - - if (owner) { - update_capabilities (self); - set_running (self, TRUE); - } else if (priv->running) { - /* Reschedule the die count reset timeout. Every time the supplicant - * dies we wait 10 seconds before resetting the counter. If the - * supplicant died more than twice before the timer is reset, then - * we don't try to talk to the supplicant for a while. - */ - if (priv->die_count_reset_id) - g_source_remove (priv->die_count_reset_id); - priv->die_count_reset_id = g_timeout_add_seconds (10, wpas_die_count_reset_cb, self); - set_die_count (self, priv->die_count + 1); - - if (die_count_exceeded (priv->die_count)) { - _LOGI ("wpa_supplicant die count %d; ignoring for 10 seconds", - priv->die_count); + 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); } + } - priv->capabilities = NM_SUPPL_CAP_MASK_NONE; + _supp_iface_remove_all (self, TRUE, "name-owner changed"); - set_running (self, FALSE); + 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). */ } - g_free (owner); + if (available_changed) + g_signal_emit (self, signals[AVAILABLE_CHANGED], 0); } static void -on_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data) +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) { - NMSupplicantManager *self; - NMSupplicantManagerPrivate *priv; - GError *error = NULL; - GDBusProxy *proxy; + gs_unref_object NMSupplicantManager *self = g_object_ref (user_data); + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + const char *name_owner; - proxy = g_dbus_proxy_new_for_bus_finish (result, &error); - if (!proxy) { - _LOGW ("failed to acquire wpa_supplicant proxy: Wi-Fi and 802.1x will not be available (%s)", - error->message); - g_clear_error (&error); + 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 = NM_SUPPLICANT_MANAGER (user_data); + self = user_data; priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - priv->proxy = proxy; - g_signal_connect (priv->proxy, "notify::g-name-owner", G_CALLBACK (name_owner_cb), self); - name_owner_cb (priv->proxy, NULL, self); + g_clear_object (&priv->get_name_owner_cancellable); + + name_owner_changed (self, nm_str_not_empty (name_owner), TRUE); } /*****************************************************************************/ -NM_DEFINE_SINGLETON_GETTER (NMSupplicantManager, nm_supplicant_manager_get, NM_TYPE_SUPPLICANT_MANAGER); - 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->cancellable = g_cancellable_new (); - g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - NM_WPAS_DBUS_SERVICE, - NM_WPAS_DBUS_PATH, - NM_WPAS_DBUS_INTERFACE, - priv->cancellable, - (GAsyncReadyCallback) on_proxy_acquired, - self); + 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 @@ -536,20 +1325,32 @@ dispose (GObject *object) { NMSupplicantManager *self = (NMSupplicantManager *) object; NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - NMSupplicantInterface *sup_iface; - nm_clear_g_source (&priv->die_count_reset_id); + _supp_iface_remove_all (self, TRUE, "NMSupplicantManager is disposing"); - nm_clear_g_cancellable (&priv->cancellable); + nm_assert (c_list_is_empty (&priv->create_iface_lst_head)); - while ((sup_iface = c_list_first_entry (&priv->supp_lst_head, NMSupplicantInterface, supp_lst))) { - c_list_unlink (&sup_iface->supp_lst); - g_object_remove_toggle_ref (G_OBJECT (sup_iface), _sup_iface_last_ref, self); - } + 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); - g_clear_object (&priv->proxy); + 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 @@ -558,5 +1359,11 @@ 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); +} |