iwd: Basic WFD support for NMDeviceIwdP2P

Enable WFD clients to work with the IWD backend.
This commit is contained in:
Andrew Zaborowski 2021-11-09 02:50:22 +01:00 committed by Thomas Haller
parent 51ef157096
commit 524675db75
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
3 changed files with 224 additions and 1 deletions

View file

@ -41,6 +41,8 @@ typedef struct {
bool enabled : 1;
bool stage2_ready : 1;
bool wfd_registered : 1;
} NMDeviceIwdP2PPrivate;
struct _NMDeviceIwdP2P {
@ -168,6 +170,70 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
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)) {
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,
@ -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;

View file

@ -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);

View file

@ -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__ */