about summary refs log tree commit diff
path: root/src/devices/wifi
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/wifi')
-rw-r--r--src/devices/wifi/meson.build14
-rw-r--r--src/devices/wifi/nm-device-iwd.c1139
-rw-r--r--src/devices/wifi/nm-device-iwd.h2
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c11
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.c1316
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.h50
-rw-r--r--src/devices/wifi/nm-device-wifi.c346
-rw-r--r--src/devices/wifi/nm-device-wifi.h1
-rw-r--r--src/devices/wifi/nm-iwd-manager.c134
-rw-r--r--src/devices/wifi/nm-wifi-ap.c6
-rw-r--r--src/devices/wifi/nm-wifi-factory.c69
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.c796
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.h114
-rw-r--r--src/devices/wifi/nm-wifi-utils.c7
-rw-r--r--src/devices/wifi/tests/meson.build5
-rw-r--r--src/devices/wifi/tests/test-general.c3
16 files changed, 3478 insertions, 535 deletions
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
index 2745040b..4dfbe4c8 100644
--- a/src/devices/wifi/meson.build
+++ b/src/devices/wifi/meson.build
@@ -1,13 +1,15 @@
 common_sources = files(
   'nm-wifi-ap.c',
-  'nm-wifi-utils.c'
+  'nm-wifi-p2p-peer.c',
+  'nm-wifi-utils.c',
 )
 
 sources = common_sources + files(
-  'nm-wifi-factory.c',
-  'nm-wifi-common.c',
+  'nm-device-olpc-mesh.c',
+  'nm-device-wifi-p2p.c',
   'nm-device-wifi.c',
-  'nm-device-olpc-mesh.c'
+  'nm-wifi-common.c',
+  'nm-wifi-factory.c',
 )
 
 if enable_iwd
@@ -18,7 +20,7 @@ if enable_iwd
 endif
 
 deps = [
-  nm_dep
+  nm_dep,
 ]
 
 libnm_device_plugin_wifi = shared_module(
@@ -28,7 +30,7 @@ libnm_device_plugin_wifi = shared_module(
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
-  install_dir: nm_plugindir
+  install_dir: nm_plugindir,
 )
 
 core_plugins += libnm_device_plugin_wifi
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 1d1be742..dcc161d2 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -22,8 +22,6 @@
 
 #include "nm-device-iwd.h"
 
-#include <string.h>
-
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
@@ -41,6 +39,7 @@
 #include "nm-config.h"
 #include "nm-iwd-manager.h"
 #include "nm-dbus-manager.h"
+#include "nm-dbus-compat.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceIwd);
@@ -69,6 +68,8 @@ typedef struct {
 	GDBusObject *   dbus_obj;
 	GDBusProxy *    dbus_device_proxy;
 	GDBusProxy *    dbus_station_proxy;
+	GDBusProxy *    dbus_ap_proxy;
+	GDBusProxy *    dbus_adhoc_proxy;
 	CList           aps_lst_head;
 	NMWifiAP *      current_ap;
 	GCancellable *  cancellable;
@@ -80,6 +81,7 @@ typedef struct {
 	bool            can_connect:1;
 	bool            scanning:1;
 	bool            scan_requested:1;
+	bool            act_mode_switch:1;
 	gint64          last_scan;
 } NMDeviceIwdPrivate;
 
@@ -104,7 +106,7 @@ G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static void schedule_periodic_scan (NMDeviceIwd *self,
-                                    NMDeviceState current_state);
+                                    gboolean initial_scan);
 
 /*****************************************************************************/
 
@@ -229,7 +231,11 @@ vardict_from_network_type (const char *type)
 }
 
 static void
-insert_ap_from_network (GHashTable *aps, const char *path, int16_t signal, uint32_t ap_id)
+insert_ap_from_network (NMDeviceIwd *self,
+                        GHashTable *aps,
+                        const char *path,
+                        int16_t signal,
+                        uint32_t ap_id)
 {
 	gs_unref_object GDBusProxy *network_proxy = NULL;
 	gs_unref_variant GVariant *name_value = NULL, *type_value = NULL;
@@ -240,6 +246,11 @@ insert_ap_from_network (GHashTable *aps, const char *path, int16_t signal, uint3
 	uint8_t bssid[6];
 	NMWifiAP *ap;
 
+	if (g_hash_table_lookup (aps, path)) {
+		_LOGD (LOGD_WIFI, "Duplicate network at %s", path);
+		return;
+	}
+
 	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
 	                                                   path,
 	                                                   NM_IWD_NETWORK_INTERFACE);
@@ -304,7 +315,7 @@ static void
 get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDeviceIwdPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *variant = NULL;
 	GVariantIter *networks;
@@ -314,33 +325,42 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	gboolean changed = FALSE;
 	GHashTableIter ap_iter;
 	gs_unref_hashtable GHashTable *new_aps = NULL;
-	/* Depending on whether we're using the Station interface or the Device
-	 * interface for compatibility with IWD <= 0.7, the return signature of
-	 * GetOrderedNetworks will be different.
-	 */
-	gboolean compat = priv->dbus_station_proxy == priv->dbus_device_proxy;
-	const char *return_sig = compat ? "(a(osns))" : "(a(on))";
+	gboolean compat;
+	const char *return_sig;
 	static uint32_t ap_id = 0;
 
-	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                      G_VARIANT_TYPE (return_sig),
-	                                      &error);
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
 	if (!variant) {
 		_LOGE (LOGD_WIFI, "Station.GetOrderedNetworks failed: %s",
 		       error->message);
 		return;
 	}
 
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	/* Depending on whether we're using the Station interface or the Device
+	 * interface for compatibility with IWD <= 0.7, the return signature of
+	 * GetOrderedNetworks will be different.
+	 */
+	compat = priv->dbus_station_proxy == priv->dbus_device_proxy;
+	return_sig = compat ? "(a(osns))" : "(a(on))";
+
+	if (!g_variant_is_of_type (variant, G_VARIANT_TYPE (return_sig))) {
+		_LOGE (LOGD_WIFI, "Station.GetOrderedNetworks returned type %s instead of %s",
+		       g_variant_get_type_string (variant), return_sig);
+		return;
+	}
+
 	new_aps = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
 
 	g_variant_get (variant, return_sig, &networks);
 
 	if (compat) {
 		while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type))
-			insert_ap_from_network (new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
 	} else {
 		while (g_variant_iter_next (networks, "(&on)", &path, &signal))
-			insert_ap_from_network (new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
 	}
 
 	g_variant_iter_free (networks);
@@ -393,7 +413,7 @@ update_aps (NMDeviceIwd *self)
 		priv->cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_call (priv->dbus_station_proxy, "GetOrderedNetworks",
-	                   g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE,
+	                   NULL, G_DBUS_CALL_FLAGS_NONE,
 	                   2000, priv->cancellable,
 	                   get_ordered_networks_cb, self);
 }
@@ -403,8 +423,8 @@ send_disconnect (NMDeviceIwd *self)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 }
 
 static void
@@ -431,55 +451,104 @@ cleanup_association_attempt (NMDeviceIwd *self, gboolean disconnect)
 }
 
 static void
+reset_mode (NMDeviceIwd *self,
+            GCancellable *cancellable,
+            GAsyncReadyCallback callback,
+            gpointer user_data)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	g_dbus_proxy_call (priv->dbus_device_proxy,
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Mode",
+	                                  g_variant_new_string ("station")),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   cancellable,
+	                   callback,
+	                   user_data);
+}
+
+static void
 deactivate (NMDevice *device)
 {
-	cleanup_association_attempt (NM_DEVICE_IWD (device), TRUE);
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (!priv->dbus_obj)
+		return;
+
+	cleanup_association_attempt (self, TRUE);
+	priv->act_mode_switch = FALSE;
+
+	if (!priv->dbus_station_proxy)
+		reset_mode (self, NULL, NULL, NULL);
 }
 
-static gboolean
-deactivate_async_finish (NMDevice *device, GAsyncResult *res, GError **error)
+static void
+disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
-	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
+	gs_unref_object NMDeviceIwd *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
 	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
 
-	variant = g_dbus_proxy_call_finish (priv->dbus_station_proxy, res, error);
-	return variant != NULL;
-}
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-typedef struct {
-	NMDeviceIwd *self;
-	GAsyncReadyCallback callback;
-	gpointer user_data;
-} DeactivateContext;
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	callback (NM_DEVICE (self), error, callback_user_data);
+}
 
 static void
-disconnect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+disconnect_cb_on_idle (gpointer user_data,
+                       GCancellable *cancellable)
 {
-	DeactivateContext *ctx = user_data;
+	gs_unref_object NMDeviceIwd *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *cancelled_error = NULL;
 
-	ctx->callback (G_OBJECT (ctx->self), res, ctx->user_data);
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-	g_object_unref (ctx->self);
-	g_slice_free (DeactivateContext, ctx);
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	callback (NM_DEVICE (self), cancelled_error, callback_user_data);
 }
 
 static void
 deactivate_async (NMDevice *device,
                   GCancellable *cancellable,
-                  GAsyncReadyCallback callback,
-                  gpointer user_data)
+                  NMDeviceDeactivateCallback callback,
+                  gpointer callback_user_data)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-	DeactivateContext *ctx;
+	gpointer user_data;
 
-	ctx = g_slice_new0 (DeactivateContext);
-	ctx->self = g_object_ref (self);
-	ctx->callback = callback;
-	ctx->user_data = user_data;
+	nm_assert (G_IS_CANCELLABLE (cancellable));
+	nm_assert (callback);
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1, cancellable, disconnect_cb, ctx);
+	user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data);
+
+	if (!priv->dbus_obj) {
+		nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable);
+		return;
+	}
+
+	cleanup_association_attempt (self, FALSE);
+	priv->act_mode_switch = FALSE;
+
+	if (priv->dbus_station_proxy) {
+		g_dbus_proxy_call (priv->dbus_station_proxy,
+		                   "Disconnect",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   cancellable,
+		                   disconnect_cb,
+		                   user_data);
+	} else
+		reset_mode (self, cancellable, disconnect_cb, user_data);
 }
 
 static gboolean
@@ -510,13 +579,37 @@ is_connection_known_network (NMConnection *connection)
 }
 
 static gboolean
+is_ap_known_network (NMWifiAP *ap)
+{
+	GDBusProxy *network_proxy;
+	gs_unref_variant GVariant *known_network = NULL;
+
+	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+	                                                   nm_wifi_ap_get_supplicant_path (ap),
+	                                                   NM_IWD_NETWORK_INTERFACE);
+	if (!network_proxy)
+		return FALSE;
+
+	known_network = g_dbus_proxy_get_cached_property (network_proxy, "KnownNetwork");
+	g_object_unref (network_proxy);
+
+	return    known_network
+	       && g_variant_is_of_type (known_network, G_VARIANT_TYPE_OBJECT_PATH);
+}
+
+static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
+	NMDeviceIwd *self = NM_DEVICE_IWD (device);
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wireless;
 	const char *mac;
 	const char * const *mac_blacklist;
 	int i;
 	const char *perm_hw_addr;
+	const char *mode;
+	NMIwdNetworkSecurity security;
+	gboolean mapped;
 
 	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
@@ -549,23 +642,65 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		return FALSE;
 	}
 
-	if (!NM_IN_STRSET (nm_setting_wireless_get_mode (s_wireless),
-	                   NULL,
-	                   NM_SETTING_WIRELESS_MODE_INFRA)) {
+	/* Hidden SSIDs not supported in any mode (client or AP) */
+	if (nm_setting_wireless_get_hidden (s_wireless)) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "IWD only support infrastructure type profiles");
+		                            "hidden networks not supported by the IWD backend");
 		return FALSE;
 	}
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
+	security = nm_wifi_connection_get_iwd_security (connection, &mapped);
+	if (!mapped) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "connection authentication type not supported by IWD backend");
+		return FALSE;
+	}
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		/* 8021x networks can only be used if they've been provisioned on the IWD side and
+		 * thus are Known Networks.
+		 */
+		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
+			if (!is_connection_known_network (connection)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "802.1x connections must have IWD provisioning files");
+				return FALSE;
+			}
+		} else if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "IWD backend only supports Open, PSK and 802.1x network "
+			                            "authentication in Infrastructure mode");
+			return FALSE;
+		}
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not support Access Point mode");
+			return FALSE;
+		}
+
+		if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_PSK)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "IWD backend only supports PSK authentication in AP mode");
+			return FALSE;
+		}
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not support Ad-Hoc mode");
+			return FALSE;
+		}
+
+		if (!NM_IN_SET (security, NM_IWD_NETWORK_SECURITY_NONE, NM_IWD_NETWORK_SECURITY_PSK)) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-			                            "802.1x profile is not a known network");
+			                            "IWD backend only supports Open and PSK authentication in Ad-Hoc mode");
 			return FALSE;
 		}
+	} else {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "%s type profiles not supported by IWD backend");
+		return FALSE;
 	}
 
 	return TRUE;
@@ -582,42 +717,15 @@ check_connection_available (NMDevice *device,
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *mode;
+	NMWifiAP *ap = NULL;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
-	/* Only Infrastrusture mode at this time */
-	mode = nm_setting_wireless_get_mode (s_wifi);
-	if (!NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "iwd only supports infrastructure mode connections");
-		return FALSE;
-	}
-
-	/* Hidden SSIDs not supported yet */
-	if (nm_setting_wireless_get_hidden (s_wifi)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "hidden networks not supported by iwd");
-		return FALSE;
-	}
-
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
-			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-			                            "network is not known to iwd");
-			return FALSE;
-		}
-	}
-
 	/* a connection that is available for a certain @specific_object, MUST
 	 * also be available in general (without @specific_object). */
 
 	if (specific_object) {
-		NMWifiAP *ap;
-
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
 		if (!ap) {
 			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
@@ -629,18 +737,36 @@ check_connection_available (NMDevice *device,
 			                            "requested access point is not compatible with profile");
 			return FALSE;
 		}
-		return TRUE;
 	}
 
+	/* AP and Ad-Hoc connections can be activated independent of the scan list */
+	mode = nm_setting_wireless_get_mode (s_wifi);
+	if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC))
+		return TRUE;
+
 	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
 		return TRUE;
 
-	if (!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection)) {
+	if (!ap)
+		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+
+	if (!ap) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 		                            "no compatible access point found");
 		return FALSE;
 	}
 
+	/* 8021x networks can only be used if they've been provisioned on the IWD side and
+	 * thus are Known Networks.
+	 */
+	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_ap_known_network (ap)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "802.1x network is not an IWD Known Network (missing provisioning file?)");
+			return FALSE;
+		}
+	}
+
 	return TRUE;
 }
 
@@ -666,15 +792,11 @@ complete_connection (NMDevice *device,
 
 	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
 
-	if (mode && !nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
-		g_set_error_literal (error,
-		                     NM_DEVICE_ERROR,
-		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-		                     "Only Infrastructure mode is supported.");
-		return FALSE;
-	}
-
-	if (!specific_object) {
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+			return FALSE;
+		ap = NULL;
+	} else if (!specific_object) {
 		/* If not given a specific object, we need at minimum an SSID */
 		if (!s_wifi) {
 			g_set_error_literal (error,
@@ -696,11 +818,16 @@ complete_connection (NMDevice *device,
 		/* Find a compatible AP in the scan list */
 		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 		if (!ap) {
-			g_set_error_literal (error,
-			                     NM_DEVICE_ERROR,
-			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-			                     "No compatible AP in the scan list and hidden SSIDs not supported.");
-			return FALSE;
+			if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+				g_set_error_literal (error,
+				                     NM_DEVICE_ERROR,
+				                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+				                     "No compatible AP in the scan list and hidden SSIDs not supported.");
+				return FALSE;
+			}
+
+			if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+				return FALSE;
 		}
 	} else {
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
@@ -720,7 +847,10 @@ complete_connection (NMDevice *device,
 		nm_connection_add_setting (connection, NM_SETTING (s_wifi));
 	}
 
-	ssid = nm_wifi_ap_get_ssid (ap);
+	ssid = nm_setting_wireless_get_ssid (s_wifi);
+	if (!ssid && ap)
+		ssid = nm_wifi_ap_get_ssid (ap);
+
 	if (!ssid) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
@@ -729,11 +859,13 @@ complete_connection (NMDevice *device,
 		return FALSE;
 	}
 
-	if (!nm_wifi_ap_complete_connection (ap,
-	                                     connection,
-	                                     nm_wifi_utils_is_manf_default_ssid (ssid),
-	                                     error))
-		return FALSE;
+	if (ap) {
+		if (!nm_wifi_ap_complete_connection (ap,
+		                                     connection,
+		                                     nm_wifi_utils_is_manf_default_ssid (ssid),
+		                                     error))
+			return FALSE;
+	}
 
 	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
 	nm_utils_complete_generic (nm_device_get_platform (device),
@@ -745,19 +877,6 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           TRUE);
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection)) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-			                     "This 8021x network has not been provisioned on this machine");
-			return FALSE;
-		}
-	}
-
 	perm_hw_addr = nm_device_get_permanent_hw_address (device);
 	if (perm_hw_addr) {
 		setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
@@ -820,8 +939,21 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-
-	return priv->enabled && priv->dbus_station_proxy;
+	NMDeviceState state = nm_device_get_state (device);
+
+	/* Available if either the device is UP and in station mode
+	 * or in AP/Ad-Hoc modes while activating or activated.  Device
+	 * may be temporarily DOWN while activating or deactivating and
+	 * we don't want it to be marked unavailable because of this.
+	 *
+	 * For reference:
+	 * We call nm_device_queue_recheck_available whenever
+	 * priv->enabled changes or priv->dbus_station_proxy changes.
+	 */
+	return    priv->dbus_obj
+	       && priv->enabled
+	       && (   priv->dbus_station_proxy
+	           || (state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_DEACTIVATING));
 }
 
 static gboolean
@@ -829,8 +961,7 @@ get_autoconnect_allowed (NMDevice *device)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
 
-	return is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)
-	       && priv->can_connect;
+	return priv->can_connect;
 }
 
 static gboolean
@@ -856,7 +987,9 @@ can_auto_connect (NMDevice *device,
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
-	/* Only Infrastrusture mode */
+	/* Don't auto-activate AP or Ad-Hoc connections.
+	 * Note the wpa_supplicant backend has the opposite policy.
+	 */
 	mode = nm_setting_wireless_get_mode (s_wifi);
 	if (mode && g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
 		return FALSE;
@@ -870,14 +1003,6 @@ can_auto_connect (NMDevice *device,
 			return FALSE;
 	}
 
-	/* 8021x networks can only be used if they've been provisioned on the IWD side and
-	 * thus are Known Networks.
-	 */
-	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection))
-			return FALSE;
-	}
-
 	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 	if (ap) {
 		/* All good; connection is usable */
@@ -908,11 +1033,11 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
 	NMDeviceIwdPrivate *priv;
+	gs_unref_variant GVariant *variant = NULL;
 	gs_free_error GError *error = NULL;
 
-	if (   !_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                   G_VARIANT_TYPE ("()"), &error)
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
 		return;
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
@@ -925,11 +1050,8 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	 * scheduled when priv->scanning goes back to false.  On error,
 	 * schedule a retry now.
 	 */
-	if (error && !priv->scanning) {
-		NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
-
-		schedule_periodic_scan (self, state);
-	}
+	if (error && !priv->scanning)
+		schedule_periodic_scan (self, FALSE);
 }
 
 static void
@@ -958,9 +1080,7 @@ dbus_request_scan_cb (NMDevice *device,
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	if (   !priv->can_scan
-	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
-	    || nm_device_is_activating (device)) {
+	if (!priv->can_scan) {
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -982,8 +1102,7 @@ dbus_request_scan_cb (NMDevice *device,
 
 	if (!priv->scanning && !priv->scan_requested) {
 		g_dbus_proxy_call (priv->dbus_station_proxy, "Scan",
-		                   g_variant_new ("()"),
-		                   G_DBUS_CALL_FLAGS_NONE, -1,
+		                   NULL, G_DBUS_CALL_FLAGS_NONE, -1,
 		                   priv->cancellable, scan_cb, self);
 		priv->scan_requested = TRUE;
 	}
@@ -999,9 +1118,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
 
-	if (   !priv->can_scan
-	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
-	    || nm_device_is_activating (device)) {
+	if (!priv->can_scan) {
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1013,7 +1130,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -1041,8 +1158,6 @@ scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
 		return TRUE;
 	case NM_DEVICE_STATE_DISCONNECTED:
 	case NM_DEVICE_STATE_FAILED:
-		/* Can always scan when disconnected */
-		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
 		break;
 	}
@@ -1199,7 +1314,7 @@ wifi_secrets_cb (NMActRequest *req,
 
 	priv->wifi_secrets_id = NULL;
 
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+	if (nm_utils_error_is_cancelled (error, FALSE)) {
 		g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                                               "NM secrets request cancelled");
@@ -1263,7 +1378,7 @@ wifi_secrets_get_one (NMDeviceIwd *self,
 	                                                    TRUE,
 	                                                    setting_name,
 	                                                    flags,
-	                                                    setting_key,
+	                                                    NM_MAKE_STRV (setting_key),
 	                                                    wifi_secrets_cb,
 	                                                    nm_utils_user_data_pack (self, invocation));
 }
@@ -1274,6 +1389,7 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	NMDeviceIwd *self = user_data;
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	gs_unref_variant GVariant *variant = NULL;
 	gs_free_error GError *error = NULL;
 	NMConnection *connection;
 	NMSettingWireless *s_wifi;
@@ -1282,9 +1398,8 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
 	GVariant *value;
 
-	if (!_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                 G_VARIANT_TYPE ("()"),
-	                                 &error)) {
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
 		gs_free char *dbus_error = NULL;
 
 		/* Connection failed; radio problems or if the network wasn't
@@ -1295,7 +1410,7 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		       "Activation: (wifi) Network.Connect failed: %s",
 		       error->message);
 
-		if (nm_utils_error_is_cancelled (error, TRUE))
+		if (nm_utils_error_is_cancelled (error, FALSE))
 			return;
 
 		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
@@ -1359,12 +1474,262 @@ failed:
 }
 
 static void
+act_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	/* Change state to FAILED unless already done by state_changed
+	 * which may have been triggered by the station interface
+	 * appearing on DBus.
+	 */
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG)
+		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+}
+
+static void
+act_start_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+	NMSettingWireless *s_wireless;
+	GBytes *ssid;
+	gs_free char *ssid_utf8 = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Network.Connect failed: %s",
+		       error->message);
+
+		if (nm_utils_error_is_cancelled (error, FALSE))
+			return;
+
+		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG))
+			return;
+
+		goto error;
+	}
+
+	nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
+
+	s_wireless = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	if (!s_wireless)
+		goto error;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		goto error;
+
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Started '%s'.",
+	       ssid_utf8);
+
+	nm_device_activate_schedule_stage3_ip_config_start (device);
+	return;
+
+error:
+	reset_mode (self, priv->cancellable, act_failed_cb, self);
+}
+
+/* Check if we're activating an AP/AdHoc connection and if the target
+ * DBus interface has appeared already.  If so proceed to call Start or
+ * StartOpen on that interface.
+ */
+static void
+act_check_interface (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMSettingWireless *s_wireless;
+	NMSettingWirelessSecurity *s_wireless_sec;
+	GDBusProxy *proxy = NULL;
+	GBytes *ssid;
+	gs_free char *ssid_utf8 = NULL;
+	const char *mode;
+
+	if (!priv->act_mode_switch)
+		return;
+
+	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
+		proxy = priv->dbus_ap_proxy;
+	else if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC))
+		proxy = priv->dbus_adhoc_proxy;
+
+	if (!proxy)
+		return;
+
+	priv->act_mode_switch = FALSE;
+
+	if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG))
+		return;
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	if (!ssid)
+		goto failed;
+
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
+	s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS_SECURITY);
+
+	if (!s_wireless_sec) {
+		g_dbus_proxy_call (proxy, "StartOpen",
+		                   g_variant_new ("(s)", ssid_utf8),
+		                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, act_start_cb, self);
+	} else {
+		const char *psk = nm_setting_wireless_security_get_psk (s_wireless_sec);
+
+		if (!psk) {
+			_LOGE (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) No PSK for '%s'.",
+			       ssid_utf8);
+			goto failed;
+		}
+
+		g_dbus_proxy_call (proxy, "Start",
+		                   g_variant_new ("(ss)", ssid_utf8, psk),
+		                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, act_start_cb, self);
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) Called Start('%s').",
+	       ssid_utf8);
+	return;
+
+failed:
+	reset_mode (self, priv->cancellable, act_failed_cb, self);
+}
+
+static void
+act_set_mode_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gs_unref_variant GVariant *variant = NULL;
+	gs_free_error GError *error = NULL;
+
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!variant) {
+		_LOGE (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi) Setting Device.Mode failed: %s",
+		       error->message);
+
+		if (nm_utils_error_is_cancelled (error, FALSE))
+			return;
+
+		if (   !NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG)
+		    || !priv->act_mode_switch)
+			return;
+
+		priv->act_mode_switch = FALSE;
+		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) IWD Device.Mode set successfully");
+
+	act_check_interface (self);
+}
+
+static void
+act_set_mode (NMDeviceIwd *self)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	const char *iwd_mode;
+	const char *mode;
+	NMSettingWireless *s_wireless;
+
+	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	mode = nm_setting_wireless_get_mode (s_wireless);
+
+	/* We need to first set interface mode (Device.Mode) to ap or ad-hoc.
+	 * We can't directly queue a call to the Start/StartOpen method on
+	 * the DBus interface that's going to be created after the property
+	 * set call returns.
+	 */
+	iwd_mode = nm_streq (mode, NM_SETTING_WIRELESS_MODE_AP) ? "ap" : "ad-hoc";
+
+	if (!priv->cancellable)
+		priv->cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_call (priv->dbus_device_proxy,
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
+	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
+	                                  "Mode",
+	                                  g_variant_new ("s", iwd_mode)),
+	                   G_DBUS_CALL_FLAGS_NONE, 2000,
+	                   priv->cancellable, act_set_mode_cb, self);
+	priv->act_mode_switch = TRUE;
+}
+
+static void
+act_psk_cb (NMActRequest *req,
+            NMActRequestGetSecretsCallId *call_id,
+            NMSettingsConnection *s_connection,
+            GError *error,
+            gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	NMDeviceIwdPrivate *priv;
+	NMDevice *device;
+
+	if (nm_utils_error_is_cancelled (error, FALSE))
+		return;
+
+	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	device = NM_DEVICE (self);
+
+	g_return_if_fail (priv->wifi_secrets_id == call_id);
+	priv->wifi_secrets_id = NULL;
+
+	g_return_if_fail (req == nm_device_get_act_request (device));
+	g_return_if_fail (nm_act_request_get_settings_connection (req) == s_connection);
+
+	if (nm_device_get_state (device) != NM_DEVICE_STATE_NEED_AUTH)
+		goto secrets_error;
+
+	if (error) {
+		_LOGW (LOGD_WIFI, "%s", error->message);
+		goto secrets_error;
+	}
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) missing PSK request completed");
+
+	/* Change state back to what it was before NEED_AUTH */
+	nm_device_state_changed (device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+	act_set_mode (self);
+	return;
+
+secrets_error:
+	nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
+	cleanup_association_attempt (self, FALSE);
+}
+
+static void
 set_powered (NMDeviceIwd *self, gboolean powered)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
 	g_dbus_proxy_call (priv->dbus_device_proxy,
-	                   "org.freedesktop.DBus.Properties.Set",
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
 	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
 	                                  "Powered",
 	                                  g_variant_new ("b", powered)),
@@ -1384,6 +1749,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMActRequest *req;
 	NMConnection *connection;
 	NMSettingWireless *s_wireless;
+	const char *mode;
 	const char *ap_path;
 
 	ret = NM_DEVICE_CLASS (nm_device_iwd_parent_class)->act_stage1_prepare (device, out_failure_reason);
@@ -1399,20 +1765,51 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
+	/* AP mode never uses a specific object or existing scanned AP */
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
+		goto add_new;
+
 	ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req));
 	ap = ap_path ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path) : NULL;
-	if (!ap) {
-		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
-		if (!ap) {
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		}
+	if (ap) {
+		set_current_ap (self, ap, TRUE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
 
+	ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+	if (ap) {
 		nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
 		                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+		set_current_ap (self, ap, TRUE);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		/* Hidden networks not supported at this time */
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
+add_new:
+	/* If the user is trying to connect to an AP that NM doesn't yet know about
+	 * (hidden network or something) or starting a Hotspot, create an fake AP
+	 * from the security settings in the connection.  This "fake" AP gets used
+	 * until the real one is found in the scan list (Ad-Hoc or Hidden), or until
+	 * the device is deactivated (Ad-Hoc or Hotspot).
+	 */
+	ap = nm_wifi_ap_new_fake_from_connection (connection);
+	g_return_val_if_fail (ap != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	if (nm_wifi_ap_is_hotspot (ap))
+		nm_wifi_ap_set_address (ap, nm_device_get_hw_address (device));
+
+	g_object_freeze_notify (G_OBJECT (self));
+	ap_add_remove (self, TRUE, ap, FALSE);
+	g_object_thaw_notify (G_OBJECT (self));
 	set_current_ap (self, ap, FALSE);
+	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
+	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
+	g_object_unref (ap);
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -1423,60 +1820,85 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMActRequest *req;
-	NMWifiAP *ap;
 	NMConnection *connection;
-	GDBusProxy *network_proxy;
+	NMSettingWireless *s_wireless;
+	const char *mode;
 
 	req = nm_device_get_act_request (device);
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
-	ap = priv->current_ap;
-	if (!ap) {
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		goto out;
-	}
-
 	connection = nm_act_request_get_applied_connection (req);
 	g_assert (connection);
 
-	/* 802.1x networks that are not IWD Known Networks will definitely
-	 * fail, for other combinations we will let the Connect call fail
-	 * or ask us for any missing secrets through the Agent.
-	 */
-	if (   !is_connection_known_network (connection)
-	    && nm_connection_get_setting_802_1x (connection)) {
-		_LOGI (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) access point '%s' has 802.1x security, but is not configured.",
-		       nm_connection_get_id (connection));
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-		ret = NM_ACT_STAGE_RETURN_FAILURE;
-		goto out;
-	}
+	mode = nm_setting_wireless_get_mode (s_wireless);
+	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		GDBusProxy *network_proxy;
+		NMWifiAP *ap = priv->current_ap;
 
-	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
-	                                                   nm_wifi_ap_get_supplicant_path (ap),
-	                                                   NM_IWD_NETWORK_INTERFACE);
-	if (!network_proxy) {
-		_LOGE (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) could not get Network interface proxy for %s",
-		       nm_wifi_ap_get_supplicant_path (ap));
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		goto out;
-	}
+		if (!ap) {
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			goto out;
+		}
 
-	if (!priv->cancellable)
-		priv->cancellable = g_cancellable_new ();
+		/* 802.1x networks that are not IWD Known Networks will definitely
+		 * fail, for other combinations we will let the Connect call fail
+		 * or ask us for any missing secrets through the Agent.
+		 */
+		if (   nm_connection_get_setting_802_1x (connection)
+		    && !is_ap_known_network (ap)) {
+			_LOGI (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) access point '%s' has 802.1x security but is not configured in IWD.",
+			       nm_connection_get_id (connection));
+
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+			goto out;
+		}
 
-	/* Call Network.Connect.  No timeout because IWD already handles
-	 * timeouts.
-	 */
-	g_dbus_proxy_call (network_proxy, "Connect",
-	                   g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
-	                   priv->cancellable, network_connect_cb, self);
+		network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+		                                                   nm_wifi_ap_get_supplicant_path (ap),
+		                                                   NM_IWD_NETWORK_INTERFACE);
+		if (!network_proxy) {
+			_LOGE (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) could not get Network interface proxy for %s",
+			       nm_wifi_ap_get_supplicant_path (ap));
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			goto out;
+		}
 
-	g_object_unref (network_proxy);
+		if (!priv->cancellable)
+			priv->cancellable = g_cancellable_new ();
+
+		/* Call Network.Connect.  No timeout because IWD already handles
+		 * timeouts.
+		 */
+		g_dbus_proxy_call (network_proxy, "Connect",
+		                   NULL, G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+		                   priv->cancellable, network_connect_cb, self);
+
+		g_object_unref (network_proxy);
+	} else if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+		NMSettingWirelessSecurity *s_wireless_sec;
+
+		s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+		if (s_wireless_sec && !nm_setting_wireless_security_get_psk (s_wireless_sec)) {
+			/* PSK is missing from the settings, have to request it */
+
+			wifi_secrets_cancel (self);
+
+			priv->wifi_secrets_id = nm_act_request_get_secrets (req,
+			                                                    TRUE,
+			                                                    NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+			                                                    NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION,
+			                                                    NM_MAKE_STRV (NM_SETTING_WIRELESS_SECURITY_PSK),
+			                                                    act_psk_cb,
+			                                                    self);
+			nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
+		} else
+			act_set_mode (self);
+	}
 
 	/* We'll get stage3 started when the supplicant connects */
 	ret = NM_ACT_STAGE_RETURN_POSTPONE;
@@ -1507,8 +1929,8 @@ periodic_scan_timeout_cb (gpointer user_data)
 	if (priv->scanning || priv->scan_requested)
 		return FALSE;
 
-	g_dbus_proxy_call (priv->dbus_station_proxy, "Scan", g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Scan",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1,
 	                   priv->cancellable, scan_cb, self);
 	priv->scan_requested = TRUE;
 
@@ -1516,18 +1938,37 @@ periodic_scan_timeout_cb (gpointer user_data)
 }
 
 static void
-schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
+schedule_periodic_scan (NMDeviceIwd *self, gboolean initial_scan)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GVariant *value;
+	gboolean disconnected;
 	guint interval;
 
-	if (current_state <= NM_DEVICE_STATE_UNAVAILABLE)
+	if (!priv->can_scan || priv->scan_requested)
 		return;
 
-	if (current_state == NM_DEVICE_STATE_DISCONNECTED)
-		interval = 10;
+	value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "State");
+	disconnected = nm_streq0 (get_variant_state (value), "disconnected");
+	g_variant_unref (value);
+
+	/* Start scan immediately after a disconnect, mode change or
+	 * device UP, otherwise wait a period dependent on the current
+	 * state.
+	 *
+	 * (initial_scan && disconnected) override priv->scanning below
+	 * because of an IWD quirk where a device will often be in the
+	 * autoconnect state and scanning at the time of our initial_scan,
+	 * but our logic will the send it a Disconnect() causeing IWD to
+	 * exit autoconnect and interrupt the ongoing scan, meaning that
+	 * we still want a new scan ASAP.
+	 */
+	if (initial_scan && disconnected)
+		interval = 0;
+	else if (!priv->periodic_scan_id && !priv->scanning)
+		interval = disconnected ? 10 : 20;
 	else
-		interval = 20;
+		return;
 
 	nm_clear_g_source (&priv->periodic_scan_id);
 	priv->periodic_scan_id = g_timeout_add_seconds (interval,
@@ -1536,6 +1977,19 @@ schedule_periodic_scan (NMDeviceIwd *self, NMDeviceState current_state)
 }
 
 static void
+set_can_scan (NMDeviceIwd *self, gboolean can_scan)
+{
+	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+
+	if (priv->can_scan == can_scan)
+		return;
+
+	priv->can_scan = can_scan;
+
+	schedule_periodic_scan (self, TRUE);
+}
+
+static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -1544,14 +1998,6 @@ device_state_changed (NMDevice *device,
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
-		remove_all_aps (self);
-		nm_clear_g_source (&priv->periodic_scan_id);
-	} else if (old_state <= NM_DEVICE_STATE_UNAVAILABLE) {
-		update_aps (self);
-		schedule_periodic_scan (self, new_state);
-	}
-
 	switch (new_state) {
 	case NM_DEVICE_STATE_UNMANAGED:
 		break;
@@ -1669,10 +2115,13 @@ get_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_MODE:
-		if (priv->current_ap)
-			g_value_set_uint (value, NM_802_11_MODE_INFRA);
-		else
+		if (!priv->current_ap)
 			g_value_set_uint (value, NM_802_11_MODE_UNKNOWN);
+		else if (nm_wifi_ap_is_hotspot (priv->current_ap))
+			g_value_set_uint (value, NM_802_11_MODE_AP);
+		else
+			g_value_set_uint (value, nm_wifi_ap_get_mode (priv->current_ap));
+
 		break;
 	case PROP_BITRATE:
 		g_value_set_uint (value, 65000);
@@ -1702,24 +2151,6 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMDeviceIwd *device = NM_DEVICE_IWD (object);
-	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (device);
-
-	switch (prop_id) {
-	case PROP_CAPABILITIES:
-		/* construct-only */
-		priv->capabilities = g_value_get_uint (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
 /*****************************************************************************/
 
 static void
@@ -1729,7 +2160,7 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceState dev_state = nm_device_get_state (device);
 	gboolean iwd_connection = FALSE;
-	gboolean can_connect;
+	gboolean can_connect = priv->can_connect;
 
 	_LOGI (LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
 
@@ -1738,7 +2169,9 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 		iwd_connection = TRUE;
 
 	/* Don't allow scanning while connecting, disconnecting or roaming */
-	priv->can_scan = NM_IN_STRSET (new_state, "connected", "disconnected");
+	set_can_scan (self, NM_IN_STRSET (new_state, "connected", "disconnected"));
+
+	priv->can_connect = FALSE;
 
 	if (NM_IN_STRSET (new_state, "connecting", "connected", "roaming")) {
 		/* If we were connecting, do nothing, the confirmation of
@@ -1766,7 +2199,7 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 		 * callback will have more information on the specific failure
 		 * reason.
 		 */
-		if (dev_state == NM_DEVICE_STATE_CONFIG)
+		if (NM_IN_SET (dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
 			return;
 
 		if (iwd_connection)
@@ -1781,10 +2214,10 @@ state_changed (NMDeviceIwd *self, const char *new_state)
 	/* Don't allow new connection until iwd exits disconnecting and no
 	 * Connect callback is pending.
 	 */
-	can_connect = NM_IN_STRSET (new_state, "disconnected");
-	if (can_connect != priv->can_connect) {
-		priv->can_connect = can_connect;
-		nm_device_emit_recheck_auto_activate (device);
+	if (NM_IN_STRSET (new_state, "disconnected")) {
+		priv->can_connect = TRUE;
+		if (!can_connect)
+			nm_device_emit_recheck_auto_activate (device);
 	}
 }
 
@@ -1792,7 +2225,6 @@ static void
 scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
 
 	if (new_scanning == priv->scanning)
 		return;
@@ -1805,7 +2237,7 @@ scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
 		update_aps (self);
 
 		if (!priv->scan_requested)
-			schedule_periodic_scan (self, state);
+			schedule_periodic_scan (self, FALSE);
 	}
 }
 
@@ -1814,40 +2246,81 @@ station_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
                             GStrv invalidate_properties, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	GVariantIter *iter;
-	const char *key;
-	GVariant *value;
+	const char *new_str;
+	gboolean new_bool;
 
-	g_variant_get (changed_properties, "a{sv}", &iter);
-	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
-		if (!strcmp (key, "State"))
-			state_changed (self, get_variant_state (value));
+	if (g_variant_lookup (changed_properties, "State", "&s", &new_str))
+		state_changed (self, new_str);
 
-		if (!strcmp (key, "Scanning"))
-			scanning_changed (self, get_variant_boolean (value, "Scanning"));
+	if (g_variant_lookup (changed_properties, "Scanning", "b", &new_bool))
+		scanning_changed (self, new_bool);
+}
 
-		g_variant_unref (value);
-	}
+static void
+ap_adhoc_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
+                             GStrv invalidate_properties, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	gboolean new_bool;
 
-	g_variant_iter_free (iter);
+	if (g_variant_lookup (changed_properties, "Started", "b", &new_bool))
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "IWD AP/AdHoc state is now %s", new_bool ? "Started" : "Stopped");
 }
 
 static void
 powered_changed (NMDeviceIwd *self, gboolean new_powered)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	GDBusInterface *interface;
+	GVariant *value;
 
 	nm_device_queue_recheck_available (NM_DEVICE (self),
 	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
 	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
 
-	if (new_powered) {
-		GDBusInterface *interface;
-		GVariant *value;
+	interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_AP_INTERFACE) : NULL;
 
-		if (priv->dbus_station_proxy)
-			return;
+	if (priv->dbus_ap_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_ap_proxy,
+		                                      ap_adhoc_properties_changed, self);
+		g_clear_object (&priv->dbus_ap_proxy);
+	}
+
+	if (interface) {
+		priv->dbus_ap_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_ap_proxy, "g-properties-changed",
+		                  G_CALLBACK (ap_adhoc_properties_changed), self);
+
+		if (priv->act_mode_switch)
+			act_check_interface (self);
+		else
+			reset_mode (self, NULL, NULL, NULL);
+	}
+
+	interface = new_powered ? g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_ADHOC_INTERFACE) : NULL;
+
+	if (priv->dbus_adhoc_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_adhoc_proxy,
+		                                      ap_adhoc_properties_changed, self);
+		g_clear_object (&priv->dbus_adhoc_proxy);
+	}
+
+	if (interface) {
+		priv->dbus_adhoc_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_adhoc_proxy, "g-properties-changed",
+		                  G_CALLBACK (ap_adhoc_properties_changed), self);
 
+		if (priv->act_mode_switch)
+			act_check_interface (self);
+		else
+			reset_mode (self, NULL, NULL, NULL);
+	}
+
+	/* We expect one of the three interfaces to always be present when
+	 * device is Powered so if AP and AdHoc are not present we should
+	 * be in station mode.
+	 */
+	if (new_powered && !priv->dbus_ap_proxy && !priv->dbus_adhoc_proxy) {
 		interface = g_dbus_object_get_interface (priv->dbus_obj, NM_IWD_STATION_INTERFACE);
 		if (!interface) {
 			/* No Station interface on the device object.  Check if the
@@ -1858,18 +2331,30 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 			 * priv->dbus_station_proxy both point at the Device interface.
 			 */
 			value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "State");
-			if (!value) {
+			if (value) {
+				g_variant_unref (value);
+				interface = g_object_ref (G_DBUS_INTERFACE (priv->dbus_device_proxy));
+			} else {
 				_LOGE (LOGD_WIFI, "Interface %s not found on obj %s",
 				       NM_IWD_STATION_INTERFACE,
 				       g_dbus_object_get_object_path (priv->dbus_obj));
-				return;
+				interface = NULL;
 			}
 
-			g_variant_unref (value);
-			interface = g_object_ref (priv->dbus_device_proxy);
 		}
+	} else
+		interface = NULL;
+
+	if (priv->dbus_station_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy,
+		                                      station_properties_changed, self);
+		g_clear_object (&priv->dbus_station_proxy);
+	}
 
+	if (interface) {
 		priv->dbus_station_proxy = G_DBUS_PROXY (interface);
+		g_signal_connect (priv->dbus_station_proxy, "g-properties-changed",
+		                  G_CALLBACK (station_properties_changed), self);
 
 		value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "Scanning");
 		priv->scanning = get_variant_boolean (value, "Scanning");
@@ -1879,27 +2364,15 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 		state_changed (self, get_variant_state (value));
 		g_variant_unref (value);
 
-		g_signal_connect (priv->dbus_station_proxy, "g-properties-changed",
-		                  G_CALLBACK (station_properties_changed), self);
-
-		/* Call Disconnect to make sure IWD's autoconnect is disabled.
-		 * Autoconnect is the default state after device is brought UP.
-		 */
-		if (priv->enabled)
-			send_disconnect (self);
+		update_aps (self);
 	} else {
-		if (!priv->dbus_station_proxy)
-			return;
-
-		g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy,
-		                                      station_properties_changed, self);
-		g_clear_object (&priv->dbus_station_proxy);
-
-		priv->can_scan = FALSE;
+		set_can_scan (self, FALSE);
+		nm_clear_g_source (&priv->periodic_scan_id);
 		priv->scanning = FALSE;
 		priv->scan_requested = FALSE;
 		priv->can_connect = FALSE;
 		cleanup_association_attempt (self, FALSE);
+		remove_all_aps (self);
 	}
 }
 
@@ -1908,19 +2381,10 @@ device_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
                            GStrv invalidate_properties, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
-	GVariantIter *iter;
-	const char *key;
-	GVariant *value;
-
-	g_variant_get (changed_properties, "a{sv}", &iter);
-	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
-		if (!strcmp (key, "Powered"))
-			powered_changed (self, get_variant_boolean (value, "Powered"));
+	gboolean new_bool;
 
-		g_variant_unref (value);
-	}
-
-	g_variant_iter_free (iter);
+	if (g_variant_lookup (changed_properties, "Powered", "b", &new_bool))
+		powered_changed (self, new_bool);
 }
 
 void
@@ -1928,24 +2392,24 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	GDBusInterface *interface;
-	GVariant *value;
+	gs_unref_variant GVariant *value = NULL;
+	gs_unref_object GDBusProxy *adapter_proxy = NULL;
+	GVariantIter *iter;
+	const char *mode;
 	gboolean powered;
+	NMDeviceWifiCapabilities capabilities;
 
-	if (!nm_g_object_ref_set ((GObject **) &priv->dbus_obj, (GObject *) object))
+	if (!nm_g_object_ref_set (&priv->dbus_obj, object))
 		return;
 
-	if (priv->enabled && priv->dbus_station_proxy) {
-		nm_device_queue_recheck_available (NM_DEVICE (self),
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-	}
-
 	if (priv->dbus_device_proxy) {
 		g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
 		                                      device_properties_changed, self);
 		g_clear_object (&priv->dbus_device_proxy);
 
 		powered_changed (self, FALSE);
+
+		priv->act_mode_switch = FALSE;
 	}
 
 	if (!object)
@@ -1965,14 +2429,63 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 	g_signal_connect (priv->dbus_device_proxy, "g-properties-changed",
 	                  G_CALLBACK (device_properties_changed), self);
 
+	/* Parse list of interface modes supported by adapter (wiphy) */
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Adapter");
+	if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_OBJECT_PATH)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "Adapter property not cached or not an object path");
+		goto error;
+	}
+
+	adapter_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
+	                                                   g_variant_get_string (value, NULL),
+	                                                   NM_IWD_WIPHY_INTERFACE);
+	if (!adapter_proxy) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "Can't get DBus proxy for IWD Adapter for IWD Device");
+		goto error;
+	}
+
+	g_variant_unref (value);
+	value = g_dbus_proxy_get_cached_property (adapter_proxy, "SupportedModes");
+	if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "SupportedModes property not cached or not a string array");
+		goto error;
+	}
+
+	capabilities = NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_RSN;
+
+	g_variant_get (value, "as", &iter);
+	while (g_variant_iter_next (iter, "&s", &mode)) {
+		if (nm_streq (mode, "ap"))
+			capabilities |= NM_WIFI_DEVICE_CAP_AP;
+		else if (nm_streq (mode, "ad-hoc"))
+			capabilities |= NM_WIFI_DEVICE_CAP_ADHOC;
+	}
+	g_variant_iter_free (iter);
+
+	if (priv->capabilities != capabilities) {
+		priv->capabilities = capabilities;
+		_notify (self, PROP_CAPABILITIES);
+	}
+
+	g_variant_unref (value);
 	value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Powered");
 	powered = get_variant_boolean (value, "Powered");
-	g_variant_unref (value);
 
 	if (powered != priv->enabled)
 		set_powered (self, priv->enabled);
 	else if (powered)
 		powered_changed (self, TRUE);
+
+	return;
+
+error:
+	g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
+	                                      device_properties_changed, self);
+	g_clear_object (&priv->dbus_device_proxy);
 }
 
 gboolean
@@ -2041,15 +2554,14 @@ nm_device_iwd_init (NMDeviceIwd *self)
 }
 
 NMDevice *
-nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities)
+nm_device_iwd_new (const char *iface)
 {
 	return g_object_new (NM_TYPE_DEVICE_IWD,
 	                     NM_DEVICE_IFACE, iface,
-	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi",
 	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
 	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
 	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
-	                     NM_DEVICE_IWD_CAPABILITIES, (guint) capabilities,
 	                     NULL);
 }
 
@@ -2067,6 +2579,8 @@ dispose (GObject *object)
 
 	g_clear_object (&priv->dbus_device_proxy);
 	g_clear_object (&priv->dbus_station_proxy);
+	g_clear_object (&priv->dbus_ap_proxy);
+	g_clear_object (&priv->dbus_adhoc_proxy);
 	g_clear_object (&priv->dbus_obj);
 
 	remove_all_aps (self);
@@ -2084,7 +2598,6 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
-	object_class->set_property = set_property;
 	object_class->dispose = dispose;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
@@ -2108,7 +2621,6 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_async = deactivate_async;
-	device_class->deactivate_async_finish = deactivate_async_finish;
 	device_class->can_reapply_change = can_reapply_change;
 
 	device_class->state_changed = device_state_changed;
@@ -2144,8 +2656,7 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	obj_properties[PROP_CAPABILITIES] =
 	    g_param_spec_uint (NM_DEVICE_IWD_CAPABILITIES, "", "",
 	                       0, G_MAXUINT32, NM_WIFI_DEVICE_CAP_NONE,
-	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_READABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_SCANNING] =
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
index 825123b1..4a2bd31e 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -47,7 +47,7 @@ typedef struct _NMDeviceIwdClass NMDeviceIwdClass;
 
 GType nm_device_iwd_get_type (void);
 
-NMDevice *nm_device_iwd_new (const char *iface, NMDeviceWifiCapabilities capabilities);
+NMDevice *nm_device_iwd_new (const char *iface);
 
 void nm_device_iwd_set_dbus_object (NMDeviceIwd *device, GDBusObject *object);
 
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 4c42e7d3..1172a613 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -29,13 +29,11 @@
 #include "nm-device-olpc-mesh.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
 #include <signal.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
-#include <errno.h>
 
 #include "devices/nm-device.h"
 #include "nm-device-wifi.h"
@@ -166,7 +164,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		       nm_device_get_iface (priv->companion));
 	}
 
-	/* wait with continuing configuration untill the companion device is done scanning */
+	/* wait with continuing configuration until the companion device is done scanning */
 	g_object_get (priv->companion, NM_DEVICE_WIFI_SCANNING, &scanning, NULL);
 	if (scanning) {
 		priv->stage1_waiting = TRUE;
@@ -193,16 +191,13 @@ static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (device);
-	NMConnection *connection;
 	NMSettingOlpcMesh *s_mesh;
 	guint32 channel;
 	GBytes *ssid;
 	const char *anycast_addr;
 
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+	s_mesh = nm_device_get_applied_setting (device, NM_TYPE_SETTING_OLPC_MESH);
 
-	s_mesh = nm_connection_get_setting_olpc_mesh (connection);
 	g_return_val_if_fail (s_mesh, NM_ACT_STAGE_RETURN_FAILURE);
 
 	channel = nm_setting_olpc_mesh_get_channel (s_mesh);
@@ -333,7 +328,7 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	g_assert (priv->companion == NULL);
 	priv->companion = g_object_ref (other);
 
-	_LOGI (LOGD_OLPC, "found companion WiFi device %s",
+	_LOGI (LOGD_OLPC, "found companion Wi-Fi device %s",
 	       nm_device_get_iface (other));
 
 	g_signal_connect (G_OBJECT (other), NM_DEVICE_STATE_CHANGED,
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
new file mode 100644
index 00000000..8381ebc7
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -0,0 +1,1316 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-wifi-p2p.h"
+
+#include <sys/socket.h>
+
+#include "supplicant/nm-supplicant-manager.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#include "nm-manager.h"
+#include "nm-utils.h"
+#include "nm-wifi-p2p-peer.h"
+#include "NetworkManagerUtils.h"
+#include "devices/nm-device-private.h"
+#include "settings/nm-settings.h"
+#include "nm-setting-wifi-p2p.h"
+#include "nm-act-request.h"
+#include "nm-ip4-config.h"
+#include "platform/nm-platform.h"
+#include "nm-manager.h"
+#include "nm-core-internal.h"
+#include "platform/nmp-object.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceWifiP2P);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifiP2P,
+	PROP_PEERS,
+);
+
+typedef struct {
+	NMSupplicantManager *sup_mgr;
+
+	/* NOTE: In theory management and group ifaces could be identical. However,
+	 * in practice, this cannot happen currently as NMDeviceWifiP2P is only
+	 * created for existing non-P2P interfaces.
+	 * (i.e. a single standalone P2P interface is not supported at this point)
+	 */
+	NMSupplicantInterface *mgmt_iface;
+	NMSupplicantInterface *group_iface;
+
+	CList peers_lst_head;
+
+	guint sup_timeout_id;
+	guint peer_dump_id;
+	guint peer_missing_id;
+
+	bool is_waiting_for_supplicant:1;
+} NMDeviceWifiP2PPrivate;
+
+struct _NMDeviceWifiP2P {
+	NMDevice parent;
+	NMDeviceWifiP2PPrivate _priv;
+};
+
+struct _NMDeviceWifiP2PClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceWifiP2P, nm_device_wifi_p2p, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_WIFI_P2P_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceWifiP2P, NM_IS_DEVICE_WIFI_P2P, NMDevice)
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added;
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed;
+
+static void supplicant_group_interface_release (NMDeviceWifiP2P *self);
+static void supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting);
+
+/*****************************************************************************/
+
+static void
+_peer_dump (NMDeviceWifiP2P *self,
+            NMLogLevel log_level,
+            const NMWifiP2PPeer *peer,
+            const char *prefix,
+            gint32 now_s)
+{
+	char buf[1024];
+
+	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-peer: %-7s %s",
+	        prefix,
+	        nm_wifi_p2p_peer_to_string (peer, buf, sizeof (buf), now_s));
+}
+
+static gboolean
+peer_list_dump (gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->peer_dump_id = 0;
+
+	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
+		NMWifiP2PPeer *peer;
+		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
+
+		_LOGD (LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s);
+		c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst)
+			_peer_dump (self, LOGL_DEBUG, peer, "dump", now_s);
+	}
+	return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_peer_list_dump (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (   !priv->peer_dump_id
+	    && _LOGD_ENABLED (LOGD_WIFI_SCAN))
+		priv->peer_dump_id = g_timeout_add_seconds (1, peer_list_dump, self);
+}
+
+/*****************************************************************************/
+
+static void
+_set_is_waiting_for_supplicant (NMDeviceWifiP2P *self, gboolean is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->is_waiting_for_supplicant == (!!is_waiting))
+		return;
+
+	priv->is_waiting_for_supplicant = is_waiting;
+
+	if (is_waiting)
+		nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+	else
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+}
+
+/*****************************************************************************/
+
+static gboolean
+check_connection_peer_joined (NMDeviceWifiP2P *device)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (device);
+	NMConnection *conn = nm_device_get_applied_connection (NM_DEVICE (device));
+	NMWifiP2PPeer *peer;
+	const char* group;
+	const char * const * groups;
+
+	if (!conn || !priv->group_iface)
+		return FALSE;
+
+	/* Comparing the object path found on the group_iface with the peers
+	 * found on the mgmt_iface is legal. */
+	group = nm_supplicant_interface_get_p2p_group_path (priv->group_iface);
+	if (!group)
+		return FALSE;
+
+	/* NOTE: We currently only support connections to a specific peer */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, conn);
+	if (!peer)
+		return FALSE;
+
+	groups = nm_wifi_p2p_peer_get_groups (peer);
+	if (   !groups
+	    || !g_strv_contains (groups, group))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+disconnect_on_connection_peer_missing_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	_LOGW (LOGD_WIFI, "Peer requested in connection is missing for too long, failing connection.");
+
+	priv->peer_missing_id = 0;
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	return FALSE;
+}
+
+static void
+update_disconnect_on_connection_peer_missing (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	state = nm_device_get_state (NM_DEVICE (self));
+	if (   state < NM_DEVICE_STATE_IP_CONFIG
+	    || state > NM_DEVICE_STATE_ACTIVATED) {
+		nm_clear_g_source (&priv->peer_missing_id);
+		return;
+	}
+
+	if (check_connection_peer_joined (self)) {
+		if (nm_clear_g_source (&priv->peer_missing_id))
+			_LOGD (LOGD_WIFI, "Peer requested in connection is joined, removing timeout");
+		return;
+	}
+
+	if (priv->peer_missing_id == 0) {
+		_LOGD (LOGD_WIFI, "Peer requested in connection is missing, adding timeout");
+		priv->peer_missing_id = g_timeout_add_seconds (5, disconnect_on_connection_peer_missing_cb, self);
+	}
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMSupplicantInterfaceState supplicant_state;
+
+	if (!priv->mgmt_iface)
+		return FALSE;
+
+	supplicant_state = nm_supplicant_interface_get_state (priv->mgmt_iface);
+	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
+{
+	if (!NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->check_connection_compatible (device, connection, error))
+		return FALSE;
+
+	/* TODO: Allow limitting the interface using the HW-address? */
+
+	/* We don't need to check anything else here. The P2P device will only
+	 * exists if we are able to establish a P2P connection, and there should
+	 * be no further restrictions necessary.
+	 */
+
+	return TRUE;
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	gs_free char *setting_name = NULL;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	const char *setting_peer;
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+
+	if (!specific_object) {
+		/* If not given a specific object, we need at minimum a peer address */
+		if (!s_wifi_p2p) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+		setting_peer = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+		if (!setting_peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INVALID_CONNECTION,
+			             "A '%s' setting with a valid Peer is required if no Peer path was given",
+			             NM_SETTING_WIFI_P2P_SETTING_NAME);
+			return FALSE;
+		}
+
+	} else {
+		peer = nm_wifi_p2p_peer_lookup_for_device (NM_DEVICE (self), specific_object);
+		if (!peer) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
+			             "The P2P peer %s is unknown",
+			             specific_object);
+			return FALSE;
+		}
+
+		setting_peer = nm_wifi_p2p_peer_get_address (peer);
+		g_return_val_if_fail (setting_peer, FALSE);
+	}
+
+	/* Add a Wi-Fi P2P setting if one doesn't exist yet */
+	if (!s_wifi_p2p) {
+		s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_setting_wifi_p2p_new ());
+		nm_connection_add_setting (connection, NM_SETTING (s_wifi_p2p));
+	}
+
+	g_object_set (G_OBJECT (s_wifi_p2p), NM_SETTING_WIFI_P2P_PEER, setting_peer, NULL);
+
+	setting_name = g_strdup_printf ("Wi-Fi P2P Peer %s", setting_peer);
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_WIFI_P2P_SETTING_NAME,
+	                           existing_connections,
+	                           setting_name,
+	                           setting_name,
+	                           NULL,
+	                           TRUE);
+
+	return TRUE;
+}
+
+/*
+ * supplicant_find_timeout_cb
+ *
+ * Called when the supplicant has been unable to find the peer we want to connect to.
+ */
+static gboolean
+supplicant_find_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) could not find peer, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMActStageReturn ret;
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	ret = NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	connection = nm_act_request_get_applied_connection (req);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	g_return_val_if_fail (s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE);
+
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		/* Set up a timeout on the find attempt and run a find for the same period of time */
+		priv->sup_timeout_id = g_timeout_add_seconds (10,
+		                                              supplicant_find_timeout_cb,
+		                                              self);
+
+		nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, 10);
+
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+cleanup_p2p_connect_attempt (NMDeviceWifiP2P *self, gboolean disconnect)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	nm_clear_g_source (&priv->peer_missing_id);
+
+	if (priv->mgmt_iface)
+		nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (disconnect && priv->group_iface)
+		nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+}
+
+/*
+ * supplicant_connection_timeout_cb
+ *
+ * Called when the supplicant has been unable to connect to a peer
+ * within a specified period of time.
+ */
+static gboolean
+supplicant_connection_timeout_cb (gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	priv->sup_timeout_id = 0;
+
+	nm_supplicant_interface_p2p_cancel_connect (priv->mgmt_iface);
+
+	if (nm_device_is_activating (device)) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI,
+		       "Activation: (wifi-p2p) connecting took too long, failing activation");
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingWifiP2P *s_wifi_p2p;
+	NMWifiP2PPeer *peer;
+	GBytes *wfd_ies;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+
+	if (!priv->mgmt_iface) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	connection = nm_device_get_applied_connection (device);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	nm_assert (NM_IS_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P)));
+
+	/* The prepare stage ensures that the peer has been found */
+	peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+	if (!peer) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	/* Set the WFD IEs before trying to establish the connection. */
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	wfd_ies = nm_setting_wifi_p2p_get_wfd_ies (s_wifi_p2p);
+	nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, wfd_ies);
+
+	/* TODO: Grab secrets if we don't have them yet! */
+
+	/* TODO: Fix "pbc" being hardcoded here! */
+	nm_supplicant_interface_p2p_connect (priv->mgmt_iface,
+	                                     nm_wifi_p2p_peer_get_supplicant_path (peer),
+	                                     "pbc", NULL);
+
+	/* Set up a timeout on the connect attempt */
+	priv->sup_timeout_id = g_timeout_add_seconds (45,
+	                                              supplicant_connection_timeout_cb,
+	                                              self);
+
+	/* We'll get stage3 started when the P2P group has been started */
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+emit_signal_p2p_peer_add_remove (NMDeviceWifiP2P *device,
+                                 NMWifiP2PPeer *peer,
+                                 gboolean is_added /* or else is_removed */)
+{
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (device),
+	                            &interface_info_device_wifi_p2p,
+	                            is_added
+	                              ? &nm_signal_info_wifi_p2p_peer_added
+	                              : &nm_signal_info_wifi_p2p_peer_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (peer)));
+}
+
+static void
+peer_add_remove (NMDeviceWifiP2P *self,
+                 gboolean is_adding, /* or else removing */
+                 NMWifiP2PPeer *peer,
+                 gboolean recheck_available_connections)
+{
+	NMDevice *device = NM_DEVICE (self);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (is_adding) {
+		g_object_ref (peer);
+		peer->wifi_device = device;
+		c_list_link_tail (&priv->peers_lst_head, &peer->peers_lst);
+		nm_dbus_object_export (NM_DBUS_OBJECT (peer));
+		_peer_dump (self, LOGL_DEBUG, peer, "added", 0);
+
+		emit_signal_p2p_peer_add_remove (self, peer, TRUE);
+	} else {
+		peer->wifi_device = NULL;
+		c_list_unlink (&peer->peers_lst);
+		_peer_dump (self, LOGL_DEBUG, peer, "removed", 0);
+	}
+
+	_notify (self, PROP_PEERS);
+
+	if (!is_adding) {
+		emit_signal_p2p_peer_add_remove (self, peer, FALSE);
+		nm_dbus_object_clear_and_unexport (&peer);
+	}
+
+	if (is_adding) {
+		/* If we are in prepare state, then we are currently runnign a find
+		 * to search for the requested peer. */
+		if (nm_device_get_state (device) == NM_DEVICE_STATE_PREPARE) {
+			NMConnection *connection;
+
+			connection = nm_device_get_applied_connection (device);
+			g_assert (connection);
+
+			peer = nm_wifi_p2p_peers_find_first_compatible (&priv->peers_lst_head, connection);
+			if (peer) {
+				/* A peer for the connection was found, cancel the timeout and go to configure state. */
+				nm_clear_g_source (&priv->sup_timeout_id);
+				nm_device_activate_schedule_stage2_device_config (device);
+			}
+		}
+
+		/* TODO: We may want to re-check auto-activation here, otherwise it will never work. */
+	}
+
+	update_disconnect_on_connection_peer_missing (self);
+}
+
+static void
+remove_all_peers (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMWifiP2PPeer *peer;
+
+	if (c_list_is_empty (&priv->peers_lst_head))
+		return;
+
+	while ((peer = c_list_first_entry (&priv->peers_lst_head, NMWifiP2PPeer, peers_lst)))
+		peer_add_remove (self, FALSE, peer, FALSE);
+
+	nm_device_recheck_available_connections (NM_DEVICE (self));
+}
+
+/*****************************************************************************/
+
+
+static NMActStageReturn
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
+{
+	gboolean indicate_addressing_running;
+	NMConnection *connection;
+	const char *method;
+
+	connection = nm_device_get_applied_connection (device);
+
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
+
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
+
+	return NM_DEVICE_CLASS (nm_device_wifi_p2p_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	int ifindex = nm_device_get_ip_ifindex (device);
+
+	cleanup_p2p_connect_attempt (self, TRUE);
+
+	/* Clear any critical protocol notification in the Wi-Fi stack */
+	if (ifindex > 0)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
+}
+
+static guint32
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+{
+	*out_source = NM_DEVICE_MTU_SOURCE_NONE;
+	return 0;
+}
+
+static const char *
+get_auto_ip_config_method (NMDevice *device, int addr_family)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	/* Override the AUTO method to mean shared if we are group owner. */
+	if (   priv->group_iface
+	    && nm_supplicant_interface_get_p2p_group_owner (priv->group_iface)) {
+		if (addr_family == AF_INET)
+			return NM_SETTING_IP4_CONFIG_METHOD_SHARED;
+
+		if (addr_family == AF_INET6)
+			return NM_SETTING_IP6_CONFIG_METHOD_SHARED;
+	}
+
+	return NULL;
+}
+
+static gboolean
+unmanaged_on_quit (NMDevice *self)
+{
+	return TRUE;
+}
+
+static void
+supplicant_iface_state_cb (NMSupplicantInterface *iface,
+                           int new_state_i,
+                           int old_state_i,
+                           int disconnect_reason,
+                           gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "supplicant management interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "supplicant ready");
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_interfaces_release (self, TRUE);
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_iface_peer_updated_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  GVariant *properties,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *found_peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	found_peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (found_peer) {
+		if (!nm_wifi_p2p_peer_update_from_properties (found_peer, object_path, properties))
+			return;
+
+		update_disconnect_on_connection_peer_missing (self);
+		_peer_dump (self, LOGL_DEBUG, found_peer, "updated", 0);
+	} else {
+		gs_unref_object NMWifiP2PPeer *peer = NULL;
+
+		peer = nm_wifi_p2p_peer_new_from_properties (object_path, properties);
+		if (!peer) {
+			_LOGD (LOGD_WIFI, "invalid P2P peer properties received for %s", object_path);
+			return;
+		}
+
+		peer_add_remove (self, TRUE, peer, TRUE);
+	}
+
+	schedule_peer_list_dump (self);
+}
+
+static void
+supplicant_iface_peer_removed_cb (NMSupplicantInterface *iface,
+                                  const char *object_path,
+                                  NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+	NMWifiP2PPeer *peer;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (object_path != NULL);
+
+	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
+	if (!peer)
+		return;
+
+	peer_add_remove (self, FALSE, peer, TRUE);
+	schedule_peer_list_dump (self);
+}
+
+static void
+check_group_iface_ready (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);;
+
+	if (!priv->group_iface)
+		return;
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	if (!nm_supplicant_interface_get_p2p_group_joined (priv->group_iface))
+		return;
+
+	nm_clear_g_source (&priv->sup_timeout_id);
+	update_disconnect_on_connection_peer_missing (self);
+
+	nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+}
+
+static void
+supplicant_group_iface_state_cb (NMSupplicantInterface *iface,
+                                 int new_state_i,
+                                 int old_state_i,
+                                 int disconnect_reason,
+                                 gpointer user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	NMSupplicantInterfaceState new_state = new_state_i;
+	NMSupplicantInterfaceState old_state = old_state_i;
+
+	if (new_state == old_state)
+		return;
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "P2P Group supplicant interface state: %s -> %s",
+	       nm_supplicant_interface_state_to_string (old_state),
+	       nm_supplicant_interface_state_to_string (new_state));
+
+	switch (new_state) {
+	case NM_SUPPLICANT_INTERFACE_STATE_READY:
+		_LOGD (LOGD_WIFI, "P2P Group supplicant ready");
+
+		if (!nm_device_set_ip_iface (device, nm_supplicant_interface_get_ifname (priv->group_iface))) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			break;
+		}
+
+		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, FALSE);
+
+		check_group_iface_ready (self);
+		break;
+	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+		supplicant_group_interface_release (self);
+
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_DISCONNECTED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+supplicant_group_iface_group_finished_cb (NMSupplicantInterface *iface,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	supplicant_group_interface_release (self);
+
+	nm_device_state_changed (NM_DEVICE (self),
+	                         NM_DEVICE_STATE_DISCONNECTED,
+	                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+}
+
+static void
+supplicant_iface_group_joined_updated_cb (NMSupplicantInterface *iface,
+                                          GParamSpec *pspec,
+                                          void *user_data)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_iface_group_started_cb (NMSupplicantInterface *iface,
+                                   NMSupplicantInterface *group_iface,
+                                   NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (self != NULL);
+
+	if (!nm_device_is_activating (NM_DEVICE (self))) {
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant notified a group start but we are not trying to connect! Ignoring the event.");
+		return;
+	}
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	supplicant_group_interface_release (self);
+	priv->group_iface = g_object_ref (group_iface);
+
+	/* We need to wait for the interface to be ready and the group
+	 * information to be resolved. */
+	g_signal_connect (priv->group_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED,
+	                  G_CALLBACK (supplicant_iface_group_joined_updated_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface,
+	                  NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_group_iface_state_cb),
+	                  self);
+
+	g_signal_connect (priv->group_iface, NM_SUPPLICANT_INTERFACE_GROUP_FINISHED,
+	                  G_CALLBACK (supplicant_group_iface_group_finished_cb),
+	                  self);
+
+	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		_set_is_waiting_for_supplicant (self, TRUE);
+
+	check_group_iface_ready (self);
+}
+
+static void
+supplicant_group_interface_release (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->group_iface)
+		return;
+
+	g_signal_handlers_disconnect_by_data (priv->group_iface, self);
+
+	nm_supplicant_interface_p2p_disconnect (priv->group_iface);
+
+	g_clear_object (&priv->group_iface);
+}
+
+static void
+supplicant_interfaces_release (NMDeviceWifiP2P *self, gboolean set_is_waiting)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->peer_dump_id);
+
+	remove_all_peers (self);
+
+	if (priv->mgmt_iface) {
+		_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: Releasing WPA supplicant interface.");
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		g_signal_handlers_disconnect_by_data (priv->mgmt_iface, self);
+		g_clear_object (&priv->mgmt_iface);
+		nm_clear_g_source (&priv->sup_timeout_id);
+	}
+
+	supplicant_group_interface_release (self);
+
+	if (set_is_waiting)
+		_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (device);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	update_disconnect_on_connection_peer_missing (self);
+
+	if (new_state <= NM_DEVICE_STATE_UNAVAILABLE) {
+		/* Clean up the supplicant interface because in these states the
+		 * device cannot be used.
+		 * Do not clean up for the UNMANAGED to UNAVAILABLE transition which
+		 * will happen during initialization.
+		 */
+		if (priv->mgmt_iface && old_state > new_state)
+			supplicant_interfaces_release (self, TRUE);
+
+		/* TODO: More cleanup needed? */
+	}
+
+	switch (new_state) {
+	case NM_DEVICE_STATE_UNMANAGED:
+		break;
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		if (   !priv->mgmt_iface
+		    || nm_supplicant_interface_get_state (priv->mgmt_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+			_set_is_waiting_for_supplicant (self, TRUE);
+
+		break;
+	case NM_DEVICE_STATE_NEED_AUTH:
+		/* Disconnect? */
+		break;
+	case NM_DEVICE_STATE_IP_CHECK:
+		/* Clear any critical protocol notification in the wifi stack */
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		//activation_success_handler (device);
+		break;
+	case NM_DEVICE_STATE_FAILED:
+		/* Clear any critical protocol notification in the wifi stack.
+		 * At this point the IP device may have been removed already. */
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		if (nm_device_get_ip_ifindex (device) > 0)
+			nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), FALSE);
+		break;
+	case NM_DEVICE_STATE_DISCONNECTED:
+		nm_supplicant_manager_set_wfd_ies (priv->sup_mgr, NULL);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+impl_device_wifi_p2p_start_find (NMDBusObject *obj,
+                                 const NMDBusInterfaceInfoExtended *interface_info,
+                                 const NMDBusMethodInfoExtended *method_info,
+                                 GDBusConnection *connection,
+                                 const char *sender,
+                                 GDBusMethodInvocation *invocation,
+                                 GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	gs_unref_variant GVariant *options = NULL;
+	const char *opts_key;
+	GVariant *opts_val;
+	GVariantIter iter;
+	gint32 timeout = 30;
+
+	g_variant_get (parameters, "(@a{sv})", &options);
+
+	g_variant_iter_init (&iter, options);
+	while (g_variant_iter_next (&iter, "{&sv}", &opts_key, &opts_val)) {
+		_nm_unused gs_unref_variant GVariant *opts_val_free = opts_val;
+
+		if (nm_streq (opts_key, "timeout")) {
+			if (!g_variant_is_of_type (opts_val, G_VARIANT_TYPE_INT32)) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_INVALID_ARGUMENT,
+				                                               "\"timeout\" must be an integer \"i\"");
+				return;
+			}
+
+			timeout = g_variant_get_int32 (opts_val);
+			if (timeout <= 0 || timeout > 600) {
+				g_dbus_method_invocation_return_error_literal (invocation,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               "The timeout for a find operation needs to be in the range of 1-600s.");
+				return;
+			}
+
+			continue;
+		}
+
+		g_dbus_method_invocation_return_error (invocation,
+		                                       NM_DEVICE_ERROR,
+		                                       NM_DEVICE_ERROR_INVALID_ARGUMENT,
+		                                       "Unsupported options key \"%s\"",
+		                                       opts_key);
+		return;
+	}
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_start_find (priv->mgmt_iface, timeout);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+impl_device_wifi_p2p_stop_find (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (obj);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (!priv->mgmt_iface) {
+		g_dbus_method_invocation_return_error_literal (invocation,
+		                                               NM_DEVICE_ERROR,
+		                                               NM_DEVICE_ERROR_NOT_ACTIVE,
+		                                               "WPA Supplicant management interface is currently unavailable.");
+		return;
+	}
+
+	nm_supplicant_interface_p2p_stop_find (priv->mgmt_iface);
+
+	g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+/*****************************************************************************/
+
+NMSupplicantInterface *
+nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE_WIFI_P2P (self), NULL);
+
+	return NM_DEVICE_WIFI_P2P_GET_PRIVATE (self)->mgmt_iface;
+}
+
+void
+nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                   NMSupplicantInterface *iface)
+{
+	NMDeviceWifiP2PPrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE_WIFI_P2P (self));
+	g_return_if_fail (!iface || NM_IS_SUPPLICANT_INTERFACE (iface));
+
+	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	if (priv->mgmt_iface == iface)
+		goto done;
+
+	supplicant_interfaces_release (self, FALSE);
+
+	if (!iface)
+		goto done;
+
+	_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant management interface changed to %s.",
+	       nm_supplicant_interface_get_object_path (iface));
+
+	priv->mgmt_iface = g_object_ref (iface);
+
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_STATE,
+	                  G_CALLBACK (supplicant_iface_state_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
+	                  G_CALLBACK (supplicant_iface_peer_updated_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
+	                  G_CALLBACK (supplicant_iface_peer_removed_cb),
+	                  self);
+	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
+	                  G_CALLBACK (supplicant_iface_group_started_cb),
+	                  self);
+done:
+	nm_device_queue_recheck_available (NM_DEVICE (self),
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	_set_is_waiting_for_supplicant (self,
+	                                   !priv->mgmt_iface
+	                                || (  nm_supplicant_interface_get_state (priv->mgmt_iface)
+	                                    < NM_SUPPLICANT_INTERFACE_STATE_READY));
+}
+
+void
+nm_device_wifi_p2p_remove (NMDeviceWifiP2P* self)
+{
+	g_signal_emit_by_name (self, NM_DEVICE_REMOVED);
+}
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "wifi-p2p";
+}
+
+/*****************************************************************************/
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerAdded",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
+	"PeerRemoved",
+	.args = NM_DEFINE_GDBUS_ARG_INFOS (
+		NM_DEFINE_GDBUS_ARG_INFO ("peer", "o"),
+	),
+);
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIFI_P2P,
+		.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StartFind",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("options", "a{sv}"),
+					),
+				),
+				.handle = impl_device_wifi_p2p_start_find,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"StopFind",
+				),
+				.handle = impl_device_wifi_p2p_stop_find,
+			),
+		),
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_wifi_p2p_peer_added,
+			&nm_signal_info_wifi_p2p_peer_removed,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",  "s",  NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Peers",      "ao", NM_DEVICE_WIFI_P2P_PEERS),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	const char **list;
+
+	switch (prop_id) {
+	case PROP_PEERS:
+		list = nm_wifi_p2p_peers_get_paths (&priv->peers_lst_head);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (list));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_wifi_p2p_init (NMDeviceWifiP2P * self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	c_list_init (&priv->peers_lst_head);
+
+	priv->sup_mgr = g_object_ref (nm_supplicant_manager_get ());
+}
+
+static void
+constructed (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->constructed (object);
+
+	_set_is_waiting_for_supplicant (self, TRUE);
+}
+
+NMDeviceWifiP2P *
+nm_device_wifi_p2p_new (const char *iface)
+{
+	return g_object_new (NM_TYPE_DEVICE_WIFI_P2P,
+	                     NM_DEVICE_IFACE, iface,
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi P2P",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI_P2P,
+	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
+	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
+	                     NULL);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (object);
+
+	g_clear_object (&priv->sup_mgr);
+
+	supplicant_interfaces_release (self, FALSE);
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDeviceWifiP2P *peer = NM_DEVICE_WIFI_P2P (object);
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (peer);
+
+	nm_assert (c_list_is_empty (&priv->peers_lst_head));
+
+	G_OBJECT_CLASS (nm_device_wifi_p2p_parent_class)->finalize (object);
+}
+
+static void
+nm_device_wifi_p2p_class_init (NMDeviceWifiP2PClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	object_class->constructed  = constructed;
+	object_class->get_property = get_property;
+	object_class->dispose      = dispose;
+	object_class->finalize     = finalize;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wifi_p2p);
+
+	device_class->connection_type_supported        = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIFI_P2P_SETTING_NAME;
+	device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIFI_P2P);
+	device_class->get_type_description             = get_type_description;
+
+	/* Do we need compatibility checking or is the default good enough? */
+	device_class->is_available                     = is_available;
+	device_class->check_connection_compatible      = check_connection_compatible;
+	device_class->complete_connection              = complete_connection;
+
+	device_class->act_stage1_prepare               = act_stage1_prepare;
+	device_class->act_stage2_config                = act_stage2_config;
+	device_class->get_configured_mtu               = get_configured_mtu;
+	device_class->get_auto_ip_config_method        = get_auto_ip_config_method;
+	device_class->act_stage3_ip_config_start      = act_stage3_ip_config_start;
+
+	device_class->deactivate                       = deactivate;
+	device_class->unmanaged_on_quit                = unmanaged_on_quit;
+
+	device_class->state_changed                    = device_state_changed;
+
+	obj_properties[PROP_PEERS] =
+	    g_param_spec_boxed (NM_DEVICE_WIFI_P2P_PEERS, "", "",
+	                        G_TYPE_STRV,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-device-wifi-p2p.h b/src/devices/wifi/nm-device-wifi-p2p.h
new file mode 100644
index 00000000..a13eef15
--- /dev/null
+++ b/src/devices/wifi/nm-device-wifi-p2p.h
@@ -0,0 +1,50 @@
+/* NetworkManager -- Wi-Fi P2P Device
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEVICE_WIFI_P2P_H__
+#define __NM_DEVICE_WIFI_P2P_H__
+
+#include "devices/nm-device.h"
+#include "supplicant/nm-supplicant-interface.h"
+
+#define NM_TYPE_DEVICE_WIFI_P2P            (nm_device_wifi_p2p_get_type ())
+#define NM_DEVICE_WIFI_P2P(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2P))
+#define NM_DEVICE_WIFI_P2P_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+#define NM_IS_DEVICE_WIFI_P2P(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_IS_DEVICE_WIFI_P2P_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WIFI_P2P))
+#define NM_DEVICE_WIFI_P2P_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WIFI_P2P, NMDeviceWifiP2PClass))
+
+#define NM_DEVICE_WIFI_P2P_PEERS       "peers"
+#define NM_DEVICE_WIFI_P2P_GROUPS      "groups"
+
+typedef struct _NMDeviceWifiP2P NMDeviceWifiP2P;
+typedef struct _NMDeviceWifiP2PClass NMDeviceWifiP2PClass;
+
+GType nm_device_wifi_p2p_get_type (void);
+
+NMDeviceWifiP2P *nm_device_wifi_p2p_new (const char *iface);
+
+NMSupplicantInterface * nm_device_wifi_p2p_get_mgmt_iface (NMDeviceWifiP2P *self);
+void                    nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
+                                                           NMSupplicantInterface  *iface);
+
+void nm_device_wifi_p2p_remove (NMDeviceWifiP2P *self);
+
+#endif /* __NM_DEVICE_WIFI_P2P_H__ */
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 2ce84618..64869672 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -24,10 +24,9 @@
 #include "nm-device-wifi.h"
 
 #include <netinet/in.h>
-#include <string.h>
 #include <unistd.h>
-#include <errno.h>
 
+#include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
 #include "nm-common-macros.h"
 #include "devices/nm-device.h"
@@ -79,6 +78,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 
 enum {
 	SCANNING_PROHIBITED,
+	P2P_DEVICE_CREATED,
 
 	LAST_SIGNAL
 };
@@ -124,6 +124,8 @@ typedef struct {
 	guint             wps_timeout_id;
 
 	NMSettingWirelessWakeOnWLan wowlan_restore;
+
+	NMDeviceWifiP2P  *p2p_device;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -186,6 +188,10 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
 
+static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                                   GParamSpec *pspec,
+                                                   NMDeviceWifi *self);
+
 static void request_wireless_scan (NMDeviceWifi *self,
                                    gboolean periodic,
                                    gboolean force_if_scanning,
@@ -198,6 +204,8 @@ static void ap_add_remove (NMDeviceWifi *self,
 
 static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset);
 
+static void recheck_p2p_availability (NMDeviceWifi *self);
+
 /*****************************************************************************/
 
 static void
@@ -291,6 +299,10 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 	                  "notify::" NM_SUPPLICANT_INTERFACE_CURRENT_BSS,
 	                  G_CALLBACK (supplicant_iface_notify_current_bss),
 	                  self);
+	g_signal_connect (priv->sup_iface,
+	                  "notify::" NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE,
+	                  G_CALLBACK (supplicant_iface_notify_p2p_available),
+	                  self);
 
 	_notify_scanning (self);
 
@@ -347,6 +359,11 @@ supplicant_interface_release (NMDeviceWifi *self)
 		g_clear_object (&priv->sup_iface);
 	}
 
+	if (priv->p2p_device) {
+		/* Signal to P2P device to also release its reference */
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, NULL);
+	}
+
 	_notify_scanning (self);
 }
 
@@ -732,7 +749,7 @@ check_connection_available (NMDevice *device,
 
 	/* Hidden SSIDs obviously don't always appear in the scan list either.
 	 *
-	 * For an explict user-activation-request, a connection is considered
+	 * For an explicit user-activation-request, a connection is considered
 	 * available because for hidden Wi-Fi, clients didn't consistently
 	 * set the 'hidden' property to indicate hidden SSID networks.  If
 	 * activating but the network isn't available let the device recheck
@@ -838,7 +855,7 @@ complete_connection (NMDevice *device,
 		ssid = nm_wifi_ap_get_ssid (ap);
 
 	if (ssid == NULL) {
-		/* The AP must be hidden.  Connecting to a WiFi AP requires the SSID
+		/* The AP must be hidden.  Connecting to a Wi-Fi AP requires the SSID
 		 * as part of the initial handshake, so check the connection details
 		 * for the SSID.  The AP object will still be used for encryption
 		 * settings and such.
@@ -909,18 +926,6 @@ complete_connection (NMDevice *device,
 				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MAC_ADDRESS);
 				return FALSE;
 			}
-		} else {
-			guint8 tmp[ETH_ALEN];
-
-			/* Lock the connection to this device by default if it uses a
-			 * permanent MAC address (ie not a 'locally administered' one)
-			 */
-			nm_utils_hwaddr_aton (perm_hw_addr, tmp, ETH_ALEN);
-			if (!(tmp[0] & 0x02)) {
-				g_object_set (G_OBJECT (s_wifi),
-				              NM_SETTING_WIRELESS_MAC_ADDRESS, perm_hw_addr,
-				              NULL);
-			}
 		}
 	}
 
@@ -981,7 +986,7 @@ can_auto_connect (NMDevice *device,
 	g_return_val_if_fail (s_wifi, FALSE);
 
 	/* Always allow autoconnect for AP and non-autoconf Ad-Hoc */
-	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	method = nm_utils_get_ip_config_method (connection, AF_INET);
 	mode = nm_setting_wireless_get_mode (s_wifi);
 	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
 		return TRUE;
@@ -1197,7 +1202,7 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NM_DEVICE_AUTH_REQUEST,
 	                       invocation,
 	                       NULL,
-	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
@@ -1236,7 +1241,7 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
 		/* Prohibit periodic scans when connected; we ask the supplicant to
-		 * background scan for us, unless the connection is locked to a specifc
+		 * background scan for us, unless the connection is locked to a specific
 		 * BSSID.
 		 */
 		if (periodic)
@@ -1685,20 +1690,37 @@ wifi_secrets_cb (NMActRequest *req,
 	g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
 
 	if (error) {
-		_LOGW (LOGD_WIFI, "%s", error->message);
-
-		if (g_error_matches (error, NM_AGENT_MANAGER_ERROR,
-		                     NM_AGENT_MANAGER_ERROR_USER_CANCELED)) {
-			/* Don't wait for WPS timeout on an explicit cancel. */
-			nm_clear_g_source (&priv->wps_timeout_id);
-		}
+		_LOGW (LOGD_WIFI, "no secrets: %s", error->message);
 
-		if (!priv->wps_timeout_id) {
-			/* Fail the device only if the WPS period is over too. */
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
-		}
+		/* Even if WPS is still pending, let's abort the activation when the secret
+		 * request returns.
+		 *
+		 * This means, a user can only effectively use WPS when also running a secret
+		 * agent, and pressing the push button while being prompted for the password.
+		 * Note, that in the secret prompt the user can see that WPS is in progress
+		 * (via the NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE flag).
+		 *
+		 * Previously, WPS was not cancelled when the secret request returns.
+		 * Note that in common use-cases WPS is enabled in the connection profile
+		 * but it won't succeed (because it's disabled in the AP or because the
+		 * user is not prepared to press the push button).
+		 * That means for example, during boot we would try to autoconnect with WPS.
+		 * At that point, there is no secret-agent running, and WPS is pending for
+		 * full 30 seconds. If in the meantime a secret agent registers (because
+		 * of logging into the DE), the profile is still busy waiting for WPS to time
+		 * out. Only after that delay, autoconnect starts again (note that autoconnect gets
+		 * not blocked in this case, because a secret agent registered in the meantime).
+		 *
+		 * It seems wrong to continue doing WPS if the user is not aware
+		 * that WPS is ongoing. The user is required to perform an action (push button),
+		 * and must be told via the secret prompt.
+		 * If no secret-agent is running, if the user cancels the secret-request, or any
+		 * other error to obtain secrets, the user apparently does not want WPS either.
+		 */
+		nm_clear_g_source (&priv->wps_timeout_id);
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
 	} else
 		nm_device_activate_schedule_stage1_device_prepare (device);
 }
@@ -1851,9 +1873,10 @@ need_new_8021x_secrets (NMDeviceWifi *self,
 	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;
 	NMConnection *connection;
 
-	g_assert (setting_name != NULL);
+	g_return_val_if_fail (setting_name, FALSE);
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
 	g_return_val_if_fail (connection != NULL, FALSE);
 
 	/* 802.1x stuff only happens in the supplicant's ASSOCIATED state when it's
@@ -1905,10 +1928,11 @@ need_new_wpa_psk (NMDeviceWifi *self,
 	NMConnection *connection;
 	const char *key_mgmt = NULL;
 
-	g_assert (setting_name != NULL);
+	g_return_val_if_fail (setting_name, FALSE);
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	g_return_val_if_fail (connection != NULL, FALSE);
+
+	g_return_val_if_fail (connection, FALSE);
 
 	/* A bad PSK will cause the supplicant to disconnect during the 4-way handshake */
 	if (old_state != NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE)
@@ -2021,6 +2045,10 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	    && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		priv->ssid_found = TRUE;
 
+	if (   old_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	    && new_state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
+		recheck_p2p_availability (self);
+
 	switch (new_state) {
 	case NM_SUPPLICANT_INTERFACE_STATE_READY:
 		_LOGD (LOGD_WIFI, "supplicant ready");
@@ -2040,15 +2068,12 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		 * schedule the next activation stage.
 		 */
 		if (devstate == NM_DEVICE_STATE_CONFIG) {
-			NMConnection *connection;
 			NMSettingWireless *s_wifi;
 			GBytes *ssid;
 			gs_free char *ssid_str = NULL;
 
-			connection = nm_device_get_applied_connection (NM_DEVICE (self));
-			g_return_if_fail (connection);
+			s_wifi = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
 
-			s_wifi = nm_connection_get_setting_wireless (connection);
 			g_return_if_fail (s_wifi);
 
 			ssid = nm_setting_wireless_get_ssid (s_wifi);
@@ -2200,6 +2225,67 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 	}
 }
 
+/* We bind the existence of the P2P device to a wifi device that is being
+ * managed by NetworkManager and is capable of P2P operation.
+ * Note that some care must be taken here, because we don't want to re-create
+ * the device every time the supplicant interface is destroyed (e.g. due to
+ * a suspend/resume cycle).
+ * Therefore, this function will be called when a change in the P2P capability
+ * is detected and the supplicant interface has been initialised.
+ */
+static void
+recheck_p2p_availability (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	gboolean p2p_available;
+
+	g_object_get (priv->sup_iface,
+	              NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, &p2p_available,
+	              NULL);
+
+	if (p2p_available && !priv->p2p_device) {
+		gs_free char *iface_name = NULL;
+
+		/* Create a P2P device. "p2p-dev-" is the same prefix as chosen by
+		 * wpa_supplicant internally.
+		 */
+		iface_name = g_strconcat ("p2p-dev-", nm_device_get_iface (NM_DEVICE (self)), NULL);
+
+		priv->p2p_device = nm_device_wifi_p2p_new (iface_name);
+
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+
+		g_signal_emit (self, signals[P2P_DEVICE_CREATED], 0, priv->p2p_device);
+		g_object_add_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		g_object_unref (priv->p2p_device);
+		return;
+	}
+
+	if (p2p_available && priv->p2p_device) {
+		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, priv->sup_iface);
+		return;
+	}
+
+	if (!p2p_available && priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+		return;
+	}
+}
+
+static void
+supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
+                                       GParamSpec *pspec,
+                                       NMDeviceWifi *self)
+{
+	/* Do not update when the interface is still initializing. */
+	if (nm_supplicant_interface_get_state (iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		return;
+
+	recheck_p2p_availability (self);
+}
+
 static gboolean
 handle_auth_or_fail (NMDeviceWifi *self,
                      NMActRequest *req,
@@ -2469,7 +2555,7 @@ wake_on_wlan_enable (NMDeviceWifi *self)
 	NMSettingWirelessWakeOnWLan wowl;
 	NMSettingWireless *s_wireless;
 
-	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
+	s_wireless = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
 	if (s_wireless) {
 		wowl = nm_setting_wireless_get_wake_on_wlan (s_wireless);
 		if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
@@ -2646,7 +2732,8 @@ set_powersave (NMDevice *device)
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessPowersave val;
 
-	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+	s_wireless = nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
+
 	g_return_if_fail (s_wireless);
 
 	val = nm_setting_wireless_get_powersave (s_wireless);
@@ -2787,50 +2874,29 @@ out:
 }
 
 static NMActStageReturn
-act_stage3_ip4_config_start (NMDevice *device,
-                             NMIP4Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	const char *method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	if (s_ip4)
-		method = nm_setting_ip_config_get_method (s_ip4);
-
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
-
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
-}
-
-static NMActStageReturn
-act_stage3_ip6_config_start (NMDevice *device,
-                             NMIP6Config **out_config,
-                             NMDeviceStateReason *out_failure_reason)
+act_stage3_ip_config_start (NMDevice *device,
+                            int addr_family,
+                            gpointer *out_config,
+                            NMDeviceStateReason *out_failure_reason)
 {
+	gboolean indicate_addressing_running;
 	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	const char *method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+	const char *method;
 
 	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (s_ip6)
-		method = nm_setting_ip_config_get_method (s_ip6);
+	method = nm_utils_get_ip_config_method (connection, addr_family);
+	if (addr_family == AF_INET)
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	else {
+		indicate_addressing_running = NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	}
 
-	/* Indicate that a critical protocol is about to start */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0 ||
-	    strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_DHCP) == 0)
-		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), TRUE);
+	if (indicate_addressing_running)
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ip_ifindex (device), TRUE);
 
-	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
 static guint32
@@ -2865,90 +2931,52 @@ is_static_wep (NMConnection *connection)
 }
 
 static NMActStageReturn
-handle_ip_config_timeout (NMDeviceWifi *self,
-                          NMConnection *connection,
-                          gboolean may_fail,
-                          gboolean *chain_up,
-                          NMDeviceStateReason *out_failure_reason)
+act_stage4_ip_config_timeout (NMDevice *device,
+                              int addr_family,
+                              NMDeviceStateReason *out_failure_reason)
 {
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	gboolean may_fail;
+
+	connection = nm_device_get_applied_connection (device);
+	s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+	may_fail = nm_setting_ip_config_get_may_fail (s_ip);
 
-	g_return_val_if_fail (connection != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	if (priv->mode == NM_802_11_MODE_AP)
+		goto call_parent;
 
-	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP) {
-		*chain_up = TRUE;
-		return NM_ACT_STAGE_RETURN_FAILURE;
+	if (   may_fail
+	    || !is_static_wep (connection)) {
+		/* Not static WEP or failure allowed; let superclass handle it */
+		goto call_parent;
 	}
 
 	/* If IP configuration times out and it's a static WEP connection, that
 	 * usually means the WEP key is wrong.  WEP's Open System auth mode has
 	 * no provision for figuring out if the WEP key is wrong, so you just have
-	 * to wait for DHCP to fail to figure it out.  For all other WiFi security
+	 * to wait for DHCP to fail to figure it out.  For all other Wi-Fi security
 	 * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the
 	 * connection would have failed before IP configuration.
-	 */
-	if (!may_fail && is_static_wep (connection)) {
-		/* Activation failed, we must have bad encryption key */
-		_LOGW (LOGD_DEVICE | LOGD_WIFI,
-		       "Activation: (wifi) could not get IP configuration for connection '%s'.",
-		       nm_connection_get_id (connection));
+	 *
+	 * Activation failed, we must have bad encryption key */
+	_LOGW (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) could not get IP configuration for connection '%s'.",
+	       nm_connection_get_id (connection));
 
-		if (handle_auth_or_fail (self, NULL, TRUE)) {
-			_LOGI (LOGD_DEVICE | LOGD_WIFI,
-			       "Activation: (wifi) asking for new secrets");
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		} else {
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-			ret = NM_ACT_STAGE_RETURN_FAILURE;
-		}
-	} else {
-		/* Not static WEP or failure allowed; let superclass handle it */
-		*chain_up = TRUE;
+	if (!handle_auth_or_fail (self, NULL, TRUE)) {
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip4_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip4;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip4);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip4_config_timeout (device, out_failure_reason);
-
-	return ret;
-}
-
-static NMActStageReturn
-act_stage4_ip6_config_timeout (NMDevice *device, NMDeviceStateReason *out_failure_reason)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip6;
-	gboolean may_fail = FALSE, chain_up = FALSE;
-	NMActStageReturn ret;
-
-	connection = nm_device_get_applied_connection (device);
-	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
-	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	may_fail = nm_setting_ip_config_get_may_fail (s_ip6);
-
-	ret = handle_ip_config_timeout (NM_DEVICE_WIFI (device), connection, may_fail, &chain_up, out_failure_reason);
-	if (chain_up)
-		ret = NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip6_config_timeout (device, out_failure_reason);
+	_LOGI (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) asking for new secrets");
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 
-	return ret;
+call_parent:
+	return NM_DEVICE_CLASS (nm_device_wifi_parent_class)->act_stage4_ip_config_timeout (device, addr_family, out_failure_reason);
 }
 
 static void
@@ -3279,7 +3307,7 @@ nm_device_wifi_new (const char *iface, NMDeviceWifiCapabilities capabilities)
 {
 	return g_object_new (NM_TYPE_DEVICE_WIFI,
 	                     NM_DEVICE_IFACE, iface,
-	                     NM_DEVICE_TYPE_DESC, "802.11 WiFi",
+	                     NM_DEVICE_TYPE_DESC, "802.11 Wi-Fi",
 	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIFI,
 	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIFI,
 	                     NM_DEVICE_RFKILL_TYPE, RFKILL_TYPE_WLAN,
@@ -3305,6 +3333,12 @@ dispose (GObject *object)
 
 	remove_all_aps (self);
 
+	if (priv->p2p_device) {
+		/* Destroy the P2P device. */
+		g_object_remove_weak_pointer (G_OBJECT (priv->p2p_device), (gpointer*) &priv->p2p_device);
+		nm_device_wifi_p2p_remove (g_steal_pointer (&priv->p2p_device));
+	}
+
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->dispose (object);
 }
 
@@ -3350,10 +3384,8 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->get_configured_mtu = get_configured_mtu;
-	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	device_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	device_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
 	device_class->unmanaged_on_quit = unmanaged_on_quit;
@@ -3417,4 +3449,12 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
 	                  NULL, NULL, NULL,
 	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
+
+	signals[P2P_DEVICE_CREATED] =
+	    g_signal_new (NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__OBJECT,
+	                  G_TYPE_NONE, 1, NM_TYPE_DEVICE);
 }
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 1c555f5f..116ad11e 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -40,6 +40,7 @@
 #define NM_DEVICE_WIFI_LAST_SCAN           "last-scan"
 
 #define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
+#define NM_DEVICE_WIFI_P2P_DEVICE_CREATED     "p2p-device-created"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index a3da9791..d668f0d8 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -22,7 +22,6 @@
 
 #include "nm-iwd-manager.h"
 
-#include <string.h>
 #include <net/if.h>
 
 #include "nm-logging.h"
@@ -48,6 +47,7 @@ typedef struct {
 
 typedef struct {
 	NMManager *manager;
+	NMSettings *settings;
 	GCancellable *cancellable;
 	gboolean running;
 	GDBusObjectManager *object_manager;
@@ -136,6 +136,7 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	int ifindex;
 	NMDevice *device;
 	gs_free char *name_owner = NULL;
+	int errsv;
 
 	/* Be paranoid and check the sender address */
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager));
@@ -171,8 +172,9 @@ agent_dbus_method_cb (GDBusConnection *connection,
 
 	ifindex = if_nametoindex (ifname);
 	if (!ifindex) {
+		errsv = errno;
 		_LOGD ("agent-request: if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, device_path, errno);
+		       ifname, device_path, errsv);
 		goto return_error;
 	}
 
@@ -338,6 +340,7 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	const char *ifname;
 	int ifindex;
 	NMDevice *device;
+	int errsv;
 
 	ifname = get_property_string_or_null (proxy, "Name");
 	if (!ifname) {
@@ -349,8 +352,9 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	ifindex = if_nametoindex (ifname);
 
 	if (!ifindex) {
+		errsv = errno;
 		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
-		       ifname, g_dbus_proxy_get_object_path (proxy), errno);
+		       ifname, g_dbus_proxy_get_object_path (proxy), errsv);
 		return;
 	}
 
@@ -363,31 +367,35 @@ set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
 	nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
 }
 
-/* Create an in-memory NMConnection for a WPA2-Enterprise network that
- * has been preprovisioned with an IWD config file so that NM autoconnect
- * mechanism and the clients know this networks needs no additional EAP
- * configuration from the user.  Only do this if no existing connection
- * SSID and security type match that network yet.
+/* Look up an existing NMSettingsConnection for a WPA2-Enterprise network
+ * that has been preprovisioned with an IWD config file, or create a new
+ * in-memory connection object so that NM autoconnect mechanism and the
+ * clients know this networks needs no additional EAP configuration from
+ * the user.
  */
 static NMSettingsConnection *
 mirror_8021x_connection (NMIwdManager *self,
-                         const char *name)
+                         const char *name,
+                         gboolean create_new)
 {
-	NMSettings *settings = NM_SETTINGS_GET;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
 	NMSettingsConnection *const*iter;
 	gs_unref_object NMConnection *connection = NULL;
-	NMSettingsConnection *settings_connection;
+	NMSettingsConnection *settings_connection = NULL;
 	char uuid[37];
 	NMSetting *setting;
 	GError *error = NULL;
 	gs_unref_bytes GBytes *new_ssid = NULL;
 
-	for (iter = nm_settings_get_connections (settings, NULL); *iter; iter++) {
+	for (iter = nm_settings_get_connections (priv->settings, NULL); *iter; iter++) {
 		NMSettingsConnection *sett_conn = *iter;
 		NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
 		NMIwdNetworkSecurity security;
 		gs_free char *ssid_name = NULL;
 		NMSettingWireless *s_wifi;
+		NMSetting8021x *s_8021x;
+		gboolean external = FALSE;
+		guint i;
 
 		security = nm_wifi_connection_get_iwd_security (conn, NULL);
 		if (security != NM_IWD_NETWORK_SECURITY_8021X)
@@ -399,14 +407,30 @@ mirror_8021x_connection (NMIwdManager *self,
 
 		ssid_name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wifi));
 
-		/* We already have an NMSettingsConnection matching this
-		 * KnownNetwork, whether it's saved or an in-memory connection
-		 * potentially created by ourselves.  Nothing to do here.
-		 */
-		if (nm_streq (ssid_name, name))
-			return NULL;
+		if (!nm_streq (ssid_name, name))
+			continue;
+
+		s_8021x = nm_connection_get_setting_802_1x (conn);
+		for (i = 0; i < nm_setting_802_1x_get_num_eap_methods (s_8021x); i++) {
+			if (nm_streq (nm_setting_802_1x_get_eap_method (s_8021x, i), "external")) {
+				external = TRUE;
+				break;
+			}
+		}
+
+		/* Prefer returning connections for EAP method "external" */
+		if (!settings_connection || external)
+			settings_connection = sett_conn;
 	}
 
+	/* If we already have an NMSettingsConnection matching this
+	 * KnownNetwork, whether it's saved or an in-memory connection
+	 * potentially created by ourselves then we have nothing left to
+	 * do here.
+	 */
+	if (settings_connection || !create_new)
+		return settings_connection;
+
 	connection = nm_simple_connection_new ();
 
 	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_CONNECTION,
@@ -446,7 +470,7 @@ mirror_8021x_connection (NMIwdManager *self,
 	if (!nm_connection_normalize (connection, NULL, NULL, NULL))
 		return NULL;
 
-	settings_connection = nm_settings_add_connection (settings, connection,
+	settings_connection = nm_settings_add_connection (priv->settings, connection,
 	                                                  FALSE, &error);
 	if (!settings_connection) {
 		_LOGW ("failed to add a mirror NMConnection for IWD's Known Network '%s': %s",
@@ -526,16 +550,19 @@ interface_added (GDBusObjectManager *object_manager, GDBusObject *object,
 		id = known_network_id_new (name, security);
 
 		data = g_hash_table_lookup (priv->known_networks, id);
-		if (data)
+		if (data) {
+			_LOGW ("DBus error: KnownNetwork already exists ('%s', %s)",
+			       name, type_str);
 			g_free (id);
-		else {
+			nm_g_object_ref_set (&data->known_network, proxy);
+		} else {
 			data = g_slice_new0 (KnownNetworkData);
 			data->known_network = g_object_ref (proxy);
 			g_hash_table_insert (priv->known_networks, id, data);
 		}
 
 		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
-			sett_conn = mirror_8021x_connection (self, name);
+			sett_conn = mirror_8021x_connection (self, name, TRUE);
 
 			if (   sett_conn
 			    && sett_conn != data->mirror_connection) {
@@ -593,6 +620,55 @@ interface_removed (GDBusObjectManager *object_manager, GDBusObject *object,
 	}
 }
 
+static void
+connection_removed (NMSettings *settings,
+                    NMSettingsConnection *sett_conn,
+                    gpointer user_data)
+{
+	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
+	NMSettingWireless *s_wireless;
+	gboolean mapped;
+	KnownNetworkData *data;
+	KnownNetworkId id;
+
+	id.security = nm_wifi_connection_get_iwd_security (conn, &mapped);
+	if (!mapped)
+		return;
+
+	s_wireless = nm_connection_get_setting_wireless (conn);
+	id.name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wireless));
+	data = g_hash_table_lookup (priv->known_networks, &id);
+	g_free ((char *) id.name);
+	if (!data)
+		return;
+
+	if (id.security == NM_IWD_NETWORK_SECURITY_8021X) {
+		NMSettingsConnection *new_mirror_conn;
+
+		if (data->mirror_connection != sett_conn)
+			return;
+
+		g_clear_object (&data->mirror_connection);
+
+		/* Don't call Forget for an 8021x network until there's no
+		 * longer *any* matching NMSettingsConnection (debatable)
+		 */
+		new_mirror_conn = mirror_8021x_connection (self, id.name, FALSE);
+		if (new_mirror_conn) {
+			data->mirror_connection = g_object_ref (new_mirror_conn);
+			return;
+		}
+	}
+
+	if (!priv->running)
+		return;
+
+	g_dbus_proxy_call (data->known_network, "Forget",
+	                   NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
+}
+
 static gboolean
 _om_has_name_owner (GDBusObjectManager *object_manager)
 {
@@ -744,7 +820,7 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 	                                   &priv->agent_path,
 	                                   &error);
 	if (!priv->agent_id) {
-		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks may not work: %s",
+		_LOGE ("failed to export the IWD Agent: PSK/8021x Wi-Fi networks may not work: %s",
 		       error->message);
 		g_clear_error (&error);
 	}
@@ -824,6 +900,10 @@ nm_iwd_manager_init (NMIwdManager *self)
 	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED,
 	                  G_CALLBACK (device_added), self);
 
+	priv->settings = g_object_ref (nm_settings_get ());
+	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
+	                  G_CALLBACK (connection_removed), self);
+
 	priv->cancellable = g_cancellable_new ();
 
 	priv->known_networks = g_hash_table_new_full ((GHashFunc) known_network_id_hash,
@@ -844,6 +924,14 @@ dispose (GObject *object)
 
 	nm_clear_g_cancellable (&priv->cancellable);
 
+	if (priv->settings) {
+		g_signal_handlers_disconnect_by_data (priv->settings, self);
+		g_clear_object (&priv->settings);
+	}
+
+	/* This may trigger mirror connection removals so it happens
+	 * after the g_signal_handlers_disconnect_by_data above.
+	 */
 	nm_clear_pointer (&priv->known_networks, g_hash_table_destroy);
 
 	if (priv->manager) {
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index e5573383..87daff59 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -23,7 +23,6 @@
 
 #include "nm-wifi-ap.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-setting-wireless.h"
@@ -425,6 +424,8 @@ security_from_vardict (GVariant *security)
 		    g_strv_contains (array, "wpa-fils-sha256") ||
 		    g_strv_contains (array, "wpa-fils-sha384"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
+		if (g_strv_contains (array, "sae"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE;
 		g_free (array);
 	}
 
@@ -1390,7 +1391,8 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_GROUP_TKIP \
 	| NM_802_11_AP_SEC_GROUP_CCMP \
 	| NM_802_11_AP_SEC_KEY_MGMT_PSK \
-	| NM_802_11_AP_SEC_KEY_MGMT_802_1X )
+	| NM_802_11_AP_SEC_KEY_MGMT_802_1X \
+	| NM_802_11_AP_SEC_KEY_MGMT_SAE )
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index 6b8e5fb8..9a89bbe9 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -26,6 +26,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-olpc-mesh.h"
 #include "nm-device-wifi.h"
+#include "nm-device-wifi-p2p.h"
 #include "nm-device-olpc-mesh.h"
 #include "nm-device-iwd.h"
 #include "settings/nm-settings-connection.h"
@@ -68,6 +69,18 @@ nm_device_factory_create (GError **error)
 
 /*****************************************************************************/
 
+static void
+p2p_device_created (NMDeviceWifi    *device,
+                    NMDeviceWifiP2P *p2p_device,
+                    NMDeviceFactory *self)
+{
+	nm_log_info (LOGD_PLATFORM | LOGD_WIFI,
+	             "Wi-Fi P2P device controlled by interface %s created",
+	             nm_device_get_iface (NM_DEVICE (device)));
+
+	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device);
+}
+
 static NMDevice *
 create_device (NMDeviceFactory *factory,
                const char *iface,
@@ -75,8 +88,6 @@ create_device (NMDeviceFactory *factory,
                NMConnection *connection,
                gboolean *out_ignore)
 {
-	NMDeviceWifiCapabilities capabilities;
-	NM80211Mode mode;
 	gs_free char *backend = NULL;
 
 	g_return_val_if_fail (iface != NULL, NULL);
@@ -84,23 +95,6 @@ create_device (NMDeviceFactory *factory,
 	g_return_val_if_fail (g_strcmp0 (iface, plink->name) == 0, NULL);
 	g_return_val_if_fail (NM_IN_SET (plink->type, NM_LINK_TYPE_WIFI, NM_LINK_TYPE_OLPC_MESH), NULL);
 
-	if (!nm_platform_wifi_get_capabilities (NM_PLATFORM_GET,
-	                                        plink->ifindex,
-	                                        &capabilities)) {
-		nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) failed to initialize Wi-Fi driver for ifindex %d", iface, plink->ifindex);
-		return NULL;
-	}
-
-	/* Ignore monitor-mode and other unhandled interface types.
-	 * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
-	 * them if/when they change to a handled type.
-	 */
-	mode = nm_platform_wifi_get_mode (NM_PLATFORM_GET, plink->ifindex);
-	if (mode == NM_802_11_MODE_UNKNOWN) {
-		*out_ignore = TRUE;
-		return NULL;
-	}
-
 	if (plink->type != NM_LINK_TYPE_WIFI)
 		return nm_device_olpc_mesh_new (iface);
 
@@ -116,11 +110,42 @@ create_device (NMDeviceFactory *factory,
 	            iface,
 	            NM_PRINT_FMT_QUOTE_STRING (backend),
 	            WITH_IWD ? " (iwd support enabled)" : "");
-	if (!backend || !strcasecmp (backend, "wpa_supplicant"))
-		return nm_device_wifi_new (iface, capabilities);
+	if (!backend || !strcasecmp (backend, "wpa_supplicant")) {
+		NMDevice *device;
+		NMDeviceWifiCapabilities capabilities;
+		NM80211Mode mode;
+
+		if (!nm_platform_wifi_get_capabilities (NM_PLATFORM_GET,
+		                                        plink->ifindex,
+		                                        &capabilities)) {
+			nm_log_warn (LOGD_PLATFORM | LOGD_WIFI,
+			             "(%s) failed to initialize Wi-Fi driver for ifindex %d",
+			             iface, plink->ifindex);
+			return NULL;
+		}
+
+		/* Ignore monitor-mode and other unhandled interface types.
+		 * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
+		 * them if/when they change to a handled type.
+		 */
+		mode = nm_platform_wifi_get_mode (NM_PLATFORM_GET, plink->ifindex);
+		if (mode == NM_802_11_MODE_UNKNOWN) {
+			*out_ignore = TRUE;
+			return NULL;
+		}
+
+		device = nm_device_wifi_new (iface, capabilities);
+
+		g_signal_connect_object (device, NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
+		                         G_CALLBACK (p2p_device_created),
+		                         factory,
+		                         0);
+
+		return device;
+	}
 #if WITH_IWD
 	else if (!strcasecmp (backend, "iwd"))
-		return nm_device_iwd_new (iface, capabilities);
+		return nm_device_iwd_new (iface);
 #endif
 
 	nm_log_warn (LOGD_PLATFORM | LOGD_WIFI, "(%s) config: unknown or unsupported wifi-backend %s", iface, backend);
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/devices/wifi/nm-wifi-p2p-peer.c
new file mode 100644
index 00000000..4b524623
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.c
@@ -0,0 +1,796 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-p2p-peer.h"
+
+#include <stdlib.h>
+
+#include "nm-setting-wireless.h"
+
+#include "nm-wifi-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "platform/nm-platform.h"
+#include "devices/nm-device.h"
+#include "nm-dbus-manager.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMWifiP2PPeer,
+	PROP_NAME,
+	PROP_MANUFACTURER,
+	PROP_MODEL,
+	PROP_MODEL_NUMBER,
+	PROP_SERIAL,
+	PROP_WFD_IES,
+	PROP_GROUPS,
+	PROP_HW_ADDRESS,
+	PROP_STRENGTH,
+	PROP_LAST_SEEN,
+	PROP_FLAGS,
+);
+
+struct _NMWifiP2PPeerPrivate {
+	char *supplicant_path;   /* D-Bus object path of this Peer from wpa_supplicant */
+
+	/* Scanned or cached values */
+	char *             name;
+	char *             manufacturer;
+	char *             model;
+	char *             model_number;
+	char *             serial;
+
+	char *             address;
+
+	GBytes *           wfd_ies;
+	char **            groups;
+
+	guint8             strength;
+
+	NM80211ApFlags     flags;      /* General flags */
+
+	/* Non-scanned attributes */
+	gint32             last_seen;    /* Timestamp when the Peer was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+};
+
+typedef struct _NMWifiP2PPeerPrivate NMWifiP2PPeerPrivate;
+
+struct _NMWifiP2PPeerClass {
+	NMDBusObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMWifiP2PPeer, nm_wifi_p2p_peer, NM_TYPE_DBUS_OBJECT)
+
+#define NM_WIFI_P2P_PEER_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMWifiP2PPeer, NM_IS_WIFI_P2P_PEER)
+
+/*****************************************************************************/
+
+const char **
+nm_wifi_p2p_peers_get_paths (const CList *peers_lst_head)
+{
+	NMWifiP2PPeer *peer;
+	const char **list;
+	const char *path;
+	gsize i, n;
+
+	n = c_list_length (peers_lst_head);
+	list = g_new (const char *, n + 1);
+
+	i = 0;
+	if (n > 0) {
+		c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+			nm_assert (i < n);
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (peer));
+			nm_assert (path);
+
+			list[i++] = path;
+		}
+		nm_assert (i <= n);
+	}
+	list[i] = NULL;
+	return list;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                         NMConnection *connection)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (connection, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_wifi_p2p_peer_check_compatible (peer, connection))
+			return peer;
+	}
+	return NULL;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
+		if (nm_streq0 (path, nm_wifi_p2p_peer_get_supplicant_path (peer)))
+			return peer;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
+
+	peer = (NMWifiP2PPeer *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
+	                                                        exported_path);
+	if (   !peer
+	    || !NM_IS_WIFI_P2P_PEER (peer)
+	    || peer->wifi_device != device)
+		return NULL;
+
+	return peer;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_wifi_p2p_peer_get_supplicant_path (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->supplicant_path;
+}
+
+const char *
+nm_wifi_p2p_peer_get_name (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->name;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_name (NMWifiP2PPeer *peer, const char *name)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (name, priv->name) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->name, g_free);
+	if (name)
+		priv->name = g_strdup (name);
+
+	_notify (peer, PROP_NAME);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_manufacturer (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->manufacturer;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_manufacturer (NMWifiP2PPeer *peer, const char *manufacturer)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (manufacturer, priv->manufacturer) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->manufacturer, g_free);
+	if (manufacturer)
+		priv->manufacturer = g_strdup (manufacturer);
+
+	_notify (peer, PROP_MANUFACTURER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model (NMWifiP2PPeer *peer, const char *model)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model, priv->model) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model, g_free);
+	if (model)
+		priv->model = g_strdup (model);
+
+	_notify (peer, PROP_MODEL);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_model_number (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->model_number;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_model_number (NMWifiP2PPeer *peer, const char *model_number)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (model_number, priv->model_number) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->model_number, g_free);
+	if (model_number)
+		priv->model_number = g_strdup (model_number);
+
+	_notify (peer, PROP_MODEL_NUMBER);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_serial (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->serial;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_serial (NMWifiP2PPeer *peer, const char *serial)
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (g_strcmp0 (serial, priv->serial) == 0)
+		return FALSE;
+
+	g_clear_pointer (&priv->serial, g_free);
+	if (serial)
+		priv->serial = g_strdup (serial);
+
+	_notify (peer, PROP_SERIAL);
+	return TRUE;
+}
+
+GBytes *
+nm_wifi_p2p_peer_get_wfd_ies (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->wfd_ies;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_wfd_ies (NMWifiP2PPeer *peer, GBytes *wfd_ies)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (nm_gbytes_equal0 (priv->wfd_ies, wfd_ies))
+		return FALSE;
+
+	g_bytes_unref (priv->wfd_ies);
+	priv->wfd_ies = wfd_ies ? g_bytes_ref (wfd_ies) : NULL;
+
+	_notify (peer, PROP_WFD_IES);
+	return TRUE;
+}
+
+const char *const*
+nm_wifi_p2p_peer_get_groups (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return (const char * const*) NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->groups;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_groups (NMWifiP2PPeer *peer, const char** groups)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (groups != NULL, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (_nm_utils_strv_equal (priv->groups, (char **) groups))
+		return FALSE;
+
+	g_strfreev (priv->groups);
+	priv->groups = g_strdupv ((char**) groups);
+
+	_notify (peer, PROP_GROUPS);
+	return TRUE;
+}
+
+const char *
+nm_wifi_p2p_peer_get_address (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->address;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_address_bin (NMWifiP2PPeer *peer, const guint8 addr[static ETH_ALEN])
+{
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (   priv->address
+	    && nm_utils_hwaddr_matches (addr, ETH_ALEN, priv->address, -1))
+		return FALSE;
+
+	g_free (priv->address);
+	priv->address = nm_utils_hwaddr_ntoa (addr, ETH_ALEN);
+	_notify (peer, PROP_HW_ADDRESS);
+	return TRUE;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_address (NMWifiP2PPeer *peer, const char *addr)
+{
+	guint8 addr_buf[ETH_ALEN];
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	if (   !addr
+	    || !nm_utils_hwaddr_aton (addr, addr_buf, sizeof (addr_buf)))
+		g_return_val_if_reached (FALSE);
+
+	return nm_wifi_p2p_peer_set_address_bin (peer, addr_buf);
+}
+
+gint8
+nm_wifi_p2p_peer_get_strength (NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), 0);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->strength;
+}
+
+gboolean
+nm_wifi_p2p_peer_set_strength (NMWifiP2PPeer *peer, const gint8 strength)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->strength != strength) {
+		priv->strength = strength;
+		_notify (peer, PROP_STRENGTH);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+NM80211ApFlags
+nm_wifi_p2p_peer_get_flags (const NMWifiP2PPeer *peer)
+{
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NM_802_11_AP_FLAGS_NONE);
+
+	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->flags;
+}
+
+static gboolean
+nm_wifi_p2p_peer_set_last_seen (NMWifiP2PPeer *peer, gint32 last_seen)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	if (priv->last_seen != last_seen) {
+		priv->last_seen = last_seen;
+		_notify (peer, PROP_LAST_SEEN);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+
+/*****************************************************************************/
+
+gboolean
+nm_wifi_p2p_peer_update_from_properties (NMWifiP2PPeer *peer,
+                                         const char *supplicant_path,
+                                         GVariant *properties)
+{
+	NMWifiP2PPeerPrivate *priv;
+	const guint8 *bytes;
+	GVariant *v;
+	gsize len;
+	const char *s;
+	const char **sv;
+	gint32 i32;
+	gboolean changed = FALSE;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
+	g_return_val_if_fail (properties, FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+
+	g_object_freeze_notify (G_OBJECT (peer));
+
+	if (g_variant_lookup (properties, "level", "i", &i32))
+		changed |= nm_wifi_p2p_peer_set_strength (peer, nm_wifi_utils_level_to_quality (i32));
+
+	if (g_variant_lookup (properties, "DeviceName", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_name (peer, s);
+
+	if (g_variant_lookup (properties, "Manufacturer", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_manufacturer (peer, s);
+
+	if (g_variant_lookup (properties, "Model", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model (peer, s);
+
+	if (g_variant_lookup (properties, "ModelNumber", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_model_number (peer, s);
+
+	if (g_variant_lookup (properties, "Serial", "&s", &s))
+		changed |= nm_wifi_p2p_peer_set_serial (peer, s);
+
+	v = g_variant_lookup_value (properties, "DeviceAddress", 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)
+			changed |= nm_wifi_p2p_peer_set_address_bin (peer, bytes);
+		g_variant_unref (v);
+	}
+
+	/* The IEs property contains the WFD R1 subelements */
+	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		gs_unref_bytes GBytes *b = NULL;
+
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		b = g_bytes_new (bytes, len);
+		changed |= nm_wifi_p2p_peer_set_wfd_ies (peer, b);
+		g_variant_unref (v);
+	}
+
+	v = g_variant_lookup_value (properties, "Groups", G_VARIANT_TYPE_OBJECT_PATH_ARRAY);
+	if (v) {
+		sv = g_variant_get_objv (v, NULL);
+		changed |= nm_wifi_p2p_peer_set_groups (peer, sv);
+		g_free (sv);
+	}
+
+	/*if (max_rate)
+		changed |= nm_wifi_p2p_peer_set_max_bitrate (peer, max_rate / 1000);*/
+
+	if (!priv->supplicant_path) {
+		priv->supplicant_path = g_strdup (supplicant_path);
+		changed = TRUE;
+	}
+
+	changed |= nm_wifi_p2p_peer_set_last_seen (peer, nm_utils_get_monotonic_timestamp_s ());
+
+	g_object_thaw_notify (G_OBJECT (peer));
+
+	return changed;
+}
+
+const char *
+nm_wifi_p2p_peer_to_string (const NMWifiP2PPeer *self,
+                            char *str_buf,
+                            gsize buf_len,
+                            gint32 now_s)
+{
+	const NMWifiP2PPeerPrivate *priv;
+	const char *supplicant_id = "-";
+	const char* export_path;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), NULL);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	if (priv->supplicant_path)
+		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
+
+	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
+	if (export_path)
+		export_path = strrchr (export_path, '/') ?: export_path;
+	else
+		export_path = "/";
+
+	g_snprintf (str_buf, buf_len,
+	            "%17s [n:%s, m:%s, mod:%s, mod_num:%s, ser:%s] %3us sup:%s [nm:%s]",
+	            priv->address ?: "(none)",
+	            priv->name,
+	            priv->manufacturer,
+	            priv->model,
+	            priv->model_number,
+	            priv->serial,
+	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,
+	            supplicant_id,
+	            export_path);
+
+	return str_buf;
+}
+
+gboolean
+nm_wifi_p2p_peer_check_compatible (NMWifiP2PPeer *self,
+                                   NMConnection *connection)
+{
+	NMWifiP2PPeerPrivate *priv;
+	NMSettingWifiP2P *s_wifi_p2p;
+	const char *hwaddr;
+
+	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	s_wifi_p2p = NM_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P));
+	if (s_wifi_p2p == NULL)
+		return FALSE;
+
+	hwaddr = nm_setting_wifi_p2p_get_peer (s_wifi_p2p);
+	if (   hwaddr
+	    && (   !priv->address
+	        || !nm_utils_hwaddr_matches (hwaddr, -1, priv->address, -1)))
+		return FALSE;
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_FLAGS:
+		g_value_set_uint (value, priv->flags);
+		break;
+	case PROP_NAME:
+		g_value_set_string (value, priv->name);
+		break;
+	case PROP_MANUFACTURER:
+		g_value_set_string (value, priv->manufacturer);
+		break;
+	case PROP_MODEL:
+		g_value_set_string (value, priv->model);
+		break;
+	case PROP_MODEL_NUMBER:
+		g_value_set_string (value, priv->model_number);
+		break;
+	case PROP_SERIAL:
+		g_value_set_string (value, priv->serial);
+		break;
+	case PROP_WFD_IES:
+		g_value_take_variant (value, nm_utils_gbytes_to_variant_ay (priv->wfd_ies));
+		break;
+	case PROP_GROUPS:
+		g_value_set_variant (value,
+		                      g_variant_new_strv (   (const char*const*) priv->groups
+		                                          ?: NM_PTRARRAY_EMPTY (const char *),
+		                                          -1));
+		break;
+	case PROP_HW_ADDRESS:
+		g_value_set_string (value, priv->address);
+		break;
+	case PROP_STRENGTH:
+		g_value_set_uchar (value, priv->strength);
+		break;
+	case PROP_LAST_SEEN:
+		g_value_set_int (value,
+		                 priv->last_seen > 0
+		                     ? (int) nm_utils_monotonic_timestamp_as_boottime (priv->last_seen, NM_UTILS_NS_PER_SECOND)
+		                     : -1);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_wifi_p2p_peer_init (NMWifiP2PPeer *self)
+{
+	NMWifiP2PPeerPrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerPrivate);
+
+	self->_priv = priv;
+
+	c_list_init (&self->peers_lst);
+
+	priv->flags = NM_802_11_AP_FLAGS_NONE;
+	priv->last_seen = -1;
+}
+
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_new_from_properties (const char *supplicant_path, GVariant *properties)
+{
+	NMWifiP2PPeer *peer;
+
+	g_return_val_if_fail (supplicant_path != NULL, NULL);
+	g_return_val_if_fail (properties != NULL, NULL);
+
+	peer = (NMWifiP2PPeer *) g_object_new (NM_TYPE_WIFI_P2P_PEER, NULL);
+	nm_wifi_p2p_peer_update_from_properties (peer, supplicant_path, properties);
+
+	/* ignore peers with invalid or missing address */
+	if (!nm_wifi_p2p_peer_get_address (peer)) {
+		g_object_unref (peer);
+		return NULL;
+	}
+
+	return peer;
+}
+
+static void
+finalize (GObject *object)
+{
+	NMWifiP2PPeer *self = NM_WIFI_P2P_PEER (object);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
+
+	nm_assert (!self->wifi_device);
+	nm_assert (c_list_is_empty (&self->peers_lst));
+
+	g_free (priv->supplicant_path);
+	g_free (priv->name);
+	g_free (priv->manufacturer);
+	g_free (priv->model);
+	g_free (priv->model_number);
+	g_free (priv->serial);
+	g_free (priv->address);
+	g_bytes_unref (priv->wfd_ies);
+	g_strfreev (priv->groups);
+
+	G_OBJECT_CLASS (nm_wifi_p2p_peer_parent_class)->finalize (object);
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_WIFI_P2P_PEER,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Flags",        "u",  NM_WIFI_P2P_PEER_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Name",         "s",  NM_WIFI_P2P_PEER_NAME),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Manufacturer", "s",  NM_WIFI_P2P_PEER_MANUFACTURER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Model",        "s",  NM_WIFI_P2P_PEER_MODEL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("ModelNumber",  "s",  NM_WIFI_P2P_PEER_MODEL_NUMBER),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Serial",       "s",  NM_WIFI_P2P_PEER_SERIAL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("WfdIEs",       "ay", NM_WIFI_P2P_PEER_WFD_IES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Groups",       "as", NM_WIFI_P2P_PEER_GROUPS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",    "s",  NM_WIFI_P2P_PEER_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Strength",     "y",  NM_WIFI_P2P_PEER_STRENGTH),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("LastSeen",     "i",  NM_WIFI_P2P_PEER_LAST_SEEN),
+		),
+	),
+	.legacy_property_changed = FALSE,
+};
+
+static void
+nm_wifi_p2p_peer_class_init (NMWifiP2PPeerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (object_class, sizeof (NMWifiP2PPeerPrivate));
+
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_WIFI_P2P_PEER);
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_p2p_peer);
+
+	object_class->get_property = get_property;
+	object_class->finalize     = finalize;
+
+	obj_properties[PROP_FLAGS] =
+	    g_param_spec_uint (NM_WIFI_P2P_PEER_FLAGS, "", "",
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       NM_802_11_AP_FLAGS_PRIVACY,
+	                       NM_802_11_AP_FLAGS_NONE,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_NAME] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_NAME, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MANUFACTURER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MANUFACTURER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_MODEL_NUMBER] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_MODEL_NUMBER, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_SERIAL] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_SERIAL, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_WFD_IES] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_WFD_IES, "", "",
+	                          G_VARIANT_TYPE ("ay"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_GROUPS] =
+	    g_param_spec_variant (NM_WIFI_P2P_PEER_GROUPS, "", "",
+	                          G_VARIANT_TYPE ("as"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_HW_ADDRESS] =
+	    g_param_spec_string (NM_WIFI_P2P_PEER_HW_ADDRESS, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_STRENGTH] =
+	    g_param_spec_uchar (NM_WIFI_P2P_PEER_STRENGTH, "", "",
+	                        0, G_MAXINT8, 0,
+	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_LAST_SEEN] =
+	    g_param_spec_int (NM_WIFI_P2P_PEER_LAST_SEEN, "", "",
+	                      -1, G_MAXINT, -1,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.h b/src/devices/wifi/nm-wifi-p2p-peer.h
new file mode 100644
index 00000000..d6ff7abc
--- /dev/null
+++ b/src/devices/wifi/nm-wifi-p2p-peer.h
@@ -0,0 +1,114 @@
+/* NetworkManager -- Wi-Fi P2P Peer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_WIFI_P2P_PEER_H__
+#define __NM_WIFI_P2P_PEER_H__
+
+#include "nm-dbus-object.h"
+#include "nm-dbus-interface.h"
+#include "nm-connection.h"
+
+#define NM_TYPE_WIFI_P2P_PEER            (nm_wifi_p2p_peer_get_type ())
+#define NM_WIFI_P2P_PEER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeer))
+#define NM_WIFI_P2P_PEER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+#define NM_IS_WIFI_P2P_PEER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_P2P_PEER))
+#define NM_IS_WIFI_P2P_PEER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_P2P_PEER))
+#define NM_WIFI_P2P_PEER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_P2P_PEER, NMWifiP2PPeerClass))
+
+#define NM_WIFI_P2P_PEER_FLAGS                "flags"
+#define NM_WIFI_P2P_PEER_NAME                 "name"
+#define NM_WIFI_P2P_PEER_MANUFACTURER         "manufacturer"
+#define NM_WIFI_P2P_PEER_MODEL                "model"
+#define NM_WIFI_P2P_PEER_MODEL_NUMBER         "model-number"
+#define NM_WIFI_P2P_PEER_SERIAL               "serial"
+#define NM_WIFI_P2P_PEER_WFD_IES              "wfd-ies"
+#define NM_WIFI_P2P_PEER_GROUPS               "groups"
+#define NM_WIFI_P2P_PEER_HW_ADDRESS           "hw-address"
+#define NM_WIFI_P2P_PEER_STRENGTH             "strength"
+#define NM_WIFI_P2P_PEER_LAST_SEEN            "last-seen"
+
+typedef struct {
+	NMDBusObject parent;
+	NMDevice *wifi_device;
+	CList peers_lst;
+	struct _NMWifiP2PPeerPrivate *_priv;
+} NMWifiP2PPeer;
+
+typedef struct _NMWifiP2PPeerClass NMWifiP2PPeerClass;
+
+GType nm_wifi_p2p_peer_get_type (void);
+
+NMWifiP2PPeer *   nm_wifi_p2p_peer_new_from_properties      (const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_update_from_properties   (NMWifiP2PPeer *peer,
+                                                             const char *supplicant_path,
+                                                             GVariant *properties);
+
+gboolean          nm_wifi_p2p_peer_check_compatible         (NMWifiP2PPeer *self,
+                                                             NMConnection *connection);
+
+const char *      nm_wifi_p2p_peer_get_supplicant_path      (NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_name                 (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_name                 (NMWifiP2PPeer *peer,
+                                                             const char *name);
+const char *      nm_wifi_p2p_peer_get_manufacturer         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_manufacturer         (NMWifiP2PPeer *peer,
+                                                             const char *manufacturer);
+const char *      nm_wifi_p2p_peer_get_model                (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model                (NMWifiP2PPeer *peer,
+                                                             const char *model);
+const char *      nm_wifi_p2p_peer_get_model_number         (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_model_number         (NMWifiP2PPeer *peer,
+                                                             const char *number);
+const char *      nm_wifi_p2p_peer_get_serial               (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_serial               (NMWifiP2PPeer *peer,
+                                                             const char *serial);
+
+GBytes *          nm_wifi_p2p_peer_get_wfd_ies             (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_wfd_ies             (NMWifiP2PPeer *peer,
+                                                            GBytes *bytes);
+
+const char *const*nm_wifi_p2p_peer_get_groups              (const NMWifiP2PPeer *peer);
+
+const char *      nm_wifi_p2p_peer_get_address              (const NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_address              (NMWifiP2PPeer *peer,
+                                                             const char *addr);
+gint8             nm_wifi_p2p_peer_get_strength             (NMWifiP2PPeer *peer);
+gboolean          nm_wifi_p2p_peer_set_strength             (NMWifiP2PPeer *peer,
+                                                             gint8 strength);
+NM80211ApFlags    nm_wifi_p2p_peer_get_flags                (const NMWifiP2PPeer *self);
+
+const char       *nm_wifi_p2p_peer_to_string                (const NMWifiP2PPeer *self,
+                                                             char *str_buf,
+                                                             gsize buf_len,
+                                                             gint32 now_s);
+
+const char      **nm_wifi_p2p_peers_get_paths               (const CList *peers_lst_head);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_first_compatible (const CList *peers_lst_head,
+                                                           NMConnection *connection);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peers_find_by_supplicant_path (const CList *peers_lst_head, const char *path);
+
+NMWifiP2PPeer    *nm_wifi_p2p_peer_lookup_for_device (NMDevice *device, const char *exported_path);
+
+#endif /* __NM_WIFI_P2P_PEER_H__ */
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 0f7836be..c6e8b3e0 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -22,7 +22,6 @@
 
 #include "nm-wifi-utils.h"
 
-#include <string.h>
 #include <stdlib.h>
 
 #include "nm-utils.h"
@@ -739,6 +738,12 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		 * setting.  Since there's so much configuration required for it, there's
 		 * no way it can be automatically completed.
 		 */
+	} else if (   (key_mgmt && !strcmp (key_mgmt, "sae"))
+	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) {
+		g_object_set (s_wsec,
+		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
+		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
+		              NULL);
 	} else if (   (key_mgmt && !strcmp (key_mgmt, "wpa-psk"))
 	           || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
 	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
index bb8f7c27..895853c4 100644
--- a/src/devices/wifi/tests/meson.build
+++ b/src/devices/wifi/tests/meson.build
@@ -3,11 +3,12 @@ test_unit = 'test-general'
 exe = executable(
   'wifi-' + test_unit,
   [test_unit + '.c'] + common_sources,
-  dependencies: test_nm_dep
+  dependencies: test_nm_dep,
 )
 
 test(
   'devices/wifi/' + test_unit,
   test_script,
-  args: test_args + [exe.full_path()]
+  args: test_args + [exe.full_path()],
+  timeout: default_test_timeout,
 )
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index f752bbfc..e0899837 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -20,10 +20,7 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
 #include "devices/wifi/nm-wifi-utils.h"
-
 #include "nm-core-internal.h"
 
 #include "nm-test-utils-core.h"