about 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.c118
-rw-r--r--src/supplicant/nm-supplicant-interface.c962
-rw-r--r--src/supplicant/nm-supplicant-interface.h42
-rw-r--r--src/supplicant/nm-supplicant-manager.c89
-rw-r--r--src/supplicant/nm-supplicant-manager.h2
-rw-r--r--src/supplicant/tests/meson.build2
-rw-r--r--src/supplicant/tests/test-supplicant-config.c4
7 files changed, 1114 insertions, 105 deletions
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 043b5550..4acb634e 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -26,6 +26,8 @@
 #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 +373,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 +396,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 +571,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)
 	 */
@@ -648,31 +656,28 @@ add_string_val (NMSupplicantConfig *self,
 
 static void
 wep128_passphrase_hash (const char *input,
-                        size_t input_len,
-                        guint8 *out_digest,
-                        size_t *out_digest_len)
+                        gsize input_len,
+                        guint8 *digest /* 13 bytes */)
 {
-	GChecksum *sum;
+	nm_auto_free_checksum GChecksum *sum = NULL;
+	guint8 md5[NM_UTILS_CHECKSUM_LENGTH_MD5];
 	guint8 data[64];
 	int i;
 
-	g_return_if_fail (out_digest != NULL);
-	g_return_if_fail (out_digest_len != NULL);
-	g_return_if_fail (*out_digest_len >= 16);
+	nm_assert (input);
+	nm_assert (input_len);
+	nm_assert (digest);
 
 	/* Get at least 64 bytes by repeating the passphrase into the buffer */
 	for (i = 0; i < sizeof (data); i++)
 		data[i] = input[i % input_len];
 
 	sum = g_checksum_new (G_CHECKSUM_MD5);
-	g_assert (sum);
 	g_checksum_update (sum, data, sizeof (data));
-	g_checksum_get_digest (sum, out_digest, out_digest_len);
-	g_checksum_free (sum);
+	nm_utils_checksum_get_digest (sum, md5);
 
-	g_assert (*out_digest_len == 16);
 	/* WEP104 keys are 13 bytes in length (26 hex characters) */
-	*out_digest_len = 13;
+	memcpy (digest, md5, 13);
 }
 
 static gboolean
@@ -682,9 +687,10 @@ add_wep_key (NMSupplicantConfig *self,
              NMWepKeyType wep_type,
              GError **error)
 {
-	size_t key_len = key ? strlen (key) : 0;
+	gsize key_len;
 
-	if (!key || !key_len)
+	if (   !key
+	    || (key_len = strlen (key)) == 0)
 		return TRUE;
 
 	if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) {
@@ -697,10 +703,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);
@@ -708,8 +720,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;
@@ -723,11 +735,10 @@ add_wep_key (NMSupplicantConfig *self,
 			return FALSE;
 		}
 	} else if (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE) {
-		guint8 digest[16];
-		size_t digest_len = sizeof (digest);
+		guint8 digest[13];
 
-		wep128_passphrase_hash (key, key_len, digest, &digest_len);
-		if (!nm_supplicant_config_add_option (self, name, (const char *) digest, digest_len, "<hidden>", error))
+		wep128_passphrase_hash (key, key_len, digest);
+		if (!nm_supplicant_config_add_option (self, name, (const char *) digest, sizeof (digest), "<hidden>", error))
 			return FALSE;
 	}
 
@@ -747,6 +758,7 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 	const char *key_mgmt, *key_mgmt_conf, *auth_alg;
 	const char *psk;
+	gboolean set_pmf;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE);
 	g_return_val_if_fail (setting != NULL, FALSE);
@@ -796,20 +808,22 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		size_t psk_len = strlen (psk);
 
 		if (psk_len == 64) {
-			gs_unref_bytes GBytes *bytes = NULL;
+			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;
@@ -834,13 +848,14 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
 
 	/* Check if we actually support PMF */
+	set_pmf = TRUE;
 	if (!priv->support_pmf) {
 		if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
 			g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			                     "Supplicant does not support PMF");
 			return FALSE;
-		} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL)
-			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+		}
+		set_pmf = FALSE;
 	}
 
 	/* Only WPA-specific things when using WPA */
@@ -854,13 +869,14 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, group, groups, "group", ' ', TRUE, NULL, error))
 			return FALSE;
 
-		if (   !nm_streq (key_mgmt, "wpa-none")
+		if (   set_pmf
+		    && !nm_streq (key_mgmt, "wpa-none")
 		    && NM_IN_SET (pmf,
-		                  NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL,
+		                  NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
 		                  NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)) {
 			if (!nm_supplicant_config_add_option (self,
 			                                      "ieee80211w",
-			                                      pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL ? "1" : "2",
+			                                      pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE ? "0" : "2",
 			                                      -1,
 			                                      NULL,
 			                                      error))
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 5237acb2..c30adc58 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -22,6 +22,7 @@
 #include "nm-default.h"
 
 #include "nm-supplicant-interface.h"
+#include "nm-supplicant-manager.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -31,12 +32,15 @@
 #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 +49,11 @@ typedef struct {
 	gulong change_id;
 } BssData;
 
+typedef struct {
+	GDBusProxy *proxy;
+	gulong change_id;
+} PeerData;
+
 struct _AddNetworkData;
 
 typedef struct {
@@ -74,26 +83,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 +125,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 +143,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 +160,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 +344,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 +599,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"))
@@ -555,6 +716,49 @@ iface_check_netreply_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	iface_check_ready (self);
 }
 
+static void
+iface_set_pmf_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	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);
+
+	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)
 {
@@ -573,6 +777,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)
@@ -613,6 +829,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
@@ -1100,10 +1334,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 udpate 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 existance 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;
@@ -1155,8 +1639,21 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   NULL,
 	                   NULL);
 
+	/* 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);
+
 	/* Check whether NetworkReply and AP mode are supported */
-	priv->ready_count = 1;
 	g_dbus_proxy_call (priv->iface_proxy,
 	                   "NetworkReply",
 	                   g_variant_new ("(oss)",
@@ -1188,13 +1685,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,
@@ -1208,6 +1755,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
@@ -1289,6 +1853,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
@@ -1303,7 +1900,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) {
@@ -1320,41 +1916,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
@@ -1374,8 +1982,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,
@@ -1863,6 +2470,142 @@ 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);
+
+	/* Find parameters */
+	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,
@@ -1878,6 +2621,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;
@@ -1896,7 +2651,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 */
@@ -1918,6 +2676,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;
@@ -1931,25 +2697,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);
 }
 
@@ -1979,12 +2754,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);
@@ -2019,12 +2803,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,
@@ -2057,6 +2867,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);
 
@@ -2092,6 +2918,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),
@@ -2115,4 +2957,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..64a057f3 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -41,6 +41,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;
@@ -159,11 +161,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 +261,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 +271,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 +279,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 +304,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 +335,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..7225a36b 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -41,5 +41,7 @@ NMSupplicantManager *nm_supplicant_manager_get (void);
 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/tests/meson.build b/src/supplicant/tests/meson.build
index 5e4cbdbe..7cc9d6af 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/supplicant/tests/meson.build
@@ -9,5 +9,5 @@ exe = executable(
 test(
   'supplicant/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
 )
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index 36831e67..d7ec1fe2 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -359,8 +359,8 @@ test_wifi_wpa_psk (const char *detail,
 	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
 	switch (pmf) {
-	case NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL:
-		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '1'");
+	case NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE:
+		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '0'");
 		break;
 	case NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED:
 		NMTST_EXPECT_NM_INFO ("Config: added 'ieee80211w' value '2'");