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..01774b12f7 --- /dev/null +++ b/src/core/devices/wifi/nm-device-iwd-p2p.c @@ -0,0 +1,1262 @@ +/* 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; + + bool wfd_registered : 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 +check_connection_available(NMDevice *device, + NMConnection *connection, + NMDeviceCheckConAvailableFlags flags, + const char *specific_object, + GError **error) +{ + NMDeviceIwdP2P *self = NM_DEVICE_IWD_P2P(device); + NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self); + NMSettingWifiP2P *s_wifi_p2p; + GBytes *wfd_ies; + NMWifiP2PPeer *peer; + + if (specific_object) { + peer = nm_wifi_p2p_peer_lookup_for_device(NM_DEVICE(self), specific_object); + if (!peer) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "The P2P peer %s is unknown", + specific_object); + return FALSE; + } + + if (!nm_wifi_p2p_peer_check_compatible(peer, connection, FALSE)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "Requested P2P peer is not compatible with profile"); + return FALSE; + } + } else { + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE); + if (!peer) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "No compatible P2P peer found"); + return FALSE; + } + } + + s_wifi_p2p = + NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)); + wfd_ies = nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p); + if (wfd_ies) { + NMIwdWfdInfo wfd_info = {}; + + if (!nm_wifi_utils_parse_wfd_ies(wfd_ies, &wfd_info)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "Can't parse connection WFD IEs"); + return FALSE; + } + + if (!nm_iwd_manager_check_wfd_info_compatible(nm_iwd_manager_get(), &wfd_info)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, + "An incompatible WFD connection is active"); + 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->wfd_registered) { + nm_iwd_manager_unregister_wfd(nm_iwd_manager_get()); + priv->wfd_registered = FALSE; + } + + 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; + GBytes *wfd_ies; + + 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); + + /* Set the WFD IEs before connecting and before peer discovery if that is needed, + * usually the WFD IEs need to actually be sent in the Probe frames before we can + * receive the peers' WFD IEs and decide whether the peer is compatible with the + * requested WFD parameters. In the current setup we only get the WFD IEs from + * the connection settings so during a normal find the client will not be getting + * any WFD information about the peers and has to decide to connect based on the + * name and device type (category + subcategory) -- assuming that the peers even + * bother to reply to probes without WFD IEs. We'll then need to redo the find + * here in PREPARE because IWD wants to see that the parameters in the peer's + * WFD IEs match those in our WFD IEs. The normal use case for IWD is that the + * WFD client registers its WFD parameters as soon as it starts and they remain + * registered during the find and then during the connect. */ + wfd_ies = nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p); + if (wfd_ies) { + NMIwdWfdInfo wfd_info = {}; + + if (!nm_wifi_utils_parse_wfd_ies(wfd_ies, &wfd_info)) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi-p2p) Can't parse connection WFD IEs"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (!nm_iwd_manager_check_wfd_info_compatible(nm_iwd_manager_get(), &wfd_info)) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, + "Activation: (wifi-p2p) An incompatible WFD connection is active"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + if (!nm_iwd_manager_register_wfd(nm_iwd_manager_get(), &wfd_info)) { + _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi-p2p) Can't register WFD service"); + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED); + return NM_ACT_STAGE_RETURN_FAILURE; + } + + priv->wfd_registered = TRUE; + } + + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, TRUE); + 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, TRUE); + 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, TRUE)) { + /* 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; + + device_class->is_available = is_available; + device_class->check_connection_compatible = check_connection_compatible; + device_class->check_connection_available = check_connection_available; + 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-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c index 4cb76e45e7..dfbf89785c 100644 --- a/src/core/devices/wifi/nm-device-wifi-p2p.c +++ b/src/core/devices/wifi/nm-device-wifi-p2p.c @@ -168,7 +168,7 @@ check_connection_peer_joined(NMDeviceWifiP2P *device) return FALSE; /* NOTE: We currently only support connections to a specific peer */ - peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, conn); + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, conn, FALSE); if (!peer) return FALSE; @@ -369,7 +369,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) 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); + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE); if (!peer) { /* Set up a timeout on the find attempt and run a find for the same period of time */ if (priv->find_peer_timeout_id == 0) { @@ -436,7 +436,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) 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); + peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE); if (!peer) { NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND); return NM_ACT_STAGE_RETURN_FAILURE; @@ -521,7 +521,8 @@ peer_add_remove(NMDeviceWifiP2P *self, connection = nm_device_get_applied_connection(device); nm_assert(NM_IS_CONNECTION(connection)); - peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection); + peer = + nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE); if (peer) { /* A peer for the connection was found, cancel the timeout and go to configure state. */ nm_clear_g_source(&priv->find_peer_timeout_id); diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c index e360b1c30a..5563ebf8fa 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; @@ -49,6 +58,10 @@ typedef struct { NMDeviceIwd *last_agent_call_device; char *last_state_dir; char *warned_state_dir; + bool netconfig_enabled; + GHashTable *p2p_devices; + NMIwdWfdInfo wfd_info; + guint wfd_use_count; } NMIwdManagerPrivate; struct _NMIwdManager { @@ -419,6 +432,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) { @@ -998,6 +1071,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 @@ -1051,6 +1140,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 @@ -1469,6 +1572,15 @@ get_daemon_info_cb(GObject *source, GAsyncResult *res, gpointer user_data) nm_clear_g_free(&priv->last_state_dir); priv->last_state_dir = g_variant_dup_string(value, NULL); + } else if (nm_streq(key, "NetworkConfigurationEnabled")) { + if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) { + _LOGE("Daemon.GetInfo property %s is typed '%s' instead of 'b'", + key, + g_variant_get_type_string(value)); + goto next; + } + + priv->netconfig_enabled = g_variant_get_boolean(value); } next: @@ -1543,6 +1655,8 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data) if (priv->agent_id) register_agent(self); + priv->netconfig_enabled = false; /* Assume false until GetInfo() results come in */ + daemon = g_dbus_object_manager_get_interface(object_manager, "/net/connman/iwd", /* IWD 1.15+ */ NM_IWD_DAEMON_INTERFACE); @@ -1639,6 +1753,121 @@ nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const ch return interface ? G_DBUS_PROXY(interface) : NULL; } +gboolean +nm_iwd_manager_get_netconfig_enabled(NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + return priv->netconfig_enabled; +} + +/* IWD's net.connman.iwd.p2p.ServiceManager.RegisterDisplayService() is global so + * two local Wi-Fi P2P devices can't be connected to (or even scanning for) WFD + * peers using different WFD IE contents, e.g. one as a sink and one as a source. + * If one device is connected to a peer without a WFD service, another can try + * to establish a WFD connection to a peer since this won't disturb the first + * connection. Similarly if one device is connected to a peer with WFD, another + * can make a connection to a non-WFD peer (if that exists...) because a non-WFD + * peer will simply ignore the WFD IEs, but it cannot connect to or search for a + * peer that's WFD capable without passing our own WFD IEs, i.e. if the new + * NMSettingsConnection has no WFD IEs and we're already in a WFD connection on + * another device, we can't activate that new connection. We expose methods + * for the NMDeviceIwdP2P's to register/unregister the service and one to check + * if there's already an incompatible connection active. + */ +gboolean +nm_iwd_manager_check_wfd_info_compatible(NMIwdManager *self, const NMIwdWfdInfo *wfd_info) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + if (priv->wfd_use_count == 0) + return TRUE; + + return nm_wifi_utils_wfd_info_eq(&priv->wfd_info, wfd_info); +} + +gboolean +nm_iwd_manager_register_wfd(NMIwdManager *self, const NMIwdWfdInfo *wfd_info) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + gs_unref_object GDBusInterface *service_manager = NULL; + GVariantBuilder builder; + + nm_assert(nm_iwd_manager_check_wfd_info_compatible(self, wfd_info)); + + if (!priv->object_manager) + return FALSE; + + service_manager = g_dbus_object_manager_get_interface(priv->object_manager, + "/net/connman/iwd", + NM_IWD_P2P_SERVICE_MANAGER_INTERFACE); + if (!service_manager) { + _LOGE("IWD P2P service manager not found"); + return FALSE; + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Source", g_variant_new_boolean(wfd_info->source)); + g_variant_builder_add(&builder, "{sv}", "Sink", g_variant_new_boolean(wfd_info->sink)); + + if (wfd_info->source) + g_variant_builder_add(&builder, "{sv}", "Port", g_variant_new_uint16(wfd_info->port)); + + if (wfd_info->sink && wfd_info->has_audio) + g_variant_builder_add(&builder, "{sv}", "HasAudio", g_variant_new_boolean(TRUE)); + + if (wfd_info->has_uibc) + g_variant_builder_add(&builder, "{sv}", "HasUIBC", g_variant_new_boolean(TRUE)); + + if (wfd_info->has_cp) + g_variant_builder_add(&builder, + "{sv}", + "HasContentProtection", + g_variant_new_boolean(TRUE)); + + g_dbus_proxy_call(G_DBUS_PROXY(service_manager), + "RegisterDisplayService", + g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); + + memcpy(&priv->wfd_info, wfd_info, sizeof(priv->wfd_info)); + priv->wfd_use_count++; + return TRUE; +} + +void +nm_iwd_manager_unregister_wfd(NMIwdManager *self) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + gs_unref_object GDBusInterface *service_manager = NULL; + + nm_assert(priv->wfd_use_count > 0); + + priv->wfd_use_count--; + + if (!priv->object_manager) + return; + + service_manager = g_dbus_object_manager_get_interface(priv->object_manager, + "/net/connman/iwd", + NM_IWD_P2P_SERVICE_MANAGER_INTERFACE); + if (!service_manager) + return; + + g_dbus_proxy_call(G_DBUS_PROXY(service_manager), + "UnregisterDisplayService", + g_variant_new("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + NULL, + NULL); +} + /*****************************************************************************/ NM_DEFINE_SINGLETON_GETTER(NMIwdManager, nm_iwd_manager_get, NM_TYPE_IWD_MANAGER); @@ -1685,6 +1914,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); } @@ -1718,6 +1949,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); } @@ -1727,4 +1960,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 b85d2e7149..02cd6bba50 100644 --- a/src/core/devices/wifi/nm-iwd-manager.h +++ b/src/core/devices/wifi/nm-iwd-manager.h @@ -13,18 +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.WiFiSimpleConfiguration" -#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_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)) @@ -35,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; @@ -51,4 +57,10 @@ NMSettingsConnection *nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self GDBusProxy * nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name); +gboolean nm_iwd_manager_get_netconfig_enabled(NMIwdManager *self); + +gboolean nm_iwd_manager_check_wfd_info_compatible(NMIwdManager *self, const NMIwdWfdInfo *wfd_info); +gboolean nm_iwd_manager_register_wfd(NMIwdManager *self, const NMIwdWfdInfo *wfd_info); +void nm_iwd_manager_unregister_wfd(NMIwdManager *self); + #endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */ diff --git a/src/core/devices/wifi/nm-wifi-factory.c b/src/core/devices/wifi/nm-wifi-factory.c index d17d04cee7..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, @@ -76,6 +91,7 @@ create_device(NMDeviceFactory *factory, { gs_free char *backend_free = NULL; const char *backend; + _NM80211Mode mode; g_return_val_if_fail(iface != NULL, NULL); g_return_val_if_fail(plink != NULL, NULL); @@ -85,6 +101,20 @@ create_device(NMDeviceFactory *factory, if (plink->type != NM_LINK_TYPE_WIFI) return nm_device_olpc_mesh_new(iface); + /* Ignore monitor-mode and other unhandled interface types. + * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage + * them if/when they change to a handled type. + */ + mode = nm_platform_wifi_get_mode(NM_PLATFORM_GET, plink->ifindex); + if (!NM_IN_SET(mode, + _NM_802_11_MODE_INFRA, + _NM_802_11_MODE_ADHOC, + _NM_802_11_MODE_AP, + _NM_802_11_MODE_MESH)) { + *out_ignore = TRUE; + return NULL; + } + backend = nm_config_data_get_device_config_by_pllink(NM_CONFIG_GET_DATA, NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND, plink, @@ -103,7 +133,6 @@ create_device(NMDeviceFactory *factory, if (!g_ascii_strcasecmp(backend, "wpa_supplicant")) { NMDevice *device; _NMDeviceWifiCapabilities capabilities; - _NM80211Mode mode; if (!nm_platform_wifi_get_capabilities(NM_PLATFORM_GET, plink->ifindex, &capabilities)) { nm_log_warn(LOGD_PLATFORM | LOGD_WIFI, @@ -113,16 +142,6 @@ create_device(NMDeviceFactory *factory, return NULL; } - /* Ignore monitor-mode and other unhandled interface types. - * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage - * them if/when they change to a handled type. - */ - mode = nm_platform_wifi_get_mode(NM_PLATFORM_GET, plink->ifindex); - if (mode == _NM_802_11_MODE_UNKNOWN) { - *out_ignore = TRUE; - return NULL; - } - device = nm_device_wifi_new(iface, capabilities); g_signal_connect_object(device, @@ -134,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, diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c index 0c8f9ba776..0a17427067 100644 --- a/src/core/devices/wifi/nm-wifi-p2p-peer.c +++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c @@ -18,6 +18,7 @@ #include "nm-setting-wireless.h" #include "nm-utils.h" #include "nm-wifi-utils.h" +#include "nm-iwd-manager.h" #include "libnm-platform/nm-platform.h" #include "supplicant/nm-supplicant-types.h" @@ -100,14 +101,16 @@ nm_wifi_p2p_peers_get_paths(const CList *peers_lst_head) } NMWifiP2PPeer * -nm_wifi_p2p_peers_find_first_compatible(const CList *peers_lst_head, NMConnection *connection) +nm_wifi_p2p_peers_find_first_compatible(const CList *peers_lst_head, + NMConnection *connection, + gboolean check_wfd) { NMWifiP2PPeer *peer; g_return_val_if_fail(connection, NULL); c_list_for_each_entry (peer, peers_lst_head, peers_lst) { - if (nm_wifi_p2p_peer_check_compatible(peer, connection)) + if (nm_wifi_p2p_peer_check_compatible(peer, connection, check_wfd)) return peer; } return NULL; @@ -419,6 +422,90 @@ nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const NMSupplicantP return changed; } +gboolean +nm_wifi_p2p_peer_update_from_iwd_object(NMWifiP2PPeer *peer, GDBusObject *obj) +{ + NMWifiP2PPeerPrivate *priv; + gboolean changed = FALSE; + nm_auto_ref_string NMRefString *peer_path = NULL; + gs_unref_object GDBusProxy *peer_proxy = NULL; + gs_unref_object GDBusProxy *wfd_proxy = NULL; + GVariant *value; + gs_unref_bytes GBytes *wfd_ies = NULL; + + g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE); + + peer_proxy = G_DBUS_PROXY(g_dbus_object_get_interface(obj, NM_IWD_P2P_PEER_INTERFACE)); + wfd_proxy = G_DBUS_PROXY(g_dbus_object_get_interface(obj, NM_IWD_P2P_WFD_INTERFACE)); + g_return_val_if_fail(peer_proxy, FALSE); + + peer_path = nm_ref_string_new(g_dbus_object_get_object_path(obj)); + priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer); + nm_assert(!priv->supplicant_path || priv->supplicant_path == peer_path); + + g_object_freeze_notify(G_OBJECT(peer)); + + if (!priv->supplicant_path) { + priv->supplicant_path = g_steal_pointer(&peer_path); + changed = TRUE; + } + + value = g_dbus_proxy_get_cached_property(peer_proxy, "Name"); + if (value && g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) + changed |= nm_wifi_p2p_peer_set_name(peer, g_variant_get_string(value, NULL)); + else + changed |= nm_wifi_p2p_peer_set_name(peer, ""); + nm_g_variant_unref(value); + + value = g_dbus_proxy_get_cached_property(peer_proxy, "Address"); + if (value && g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) + changed |= nm_wifi_p2p_peer_set_address(peer, g_variant_get_string(value, NULL)); + nm_g_variant_unref(value); + + if (wfd_proxy) { + NMIwdWfdInfo wfd = {}; + + value = g_dbus_proxy_get_cached_property(wfd_proxy, "Source"); + wfd.source = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(value); + nm_g_variant_unref(value); + + value = g_dbus_proxy_get_cached_property(wfd_proxy, "Sink"); + wfd.sink = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(value); + nm_g_variant_unref(value); + + value = g_dbus_proxy_get_cached_property(wfd_proxy, "Port"); + wfd.port = (value && g_variant_is_of_type(value, G_VARIANT_TYPE_UINT16)) + ? g_variant_get_uint16(value) + : 0; + nm_g_variant_unref(value); + + value = g_dbus_proxy_get_cached_property(wfd_proxy, "HasAudio"); + wfd.has_audio = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(value); + nm_g_variant_unref(value); + + value = g_dbus_proxy_get_cached_property(wfd_proxy, "HasUIBC"); + wfd.has_uibc = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(value); + nm_g_variant_unref(value); + + value = g_dbus_proxy_get_cached_property(wfd_proxy, "HasContentProtection"); + wfd.has_cp = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN) + && g_variant_get_boolean(value); + nm_g_variant_unref(value); + + wfd_ies = nm_wifi_utils_build_wfd_ies(&wfd); + } + + changed |= nm_wifi_p2p_peer_set_wfd_ies(peer, wfd_ies); + + g_object_thaw_notify(G_OBJECT(peer)); + + return changed; +} + const char * nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_len, gint32 now_s) { @@ -458,7 +545,7 @@ nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_l } gboolean -nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection) +nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection, gboolean check_wfd) { NMWifiP2PPeerPrivate *priv; NMSettingWifiP2P *s_wifi_p2p; @@ -478,6 +565,10 @@ nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection) if (hwaddr && (!priv->address || !nm_utils_hwaddr_matches(hwaddr, -1, priv->address, -1))) return FALSE; + if (check_wfd && nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p) + && !nm_wifi_p2p_peer_get_wfd_ies(self)) + return FALSE; + return TRUE; } @@ -559,6 +650,17 @@ nm_wifi_p2p_peer_new_from_properties(const NMSupplicantPeerInfo *peer_info) return peer; } +NMWifiP2PPeer * +nm_wifi_p2p_peer_new_from_iwd_object(GDBusObject *obj) +{ + NMWifiP2PPeer *peer; + + /* TODO: Set the flags here */ + peer = g_object_new(NM_TYPE_WIFI_P2P_PEER, NULL); + nm_wifi_p2p_peer_update_from_iwd_object(peer, obj); + return peer; +} + static void finalize(GObject *object) { diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.h b/src/core/devices/wifi/nm-wifi-p2p-peer.h index f8a2890dff..5124d1dea4 100644 --- a/src/core/devices/wifi/nm-wifi-p2p-peer.h +++ b/src/core/devices/wifi/nm-wifi-p2p-peer.h @@ -45,11 +45,15 @@ struct _NMSupplicantPeerInfo; GType nm_wifi_p2p_peer_get_type(void); NMWifiP2PPeer *nm_wifi_p2p_peer_new_from_properties(const struct _NMSupplicantPeerInfo *peer_info); +NMWifiP2PPeer *nm_wifi_p2p_peer_new_from_iwd_object(GDBusObject *obj); gboolean nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const struct _NMSupplicantPeerInfo *peer_info); +gboolean nm_wifi_p2p_peer_update_from_iwd_object(NMWifiP2PPeer *peer, GDBusObject *obj); -gboolean nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection); +gboolean nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, + NMConnection *connection, + gboolean check_wfd); const char *nm_wifi_p2p_peer_get_supplicant_path(NMWifiP2PPeer *peer); @@ -81,7 +85,8 @@ nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_l const char **nm_wifi_p2p_peers_get_paths(const CList *peers_lst_head); NMWifiP2PPeer *nm_wifi_p2p_peers_find_first_compatible(const CList *peers_lst_head, - NMConnection *connection); + NMConnection *connection, + gboolean check_wfd); NMWifiP2PPeer *nm_wifi_p2p_peers_find_by_supplicant_path(const CList *peers_lst_head, const char *path); diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c index c3f3b1200f..1a8204b082 100644 --- a/src/core/devices/wifi/nm-wifi-utils.c +++ b/src/core/devices/wifi/nm-wifi-utils.c @@ -1800,3 +1800,151 @@ nm_wifi_utils_connection_to_iwd_config(NMConnection *connection, return g_steal_pointer(&file); } + +/* Wi-Fi Display Technical Specification v2.1.0 Table 27 */ +enum wfd_subelem_type { + WFD_SUBELEM_WFD_DEVICE_INFORMATION = 0, + WFD_SUBELEM_ASSOCIATED_BSSID = 1, + WFD_SUBELEM_COUPLED_SINK_INFORMATION = 6, + WFD_SUBELEM_EXTENDED_CAPABILITY = 7, + WFD_SUBELEM_LOCAL_IP_ADDRESS = 8, + WFD_SUBELEM_SESION_INFORMATION = 9, + WFD_SUBELEM_ALTERNATIVE_MAC_ADDRESS = 10, + WFD_SUBELEM_R2_DEVICE_INFORMATION = 11, +}; + +bool +nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd) +{ + size_t len; + const uint8_t *data = g_bytes_get_data(ies, &len); + const uint8_t *dev_info = NULL; + uint16_t dev_info_len = 0; + uint16_t dev_info_flags; + const uint8_t *ext_capability = NULL; + uint16_t ext_capability_len = 0; + + /* The single WFD IEs array provided by the client is supposed to be sent to + * the peer in the different frame types that may include the WFD IE: Probe + * Request/Response, Beacon, (Re)Association Request/Response, GO + * Negotiation Request/Response/Confirm and Provision Discovery + * Request/Response. + * + * It's going to be a subset of the elements allowed in all those frames. + * Validate that it contains at least a valid WFD Device Information (with + * the Session Available bit true) and that the sequence of subelements is + * valid. + */ + while (len) { + uint8_t subelem_id; + uint16_t subelem_len; + + /* Does the subelement header fit */ + if (len < 3) + return FALSE; + + subelem_id = data[0]; + subelem_len = (data[1] << 8) | data[2]; + data += 3; + len -= 3; + + if (subelem_len > len) + return FALSE; + + if (subelem_id == WFD_SUBELEM_WFD_DEVICE_INFORMATION) { + /* Is there a duplicate WFD Device Information */ + if (dev_info) + return FALSE; + + dev_info = data; + dev_info_len = subelem_len; + } + + if (subelem_id == WFD_SUBELEM_EXTENDED_CAPABILITY) { + /* Is there a duplicate WFD Extended Capability */ + if (ext_capability) + return FALSE; + + ext_capability = data; + ext_capability_len = subelem_len; + } + + data += subelem_len; + len -= subelem_len; + } + + if (!dev_info || dev_info_len != 6) + return FALSE; + + dev_info_flags = (dev_info[0] << 8) | dev_info[1]; + + /* Secondary sink not supported */ + if ((dev_info_flags & 3) == 2) + return FALSE; + + /* Must be available for WFD Session */ + if (((dev_info_flags >> 4) & 3) != 1) + return FALSE; + + /* TDLS persistent group re-invocation not supported */ + if ((dev_info_flags >> 13) & 1) + return FALSE; + + /* All other flags indicate support but not a requirement for something + * so not preserving them in the IEs IWD eventually sends doesn't break + * basic functionality. + */ + + if (ext_capability && ext_capability_len != 2) + return FALSE; + + if (!out_wfd) + return TRUE; + + out_wfd->source = NM_IN_SET(dev_info_flags & 3, 0, 3); + out_wfd->sink = NM_IN_SET(dev_info_flags & 3, 1, 3); + out_wfd->port = (dev_info[2] << 8) | dev_info[3]; + out_wfd->has_audio = + out_wfd->sink ? ((dev_info_flags >> 10) & 1) == 0 : (((dev_info_flags >> 11) & 1) == 1); + out_wfd->has_uibc = ext_capability && (ext_capability[1] & 1) == 1; + out_wfd->has_cp = ((dev_info_flags >> 8) & 1) == 1; + return TRUE; +} + +GBytes * +nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd) +{ + uint8_t data[64]; + uint8_t *ptr = data; + + *ptr++ = WFD_SUBELEM_WFD_DEVICE_INFORMATION; + *ptr++ = 0; /* WFD Subelement length */ + *ptr++ = 6; + *ptr++ = 0; /* WFD Device Information bitmap: */ + *ptr++ = (wfd->source ? (wfd->sink ? 3 : 0) : 1) | 0x10 | /* WFD Session Available */ + (wfd->has_cp ? 0x100 : 0) | (wfd->has_audio ? 0 : 0x400); + *ptr++ = wfd->port >> 8; + *ptr++ = wfd->port & 255; + *ptr++ = 0; /* WFD Device Maximum throughput */ + *ptr++ = 10; + + if (wfd->has_uibc) { + *ptr++ = WFD_SUBELEM_EXTENDED_CAPABILITY; + *ptr++ = 0; /* WFD Subelement length */ + *ptr++ = 2; + *ptr++ = 0x00; /* WFD Extended Capability Bitmap: */ + *ptr++ = 0x10; /* UIBC Support */ + } + + return g_bytes_new(data, ptr - data); +} + +bool +nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b) +{ + if (!a || !b) + return a == b; + + return a->source == b->source && a->sink == b->sink && a->port == b->port + && a->has_audio == b->has_audio && a->has_uibc == b->has_uibc && a->has_cp == b->has_cp; +} diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h index 5085e99af7..1d46a9000f 100644 --- a/src/core/devices/wifi/nm-wifi-utils.h +++ b/src/core/devices/wifi/nm-wifi-utils.h @@ -20,6 +20,15 @@ typedef enum { NM_IWD_NETWORK_SECURITY_8021X, } NMIwdNetworkSecurity; +typedef struct { + bool source; + bool sink; + uint16_t port; + bool has_audio; + bool has_uibc; + bool has_cp; +} NMIwdWfdInfo; + gboolean nm_wifi_utils_complete_connection(GBytes *ssid, const char *bssid, _NM80211Mode mode, @@ -43,4 +52,8 @@ char *nm_wifi_utils_get_iwd_config_filename(const char *ssid, GKeyFile * nm_wifi_utils_connection_to_iwd_config(NMConnection *conn, char **out_filename, GError **error); +bool nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd); +GBytes *nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd); +bool nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b); + #endif /* __NM_WIFI_UTILS_H__ */