summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Zaborowski <andrew.zaborowski@intel.com>2021-11-09 02:50:22 +0100
committerThomas Haller <thaller@redhat.com>2022-01-21 11:16:24 +0100
commit524675db75c0edcff25932e038fe215d8f03868d (patch)
treeec06ab46d136cfdbd5699045af68d1cf767b47e0
parent51ef15709612677e59ccd5da58961fb673dfc77c (diff)
iwd: Basic WFD support for NMDeviceIwdP2P
Enable WFD clients to work with the IWD backend.
-rw-r--r--src/core/devices/wifi/nm-device-iwd-p2p.c112
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.c109
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.h4
3 files changed, 224 insertions, 1 deletions
diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.c b/src/core/devices/wifi/nm-device-iwd-p2p.c
index da221bf616..2525db355f 100644
--- a/src/core/devices/wifi/nm-device-iwd-p2p.c
+++ b/src/core/devices/wifi/nm-device-iwd-p2p.c
@@ -41,6 +41,8 @@ typedef struct {
bool enabled : 1;
bool stage2_ready : 1;
+
+ bool wfd_registered : 1;
} NMDeviceIwdP2PPrivate;
struct _NMDeviceIwdP2P {
@@ -169,6 +171,70 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
}
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)) {
+ 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);
+ 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,
@@ -428,6 +494,11 @@ cleanup_connect_attempt(NMDeviceIwdP2P *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;
@@ -473,6 +544,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
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);
@@ -486,6 +558,44 @@ 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);
+ /* 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);
if (!peer) {
iwd_request_discovery(self, 10);
@@ -1126,9 +1236,9 @@ nm_device_iwd_p2p_class_init(NMDeviceIwdP2PClass *klass)
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->check_connection_available = check_connection_available;
device_class->complete_connection = complete_connection;
device_class->get_enabled = get_enabled;
device_class->set_enabled = set_enabled;
diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c
index fc10ed2ceb..5563ebf8fa 100644
--- a/src/core/devices/wifi/nm-iwd-manager.c
+++ b/src/core/devices/wifi/nm-iwd-manager.c
@@ -60,6 +60,8 @@ typedef struct {
char *warned_state_dir;
bool netconfig_enabled;
GHashTable *p2p_devices;
+ NMIwdWfdInfo wfd_info;
+ guint wfd_use_count;
} NMIwdManagerPrivate;
struct _NMIwdManager {
@@ -1759,6 +1761,113 @@ nm_iwd_manager_get_netconfig_enabled(NMIwdManager *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);
diff --git a/src/core/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h
index 2cf4b80c90..02cd6bba50 100644
--- a/src/core/devices/wifi/nm-iwd-manager.h
+++ b/src/core/devices/wifi/nm-iwd-manager.h
@@ -59,4 +59,8 @@ nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const ch
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__ */