diff --git a/Makefile.am b/Makefile.am index 405577bd44..7a1616d50f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3935,6 +3935,8 @@ if WITH_IWD src_core_devices_wifi_libnm_wifi_base_la_SOURCES += \ src/core/devices/wifi/nm-device-iwd.c \ src/core/devices/wifi/nm-device-iwd.h \ + src/core/devices/wifi/nm-device-iwd-p2p.c \ + src/core/devices/wifi/nm-device-iwd-p2p.h \ src/core/devices/wifi/nm-iwd-manager.c \ src/core/devices/wifi/nm-iwd-manager.h \ $(NULL) diff --git a/src/core/devices/wifi/meson.build b/src/core/devices/wifi/meson.build index 85553c5310..715bc0c90b 100644 --- a/src/core/devices/wifi/meson.build +++ b/src/core/devices/wifi/meson.build @@ -4,6 +4,7 @@ iwd_sources = files() if enable_iwd iwd_sources += files( 'nm-device-iwd.c', + 'nm-device-iwd-p2p.c', 'nm-iwd-manager.c', ) endif diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.c b/src/core/devices/wifi/nm-device-iwd-p2p.c new file mode 100644 index 0000000000..da221bf616 --- /dev/null +++ b/src/core/devices/wifi/nm-device-iwd-p2p.c @@ -0,0 +1,1152 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021 Intel Corporation + */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-device-iwd-p2p.h" + +#include "NetworkManagerUtils.h" +#include "devices/nm-device-private.h" +#include "nm-act-request.h" +#include "libnm-core-aux-intern/nm-libnm-core-utils.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "libnm-std-aux/nm-dbus-compat.h" +#include "nm-setting-wifi-p2p.h" +#include "nm-utils.h" +#include "nm-wifi-p2p-peer.h" +#include "nm-iwd-manager.h" +#include "settings/nm-settings.h" + +#define _NMLOG_DEVICE_TYPE NMDeviceIwdP2P +#include "devices/nm-device-logging.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIwdP2P, PROP_PEERS, ); + +typedef struct { + GDBusObject *dbus_obj; + GDBusProxy *dbus_p2p_proxy; + GDBusProxy *dbus_peer_proxy; + CList peers_lst_head; + + GSource *find_peer_timeout_source; + GSource *peer_dump_source; + + GCancellable *find_cancellable; + GCancellable *connect_cancellable; + + bool enabled : 1; + + bool stage2_ready : 1; +} NMDeviceIwdP2PPrivate; + +struct _NMDeviceIwdP2P { + NMDevice parent; + NMDeviceIwdP2PPrivate _priv; +}; + +struct _NMDeviceIwdP2PClass { + NMDeviceClass parent; +}; + +G_DEFINE_TYPE(NMDeviceIwdP2P, nm_device_iwd_p2p, NM_TYPE_DEVICE) + +#define NM_DEVICE_IWD_P2P_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDeviceIwdP2P, NM_IS_DEVICE_IWD_P2P, NMDevice) + +/*****************************************************************************/ + +static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p; +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added; +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed; +static gboolean iwd_discovery_timeout_cb(gpointer user_data); + +/*****************************************************************************/ + +static void +_peer_dump(NMDeviceIwdP2P *self, + NMLogLevel log_level, + const NMWifiP2PPeer *peer, + const char *prefix, + gint32 now_s) +{ + char buf[1024]; + + _NMLOG(log_level, + LOGD_WIFI_SCAN, + "wifi-peer: %-7s %s", + prefix, + nm_wifi_p2p_peer_to_string(peer, buf, sizeof(buf), now_s)); +} + +static gboolean +peer_list_dump(gpointer user_data) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(user_data); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + nm_clear_g_source_inst(&priv->peer_dump_source); + + if (_LOGD_ENABLED(LOGD_WIFI_SCAN)) { + NMWifiP2PPeer *peer; + gint32 now_s = nm_utils_get_monotonic_timestamp_sec(); + + _LOGD(LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s); + c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst) + _peer_dump(self, LOGL_DEBUG, peer, "dump", now_s); + } + + return G_SOURCE_REMOVE; +} + +static void +schedule_peer_list_dump(NMDeviceIwdP2P *self) +{ + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + if (!priv->peer_dump_source && _LOGD_ENABLED(LOGD_WIFI_SCAN)) { + priv->peer_dump_source = nm_g_timeout_add_seconds_source(1, peer_list_dump, self); + } +} + +/*****************************************************************************/ + +static gboolean +is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + return priv->enabled; +} + +static gboolean +check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error) +{ + NMSettingWifiP2P *s_wifi_p2p; + GBytes *wfd_ies; + NMSettingIPConfig *s_ip; + + if (!NM_DEVICE_CLASS(nm_device_iwd_p2p_parent_class) + ->check_connection_compatible(device, connection, error)) + return FALSE; + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + + /* Any of the existing values other than DISABLED is ok */ + if (nm_setting_wifi_p2p_get_wps_method(s_wifi_p2p) + == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "No WPS method enabled"); + return FALSE; + } + + wfd_ies = nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p); + if (wfd_ies && !nm_wifi_utils_parse_wfd_ies(wfd_ies, NULL)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "Can't parse connection WFD IEs"); + return FALSE; + } + + s_ip = NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(connection)); + if (s_ip + && !NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip), + NULL, + NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "P2P implies 'auto' IPv4 config method"); + return FALSE; + } + + return TRUE; +} + +static gboolean +complete_connection(NMDevice *device, + NMConnection *connection, + const char *specific_object, + NMConnection *const *existing_connections, + GError **error) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + gs_free char *setting_name = NULL; + NMSettingWifiP2P *s_wifi_p2p; + NMWifiP2PPeer *peer; + const char *setting_peer; + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + + if (!specific_object) { + /* If not given a specific object, we need at minimum a peer address */ + if (!s_wifi_p2p) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A '%s' setting is required if no Peer path was given", + NM_SETTING_WIFI_P2P_SETTING_NAME); + return FALSE; + } + + setting_peer = nm_setting_wifi_p2p_get_peer(s_wifi_p2p); + if (!setting_peer) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A '%s' setting with a valid Peer is required if no Peer path was given", + NM_SETTING_WIFI_P2P_SETTING_NAME); + return FALSE; + } + } else { + peer = nm_wifi_p2p_peer_lookup_for_device(NM_DEVICE(self), specific_object); + if (!peer) { + g_set_error(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND, + "The P2P peer %s is unknown", + specific_object); + return FALSE; + } + + setting_peer = nm_wifi_p2p_peer_get_address(peer); + g_return_val_if_fail(setting_peer, FALSE); + } + + /* Add a Wi-Fi P2P setting if one doesn't exist yet */ + s_wifi_p2p = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIFI_P2P); + + g_object_set(G_OBJECT(s_wifi_p2p), NM_SETTING_WIFI_P2P_PEER, setting_peer, NULL); + + setting_name = g_strdup_printf("Wi-Fi P2P Peer %s", setting_peer); + nm_utils_complete_generic(nm_device_get_platform(device), + connection, + NM_SETTING_WIFI_P2P_SETTING_NAME, + existing_connections, + setting_name, + setting_name, + NULL, + NULL, + TRUE); + + return TRUE; +} + +static gboolean +get_enabled(NMDevice *device) +{ + return NM_DEVICE_IWD_P2P_GET_PRIVATE(device)->enabled; +} + +static void +set_enabled_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwdP2P *self = user_data; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, ".Set failed: %s", error->message); + return; + } + _LOGD(LOGD_DEVICE | LOGD_WIFI, ".Set OK!"); +} + +static void +set_enabled(NMDevice *device, gboolean enabled) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + enabled = !!enabled; + + if (priv->enabled == enabled) + return; + + _LOGD(LOGD_WIFI, "device will be %s", enabled ? "enabled" : "disabled"); + + g_dbus_proxy_call( + priv->dbus_p2p_proxy, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new("(ssv)", NM_IWD_P2P_INTERFACE, "Enabled", g_variant_new("b", enabled)), + G_DBUS_CALL_FLAGS_NONE, + 2000, + NULL, + set_enabled_cb, + self); +} + +static void +p2p_properties_changed_cb(GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidate_properties, + gpointer user_data) +{ + NMDeviceIwdP2P *self = user_data; + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + NMDevice *device = NM_DEVICE(self); + gboolean new_bool; + + if (g_variant_lookup(changed_properties, "Enabled", "b", &new_bool) + && new_bool != priv->enabled) { + priv->enabled = new_bool; + + _LOGD(LOGD_WIFI, "device now %s", priv->enabled ? "enabled" : "disabled"); + + if (priv->enabled) { + NMDeviceState state = nm_device_get_state(device); + + if (state != NM_DEVICE_STATE_UNAVAILABLE) + _LOGW(LOGD_CORE, "not in expected unavailable state!"); + + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } else { + nm_device_state_changed(device, + NM_DEVICE_STATE_UNAVAILABLE, + NM_DEVICE_STATE_REASON_NONE); + } + } +} + +static void +iwd_request_discovery_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwdP2P *self = user_data; + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + NMDevice *device = NM_DEVICE(self); + + _LOGE(LOGD_DEVICE | LOGD_WIFI, + "%s(wifi-p2p) IWD p2p.Device.RequestDiscovery failed: %s", + nm_device_is_activating(device) ? "Activation: " : "", + error->message); + + if (nm_utils_error_is_cancelled(error) && !nm_device_is_activating(device)) + return; + + nm_clear_g_cancellable(&priv->find_cancellable); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); + return; + } + + nm_clear_g_cancellable(&priv->find_cancellable); + _LOGI(LOGD_DEVICE | LOGD_WIFI, + "%s(wifi-p2p) Target peer discovery running", + nm_device_is_activating(NM_DEVICE(self)) ? "Activation: " : ""); +} + +static void +iwd_request_discovery(NMDeviceIwdP2P *self, unsigned timeout) +{ + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + bool requested = priv->find_peer_timeout_source != NULL; + + nm_clear_g_source_inst(&priv->find_peer_timeout_source); + priv->find_peer_timeout_source = + nm_g_timeout_add_seconds_source(timeout, iwd_discovery_timeout_cb, self); + + if (!requested) { + priv->find_cancellable = g_cancellable_new(); + + g_dbus_proxy_call(priv->dbus_p2p_proxy, + "RequestDiscovery", + NULL, + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + priv->find_cancellable, + iwd_request_discovery_cb, + self); + } +} + +static void +iwd_release_discovery(NMDeviceIwdP2P *self) +{ + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + nm_clear_g_source_inst(&priv->find_peer_timeout_source); + nm_clear_g_cancellable(&priv->find_cancellable); + + g_dbus_proxy_call(priv->dbus_p2p_proxy, + "ReleaseDiscovery", + NULL, + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + NULL, + self); +} + +/* + * Called when IWD has been unable to find the peer we want to connect to within the + * 10s time limit or when a D-bus Find() ends. + */ +static gboolean +iwd_discovery_timeout_cb(gpointer user_data) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(user_data); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + NMDevice *device = NM_DEVICE(self); + + nm_clear_g_source_inst(&priv->find_peer_timeout_source); + + iwd_release_discovery(self); + + if (nm_device_is_activating(device)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi-p2p) Could not find peer, failing activation"); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); + } else { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "(wifi-p2p) Find timeout"); + } + + return G_SOURCE_REMOVE; +} + +static void +cleanup_connect_attempt(NMDeviceIwdP2P *self) +{ + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + if (priv->find_peer_timeout_source) + iwd_release_discovery(self); + + if (!priv->dbus_peer_proxy) + return; + + if (nm_device_is_activating(NM_DEVICE(self))) + nm_device_set_ip_iface(NM_DEVICE(self), NULL); + + priv->stage2_ready = FALSE; + g_signal_handlers_disconnect_by_data(priv->dbus_peer_proxy, self); + g_clear_object(&priv->dbus_peer_proxy); + nm_clear_g_cancellable(&priv->connect_cancellable); +} + +static void +peer_properties_changed_cb(GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidate_properties, + gpointer user_data) +{ + NMDeviceIwdP2P *self = user_data; + NMDeviceState state = nm_device_get_state(NM_DEVICE(self)); + gboolean new_bool; + const char *new_str; + + if (g_variant_lookup(changed_properties, "Connected", "b", &new_bool) && !new_bool + && state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_DEACTIVATING) { + cleanup_connect_attempt(self); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + } + + if (g_variant_lookup(changed_properties, "ConnectedInterface", "&s", &new_str) + && state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_IP_CONFIG) { + nm_device_set_ip_iface(NM_DEVICE(self), new_str); + } +} + +static NMActStageReturn +act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + NMConnection *connection; + NMSettingWifiP2P *s_wifi_p2p; + NMWifiP2PPeer *peer; + + if (!priv->enabled) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + connection = nm_device_get_applied_connection(NM_DEVICE(self)); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + g_return_val_if_fail(s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE); + + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection); + if (!peer) { + iwd_request_discovery(self, 10); + return NM_ACT_STAGE_RETURN_POSTPONE; + } else if (priv->find_peer_timeout_source) { + iwd_release_discovery(self); + } + + return NM_ACT_STAGE_RETURN_SUCCESS; +} + +static void +iwd_wsc_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + NMDeviceIwdP2P *self = user_data; + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + NMDevice *device = NM_DEVICE(self); + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi-p2p) IWD SimpleConfiguration.PushButton/StartPin() failed: %s", + error->message); + + if (nm_utils_error_is_cancelled(error) && !nm_device_is_activating(device)) + return; + + nm_clear_g_cancellable(&priv->connect_cancellable); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return; + } + + nm_clear_g_cancellable(&priv->connect_cancellable); + _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi-p2p) IWD connection successful"); + + g_signal_connect(priv->dbus_peer_proxy, + "g-properties-changed", + G_CALLBACK(peer_properties_changed_cb), + self); + + priv->stage2_ready = TRUE; + + nm_device_activate_schedule_stage2_device_config(device, FALSE); +} + +static NMActStageReturn +act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + NMConnection *connection; + NMSettingWifiP2P *s_wifi_p2p; + NMWifiP2PPeer *peer; + gs_unref_object GDBusProxy *peer_proxy = NULL; + gs_unref_object GDBusProxy *wsc_proxy = NULL; + + if (priv->stage2_ready) + return NM_ACT_STAGE_RETURN_SUCCESS; + + if (!priv->dbus_p2p_proxy) { + cleanup_connect_attempt(self); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (nm_clear_g_source_inst(&priv->find_peer_timeout_source)) + nm_assert_not_reached(); + + connection = nm_device_get_applied_connection(device); + g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE); + nm_assert( + NM_IS_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P))); + + /* The prepare stage ensures that the peer has been found */ + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection); + if (!peer) { + cleanup_connect_attempt(self); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + if (nm_setting_wifi_p2p_get_wps_method(s_wifi_p2p) + == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) { + /* TODO: check we have the pin secret, if so use StartPin(pin) otherwise request pin, + * move to NEED_AUTH and return postpone */ + cleanup_connect_attempt(self); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + peer_proxy = nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), + nm_wifi_p2p_peer_get_supplicant_path(peer), + NM_IWD_P2P_PEER_INTERFACE); + wsc_proxy = nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), + nm_wifi_p2p_peer_get_supplicant_path(peer), + NM_IWD_WSC_INTERFACE); + + if (!wsc_proxy || !peer_proxy) { + cleanup_connect_attempt(self); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + g_dbus_proxy_call(wsc_proxy, + "PushButton", + NULL, + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + priv->connect_cancellable, + iwd_wsc_connect_cb, + self); + + priv->dbus_peer_proxy = g_steal_pointer(&peer_proxy); + return NM_ACT_STAGE_RETURN_POSTPONE; +} + +/*****************************************************************************/ + +static void +emit_signal_p2p_peer_add_remove(NMDeviceIwdP2P *device, + NMWifiP2PPeer *peer, + gboolean is_added /* or else is_removed */) +{ + nm_dbus_object_emit_signal(NM_DBUS_OBJECT(device), + &interface_info_device_wifi_p2p, + is_added ? &nm_signal_info_wifi_p2p_peer_added + : &nm_signal_info_wifi_p2p_peer_removed, + "(o)", + nm_dbus_object_get_path(NM_DBUS_OBJECT(peer))); +} + +static void +act_check_new_peer_compatible(NMDeviceIwdP2P *self, NMWifiP2PPeer *peer) +{ + NMDevice *device = NM_DEVICE(self); + NMConnection *connection; + + connection = nm_device_get_applied_connection(device); + nm_assert(NM_IS_CONNECTION(connection)); + + if (nm_wifi_p2p_peer_check_compatible(peer, connection)) { + /* A peer for the connection was found, cancel the timeout and go to configure state. */ + iwd_release_discovery(self); + nm_device_activate_schedule_stage2_device_config(device, FALSE); + } +} + +static void +peer_add_remove(NMDeviceIwdP2P *self, + gboolean is_adding, /* or else removing */ + NMWifiP2PPeer *peer, + gboolean recheck_available_connections) +{ + NMDevice *device = NM_DEVICE(self); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + if (is_adding) { + g_object_ref(peer); + peer->wifi_device = device; + c_list_link_tail(&priv->peers_lst_head, &peer->peers_lst); + nm_dbus_object_export(NM_DBUS_OBJECT(peer)); + _peer_dump(self, LOGL_DEBUG, peer, "added", 0); + + emit_signal_p2p_peer_add_remove(self, peer, TRUE); + } else { + peer->wifi_device = NULL; + c_list_unlink(&peer->peers_lst); + _peer_dump(self, LOGL_DEBUG, peer, "removed", 0); + } + + _notify(self, PROP_PEERS); + + if (!is_adding) { + emit_signal_p2p_peer_add_remove(self, peer, FALSE); + nm_dbus_object_clear_and_unexport(&peer); + } + + if (is_adding) { + /* If we are in prepare state, then we are currently running a find + * to search for the requested peer. */ + if (priv->find_peer_timeout_source + && nm_device_get_state(device) == NM_DEVICE_STATE_PREPARE) + act_check_new_peer_compatible(self, peer); + + /* TODO: We may want to re-check auto-activation here */ + } +} + +static void +iwd_peer_interface_added_cb(GDBusObject *peer_obj, GDBusInterface *interface, gpointer user_data) +{ + NMDeviceIwdP2P *self = user_data; + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + const char *iface_name; + NMWifiP2PPeer *peer; + + g_return_if_fail(G_IS_DBUS_PROXY(interface)); + + iface_name = g_dbus_proxy_get_interface_name(G_DBUS_PROXY(interface)); + if (!nm_streq(iface_name, NM_IWD_P2P_WFD_INTERFACE)) + return; + + peer = nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head, + g_dbus_object_get_object_path(peer_obj)); + if (!peer) + return; + + nm_wifi_p2p_peer_update_from_iwd_object(peer, peer_obj); + + /* If we are in prepare state, then we are currently running a find + * to search for the requested peer. */ + if (priv->find_peer_timeout_source) + act_check_new_peer_compatible(self, peer); +} + +static void +iwd_peer_interface_removed_cb(GDBusObject *peer_obj, GDBusInterface *interface, gpointer user_data) +{ + NMDeviceIwdP2P *self = user_data; + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + const char *iface_name; + NMWifiP2PPeer *peer; + + g_return_if_fail(G_IS_DBUS_PROXY(interface)); + + iface_name = g_dbus_proxy_get_interface_name(G_DBUS_PROXY(interface)); + if (!nm_streq(iface_name, NM_IWD_P2P_WFD_INTERFACE)) + return; + + peer = nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head, + g_dbus_object_get_object_path(peer_obj)); + if (!peer) + return; + + nm_wifi_p2p_peer_set_wfd_ies(peer, NULL); +} + +void +nm_device_iwd_p2p_peer_add_remove(NMDeviceIwdP2P *self, GDBusObject *peer_obj, bool add) +{ + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + NMWifiP2PPeer *found_peer; + + found_peer = nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head, + g_dbus_object_get_object_path(peer_obj)); + + if (found_peer && !add) { + if (priv->dbus_peer_proxy + && !nm_streq(g_dbus_object_get_object_path(peer_obj), + g_dbus_proxy_get_object_path(priv->dbus_peer_proxy))) { + cleanup_connect_attempt(self); + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_DISCONNECTED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + } + + peer_add_remove(self, FALSE, found_peer, TRUE); + g_signal_handlers_disconnect_by_data(peer_obj, self); + } + + if (!found_peer && add) { + gs_unref_object NMWifiP2PPeer *peer = nm_wifi_p2p_peer_new_from_iwd_object(peer_obj); + + if (!peer) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, + "Can't interpret IWD Peer properties at %s", + g_dbus_object_get_object_path(peer_obj)); + return; + } + + peer_add_remove(self, TRUE, peer, TRUE); + + /* None of the D-Bus properties that we use on this interface emit PropertiesChanges + * signals, only the WFD properties do. We do listen to changes to "Connected" + * but only while we're connecting/connected to a given peer. + */ + g_signal_connect(peer_obj, + "interface-added", + G_CALLBACK(iwd_peer_interface_added_cb), + self); + g_signal_connect(peer_obj, + "interface-removed", + G_CALLBACK(iwd_peer_interface_removed_cb), + self); + + /* TODO: every now and then call p2p.Device.GetPeers() and update the signal strength + * values for all peers we got through ObjectManager events. + */ + } + + schedule_peer_list_dump(self); +} + +/*****************************************************************************/ + +static void +deactivate(NMDevice *device) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + if (priv->find_peer_timeout_source) + iwd_release_discovery(self); + + if (priv->dbus_peer_proxy) { + g_dbus_proxy_call(priv->dbus_peer_proxy, + "Disconnect", + NULL, + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT, + NULL, + NULL, + self); + + cleanup_connect_attempt(self); + } +} + +static guint32 +get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force) +{ + *out_source = NM_DEVICE_MTU_SOURCE_NONE; + return 0; +} + +static gboolean +unmanaged_on_quit(NMDevice *self) +{ + return TRUE; +} + +static void +device_state_changed(NMDevice *device, + NMDeviceState new_state, + NMDeviceState old_state, + NMDeviceStateReason reason) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + switch (new_state) { + case NM_DEVICE_STATE_UNMANAGED: + break; + case NM_DEVICE_STATE_UNAVAILABLE: + if (priv->enabled) { + nm_device_queue_recheck_available(device, + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + } + break; + case NM_DEVICE_STATE_IP_CONFIG: + /* TODO: start periodic RSSI and bitrate updates? */ + break; + default: + break; + } +} + +static void +impl_device_iwd_p2p_start_find(NMDBusObject *obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended *method_info, + GDBusConnection *connection, + const char *sender, + GDBusMethodInvocation *invocation, + GVariant *parameters) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(obj); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + gs_unref_variant GVariant *options = NULL; + const char *opts_key; + GVariant *opts_val; + GVariantIter iter; + gint32 timeout = 30; + + g_variant_get(parameters, "(@a{sv})", &options); + + g_variant_iter_init(&iter, options); + while (g_variant_iter_next(&iter, "{&sv}", &opts_key, &opts_val)) { + _nm_unused gs_unref_variant GVariant *opts_val_free = opts_val; + + if (nm_streq(opts_key, "timeout")) { + if (!g_variant_is_of_type(opts_val, G_VARIANT_TYPE_INT32)) { + g_dbus_method_invocation_return_error_literal( + invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "\"timeout\" must be an integer \"i\""); + return; + } + + timeout = g_variant_get_int32(opts_val); + if (timeout <= 0 || timeout > 600) { + g_dbus_method_invocation_return_error_literal( + invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ALLOWED, + "The timeout for a find operation needs to be in the range of 1-600s."); + return; + } + + continue; + } + + g_dbus_method_invocation_return_error(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_ARGUMENT, + "Unsupported options key \"%s\"", + opts_key); + return; + } + + if (!priv->enabled || nm_device_is_activating(NM_DEVICE(self))) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "P2P device not enabled or busy."); + return; + } + + iwd_request_discovery(self, timeout); + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static void +impl_device_iwd_p2p_stop_find(NMDBusObject *obj, + const NMDBusInterfaceInfoExtended *interface_info, + const NMDBusMethodInfoExtended *method_info, + GDBusConnection *connection, + const char *sender, + GDBusMethodInvocation *invocation, + GVariant *parameters) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(obj); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + if (!priv->find_peer_timeout_source || nm_device_is_activating(NM_DEVICE(self))) { + g_dbus_method_invocation_return_error_literal(invocation, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_NOT_ACTIVE, + "Find phase is not active."); + return; + } + + iwd_release_discovery(self); + g_dbus_method_invocation_return_value(invocation, NULL); +} + +/*****************************************************************************/ + +static bool +nm_device_iwd_p2p_set_dbus_obj(NMDeviceIwdP2P *self, GDBusObject *obj) +{ + NMDeviceIwdP2PPrivate *priv; + gs_unref_variant GVariant *enabled_value = NULL; + + g_return_val_if_fail(NM_IS_DEVICE_IWD_P2P(self), FALSE); + + priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + if (priv->dbus_obj == obj) + goto done; + + if (priv->dbus_obj) { + cleanup_connect_attempt(self); + g_signal_handlers_disconnect_by_data(priv->dbus_p2p_proxy, self); + g_clear_object(&priv->dbus_p2p_proxy); + g_clear_object(&priv->dbus_obj); + priv->enabled = FALSE; + } + + if (!obj) + goto done; + + priv->dbus_p2p_proxy = G_DBUS_PROXY(g_dbus_object_get_interface(obj, NM_IWD_P2P_INTERFACE)); + if (!priv->dbus_p2p_proxy) + return FALSE; + + enabled_value = g_dbus_proxy_get_cached_property(priv->dbus_p2p_proxy, "Enabled"); + if (!enabled_value || !g_variant_is_of_type(enabled_value, G_VARIANT_TYPE_BOOLEAN)) + return FALSE; + + priv->dbus_obj = g_object_ref(obj); + + g_signal_connect(priv->dbus_p2p_proxy, + "g-properties-changed", + G_CALLBACK(p2p_properties_changed_cb), + self); + + priv->enabled = g_variant_get_boolean(enabled_value); + _LOGD(LOGD_WIFI, "iniital state is %s", priv->enabled ? "enabled" : "disabled"); + +done: + nm_device_queue_recheck_available(NM_DEVICE(self), + NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return TRUE; +} + +void +nm_device_iwd_p2p_remove(NMDeviceIwdP2P *self) +{ + g_signal_emit_by_name(self, NM_DEVICE_REMOVED); +} + +/*****************************************************************************/ + +static const char * +get_type_description(NMDevice *device) +{ + return "wifi-p2p"; +} + +/*****************************************************************************/ + +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "PeerAdded", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("peer", "o"), ), ); + +static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed = + NM_DEFINE_GDBUS_SIGNAL_INFO_INIT( + "PeerRemoved", + .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("peer", "o"), ), ); + +static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = { + .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT( + NM_DBUS_INTERFACE_DEVICE_WIFI_P2P, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED( + NM_DEFINE_GDBUS_METHOD_INFO_INIT( + "StartFind", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("options", "a{sv}"), ), ), + .handle = impl_device_iwd_p2p_start_find, ), + NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("StopFind", ), + .handle = impl_device_iwd_p2p_stop_find, ), ), + .signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_wifi_p2p_peer_added, + &nm_signal_info_wifi_p2p_peer_removed, ), + .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS( + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS), + NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Peers", + "ao", + NM_DEVICE_IWD_P2P_PEERS), ), ), +}; + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(object); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + const char **list; + + switch (prop_id) { + case PROP_PEERS: + list = nm_wifi_p2p_peers_get_paths(&priv->peers_lst_head); + g_value_take_boxed(value, nm_strv_make_deep_copied(list)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_device_iwd_p2p_init(NMDeviceIwdP2P *self) +{ + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + + c_list_init(&priv->peers_lst_head); +} + +NMDeviceIwdP2P * +nm_device_iwd_p2p_new(GDBusObject *dbus_obj) +{ + gs_unref_object NMDeviceIwdP2P *self = NULL; + + g_return_val_if_fail(!dbus_obj || G_IS_DBUS_OBJECT(dbus_obj), NULL); + + /* cfg80211 P2P-Device virtual interfaces don't map to netdev-type interfaces. + * Provide a false unique interface name only to avoid triggering assertions + * in NMManager and for that name to appear in debug messages. */ + self = g_object_new(NM_TYPE_DEVICE_IWD_P2P, + NM_DEVICE_IFACE, + g_dbus_object_get_object_path(dbus_obj), + NM_DEVICE_TYPE_DESC, + "802.11 Wi-Fi P2P", + NM_DEVICE_DEVICE_TYPE, + NM_DEVICE_TYPE_WIFI_P2P, + NM_DEVICE_LINK_TYPE, + NM_LINK_TYPE_WIFI, + NM_DEVICE_RFKILL_TYPE, + RFKILL_TYPE_WLAN, + NULL); + + if (!self || !nm_device_iwd_p2p_set_dbus_obj(self, dbus_obj)) + return NULL; + + return nm_steal_pointer(&self); +} + +static void +dispose(GObject *object) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(object); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(object); + + nm_clear_g_source_inst(&priv->peer_dump_source); + + nm_device_iwd_p2p_set_dbus_obj(self, NULL); + + G_OBJECT_CLASS(nm_device_iwd_p2p_parent_class)->dispose(object); +} + +static void +nm_device_iwd_p2p_class_init(NMDeviceIwdP2PClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass); + NMDeviceClass *device_class = NM_DEVICE_CLASS(klass); + + object_class->get_property = get_property; + object_class->dispose = dispose; + + dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_wifi_p2p); + + device_class->connection_type_supported = NM_SETTING_WIFI_P2P_SETTING_NAME; + device_class->connection_type_check_compatible = NM_SETTING_WIFI_P2P_SETTING_NAME; + device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WIFI_P2P); + device_class->get_type_description = get_type_description; + + /* Do we need compatibility checking or is the default good enough? */ + device_class->is_available = is_available; + device_class->check_connection_compatible = check_connection_compatible; + device_class->complete_connection = complete_connection; + device_class->get_enabled = get_enabled; + device_class->set_enabled = set_enabled; + + device_class->act_stage1_prepare = act_stage1_prepare; + device_class->act_stage2_config = act_stage2_config; + device_class->get_configured_mtu = get_configured_mtu; + + device_class->deactivate = deactivate; + device_class->unmanaged_on_quit = unmanaged_on_quit; + + device_class->state_changed = device_state_changed; + + obj_properties[PROP_PEERS] = g_param_spec_boxed(NM_DEVICE_IWD_P2P_PEERS, + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.h b/src/core/devices/wifi/nm-device-iwd-p2p.h new file mode 100644 index 0000000000..9ce353c02d --- /dev/null +++ b/src/core/devices/wifi/nm-device-iwd-p2p.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2021 Intel Corporation + */ + +#ifndef __NM_DEVICE_IWD_P2P_H__ +#define __NM_DEVICE_IWD_P2P_H__ + +#include "devices/nm-device.h" +#include "nm-device-wifi-p2p.h" + +#define NM_TYPE_DEVICE_IWD_P2P (nm_device_iwd_p2p_get_type()) +#define NM_DEVICE_IWD_P2P(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2P)) +#define NM_DEVICE_IWD_P2P_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2PClass)) +#define NM_IS_DEVICE_IWD_P2P(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_IWD_P2P)) +#define NM_IS_DEVICE_IWD_P2P_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_IWD_P2P)) +#define NM_DEVICE_IWD_P2P_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2PClass)) + +#define NM_DEVICE_IWD_P2P_PEERS NM_DEVICE_WIFI_P2P_PEERS +#define NM_DEVICE_IWD_P2P_GROUPS NM_DEVICE_WIFI_P2P_GROUPS + +typedef struct _NMDeviceIwdP2P NMDeviceIwdP2P; +typedef struct _NMDeviceIwdP2PClass NMDeviceIwdP2PClass; + +GType nm_device_iwd_p2p_get_type(void); + +NMDeviceIwdP2P *nm_device_iwd_p2p_new(GDBusObject *object); + +void nm_device_iwd_p2p_remove(NMDeviceIwdP2P *p2p); + +void nm_device_iwd_p2p_peer_add_remove(NMDeviceIwdP2P *p2p, GDBusObject *peer_obj, bool add); + +#endif /* __NM_DEVICE_IWD_P2P_H__ */ diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c index 9df1675fac..fc10ed2ceb 100644 --- a/src/core/devices/wifi/nm-iwd-manager.c +++ b/src/core/devices/wifi/nm-iwd-manager.c @@ -15,6 +15,7 @@ #include "libnm-core-intern/nm-core-internal.h" #include "nm-manager.h" #include "nm-device-iwd.h" +#include "nm-device-iwd-p2p.h" #include "nm-wifi-utils.h" #include "libnm-glib-aux/nm-uuid.h" #include "libnm-glib-aux/nm-random-utils.h" @@ -25,6 +26,14 @@ /*****************************************************************************/ +enum { + P2P_DEVICE_ADDED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL]; + typedef struct { const char *name; NMIwdNetworkSecurity security; @@ -50,6 +59,7 @@ typedef struct { char *last_state_dir; char *warned_state_dir; bool netconfig_enabled; + GHashTable *p2p_devices; } NMIwdManagerPrivate; struct _NMIwdManager { @@ -420,6 +430,66 @@ set_device_dbus_object(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *objec nm_device_iwd_set_dbus_object(NM_DEVICE_IWD(device), object); } +static void +add_p2p_device(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *object) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + const char *path = g_dbus_object_get_object_path(object); + NMDeviceIwdP2P *p2p; + gs_unref_object GDBusInterface *wiphy = NULL; + const char *phy_name; + + if (g_hash_table_contains(priv->p2p_devices, path)) + return; + + wiphy = g_dbus_object_get_interface(object, NM_IWD_WIPHY_INTERFACE); + if (!wiphy) + return; + + phy_name = get_property_string_or_null(G_DBUS_PROXY(wiphy), "Name"); + if (!phy_name) { + _LOGE("Name not cached for phy at %s", path); + return; + } + + p2p = nm_device_iwd_p2p_new(object); + if (!p2p) { + _LOGE("Can't create NMDeviceIwdP2P for phy at %s", path); + return; + } + + g_hash_table_insert(priv->p2p_devices, g_strdup(path), p2p); + g_signal_emit(self, signals[P2P_DEVICE_ADDED], 0, p2p, phy_name); + + /* There should be no peer objects before the device object appeared so don't + * try to look for them and notify the new device. */ +} + +static void +remove_p2p_device(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *object) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + const char *path = g_dbus_object_get_object_path(object); + NMDeviceIwdP2P *p2p = g_hash_table_lookup(priv->p2p_devices, path); + + if (!p2p) + return; + + g_hash_table_remove(priv->p2p_devices, path); +} + +static NMDeviceIwdP2P * +get_p2p_device_from_peer(NMIwdManager *self, GDBusProxy *proxy) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + const char *device_path = get_property_string_or_null(proxy, "Device"); + + if (!device_path) + return NULL; + + return g_hash_table_lookup(priv->p2p_devices, device_path); +} + static void known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data) { @@ -999,6 +1069,22 @@ interface_added(GDBusObjectManager *object_manager, return; } + + if (nm_streq(iface_name, NM_IWD_P2P_INTERFACE)) { + add_p2p_device(self, proxy, object); + return; + } + + if (nm_streq(iface_name, NM_IWD_P2P_PEER_INTERFACE)) { + NMDeviceIwdP2P *p2p = get_p2p_device_from_peer(self, proxy); + + /* This is more conveniently done with a direct call than a signal because + * this way we only notify the interested NMDeviceIwdP2P. */ + if (p2p) + nm_device_iwd_p2p_peer_add_remove(p2p, object, TRUE); + + return; + } } static void @@ -1052,6 +1138,20 @@ interface_removed(GDBusObjectManager *object_manager, return; } + + if (nm_streq(iface_name, NM_IWD_P2P_INTERFACE)) { + remove_p2p_device(self, proxy, object); + return; + } + + if (nm_streq(iface_name, NM_IWD_P2P_PEER_INTERFACE)) { + NMDeviceIwdP2P *p2p = get_p2p_device_from_peer(self, proxy); + + if (p2p) + nm_device_iwd_p2p_peer_add_remove(p2p, object, FALSE); + + return; + } } static void @@ -1705,6 +1805,8 @@ nm_iwd_manager_init(NMIwdManager *self) g_free, (GDestroyNotify) known_network_data_free); + priv->p2p_devices = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref); + prepare_object_manager(self); } @@ -1738,6 +1840,8 @@ dispose(GObject *object) nm_clear_g_free(&priv->last_state_dir); nm_clear_g_free(&priv->warned_state_dir); + g_hash_table_unref(nm_steal_pointer(&priv->p2p_devices)); + G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object); } @@ -1747,4 +1851,16 @@ nm_iwd_manager_class_init(NMIwdManagerClass *klass) GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->dispose = dispose; + + signals[P2P_DEVICE_ADDED] = g_signal_new(NM_IWD_MANAGER_P2P_DEVICE_ADDED, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 2, + NM_TYPE_DEVICE, + G_TYPE_STRING); } diff --git a/src/core/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h index d75ddee010..2cf4b80c90 100644 --- a/src/core/devices/wifi/nm-iwd-manager.h +++ b/src/core/devices/wifi/nm-iwd-manager.h @@ -13,20 +13,22 @@ #define NM_IWD_BUS_TYPE G_BUS_TYPE_SYSTEM #define NM_IWD_SERVICE "net.connman.iwd" -#define NM_IWD_DAEMON_INTERFACE "net.connman.iwd.Daemon" -#define NM_IWD_AGENT_MANAGER_INTERFACE "net.connman.iwd.AgentManager" -#define NM_IWD_WIPHY_INTERFACE "net.connman.iwd.Adapter" -#define NM_IWD_DEVICE_INTERFACE "net.connman.iwd.Device" -#define NM_IWD_NETWORK_INTERFACE "net.connman.iwd.Network" -#define NM_IWD_AGENT_INTERFACE "net.connman.iwd.Agent" -#define NM_IWD_WSC_INTERFACE "net.connman.iwd.SimpleConfiguration" -#define NM_IWD_KNOWN_NETWORK_INTERFACE "net.connman.iwd.KnownNetwork" -#define NM_IWD_SIGNAL_AGENT_INTERFACE "net.connman.iwd.SignalLevelAgent" -#define NM_IWD_AP_INTERFACE "net.connman.iwd.AccessPoint" -#define NM_IWD_ADHOC_INTERFACE "net.connman.iwd.AdHoc" -#define NM_IWD_STATION_INTERFACE "net.connman.iwd.Station" -#define NM_IWD_P2P_PEER_INTERFACE "net.connman.iwd.p2p.Peer" -#define NM_IWD_P2P_WFD_INTERFACE "net.connman.iwd.p2p.Display" +#define NM_IWD_DAEMON_INTERFACE "net.connman.iwd.Daemon" +#define NM_IWD_AGENT_MANAGER_INTERFACE "net.connman.iwd.AgentManager" +#define NM_IWD_WIPHY_INTERFACE "net.connman.iwd.Adapter" +#define NM_IWD_DEVICE_INTERFACE "net.connman.iwd.Device" +#define NM_IWD_NETWORK_INTERFACE "net.connman.iwd.Network" +#define NM_IWD_AGENT_INTERFACE "net.connman.iwd.Agent" +#define NM_IWD_WSC_INTERFACE "net.connman.iwd.SimpleConfiguration" +#define NM_IWD_KNOWN_NETWORK_INTERFACE "net.connman.iwd.KnownNetwork" +#define NM_IWD_SIGNAL_AGENT_INTERFACE "net.connman.iwd.SignalLevelAgent" +#define NM_IWD_AP_INTERFACE "net.connman.iwd.AccessPoint" +#define NM_IWD_ADHOC_INTERFACE "net.connman.iwd.AdHoc" +#define NM_IWD_STATION_INTERFACE "net.connman.iwd.Station" +#define NM_IWD_P2P_INTERFACE "net.connman.iwd.p2p.Device" +#define NM_IWD_P2P_PEER_INTERFACE "net.connman.iwd.p2p.Peer" +#define NM_IWD_P2P_SERVICE_MANAGER_INTERFACE "net.connman.iwd.p2p.ServiceManager" +#define NM_IWD_P2P_WFD_INTERFACE "net.connman.iwd.p2p.Display" #define NM_TYPE_IWD_MANAGER (nm_iwd_manager_get_type()) #define NM_IWD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IWD_MANAGER, NMIwdManager)) @@ -37,6 +39,8 @@ #define NM_IWD_MANAGER_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_IWD_MANAGER, NMIwdManagerClass)) +#define NM_IWD_MANAGER_P2P_DEVICE_ADDED "p2p-device-added" + typedef struct _NMIwdManager NMIwdManager; typedef struct _NMIwdManagerClass NMIwdManagerClass; diff --git a/src/core/devices/wifi/nm-wifi-factory.c b/src/core/devices/wifi/nm-wifi-factory.c index f44d187bcc..5e354c6b8d 100644 --- a/src/core/devices/wifi/nm-wifi-factory.c +++ b/src/core/devices/wifi/nm-wifi-factory.c @@ -14,6 +14,8 @@ #include "nm-device-wifi-p2p.h" #include "nm-device-olpc-mesh.h" #include "nm-device-iwd.h" +#include "nm-device-iwd-p2p.h" +#include "nm-iwd-manager.h" #include "settings/nm-settings-connection.h" #include "libnm-platform/nm-platform.h" #include "nm-config.h" @@ -67,6 +69,19 @@ p2p_device_created(NMDeviceWifi *device, NMDeviceWifiP2P *p2p_device, NMDeviceFa g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device); } +#if WITH_IWD +static void +iwd_p2p_device_added(NMIwdManager *iwd, + NMDeviceIwdP2P *p2p_device, + const char *phy_name, + NMDeviceFactory *self) +{ + nm_log_info(LOGD_PLATFORM | LOGD_WIFI, "Wi-Fi P2P device added on %s", phy_name); + + g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device); +} +#endif + static NMDevice * create_device(NMDeviceFactory *factory, const char *iface, @@ -138,8 +153,23 @@ create_device(NMDeviceFactory *factory, return device; } #if WITH_IWD - else if (!g_ascii_strcasecmp(backend, "iwd")) + else if (!g_ascii_strcasecmp(backend, "iwd")) { + NMIwdManager *iwd = nm_iwd_manager_get(); + + if (!g_signal_handler_find(iwd, + G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, + 0, + 0, + NULL, + G_CALLBACK(iwd_p2p_device_added), + factory)) + g_signal_connect(iwd, + NM_IWD_MANAGER_P2P_DEVICE_ADDED, + G_CALLBACK(iwd_p2p_device_added), + factory); + return nm_device_iwd_new(iface); + } #endif nm_log_warn(LOGD_PLATFORM | LOGD_WIFI,