summary refs log tree commit diff
path: root/src/supplicant
diff options
context:
space:
mode:
Diffstat (limited to 'src/supplicant')
-rw-r--r--src/supplicant/nm-supplicant-config.c101
-rw-r--r--src/supplicant/nm-supplicant-interface.c965
-rw-r--r--src/supplicant/nm-supplicant-interface.h42
-rw-r--r--src/supplicant/nm-supplicant-manager.c159
-rw-r--r--src/supplicant/nm-supplicant-manager.h5
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c20
-rw-r--r--src/supplicant/tests/meson.build3
-rw-r--r--src/supplicant/tests/test-supplicant-config.c72
8 files changed, 1238 insertions, 129 deletions
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index cfb33008..7708224b 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -23,9 +23,10 @@
 
 #include "nm-supplicant-config.h"
 
-#include <string.h>
 #include <stdlib.h>
 
+#include "nm-core-internal.h"
+
 #include "nm-supplicant-settings-verify.h"
 #include "nm-setting.h"
 #include "nm-auth-subject.h"
@@ -371,7 +372,6 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
                                          NMSettingMacsec * setting,
                                          GError **error)
 {
-	gs_unref_bytes GBytes *bytes = NULL;
 	const char *value;
 	char buf[32];
 	int port;
@@ -395,43 +395,50 @@ nm_supplicant_config_add_setting_macsec (NMSupplicantConfig * self,
 	}
 
 	if (nm_setting_macsec_get_mode (setting) == NM_SETTING_MACSEC_MODE_PSK) {
+		guint8 buffer_cak[NM_SETTING_MACSEC_MKA_CAK_LENGTH/2];
+		guint8 buffer_ckn[NM_SETTING_MACSEC_MKA_CKN_LENGTH/2];
+
 		if (!nm_supplicant_config_add_option (self, "key_mgmt", "NONE", -1, NULL, error))
 			return FALSE;
 
-		/* CAK */
 		value = nm_setting_macsec_get_mka_cak (setting);
-		if (!value) {
+		if (   !value
+		    || !nm_utils_hexstr2bin_buf (value,
+		                                 FALSE,
+		                                 FALSE,
+		                                 NULL,
+		                                 buffer_cak)) {
 			g_set_error_literal (error,
 			                     NM_SUPPLICANT_ERROR,
 			                     NM_SUPPLICANT_ERROR_CONFIG,
-			                     "missing MKA CAK");
+			                     value ? "invalid MKA CAK" : "missing MKA CAK");
 			return FALSE;
 		}
-
-		bytes = nm_utils_hexstr2bin (value);
 		if (!nm_supplicant_config_add_option (self,
 		                                      "mka_cak",
-		                                      g_bytes_get_data (bytes, NULL),
-		                                      g_bytes_get_size (bytes),
+		                                      (char *) buffer_cak,
+		                                      sizeof (buffer_cak),
 		                                      "<hidden>",
 		                                      error))
 			return FALSE;
 
-		/* CKN */
 		value = nm_setting_macsec_get_mka_ckn (setting);
-		if (!value) {
+		if (   !value
+		    || !nm_utils_hexstr2bin_buf (value,
+		                                 FALSE,
+		                                 FALSE,
+		                                 NULL,
+		                                 buffer_ckn)) {
 			g_set_error_literal (error,
 			                     NM_SUPPLICANT_ERROR,
 			                     NM_SUPPLICANT_ERROR_CONFIG,
-			                     "missing MKA CKN");
+			                     value ? "invalid MKA CKN" : "missing MKA CKN");
 			return FALSE;
 		}
-
-		bytes = nm_utils_hexstr2bin (value);
 		if (!nm_supplicant_config_add_option (self,
 		                                      "mka_ckn",
-		                                      g_bytes_get_data (bytes, NULL),
-		                                      g_bytes_get_size (bytes),
+		                                      (char *) buffer_ckn,
+		                                      sizeof (buffer_ckn),
 		                                      NULL,
 		                                      error))
 			return FALSE;
@@ -563,7 +570,7 @@ nm_supplicant_config_add_bgscan (NMSupplicantConfig *self,
 	                  NM_SETTING_WIRELESS_MODE_ADHOC))
 		return TRUE;
 
-	/* Don't scan when the connection is locked to a specifc AP, since
+	/* Don't scan when the connection is locked to a specific AP, since
 	 * intra-ESS roaming (which requires periodic scanning) isn't being
 	 * used due to the specific AP lock. (bgo #513820)
 	 */
@@ -695,10 +702,16 @@ add_wep_key (NMSupplicantConfig *self,
 	if (   (wep_type == NM_WEP_KEY_TYPE_UNKNOWN)
 	    || (wep_type == NM_WEP_KEY_TYPE_KEY)) {
 		if ((key_len == 10) || (key_len == 26)) {
-			gs_unref_bytes GBytes *bytes = NULL;
-
-			bytes = nm_utils_hexstr2bin (key);
-			if (!bytes) {
+			guint8 buffer[26/2];
+
+			if (!nm_utils_hexstr2bin_full (key,
+			                               FALSE,
+			                               FALSE,
+			                               NULL,
+			                               key_len / 2,
+			                               buffer,
+			                               sizeof (buffer),
+			                               NULL)) {
 				g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 				             "cannot add wep-key %s to suplicant config because key is not hex",
 				             name);
@@ -706,8 +719,8 @@ add_wep_key (NMSupplicantConfig *self,
 			}
 			if (!nm_supplicant_config_add_option (self,
 			                                      name,
-			                                      g_bytes_get_data (bytes, NULL),
-			                                      g_bytes_get_size (bytes),
+			                                      (char *) buffer,
+			                                      key_len / 2,
 			                                      "<hidden>",
 			                                      error))
 				return FALSE;
@@ -793,32 +806,41 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	if (psk) {
 		size_t psk_len = strlen (psk);
 
-		if (psk_len == 64) {
-			gs_unref_bytes GBytes *bytes = NULL;
+
+		if (psk_len >= 8 && psk_len <= 63) {
+			/* Use TYPE_STRING here so that it gets pushed to the
+			 * supplicant as a string, and therefore gets quoted,
+			 * and therefore the supplicant will interpret it as a
+			 * passphrase and not a hex key.
+			 */
+			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error))
+				return FALSE;
+		} else if (nm_streq (key_mgmt, "sae")) {
+			/* If the SAE password doesn't comply with WPA-PSK limitation,
+			 * we need to call it "sae_password" instead of "psk".
+			 */
+			if (!nm_supplicant_config_add_option_with_type (self, "sae_password", psk, -1, TYPE_STRING, "<hidden>", error))
+				return FALSE;
+		} else if (psk_len == 64) {
+			guint8 buffer[32];
 
 			/* Hex PSK */
-			bytes = nm_utils_hexstr2bin (psk);
-			if (!bytes) {
+			if (!nm_utils_hexstr2bin_buf (psk,
+			                              FALSE,
+			                              FALSE,
+			                              NULL,
+			                              buffer)) {
 				g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 				             "Cannot add psk to supplicant config due to invalid hex");
 				return FALSE;
 			}
-
 			if (!nm_supplicant_config_add_option (self,
 			                                      "psk",
-			                                      g_bytes_get_data (bytes, NULL),
-			                                      g_bytes_get_size (bytes),
+			                                      (char *) buffer,
+			                                      sizeof (buffer),
 			                                      "<hidden>",
 			                                      error))
 				return FALSE;
-		} else if (psk_len >= 8 && psk_len <= 63) {
-			/* Use TYPE_STRING here so that it gets pushed to the
-			 * supplicant as a string, and therefore gets quoted,
-			 * and therefore the supplicant will interpret it as a
-			 * passphrase and not a hex key.
-			 */
-			if (!nm_supplicant_config_add_option_with_type (self, "psk", psk, -1, TYPE_STRING, "<hidden>", error))
-				return FALSE;
 		} else {
 			g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			             "Cannot add psk to supplicant config due to invalid PSK length %u (not between 8 and 63 characters)",
@@ -845,7 +867,8 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	/* Only WPA-specific things when using WPA */
 	if (   !strcmp (key_mgmt, "wpa-none")
 	    || !strcmp (key_mgmt, "wpa-psk")
-	    || !strcmp (key_mgmt, "wpa-eap")) {
+	    || !strcmp (key_mgmt, "wpa-eap")
+	    || !strcmp (key_mgmt, "sae")) {
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, proto, protos, "proto", ' ', TRUE, NULL, error))
 			return FALSE;
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, pairwise, pairwise, "pairwise", ' ', TRUE, NULL, error))
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 0af9ebdb..f46689eb 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -22,21 +22,24 @@
 #include "nm-default.h"
 
 #include "nm-supplicant-interface.h"
+#include "nm-supplicant-manager.h"
 
 #include <stdio.h>
-#include <string.h>
 
 #include "NetworkManagerUtils.h"
 #include "nm-supplicant-config.h"
 #include "nm-core-internal.h"
 #include "nm-dbus-compat.h"
 
-#define WPAS_DBUS_IFACE_INTERFACE       WPAS_DBUS_INTERFACE ".Interface"
-#define WPAS_DBUS_IFACE_INTERFACE_WPS   WPAS_DBUS_INTERFACE ".Interface.WPS"
-#define WPAS_DBUS_IFACE_BSS             WPAS_DBUS_INTERFACE ".BSS"
-#define WPAS_DBUS_IFACE_NETWORK         WPAS_DBUS_INTERFACE ".Network"
-#define WPAS_ERROR_INVALID_IFACE        WPAS_DBUS_INTERFACE ".InvalidInterface"
-#define WPAS_ERROR_EXISTS_ERROR         WPAS_DBUS_INTERFACE ".InterfaceExists"
+#define WPAS_DBUS_IFACE_INTERFACE             WPAS_DBUS_INTERFACE ".Interface"
+#define WPAS_DBUS_IFACE_INTERFACE_WPS         WPAS_DBUS_INTERFACE ".Interface.WPS"
+#define WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE  WPAS_DBUS_INTERFACE ".Interface.P2PDevice"
+#define WPAS_DBUS_IFACE_BSS                   WPAS_DBUS_INTERFACE ".BSS"
+#define WPAS_DBUS_IFACE_PEER                  WPAS_DBUS_INTERFACE ".Peer"
+#define WPAS_DBUS_IFACE_GROUP                 WPAS_DBUS_INTERFACE ".Group"
+#define WPAS_DBUS_IFACE_NETWORK               WPAS_DBUS_INTERFACE ".Network"
+#define WPAS_ERROR_INVALID_IFACE              WPAS_DBUS_INTERFACE ".InvalidInterface"
+#define WPAS_ERROR_EXISTS_ERROR               WPAS_DBUS_INTERFACE ".InterfaceExists"
 
 /*****************************************************************************/
 
@@ -45,6 +48,11 @@ typedef struct {
 	gulong change_id;
 } BssData;
 
+typedef struct {
+	GDBusProxy *proxy;
+	gulong change_id;
+} PeerData;
+
 struct _AddNetworkData;
 
 typedef struct {
@@ -74,26 +82,38 @@ typedef struct _AddNetworkData {
 } AddNetworkData;
 
 enum {
-	STATE,               /* change in the interface's state */
-	REMOVED,             /* interface was removed by the supplicant */
-	BSS_UPDATED,         /* a new BSS appeared or an existing had properties changed */
-	BSS_REMOVED,         /* supplicant removed BSS from its scan list */
-	SCAN_DONE,           /* wifi scan is complete */
-	CREDENTIALS_REQUEST, /* 802.1x identity or password requested */
-	WPS_CREDENTIALS,     /* WPS credentials received */
+	STATE,                   /* change in the interface's state */
+	REMOVED,                 /* interface was removed by the supplicant */
+	BSS_UPDATED,             /* a new BSS appeared or an existing had properties changed */
+	BSS_REMOVED,             /* supplicant removed BSS from its scan list */
+	PEER_UPDATED,            /* a new Peer appeared or an existing had properties changed */
+	PEER_REMOVED,            /* supplicant removed Peer from its scan list */
+	SCAN_DONE,               /* wifi scan is complete */
+	CREDENTIALS_REQUEST,     /* 802.1x identity or password requested */
+	WPS_CREDENTIALS,         /* WPS credentials received */
+	GROUP_STARTED,           /* a new Group (interface) was created */
+	GROUP_FINISHED,          /* a Group (interface) has been finished */
+	GROUP_FORMATION_FAILURE, /* P2P Group formation failed */
 	LAST_SIGNAL
 };
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_IFACE,
+	PROP_OBJECT_PATH,
+	PROP_P2P_GROUP_JOINED,
+	PROP_P2P_GROUP_PATH,
+	PROP_P2P_GROUP_OWNER,
 	PROP_SCANNING,
 	PROP_CURRENT_BSS,
 	PROP_DRIVER,
+	PROP_P2P_AVAILABLE,
 	PROP_FAST_SUPPORT,
 	PROP_AP_SUPPORT,
 	PROP_PMF_SUPPORT,
 	PROP_FILS_SUPPORT,
+	PROP_P2P_SUPPORT,
+	PROP_WFD_SUPPORT,
 );
 
 typedef struct {
@@ -104,6 +124,8 @@ typedef struct {
 	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
 	NMSupplicantFeature pmf_support;
 	NMSupplicantFeature fils_support;
+	NMSupplicantFeature p2p_support;
+	NMSupplicantFeature wfd_support;
 	guint32        max_scan_ssids;
 	guint32        ready_count;
 
@@ -120,6 +142,14 @@ typedef struct {
 	GCancellable * init_cancellable;
 	GDBusProxy *   iface_proxy;
 	GCancellable * other_cancellable;
+	GDBusProxy *   p2p_proxy;
+	GDBusProxy *   group_proxy;
+
+	gboolean       p2p_proxy_acquired;
+	gboolean       group_proxy_acquired;
+	gboolean       p2p_capable;
+
+	gboolean       p2p_group_owner;
 
 	WpsData *wps_data;
 
@@ -129,6 +159,8 @@ typedef struct {
 	GHashTable *   bss_proxies;
 	char *         current_bss;
 
+	GHashTable *   peer_proxies;
+
 	gint64         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_ms() */
 
 } NMSupplicantInterfacePrivate;
@@ -311,6 +343,122 @@ bss_add_new (NMSupplicantInterface *self, const char *object_path)
 	                             self);
 }
 
+static void
+peer_data_destroy (gpointer user_data)
+{
+	PeerData *peer_data = user_data;
+
+	nm_clear_g_signal_handler (peer_data->proxy, &peer_data->change_id);
+	g_object_unref (peer_data->proxy);
+	g_slice_free (PeerData, peer_data);
+}
+
+static void
+peer_proxy_properties_changed_cb (GDBusProxy *proxy,
+                                  GVariant *changed_properties,
+                                  char **invalidated_properties,
+                                  gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	g_signal_emit (self, signals[PEER_UPDATED], 0,
+	               g_dbus_proxy_get_object_path (proxy),
+	               changed_properties);
+}
+
+static GVariant *
+peer_proxy_get_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
+{
+	gs_strfreev char **properties = NULL;
+	GVariantBuilder builder;
+	char **iter;
+
+	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+	if (iter) {
+		while (*iter) {
+			GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);
+
+			g_variant_builder_add (&builder, "{sv}", *iter++, copy);
+			g_variant_unref (copy);
+		}
+	}
+	return g_variant_builder_end (&builder);
+}
+
+static void
+peer_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+	GVariant *props = NULL;
+	const char *object_path;
+	PeerData *peer_data;
+	gboolean success;
+
+	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
+	if (   !success
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (!success) {
+		_LOGD ("failed to acquire Peer proxy: (%s)", error->message);
+		g_hash_table_remove (priv->peer_proxies,
+		                     g_dbus_proxy_get_object_path (proxy));
+		return;
+	}
+
+	object_path = g_dbus_proxy_get_object_path (proxy);
+	peer_data = g_hash_table_lookup (priv->peer_proxies, object_path);
+	if (!peer_data)
+		return;
+
+	peer_data->change_id = g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (peer_proxy_properties_changed_cb), self);
+
+	props = peer_proxy_get_properties (self, proxy);
+
+	g_signal_emit (self, signals[PEER_UPDATED], 0,
+	               g_dbus_proxy_get_object_path (proxy),
+	               g_variant_ref_sink (props));
+	g_variant_unref (props);
+}
+
+static void
+peer_add_new (NMSupplicantInterface *self, const char *object_path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	GDBusProxy *peer_proxy;
+	PeerData *peer_data;
+
+	g_return_if_fail (object_path != NULL);
+
+	if (g_hash_table_lookup (priv->peer_proxies, object_path))
+		return;
+
+	peer_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+	                           "g-bus-type", G_BUS_TYPE_SYSTEM,
+	                           "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+	                           "g-name", WPAS_DBUS_SERVICE,
+	                           "g-object-path", object_path,
+	                           "g-interface-name", WPAS_DBUS_IFACE_PEER,
+	                           NULL);
+	peer_data = g_slice_new0 (PeerData);
+	peer_data->proxy = peer_proxy;
+	g_hash_table_insert (priv->peer_proxies,
+	                     (char *) g_dbus_proxy_get_object_path (peer_proxy),
+	                     peer_data);
+	g_async_initable_init_async (G_ASYNC_INITABLE (peer_proxy),
+	                             G_PRIORITY_DEFAULT,
+	                             priv->other_cancellable,
+	                             (GAsyncReadyCallback) peer_proxy_acquired_cb,
+	                             self);
+}
+
 /*****************************************************************************/
 
 static void
@@ -450,12 +598,24 @@ static void
 parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	gboolean have_active = FALSE, have_ssid = FALSE;
+	gboolean have_active = FALSE, have_p2p = FALSE, have_ssid = FALSE;
 	gint32 max_scan_ssids = -1;
 	const char **array;
 
 	g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
 
+	if (   g_variant_lookup (capabilities, "Modes", "^a&s", &array)
+	    && array) {
+		if (g_strv_contains (array, "p2p"))
+			have_p2p = TRUE;
+		g_free (array);
+	}
+
+	if (priv->p2p_capable != have_p2p) {
+		priv->p2p_capable = have_p2p;
+		_notify (self, PROP_P2P_AVAILABLE);
+	}
+
 	if (   g_variant_lookup (capabilities, "Scan", "^a&s", &array)
 	    && array) {
 		if (g_strv_contains (array, "active"))
@@ -568,13 +728,35 @@ iface_set_pmf_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
 
-	/* This can fail if the supplicant doesn't support PMF */
 	if (error)
-		_LOGD ("failed to set Pmf=1: %s", error->message);
+		_LOGW ("failed to set Pmf=1: %s", error->message);
 
 	iface_check_ready (self);
 }
 
+gboolean
+nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->group_proxy_acquired;
+}
+
+const char*
+nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (priv->group_proxy_acquired)
+		return g_dbus_proxy_get_object_path (priv->group_proxy);
+	else
+		return NULL;
+}
+
+gboolean
+nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_owner;
+}
+
 NMSupplicantFeature
 nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
 {
@@ -593,6 +775,18 @@ nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->fils_support;
 }
 
+NMSupplicantFeature
+nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_support;
+}
+
+NMSupplicantFeature
+nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->wfd_support;
+}
+
 void
 nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                         NMSupplicantFeature ap_support)
@@ -633,6 +827,24 @@ nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
 	priv->fils_support = fils_support;
 }
 
+void
+nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
+                                         NMSupplicantFeature p2p_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->p2p_support = p2p_support;
+}
+
+void
+nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
+                                         NMSupplicantFeature wfd_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->wfd_support = wfd_support;
+}
+
 /*****************************************************************************/
 
 static void
@@ -1120,10 +1332,260 @@ props_changed_cb (GDBusProxy *proxy,
 			_LOGW ("connection disconnected (reason %d)", priv->disconnect_reason);
 	}
 
+	/* We may not have priv->dev set yet if this interface was created from a
+	 * known wpa_supplicant interface without knowing the device name.
+	 */
+	if (priv->dev == NULL && g_variant_lookup (changed_properties, "Ifname", "&s", &s)) {
+		priv->dev = g_strdup (s);
+		_notify (self, PROP_IFACE);
+	}
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+group_props_changed_cb (GDBusProxy *proxy,
+                        GVariant *changed_properties,
+                        char **invalidated_properties,
+                        gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	char *s;
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+#if 0
+	v = g_variant_lookup_value (properties, "BSSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		if (   len == ETH_ALEN
+		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
+			nm_wifi_p2p_group_set_bssid_bin (group, bytes);
+		g_variant_unref (v);
+	}
+
+	v = g_variant_lookup_value (properties, "SSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		len = MIN (32, len);
+
+		/* Stupid ieee80211 layer uses <hidden> */
+		if (   bytes && len
+		    && !(((len == 8) || (len == 9)) && !memcmp (bytes, "<hidden>", 8))
+		    && !nm_utils_is_empty_ssid (bytes, len))
+			nm_wifi_p2p_group_set_ssid (group, bytes, len);
+
+		g_variant_unref (v);
+	}
+#endif
+
+	if (g_variant_lookup (changed_properties, "Role", "s", &s)) {
+		priv->p2p_group_owner = g_strcmp0 (s, "GO") == 0;
+		_notify (self, PROP_P2P_GROUP_OWNER);
+		g_free (s);
+	}
+
+	/* NOTE: We do not seem to get any property change notifications for the Members
+	 *       property. However, we can keep track of these indirectly either by querying
+	 *       the groups that each peer is in or listening to the Join/Disconnect
+	 *       notifications.
+	 */
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+group_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+	gboolean success;
+
+	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
+	if (   !success
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (!success) {
+		_LOGD ("failed to acquire Group proxy: (%s)", error->message);
+		g_clear_object (&priv->group_proxy);
+		return;
+	}
+
+	priv->group_proxy_acquired = TRUE;
+	_notify (self, PROP_P2P_GROUP_JOINED);
+	_notify (self, PROP_P2P_GROUP_PATH);
+
+	iface_check_ready (self);
+}
+
+static void
+p2p_props_changed_cb (GDBusProxy *proxy,
+                      GVariant *changed_properties,
+                      GStrv invalidated_properties,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char **array, **iter;
+	const char *path = NULL;
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+	if (g_variant_lookup (changed_properties, "Peers", "^a&o", &array)) {
+		iter = array;
+		while (*iter)
+			peer_add_new (self, *iter++);
+		g_free (array);
+	}
+
+	if (g_variant_lookup (changed_properties, "Group", "&o", &path)) {
+		if (priv->group_proxy && g_strcmp0 (path, g_dbus_proxy_get_object_path (priv->group_proxy)) == 0) {
+			/* We already have the proxy, nothing to do. */
+		} else if (path && g_strcmp0 (path, "/") != 0) {
+			if (priv->group_proxy != NULL) {
+				_LOGW ("P2P: Unexpected update of the group object path");
+				priv->group_proxy_acquired = FALSE;
+				_notify (self, PROP_P2P_GROUP_JOINED);
+				_notify (self, PROP_P2P_GROUP_PATH);
+				g_clear_object (&priv->group_proxy);
+			}
+
+			/* Delay ready state if we have not reached it yet. */
+			if (priv->ready_count)
+				priv->ready_count++;
+
+			priv->group_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+			                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
+			                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+			                                  "g-name", WPAS_DBUS_SERVICE,
+			                                  "g-object-path", path,
+			                                  "g-interface-name", WPAS_DBUS_IFACE_GROUP,
+			                                  NULL);
+			g_signal_connect (priv->group_proxy, "g-properties-changed", G_CALLBACK (group_props_changed_cb), self);
+			g_async_initable_init_async (G_ASYNC_INITABLE (priv->group_proxy),
+			                             G_PRIORITY_DEFAULT,
+			                             priv->other_cancellable,
+			                             (GAsyncReadyCallback) group_proxy_acquired_cb,
+			                             self);
+		} else {
+			priv->group_proxy_acquired = FALSE;
+			_notify (self, PROP_P2P_GROUP_JOINED);
+			_notify (self, PROP_P2P_GROUP_PATH);
+			g_clear_object (&priv->group_proxy);
+		}
+	}
+
 	g_object_thaw_notify (G_OBJECT (self));
 }
 
 static void
+p2p_device_found (GDBusProxy *proxy,
+                  const char *path,
+                  gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	peer_add_new (self, path);
+}
+
+static void
+p2p_device_lost (GDBusProxy *proxy,
+                 const char *path,
+                 gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	PeerData *peer_data;
+
+	peer_data = g_hash_table_lookup (priv->peer_proxies, path);
+	if (!peer_data)
+		return;
+	g_hash_table_steal (priv->peer_proxies, path);
+	g_signal_emit (self, signals[PEER_REMOVED], 0, path);
+	peer_data_destroy (peer_data);
+}
+
+static void
+p2p_group_started (GDBusProxy *proxy,
+                   GVariant *params,
+                   gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterface *iface = NULL;
+	char *group_path = NULL;
+	char *iface_path = NULL;
+
+	/* There is one more parameter: the role, but we don't really care about that here. */
+	if (!g_variant_lookup (params, "group_object", "&o", &group_path)) {
+		_LOGW ("P2P: GroupStarted signal is missing the \"group_object\" parameter");
+		return;
+	}
+
+	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
+		_LOGW ("P2P: GroupStarted signal is missing the \"interface\" parameter");
+		return;
+	}
+
+	if (g_strcmp0 (iface_path, priv->object_path) == 0) {
+		_LOGW ("P2P: GroupStarted on existing interface");
+		iface = g_object_ref (self);
+	} else {
+		iface = nm_supplicant_manager_create_interface_from_path (nm_supplicant_manager_get (),
+		                                                          iface_path);
+		if (iface == NULL) {
+			_LOGW ("P2P: Group interface already exists in GroupStarted handler, aborting further processing.");
+			return;
+		}
+	}
+
+	/* Signal existence of the (new) interface. */
+	g_signal_emit (self, signals[GROUP_STARTED], 0, iface);
+	g_object_unref (iface);
+}
+
+static void
+p2p_group_formation_failure (GDBusProxy *proxy,
+                             const char *group,
+                             gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	g_signal_emit (self, signals[GROUP_FORMATION_FAILURE], 0, group);
+}
+
+static void
+p2p_group_finished (GDBusProxy *proxy,
+                    GVariant *params,
+                    gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char *iface_path = NULL;
+	/* TODO: Group finished is called on the management interface!
+	 *       This means the signal consumer will currently need to assume which
+	 *       interface is finishing or it needs to match the object paths.
+	 */
+
+	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
+		_LOGW ("P2P: GroupFinished signal is missing the \"interface\" parameter");
+		return;
+	}
+
+	_LOGD ("P2P: GroupFinished signal on interface %s for interface %s", priv->object_path, iface_path);
+
+	/* Signal group finish interface (on management interface). */
+	g_signal_emit (self, signals[GROUP_FINISHED], 0, iface_path);
+}
+
+static void
 on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
@@ -1175,22 +1637,9 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   NULL,
 	                   NULL);
 
-	/* Initialize global PMF setting to 'optional' */
-	priv->ready_count = 1;
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "Pmf",
-	                                  g_variant_new_string ("1")),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   (GAsyncReadyCallback) iface_set_pmf_cb,
-	                   self);
-
-	/* Check whether NetworkReply and AP mode are supported */
-	priv->ready_count++;
+	/* Check whether NetworkReply and AP mode are supported.
+	 * ready_count was initialized to 1 in interface_add_done().
+	 */
 	g_dbus_proxy_call (priv->iface_proxy,
 	                   "NetworkReply",
 	                   g_variant_new ("(oss)",
@@ -1203,6 +1652,22 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   (GAsyncReadyCallback) iface_check_netreply_cb,
 	                   self);
 
+	if (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) {
+		/* Initialize global PMF setting to 'optional' */
+		priv->ready_count++;
+		g_dbus_proxy_call (priv->iface_proxy,
+		                   DBUS_INTERFACE_PROPERTIES ".Set",
+		                   g_variant_new ("(ssv)",
+		                                  WPAS_DBUS_IFACE_INTERFACE,
+		                                  "Pmf",
+		                                  g_variant_new_string ("1")),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->init_cancellable,
+		                   (GAsyncReadyCallback) iface_set_pmf_cb,
+		                   self);
+	}
+
 	if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN) {
 		/* If the global supplicant capabilities property is not present, we can
 		 * fall back to checking whether the ProbeRequest method is supported.  If
@@ -1222,13 +1687,63 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 }
 
 static void
+on_p2p_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_free_error GError *error = NULL;
+
+	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			self = NM_SUPPLICANT_INTERFACE (user_data);
+			priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+			_LOGW ("failed to acquire wpa_supplicant p2p proxy: (%s)", error->message);
+
+			g_clear_object (&priv->p2p_proxy);
+
+			iface_check_ready (self);
+		}
+		return;
+	}
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceFound", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (p2p_device_found), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceLost", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (p2p_device_lost), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupStarted", G_VARIANT_TYPE ("(a{sv})"),
+	                         G_CALLBACK (p2p_group_started), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFormationFailure", G_VARIANT_TYPE ("(s)"),
+	                         G_CALLBACK (p2p_group_formation_failure), self);
+	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFinished", G_VARIANT_TYPE ("(a{sv})"),
+	                         G_CALLBACK (p2p_group_finished), self);
+	/* TODO:
+	 *  * WpsFailed
+	 *  * FindStopped
+	 *  * GONegotationFailure
+	 *  * InvitationReceived
+	 */
+
+	priv->p2p_proxy_acquired = TRUE;
+	_notify (self, PROP_P2P_AVAILABLE);
+
+	iface_check_ready (self);
+}
+
+static void
 interface_add_done (NMSupplicantInterface *self, const char *path)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	_LOGD ("interface added to supplicant");
 
+	/* Iface ready check happens in iface_check_netreply_cb */
+	priv->ready_count = 1;
+
 	priv->object_path = g_strdup (path);
+	_notify (self, PROP_OBJECT_PATH);
 	priv->iface_proxy = g_object_new (G_TYPE_DBUS_PROXY,
 	                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
 	                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
@@ -1242,6 +1757,23 @@ interface_add_done (NMSupplicantInterface *self, const char *path)
 	                             priv->init_cancellable,
 	                             (GAsyncReadyCallback) on_iface_proxy_acquired,
 	                             self);
+
+	if (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) {
+		priv->ready_count++;
+		priv->p2p_proxy = g_object_new (G_TYPE_DBUS_PROXY,
+		                                "g-bus-type", G_BUS_TYPE_SYSTEM,
+		                                "g-flags", G_DBUS_PROXY_FLAGS_NONE,
+		                                "g-name", WPAS_DBUS_SERVICE,
+		                                "g-object-path", priv->object_path,
+		                                "g-interface-name", WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+		                                NULL);
+		g_signal_connect (priv->p2p_proxy, "g-properties-changed", G_CALLBACK (p2p_props_changed_cb), self);
+		g_async_initable_init_async (G_ASYNC_INITABLE (priv->p2p_proxy),
+		                             G_PRIORITY_DEFAULT,
+		                             priv->init_cancellable,
+		                             (GAsyncReadyCallback) on_p2p_proxy_acquired,
+		                             self);
+	}
 }
 
 static void
@@ -1323,6 +1855,39 @@ interface_add_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 	}
 }
 
+static void
+interface_removed_cb (GDBusProxy *proxy,
+                      const char *path,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (g_strcmp0 (priv->object_path, path) != 0)
+		return;
+
+	_LOGD ("Received interface removed signal");
+
+	/* The interface may lose its last reference during signal handling otherwise. */
+	g_object_ref (self);
+
+	/* Invalidate the object path to prevent the manager from trying to remove
+	 * a non-existing interface. */
+	g_clear_pointer (&priv->object_path, g_free);
+	_notify (self, PROP_OBJECT_PATH);
+
+	/* No need to clean up everything now, that will happen at dispose time. */
+
+	/* Interface is down and has been removed. */
+	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	g_signal_emit (self, signals[REMOVED], 0);
+
+	g_object_unref (self);
+}
+
 #if HAVE_WEXT
 #define DEFAULT_WIFI_DRIVER "nl80211,wext"
 #else
@@ -1337,7 +1902,6 @@ on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_d
 	gs_free_error GError *error = NULL;
 	GDBusProxy *wpas_proxy;
 	GVariantBuilder props;
-	const char *driver_name = NULL;
 
 	wpas_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
 	if (!wpas_proxy) {
@@ -1354,41 +1918,53 @@ on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_d
 
 	priv->wpas_proxy = wpas_proxy;
 
+	/* Watch for interface removal. */
+	_nm_dbus_signal_connect (priv->wpas_proxy, "InterfaceRemoved", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (interface_removed_cb), self);
+
 	/* Try to add the interface to the supplicant.  If the supplicant isn't
 	 * running, this will start it via D-Bus activation and return the response
 	 * when the supplicant has started.
 	 */
 
-	switch (priv->driver) {
-	case NM_SUPPLICANT_DRIVER_WIRELESS:
-		driver_name = DEFAULT_WIFI_DRIVER;
-		break;
-	case NM_SUPPLICANT_DRIVER_WIRED:
-		driver_name = "wired";
-		break;
-	case NM_SUPPLICANT_DRIVER_MACSEC:
-		driver_name = "macsec_linux";
-		break;
-	}
+	if (priv->dev != NULL) {
+		const char *driver_name = NULL;
+
+		switch (priv->driver) {
+		case NM_SUPPLICANT_DRIVER_WIRELESS:
+			driver_name = DEFAULT_WIFI_DRIVER;
+			break;
+		case NM_SUPPLICANT_DRIVER_WIRED:
+			driver_name = "wired";
+			break;
+		case NM_SUPPLICANT_DRIVER_MACSEC:
+			driver_name = "macsec_linux";
+			break;
+		}
 
-	g_return_if_fail (driver_name);
+		g_return_if_fail (driver_name);
 
-	g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
-	g_variant_builder_add (&props, "{sv}",
-	                       "Driver",
-	                       g_variant_new_string (driver_name));
-	g_variant_builder_add (&props, "{sv}",
-	                       "Ifname",
-	                       g_variant_new_string (priv->dev));
+		g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
+		g_variant_builder_add (&props, "{sv}",
+		                       "Driver",
+		                       g_variant_new_string (driver_name));
+		g_variant_builder_add (&props, "{sv}",
+		                       "Ifname",
+		                       g_variant_new_string (priv->dev));
 
-	g_dbus_proxy_call (priv->wpas_proxy,
-	                   "CreateInterface",
-	                   g_variant_new ("(a{sv})", &props),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   (GAsyncReadyCallback) interface_add_cb,
-	                   self);
+		g_dbus_proxy_call (priv->wpas_proxy,
+		                   "CreateInterface",
+		                   g_variant_new ("(a{sv})", &props),
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   priv->init_cancellable,
+		                   (GAsyncReadyCallback) interface_add_cb,
+		                   self);
+	} else if (priv->object_path) {
+		interface_add_done (self, priv->object_path);
+	} else {
+		g_assert_not_reached ();
+	}
 }
 
 static void
@@ -1408,8 +1984,7 @@ interface_add (NMSupplicantInterface *self)
 	priv->init_cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
-	                            G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
 	                          NULL,
 	                          WPAS_DBUS_SERVICE,
 	                          WPAS_DBUS_PATH,
@@ -1897,6 +2472,141 @@ nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self)
 
 /*****************************************************************************/
 
+void
+nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
+                                        guint timeout)
+{
+	NMSupplicantInterfacePrivate *priv;
+	GVariantBuilder builder;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+	g_return_if_fail (timeout > 0 && timeout <= 600);
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&builder, "{sv}", "Timeout", g_variant_new_int32 (timeout));
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Find",
+	                   g_variant_new ("(a{sv})", &builder),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   self);
+}
+
+void
+nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "StopFind",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) scan_request_cb,
+	                   self);
+}
+
+/*****************************************************************************/
+
+void
+nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
+                                     const char * peer,
+                                     const char * wps_method,
+                                     const char * wps_pin)
+{
+	NMSupplicantInterfacePrivate *priv;
+	GVariantBuilder builder;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Don't do anything if there is no connection to the supplicant yet. */
+	if (!priv->p2p_proxy || !priv->object_path)
+		return;
+
+	/* Connect parameters */
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+	g_variant_builder_add (&builder, "{sv}", "wps_method", g_variant_new_string (wps_method));
+
+	if (wps_pin)
+		g_variant_builder_add (&builder, "{sv}", "pin", g_variant_new_string (wps_pin));
+
+	g_variant_builder_add (&builder, "{sv}", "peer", g_variant_new_object_path (peer));
+
+	g_variant_builder_add (&builder, "{sv}", "join", g_variant_new_boolean (FALSE));
+	g_variant_builder_add (&builder, "{sv}", "persistent", g_variant_new_boolean (FALSE));
+	g_variant_builder_add (&builder, "{sv}", "go_intent", g_variant_new_int32 (7));
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Connect",
+	                   g_variant_new ("(a{sv})", &builder),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   "p2p connect");
+}
+
+void
+nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Don't do anything if there is no connection to the supplicant yet. */
+	if (!priv->p2p_proxy || !priv->object_path)
+		return;
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Cancel",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   "cancel p2p connect");
+}
+
+void
+nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self)
+{
+	NMSupplicantInterfacePrivate *priv;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	/* Don't do anything if there is no connection to the supplicant. */
+	if (!priv->p2p_proxy || !priv->object_path)
+		return;
+
+	g_dbus_proxy_call (priv->p2p_proxy,
+	                   "Disconnect",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   priv->other_cancellable,
+	                   (GAsyncReadyCallback) log_result_cb,
+	                   "p2p disconnect");
+}
+
+/*****************************************************************************/
+
 static void
 get_property (GObject *object,
               guint prop_id,
@@ -1912,6 +2622,18 @@ get_property (GObject *object,
 	case PROP_CURRENT_BSS:
 		g_value_set_string (value, priv->current_bss);
 		break;
+	case PROP_P2P_GROUP_JOINED:
+		g_value_set_boolean (value, priv->p2p_capable && priv->group_proxy_acquired);
+		break;
+	case PROP_P2P_GROUP_PATH:
+		g_value_set_string (value, nm_supplicant_interface_get_p2p_group_path (NM_SUPPLICANT_INTERFACE (object)));
+		break;
+	case PROP_P2P_GROUP_OWNER:
+		g_value_set_boolean (value, priv->p2p_group_owner);
+		break;
+	case PROP_P2P_AVAILABLE:
+		g_value_set_boolean (value, priv->p2p_capable && priv->p2p_proxy_acquired);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1930,7 +2652,10 @@ set_property (GObject *object,
 	case PROP_IFACE:
 		/* construct-only */
 		priv->dev = g_value_dup_string (value);
-		g_return_if_fail (priv->dev);
+		break;
+	case PROP_OBJECT_PATH:
+		/* construct-only */
+		priv->object_path = g_value_dup_string (value);
 		break;
 	case PROP_DRIVER:
 		/* construct-only */
@@ -1952,6 +2677,14 @@ set_property (GObject *object,
 		/* construct-only */
 		priv->fils_support = g_value_get_int (value);
 		break;
+	case PROP_P2P_SUPPORT:
+		/* construct-only */
+		priv->p2p_support = g_value_get_int (value);
+		break;
+	case PROP_WFD_SUPPORT:
+		/* construct-only */
+		priv->wfd_support = g_value_get_int (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1965,25 +2698,34 @@ nm_supplicant_interface_init (NMSupplicantInterface * self)
 
 	priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT;
 	priv->bss_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, bss_data_destroy);
+	priv->peer_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, peer_data_destroy);
 }
 
 NMSupplicantInterface *
 nm_supplicant_interface_new (const char *ifname,
+                             const char *object_path,
                              NMSupplicantDriver driver,
                              NMSupplicantFeature fast_support,
                              NMSupplicantFeature ap_support,
                              NMSupplicantFeature pmf_support,
-                             NMSupplicantFeature fils_support)
+                             NMSupplicantFeature fils_support,
+                             NMSupplicantFeature p2p_support,
+                             NMSupplicantFeature wfd_support)
 {
-	g_return_val_if_fail (ifname != NULL, NULL);
+	/* One of ifname or path need to be set */
+	g_return_val_if_fail (ifname != NULL || object_path != NULL, NULL);
+	g_return_val_if_fail (ifname == NULL || object_path == NULL, NULL);
 
 	return g_object_new (NM_TYPE_SUPPLICANT_INTERFACE,
 	                     NM_SUPPLICANT_INTERFACE_IFACE, ifname,
+	                     NM_SUPPLICANT_INTERFACE_OBJECT_PATH, object_path,
 	                     NM_SUPPLICANT_INTERFACE_DRIVER, (guint) driver,
 	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, (int) fast_support,
 	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
 	                     NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, (int) pmf_support,
 	                     NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, (int) fils_support,
+	                     NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, (int) p2p_support,
+	                     NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, (int) wfd_support,
 	                     NULL);
 }
 
@@ -2013,12 +2755,21 @@ dispose (GObject *object)
 	if (priv->iface_proxy)
 		g_signal_handlers_disconnect_by_data (priv->iface_proxy, object);
 	g_clear_object (&priv->iface_proxy);
+	if (priv->p2p_proxy)
+		g_signal_handlers_disconnect_by_data (priv->p2p_proxy, object);
+	g_clear_object (&priv->p2p_proxy);
+	if (priv->group_proxy)
+		g_signal_handlers_disconnect_by_data (priv->group_proxy, object);
+	g_clear_object (&priv->group_proxy);
 
 	nm_clear_g_cancellable (&priv->init_cancellable);
 	nm_clear_g_cancellable (&priv->other_cancellable);
 
+	if (priv->wpas_proxy)
+		g_signal_handlers_disconnect_by_data (priv->wpas_proxy, object);
 	g_clear_object (&priv->wpas_proxy);
 	g_clear_pointer (&priv->bss_proxies, g_hash_table_destroy);
+	g_clear_pointer (&priv->peer_proxies, g_hash_table_destroy);
 
 	g_clear_pointer (&priv->net_path, g_free);
 	g_clear_pointer (&priv->dev, g_free);
@@ -2053,12 +2804,38 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                         G_PARAM_WRITABLE |
 	                         G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_OBJECT_PATH] =
+	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_OBJECT_PATH, "", "",
+	                         NULL,
+	                         G_PARAM_WRITABLE |
+	                         G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_GROUP_JOINED] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_GROUP_PATH] =
+	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_GROUP_OWNER] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_DRIVER] =
 	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "",
 	                       0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS,
 	                       G_PARAM_WRITABLE |
 	                       G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_AVAILABLE] =
+	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, "", "",
+	                          FALSE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_FAST_SUPPORT] =
 	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, "", "",
 	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
@@ -2091,6 +2868,22 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                      G_PARAM_WRITABLE |
 	                      G_PARAM_CONSTRUCT_ONLY |
 	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_P2P_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_WFD_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
@@ -2126,6 +2919,22 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_STRING);
 
+	signals[PEER_UPDATED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
+
+	signals[PEER_REMOVED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_STRING);
+
 	signals[SCAN_DONE] =
 	    g_signal_new (NM_SUPPLICANT_INTERFACE_SCAN_DONE,
 	                  G_OBJECT_CLASS_TYPE (object_class),
@@ -2149,4 +2958,28 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_VARIANT);
+
+	signals[GROUP_STARTED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, NM_TYPE_SUPPLICANT_INTERFACE);
+
+	signals[GROUP_FINISHED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_FINISHED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	signals[GROUP_FORMATION_FAILURE] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_VARIANT);
 }
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index 0365fdcd..0aa7732a 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -55,33 +55,48 @@ typedef enum {
 
 /* Properties */
 #define NM_SUPPLICANT_INTERFACE_IFACE            "iface"
+#define NM_SUPPLICANT_INTERFACE_OBJECT_PATH      "object-path"
 #define NM_SUPPLICANT_INTERFACE_SCANNING         "scanning"
 #define NM_SUPPLICANT_INTERFACE_CURRENT_BSS      "current-bss"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED "p2p-group-joined"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH   "p2p-group-path"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER  "p2p-group-owner"
 #define NM_SUPPLICANT_INTERFACE_DRIVER           "driver"
+#define NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE    "p2p-available"
 #define NM_SUPPLICANT_INTERFACE_FAST_SUPPORT     "fast-support"
 #define NM_SUPPLICANT_INTERFACE_AP_SUPPORT       "ap-support"
 #define NM_SUPPLICANT_INTERFACE_PMF_SUPPORT      "pmf-support"
 #define NM_SUPPLICANT_INTERFACE_FILS_SUPPORT     "fils-support"
+#define NM_SUPPLICANT_INTERFACE_P2P_SUPPORT      "p2p-support"
+#define NM_SUPPLICANT_INTERFACE_WFD_SUPPORT      "wfd-support"
 
 /* Signals */
 #define NM_SUPPLICANT_INTERFACE_STATE            "state"
 #define NM_SUPPLICANT_INTERFACE_REMOVED          "removed"
 #define NM_SUPPLICANT_INTERFACE_BSS_UPDATED      "bss-updated"
 #define NM_SUPPLICANT_INTERFACE_BSS_REMOVED      "bss-removed"
+#define NM_SUPPLICANT_INTERFACE_PEER_UPDATED     "peer-updated"
+#define NM_SUPPLICANT_INTERFACE_PEER_REMOVED     "peer-removed"
 #define NM_SUPPLICANT_INTERFACE_SCAN_DONE        "scan-done"
 #define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request"
 #define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS  "wps-credentials"
+#define NM_SUPPLICANT_INTERFACE_GROUP_STARTED           "group-started"
+#define NM_SUPPLICANT_INTERFACE_GROUP_FINISHED          "group-finished"
+#define NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE "group-formation-failure"
 
 typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass;
 
 GType nm_supplicant_interface_get_type (void);
 
 NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
+                                                     const char *object_path,
                                                      NMSupplicantDriver driver,
                                                      NMSupplicantFeature fast_support,
                                                      NMSupplicantFeature ap_support,
                                                      NMSupplicantFeature pmf_support,
-                                                     NMSupplicantFeature fils_support);
+                                                     NMSupplicantFeature fils_support,
+                                                     NMSupplicantFeature p2p_support,
+                                                     NMSupplicantFeature wfd_support);
 
 void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
                                                        gboolean available);
@@ -120,14 +135,33 @@ guint nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self);
 
 gboolean nm_supplicant_interface_get_has_credentials_request (NMSupplicantInterface *self);
 
+gboolean nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self);
+
+const char* nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self);
+
+gboolean nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self);
+
 gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
                                                     const char *field,
                                                     const char *value,
                                                     GError **error);
 
+void nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
+                                             guint timeout);
+void nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self);
+
+void nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
+                                          const char * peer,
+                                          const char * wps_method,
+                                          const char * wps_pin);
+void nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self);
+void nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self);
+
 NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self);
 
 void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                              NMSupplicantFeature apmode);
@@ -141,6 +175,12 @@ void nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
 void nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
                                                NMSupplicantFeature fils_support);
 
+void nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
+                                              NMSupplicantFeature p2p_support);
+
+void nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
+                                              NMSupplicantFeature wfd_support);
+
 void nm_supplicant_interface_enroll_wps (NMSupplicantInterface *self,
                                          const char *const type,
                                          const char *bssid,
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
index 5ab96f88..104aeee7 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -23,8 +23,6 @@
 
 #include "nm-supplicant-manager.h"
 
-#include <string.h>
-
 #include "nm-supplicant-interface.h"
 #include "nm-supplicant-types.h"
 #include "nm-core-internal.h"
@@ -41,6 +39,8 @@ typedef struct {
 	NMSupplicantFeature ap_support;
 	NMSupplicantFeature pmf_support;
 	NMSupplicantFeature fils_support;
+	NMSupplicantFeature p2p_support;
+	NMSupplicantFeature wfd_support;
 	guint             die_count_reset_id;
 	guint             die_count;
 } NMSupplicantManagerPrivate;
@@ -69,7 +69,7 @@ NM_CACHED_QUARK_FCN ("nm-supplicant-error-quark", nm_supplicant_error_quark)
 
 /*****************************************************************************/
 
-static inline gboolean
+static gboolean
 die_count_exceeded (guint32 count)
 {
 	return count > 2;
@@ -123,6 +123,72 @@ _sup_iface_last_ref (gpointer data,
 	g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self);
 }
 
+static void
+on_supplicant_wfd_ies_set (GObject *source_object,
+                           GAsyncResult *res,
+                           gpointer user_data)
+{
+	gs_unref_variant GVariant *result = NULL;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
+
+	if (!result)
+		_LOGW ("failed to set WFD IEs on wpa_supplicant: %s", error->message);
+}
+
+/**
+ * nm_supplicant_manager_set_wfd_ies:
+ * @self: the #NMSupplicantManager
+ * @wfd_ies: a #GBytes with the WFD IEs or %NULL
+ *
+ * This function sets the global WFD IEs on wpa_supplicant. Note that
+ * it would make more sense if this was per-device, but wpa_supplicant
+ * simply does not work that way.
+ * */
+void
+nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
+                                   GBytes *wfd_ies)
+{
+	NMSupplicantManagerPrivate *priv;
+	GVariantBuilder params;
+	GVariant *val;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
+
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	_LOGD ("setting WFD IEs for P2P operation");
+
+	if (wfd_ies)
+		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                 g_bytes_get_data (wfd_ies, NULL),
+		                                 g_bytes_get_size (wfd_ies),
+		                                 sizeof (guint8));
+	else
+		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                 NULL, 0, sizeof (guint8));
+
+	g_variant_builder_init (&params, G_VARIANT_TYPE ("(ssv)"));
+
+	g_variant_builder_add (&params, "s", g_dbus_proxy_get_interface_name (priv->proxy));
+	g_variant_builder_add (&params, "s", "WFDIEs");
+	g_variant_builder_add_value (&params, g_variant_new_variant (val));
+
+	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
+	                        g_dbus_proxy_get_name (priv->proxy),
+	                        g_dbus_proxy_get_object_path (priv->proxy),
+	                        "org.freedesktop.DBus.Properties",
+	                        "Set",
+	                        g_variant_builder_end (&params),
+	                        G_VARIANT_TYPE_UNIT,
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        1000,
+	                        NULL,
+	                        on_supplicant_wfd_ies_set,
+	                        NULL);
+}
+
 /**
  * nm_supplicant_manager_create_interface:
  * @self: the #NMSupplicantManager
@@ -159,11 +225,71 @@ nm_supplicant_manager_create_interface (NMSupplicantManager *self,
 	}
 
 	iface = nm_supplicant_interface_new (ifname,
+	                                     NULL,
 	                                     driver,
 	                                     priv->fast_support,
 	                                     priv->ap_support,
 	                                     priv->pmf_support,
-	                                     priv->fils_support);
+	                                     priv->fils_support,
+	                                     priv->p2p_support,
+	                                     priv->wfd_support);
+
+	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
+	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
+
+	/* If we're making the supplicant take a time out for a bit, don't
+	 * let the supplicant interface start immediately, just let it hang
+	 * around in INIT state until we're ready to talk to the supplicant
+	 * again.
+	 */
+	if (is_available (self))
+		nm_supplicant_interface_set_supplicant_available (iface, TRUE);
+
+	return iface;
+}
+
+/**
+ * nm_supplicant_manager_create_interface_from_path:
+ * @self: the #NMSupplicantManager
+ * @object_path: the DBus object path for which to obtain the supplicant interface
+ *
+ * Note: the manager owns a reference to the instance and the only way to
+ *   get the manager to release it, is by dropping all other references
+ *   to the supplicant-interface (or destroying the manager).
+ *
+ * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL.
+ *   Must be unrefed at the end.
+ * */
+NMSupplicantInterface *
+nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
+                                                  const char *object_path)
+{
+	NMSupplicantManagerPrivate *priv;
+	NMSupplicantInterface *iface;
+	GSList *ifaces;
+
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
+	g_return_val_if_fail (object_path != NULL, NULL);
+
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	_LOGD ("creating new supplicant interface for dbus path %s", object_path);
+
+	/* assert against not requesting duplicate interfaces. */
+	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
+		if (g_strcmp0 (nm_supplicant_interface_get_object_path (ifaces->data), object_path) == 0)
+			g_return_val_if_reached (NULL);
+	}
+
+	iface = nm_supplicant_interface_new (NULL,
+	                                     object_path,
+	                                     NM_SUPPLICANT_DRIVER_WIRELESS,
+	                                     priv->fast_support,
+	                                     priv->ap_support,
+	                                     priv->pmf_support,
+	                                     priv->fils_support,
+	                                     priv->p2p_support,
+	                                     priv->wfd_support);
 
 	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
 	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
@@ -199,6 +325,8 @@ update_capabilities (NMSupplicantManager *self)
 	priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 	priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 	priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+	/* P2P support is newer than the capabilities property */
+	priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
 
 	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
 	if (value) {
@@ -207,6 +335,7 @@ update_capabilities (NMSupplicantManager *self)
 			priv->ap_support = NM_SUPPLICANT_FEATURE_NO;
 			priv->pmf_support = NM_SUPPLICANT_FEATURE_NO;
 			priv->fils_support = NM_SUPPLICANT_FEATURE_NO;
+			priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
 			if (array) {
 				if (g_strv_contains (array, "ap"))
 					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
@@ -214,17 +343,20 @@ update_capabilities (NMSupplicantManager *self)
 					priv->pmf_support = NM_SUPPLICANT_FEATURE_YES;
 				if (g_strv_contains (array, "fils"))
 					priv->fils_support = NM_SUPPLICANT_FEATURE_YES;
+				if (g_strv_contains (array, "p2p"))
+					priv->p2p_support = NM_SUPPLICANT_FEATURE_YES;
 				g_free (array);
 			}
 		}
 		g_variant_unref (value);
 	}
 
-	/* Tell all interfaces about results of the AP/PMF/FILS check */
+	/* Tell all interfaces about results of the AP/PMF/FILS/P2P check */
 	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
 		nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support);
 		nm_supplicant_interface_set_pmf_support (ifaces->data, priv->pmf_support);
 		nm_supplicant_interface_set_fils_support (ifaces->data, priv->fils_support);
+		nm_supplicant_interface_set_p2p_support (ifaces->data, priv->p2p_support);
 	}
 
 	_LOGD ("AP mode is %ssupported",
@@ -236,6 +368,9 @@ update_capabilities (NMSupplicantManager *self)
 	_LOGD ("FILS is %ssupported",
 	       (priv->fils_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
 	           (priv->fils_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	_LOGD ("P2P is %ssupported",
+	       (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->p2p_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
 
 	/* EAP-FAST */
 	priv->fast_support = NM_SUPPLICANT_FEATURE_NO;
@@ -264,6 +399,20 @@ update_capabilities (NMSupplicantManager *self)
 	_LOGD ("EAP-FAST is %ssupported",
 	       (priv->fast_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
 	           (priv->fast_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+
+	priv->wfd_support = NM_SUPPLICANT_FEATURE_NO;
+	value = g_dbus_proxy_get_cached_property (priv->proxy, "WFDIEs");
+	if (value) {
+		priv->wfd_support = NM_SUPPLICANT_FEATURE_YES;
+		g_variant_unref (value);
+	}
+
+	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
+		nm_supplicant_interface_set_wfd_support (ifaces->data, priv->fast_support);
+
+	_LOGD ("WFD is %ssupported",
+	       (priv->wfd_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->wfd_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
 }
 
 static void
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h
index 8928cf20..058745fb 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -38,8 +38,13 @@ GType nm_supplicant_manager_get_type (void);
 
 NMSupplicantManager *nm_supplicant_manager_get (void);
 
+void nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
+                                        GBytes *wfd_ies);
+
 NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr,
                                                                const char *ifname,
                                                                NMSupplicantDriver driver);
+NMSupplicantInterface *nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
+                                                                         const char *object_path);
 
 #endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index 1e25675d..f10bbb04 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -24,8 +24,6 @@
 
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
-#include <errno.h>
 
 struct Opt {
 	const char *     key;
@@ -72,7 +70,7 @@ const char * proto_allowed[] =    { "WPA", "RSN", NULL };
 const char * key_mgmt_allowed[] = { "WPA-PSK", "WPA-PSK-SHA256",
                                     "WPA-EAP", "WPA-EAP-SHA256",
                                     "FILS-SHA256", "FILS-SHA384",
-                                    "IEEE8021X", "WPA-NONE",
+                                    "IEEE8021X", "WPA-NONE", "SAE",
                                     "NONE", NULL };
 const char * auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
 const char * eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
@@ -159,23 +157,13 @@ validate_type_int (const struct Opt * opt,
                    const char * value,
                    const guint32 len)
 {
-	long int intval;
+	gint64 v;
 
 	g_return_val_if_fail (opt != NULL, FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
 
-	errno = 0;
-	intval = strtol (value, NULL, 10);
-	if (errno != 0)
-		return FALSE;
-
-	/* strtol returns a long, but we are dealing with ints */
-	if (intval > INT_MAX || intval < INT_MIN)
-		return FALSE;
-	if (intval > opt->int_high || intval < opt->int_low)
-		return FALSE;
-
-	return TRUE;
+	v = _nm_utils_ascii_str_to_int64 (value, 10, opt->int_low, opt->int_high, G_MININT64);
+	return v != G_MININT64 || errno == 0;
 }
 
 static gboolean
diff --git a/src/supplicant/tests/meson.build b/src/supplicant/tests/meson.build
index 5e4cbdbe..fbccb313 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/supplicant/tests/meson.build
@@ -9,5 +9,6 @@ exe = executable(
 test(
   'supplicant/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index d7ec1fe2..2c7a71a3 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -23,7 +23,6 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <unistd.h>
-#include <string.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -389,6 +388,76 @@ test_wifi_wpa_psk (const char *detail,
 }
 
 static void
+test_wifi_sae_psk (const char *psk)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	NMSettingWirelessSecurity *s_wsec;
+	gboolean success;
+	GError *error = NULL;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+	const char *bssid_str = "11:22:33:44:55:66";
+	int short_psk = strlen (psk) < 8;
+
+	connection = new_basic_connection ("Test Wifi SAE", ssid, bssid_str);
+
+	/* Wifi Security setting */
+	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
+	g_object_set (s_wsec,
+	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
+	              NM_SETTING_WIRELESS_SECURITY_PSK, psk,
+	              NULL);
+	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
+	nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp");
+	nm_setting_wireless_security_add_group (s_wsec, "tkip");
+	nm_setting_wireless_security_add_group (s_wsec, "ccmp");
+
+	success = nm_connection_verify (connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	NMTST_EXPECT_NM_INFO ("Config: added 'ssid' value 'Test SSID'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'scan_ssid' value '1'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
+	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'SAE'");
+	if (short_psk)
+		NMTST_EXPECT_NM_INFO ("Config: added 'sae_password' value *");
+	else
+		NMTST_EXPECT_NM_INFO ("Config: added 'psk' value *");
+	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'RSN'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
+	NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '0'");
+	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+
+	g_test_assert_expected_messages ();
+	g_assert (config_dict);
+
+	validate_opt ("wifi-sae", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-sae", config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt ("wifi-sae", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-sae", config_dict, "key_mgmt", TYPE_KEYWORD, "SAE");
+	validate_opt ("wifi-sae", config_dict, "proto", TYPE_KEYWORD, "RSN");
+	validate_opt ("wifi-sae", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-sae", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	if (short_psk)
+		validate_opt ("wifi-sae", config_dict, "sae_password", TYPE_KEYWORD, psk);
+	else
+		validate_opt ("wifi-sae", config_dict, "psk", TYPE_KEYWORD, psk);
+}
+
+static void
+test_wifi_sae (void)
+{
+	test_wifi_sae_psk ("Moo");
+	test_wifi_sae_psk ("Hello World!");
+}
+
+static void
 test_wifi_wpa_psk_types (void)
 {
 	const char *key1 = "d4721e911461d3cdef9793858e977fcda091779243abb7316c2f11605a160893";
@@ -580,6 +649,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/supplicant-config/wifi-eap/locked-bssid", test_wifi_eap_locked_bssid);
 	g_test_add_func ("/supplicant-config/wifi-eap/unlocked-bssid", test_wifi_eap_unlocked_bssid);
 	g_test_add_func ("/supplicant-config/wifi-eap/fils-disabled", test_wifi_eap_fils_disabled);
+	g_test_add_func ("/supplicant-config/wifi-sae", test_wifi_sae);
 
 	return g_test_run ();
 }