summary refs log tree commit diff
path: root/src/supplicant
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
committerMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
commit1e5977b62f896e844b548c3007ace9e1dfa7f9ed (patch)
tree7a7416ed410e72b6200f3d860fd315ec11cc106b /src/supplicant
parentb012fa6e1d808e0736c009799c62d835cbfcc1dd (diff)
New upstream version 1.23.90 upstream/1.23.90
Diffstat (limited to 'src/supplicant')
-rw-r--r--src/supplicant/nm-supplicant-config.c84
-rw-r--r--src/supplicant/nm-supplicant-config.h3
-rw-r--r--src/supplicant/nm-supplicant-interface.c4308
-rw-r--r--src/supplicant/nm-supplicant-interface.h156
-rw-r--r--src/supplicant/nm-supplicant-manager.c1525
-rw-r--r--src/supplicant/nm-supplicant-manager.h36
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c4
-rw-r--r--src/supplicant/nm-supplicant-types.h175
-rw-r--r--src/supplicant/tests/meson.build2
-rw-r--r--src/supplicant/tests/test-supplicant-config.c58
10 files changed, 3729 insertions, 2622 deletions
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index dec4556d..53b6d360 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -14,7 +14,7 @@
 
 #include "nm-supplicant-settings-verify.h"
 #include "nm-setting.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-setting-ip4-config.h"
@@ -30,13 +30,10 @@ typedef struct {
 typedef struct {
 	GHashTable *config;
 	GHashTable *blobs;
-	guint32    ap_scan;
-	gboolean   fast_required;
-	gboolean   dispose_has_run;
-	gboolean   support_pmf;
-	gboolean   support_fils;
-	gboolean   support_ft;
-	gboolean   support_sha384;
+	NMSupplCapMask capabilities;
+	guint32 ap_scan;
+	bool fast_required:1;
+	bool dispose_has_run:1;
 } NMSupplicantConfigPrivate;
 
 struct _NMSupplicantConfig {
@@ -54,9 +51,15 @@ G_DEFINE_TYPE (NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+static gboolean
+_get_capability (NMSupplicantConfigPrivate *priv,
+                 NMSupplCapType type)
+{
+	return NM_SUPPL_CAP_MASK_GET (priv->capabilities, type) == NM_TERNARY_TRUE;
+}
+
 NMSupplicantConfig *
-nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
-                          gboolean support_ft, gboolean support_sha384)
+nm_supplicant_config_new (NMSupplCapMask capabilities)
 {
 	NMSupplicantConfigPrivate *priv;
 	NMSupplicantConfig *self;
@@ -64,10 +67,7 @@ nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
 	self = g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL);
 	priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 
-	priv->support_pmf = support_pmf;
-	priv->support_fils = support_fils;
-	priv->support_ft = support_ft;
-	priv->support_sha384 = support_sha384;
+	priv->capabilities = capabilities;
 
 	return self;
 }
@@ -88,10 +88,6 @@ nm_supplicant_config_init (NMSupplicantConfig * self)
 	                                      g_free,
 	                                      (GDestroyNotify) config_option_free);
 
-	priv->blobs = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                     g_free,
-	                                     (GDestroyNotify) g_bytes_unref);
-
 	priv->ap_scan = 1;
 	priv->dispose_has_run = FALSE;
 }
@@ -224,6 +220,11 @@ nm_supplicant_config_add_blob (NMSupplicantConfig *self,
 	nm_log_info (LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, opt->value);
 
 	g_hash_table_insert (priv->config, g_strdup (key), opt);
+	if (!priv->blobs) {
+		priv->blobs = g_hash_table_new_full (nm_str_hash, g_str_equal,
+		                                     g_free,
+		                                     (GDestroyNotify) g_bytes_unref);
+	}
 	g_hash_table_insert (priv->blobs,
 	                     g_strdup (blobid),
 	                     g_bytes_ref (value));
@@ -256,10 +257,10 @@ nm_supplicant_config_add_blob_for_connection (NMSupplicantConfig *self,
 static void
 nm_supplicant_config_finalize (GObject *object)
 {
-	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE ((NMSupplicantConfig *) object);
+	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (object);
 
 	g_hash_table_destroy (priv->config);
-	g_hash_table_destroy (priv->blobs);
+	nm_clear_pointer (&priv->blobs, g_hash_table_destroy);
 
 	G_OBJECT_CLASS (nm_supplicant_config_parent_class)->finalize (object);
 }
@@ -768,7 +769,7 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	g_return_val_if_fail (!error || !*error, FALSE);
 
 	/* Check if we actually support FILS */
-	if (!priv->support_fils) {
+	if (!_get_capability (priv, NM_SUPPL_CAP_TYPE_FILS)) {
 		if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED) {
 			g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			                     "Supplicant does not support FILS");
@@ -780,36 +781,40 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting);
 	key_mgmt_conf = g_string_new (key_mgmt);
 	if (nm_streq (key_mgmt, "wpa-psk")) {
-		if (priv->support_pmf)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 			g_string_append (key_mgmt_conf, " wpa-psk-sha256");
-		if (priv->support_ft)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 			g_string_append (key_mgmt_conf, " ft-psk");
 	} else if (nm_streq (key_mgmt, "wpa-eap")) {
-		if (priv->support_pmf)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 			g_string_append (key_mgmt_conf, " wpa-eap-sha256");
-		if (priv->support_ft)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 			g_string_append (key_mgmt_conf, " ft-eap");
-		if (priv->support_ft && priv->support_sha384)
+		if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_FT)
+		    && _get_capability (priv, NM_SUPPL_CAP_TYPE_SHA384))
 			g_string_append (key_mgmt_conf, " ft-eap-sha384");
 		switch (fils) {
 		case NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED:
 			g_string_truncate (key_mgmt_conf, 0);
-			if (!priv->support_pmf)
+			if (!_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 				g_string_assign (key_mgmt_conf, "fils-sha256 fils-sha384");
 			/* fall-through */
 		case NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL:
-			if (priv->support_pmf)
+			if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 				g_string_append (key_mgmt_conf, " fils-sha256 fils-sha384");
-			if (priv->support_pmf && priv->support_ft)
+			if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_PMF)
+			    && _get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 				g_string_append (key_mgmt_conf, " ft-fils-sha256");
-			if (priv->support_pmf && priv->support_ft & priv->support_sha384)
+			if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_PMF)
+			    && _get_capability (priv, NM_SUPPL_CAP_TYPE_FT)
+			    && _get_capability (priv, NM_SUPPL_CAP_TYPE_SHA384))
 				g_string_append (key_mgmt_conf, " ft-fils-sha384");
 			break;
 		default:
 			break;
 		}
 	} else if (nm_streq (key_mgmt, "sae")) {
-		if (priv->support_ft)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 			g_string_append (key_mgmt_conf, " ft-sae");
 	}
 
@@ -867,13 +872,13 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		}
 	}
 
-	/* Don't try to enable PMF on non-WPA/SAE networks */
-	if (!NM_IN_STRSET (key_mgmt, "wpa-eap", "wpa-psk", "sae"))
+	/* Don't try to enable PMF on non-WPA/SAE/OWE networks */
+	if (!NM_IN_STRSET (key_mgmt, "wpa-eap", "wpa-psk", "sae", "owe"))
 		pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
 
 	/* Check if we actually support PMF */
 	set_pmf = TRUE;
-	if (!priv->support_pmf) {
+	if (!_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF)) {
 		if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
 			g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			                     "Supplicant does not support PMF");
@@ -885,7 +890,8 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	/* Only WPA-specific things when using WPA */
 	if (   !strcmp (key_mgmt, "wpa-psk")
 	    || !strcmp (key_mgmt, "wpa-eap")
-	    || !strcmp (key_mgmt, "sae")) {
+	    || !strcmp (key_mgmt, "sae")
+	    || !strcmp (key_mgmt, "owe")) {
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, proto, protos, "proto", ' ', TRUE, NULL, error))
 			return FALSE;
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, pairwise, pairwise, "pairwise", ' ', TRUE, NULL, error))
@@ -1323,6 +1329,14 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
 	if (!add_string_val (self, value, "domain_suffix_match2", FALSE, NULL, error))
 		return FALSE;
 
+	/* domain match */
+	value = nm_setting_802_1x_get_domain_match (setting);
+	if (!add_string_val (self, value, "domain_match", FALSE, NULL, error))
+		return FALSE;
+	value = nm_setting_802_1x_get_phase2_domain_match (setting);
+	if (!add_string_val (self, value, "domain_match2", FALSE, NULL, error))
+		return FALSE;
+
 	/* Private key */
 	added = FALSE;
 	switch (nm_setting_802_1x_get_private_key_scheme (setting)) {
diff --git a/src/supplicant/nm-supplicant-config.h b/src/supplicant/nm-supplicant-config.h
index 361f9ac6..e1da9a1e 100644
--- a/src/supplicant/nm-supplicant-config.h
+++ b/src/supplicant/nm-supplicant-config.h
@@ -25,8 +25,7 @@ typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
 
 GType nm_supplicant_config_get_type (void);
 
-NMSupplicantConfig *nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
-                                              gboolean support_ft, gboolean support_sha384);
+NMSupplicantConfig *nm_supplicant_config_new (NMSupplCapMask capabilities);
 
 guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self);
 
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index c19da11c..cc3d109e 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -7,49 +7,35 @@
 #include "nm-default.h"
 
 #include "nm-supplicant-interface.h"
-#include "nm-supplicant-manager.h"
 
 #include <stdio.h>
 
 #include "NetworkManagerUtils.h"
-#include "nm-supplicant-config.h"
 #include "nm-core-internal.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-ref-string.h"
 #include "nm-std-aux/nm-dbus-compat.h"
+#include "nm-supplicant-config.h"
+#include "nm-supplicant-manager.h"
+#include "shared/nm-glib-aux/nm-dbus-aux.h"
 
-#define WPAS_DBUS_IFACE_INTERFACE             WPAS_DBUS_INTERFACE ".Interface"
-#define WPAS_DBUS_IFACE_INTERFACE_WPS         WPAS_DBUS_INTERFACE ".Interface.WPS"
-#define WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE  WPAS_DBUS_INTERFACE ".Interface.P2PDevice"
-#define WPAS_DBUS_IFACE_BSS                   WPAS_DBUS_INTERFACE ".BSS"
-#define WPAS_DBUS_IFACE_PEER                  WPAS_DBUS_INTERFACE ".Peer"
-#define WPAS_DBUS_IFACE_GROUP                 WPAS_DBUS_INTERFACE ".Group"
-#define WPAS_DBUS_IFACE_NETWORK               WPAS_DBUS_INTERFACE ".Network"
-#define WPAS_ERROR_INVALID_IFACE              WPAS_DBUS_INTERFACE ".InvalidInterface"
-#define WPAS_ERROR_EXISTS_ERROR               WPAS_DBUS_INTERFACE ".InterfaceExists"
+#define DBUS_TIMEOUT_MSEC 20000
 
 /*****************************************************************************/
 
 typedef struct {
-	GDBusProxy *proxy;
-	gulong change_id;
-} BssData;
-
-typedef struct {
-	GDBusProxy *proxy;
-	gulong change_id;
-} PeerData;
-
-struct _AddNetworkData;
-
-typedef struct {
 	NMSupplicantInterface *self;
 	char *type;
 	char *bssid;
 	char *pin;
-	GDBusProxy *proxy;
+	guint signal_id;
 	GCancellable *cancellable;
-	bool is_cancelling;
+	bool needs_cancelling:1;
+	bool is_cancelling:1;
 } WpsData;
 
+struct _AddNetworkData;
+
 typedef struct {
 	NMSupplicantInterface *self;
 	NMSupplicantConfig *cfg;
@@ -64,34 +50,27 @@ typedef struct {
 typedef struct _AddNetworkData {
 	/* the assoc_data at the time when doing the call. */
 	AssocData *assoc_data;
+	NMRefString *name_owner;
+	NMRefString *object_path;
+	GObject *shutdown_wait_obj;
 } AddNetworkData;
 
-typedef struct {
-	NMSupplicantInterface *self;
-	NMSupplicantInterfaceDisconnectCb callback;
-	gpointer user_data;
-} DisconnectData;
-
 enum {
 	STATE,                   /* change in the interface's state */
-	REMOVED,                 /* interface was removed by the supplicant */
-	BSS_UPDATED,             /* a new BSS appeared or an existing had properties changed */
-	BSS_REMOVED,             /* supplicant removed BSS from its scan list */
-	PEER_UPDATED,            /* a new Peer appeared or an existing had properties changed */
-	PEER_REMOVED,            /* supplicant removed Peer from its scan list */
-	SCAN_DONE,               /* wifi scan is complete */
-	CREDENTIALS_REQUEST,     /* 802.1x identity or password requested */
+	BSS_CHANGED,             /* a new BSS appeared, was updated, or was removed. */
+	PEER_CHANGED,            /* a new Peer appeared, was updated, or was removed */
 	WPS_CREDENTIALS,         /* WPS credentials received */
 	GROUP_STARTED,           /* a new Group (interface) was created */
 	GROUP_FINISHED,          /* a Group (interface) has been finished */
-	GROUP_FORMATION_FAILURE, /* P2P Group formation failed */
 	LAST_SIGNAL
 };
+
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
-	PROP_IFACE,
-	PROP_OBJECT_PATH,
+	PROP_SUPPLICANT_MANAGER,
+	PROP_DBUS_OBJECT_PATH,
+	PROP_IFINDEX,
 	PROP_P2P_GROUP_JOINED,
 	PROP_P2P_GROUP_PATH,
 	PROP_P2P_GROUP_OWNER,
@@ -99,76 +78,83 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_CURRENT_BSS,
 	PROP_DRIVER,
 	PROP_P2P_AVAILABLE,
-	PROP_FAST_SUPPORT,
-	PROP_AP_SUPPORT,
-	PROP_PMF_SUPPORT,
-	PROP_FILS_SUPPORT,
-	PROP_P2P_SUPPORT,
-	PROP_MESH_SUPPORT,
-	PROP_WFD_SUPPORT,
-	PROP_FT_SUPPORT,
-	PROP_SHA384_SUPPORT,
 	PROP_AUTH_STATE,
 );
 
-typedef struct {
-	char *         dev;
-	NMSupplicantDriver driver;
-	gboolean       has_credreq;  /* Whether querying 802.1x credentials is supported */
-	NMSupplicantFeature fast_support;
-	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
-	NMSupplicantFeature pmf_support;
-	NMSupplicantFeature fils_support;
-	NMSupplicantFeature p2p_support;
-	NMSupplicantFeature mesh_support;
-	NMSupplicantFeature wfd_support;
-	NMSupplicantFeature ft_support_global;
-	NMSupplicantFeature ft_support_per_iface;
-	NMSupplicantFeature sha384_support;
-	guint32        max_scan_ssids;
-	guint32        ready_count;
+typedef struct _NMSupplicantInterfacePrivate {
 
-	char *         object_path;
-	NMSupplicantInterfaceState state;
-	int            disconnect_reason;
+	NMSupplicantManager *supplicant_manager;
 
-	bool           scanning:1;
+	GDBusConnection *dbus_connection;
+	NMRefString *name_owner;
+	NMRefString *object_path;
 
-	bool           scan_done_pending:1;
-	bool           scan_done_success:1;
+	char          *ifname;
 
-	GDBusProxy *   wpas_proxy;
-	GCancellable * init_cancellable;
-	GDBusProxy *   iface_proxy;
-	GCancellable * other_cancellable;
-	GDBusProxy *   p2p_proxy;
-	GDBusProxy *   group_proxy;
+	GCancellable  *main_cancellable;
 
-	gboolean       p2p_proxy_acquired;
-	gboolean       group_proxy_acquired;
-	gboolean       p2p_capable;
+	NMRefString   *p2p_group_path;
 
-	gboolean       p2p_group_owner;
+	GCancellable  *p2p_group_properties_cancellable;
 
-	WpsData *wps_data;
+	WpsData       *wps_data;
+
+	AssocData     *assoc_data;
+
+	char          *net_path;
 
-	AssocData *    assoc_data;
+	char          *driver;
 
-	char *         net_path;
-	GHashTable *   bss_proxies;
-	char *         current_bss;
+	GHashTable    *bss_idx;
+	CList          bss_lst_head;
+	CList          bss_initializing_lst_head;
 
-	GHashTable *   peer_proxies;
+	NMRefString   *current_bss;
 
-	gint64         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_ms() */
+	GHashTable    *peer_idx;
+	CList          peer_lst_head;
+	CList          peer_initializing_lst_head;
+
+	gint64         last_scan_msec;
 
 	NMSupplicantAuthState auth_state;
-} NMSupplicantInterfacePrivate;
 
-struct _NMSupplicantInterface {
-	GObject parent;
-	NMSupplicantInterfacePrivate _priv;
-};
+	NMSupplicantDriver requested_driver;
+	NMSupplCapMask global_capabilities;
+	NMSupplCapMask iface_capabilities;
+
+	guint          properties_changed_id;
+	guint          signal_id;
+	guint          bss_properties_changed_id;
+	guint          peer_properties_changed_id;
+	guint          p2p_group_properties_changed_id;
+
+	int            ifindex;
+
+	int            starting_pending_count;
+
+	guint32        max_scan_ssids;
+
+	gint32         disconnect_reason;
+
+	NMSupplicantInterfaceState state;
+	NMSupplicantInterfaceState supp_state;
+
+	bool           scanning_property:1;
+	bool           scanning_cached:1;
+
+	bool           p2p_capable_property:1;
+	bool           p2p_capable_cached:1;
+
+	bool           p2p_group_owner_property:1;
+	bool           p2p_group_owner_cached:1;
+
+	bool           p2p_group_joined_cached:1;
+
+	bool           is_ready_main:1;
+	bool           is_ready_p2p_device:1;
+
+} NMSupplicantInterfacePrivate;
 
 struct _NMSupplicantInterfaceClass {
 	GObjectClass parent;
@@ -176,420 +162,1003 @@ struct _NMSupplicantInterfaceClass {
 
 G_DEFINE_TYPE (NMSupplicantInterface, nm_supplicant_interface, G_TYPE_OBJECT)
 
-#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE)
+#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE)
 
 /*****************************************************************************/
 
+static const char *
+_log_pretty_object_path (NMSupplicantInterfacePrivate *priv)
+{
+	const char *s;
+
+	nm_assert (priv);
+	nm_assert (NM_IS_REF_STRING (priv->object_path));
+
+	s = priv->object_path->str;
+	if (NM_STR_HAS_PREFIX (s, "/fi/w1/wpa_supplicant1/Interfaces/")) {
+		s += NM_STRLEN ("/fi/w1/wpa_supplicant1/Interfaces/");
+		if (   s[0]
+		    && s[0] != '/')
+			return s;
+	}
+	return priv->object_path->str;
+}
+
 #define _NMLOG_DOMAIN           LOGD_SUPPLICANT
 #define _NMLOG_PREFIX_NAME      "sup-iface"
 #define _NMLOG(level, ...) \
     G_STMT_START { \
-         char _sbuf[64]; \
-         const char *__ifname = self ?  NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev : NULL; \
+         NMSupplicantInterface *_self = (self); \
+         NMSupplicantInterfacePrivate *_priv = _self ? NM_SUPPLICANT_INTERFACE_GET_PRIVATE (_self) : NULL; \
+         char _sbuf[255]; \
+         const char *_ifname = _priv ? _priv->ifname : NULL; \
          \
-         nm_log ((level), _NMLOG_DOMAIN, __ifname, NULL, \
+         nm_log ((level), _NMLOG_DOMAIN, _ifname, NULL, \
                  "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
                  _NMLOG_PREFIX_NAME, \
-                 ((self) ? nm_sprintf_buf (_sbuf, "[%p,%s]", (self), __ifname) : "") \
+                 (  _self \
+                  ? nm_sprintf_buf (_sbuf, \
+                                    "["NM_HASH_OBFUSCATE_PTR_FMT",%s,%s]", \
+                                    NM_HASH_OBFUSCATE_PTR (_self), \
+                                    _log_pretty_object_path (_priv), \
+                                    _ifname ?: "???") \
+                  : "") \
                  _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
     } G_STMT_END
 
 /*****************************************************************************/
 
-static void scan_done_emit_signal (NMSupplicantInterface *self);
+static void _starting_check_ready (NMSupplicantInterface *self);
+
+static void assoc_return (NMSupplicantInterface *self,
+                          GError *error,
+                          const char *message);
 
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE (nm_supplicant_interface_state_to_string, NMSupplicantInterfaceState,
-	NM_UTILS_LOOKUP_DEFAULT_WARN ("unknown"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INVALID,         "invalid"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INIT,            "init"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_STARTING,        "starting"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_READY,           "ready"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DISABLED,        "disabled"),
+	NM_UTILS_LOOKUP_DEFAULT_WARN ("internal-unknown"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INVALID,         "internal-invalid"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_STARTING,        "internal-starting"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,  "4way_handshake"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,      "associated"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,     "associating"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING,  "authenticating"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_COMPLETED,       "completed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED,    "disconnected"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, "group_handshake"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INACTIVE,        "inactive"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DISABLED,        "interface_disabled"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_SCANNING,        "scanning"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING,  "authenticating"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,     "associating"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,      "associated"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,  "4-way handshake"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, "group handshake"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_COMPLETED,       "completed"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DOWN,            "down"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DOWN,            "internal-down"),
+);
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	wpas_state_string_to_enum,
+	NMSupplicantInterfaceState,
+	{ nm_assert (name); },
+	{ return NM_SUPPLICANT_INTERFACE_STATE_INVALID; },
+	{ "4way_handshake",     NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE  },
+	{ "associated",         NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED      },
+	{ "associating",        NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING     },
+	{ "authenticating",     NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING  },
+	{ "completed",          NM_SUPPLICANT_INTERFACE_STATE_COMPLETED       },
+	{ "disconnected",       NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED    },
+	{ "group_handshake",    NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE },
+	{ "inactive",           NM_SUPPLICANT_INTERFACE_STATE_INACTIVE        },
+	{ "interface_disabled", NM_SUPPLICANT_INTERFACE_STATE_DISABLED        },
+	{ "scanning",           NM_SUPPLICANT_INTERFACE_STATE_SCANNING        },
 );
 
 /*****************************************************************************/
 
+static NM80211ApSecurityFlags
+security_from_vardict (GVariant *security)
+{
+	NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE;
+	const char **array;
+	const char *tmp;
+
+	nm_assert (g_variant_is_of_type (security, G_VARIANT_TYPE_VARDICT));
+
+	if (g_variant_lookup (security, "KeyMgmt", "^a&s", &array)) {
+		if (g_strv_contains (array, "wpa-psk") ||
+		    g_strv_contains (array, "wpa-ft-psk"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK;
+		if (g_strv_contains (array, "wpa-eap") ||
+		    g_strv_contains (array, "wpa-ft-eap") ||
+		    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;
+		if (g_strv_contains (array, "owe"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE;
+		g_free (array);
+	}
+
+	if (g_variant_lookup (security, "Pairwise", "^a&s", &array)) {
+		if (g_strv_contains (array, "tkip"))
+			flags |= NM_802_11_AP_SEC_PAIR_TKIP;
+		if (g_strv_contains (array, "ccmp"))
+			flags |= NM_802_11_AP_SEC_PAIR_CCMP;
+		g_free (array);
+	}
+
+	if (g_variant_lookup (security, "Group", "&s", &tmp)) {
+		if (nm_streq (tmp, "wep40"))
+			flags |= NM_802_11_AP_SEC_GROUP_WEP40;
+		else if (nm_streq (tmp, "wep104"))
+			flags |= NM_802_11_AP_SEC_GROUP_WEP104;
+		else if (nm_streq (tmp, "tkip"))
+			flags |= NM_802_11_AP_SEC_GROUP_TKIP;
+		else if (nm_streq (tmp, "ccmp"))
+			flags |= NM_802_11_AP_SEC_GROUP_CCMP;
+	}
+
+	return flags;
+}
+
+/*****************************************************************************/
+
+/* Various conditions prevent _starting_check_ready() from completing. For example,
+ * bss_initializing_lst_head, peer_initializing_lst_head and p2p_group_properties_cancellable.
+ * At some places, these conditions might toggle, and it would seems we would have
+ * to call _starting_check_ready() at that point, to ensure we don't miss a state
+ * change that we are ready. However, these places are deep in the call stack and
+ * not suitable to perform this state change. Instead, the callers *MUST* have
+ * added their own starting_pending_count to delay _starting_check_ready().
+ *
+ * Assert that is the case. */
+#define nm_assert_starting_has_pending_count(v) nm_assert ((v) > 0)
+
+/*****************************************************************************/
+
 static void
-bss_data_destroy (gpointer user_data)
+_dbus_connection_call (NMSupplicantInterface *self,
+                       const char *interface_name,
+                       const char *method_name,
+                       GVariant *parameters,
+                       const GVariantType *reply_type,
+                       GDBusCallFlags flags,
+                       int timeout_msec,
+                       GCancellable *cancellable,
+                       GAsyncReadyCallback callback,
+                       gpointer user_data)
 {
-	BssData *bss_data = user_data;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	nm_clear_g_signal_handler (bss_data->proxy, &bss_data->change_id);
-	g_object_unref (bss_data->proxy);
-	g_slice_free (BssData, bss_data);
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner->str,
+	                        priv->object_path->str,
+	                        interface_name,
+	                        method_name,
+	                        parameters,
+	                        reply_type,
+	                        flags,
+	                        timeout_msec,
+	                        cancellable,
+	                        callback,
+	                        user_data);
 }
 
 static void
-bss_proxy_properties_changed_cb (GDBusProxy *proxy,
-                                 GVariant *changed_properties,
-                                 char **invalidated_properties,
+_dbus_connection_call_simple_cb (GObject *source,
+                                 GAsyncResult *result,
                                  gpointer user_data)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+	const char *log_reason;
+	gs_free char *remote_error = NULL;
+
+	nm_utils_user_data_unpack (user_data, &self, &log_reason);
+
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (res) {
+		_LOGT ("call-%s: success", log_reason);
+		return;
+	}
 
-	if (priv->scanning)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+	remote_error = g_dbus_error_get_remote_error (error);
+	if (!nm_streq0 (remote_error, "fi.w1.wpa_supplicant1.NotConnected")) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("call-%s: failed with %s", log_reason, error->message);
+		return;
+	}
 
-	g_signal_emit (self, signals[BSS_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               changed_properties);
+	_LOGT ("call-%s: failed with %s", log_reason, error->message);
 }
 
-static GVariant *
-bss_proxy_get_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
+static void
+_dbus_connection_call_simple (NMSupplicantInterface *self,
+                              const char *interface_name,
+                              const char *method_name,
+                              GVariant *parameters,
+                              const GVariantType *reply_type,
+                              const char *log_reason)
 {
-	gs_strfreev char **properties = NULL;
-	GVariantBuilder builder;
-	char **iter;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
+	_dbus_connection_call (self,
+	                       interface_name,
+	                       method_name,
+	                       parameters,
+	                       reply_type,
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       priv->main_cancellable,
+	                       _dbus_connection_call_simple_cb,
+	                       nm_utils_user_data_pack (self, log_reason));
+}
 
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-	if (iter) {
-		while (*iter) {
-			GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);
+/*****************************************************************************/
 
-			g_variant_builder_add (&builder, "{sv}", *iter++, copy);
-			g_variant_unref (copy);
-		}
-	}
-	return g_variant_builder_end (&builder);
+static void
+_emit_signal_state (NMSupplicantInterface *self,
+                    NMSupplicantInterfaceState new_state,
+                    NMSupplicantInterfaceState old_state,
+                    gint32 disconnect_reason)
+{
+	g_signal_emit (self,
+	               signals[STATE],
+	               0,
+	               (int) new_state,
+	               (int) old_state,
+	               (int) disconnect_reason);
 }
 
+/*****************************************************************************/
+
 static void
-bss_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+_remove_network (NMSupplicantInterface *self)
 {
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	GVariant *props = NULL;
-	const char *object_path;
-	BssData *bss_data;
-	gboolean success;
-
-	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
-	if (   !success
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gs_free char *net_path = NULL;
+
+	if (!priv->net_path)
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	net_path = g_steal_pointer (&priv->net_path);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE,
+	                              "RemoveNetwork",
+	                              g_variant_new ("(o)", net_path),
+	                              G_VARIANT_TYPE ("()"),
+	                              "remove-network");
+}
 
-	if (!success) {
-		_LOGD ("failed to acquire BSS proxy: (%s)", error->message);
-		g_hash_table_remove (priv->bss_proxies,
-		                     g_dbus_proxy_get_object_path (proxy));
-		return;
-	}
+/*****************************************************************************/
+
+static void
+_notify_maybe_scanning (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gboolean scanning;
+
+	scanning =   nm_supplicant_interface_state_is_operational (priv->state)
+	          && (   priv->scanning_property
+	              || priv->supp_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING);
 
-	object_path = g_dbus_proxy_get_object_path (proxy);
-	bss_data = g_hash_table_lookup (priv->bss_proxies, object_path);
-	if (!bss_data)
+	if (priv->scanning_cached == scanning)
 		return;
 
-	bss_data->change_id = g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (bss_proxy_properties_changed_cb), self);
+	if (   !scanning
+	    && !c_list_is_empty (&priv->bss_initializing_lst_head)) {
+		/* we would change state to indicate we no longer scan. However,
+		 * we still have BSS instances to be initialized. Delay the
+		 * state change further. */
+		return;
+	}
 
-	props = bss_proxy_get_properties (self, proxy);
-	g_signal_emit (self, signals[BSS_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               g_variant_ref_sink (props));
-	g_variant_unref (props);
+	_LOGT ("scanning: %s", scanning ? "yes" : "no");
 
-	if (priv->scan_done_pending)
-		scan_done_emit_signal (self);
+	if (!scanning)
+		priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec ();
+	else {
+		/* while we are scanning, we set the timestamp to -1. */
+		priv->last_scan_msec = -1;
+	}
+	priv->scanning_cached = scanning;
+	_notify (self, PROP_SCANNING);
 }
 
 static void
-bss_add_new (NMSupplicantInterface *self, const char *object_path)
+_notify_maybe_p2p_available (NMSupplicantInterface *self)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	GDBusProxy *bss_proxy;
-	BssData *bss_data;
+	gboolean value;
 
-	g_return_if_fail (object_path != NULL);
+	value =    priv->is_ready_p2p_device
+	        && priv->p2p_capable_property;
 
-	if (g_hash_table_lookup (priv->bss_proxies, object_path))
+	if (priv->p2p_capable_cached == value)
 		return;
 
-	bss_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-	                          "g-bus-type", G_BUS_TYPE_SYSTEM,
-	                          "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-	                          "g-name", WPAS_DBUS_SERVICE,
-	                          "g-object-path", object_path,
-	                          "g-interface-name", WPAS_DBUS_IFACE_BSS,
-	                          NULL);
-	bss_data = g_slice_new0 (BssData);
-	bss_data->proxy = bss_proxy;
-	g_hash_table_insert (priv->bss_proxies,
-	                     (char *) g_dbus_proxy_get_object_path (bss_proxy),
-	                     bss_data);
-	g_async_initable_init_async (G_ASYNC_INITABLE (bss_proxy),
-	                             G_PRIORITY_DEFAULT,
-	                             priv->other_cancellable,
-	                             (GAsyncReadyCallback) bss_proxy_acquired_cb,
-	                             self);
+	priv->p2p_capable_cached = value;
+	_notify (self, PROP_P2P_AVAILABLE);
 }
 
 static void
-peer_data_destroy (gpointer user_data)
+_notify_maybe_p2p_group (NMSupplicantInterface *self)
 {
-	PeerData *peer_data = user_data;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gboolean value_joined;
+	gboolean value_owner;
+	gboolean joined_changed;
+	gboolean owner_changed;
+
+	value_joined =    priv->p2p_group_path
+	               && !priv->p2p_group_properties_cancellable;
+	value_owner =    value_joined
+	              && priv->p2p_group_owner_property;
+
+	if ((joined_changed = (priv->p2p_group_joined_cached != value_joined)))
+		priv->p2p_group_joined_cached = value_joined;
 
-	nm_clear_g_signal_handler (peer_data->proxy, &peer_data->change_id);
-	g_object_unref (peer_data->proxy);
-	g_slice_free (PeerData, peer_data);
+	if ((owner_changed = (priv->p2p_group_owner_cached != value_owner)))
+		priv->p2p_group_owner_cached = value_owner;
+
+	if (joined_changed)
+		_notify (self, PROP_P2P_GROUP_JOINED);
+	if (owner_changed)
+		_notify (self, PROP_P2P_GROUP_OWNER);
 }
 
+/*****************************************************************************/
+
 static void
-peer_proxy_properties_changed_cb (GDBusProxy *proxy,
-                                  GVariant *changed_properties,
-                                  char **invalidated_properties,
-                                  gpointer user_data)
+_bss_info_destroy (NMSupplicantBssInfo *bss_info)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	g_signal_emit (self, signals[PEER_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               changed_properties);
+	c_list_unlink_stale (&bss_info->_bss_lst);
+	nm_clear_g_cancellable (&bss_info->_init_cancellable);
+	g_bytes_unref (bss_info->ssid);
+	nm_ref_string_unref (bss_info->bss_path);
+	nm_g_slice_free (bss_info);
 }
 
-static GVariant *
-peer_proxy_get_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
+static void
+_bss_info_changed_emit (NMSupplicantInterface *self,
+                        NMSupplicantBssInfo *bss_info,
+                        gboolean is_present)
 {
-	gs_strfreev char **properties = NULL;
-	GVariantBuilder builder;
-	char **iter;
+	_LOGT ("BSS %s %s",
+	       bss_info->bss_path->str,
+	       is_present ? "updated" : "deleted");
+	g_signal_emit (self,
+	               signals[BSS_CHANGED],
+	               0,
+	               bss_info,
+	               is_present);
+}
 
-	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
+static void
+_bss_info_properties_changed (NMSupplicantInterface *self,
+                              NMSupplicantBssInfo *bss_info,
+                              GVariant *properties,
+                              gboolean initial)
+{
+	gboolean v_b;
+	GVariant *v_v;
+	const char *v_s;
+	gint16 v_i16;
+	guint16 v_u16;
+	guint32 v_u32;
+	NM80211ApFlags p_ap_flags;
+	NM80211Mode p_mode;
+	guint8 p_signal_percent;
+	const guint8 *arr_data;
+	gsize arr_len;
+	guint32 p_max_rate;
+	gboolean p_max_rate_has;
+	gint64 now_msec = 0;
+
+	if (nm_g_variant_lookup (properties, "Age", "u", &v_u32)) {
+		bss_info->last_seen_msec =   nm_utils_get_monotonic_timestamp_msec_cached (&now_msec)
+		                           - (((gint64) v_u32) * 1000);
+	} else if (initial) {
+		/* Unknown Age. Assume we just received it. */
+		bss_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
+	}
+
+	p_ap_flags = bss_info->ap_flags;
+	if (nm_g_variant_lookup (properties, "Privacy", "b", &v_b))
+		p_ap_flags = NM_FLAGS_ASSIGN (p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY, v_b);
+	else {
+		nm_assert (  !initial
+		           || !NM_FLAGS_HAS (p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY));
+	}
+	v_v = nm_g_variant_lookup_value (properties, "WPS", G_VARIANT_TYPE_VARDICT);
+	if (   v_v
+	    || initial) {
+		NM80211ApFlags f = NM_802_11_AP_FLAGS_NONE;
+
+		if (v_v) {
+			if (g_variant_lookup (v_v, "Type", "&s", &v_s)) {
+				p_ap_flags = NM_802_11_AP_FLAGS_WPS;
+				if (nm_streq (v_s, "pcb"))
+					f |= NM_802_11_AP_FLAGS_WPS_PBC;
+				else if (nm_streq (v_s, "pin"))
+					f |= NM_802_11_AP_FLAGS_WPS_PIN;
+			}
+			g_variant_unref (v_v);
+		}
+		p_ap_flags = NM_FLAGS_ASSIGN_MASK (p_ap_flags,
+		                                     NM_802_11_AP_FLAGS_WPS
+		                                   | NM_802_11_AP_FLAGS_WPS_PBC
+		                                   | NM_802_11_AP_FLAGS_WPS_PIN,
+		                                   f);
+	}
+	if (bss_info->ap_flags != p_ap_flags) {
+		bss_info->ap_flags = p_ap_flags;
+		nm_assert (bss_info->ap_flags == p_ap_flags);
+	}
+
+	if (nm_g_variant_lookup (properties, "Mode", "&s", &v_s)) {
+		if (nm_streq (v_s, "infrastructure"))
+			p_mode = NM_802_11_MODE_INFRA;
+		else if (nm_streq (v_s, "ad-hoc"))
+			p_mode = NM_802_11_MODE_ADHOC;
+		else if (nm_streq (v_s, "mesh"))
+			p_mode = NM_802_11_MODE_MESH;
+		else
+			p_mode = NM_802_11_MODE_UNKNOWN;
+	} else if (initial)
+		p_mode = NM_802_11_MODE_UNKNOWN;
+	else
+		p_mode = bss_info->mode;
+	if (bss_info->mode != p_mode) {
+		bss_info->mode = p_mode;
+		nm_assert (bss_info->mode == p_mode);
+	}
+
+	if (nm_g_variant_lookup (properties, "Signal", "n", &v_i16))
+		p_signal_percent = nm_wifi_utils_level_to_quality (v_i16);
+	else if (initial)
+		p_signal_percent = 0;
+	else
+		p_signal_percent = bss_info->signal_percent;
+	bss_info->signal_percent = p_signal_percent;
+
+	if (nm_g_variant_lookup (properties, "Frequency", "q", &v_u16))
+		bss_info->frequency = v_u16;
+
+	v_v = nm_g_variant_lookup_value (properties, "SSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		arr_len = MIN (32, arr_len);
+
+		/* Stupid ieee80211 layer uses <hidden> */
+		if (   arr_data
+		    && arr_len
+		    && !(   NM_IN_SET (arr_len, 8, 9)
+		         && memcmp (arr_data, "<hidden>", arr_len) == 0)
+		    && !nm_utils_is_empty_ssid (arr_data, arr_len)) {
+			/* good */
+		} else
+			arr_len = 0;
 
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-	if (iter) {
-		while (*iter) {
-			GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);
+		if (!nm_utils_gbytes_equal_mem (bss_info->ssid, arr_data, arr_len)) {
+			_nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer (&bss_info->ssid);
 
-			g_variant_builder_add (&builder, "{sv}", *iter++, copy);
-			g_variant_unref (copy);
+			bss_info->ssid =   (arr_len == 0)
+			                 ? NULL
+			                 : g_bytes_new (arr_data, arr_len);
 		}
+
+		g_variant_unref (v_v);
+	} else {
+		nm_assert (   !initial
+		           || !bss_info->ssid);
+	}
+
+	v_v = nm_g_variant_lookup_value (properties, "BSSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		if (   arr_len == ETH_ALEN
+		    && memcmp (arr_data, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (arr_data, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0) {
+			/* pass */
+		} else
+			arr_len = 0;
+
+		if (arr_len != 0) {
+			nm_assert (arr_len == sizeof (bss_info->bssid));
+			bss_info->bssid_valid = TRUE;
+			memcpy (bss_info->bssid, arr_data, sizeof (bss_info->bssid));
+		} else if (bss_info->bssid_valid) {
+			bss_info->bssid_valid = FALSE;
+			memset (bss_info->bssid, 0, sizeof (bss_info->bssid));
+		}
+		g_variant_unref (v_v);
+	} else {
+		nm_assert (   !initial
+		           || !bss_info->bssid_valid);
+	}
+	nm_assert (   (   bss_info->bssid_valid
+	               && !nm_utils_memeqzero (bss_info->bssid, sizeof (bss_info->bssid)))
+	           || (  !bss_info->bssid_valid
+	               && nm_utils_memeqzero (bss_info->bssid, sizeof (bss_info->bssid))));
+
+	p_max_rate_has = FALSE;
+	p_max_rate = 0;
+	v_v = nm_g_variant_lookup_value (properties, "Rates", G_VARIANT_TYPE ("au"));
+	if (v_v) {
+		const guint32 *rates = g_variant_get_fixed_array (v_v, &arr_len, sizeof (guint32));
+		gsize i;
+
+		for (i = 0; i < arr_len; i++)
+			p_max_rate = NM_MAX (p_max_rate, rates[i]);
+		p_max_rate_has = TRUE;
+		g_variant_unref (v_v);
+	}
+	v_v = nm_g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		gboolean p_owe_transition_mode;
+		gboolean p_metered;
+		guint32 rate;
+
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		nm_wifi_utils_parse_ies (arr_data, arr_len, &rate, &p_metered, &p_owe_transition_mode);
+		p_max_rate = NM_MAX (p_max_rate, rate);
+		p_max_rate_has = TRUE;
+		g_variant_unref (v_v);
+
+		if (p_owe_transition_mode)
+			bss_info->rsn_flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE;
+		else
+			bss_info->rsn_flags &= ~NM_802_11_AP_SEC_KEY_MGMT_OWE;
+
+		bss_info->metered = p_metered;
 	}
-	return g_variant_builder_end (&builder);
+	if (p_max_rate_has)
+		bss_info->max_rate = p_max_rate / 1000u;
+
+	v_v = nm_g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		bss_info->wpa_flags = security_from_vardict (v_v);
+		g_variant_unref (v_v);
+	}
+
+	v_v = nm_g_variant_lookup_value (properties, "RSN", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		bss_info->rsn_flags = security_from_vardict (v_v);
+		g_variant_unref (v_v);
+	}
+
+	_bss_info_changed_emit (self, bss_info, TRUE);
 }
 
 static void
-peer_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+_bss_info_get_all_cb (GVariant *result,
+                      GError *error,
+                      gpointer user_data)
 {
+	NMSupplicantBssInfo *bss_info;
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	GVariant *props = NULL;
-	const char *object_path;
-	PeerData *peer_data;
-	gboolean success;
-
-	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
-	if (   !success
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
+	bss_info = user_data;
+	self = bss_info->_self;
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (!success) {
-		_LOGD ("failed to acquire Peer proxy: (%s)", error->message);
-		g_hash_table_remove (priv->peer_proxies,
-		                     g_dbus_proxy_get_object_path (proxy));
-		return;
-	}
+	g_clear_object (&bss_info->_init_cancellable);
+	nm_c_list_move_tail (&priv->bss_lst_head, &bss_info->_bss_lst);
 
-	object_path = g_dbus_proxy_get_object_path (proxy);
-	peer_data = g_hash_table_lookup (priv->peer_proxies, object_path);
-	if (!peer_data)
-		return;
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
 
-	peer_data->change_id = g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (peer_proxy_properties_changed_cb), self);
+	_bss_info_properties_changed (self, bss_info, properties, TRUE);
 
-	props = peer_proxy_get_properties (self, proxy);
+	_starting_check_ready (self);
 
-	g_signal_emit (self, signals[PEER_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               g_variant_ref_sink (props));
-	g_variant_unref (props);
+	_notify_maybe_scanning (self);
 }
 
 static void
-peer_add_new (NMSupplicantInterface *self, const char *object_path)
+_bss_info_add (NMSupplicantInterface *self, const char *object_path)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	GDBusProxy *peer_proxy;
-	PeerData *peer_data;
+	nm_auto_ref_string NMRefString *bss_path = NULL;
+	NMSupplicantBssInfo *bss_info;
 
-	g_return_if_fail (object_path != NULL);
+	bss_path = nm_ref_string_new (nm_dbus_path_not_empty (object_path));
+	if (!bss_path)
+		return;
 
-	if (g_hash_table_lookup (priv->peer_proxies, object_path))
+	bss_info = g_hash_table_lookup (priv->bss_idx, &bss_path);
+	if (bss_info) {
+		bss_info->_bss_dirty = FALSE;
 		return;
+	}
 
-	peer_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-	                           "g-bus-type", G_BUS_TYPE_SYSTEM,
-	                           "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-	                           "g-name", WPAS_DBUS_SERVICE,
-	                           "g-object-path", object_path,
-	                           "g-interface-name", WPAS_DBUS_IFACE_PEER,
-	                           NULL);
-	peer_data = g_slice_new0 (PeerData);
-	peer_data->proxy = peer_proxy;
-	g_hash_table_insert (priv->peer_proxies,
-	                     (char *) g_dbus_proxy_get_object_path (peer_proxy),
-	                     peer_data);
-	g_async_initable_init_async (G_ASYNC_INITABLE (peer_proxy),
-	                             G_PRIORITY_DEFAULT,
-	                             priv->other_cancellable,
-	                             (GAsyncReadyCallback) peer_proxy_acquired_cb,
-	                             self);
+	bss_info = g_slice_new (NMSupplicantBssInfo);
+	*bss_info = (NMSupplicantBssInfo) {
+		._self             = self,
+		.bss_path          = g_steal_pointer (&bss_path),
+		._init_cancellable = g_cancellable_new (),
+	};
+	c_list_link_tail (&priv->bss_initializing_lst_head, &bss_info->_bss_lst);
+	g_hash_table_add (priv->bss_idx, bss_info);
+
+	nm_dbus_connection_call_get_all (priv->dbus_connection,
+	                                 priv->name_owner->str,
+	                                 bss_info->bss_path->str,
+	                                 NM_WPAS_DBUS_IFACE_BSS,
+	                                 5000,
+	                                 bss_info->_init_cancellable,
+	                                 _bss_info_get_all_cb,
+	                                 bss_info);
+}
+
+static gboolean
+_bss_info_remove (NMSupplicantInterface *self,
+                  NMRefString **p_bss_path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantBssInfo *bss_info;
+	gpointer unused_but_required;
+
+	if (!g_hash_table_steal_extended (priv->bss_idx,
+	                                  p_bss_path,
+	                                  (gpointer *) &bss_info,
+	                                  &unused_but_required))
+		return FALSE;
+
+	c_list_unlink (&bss_info->_bss_lst);
+	if (!bss_info->_init_cancellable)
+		_bss_info_changed_emit (self, bss_info, FALSE);
+	_bss_info_destroy (bss_info);
+
+	nm_assert_starting_has_pending_count (priv->starting_pending_count);
+
+	return TRUE;
 }
 
 /*****************************************************************************/
 
 static void
-set_state (NMSupplicantInterface *self, NMSupplicantInterfaceState new_state)
+_peer_info_destroy (NMSupplicantPeerInfo *peer_info)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	NMSupplicantInterfaceState old_state = priv->state;
+	c_list_unlink (&peer_info->_peer_lst);
+	nm_clear_g_cancellable (&peer_info->_init_cancellable);
 
-	if (new_state == priv->state)
-		return;
+	g_free (peer_info->device_name);
+	g_free (peer_info->manufacturer);
+	g_free (peer_info->model);
+	g_free (peer_info->model_number);
+	g_free (peer_info->serial);
+	g_bytes_unref (peer_info->ies);
+
+	nm_g_slice_free (peer_info);
+}
+
+static void
+_peer_info_changed_emit (NMSupplicantInterface *self,
+                         NMSupplicantPeerInfo *peer_info,
+                         gboolean is_present)
+{
+	g_signal_emit (self,
+	               signals[PEER_CHANGED],
+	               0,
+	               peer_info,
+	               is_present);
+}
+
+static void
+_peer_info_properties_changed (NMSupplicantInterface *self,
+                               NMSupplicantPeerInfo *peer_info,
+                               GVariant *properties,
+                               gboolean initial)
+{
+	GVariant *v_v;
+	const char *v_s;
+	gint32 v_i32;
+	const guint8 *arr_data;
+	gsize arr_len;
+
+	peer_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec ();
 
-	/* DOWN is a terminal state */
-	g_return_if_fail (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	if (nm_g_variant_lookup (properties, "level", "i", &v_i32))
+		peer_info->signal_percent = nm_wifi_utils_level_to_quality (v_i32);
 
-	/* Cannot regress to READY, STARTING, or INIT from higher states */
-	if (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
-		g_return_if_fail (new_state > NM_SUPPLICANT_INTERFACE_STATE_READY);
+	if (nm_g_variant_lookup (properties, "DeviceName", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->device_name, v_s);
 
-	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_READY) {
-		nm_clear_g_cancellable (&priv->other_cancellable);
-		priv->other_cancellable = g_cancellable_new ();
-	} else if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
-		nm_clear_g_cancellable (&priv->init_cancellable);
-		nm_clear_g_cancellable (&priv->other_cancellable);
+	if (nm_g_variant_lookup (properties, "Manufacturer", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->manufacturer, v_s);
 
-		if (priv->iface_proxy)
-			g_signal_handlers_disconnect_by_data (priv->iface_proxy, self);
+	if (nm_g_variant_lookup (properties, "Model", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->model, v_s);
+
+	if (nm_g_variant_lookup (properties, "ModelNumber", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->model_number, v_s);
+
+	if (nm_g_variant_lookup (properties, "Serial", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->serial, v_s);
+
+	v_v = nm_g_variant_lookup_value (properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		if (   arr_len == ETH_ALEN
+		    && memcmp (arr_data, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (arr_data, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0) {
+			/* pass */
+		} else
+			arr_len = 0;
+
+		if (arr_len != 0) {
+			nm_assert (arr_len == sizeof (peer_info->address));
+			peer_info->address_valid = TRUE;
+			memcpy (peer_info->address, arr_data, sizeof (peer_info->address));
+		} else if (peer_info->address_valid) {
+			peer_info->address_valid = FALSE;
+			memset (peer_info->address, 0, sizeof (peer_info->address));
+		}
+		g_variant_unref (v_v);
+	} else {
+		nm_assert (   !initial
+		           || !peer_info->address_valid);
 	}
+	nm_assert (   (   peer_info->address_valid
+	               && !nm_utils_memeqzero (peer_info->address, sizeof (peer_info->address)))
+	           || (  !peer_info->address_valid
+	               && nm_utils_memeqzero (peer_info->address, sizeof (peer_info->address))));
 
-	priv->state = new_state;
+	/* The IEs property contains the WFD R1 subelements */
+	v_v = nm_g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		if (!nm_utils_gbytes_equal_mem (peer_info->ies, arr_data, arr_len)) {
+			_nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer (&peer_info->ies);
 
-	if (   priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
-	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+			peer_info->ies = g_bytes_new (arr_data, arr_len);
+		} else if (   arr_len == 0
+		           && !peer_info->ies)
+			peer_info->ies = g_bytes_new (NULL, 0);
+		g_variant_unref (v_v);
+	}
 
-	/* Disconnect reason is no longer relevant when not in the DISCONNECTED state */
-	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED)
-		priv->disconnect_reason = 0;
+	_peer_info_changed_emit (self, peer_info, TRUE);
+}
 
-	g_signal_emit (self, signals[STATE], 0,
-	               (int) priv->state,
-	               (int) old_state,
-	               (int) priv->disconnect_reason);
-}
-
-static NMSupplicantInterfaceState
-wpas_state_string_to_enum (const char *str_state)
-{
-	if (!strcmp (str_state, "interface_disabled"))
-		return NM_SUPPLICANT_INTERFACE_STATE_DISABLED;
-	else if (!strcmp (str_state, "disconnected"))
-		return NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED;
-	else if (!strcmp (str_state, "inactive"))
-		return NM_SUPPLICANT_INTERFACE_STATE_INACTIVE;
-	else if (!strcmp (str_state, "scanning"))
-		return NM_SUPPLICANT_INTERFACE_STATE_SCANNING;
-	else if (!strcmp (str_state, "authenticating"))
-		return NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING;
-	else if (!strcmp (str_state, "associating"))
-		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING;
-	else if (!strcmp (str_state, "associated"))
-		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED;
-	else if (!strcmp (str_state, "4way_handshake"))
-		return NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE;
-	else if (!strcmp (str_state, "group_handshake"))
-		return NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE;
-	else if (!strcmp (str_state, "completed"))
-		return NM_SUPPLICANT_INTERFACE_STATE_COMPLETED;
-
-	return NM_SUPPLICANT_INTERFACE_STATE_INVALID;
+static void
+_peer_info_get_all_cb (GVariant *result,
+                       GError *error,
+                       gpointer user_data)
+{
+	NMSupplicantPeerInfo *peer_info;
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	peer_info = user_data;
+	self = peer_info->_self;
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_clear_object (&peer_info->_init_cancellable);
+	nm_c_list_move_tail (&priv->peer_lst_head, &peer_info->_peer_lst);
+
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
+
+	_peer_info_properties_changed (self, peer_info, properties, TRUE);
+
+	_starting_check_ready (self);
 }
 
 static void
-set_state_from_string (NMSupplicantInterface *self, const char *new_state)
+_peer_info_add (NMSupplicantInterface *self, const char *object_path)
 {
-	NMSupplicantInterfaceState state;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *peer_path = NULL;
+	NMSupplicantPeerInfo *peer_info;
 
-	state = wpas_state_string_to_enum (new_state);
-	if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID) {
-		_LOGW ("unknown supplicant state '%s'", new_state);
+	peer_path = nm_ref_string_new (nm_dbus_path_not_empty (object_path));
+	if (!peer_path)
+		return;
+
+	peer_info = g_hash_table_lookup (priv->peer_idx, &peer_path);
+
+	if (peer_info) {
+		peer_info->_peer_dirty = FALSE;
 		return;
 	}
-	set_state (self, state);
+
+	peer_info = g_slice_new (NMSupplicantPeerInfo);
+	*peer_info = (NMSupplicantPeerInfo) {
+		._self             = self,
+		.peer_path         = g_steal_pointer (&peer_path),
+		._init_cancellable = g_cancellable_new (),
+	};
+	c_list_link_tail (&priv->peer_initializing_lst_head, &peer_info->_peer_lst);
+	g_hash_table_add (priv->peer_idx, peer_info);
+
+	nm_dbus_connection_call_get_all (priv->dbus_connection,
+	                                 priv->name_owner->str,
+	                                 peer_info->peer_path->str,
+	                                 NM_WPAS_DBUS_IFACE_PEER,
+	                                 5000,
+	                                 peer_info->_init_cancellable,
+	                                 _peer_info_get_all_cb,
+	                                 peer_info);
 }
 
+static gboolean
+_peer_info_remove (NMSupplicantInterface *self,
+                   NMRefString **p_peer_path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantPeerInfo *peer_info;
+	gpointer unused_but_required;
+
+	if (!g_hash_table_steal_extended (priv->peer_idx,
+	                                  p_peer_path,
+	                                  (gpointer *) &peer_info,
+	                                  &unused_but_required))
+		return FALSE;
+
+	c_list_unlink (&peer_info->_peer_lst);
+	if (!peer_info->_init_cancellable)
+		_peer_info_changed_emit (self, peer_info, FALSE);
+	_peer_info_destroy (peer_info);
+
+	nm_assert_starting_has_pending_count (priv->starting_pending_count);
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 static void
-set_scanning (NMSupplicantInterface *self, gboolean new_scanning)
+set_state_down (NMSupplicantInterface *self,
+                gboolean force_remove_from_supplicant,
+                const char *reason)
 {
+	_nm_unused gs_unref_object NMSupplicantInterface *self_keep_alive = g_object_ref (self);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantBssInfo *bss_info;
+	NMSupplicantPeerInfo *peer_info;
+	NMSupplicantInterfaceState old_state;
+
+	nm_assert (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	nm_assert (!c_list_is_empty (&self->supp_lst));
+
+	_LOGD ("remove interface \"%s\" on %s (%s)%s",
+	       priv->object_path->str,
+	       priv->name_owner->str,
+	       reason,
+	       force_remove_from_supplicant ? " (remove in wpa_supplicant)" : "");
+
+	old_state = priv->state;
+
+	priv->state = NM_SUPPLICANT_INTERFACE_STATE_DOWN;
+
+	_nm_supplicant_manager_unregister_interface (priv->supplicant_manager, self);
+
+	nm_assert (c_list_is_empty (&self->supp_lst));
+
+	if (force_remove_from_supplicant) {
+		_nm_supplicant_manager_dbus_call_remove_interface (priv->supplicant_manager,
+		                                                   priv->name_owner->str,
+		                                                   priv->object_path->str);
+	}
+
+	_emit_signal_state (self, priv->state, old_state, 0);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->properties_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->signal_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->bss_properties_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->peer_properties_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->p2p_group_properties_changed_id);
+
+	nm_supplicant_interface_cancel_wps (self);
 
-	if (priv->scanning != new_scanning) {
-		priv->scanning = new_scanning;
+	if (priv->assoc_data) {
+		gs_free_error GError *error = NULL;
+
+		nm_utils_error_set_cancelled (&error, TRUE, "NMSupplicantInterface");
+		assoc_return (self, error, "cancelled because supplicant interface is going down");
+	}
 
-		/* Cache time of last scan completion */
-		if (priv->scanning == FALSE)
-			priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+	while ((bss_info = c_list_first_entry (&priv->bss_initializing_lst_head, NMSupplicantBssInfo, _bss_lst))) {
+		g_hash_table_remove (priv->bss_idx, bss_info);
+		_bss_info_destroy (bss_info);
+	}
+	while ((bss_info = c_list_first_entry (&priv->bss_lst_head, NMSupplicantBssInfo, _bss_lst))) {
+		g_hash_table_remove (priv->bss_idx, bss_info);
+		_bss_info_destroy (bss_info);
+	}
+	nm_assert (g_hash_table_size (priv->bss_idx) == 0);
 
-		_notify (self, PROP_SCANNING);
+	while ((peer_info = c_list_first_entry (&priv->peer_initializing_lst_head, NMSupplicantPeerInfo, _peer_lst))) {
+		g_hash_table_remove (priv->peer_idx, peer_info);
+		_peer_info_destroy (peer_info);
 	}
+	while ((peer_info = c_list_first_entry (&priv->peer_lst_head, NMSupplicantPeerInfo, _peer_lst))) {
+		g_hash_table_remove (priv->peer_idx, peer_info);
+		_peer_info_destroy (peer_info);
+	}
+	nm_assert (g_hash_table_size (priv->peer_idx) == 0);
+
+	nm_clear_g_cancellable (&priv->main_cancellable);
+	nm_clear_g_cancellable (&priv->p2p_group_properties_cancellable);
+
+	nm_clear_pointer (&priv->p2p_group_path, nm_ref_string_unref);
+
+	_remove_network (self);
+
+	nm_clear_pointer (&priv->current_bss, nm_ref_string_unref);
+
+	_notify_maybe_scanning (self);
 }
 
-gboolean
-nm_supplicant_interface_get_scanning (NMSupplicantInterface *self)
+static void
+set_state (NMSupplicantInterface *self, NMSupplicantInterfaceState new_state)
 {
-	NMSupplicantInterfacePrivate *priv;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterfaceState old_state = priv->state;
 
-	g_return_val_if_fail (self, FALSE);
+	nm_assert (new_state > NM_SUPPLICANT_INTERFACE_STATE_STARTING);
+	nm_assert (new_state < NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	nm_assert (nm_supplicant_interface_state_is_operational (new_state));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	if (priv->scanning)
-		return TRUE;
-	if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		return TRUE;
-	return FALSE;
+	nm_assert (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_STARTING);
+	nm_assert (priv->state < NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+
+	if (new_state == priv->state)
+		return;
+
+	_LOGT ("set state \"%s\" (was \"%s\")",
+	       nm_supplicant_interface_state_to_string (new_state),
+	       nm_supplicant_interface_state_to_string (priv->state));
+
+	priv->state = new_state;
+
+	_emit_signal_state (self,
+	                    priv->state,
+	                    old_state,
+	                      priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED
+	                    ? 0u
+	                    : priv->disconnect_reason);
 }
 
-const char *
+NMRefString *
 nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self)
 {
-	NMSupplicantInterfacePrivate *priv;
-
 	g_return_val_if_fail (self != NULL, FALSE);
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	return priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY ? priv->current_bss : NULL;
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->current_bss;
+}
+
+gboolean
+nm_supplicant_interface_get_scanning (NMSupplicantInterface *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->scanning_cached;
 }
 
 gint64
 nm_supplicant_interface_get_last_scan (NMSupplicantInterface *self)
 {
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan;
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan_msec;
 }
 
 #define MATCH_PROPERTY(p, n, v, t) (!strcmp (p, n) && g_variant_is_of_type (v, t))
@@ -600,33 +1169,31 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 	gboolean have_active = FALSE;
 	gboolean have_ssid = FALSE;
-	gboolean have_p2p = FALSE;
 	gboolean have_ft = FALSE;
 	gint32 max_scan_ssids = -1;
 	const char **array;
 
-	g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
+	nm_assert (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
 
 	if (g_variant_lookup (capabilities, "KeyMgmt", "^a&s", &array)) {
 		have_ft = g_strv_contains (array, "wpa-ft-psk");
 		g_free (array);
 	}
 
-	priv->ft_support_per_iface =   have_ft
-	                             ? NM_SUPPLICANT_FEATURE_YES
-	                             : NM_SUPPLICANT_FEATURE_NO;
+	priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET (priv->iface_capabilities,
+	                                                  NM_SUPPL_CAP_TYPE_FT,
+	                                                    have_ft
+	                                                  ? NM_TERNARY_TRUE
+	                                                  : NM_TERNARY_FALSE);
 
 	if (g_variant_lookup (capabilities, "Modes", "^a&s", &array)) {
-		if (g_strv_contains (array, "p2p"))
-			have_p2p = TRUE;
+		/* Setting p2p_capable might toggle _prop_p2p_available_get(). However,
+		 * we don't need to check for a property changed notification, because
+		 * the caller did g_object_freeze_notify() and will perform the check. */
+		priv->p2p_capable_property = g_strv_contains (array, "p2p");
 		g_free (array);
 	}
 
-	if (priv->p2p_capable != have_p2p) {
-		priv->p2p_capable = have_p2p;
-		_notify (self, PROP_P2P_AVAILABLE);
-	}
-
 	if (g_variant_lookup (capabilities, "Scan", "^a&s", &array)) {
 		if (g_strv_contains (array, "active"))
 			have_active = TRUE;
@@ -638,187 +1205,116 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 	if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) {
 		/* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */
 		if (max_scan_ssids > 0 && have_active && have_ssid) {
-			/* wpa_supplicant's WPAS_MAX_SCAN_SSIDS value is 16, but for speed
+			/* wpa_supplicant's NM_WPAS_MAX_SCAN_SSIDS value is 16, but for speed
 			 * and to ensure we don't disclose too many SSIDs from the hidden
 			 * list, we'll limit to 5.
 			 */
 			priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, 5);
-			_LOGI ("supports %d scan SSIDs", priv->max_scan_ssids);
+			_LOGD ("supports %d scan SSIDs", priv->max_scan_ssids);
 		}
 	}
 }
 
 static void
-iface_check_ready (NMSupplicantInterface *self)
+_starting_check_ready (NMSupplicantInterface *self)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (priv->ready_count && priv->state < NM_SUPPLICANT_INTERFACE_STATE_READY) {
-		priv->ready_count--;
-		if (priv->ready_count == 0)
-			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_READY);
-	}
-}
-
-gboolean
-nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
-                                           const char *field,
-                                           const char *value,
-                                           GError **error)
-{
-	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
-
-	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
-	g_return_val_if_fail (field != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	g_return_val_if_fail (priv->has_credreq == TRUE, FALSE);
-
-	/* Need a network block object path */
-	g_return_val_if_fail (priv->net_path, FALSE);
-	reply = g_dbus_proxy_call_sync (priv->iface_proxy,
-	                                "NetworkReply",
-	                                g_variant_new ("(oss)",
-	                                               priv->net_path,
-	                                               field,
-	                                               value),
-	                                G_DBUS_CALL_FLAGS_NONE,
-	                                5000,
-	                                NULL,
-	                                error);
-	if (error && *error)
-		g_dbus_error_strip_remote_error (*error);
-
-	return !!reply;
-}
-
-static void
-iface_check_netreply_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *variant = NULL;
-	gs_free_error GError *error = NULL;
-
-	/* We know NetworkReply is supported if the NetworkReply method returned
-	 * successfully (which is unexpected since we sent a bogus network
-	 * object path) or if we got an "InvalidArgs" (which indicates NetworkReply
-	 * is supported).  We know it's not supported if we get an
-	 * "UnknownMethod" error.
-	 */
-
-	variant = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_STARTING)
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (variant || _nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.InvalidArgs"))
-		priv->has_credreq = TRUE;
-
-	_LOGD ("supplicant %s network credentials requests",
-	       priv->has_credreq ? "supports" : "does not support");
-
-	iface_check_ready (self);
-}
+	if (priv->starting_pending_count > 0)
+		return;
 
-static void
-iface_set_pmf_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *variant = NULL;
-	gs_free_error GError *error = NULL;
+	if (!c_list_is_empty (&priv->bss_initializing_lst_head))
+		return;
 
-	variant = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (!c_list_is_empty (&priv->peer_initializing_lst_head))
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
+	if (priv->p2p_group_properties_cancellable)
+		return;
 
-	if (error)
-		_LOGW ("failed to set Pmf=1: %s", error->message);
+	nm_assert (priv->state == NM_SUPPLICANT_INTERFACE_STATE_STARTING);
 
-	iface_check_ready (self);
-}
+	if (!nm_supplicant_interface_state_is_operational (priv->supp_state)) {
+		_LOGW ("Supplicant state is unknown during initialization. Destroy the interface");
+		set_state_down (self, TRUE, "failure to get valid interface state");
+		return;
+	}
 
-gboolean
-nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->group_proxy_acquired;
+	set_state (self, priv->supp_state);
 }
 
-const char*
-nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self)
+static NMTernary
+_get_capability (NMSupplicantInterfacePrivate *priv,
+                 NMSupplCapType type)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMTernary value;
+	NMTernary iface_value;
 
-	if (priv->group_proxy_acquired)
-		return g_dbus_proxy_get_object_path (priv->group_proxy);
-	else
-		return NULL;
+	switch (type) {
+	case NM_SUPPL_CAP_TYPE_AP:
+		iface_value = NM_SUPPL_CAP_MASK_GET (priv->iface_capabilities, type);
+		value = NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, type);
+		value = MAX (iface_value, value);
+		break;
+	case NM_SUPPL_CAP_TYPE_FT:
+		value = NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, type);
+		if (value != NM_TERNARY_FALSE) {
+			iface_value = NM_SUPPL_CAP_MASK_GET (priv->iface_capabilities, type);
+			if (iface_value != NM_TERNARY_DEFAULT)
+				value = iface_value;
+		}
+		break;
+	default:
+		nm_assert (NM_SUPPL_CAP_MASK_GET (priv->iface_capabilities, type) == NM_TERNARY_DEFAULT);
+		value = NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, type);
+		break;
+	}
+	return value;
 }
 
-gboolean
-nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self)
+NMTernary
+nm_supplicant_interface_get_capability (NMSupplicantInterface *self,
+                                        NMSupplCapType type)
 {
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_owner;
+	return _get_capability (NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self), type);
 }
 
-NMSupplicantFeature
-nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
+NMSupplCapMask
+nm_supplicant_interface_get_capabilities (NMSupplicantInterface *self)
 {
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ap_support;
-}
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplCapMask caps;
 
-NMSupplicantFeature
-nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->pmf_support;
-}
+	caps = priv->global_capabilities;
+	caps = NM_SUPPL_CAP_MASK_SET (caps, NM_SUPPL_CAP_TYPE_AP, _get_capability (priv, NM_SUPPL_CAP_TYPE_AP));
+	caps = NM_SUPPL_CAP_MASK_SET (caps, NM_SUPPL_CAP_TYPE_FT, _get_capability (priv, NM_SUPPL_CAP_TYPE_FT));
 
-NMSupplicantFeature
-nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->fils_support;
-}
+	nm_assert (!NM_FLAGS_ANY (priv->iface_capabilities,
+	                          ~(  NM_SUPPL_CAP_MASK_T_AP_MASK
+	                            | NM_SUPPL_CAP_MASK_T_FT_MASK)));
 
-NMSupplicantFeature
-nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_support;
-}
+#if NM_MORE_ASSERTS > 10
+	{
+		NMSupplCapType type;
 
-NMSupplicantFeature
-nm_supplicant_interface_get_mesh_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->mesh_support;
-}
+		for (type = 0; type < _NM_SUPPL_CAP_TYPE_NUM; type++)
+			nm_assert (NM_SUPPL_CAP_MASK_GET (caps, type) == _get_capability (priv, type));
+	}
+#endif
 
-NMSupplicantFeature
-nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->wfd_support;
+	return caps;
 }
 
-NMSupplicantFeature
-nm_supplicant_interface_get_ft_support (NMSupplicantInterface *self)
+void
+nm_supplicant_interface_set_global_capabilities (NMSupplicantInterface *self,
+                                                 NMSupplCapMask value)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (priv->ft_support_global == NM_SUPPLICANT_FEATURE_NO)
-		return NM_SUPPLICANT_FEATURE_NO;
-	if (priv->ft_support_per_iface != NM_SUPPLICANT_FEATURE_UNKNOWN)
-		return priv->ft_support_per_iface;
-	return priv->ft_support_global;
-}
-
-NMSupplicantFeature
-nm_supplicant_interface_get_sha384_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->sha384_support;
+	priv->global_capabilities = value;
 }
 
 NMSupplicantAuthState
@@ -827,270 +1323,296 @@ nm_supplicant_interface_get_auth_state (NMSupplicantInterface *self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->auth_state;
 }
 
-void
-nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
-                                        NMSupplicantFeature ap_support)
+/*****************************************************************************/
+
+static void
+_p2p_group_properties_changed (NMSupplicantInterface *self,
+                               GVariant *properties)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char *s;
 
-	/* Use the best indicator of support between the supplicant global
-	 * Capabilities property and the interface's introspection data.
-	 */
-	if (ap_support > priv->ap_support)
-		priv->ap_support = ap_support;
+	if (!properties)
+		priv->p2p_group_owner_property = FALSE;
+	else if (g_variant_lookup (properties, "Role", "&s", &s))
+		priv->p2p_group_owner_property = nm_streq (s, "GO");
+
+	_notify_maybe_p2p_group (self);
 }
 
-void
-nm_supplicant_interface_set_fast_support (NMSupplicantInterface *self,
-                                          NMSupplicantFeature fast_support)
+static void
+_p2p_group_properties_changed_cb (GDBusConnection *connection,
+                                  const char *sender_name,
+                                  const char *object_path,
+                                  const char *signal_interface_name,
+                                  const char *signal_name,
+                                  GVariant *parameters,
+                                  gpointer user_data)
 {
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gs_unref_variant GVariant *changed_properties = NULL;
 
-	priv->fast_support = fast_support;
-}
+	if (priv->p2p_group_properties_cancellable)
+		return;
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
 
-void
-nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
-                                         NMSupplicantFeature pmf_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               NULL,
+	               &changed_properties,
+	               NULL);
 
-	priv->pmf_support = pmf_support;
+	_p2p_group_properties_changed (self, changed_properties);
 }
 
-void
-nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
-                                          NMSupplicantFeature fils_support)
+static void
+_p2p_group_properties_get_all_cb (GVariant *result,
+                                  GError *error,
+                                  gpointer user_data)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *properties = NULL;
 
-	priv->fils_support = fils_support;
-}
+	if (nm_utils_error_is_cancelled (error))
+		return;
 
-void
-nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
-                                         NMSupplicantFeature p2p_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	priv->p2p_support = p2p_support;
-}
+	g_object_freeze_notify (G_OBJECT (self));
 
-void
-nm_supplicant_interface_set_mesh_support (NMSupplicantInterface *self,
-                                          NMSupplicantFeature mesh_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_clear_g_cancellable (&priv->p2p_group_properties_cancellable);
 
-	priv->mesh_support = mesh_support;
-}
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
 
-void
-nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
-                                         NMSupplicantFeature wfd_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	_p2p_group_properties_changed (self, properties);
 
-	priv->wfd_support = wfd_support;
-}
+	_starting_check_ready (self);
 
-void
-nm_supplicant_interface_set_ft_support (NMSupplicantInterface *self,
-                                        NMSupplicantFeature ft_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	_notify_maybe_p2p_group (self);
 
-	priv->ft_support_global = ft_support;
+	g_object_thaw_notify (G_OBJECT (self));
 }
 
-void
-nm_supplicant_interface_set_sha384_support (NMSupplicantInterface *self,
-                                            NMSupplicantFeature sha384_support)
+static void
+_p2p_group_set_path (NMSupplicantInterface *self,
+                     const char *path)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *group_path = NULL;
+
+	group_path = nm_ref_string_new (nm_dbus_path_not_empty (path));
 
-	priv->sha384_support = sha384_support;
+	if (priv->p2p_group_path == group_path)
+		return;
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->p2p_group_properties_changed_id);
+	nm_clear_g_cancellable (&priv->p2p_group_properties_cancellable);
+
+	nm_ref_string_unref (priv->p2p_group_path);
+	priv->p2p_group_path = g_steal_pointer (&group_path);
+
+	if (priv->p2p_group_path) {
+		priv->p2p_group_properties_cancellable = g_cancellable_new ();
+		priv->p2p_group_properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+		                                                                                                priv->name_owner->str,
+		                                                                                                priv->p2p_group_path->str,
+		                                                                                                NM_WPAS_DBUS_IFACE_GROUP,
+		                                                                                                _p2p_group_properties_changed_cb,
+		                                                                                                self,
+		                                                                                                NULL);
+		nm_dbus_connection_call_get_all (priv->dbus_connection,
+		                                 priv->name_owner->str,
+		                                 priv->p2p_group_path->str,
+		                                 NM_WPAS_DBUS_IFACE_GROUP,
+		                                 5000,
+		                                 priv->p2p_group_properties_cancellable,
+		                                 _p2p_group_properties_get_all_cb,
+		                                 self);
+	}
+
+	_notify (self, PROP_P2P_GROUP_PATH);
+	_notify_maybe_p2p_group (self);
+
+	nm_assert_starting_has_pending_count (priv->starting_pending_count);
 }
 
 /*****************************************************************************/
 
 static void
-_wps_data_free (WpsData *data)
+_wps_data_free (WpsData *wps_data,
+                GDBusConnection *dbus_connection)
 {
-	g_free (data->type);
-	g_free (data->pin);
-	g_free (data->bssid);
-	g_clear_object (&data->cancellable);
-	if (data->proxy && data->self)
-		g_signal_handlers_disconnect_by_data (data->proxy, data->self);
-	g_clear_object (&data->proxy);
-	g_slice_free (WpsData, data);
+	nm_clear_g_dbus_connection_signal (dbus_connection,
+	                                   &wps_data->signal_id);
+	nm_clear_g_cancellable (&wps_data->cancellable);
+	g_free (wps_data->type);
+	g_free (wps_data->pin);
+	g_free (wps_data->bssid);
+	nm_g_slice_free (wps_data);
 }
 
 static void
-_wps_credentials_changed_cb (GDBusProxy *proxy,
-                             GVariant *props,
+_wps_credentials_changed_cb (GDBusConnection *connection,
+                             const char *sender_name,
+                             const char *object_path,
+                             const char *signal_interface_name,
+                             const char *signal_name,
+                             GVariant *parameters,
                              gpointer user_data)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterface *self = user_data;
+	gs_unref_variant GVariant *props = NULL;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
+		return;
+
+	g_variant_get (parameters, "(@a{sv})", &props);
 
 	_LOGT ("wps: new credentials");
 	g_signal_emit (self, signals[WPS_CREDENTIALS], 0, props);
 }
 
 static void
-_wps_handle_start_cb (GObject *source_object,
-                      GAsyncResult *res,
+_wps_handle_start_cb (GObject *source,
+                      GAsyncResult *result,
                       gpointer user_data)
 {
 	NMSupplicantInterface *self;
-	WpsData *data;
-	gs_unref_variant GVariant *result = NULL;
+	WpsData *wps_data;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
-	if (   !result
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
-	data = user_data;
-	self = data->self;
+	wps_data = user_data;
+	self = wps_data->self;
 
-	if (result)
+	if (res)
 		_LOGT ("wps: started with success");
 	else
 		_LOGW ("wps: start failed with %s", error->message);
 
-	g_clear_object (&data->cancellable);
-	nm_clear_g_free (&data->type);
-	nm_clear_g_free (&data->pin);
-	nm_clear_g_free (&data->bssid);
+	g_clear_object (&wps_data->cancellable);
+	nm_clear_g_free (&wps_data->type);
+	nm_clear_g_free (&wps_data->pin);
+	nm_clear_g_free (&wps_data->bssid);
 }
 
 static void
-_wps_handle_set_pc_cb (GObject *source_object,
-                       GAsyncResult *res,
+_wps_handle_set_pc_cb (GVariant *res,
+                       GError *error,
                        gpointer user_data)
 {
-	WpsData *data;
 	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *result = NULL;
-	gs_free_error GError *error = NULL;
+	NMSupplicantInterfacePrivate *priv;
+	WpsData *wps_data;
 	GVariantBuilder start_args;
 	guint8 bssid_buf[ETH_ALEN];
 
-	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
-	if (   !result
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
-	data = user_data;
-	self = data->self;
+	wps_data = user_data;
+	self = wps_data->self;
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (result)
+	if (res)
 		_LOGT ("wps: ProcessCredentials successfully set, starting...");
 	else
 		_LOGW ("wps: ProcessCredentials failed to set (%s), starting...", error->message);
 
-	_nm_dbus_signal_connect (data->proxy, "Credentials", G_VARIANT_TYPE ("(a{sv})"),
-	                         G_CALLBACK (_wps_credentials_changed_cb), self);
+	wps_data->signal_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+	                                                          priv->name_owner->str,
+	                                                          NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+	                                                          "Credentials",
+	                                                          priv->object_path->str,
+	                                                          NULL,
+	                                                          G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                          _wps_credentials_changed_cb,
+	                                                          self,
+	                                                          NULL);
 
 	g_variant_builder_init (&start_args, G_VARIANT_TYPE_VARDICT);
 	g_variant_builder_add (&start_args, "{sv}", "Role", g_variant_new_string ("enrollee"));
-	g_variant_builder_add (&start_args, "{sv}", "Type", g_variant_new_string (data->type));
-	if (data->pin)
-		g_variant_builder_add (&start_args, "{sv}", "Pin", g_variant_new_string (data->pin));
-
-	if (data->bssid) {
+	g_variant_builder_add (&start_args, "{sv}", "Type", g_variant_new_string (wps_data->type));
+	if (wps_data->pin)
+		g_variant_builder_add (&start_args, "{sv}", "Pin", g_variant_new_string (wps_data->pin));
+	if (wps_data->bssid) {
 		/* The BSSID is in fact not mandatory. If it is not set the supplicant would
 		 * enroll with any BSS in range. */
-		if (!nm_utils_hwaddr_aton (data->bssid, bssid_buf, sizeof (bssid_buf)))
+		if (!nm_utils_hwaddr_aton (wps_data->bssid, bssid_buf, sizeof (bssid_buf)))
 			nm_assert_not_reached ();
 		g_variant_builder_add (&start_args, "{sv}", "Bssid",
 		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, bssid_buf,
 		                                                  ETH_ALEN, sizeof (guint8)));
 	}
 
-	g_dbus_proxy_call (data->proxy,
-	                   "Start",
-	                   g_variant_new ("(a{sv})", &start_args),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   data->cancellable,
-	                   _wps_handle_start_cb,
-	                   data);
-}
+	wps_data->needs_cancelling = TRUE;
+	if (!wps_data->cancellable)
+		wps_data->cancellable = g_cancellable_new ();
 
-static void
-_wps_call_set_pc (WpsData *data)
-{
-	g_dbus_proxy_call (data->proxy,
-	                   "org.freedesktop.DBus.Properties.Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE_WPS,
-	                                  "ProcessCredentials",
-	                                  g_variant_new_boolean (TRUE)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   data->cancellable,
-	                   _wps_handle_set_pc_cb,
-	                   data);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+	                       "Start",
+	                       g_variant_new ("(a{sv})", &start_args),
+	                       G_VARIANT_TYPE ("(a{sv})"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       5000,
+	                       wps_data->cancellable,
+	                       _wps_handle_start_cb,
+	                       wps_data);
 }
 
 static void
-_wps_handle_proxy_cb (GObject *source_object,
-                      GAsyncResult *res,
-                      gpointer user_data)
+_wps_call_set_pc (NMSupplicantInterface *self,
+                  WpsData *wps_data)
 {
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	WpsData *data;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *proxy;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
-	if (   !proxy
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
+	if (!wps_data->cancellable)
+		wps_data->cancellable = g_cancellable_new ();
 
-	data = user_data;
-	self = data->self;
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (!proxy) {
-		_LOGW ("wps: failure to create D-Bus proxy: %s", error->message);
-		_wps_data_free (data);
-		priv->wps_data = NULL;
-		return;
-	}
-
-	data->proxy = proxy;
-	_LOGT ("wps: D-Bus proxy created. set ProcessCredentials...");
-	_wps_call_set_pc (data);
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+	                             "ProcessCredentials",
+	                             g_variant_new_boolean (TRUE),
+	                             5000,
+	                             wps_data->cancellable,
+	                             _wps_handle_set_pc_cb,
+	                             wps_data);
 }
 
 static void
-_wps_handle_cancel_cb (GObject *source_object,
-                       GAsyncResult *res,
+_wps_handle_cancel_cb (GObject *source,
+                       GAsyncResult *result,
                        gpointer user_data)
 {
+	GDBusConnection *dbus_connection = G_DBUS_CONNECTION (source);
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	WpsData *data;
-	gs_unref_variant GVariant *result = NULL;
+	WpsData *wps_data;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
-	if (   !result
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
+	res = g_dbus_connection_call_finish (dbus_connection, result, &error);
+	nm_assert (!nm_utils_error_is_cancelled (error));
 
-	data = user_data;
-	self = data->self;
+	wps_data = user_data;
+	self = wps_data->self;
 
 	if (!self) {
-		_wps_data_free (data);
-		if (result)
+		_wps_data_free (wps_data, dbus_connection);
+		if (res)
 			_LOGT ("wps: cancel completed successfully, after supplicant interface is gone");
 		else
 			_LOGW ("wps: cancel failed (%s), after supplicant interface is gone", error->message);
@@ -1099,23 +1621,24 @@ _wps_handle_cancel_cb (GObject *source_object,
 
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	data->is_cancelling = FALSE;
+	wps_data->is_cancelling = FALSE;
 
-	if (!data->type) {
+	if (!wps_data->type) {
 		priv->wps_data = NULL;
-		_wps_data_free (data);
-		if (result)
+		_wps_data_free (wps_data, dbus_connection);
+		if (res)
 			_LOGT ("wps: cancel completed successfully");
 		else
 			_LOGW ("wps: cancel failed (%s)", error->message);
 		return;
 	}
 
-	if (result)
+	if (res)
 		_LOGT ("wps: cancel completed successfully, setting ProcessCredentials now...");
 	else
 		_LOGW ("wps: cancel failed (%s), setting ProcessCredentials now...", error->message);
-	_wps_call_set_pc (data);
+
+	_wps_call_set_pc (self, wps_data);
 }
 
 static void
@@ -1125,74 +1648,71 @@ _wps_start (NMSupplicantInterface *self,
             const char *pin)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	WpsData *data = priv->wps_data;
+	WpsData *wps_data;
 
 	if (type)
 		_LOGI ("wps: type %s start...", type);
 
-	if (!data) {
+	wps_data = priv->wps_data;
+
+	if (!wps_data) {
 		if (!type)
 			return;
 
-		data = g_slice_new0 (WpsData);
-		data->self = self;
-		data->type = g_strdup (type);
-		data->bssid = g_strdup (bssid);
-		data->pin = g_strdup (pin);
-		data->cancellable = g_cancellable_new ();
-
-		priv->wps_data = data;
-
-		_LOGT ("wps: create D-Bus proxy...");
-
-		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-		                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-		                          NULL,
-		                          WPAS_DBUS_SERVICE,
-		                          priv->object_path,
-		                          WPAS_DBUS_IFACE_INTERFACE_WPS,
-		                          data->cancellable,
-		                          _wps_handle_proxy_cb,
-		                          data);
-		return;
-	}
-
-	g_free (data->type);
-	g_free (data->bssid);
-	g_free (data->pin);
-	data->type = g_strdup (type);
-	data->bssid = g_strdup (bssid);
-	data->pin = g_strdup (pin);
+		if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
+			_LOGD ("wps: interface is down. Cannot start with WPS");
+			return;
+		}
 
-	if (!data->proxy) {
-		if (!type) {
-			nm_clear_g_cancellable (&data->cancellable);
-			priv->wps_data = NULL;
-			_wps_data_free (data);
+		wps_data = g_slice_new (WpsData);
+		*wps_data = (WpsData) {
+			.self        = self,
+			.type        = g_strdup (type),
+			.bssid       = g_strdup (bssid),
+			.pin         = g_strdup (pin),
+		};
+		priv->wps_data = wps_data;
+	} else {
+		g_free (wps_data->type);
+		g_free (wps_data->bssid);
+		g_free (wps_data->pin);
+		wps_data->type = g_strdup (type);
+		wps_data->bssid = g_strdup (bssid);
+		wps_data->pin = g_strdup (pin);
+	}
 
-			_LOGT ("wps: abort creation of D-Bus proxy");
-		} else
-			_LOGT ("wps: new enrollment. Wait for D-Bus proxy...");
+	if (wps_data->is_cancelling) {
+		/* we wait for cancellation to complete. */
 		return;
 	}
 
-	if (data->is_cancelling)
+	if (   !type
+	    || wps_data->needs_cancelling) {
+
+		_LOGT ("wps: cancel %senrollment...",
+		       wps_data->needs_cancelling ? "previous " : "");
+
+		wps_data->is_cancelling = TRUE;
+		wps_data->needs_cancelling = FALSE;
+		nm_clear_g_cancellable (&wps_data->cancellable);
+		nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+		                                   &wps_data->signal_id);
+
+		_dbus_connection_call (self,
+		                       NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+		                       "Cancel",
+		                       NULL,
+		                       G_VARIANT_TYPE ("()"),
+		                       G_DBUS_CALL_FLAGS_NONE,
+		                       5000,
+		                       NULL,
+		                       _wps_handle_cancel_cb,
+		                       wps_data);
 		return;
+	}
 
-	_LOGT ("wps: cancel previous enrollment...");
-
-	data->is_cancelling = TRUE;
-	nm_clear_g_cancellable (&data->cancellable);
-	data->cancellable = g_cancellable_new ();
-	g_signal_handlers_disconnect_by_data (data->proxy, self);
-	g_dbus_proxy_call (data->proxy,
-	                   "Cancel",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   data->cancellable,
-	                   _wps_handle_cancel_cb,
-	                   data);
+	_LOGT ("wps: setting ProcessCredentials...");
+	_wps_call_set_pc (self, wps_data);
 }
 
 void
@@ -1213,926 +1733,199 @@ nm_supplicant_interface_cancel_wps (NMSupplicantInterface *self)
 /*****************************************************************************/
 
 static void
-iface_introspect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+iface_introspect_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *variant = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 	const char *data;
+	NMTernary value;
 
-	variant = _nm_dbus_proxy_call_finish (proxy, result,
-	                                      G_VARIANT_TYPE ("(s)"),
-	                                      &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (variant) {
-		g_variant_get (variant, "(&s)", &data);
-
-		/* The ProbeRequest method only exists if AP mode has been enabled */
-		if (strstr (data, "ProbeRequest"))
-			priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
-	}
-
-	iface_check_ready (self);
-}
-
-static void
-scan_done_emit_signal (NMSupplicantInterface *self)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char *object_path;
-	BssData *bss_data;
-	gboolean success;
-	GHashTableIter iter;
-
-	g_hash_table_iter_init (&iter, priv->bss_proxies);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &bss_data)) {
-		/* we have some BSS' that need to be initialized first. Delay
-		 * emitting signal. */
-		if (!bss_data->change_id) {
-			priv->scan_done_pending = TRUE;
-			return;
-		}
-	}
+	nm_assert (NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_DEFAULT);
 
-	/* Emit BSS_UPDATED so that wifi device has the APs (in case it removed them) */
-	g_hash_table_iter_init (&iter, priv->bss_proxies);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &object_path, (gpointer *) &bss_data)) {
-		gs_unref_variant GVariant *props = NULL;
+	value = NM_TERNARY_DEFAULT;
+	if (res) {
+		g_variant_get (res, "(&s)", &data);
 
-		props = bss_proxy_get_properties (self, bss_data->proxy);
-		g_signal_emit (self, signals[BSS_UPDATED], 0,
-		               object_path,
-		               g_variant_ref_sink (props));
+		/* The ProbeRequest method only exists if AP mode has been enabled */
+		value =   strstr (data, "ProbeRequest")
+		        ? NM_TERNARY_TRUE
+		        : NM_TERNARY_FALSE;
 	}
 
-	success = priv->scan_done_success;
-	priv->scan_done_success = FALSE;
-	priv->scan_done_pending = FALSE;
-	g_signal_emit (self, signals[SCAN_DONE], 0, success);
-}
-
-static void
-wpas_iface_scan_done (GDBusProxy *proxy,
-                      gboolean success,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	/* Cache last scan completed time */
-	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
-	priv->scan_done_success |= success;
-	scan_done_emit_signal (self);
-}
-
-static void
-wpas_iface_bss_added (GDBusProxy *proxy,
-                      const char *path,
-                      GVariant *props,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (priv->scanning)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
-
-	bss_add_new (self, path);
-}
-
-static void
-wpas_iface_bss_removed (GDBusProxy *proxy,
-                        const char *path,
-                        gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	BssData *bss_data;
-
-	bss_data = g_hash_table_lookup (priv->bss_proxies, path);
-	if (!bss_data)
-		return;
-	g_hash_table_steal (priv->bss_proxies, path);
-	g_signal_emit (self, signals[BSS_REMOVED], 0, path);
-	bss_data_destroy (bss_data);
-}
-
-static void
-wpas_iface_network_request (GDBusProxy *proxy,
-                            const char *path,
-                            const char *field,
-                            const char *message,
-                            gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (priv->has_credreq && priv->net_path && !g_strcmp0 (path, priv->net_path))
-		g_signal_emit (self, signals[CREDENTIALS_REQUEST], 0, field, message);
-}
-
-static void
-eap_changed (GDBusProxy *proxy,
-             const char *status,
-             const char *parameter,
-             gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	NMSupplicantAuthState auth_state = NM_SUPPLICANT_AUTH_STATE_UNKNOWN;
-
-	if (nm_streq0 (status, "started"))
-		auth_state = NM_SUPPLICANT_AUTH_STATE_STARTED;
-	else if (nm_streq0 (status, "completion")) {
-		if (nm_streq0 (parameter, "success"))
-			auth_state = NM_SUPPLICANT_AUTH_STATE_SUCCESS;
-		else if (nm_streq0 (parameter, "failure"))
-			auth_state = NM_SUPPLICANT_AUTH_STATE_FAILURE;
-	}
+	priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET (priv->iface_capabilities, NM_SUPPL_CAP_TYPE_AP, value);
 
-	/* the state eventually reaches one of started, success or failure
-	 * so ignore any other intermediate (unknown) state change. */
-	if (   auth_state != NM_SUPPLICANT_AUTH_STATE_UNKNOWN
-	    && auth_state != priv->auth_state) {
-		priv->auth_state = auth_state;
-		_notify (self, PROP_AUTH_STATE);
-	}
+	priv->starting_pending_count--;
+	_starting_check_ready (self);
 }
 
 static void
-props_changed_cb (GDBusProxy *proxy,
-                  GVariant *changed_properties,
-                  GStrv invalidated_properties,
-                  gpointer user_data)
+_properties_changed_main (NMSupplicantInterface *self,
+                          GVariant *properties)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char *s, **array, **iter;
-	gboolean b = FALSE;
-	gint32 i32;
-	GVariant *v;
-
-	g_object_freeze_notify (G_OBJECT (self));
+	const char **v_strv;
+	const char *v_s;
+	gboolean v_b;
+	gint32 v_i32;
+	GVariant *v_v;
+	gboolean do_log_driver_info = FALSE;
+	gboolean do_set_state = FALSE;
+	gboolean do_notify_current_bss = FALSE;
 
-	if (g_variant_lookup (changed_properties, "Scanning", "b", &b))
-		set_scanning (self, b);
+	nm_assert (properties || g_variant_is_of_type (properties, G_VARIANT_TYPE ("a{sv}")));
 
-	if (   g_variant_lookup (changed_properties, "State", "&s", &s)
-	    && priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY) {
-		/* Only transition to actual wpa_supplicant interface states (ie,
-		 * anything > READY) after the NMSupplicantInterface has had a
-		 * chance to initialize, which is signalled by entering the READY
-		 * state.
-		 */
-		set_state_from_string (self, s);
+	v_v = g_variant_lookup_value (properties, "Capabilities", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		parse_capabilities (self, v_v);
+		g_variant_unref (v_v);
 	}
 
-	if (g_variant_lookup (changed_properties, "BSSs", "^a&o", &array)) {
-		iter = array;
-		while (*iter)
-			bss_add_new (self, *iter++);
-		g_free (array);
-	}
+	if (nm_g_variant_lookup (properties, "Scanning", "b", &v_b))
+		priv->scanning_property = v_b;
 
-	if (g_variant_lookup (changed_properties, "CurrentBSS", "&o", &s)) {
-		s = nm_utils_dbus_normalize_object_path (s);
-		if (!nm_streq0 (s, priv->current_bss)) {
-			g_free (priv->current_bss);
-			priv->current_bss = g_strdup (s);
-			_notify (self, PROP_CURRENT_BSS);
-		}
+	if (nm_g_variant_lookup (properties, "Ifname", "&s", &v_s)) {
+		if (nm_utils_strdup_reset (&priv->ifname, v_s))
+			do_log_driver_info = TRUE;
 	}
-
-	v = g_variant_lookup_value (changed_properties, "Capabilities", G_VARIANT_TYPE_VARDICT);
-	if (v) {
-		parse_capabilities (self, v);
-		g_variant_unref (v);
+	if (nm_g_variant_lookup (properties, "Driver", "&s", &v_s)) {
+		if (nm_utils_strdup_reset (&priv->driver, v_s))
+			do_log_driver_info = TRUE;
 	}
 
-	if (g_variant_lookup (changed_properties, "DisconnectReason", "i", &i32)) {
+	if (nm_g_variant_lookup (properties, "DisconnectReason", "i", &v_i32)) {
 		/* Disconnect reason is currently only given for deauthentication events,
 		 * not disassociation; currently they are IEEE 802.11 "reason codes",
 		 * defined by (IEEE 802.11-2007, 7.3.1.7, Table 7-22).  Any locally caused
 		 * deauthentication will be negative, while authentications caused by the
 		 * AP will be positive.
 		 */
-		priv->disconnect_reason = i32;
-		if (priv->disconnect_reason != 0)
-			_LOGW ("connection disconnected (reason %d)", priv->disconnect_reason);
-	}
-
-	/* We may not have priv->dev set yet if this interface was created from a
-	 * known wpa_supplicant interface without knowing the device name.
-	 */
-	if (priv->dev == NULL && g_variant_lookup (changed_properties, "Ifname", "&s", &s)) {
-		priv->dev = g_strdup (s);
-		_notify (self, PROP_IFACE);
-	}
-
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-group_props_changed_cb (GDBusProxy *proxy,
-                        GVariant *changed_properties,
-                        char **invalidated_properties,
-                        gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	char *s;
-
-	g_object_freeze_notify (G_OBJECT (self));
-
-#if 0
-	v = g_variant_lookup_value (properties, "BSSID", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		if (   len == ETH_ALEN
-		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
-		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
-			nm_wifi_p2p_group_set_bssid_bin (group, bytes);
-		g_variant_unref (v);
-	}
-
-	v = g_variant_lookup_value (properties, "SSID", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		len = MIN (32, len);
-
-		/* Stupid ieee80211 layer uses <hidden> */
-		if (   bytes && len
-		    && !(((len == 8) || (len == 9)) && !memcmp (bytes, "<hidden>", 8))
-		    && !nm_utils_is_empty_ssid (bytes, len))
-			nm_wifi_p2p_group_set_ssid (group, bytes, len);
-
-		g_variant_unref (v);
-	}
-#endif
-
-	if (g_variant_lookup (changed_properties, "Role", "s", &s)) {
-		priv->p2p_group_owner = g_strcmp0 (s, "GO") == 0;
-		_notify (self, PROP_P2P_GROUP_OWNER);
-		g_free (s);
-	}
-
-	/* NOTE: We do not seem to get any property change notifications for the Members
-	 *       property. However, we can keep track of these indirectly either by querying
-	 *       the groups that each peer is in or listening to the Join/Disconnect
-	 *       notifications.
-	 */
-
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-group_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	gboolean success;
-
-	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
-	if (   !success
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (!success) {
-		_LOGD ("failed to acquire Group proxy: (%s)", error->message);
-		g_clear_object (&priv->group_proxy);
-		return;
-	}
-
-	priv->group_proxy_acquired = TRUE;
-	_notify (self, PROP_P2P_GROUP_JOINED);
-	_notify (self, PROP_P2P_GROUP_PATH);
-
-	iface_check_ready (self);
-}
-
-static void
-p2p_props_changed_cb (GDBusProxy *proxy,
-                      GVariant *changed_properties,
-                      GStrv invalidated_properties,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char **array, **iter;
-	const char *path = NULL;
-
-	g_object_freeze_notify (G_OBJECT (self));
-
-	if (g_variant_lookup (changed_properties, "Peers", "^a&o", &array)) {
-		iter = array;
-		while (*iter)
-			peer_add_new (self, *iter++);
-		g_free (array);
-	}
-
-	if (g_variant_lookup (changed_properties, "Group", "&o", &path)) {
-		if (priv->group_proxy && g_strcmp0 (path, g_dbus_proxy_get_object_path (priv->group_proxy)) == 0) {
-			/* We already have the proxy, nothing to do. */
-		} else if (nm_dbus_path_not_empty (path)) {
-			if (priv->group_proxy != NULL) {
-				_LOGW ("P2P: Unexpected update of the group object path");
-				priv->group_proxy_acquired = FALSE;
-				_notify (self, PROP_P2P_GROUP_JOINED);
-				_notify (self, PROP_P2P_GROUP_PATH);
-				g_clear_object (&priv->group_proxy);
+		priv->disconnect_reason = v_i32;
+	}
+
+	if (nm_g_variant_lookup (properties, "State", "&s", &v_s)) {
+		NMSupplicantInterfaceState state;
+
+		state = wpas_state_string_to_enum (v_s);
+		if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID)
+			_LOGT ("ignore unknown supplicant state '%s'", v_s);
+		else if (priv->supp_state != state) {
+			priv->supp_state = state;
+			if (priv->state > NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+				/* Only transition to actual wpa_supplicant interface states (ie,
+				 * anything > STARTING) after the NMSupplicantInterface has had a
+				 * chance to initialize, which is signalled by entering the STARTING
+				 * state.
+				 */
+				do_set_state = TRUE;
 			}
-
-			/* Delay ready state if we have not reached it yet. */
-			if (priv->ready_count)
-				priv->ready_count++;
-
-			priv->group_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-			                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
-			                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-			                                  "g-name", WPAS_DBUS_SERVICE,
-			                                  "g-object-path", path,
-			                                  "g-interface-name", WPAS_DBUS_IFACE_GROUP,
-			                                  NULL);
-			g_signal_connect (priv->group_proxy, "g-properties-changed", G_CALLBACK (group_props_changed_cb), self);
-			g_async_initable_init_async (G_ASYNC_INITABLE (priv->group_proxy),
-			                             G_PRIORITY_DEFAULT,
-			                             priv->other_cancellable,
-			                             (GAsyncReadyCallback) group_proxy_acquired_cb,
-			                             self);
-		} else {
-			priv->group_proxy_acquired = FALSE;
-			_notify (self, PROP_P2P_GROUP_JOINED);
-			_notify (self, PROP_P2P_GROUP_PATH);
-			g_clear_object (&priv->group_proxy);
 		}
 	}
 
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-p2p_device_found (GDBusProxy *proxy,
-                  const char *path,
-                  gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	peer_add_new (self, path);
-}
-
-static void
-p2p_device_lost (GDBusProxy *proxy,
-                 const char *path,
-                 gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	PeerData *peer_data;
-
-	peer_data = g_hash_table_lookup (priv->peer_proxies, path);
-	if (!peer_data)
-		return;
-	g_hash_table_steal (priv->peer_proxies, path);
-	g_signal_emit (self, signals[PEER_REMOVED], 0, path);
-	peer_data_destroy (peer_data);
-}
-
-static void
-p2p_group_started (GDBusProxy *proxy,
-                   GVariant *params,
-                   gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	NMSupplicantInterface *iface = NULL;
-	char *group_path = NULL;
-	char *iface_path = NULL;
-
-	/* There is one more parameter: the role, but we don't really care about that here. */
-	if (!g_variant_lookup (params, "group_object", "&o", &group_path)) {
-		_LOGW ("P2P: GroupStarted signal is missing the \"group_object\" parameter");
-		return;
-	}
-
-	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
-		_LOGW ("P2P: GroupStarted signal is missing the \"interface\" parameter");
-		return;
-	}
-
-	if (g_strcmp0 (iface_path, priv->object_path) == 0) {
-		_LOGW ("P2P: GroupStarted on existing interface");
-		iface = g_object_ref (self);
-	} else {
-		iface = nm_supplicant_manager_create_interface_from_path (nm_supplicant_manager_get (),
-		                                                          iface_path);
-		if (iface == NULL) {
-			_LOGW ("P2P: Group interface already exists in GroupStarted handler, aborting further processing.");
-			return;
+	if (nm_g_variant_lookup (properties, "CurrentBSS", "&o", &v_s)) {
+		v_s = nm_dbus_path_not_empty (v_s);
+		if (!nm_ref_string_equals_str (priv->current_bss, v_s)) {
+			nm_ref_string_unref (priv->current_bss);
+			priv->current_bss = nm_ref_string_new (v_s);
+			do_notify_current_bss = TRUE;
 		}
 	}
 
-	/* Signal existence of the (new) interface. */
-	g_signal_emit (self, signals[GROUP_STARTED], 0, iface);
-	g_object_unref (iface);
-}
-
-static void
-p2p_group_formation_failure (GDBusProxy *proxy,
-                             const char *group,
-                             gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	g_signal_emit (self, signals[GROUP_FORMATION_FAILURE], 0, group);
-}
-
-static void
-p2p_group_finished (GDBusProxy *proxy,
-                    GVariant *params,
-                    gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char *iface_path = NULL;
-	/* TODO: Group finished is called on the management interface!
-	 *       This means the signal consumer will currently need to assume which
-	 *       interface is finishing or it needs to match the object paths.
-	 */
-
-	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
-		_LOGW ("P2P: GroupFinished signal is missing the \"interface\" parameter");
-		return;
+	if (do_log_driver_info) {
+		_LOGD ("supplicant interface for ifindex=%d, ifname=%s%s%s, driver=%s%s%s (requested %s)",
+		       priv->ifindex,
+		       NM_PRINT_FMT_QUOTE_STRING (priv->ifname),
+		       NM_PRINT_FMT_QUOTE_STRING (priv->driver),
+		       nm_supplicant_driver_to_string (priv->requested_driver));
 	}
 
-	_LOGD ("P2P: GroupFinished signal on interface %s for interface %s", priv->object_path, iface_path);
-
-	/* Signal group finish interface (on management interface). */
-	g_signal_emit (self, signals[GROUP_FINISHED], 0, iface_path);
-}
+	if (nm_g_variant_lookup (properties, "BSSs", "^a&o", &v_strv)) {
+		NMSupplicantBssInfo *bss_info;
+		NMSupplicantBssInfo *bss_info_safe;
+		const char **iter;
 
-static void
-on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
+		c_list_for_each_entry (bss_info, &priv->bss_lst_head, _bss_lst)
+			bss_info->_bss_dirty = TRUE;
+		c_list_for_each_entry (bss_info, &priv->bss_initializing_lst_head, _bss_lst)
+			bss_info->_bss_dirty = TRUE;
 
-	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-			self = NM_SUPPLICANT_INTERFACE (user_data);
-			_LOGW ("failed to acquire wpa_supplicant interface proxy: (%s)", error->message);
-			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-		}
-		return;
-	}
+		for (iter = v_strv; *iter; iter++)
+			_bss_info_add (self, *iter);
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+		g_free (v_strv);
 
-	_nm_dbus_signal_connect (priv->iface_proxy, "ScanDone", G_VARIANT_TYPE ("(b)"),
-	                         G_CALLBACK (wpas_iface_scan_done), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "BSSAdded", G_VARIANT_TYPE ("(oa{sv})"),
-	                         G_CALLBACK (wpas_iface_bss_added), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "BSSRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (wpas_iface_bss_removed), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "NetworkRequest", G_VARIANT_TYPE ("(oss)"),
-	                         G_CALLBACK (wpas_iface_network_request), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "EAP", G_VARIANT_TYPE ("(ss)"),
-	                         G_CALLBACK (eap_changed), self);
-
-	/* Scan result aging parameters */
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "BSSExpireAge",
-	                                  g_variant_new_uint32 (250)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   NULL,
-	                   NULL);
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "BSSExpireCount",
-	                                  g_variant_new_uint32 (2)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   NULL,
-	                   NULL);
-
-	/* Check whether NetworkReply and AP mode are supported.
-	 * ready_count was initialized to 1 in interface_add_done().
-	 */
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "NetworkReply",
-	                   g_variant_new ("(oss)",
-	                                  "/fff",
-	                                  "foobar",
-	                                  "foobar"),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   (GAsyncReadyCallback) iface_check_netreply_cb,
-	                   self);
-
-	if (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) {
-		/* Initialize global PMF setting to 'optional' */
-		priv->ready_count++;
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   DBUS_INTERFACE_PROPERTIES ".Set",
-		                   g_variant_new ("(ssv)",
-		                                  WPAS_DBUS_IFACE_INTERFACE,
-		                                  "Pmf",
-		                                  g_variant_new_string ("1")),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) iface_set_pmf_cb,
-		                   self);
-	}
-
-	if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN) {
-		/* If the global supplicant capabilities property is not present, we can
-		 * fall back to checking whether the ProbeRequest method is supported.  If
-		 * neither of these works we have no way of determining if AP mode is
-		 * supported or not.  hostap 1.0 and earlier don't support either of these.
-		 */
-		priv->ready_count++;
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   DBUS_INTERFACE_INTROSPECTABLE ".Introspect",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) iface_introspect_cb,
-		                   self);
-	}
-}
-
-static void
-on_p2p_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-
-	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-			self = NM_SUPPLICANT_INTERFACE (user_data);
-			priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-			_LOGW ("failed to acquire wpa_supplicant p2p proxy: (%s)", error->message);
-
-			g_clear_object (&priv->p2p_proxy);
-
-			iface_check_ready (self);
+		c_list_for_each_entry_safe (bss_info, bss_info_safe, &priv->bss_initializing_lst_head, _bss_lst) {
+			if (bss_info->_bss_dirty)
+				_bss_info_remove (self, &bss_info->bss_path);
+		}
+		c_list_for_each_entry_safe (bss_info, bss_info_safe, &priv->bss_lst_head, _bss_lst) {
+			if (bss_info->_bss_dirty)
+				_bss_info_remove (self, &bss_info->bss_path);
 		}
-		return;
 	}
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	if (do_notify_current_bss)
+		_notify (self, PROP_CURRENT_BSS);
 
-	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceFound", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (p2p_device_found), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceLost", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (p2p_device_lost), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupStarted", G_VARIANT_TYPE ("(a{sv})"),
-	                         G_CALLBACK (p2p_group_started), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFormationFailure", G_VARIANT_TYPE ("(s)"),
-	                         G_CALLBACK (p2p_group_formation_failure), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFinished", G_VARIANT_TYPE ("(a{sv})"),
-	                         G_CALLBACK (p2p_group_finished), self);
-	/* TODO:
-	 *  * WpsFailed
-	 *  * FindStopped
-	 *  * GONegotationFailure
-	 *  * InvitationReceived
-	 */
-
-	priv->p2p_proxy_acquired = TRUE;
-	_notify (self, PROP_P2P_AVAILABLE);
+	if (do_set_state)
+		set_state (self, priv->supp_state);
 
-	iface_check_ready (self);
+	_notify_maybe_scanning (self);
 }
 
 static void
-interface_add_done (NMSupplicantInterface *self, const char *path)
+_properties_changed_p2p_device (NMSupplicantInterface *self,
+                                GVariant *properties)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char **v_strv;
+	const char *v_s;
 
-	_LOGD ("interface added to supplicant");
-
-	/* Iface ready check happens in iface_check_netreply_cb */
-	priv->ready_count = 1;
-
-	priv->object_path = g_strdup (path);
-	_notify (self, PROP_OBJECT_PATH);
-	priv->iface_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-	                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
-	                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-	                                  "g-name", WPAS_DBUS_SERVICE,
-	                                  "g-object-path", priv->object_path,
-	                                  "g-interface-name", WPAS_DBUS_IFACE_INTERFACE,
-	                                  NULL);
-	g_signal_connect (priv->iface_proxy, "g-properties-changed", G_CALLBACK (props_changed_cb), self);
-	g_async_initable_init_async (G_ASYNC_INITABLE (priv->iface_proxy),
-	                             G_PRIORITY_DEFAULT,
-	                             priv->init_cancellable,
-	                             (GAsyncReadyCallback) on_iface_proxy_acquired,
-	                             self);
-
-	if (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) {
-		priv->ready_count++;
-		priv->p2p_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-		                                "g-bus-type", G_BUS_TYPE_SYSTEM,
-		                                "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-		                                "g-name", WPAS_DBUS_SERVICE,
-		                                "g-object-path", priv->object_path,
-		                                "g-interface-name", WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
-		                                NULL);
-		g_signal_connect (priv->p2p_proxy, "g-properties-changed", G_CALLBACK (p2p_props_changed_cb), self);
-		g_async_initable_init_async (G_ASYNC_INITABLE (priv->p2p_proxy),
-		                             G_PRIORITY_DEFAULT,
-		                             priv->init_cancellable,
-		                             (GAsyncReadyCallback) on_p2p_proxy_acquired,
-		                             self);
-	}
-}
-
-static void
-interface_get_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *variant = NULL;
-	gs_free_error GError *error = NULL;
-	const char *path;
-
-	variant = _nm_dbus_proxy_call_finish (proxy, result,
-	                                      G_VARIANT_TYPE ("(o)"),
-	                                      &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	if (variant) {
-		g_variant_get (variant, "(&o)", &path);
-		interface_add_done (self, path);
-	} else {
-		g_dbus_error_strip_remote_error (error);
-		_LOGE ("error getting interface: %s", error->message);
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-	}
-}
-
-static void
-interface_add_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	gs_unref_variant GVariant *variant = NULL;
-	const char *path;
-
-	variant = _nm_dbus_proxy_call_finish (proxy, result,
-	                                      G_VARIANT_TYPE ("(o)"),
-	                                      &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (variant) {
-		g_variant_get (variant, "(&o)", &path);
-		interface_add_done (self, path);
-	} else if (_nm_dbus_error_has_name (error, WPAS_ERROR_EXISTS_ERROR)) {
-		/* Interface already added, just get its object path */
-		g_dbus_proxy_call (priv->wpas_proxy,
-		                   "GetInterface",
-		                   g_variant_new ("(s)", priv->dev),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) interface_get_cb,
-		                   self);
-	} else if (   g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMED_OUT)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
-		/* Supplicant wasn't running and could not be launched via service
-		 * activation.  Wait for it to start by moving back to the INIT
-		 * state.
-		 */
-		g_dbus_error_strip_remote_error (error);
-		_LOGD ("failed to activate supplicant: %s", error->message);
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_INIT);
-	} else {
-		g_dbus_error_strip_remote_error (error);
-		_LOGE ("error adding interface: %s", error->message);
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-	}
-}
-
-static void
-interface_removed_cb (GDBusProxy *proxy,
-                      const char *path,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
+	nm_assert (!properties || g_variant_is_of_type (properties, G_VARIANT_TYPE ("a{sv}")));
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (g_strcmp0 (priv->object_path, path) != 0)
-		return;
+	if (nm_g_variant_lookup (properties, "Peers", "^a&o", &v_strv)) {
+		NMSupplicantPeerInfo *peer_info;
+		NMSupplicantPeerInfo *peer_info_safe;
+		const char *const*iter;
 
-	_LOGD ("Received interface removed signal");
+		c_list_for_each_entry (peer_info, &priv->peer_lst_head, _peer_lst)
+			peer_info->_peer_dirty = TRUE;
+		c_list_for_each_entry (peer_info, &priv->peer_initializing_lst_head, _peer_lst)
+			peer_info->_peer_dirty = TRUE;
 
-	/* The interface may lose its last reference during signal handling otherwise. */
-	g_object_ref (self);
+		for (iter = v_strv; *iter; iter++)
+			_peer_info_add (self, *iter);
 
-	/* Invalidate the object path to prevent the manager from trying to remove
-	 * a non-existing interface. */
-	g_clear_pointer (&priv->object_path, g_free);
-	_notify (self, PROP_OBJECT_PATH);
+		g_free (v_strv);
 
-	/* No need to clean up everything now, that will happen at dispose time. */
-
-	/* Interface is down and has been removed. */
-	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-	g_signal_emit (self, signals[REMOVED], 0);
-
-	g_object_unref (self);
-}
-
-#if HAVE_WEXT
-#define DEFAULT_WIFI_DRIVER "nl80211,wext"
-#else
-#define DEFAULT_WIFI_DRIVER "nl80211"
-#endif
-
-static void
-on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *wpas_proxy;
-	GVariantBuilder props;
-
-	wpas_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (!wpas_proxy) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-			self = NM_SUPPLICANT_INTERFACE (user_data);
-			_LOGW ("failed to acquire wpa_supplicant proxy: (%s)", error->message);
-			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+		c_list_for_each_entry_safe (peer_info, peer_info_safe, &priv->peer_initializing_lst_head, _peer_lst) {
+			if (peer_info->_peer_dirty)
+				_peer_info_remove (self, &peer_info->peer_path);
 		}
-		return;
-	}
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	priv->wpas_proxy = wpas_proxy;
-
-	/* Watch for interface removal. */
-	_nm_dbus_signal_connect (priv->wpas_proxy, "InterfaceRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (interface_removed_cb), self);
-
-	/* Try to add the interface to the supplicant.  If the supplicant isn't
-	 * running, this will start it via D-Bus activation and return the response
-	 * when the supplicant has started.
-	 */
-
-	if (priv->dev != NULL) {
-		const char *driver_name = NULL;
-
-		switch (priv->driver) {
-		case NM_SUPPLICANT_DRIVER_WIRELESS:
-			driver_name = DEFAULT_WIFI_DRIVER;
-			break;
-		case NM_SUPPLICANT_DRIVER_WIRED:
-			driver_name = "wired";
-			break;
-		case NM_SUPPLICANT_DRIVER_MACSEC:
-			driver_name = "macsec_linux";
-			break;
+		c_list_for_each_entry_safe (peer_info, peer_info_safe, &priv->peer_lst_head, _peer_lst) {
+			if (peer_info->_peer_dirty)
+				_peer_info_remove (self, &peer_info->peer_path);
 		}
-
-		g_return_if_fail (driver_name);
-
-		g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
-		g_variant_builder_add (&props, "{sv}",
-		                       "Driver",
-		                       g_variant_new_string (driver_name));
-		g_variant_builder_add (&props, "{sv}",
-		                       "Ifname",
-		                       g_variant_new_string (priv->dev));
-
-		g_dbus_proxy_call (priv->wpas_proxy,
-		                   "CreateInterface",
-		                   g_variant_new ("(a{sv})", &props),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) interface_add_cb,
-		                   self);
-	} else if (priv->object_path) {
-		interface_add_done (self, priv->object_path);
-	} else {
-		g_assert_not_reached ();
-	}
-}
-
-static void
-interface_add (NMSupplicantInterface *self)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	/* Can only start the interface from INIT state */
-	g_return_if_fail (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT);
-
-	_LOGD ("adding interface to supplicant");
-
-	/* Move to starting to prevent double-calls of interface_add() */
-	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_STARTING);
-
-	nm_clear_g_cancellable (&priv->init_cancellable);
-	priv->init_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                          NULL,
-	                          WPAS_DBUS_SERVICE,
-	                          WPAS_DBUS_PATH,
-	                          WPAS_DBUS_INTERFACE,
-	                          priv->init_cancellable,
-	                          (GAsyncReadyCallback) on_wpas_proxy_acquired,
-	                          self);
-}
-
-void
-nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
-                                                  gboolean available)
-{
-	NMSupplicantInterfacePrivate *priv;
-
-	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
-
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (available) {
-		/* This can happen if the supplicant couldn't be activated but
-		 * for some reason was started after the activation failure.
-		 */
-		if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT)
-			interface_add (self);
-	} else {
-		/* The supplicant stopped; so we must tear down the interface */
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
 	}
-}
-
-static void
-log_result_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	gs_unref_variant GVariant *reply = NULL;
-	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (   !reply
-	    && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
-	    && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) {
-		g_dbus_error_strip_remote_error (error);
-		nm_log_warn (_NMLOG_DOMAIN, "%s: failed to %s: %s",
-		             _NMLOG_PREFIX_NAME, (const char *) user_data, error->message);
-	}
+	if (nm_g_variant_lookup (properties, "Group", "&o", &v_s))
+		_p2p_group_set_path (self, v_s);
 }
 
 /*****************************************************************************/
 
 static void
-assoc_return (NMSupplicantInterface *self, GError *error, const char *message)
+assoc_return (NMSupplicantInterface *self,
+              GError *error,
+              const char *message)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 	AssocData *assoc_data;
@@ -2143,9 +1936,14 @@ assoc_return (NMSupplicantInterface *self, GError *error, const char *message)
 
 	if (error) {
 		g_dbus_error_strip_remote_error (error);
-		_LOGW ("assoc[%p]: %s: %s", assoc_data, message, error->message);
-	} else
-		_LOGD ("assoc[%p]: association request successful", assoc_data);
+		_LOGW ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: %s: %s",
+		       NM_HASH_OBFUSCATE_PTR (assoc_data),
+		       message,
+		       error->message);
+	} else {
+		_LOGD ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: association request successful",
+		       NM_HASH_OBFUSCATE_PTR (assoc_data));
+	}
 
 	if (assoc_data->add_network_data) {
 		/* signal that this request already completed */
@@ -2171,112 +1969,83 @@ nm_supplicant_interface_disconnect (NMSupplicantInterface * self)
 
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	/* Cancel all pending calls related to a prior connection attempt */
-	if (priv->assoc_data) {
-		gs_free_error GError *error = NULL;
-
-		nm_utils_error_set_cancelled (&error, FALSE, "NMSupplicantInterface");
-		assoc_return (self, error, "abort due to disconnect");
-	}
-
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->iface_proxy)
-		return;
-
 	/* Disconnect from the current AP */
 	if (   (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
 	    && (priv->state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)) {
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   "Disconnect",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   NULL,
-		                   (GAsyncReadyCallback) log_result_cb,
-		                   "disconnect");
-	}
-
-	/* Remove any network that was added by NetworkManager */
-	if (priv->net_path) {
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   "RemoveNetwork",
-		                   g_variant_new ("(o)", priv->net_path),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->other_cancellable,
-		                   (GAsyncReadyCallback) log_result_cb,
-		                   "remove network");
-		g_free (priv->net_path);
-		priv->net_path = NULL;
+		_dbus_connection_call_simple (self,
+		                              NM_WPAS_DBUS_IFACE_INTERFACE,
+		                              "Disconnect",
+		                              NULL,
+		                              G_VARIANT_TYPE ("()"),
+		                              "disconnect");
 	}
 
+	_remove_network (self);
+
 	/* Cancel any WPS enrollment, if any */
 	nm_supplicant_interface_cancel_wps (self);
+
+	/* Cancel all pending calls related to a prior connection attempt */
+	if (priv->assoc_data) {
+		gs_free_error GError *error = NULL;
+
+		nm_utils_error_set_cancelled (&error, FALSE, "NMSupplicantInterface");
+		assoc_return (self, error, "abort due to disconnect");
+	}
 }
 
 static void
-disconnect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+disconnect_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
-	DisconnectData *disconnect_data = user_data;
-	gs_unref_object NMSupplicantInterface *self = disconnect_data->self;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_object NMSupplicantInterface *self = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
+	NMSupplicantInterfaceDisconnectCb callback;
+	gpointer callback_user_data;
+
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 
-	/* an already disconnected interface is not an error*/
-	if (   !reply
+	if (   !res
 	    && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) {
+		/* an already disconnected interface is not an error*/
 		g_clear_error(&error);
 	}
 
-	disconnect_data->callback(self, error, disconnect_data->user_data);
-	g_slice_free (DisconnectData, disconnect_data);
+	callback (self, error, callback_user_data);
 }
 
 void
-nm_supplicant_interface_disconnect_async ( NMSupplicantInterface * self,
-                                           GCancellable * cancellable,
-                                           NMSupplicantInterfaceDisconnectCb callback,
-                                           gpointer user_data)
+nm_supplicant_interface_disconnect_async (NMSupplicantInterface *self,
+                                          GCancellable *cancellable,
+                                          NMSupplicantInterfaceDisconnectCb callback,
+                                          gpointer user_data)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	DisconnectData *disconnect_data;
-
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->iface_proxy)
-		return;
-
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
-	g_return_if_fail (NULL != callback);
-
-	disconnect_data = g_slice_new0(DisconnectData);
+	g_return_if_fail (callback);
 
-	/* Keep interface alive until disconnect finishes */
-	disconnect_data->self = g_object_ref (self);
-	disconnect_data->callback = callback;
-	disconnect_data->user_data = user_data;
-
-	/* Disconnect the interface */
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "Disconnect",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   cancellable,
-	                   (GAsyncReadyCallback) disconnect_cb,
-	                   disconnect_data);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "Disconnect",
+	                       NULL,
+	                       G_VARIANT_TYPE ("()"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       cancellable,
+	                       disconnect_cb,
+	                       nm_utils_user_data_pack (g_object_ref (self), callback, user_data));
 }
 
 static void
-assoc_select_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_select_network_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
@@ -2291,26 +2060,28 @@ assoc_call_select_network (NMSupplicantInterface *self)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "SelectNetwork",
-	                   g_variant_new ("(o)", priv->net_path),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->assoc_data->cancellable,
-	                   (GAsyncReadyCallback) assoc_select_network_cb,
-	                   self);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "SelectNetwork",
+	                       g_variant_new ("(o)", priv->net_path),
+	                       G_VARIANT_TYPE ("()"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       priv->assoc_data->cancellable,
+	                       assoc_select_network_cb,
+	                       self);
 }
 
 static void
-assoc_add_blob_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_add_blob_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
@@ -2322,52 +2093,62 @@ assoc_add_blob_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 	}
 
 	priv->assoc_data->blobs_left--;
-	_LOGT ("assoc[%p]: blob added (%u left)", priv->assoc_data, priv->assoc_data->blobs_left);
+	_LOGT ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: blob added (%u left)",
+	       NM_HASH_OBFUSCATE_PTR (priv->assoc_data),
+	       priv->assoc_data->blobs_left);
 	if (priv->assoc_data->blobs_left == 0)
 		assoc_call_select_network (self);
 }
 
 static void
-assoc_add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_add_network_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	AddNetworkData *add_network_data = user_data;
 	AssocData *assoc_data;
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 	GHashTable *blobs;
 	GHashTableIter iter;
 	const char *blob_name;
 	GBytes *blob_data;
+	nm_auto_ref_string NMRefString *name_owner = NULL;
+	nm_auto_ref_string NMRefString *object_path = NULL;
+
+	g_clear_object (&add_network_data->shutdown_wait_obj);
 
 	assoc_data = add_network_data->assoc_data;
 	if (assoc_data)
 		assoc_data->add_network_data = NULL;
-	g_slice_free (AddNetworkData, add_network_data);
+	name_owner = g_steal_pointer (&add_network_data->name_owner);
+	object_path = g_steal_pointer (&add_network_data->object_path);
+	nm_g_slice_free (add_network_data);
 
-	reply = _nm_dbus_proxy_call_finish (proxy, result,
-	                                    G_VARIANT_TYPE ("(o)"),
-	                                    &error);
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 
 	if (!assoc_data) {
 		if (!error) {
-			gs_free char *net_path = NULL;
+			const char *net_path;
 
 			/* the assoc-request was already cancelled, but the AddNetwork request succeeded.
 			 * Cleanup the created network.
 			 *
 			 * This cleanup action does not work when NetworkManager is about to exit
 			 * and leaves the mainloop. During program shutdown, we may orphan networks. */
-			g_variant_get (reply, "(o)", &net_path);
-			g_dbus_proxy_call (proxy,
-			                   "RemoveNetwork",
-			                   g_variant_new ("(o)", net_path),
-			                   G_DBUS_CALL_FLAGS_NONE,
-			                   -1,
-			                   NULL,
-			                   NULL,
-			                   NULL);
+			g_variant_get (res, "(&o)", &net_path);
+			g_dbus_connection_call (G_DBUS_CONNECTION (source),
+			                        name_owner->str,
+			                        object_path->str,
+			                        NM_WPAS_DBUS_IFACE_INTERFACE,
+			                        "RemoveNetwork",
+			                        g_variant_new ("(o)", net_path),
+			                        G_VARIANT_TYPE ("()"),
+			                        G_DBUS_CALL_FLAGS_NONE,
+			                        DBUS_TIMEOUT_MSEC,
+			                        NULL,
+			                        NULL,
+			                        NULL);
 		}
 		return;
 	}
@@ -2380,44 +2161,50 @@ assoc_add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 		return;
 	}
 
-	g_variant_get (reply, "(o)", &priv->net_path);
+	nm_assert (!priv->net_path);
+	g_variant_get (res, "(o)", &priv->net_path);
 
 	/* Send blobs first; otherwise jump to selecting the network */
 	blobs = nm_supplicant_config_get_blobs (priv->assoc_data->cfg);
-	priv->assoc_data->blobs_left = g_hash_table_size (blobs);
+	priv->assoc_data->blobs_left =   blobs
+	                               ? g_hash_table_size (blobs)
+	                               : 0u;
 
-	_LOGT ("assoc[%p]: network added (%s) (%u blobs left)", priv->assoc_data, priv->net_path, priv->assoc_data->blobs_left);
+	_LOGT ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: network added (%s) (%u blobs left)",
+	       NM_HASH_OBFUSCATE_PTR (priv->assoc_data),
+	       priv->net_path,
+	       priv->assoc_data->blobs_left);
 
-	if (priv->assoc_data->blobs_left == 0)
+	if (priv->assoc_data->blobs_left == 0) {
 		assoc_call_select_network (self);
-	else {
-		g_hash_table_iter_init (&iter, blobs);
-		while (g_hash_table_iter_next (&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
-			g_dbus_proxy_call (priv->iface_proxy,
-			                   "AddBlob",
-			                   g_variant_new ("(s@ay)",
-			                                  blob_name,
-			                                  nm_utils_gbytes_to_variant_ay (blob_data)),
-			                   G_DBUS_CALL_FLAGS_NONE,
-			                   -1,
-			                   priv->assoc_data->cancellable,
-			                   (GAsyncReadyCallback) assoc_add_blob_cb,
-			                   self);
-		}
+		return;
+	}
+
+	g_hash_table_iter_init (&iter, blobs);
+	while (g_hash_table_iter_next (&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
+		_dbus_connection_call (self,
+		                       NM_WPAS_DBUS_IFACE_INTERFACE,
+		                       "AddBlob",
+		                       g_variant_new ("(s@ay)",
+		                                      blob_name,
+		                                      nm_utils_gbytes_to_variant_ay (blob_data)),
+		                       G_VARIANT_TYPE ("()"),
+		                       G_DBUS_CALL_FLAGS_NONE,
+		                       DBUS_TIMEOUT_MSEC,
+		                       priv->assoc_data->cancellable,
+		                       assoc_add_blob_cb,
+		                       self);
 	}
 }
 
 static void
-assoc_set_ap_scan_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_set_ap_scan_cb (GVariant *ret, GError *error, gpointer user_data)
 {
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
-	gs_free_error GError *error = NULL;
 	AddNetworkData *add_network_data;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
@@ -2428,23 +2215,36 @@ assoc_set_ap_scan_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 		return;
 	}
 
-	_LOGT ("assoc[%p]: set interface ap_scan to %d",
-	       priv->assoc_data,
+	_LOGT ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: interface ap_scan set to %d",
+	       NM_HASH_OBFUSCATE_PTR (priv->assoc_data),
 	       nm_supplicant_config_get_ap_scan (priv->assoc_data->cfg));
 
-	add_network_data = g_slice_new0 (AddNetworkData);
+	/* the association does not keep @self alive. We want to be able to remove
+	 * the network again, even if @self is already gone. Hence, track the data
+	 * separately.
+	 *
+	 * For that we also have a shutdown_wait_obj so that on exit we still wait
+	 * to handle the response. */
+	add_network_data = g_slice_new (AddNetworkData);
+	*add_network_data = (AddNetworkData) {
+		.assoc_data        = priv->assoc_data,
+		.name_owner        = nm_ref_string_ref (priv->name_owner),
+		.object_path       = nm_ref_string_ref (priv->object_path),
+		.shutdown_wait_obj = g_object_new (G_TYPE_OBJECT, NULL),
+	};
+	nm_shutdown_wait_obj_register_object (add_network_data->shutdown_wait_obj, "supplicant-add-network");
 	priv->assoc_data->add_network_data = add_network_data;
 
-	add_network_data->assoc_data = priv->assoc_data;
-
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "AddNetwork",
-	                   g_variant_new ("(@a{sv})", nm_supplicant_config_to_variant (priv->assoc_data->cfg)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   NULL,
-	                   (GAsyncReadyCallback) assoc_add_network_cb,
-	                   add_network_data);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "AddNetwork",
+	                       g_variant_new ("(@a{sv})", nm_supplicant_config_to_variant (priv->assoc_data->cfg)),
+	                       G_VARIANT_TYPE ("(o)"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       NULL,
+	                       assoc_add_network_cb,
+	                       add_network_data);
 }
 
 static gboolean
@@ -2491,61 +2291,64 @@ nm_supplicant_interface_assoc (NMSupplicantInterface *self,
 
 	nm_supplicant_interface_disconnect (self);
 
-	assoc_data = g_slice_new0 (AssocData);
-	priv->assoc_data = assoc_data;
+	assoc_data = g_slice_new (AssocData);
+	*assoc_data = (AssocData) {
+		.self      = self,
+		.cfg       = g_object_ref (cfg),
+		.callback  = callback,
+		.user_data = user_data,
+	};
 
-	assoc_data->self = self;
-	assoc_data->cfg = g_object_ref (cfg);
-	assoc_data->callback = callback;
-	assoc_data->user_data = user_data;
+	priv->assoc_data = assoc_data;
 
-	_LOGD ("assoc[%p]: starting association...", assoc_data);
+	_LOGD ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: starting association...",
+	       NM_HASH_OBFUSCATE_PTR (assoc_data));
 
-	/* Make sure the supplicant supports EAP-FAST before trying to send
-	 * it an EAP-FAST configuration.
-	 */
-	if (   priv->fast_support == NM_SUPPLICANT_FEATURE_NO
+	if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_FAST) == NM_TERNARY_FALSE
 	    && nm_supplicant_config_fast_required (cfg)) {
+		/* Make sure the supplicant supports EAP-FAST before trying to send
+		 * it an EAP-FAST configuration.
+		 */
 		assoc_data->fail_on_idle_id = g_idle_add (assoc_fail_on_idle_cb, self);
 		return;
 	}
 
 	assoc_data->cancellable = g_cancellable_new();
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "ApScan",
-	                                  g_variant_new_uint32 (nm_supplicant_config_get_ap_scan (priv->assoc_data->cfg))),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->assoc_data->cancellable,
-	                   (GAsyncReadyCallback) assoc_set_ap_scan_cb,
-	                   self);
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE,
+	                             "ApScan",
+	                             g_variant_new_uint32 (nm_supplicant_config_get_ap_scan (priv->assoc_data->cfg)),
+	                             DBUS_TIMEOUT_MSEC,
+	                             assoc_data->cancellable,
+	                             assoc_set_ap_scan_cb,
+	                             self);
 }
 
 /*****************************************************************************/
 
 static void
-scan_request_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+scan_request_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
 	if (error) {
 		if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError"))
-			_LOGD ("could not get scan request result: %s", error->message);
+			_LOGD ("request-scan: could not get scan request result: %s", error->message);
 		else {
 			g_dbus_error_strip_remote_error (error);
-			_LOGW ("could not get scan request result: %s", error->message);
+			_LOGW ("request-scan: could not get scan request result: %s", error->message);
 		}
-	}
+	} else
+		_LOGT ("request-scan: request scanning success");
 }
 
 void
@@ -2561,7 +2364,8 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
 
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	/* Scan parameters */
+	_LOGT ("request-scan: request scanning (%u ssids)...", ssids_len);
+
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 	g_variant_builder_add (&builder, "{sv}", "Type", g_variant_new_string ("active"));
 	g_variant_builder_add (&builder, "{sv}", "AllowRoam", g_variant_new_boolean (FALSE));
@@ -2577,14 +2381,16 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
 		g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder));
 	}
 
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "Scan",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) scan_request_cb,
-	                   self);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "Scan",
+	                       g_variant_new ("(a{sv})", &builder),
+	                       G_VARIANT_TYPE ("()"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       priv->main_cancellable,
+	                       scan_request_cb,
+	                       self);
 }
 
 /*****************************************************************************/
@@ -2597,7 +2403,23 @@ nm_supplicant_interface_get_state (NMSupplicantInterface * self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->state;
 }
 
-const char *
+void
+_nm_supplicant_interface_set_state_down (NMSupplicantInterface * self,
+                                         gboolean force_remove_from_supplicant,
+                                         const char *reason)
+{
+	set_state_down (self, force_remove_from_supplicant, reason);
+}
+
+NMRefString *
+nm_supplicant_interface_get_name_owner (NMSupplicantInterface *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->name_owner;
+}
+
+NMRefString *
 nm_supplicant_interface_get_object_path (NMSupplicantInterface *self)
 {
 	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
@@ -2610,7 +2432,7 @@ nm_supplicant_interface_get_ifname (NMSupplicantInterface *self)
 {
 	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
 
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev;
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ifname;
 }
 
 guint
@@ -2627,133 +2449,461 @@ void
 nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
                                         guint timeout)
 {
-	NMSupplicantInterfacePrivate *priv;
 	GVariantBuilder builder;
 
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 	g_return_if_fail (timeout > 0 && timeout <= 600);
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 	g_variant_builder_add (&builder, "{sv}", "Timeout", g_variant_new_int32 (timeout));
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Find",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   self);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Find",
+	                              g_variant_new ("(a{sv})", &builder),
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-find");
 }
 
 void
 nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self)
 {
-	NMSupplicantInterfacePrivate *priv;
-
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "StopFind",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) scan_request_cb,
-	                   self);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "StopFind",
+	                              NULL,
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-stop-find");
 }
 
 /*****************************************************************************/
 
 void
-nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
-                                     const char * peer,
-                                     const char * wps_method,
-                                     const char * wps_pin)
+nm_supplicant_interface_p2p_connect (NMSupplicantInterface *self,
+                                     const char *peer,
+                                     const char *wps_method,
+                                     const char *wps_pin)
 {
-	NMSupplicantInterfacePrivate *priv;
 	GVariantBuilder builder;
 
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->p2p_proxy || !priv->object_path)
-		return;
-
-	/* Connect parameters */
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 
 	g_variant_builder_add (&builder, "{sv}", "wps_method", g_variant_new_string (wps_method));
-
 	if (wps_pin)
 		g_variant_builder_add (&builder, "{sv}", "pin", g_variant_new_string (wps_pin));
-
 	g_variant_builder_add (&builder, "{sv}", "peer", g_variant_new_object_path (peer));
-
 	g_variant_builder_add (&builder, "{sv}", "join", g_variant_new_boolean (FALSE));
 	g_variant_builder_add (&builder, "{sv}", "persistent", g_variant_new_boolean (FALSE));
 	g_variant_builder_add (&builder, "{sv}", "go_intent", g_variant_new_int32 (7));
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Connect",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   "p2p connect");
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Connect",
+	                              g_variant_new ("(a{sv})", &builder),
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-connect");
 }
 
 void
 nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self)
 {
-	NMSupplicantInterfacePrivate *priv;
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Cancel",
+	                              NULL,
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-cancel");
+}
 
+void
+nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self)
+{
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Disconnect",
+	                              NULL,
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-disconnect");
+}
+
+/*****************************************************************************/
+
+static void
+_properties_changed (NMSupplicantInterface *self,
+                     const char *interface_name,
+                     GVariant *properties,
+                     gboolean initial)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gboolean is_main;
+
+	nm_assert (!properties || g_variant_is_of_type (properties, G_VARIANT_TYPE ("a{sv}")));
+
+	if (initial)
+		priv->starting_pending_count--;
+
+	if (   (initial || priv->is_ready_main)
+	    && nm_streq (interface_name, NM_WPAS_DBUS_IFACE_INTERFACE))
+		is_main = TRUE;
+	else if (   (initial || priv->is_ready_p2p_device)
+	         && nm_streq (interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) {
+		nm_assert (_get_capability (priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE);
+		is_main = FALSE;
+	} else
+		return;
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+	priv->starting_pending_count++;
 
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->p2p_proxy || !priv->object_path)
+	if (is_main) {
+		priv->is_ready_main = TRUE;
+		_properties_changed_main (self, properties);
+	} else {
+		priv->is_ready_p2p_device = TRUE;
+		_properties_changed_p2p_device (self, properties);
+	}
+
+	priv->starting_pending_count--;
+	_starting_check_ready (self);
+
+	_notify_maybe_scanning (self);
+	_notify_maybe_p2p_available (self);
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+_properties_changed_cb (GDBusConnection *connection,
+                        const char *sender_name,
+                        const char *object_path,
+                        const char *signal_interface_name,
+                        const char *signal_name,
+                        GVariant *parameters,
+                        gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	const char *interface_name;
+	gs_unref_variant GVariant *changed_properties = NULL;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
 		return;
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Cancel",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   "cancel p2p connect");
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               &interface_name,
+	               &changed_properties,
+	               NULL);
+	_properties_changed (self,
+	                     interface_name,
+	                     changed_properties,
+	                     FALSE);
 }
 
-void
-nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self)
+static void
+_bss_properties_changed_cb (GDBusConnection *connection,
+                            const char *sender_name,
+                            const char *object_path,
+                            const char *signal_interface_name,
+                            const char *signal_name,
+                            GVariant *parameters,
+                            gpointer user_data)
 {
-	NMSupplicantInterfacePrivate *priv;
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *bss_path = NULL;
+	gs_unref_variant GVariant *changed_properties = NULL;
+	NMSupplicantBssInfo *bss_info;
 
-	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	bss_path = nm_ref_string_new (object_path);
 
-	/* Don't do anything if there is no connection to the supplicant. */
-	if (!priv->p2p_proxy || !priv->object_path)
+	bss_info = g_hash_table_lookup (priv->bss_idx, &bss_path);
+	if (!bss_info)
+		return;
+	if (bss_info->_init_cancellable)
 		return;
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Disconnect",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   "p2p disconnect");
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               NULL,
+	               &changed_properties,
+	               NULL);
+	_bss_info_properties_changed (self, bss_info, changed_properties, FALSE);
+}
+
+static void
+_peer_properties_changed_cb (GDBusConnection *connection,
+                             const char *sender_name,
+                             const char *object_path,
+                             const char *signal_interface_name,
+                             const char *signal_name,
+                             GVariant *parameters,
+                             gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *peer_path = NULL;
+	gs_unref_variant GVariant *changed_properties = NULL;
+	NMSupplicantPeerInfo *peer_info;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
+
+	peer_path = nm_ref_string_new (object_path);
+
+	peer_info = g_hash_table_lookup (priv->peer_idx, &peer_path);
+	if (!peer_info)
+		return;
+	if (peer_info->_init_cancellable)
+		return;
+
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               NULL,
+	               &changed_properties,
+	               NULL);
+	_peer_info_properties_changed (self, peer_info, changed_properties, FALSE);
+}
+
+static void
+_get_all_main_cb (GVariant *result,
+                  GError *error,
+                  gpointer user_data)
+{
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
+	_properties_changed (user_data,
+	                     NM_WPAS_DBUS_IFACE_INTERFACE,
+	                     properties,
+	                     TRUE);
+}
+
+static void
+_get_all_p2p_device_cb (GVariant *result,
+                        GError *error,
+                        gpointer user_data)
+{
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
+	_properties_changed (user_data,
+	                     NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                     properties,
+	                     TRUE);
+}
+
+static void
+_signal_handle (NMSupplicantInterface *self,
+                const char *signal_interface_name,
+                const char *signal_name,
+                GVariant *parameters)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char *path;
+
+	if (nm_streq (signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE)) {
+
+		if (!priv->is_ready_main)
+			return;
+
+		if (nm_streq (signal_name, "BSSAdded")) {
+			if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sv})")))
+				return;
+
+			g_variant_get (parameters, "(&oa{sv})", &path, NULL);
+			_bss_info_add (self, path);
+			return;
+		}
+
+		if (nm_streq (signal_name, "BSSRemoved")) {
+			nm_auto_ref_string NMRefString *bss_path = NULL;
+
+			if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+				return;
+
+			g_variant_get (parameters, "(&o)", &path);
+			bss_path = nm_ref_string_new (path);
+			_bss_info_remove (self, &bss_path);
+			return;
+		}
+
+		if (nm_streq (signal_name, "EAP")) {
+			NMSupplicantAuthState auth_state = NM_SUPPLICANT_AUTH_STATE_UNKNOWN;
+			const char *status;
+			const char *parameter;
+
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ss)")))
+				return;
+
+			g_variant_get (parameters, "(&s&s)", &status, &parameter);
+
+			if (nm_streq (status, "started"))
+				auth_state = NM_SUPPLICANT_AUTH_STATE_STARTED;
+			else if (nm_streq (status, "completion")) {
+				if (nm_streq (parameter, "success"))
+					auth_state = NM_SUPPLICANT_AUTH_STATE_SUCCESS;
+				else if (nm_streq (parameter, "failure"))
+					auth_state = NM_SUPPLICANT_AUTH_STATE_FAILURE;
+			}
+
+			/* the state eventually reaches one of started, success or failure
+			 * so ignore any other intermediate (unknown) state change. */
+			if (   auth_state != NM_SUPPLICANT_AUTH_STATE_UNKNOWN
+			    && auth_state != priv->auth_state) {
+				priv->auth_state = auth_state;
+				_notify (self, PROP_AUTH_STATE);
+			}
+			return;
+		}
+
+		return;
+	}
+
+	if (nm_streq (signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) {
+
+		if (!priv->is_ready_p2p_device)
+			return;
+
+		if (nm_streq (signal_name, "DeviceFound")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) {
+				g_variant_get (parameters, "(&o)", &path);
+				_peer_info_add (self, path);
+			}
+			return;
+		}
+
+		if (nm_streq (signal_name, "DeviceLost")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) {
+				nm_auto_ref_string NMRefString *peer_path = NULL;
+
+				g_variant_get (parameters, "(&o)", &path);
+				peer_path = nm_ref_string_new (path);
+				_peer_info_remove (self, &peer_path);
+			}
+			return;
+		}
+
+		if (nm_streq (signal_name, "GroupStarted")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})"))) {
+				gs_unref_variant GVariant *args = NULL;
+				gs_unref_object NMSupplicantInterface *iface = NULL;
+				const char *group_path;
+				const char *iface_path;
+
+				g_variant_get (parameters, "(@a{sv})", &args);
+				if (!g_variant_lookup (args, "group_object", "&o", &group_path))
+					return;
+				if (!g_variant_lookup (args, "interface_object", "&o", &iface_path))
+					return;
+
+				if (nm_streq (iface_path, priv->object_path->str)) {
+					_LOGW ("P2P: GroupStarted on existing interface");
+					iface = g_object_ref (self);
+				} else {
+					iface = nm_supplicant_manager_create_interface_from_path (priv->supplicant_manager,
+					                                                          iface_path);
+					if (iface == NULL) {
+						_LOGW ("P2P: Group interface already exists in GroupStarted handler, aborting further processing.");
+						return;
+					}
+				}
+
+				/* Signal existence of the (new) interface. */
+				g_signal_emit (self, signals[GROUP_STARTED], 0, iface);
+			}
+			return;
+		}
+
+		if (nm_streq (signal_name, "GroupFinished")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})"))) {
+				gs_unref_variant GVariant *args = NULL;
+				const char *iface_path;
+
+				g_variant_get (parameters, "(@a{sv})", &args);
+
+				/* TODO: Group finished is called on the management interface!
+				 *       This means the signal consumer will currently need to assume which
+				 *       interface is finishing or it needs to match the object paths.
+				 */
+				if (!g_variant_lookup (args, "interface_object", "&o", &iface_path))
+					return;
+
+				_LOGD ("P2P: GroupFinished signal on interface %s for interface %s", priv->object_path->str, iface_path);
+
+				/* Signal group finish interface (on management interface). */
+				g_signal_emit (self, signals[GROUP_FINISHED], 0, iface_path);
+			}
+			return;
+		}
+
+		return;
+	}
+}
+
+static void
+_signal_cb (GDBusConnection *connection,
+            const char *sender_name,
+            const char *object_path,
+            const char *signal_interface_name,
+            const char *signal_name,
+            GVariant *parameters,
+            gpointer user_data)
+{
+	NMSupplicantInterface *self = user_data;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->starting_pending_count++;
+	_signal_handle (self, signal_interface_name, signal_name, parameters);
+	priv->starting_pending_count--;
+	_starting_check_ready (self);
+
+	_notify_maybe_scanning (self);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_supplicant_interface_get_p2p_available (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_capable_cached;
+}
+
+gboolean
+nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_joined_cached;
+}
+
+const char*
+nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self)
+{
+	return nm_ref_string_get_str (NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_path);
+}
+
+gboolean
+nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_owner_cached;
 }
 
 /*****************************************************************************/
@@ -2764,26 +2914,27 @@ get_property (GObject *object,
               GValue *value,
               GParamSpec *pspec)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object);
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (object);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_SCANNING:
-		g_value_set_boolean (value, priv->scanning);
+		g_value_set_boolean (value, nm_supplicant_interface_get_scanning (self));
 		break;
 	case PROP_CURRENT_BSS:
-		g_value_set_string (value, priv->current_bss);
+		g_value_set_string (value, nm_ref_string_get_str (nm_supplicant_interface_get_current_bss (self)));
 		break;
 	case PROP_P2P_GROUP_JOINED:
-		g_value_set_boolean (value, priv->p2p_capable && priv->group_proxy_acquired);
+		g_value_set_boolean (value, nm_supplicant_interface_get_p2p_group_joined (self));
 		break;
 	case PROP_P2P_GROUP_PATH:
-		g_value_set_string (value, nm_supplicant_interface_get_p2p_group_path (NM_SUPPLICANT_INTERFACE (object)));
+		g_value_set_string (value, nm_supplicant_interface_get_p2p_group_path (self));
 		break;
 	case PROP_P2P_GROUP_OWNER:
-		g_value_set_boolean (value, priv->p2p_group_owner);
+		g_value_set_boolean (value, nm_supplicant_interface_get_p2p_group_owner (self));
 		break;
 	case PROP_P2P_AVAILABLE:
-		g_value_set_boolean (value, priv->p2p_capable && priv->p2p_proxy_acquired);
+		g_value_set_boolean (value, nm_supplicant_interface_get_p2p_available (self));
 		break;
 	case PROP_AUTH_STATE:
 		g_value_set_uint (value, priv->auth_state);
@@ -2800,56 +2951,34 @@ set_property (GObject *object,
               const GValue *value,
               GParamSpec *pspec)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (object);
 
 	switch (prop_id) {
-	case PROP_IFACE:
+	case PROP_SUPPLICANT_MANAGER:
 		/* construct-only */
-		priv->dev = g_value_dup_string (value);
-		break;
-	case PROP_OBJECT_PATH:
-		/* construct-only */
-		priv->object_path = g_value_dup_string (value);
-		break;
-	case PROP_DRIVER:
-		/* construct-only */
-		priv->driver = g_value_get_uint (value);
-		break;
-	case PROP_FAST_SUPPORT:
-		/* construct-only */
-		priv->fast_support = g_value_get_int (value);
-		break;
-	case PROP_AP_SUPPORT:
-		/* construct-only */
-		priv->ap_support = g_value_get_int (value);
-		break;
-	case PROP_PMF_SUPPORT:
-		/* construct-only */
-		priv->pmf_support = g_value_get_int (value);
-		break;
-	case PROP_FILS_SUPPORT:
-		/* construct-only */
-		priv->fils_support = g_value_get_int (value);
-		break;
-	case PROP_P2P_SUPPORT:
-		/* construct-only */
-		priv->p2p_support = g_value_get_int (value);
-		break;
-	case PROP_MESH_SUPPORT:
-		/* construct-only */
-		priv->mesh_support = g_value_get_int (value);
+		priv->supplicant_manager = g_object_ref (g_value_get_pointer (value));
+		nm_assert (NM_IS_SUPPLICANT_MANAGER (priv->supplicant_manager));
+
+		priv->dbus_connection = g_object_ref (nm_supplicant_manager_get_dbus_connection (priv->supplicant_manager));
+		nm_assert (G_IS_DBUS_CONNECTION (priv->dbus_connection));
+
+		priv->name_owner = nm_ref_string_ref (nm_supplicant_manager_get_dbus_name_owner (priv->supplicant_manager));
+		nm_assert (NM_IS_REF_STRING (priv->name_owner));
+
+		priv->global_capabilities = nm_supplicant_manager_get_global_capabilities (priv->supplicant_manager);
 		break;
-	case PROP_WFD_SUPPORT:
+	case PROP_DBUS_OBJECT_PATH:
 		/* construct-only */
-		priv->wfd_support = g_value_get_int (value);
+		priv->object_path = nm_ref_string_ref (g_value_get_pointer (value));
+		nm_assert (NM_IS_REF_STRING (priv->object_path));
 		break;
-	case PROP_FT_SUPPORT:
+	case PROP_IFINDEX:
 		/* construct-only */
-		priv->ft_support_global = g_value_get_int (value);
+		priv->ifindex = g_value_get_int (value);
 		break;
-	case PROP_SHA384_SUPPORT:
+	case PROP_DRIVER:
 		/* construct-only */
-		priv->sha384_support = g_value_get_int (value);
+		priv->requested_driver = g_value_get_uint (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2857,47 +2986,179 @@ set_property (GObject *object,
 	}
 }
 
+/*****************************************************************************/
+
 static void
 nm_supplicant_interface_init (NMSupplicantInterface * self)
 {
+	NMSupplicantInterfacePrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfacePrivate);
+
+	self->_priv = priv;
+
+	nm_assert (priv->global_capabilities == NM_SUPPL_CAP_MASK_NONE);
+	nm_assert (priv->iface_capabilities == NM_SUPPL_CAP_MASK_NONE);
+
+	priv->state = NM_SUPPLICANT_INTERFACE_STATE_STARTING;
+	priv->supp_state = NM_SUPPLICANT_INTERFACE_STATE_INVALID;
+	priv->last_scan_msec = -1;
+
+	c_list_init (&self->supp_lst);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSupplicantBssInfo, bss_path) == 0);
+	priv->bss_idx = g_hash_table_new (nm_pdirect_hash, nm_pdirect_equal);
+
+	c_list_init (&priv->bss_lst_head);
+	c_list_init (&priv->bss_initializing_lst_head);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSupplicantPeerInfo, peer_path) == 0);
+	priv->peer_idx = g_hash_table_new (nm_pdirect_hash, nm_pdirect_equal);
+
+	c_list_init (&priv->peer_lst_head);
+	c_list_init (&priv->peer_initializing_lst_head);
+
+	priv->main_cancellable = g_cancellable_new ();
+}
+
+static void
+constructed (GObject *object)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (object);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT;
-	priv->bss_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, bss_data_destroy);
-	priv->peer_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, peer_data_destroy);
+	G_OBJECT_CLASS (nm_supplicant_interface_parent_class)->constructed (object);
+
+	_LOGD ("new supplicant interface %s on %s",
+	       priv->object_path->str,
+	       priv->name_owner->str);
+
+	priv->properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+	                                                                                      priv->name_owner->str,
+	                                                                                      priv->object_path->str,
+	                                                                                      NULL,
+	                                                                                      _properties_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+
+	priv->bss_properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+	                                                                                          priv->name_owner->str,
+	                                                                                          NULL,
+	                                                                                          NM_WPAS_DBUS_IFACE_BSS,
+	                                                                                          _bss_properties_changed_cb,
+	                                                                                          self,
+	                                                                                          NULL);
+
+	priv->signal_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+	                                                      priv->name_owner->str,
+	                                                      NULL,
+	                                                      NULL,
+	                                                      priv->object_path->str,
+	                                                      NULL,
+	                                                      G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                      _signal_cb,
+	                                                      self,
+	                                                      NULL);
+
+	/* Scan result aging parameters */
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE,
+	                             "BSSExpireAge",
+	                             g_variant_new_uint32 (250),
+	                             DBUS_TIMEOUT_MSEC,
+	                             NULL,
+	                             NULL,
+	                             NULL);
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE,
+	                             "BSSExpireCount",
+	                             g_variant_new_uint32 (2),
+	                             DBUS_TIMEOUT_MSEC,
+	                             NULL,
+	                             NULL,
+	                             NULL);
+
+	if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF) == NM_TERNARY_TRUE) {
+		/* Initialize global PMF setting to 'optional' */
+		nm_dbus_connection_call_set (priv->dbus_connection,
+		                             priv->name_owner->str,
+		                             priv->object_path->str,
+		                             NM_WPAS_DBUS_IFACE_INTERFACE,
+		                             "Pmf",
+		                             g_variant_new_string ("1"),
+		                             DBUS_TIMEOUT_MSEC,
+		                             NULL,
+		                             NULL,
+		                             NULL);
+	}
+
+	if (_get_capability (priv, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_DEFAULT) {
+		/* If the global supplicant capabilities property is not present, we can
+		 * fall back to checking whether the ProbeRequest method is supported.  If
+		 * neither of these works we have no way of determining if AP mode is
+		 * supported or not.  hostap 1.0 and earlier don't support either of these.
+		 */
+		priv->starting_pending_count++;
+		_dbus_connection_call (self,
+		                       DBUS_INTERFACE_INTROSPECTABLE,
+		                       "Introspect",
+		                       NULL,
+		                       G_VARIANT_TYPE ("(s)"),
+		                       G_DBUS_CALL_FLAGS_NONE,
+		                       5000,
+		                       priv->main_cancellable,
+		                       iface_introspect_cb,
+		                       self);
+	}
+
+	priv->starting_pending_count++;
+	nm_dbus_connection_call_get_all (priv->dbus_connection,
+	                                 priv->name_owner->str,
+	                                 priv->object_path->str,
+	                                 NM_WPAS_DBUS_IFACE_INTERFACE,
+	                                 5000,
+	                                 priv->main_cancellable,
+	                                 _get_all_main_cb,
+	                                 self);
+
+	if (_get_capability (priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE) {
+		priv->peer_properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+		                                                                                           priv->name_owner->str,
+		                                                                                           NULL,
+		                                                                                           NM_WPAS_DBUS_IFACE_PEER,
+		                                                                                           _peer_properties_changed_cb,
+		                                                                                           self,
+		                                                                                           NULL);
+
+		priv->starting_pending_count++;
+		nm_dbus_connection_call_get_all (priv->dbus_connection,
+		                                 priv->name_owner->str,
+		                                 priv->object_path->str,
+		                                 NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+		                                 5000,
+		                                 priv->main_cancellable,
+		                                 _get_all_p2p_device_cb,
+		                                 self);
+	}
 }
 
 NMSupplicantInterface *
-nm_supplicant_interface_new (const char *ifname,
-                             const char *object_path,
-                             NMSupplicantDriver driver,
-                             NMSupplicantFeature fast_support,
-                             NMSupplicantFeature ap_support,
-                             NMSupplicantFeature pmf_support,
-                             NMSupplicantFeature fils_support,
-                             NMSupplicantFeature p2p_support,
-                             NMSupplicantFeature mesh_support,
-                             NMSupplicantFeature wfd_support,
-                             NMSupplicantFeature ft_support,
-                             NMSupplicantFeature sha384_support)
-{
-	/* One of ifname or path need to be set */
-	g_return_val_if_fail (ifname != NULL || object_path != NULL, NULL);
-	g_return_val_if_fail (ifname == NULL || object_path == NULL, NULL);
+nm_supplicant_interface_new (NMSupplicantManager *supplicant_manager,
+                             NMRefString *object_path,
+                             int ifindex,
+                             NMSupplicantDriver driver)
+{
+	nm_assert (NM_IS_SUPPLICANT_MANAGER (supplicant_manager));
 
 	return g_object_new (NM_TYPE_SUPPLICANT_INTERFACE,
-	                     NM_SUPPLICANT_INTERFACE_IFACE, ifname,
-	                     NM_SUPPLICANT_INTERFACE_OBJECT_PATH, object_path,
+	                     NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, supplicant_manager,
+	                     NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, object_path,
+	                     NM_SUPPLICANT_INTERFACE_IFINDEX, ifindex,
 	                     NM_SUPPLICANT_INTERFACE_DRIVER, (guint) driver,
-	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, (int) fast_support,
-	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
-	                     NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, (int) pmf_support,
-	                     NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, (int) fils_support,
-	                     NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, (int) p2p_support,
-	                     NM_SUPPLICANT_INTERFACE_MESH_SUPPORT, (int) mesh_support,
-	                     NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, (int) wfd_support,
-	                     NM_SUPPLICANT_INTERFACE_FT_SUPPORT, (int) ft_support,
-	                     NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT, (int) sha384_support,
 	                     NULL);
 }
 
@@ -2907,7 +3168,11 @@ dispose (GObject *object)
 	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (object);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	nm_supplicant_interface_cancel_wps (self);
+	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN)
+		set_state_down (self, TRUE, "NMSupplicantInterface is disposing");
+
+	nm_assert (c_list_is_empty (&self->supp_lst));
+
 	if (priv->wps_data) {
 		/* we shut down, but an asynchronous Cancel request is pending.
 		 * We don't want to cancel it, so mark wps-data that @self is gone.
@@ -2917,38 +3182,21 @@ dispose (GObject *object)
 		priv->wps_data = NULL;
 	}
 
-	if (priv->assoc_data) {
-		gs_free_error GError *error = NULL;
-
-		nm_utils_error_set_cancelled (&error, TRUE, "NMSupplicantInterface");
-		assoc_return (self, error, "cancelled due to dispose of supplicant interface");
-	}
-
-	if (priv->iface_proxy)
-		g_signal_handlers_disconnect_by_data (priv->iface_proxy, object);
-	g_clear_object (&priv->iface_proxy);
-	if (priv->p2p_proxy)
-		g_signal_handlers_disconnect_by_data (priv->p2p_proxy, object);
-	g_clear_object (&priv->p2p_proxy);
-	if (priv->group_proxy)
-		g_signal_handlers_disconnect_by_data (priv->group_proxy, object);
-	g_clear_object (&priv->group_proxy);
+	nm_assert (!priv->assoc_data);
 
-	nm_clear_g_cancellable (&priv->init_cancellable);
-	nm_clear_g_cancellable (&priv->other_cancellable);
+	nm_clear_pointer (&priv->bss_idx, g_hash_table_destroy);
+	nm_clear_pointer (&priv->peer_idx, g_hash_table_destroy);
 
-	if (priv->wpas_proxy)
-		g_signal_handlers_disconnect_by_data (priv->wpas_proxy, object);
-	g_clear_object (&priv->wpas_proxy);
-	g_clear_pointer (&priv->bss_proxies, g_hash_table_destroy);
-	g_clear_pointer (&priv->peer_proxies, g_hash_table_destroy);
-
-	g_clear_pointer (&priv->net_path, g_free);
-	g_clear_pointer (&priv->dev, g_free);
-	g_clear_pointer (&priv->object_path, g_free);
-	g_clear_pointer (&priv->current_bss, g_free);
+	nm_clear_pointer (&priv->current_bss, nm_ref_string_unref);
 
 	G_OBJECT_CLASS (nm_supplicant_interface_parent_class)->dispose (object);
+
+	nm_clear_pointer (&priv->object_path, nm_ref_string_unref);
+	nm_clear_pointer (&priv->name_owner, nm_ref_string_unref);
+	g_clear_object (&priv->supplicant_manager);
+	g_clear_object (&priv->dbus_connection);
+	nm_clear_g_free (&priv->ifname);
+	nm_assert (!priv->net_path);
 }
 
 static void
@@ -2956,10 +3204,36 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	object_class->dispose = dispose;
+	g_type_class_add_private (object_class, sizeof (NMSupplicantInterfacePrivate));
+
+	object_class->constructed  = constructed;
+	object_class->dispose      = dispose;
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 
+	obj_properties[PROP_SUPPLICANT_MANAGER] =
+	    g_param_spec_pointer (NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, "", "",
+	                          G_PARAM_WRITABLE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_DBUS_OBJECT_PATH] =
+	    g_param_spec_pointer (NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, "", "",
+	                          G_PARAM_WRITABLE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_IFINDEX] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_IFINDEX, "", "",
+	                      0, G_MAXINT, 0,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_DRIVER] =
+	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "",
+	                       0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS,
+	                       G_PARAM_WRITABLE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_SCANNING] =
 	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_SCANNING, "", "",
 	                          FALSE,
@@ -2970,18 +3244,6 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                         NULL,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_IFACE] =
-	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_IFACE, "", "",
-	                         NULL,
-	                         G_PARAM_WRITABLE |
-	                         G_PARAM_CONSTRUCT_ONLY |
-	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_OBJECT_PATH] =
-	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_OBJECT_PATH, "", "",
-	                         NULL,
-	                         G_PARAM_WRITABLE |
-	                         G_PARAM_CONSTRUCT_ONLY |
-	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_P2P_GROUP_JOINED] =
 	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED, "", "",
 	                          FALSE,
@@ -2997,89 +3259,11 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                          FALSE,
 	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_DRIVER] =
-	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "",
-	                       0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS,
-	                       G_PARAM_WRITABLE |
-	                       G_PARAM_CONSTRUCT_ONLY |
-	                       G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_P2P_AVAILABLE] =
 	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, "", "",
 	                          FALSE,
 	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_FAST_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_AP_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_AP_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_PMF_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_FILS_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_P2P_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_MESH_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_MESH_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_WFD_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_FT_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FT_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_SHA384_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_AUTH_STATE] =
 	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_AUTH_STATE, "", "",
 	                       NM_SUPPLICANT_AUTH_STATE_UNKNOWN,
@@ -3098,61 +3282,21 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
 
-	signals[REMOVED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
-
-	signals[BSS_UPDATED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_UPDATED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
-
-	signals[BSS_REMOVED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
-	signals[PEER_UPDATED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
-
-	signals[PEER_REMOVED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
-	signals[SCAN_DONE] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_SCAN_DONE,
+	signals[BSS_CHANGED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_LAST,
 	                  0,
 	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+	                  G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
 
-	signals[CREDENTIALS_REQUEST] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST,
+	signals[PEER_CHANGED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_LAST,
 	                  0,
 	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+	                  G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
 
 	signals[WPS_CREDENTIALS] =
 	    g_signal_new (NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
@@ -3177,12 +3321,4 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
-	signals[GROUP_FORMATION_FAILURE] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_STRING);
 }
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index 3580f754..eb414f26 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -9,15 +9,17 @@
 
 #include "nm-supplicant-types.h"
 
+#include "c-list/src/c-list.h"
+
 /*
  * Supplicant interface states
  *   A mix of wpa_supplicant interface states and internal states.
  */
 typedef enum {
-	NM_SUPPLICANT_INTERFACE_STATE_INVALID = -1,
-	NM_SUPPLICANT_INTERFACE_STATE_INIT = 0,
-	NM_SUPPLICANT_INTERFACE_STATE_STARTING,
-	NM_SUPPLICANT_INTERFACE_STATE_READY,
+	NM_SUPPLICANT_INTERFACE_STATE_INVALID = 0,
+
+	NM_SUPPLICANT_INTERFACE_STATE_STARTING = 1,
+
 	NM_SUPPLICANT_INTERFACE_STATE_DISABLED,
 	NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED,
 	NM_SUPPLICANT_INTERFACE_STATE_INACTIVE,
@@ -28,9 +30,24 @@ typedef enum {
 	NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
 	NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE,
 	NM_SUPPLICANT_INTERFACE_STATE_COMPLETED,
+
 	NM_SUPPLICANT_INTERFACE_STATE_DOWN,
 } NMSupplicantInterfaceState;
 
+static inline gboolean
+nm_supplicant_interface_state_is_operational (NMSupplicantInterfaceState state)
+{
+	return    state > NM_SUPPLICANT_INTERFACE_STATE_STARTING
+	       && state < NM_SUPPLICANT_INTERFACE_STATE_DOWN;
+}
+
+static inline gboolean
+nm_supplicant_interface_state_is_associated (NMSupplicantInterfaceState state)
+{
+	return    state >= NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING
+	       && state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED;
+}
+
 typedef enum {
 	NM_SUPPLICANT_AUTH_STATE_UNKNOWN,
 	NM_SUPPLICANT_AUTH_STATE_STARTED,
@@ -46,60 +63,48 @@ typedef enum {
 #define NM_IS_SUPPLICANT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_SUPPLICANT_INTERFACE))
 #define NM_SUPPLICANT_INTERFACE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass))
 
-/* Properties */
-#define NM_SUPPLICANT_INTERFACE_IFACE            "iface"
-#define NM_SUPPLICANT_INTERFACE_OBJECT_PATH      "object-path"
-#define NM_SUPPLICANT_INTERFACE_SCANNING         "scanning"
-#define NM_SUPPLICANT_INTERFACE_CURRENT_BSS      "current-bss"
-#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED "p2p-group-joined"
-#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH   "p2p-group-path"
-#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER  "p2p-group-owner"
-#define NM_SUPPLICANT_INTERFACE_DRIVER           "driver"
-#define NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE    "p2p-available"
-#define NM_SUPPLICANT_INTERFACE_FAST_SUPPORT     "fast-support"
-#define NM_SUPPLICANT_INTERFACE_AP_SUPPORT       "ap-support"
-#define NM_SUPPLICANT_INTERFACE_PMF_SUPPORT      "pmf-support"
-#define NM_SUPPLICANT_INTERFACE_FILS_SUPPORT     "fils-support"
-#define NM_SUPPLICANT_INTERFACE_P2P_SUPPORT      "p2p-support"
-#define NM_SUPPLICANT_INTERFACE_MESH_SUPPORT     "mesh-support"
-#define NM_SUPPLICANT_INTERFACE_WFD_SUPPORT      "wfd-support"
-#define NM_SUPPLICANT_INTERFACE_FT_SUPPORT       "ft-support"
-#define NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT   "sha384-support"
-#define NM_SUPPLICANT_INTERFACE_AUTH_STATE       "auth-state"
-
-/* Signals */
-#define NM_SUPPLICANT_INTERFACE_STATE            "state"
-#define NM_SUPPLICANT_INTERFACE_REMOVED          "removed"
-#define NM_SUPPLICANT_INTERFACE_BSS_UPDATED      "bss-updated"
-#define NM_SUPPLICANT_INTERFACE_BSS_REMOVED      "bss-removed"
-#define NM_SUPPLICANT_INTERFACE_PEER_UPDATED     "peer-updated"
-#define NM_SUPPLICANT_INTERFACE_PEER_REMOVED     "peer-removed"
-#define NM_SUPPLICANT_INTERFACE_SCAN_DONE        "scan-done"
-#define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request"
-#define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS  "wps-credentials"
+#define NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER  "supplicant-manager"
+#define NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH    "dbus-object-path"
+#define NM_SUPPLICANT_INTERFACE_IFINDEX             "ifindex"
+#define NM_SUPPLICANT_INTERFACE_SCANNING            "scanning"
+#define NM_SUPPLICANT_INTERFACE_CURRENT_BSS         "current-bss"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED    "p2p-group-joined"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH      "p2p-group-path"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER     "p2p-group-owner"
+#define NM_SUPPLICANT_INTERFACE_DRIVER              "driver"
+#define NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE       "p2p-available"
+#define NM_SUPPLICANT_INTERFACE_AUTH_STATE          "auth-state"
+
+#define NM_SUPPLICANT_INTERFACE_STATE                   "state"
+#define NM_SUPPLICANT_INTERFACE_BSS_CHANGED             "bss-changed"
+#define NM_SUPPLICANT_INTERFACE_PEER_CHANGED            "peer-changed"
+#define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS         "wps-credentials"
 #define NM_SUPPLICANT_INTERFACE_GROUP_STARTED           "group-started"
 #define NM_SUPPLICANT_INTERFACE_GROUP_FINISHED          "group-finished"
-#define NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE "group-formation-failure"
 
 typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass;
 
+struct _NMSupplicantInterfacePrivate;
+
+struct _NMSupplicantInterface {
+	GObject parent;
+	CList supp_lst;
+	struct _NMSupplicantInterfacePrivate *_priv;
+};
+
 GType nm_supplicant_interface_get_type (void);
 
-NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
-                                                     const char *object_path,
-                                                     NMSupplicantDriver driver,
-                                                     NMSupplicantFeature fast_support,
-                                                     NMSupplicantFeature ap_support,
-                                                     NMSupplicantFeature pmf_support,
-                                                     NMSupplicantFeature fils_support,
-                                                     NMSupplicantFeature p2p_support,
-                                                     NMSupplicantFeature mesh_support,
-                                                     NMSupplicantFeature wfd_support,
-                                                     NMSupplicantFeature ft_support,
-                                                     NMSupplicantFeature sha384_support);
-
-void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
-                                                       gboolean available);
+NMSupplicantInterface *nm_supplicant_interface_new (NMSupplicantManager *supplicant_manager,
+                                                    NMRefString *object_path,
+                                                    int ifindex,
+                                                    NMSupplicantDriver driver);
+
+NMRefString *nm_supplicant_interface_get_name_owner (NMSupplicantInterface *self);
+NMRefString *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface);
+
+void _nm_supplicant_interface_set_state_down (NMSupplicantInterface * self,
+                                              gboolean force_remove_from_supplicant,
+                                              const char *reason);
 
 typedef void (*NMSupplicantInterfaceAssocCb) (NMSupplicantInterface *iface,
                                               GError *error,
@@ -123,8 +128,6 @@ nm_supplicant_interface_disconnect_async (NMSupplicantInterface * self,
                                           NMSupplicantInterfaceDisconnectCb callback,
                                           gpointer user_data);
 
-const char *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface);
-
 void nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
                                            GBytes *const*ssids,
                                            guint ssids_len);
@@ -135,7 +138,7 @@ const char *nm_supplicant_interface_state_to_string (NMSupplicantInterfaceState
 
 gboolean nm_supplicant_interface_get_scanning (NMSupplicantInterface *self);
 
-const char *nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self);
+NMRefString *nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self);
 
 gint64 nm_supplicant_interface_get_last_scan (NMSupplicantInterface *self);
 
@@ -143,7 +146,7 @@ const char *nm_supplicant_interface_get_ifname (NMSupplicantInterface *self);
 
 guint nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self);
 
-gboolean nm_supplicant_interface_get_has_credentials_request (NMSupplicantInterface *self);
+gboolean nm_supplicant_interface_get_p2p_available (NMSupplicantInterface *self);
 
 gboolean nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self);
 
@@ -151,11 +154,6 @@ const char* nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *s
 
 gboolean nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self);
 
-gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
-                                                    const char *field,
-                                                    const char *value,
-                                                    GError **error);
-
 void nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
                                              guint timeout);
 void nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self);
@@ -167,41 +165,13 @@ void nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
 void nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self);
 void nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self);
 
-NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_mesh_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_ft_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_sha384_support (NMSupplicantInterface *self);
-
-void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
-                                             NMSupplicantFeature apmode);
-
-void nm_supplicant_interface_set_fast_support (NMSupplicantInterface *self,
-                                               NMSupplicantFeature fast_support);
-
-void nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
-                                              NMSupplicantFeature pmf_support);
-
-void nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
-                                               NMSupplicantFeature fils_support);
-
-void nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
-                                              NMSupplicantFeature p2p_support);
-
-void nm_supplicant_interface_set_mesh_support (NMSupplicantInterface *self,
-                                               NMSupplicantFeature mesh_support);
-
-void nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
-                                              NMSupplicantFeature wfd_support);
+void nm_supplicant_interface_set_global_capabilities (NMSupplicantInterface *self,
+                                                      NMSupplCapMask value);
 
-void nm_supplicant_interface_set_ft_support (NMSupplicantInterface *self,
-                                             NMSupplicantFeature ft_support);
+NMTernary nm_supplicant_interface_get_capability (NMSupplicantInterface *self,
+                                                  NMSupplCapType type);
 
-void nm_supplicant_interface_set_sha384_support (NMSupplicantInterface *self,
-                                                 NMSupplicantFeature sha384_support);
+NMSupplCapMask nm_supplicant_interface_get_capabilities (NMSupplicantInterface *self);
 
 void nm_supplicant_interface_enroll_wps (NMSupplicantInterface *self,
                                          const char *const type,
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
index 49581f6e..cf5cf111 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -8,29 +8,64 @@
 
 #include "nm-supplicant-manager.h"
 
+#include "nm-core-internal.h"
+#include "nm-dbus-manager.h"
+#include "nm-glib-aux/nm-dbus-aux.h"
+#include "nm-glib-aux/nm-ref-string.h"
 #include "nm-supplicant-interface.h"
 #include "nm-supplicant-types.h"
-#include "nm-core-internal.h"
+#include "platform/nm-platform.h"
 
 /*****************************************************************************/
 
+#define CREATE_IFACE_TRY_COUNT_MAX 7u
+
+struct _NMSupplMgrCreateIfaceHandle {
+	NMSupplicantManager *self;
+	CList create_iface_lst;
+	GCancellable *cancellable;
+	NMSupplicantManagerCreateInterfaceCb callback;
+	gpointer callback_user_data;
+	NMShutdownWaitObjHandle *shutdown_handle;
+	NMRefString *name_owner;
+	GError *fail_on_idle_error;
+	NMSupplicantDriver driver;
+	int ifindex;
+	guint fail_on_idle_id;
+	guint create_iface_try_count:5;
+};
+
+enum {
+	AVAILABLE_CHANGED,
+	LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
 typedef struct {
-	GDBusProxy *     proxy;
-	GCancellable *   cancellable;
-	gboolean         running;
-
-	GSList          *ifaces;
-	NMSupplicantFeature fast_support;
-	NMSupplicantFeature ap_support;
-	NMSupplicantFeature pmf_support;
-	NMSupplicantFeature fils_support;
-	NMSupplicantFeature p2p_support;
-	NMSupplicantFeature mesh_support;
-	NMSupplicantFeature wfd_support;
-	NMSupplicantFeature ft_support;
-	NMSupplicantFeature sha384_support;
-	guint             die_count_reset_id;
-	guint             die_count;
+	GDBusConnection *dbus_connection;
+
+	NMRefString *name_owner;
+
+	GCancellable *get_name_owner_cancellable;
+	GCancellable *get_capabilities_cancellable;
+	GCancellable *poke_name_owner_cancellable;
+
+	GHashTable *supp_ifaces;
+	CList supp_lst_head;
+
+	CList create_iface_lst_head;
+
+	NMSupplCapMask capabilities;
+
+	guint name_owner_changed_id;
+	guint interface_removed_id;
+	guint poke_name_owner_timeout_id;
+	guint available_reset_id;
+
+	/* see nm_supplicant_manager_get_available(). */
+	NMTernary available:2;
+
 } NMSupplicantManagerPrivate;
 
 struct _NMSupplicantManager {
@@ -46,6 +81,8 @@ G_DEFINE_TYPE (NMSupplicantManager, nm_supplicant_manager, G_TYPE_OBJECT)
 
 #define NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantManager, NM_IS_SUPPLICANT_MANAGER)
 
+NM_DEFINE_SINGLETON_GETTER (NMSupplicantManager, nm_supplicant_manager_get, NM_TYPE_SUPPLICANT_MANAGER);
+
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN      LOGD_SUPPLICANT
@@ -57,72 +94,140 @@ NM_CACHED_QUARK_FCN ("nm-supplicant-error-quark", nm_supplicant_error_quark)
 
 /*****************************************************************************/
 
-static gboolean
-die_count_exceeded (guint32 count)
+static void _create_iface_proceed_all (NMSupplicantManager *self,
+                                       GError *error);
+static void _supp_iface_add (NMSupplicantManager *self,
+                             NMRefString *iface_path,
+                             NMSupplicantInterface *supp_iface);
+static void _supp_iface_remove_one (NMSupplicantManager *self,
+                                    NMSupplicantInterface *supp_iface,
+                                    gboolean force_remove_from_supplicant,
+                                    const char *reason);
+static void _create_iface_dbus_call_get_interface (NMSupplicantManager *self,
+                                                   NMSupplMgrCreateIfaceHandle *handle,
+                                                   const char *ifname);
+static void _create_iface_dbus_call_create_interface (NMSupplicantManager *self,
+                                                      NMSupplMgrCreateIfaceHandle *handle,
+                                                      const char *ifname);
+static gboolean _create_iface_fail_on_idle_cb (gpointer user_data);
+
+static gboolean _available_reset_cb (gpointer user_data);
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE (nm_supplicant_driver_to_string, NMSupplicantDriver,
+	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_UNKNOWN,  "???"),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_WIRELESS, NM_WPAS_DEFAULT_WIFI_DRIVER),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_WIRED,    "wired"),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_MACSEC,   "macsec_linux"),
+);
+
+/*****************************************************************************/
+
+NMTernary
+nm_supplicant_manager_is_available (NMSupplicantManager *self)
 {
-	return count > 2;
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NM_TERNARY_FALSE);
+
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->available;
 }
 
-static gboolean
-is_available (NMSupplicantManager *self)
+NMRefString *
+nm_supplicant_manager_get_dbus_name_owner (NMSupplicantManager *self)
 {
-	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
 
-	return    priv->running
-	       && !die_count_exceeded (priv->die_count);
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->name_owner;
+}
+
+GDBusConnection *nm_supplicant_manager_get_dbus_connection (NMSupplicantManager *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
+
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->dbus_connection;
+}
+
+NMSupplCapMask
+nm_supplicant_manager_get_global_capabilities (NMSupplicantManager *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NM_SUPPL_CAP_MASK_NONE);
+
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->capabilities;
 }
 
 /*****************************************************************************/
 
 static void
-_sup_iface_last_ref (gpointer data,
-                     GObject *object,
-                     gboolean is_last_ref)
+_caps_set (NMSupplicantManagerPrivate *priv,
+           NMSupplCapType type,
+           NMTernary value)
 {
-	NMSupplicantManager *self = data;
-	NMSupplicantManagerPrivate *priv;
-	NMSupplicantInterface *sup_iface = (NMSupplicantInterface *) object;
-	const char *op;
+	priv->capabilities = NM_SUPPL_CAP_MASK_SET (priv->capabilities, type, value);
+}
 
-	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
-	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (sup_iface));
-	g_return_if_fail (is_last_ref);
+static char
+_caps_to_char (NMSupplicantManagerPrivate *priv,
+               NMSupplCapType type)
+{
+	NMTernary val;
+
+	val = NM_SUPPL_CAP_MASK_GET (priv->capabilities, type);
+	if (val == NM_TERNARY_TRUE)
+		return '+';
+	if (val == NM_TERNARY_FALSE)
+		return '-';
+	return '?';
+}
 
-	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+/*****************************************************************************/
 
-	if (!g_slist_find (priv->ifaces, sup_iface))
-		g_return_if_reached ();
-
-	/* Ask wpa_supplicant to remove this interface */
-	if (   priv->running
-	    && priv->proxy
-	    && (op = nm_supplicant_interface_get_object_path (sup_iface))) {
-		g_dbus_proxy_call (priv->proxy,
-		                   "RemoveInterface",
-		                   g_variant_new ("(o)", op),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   3000,
-		                   NULL,
-		                   NULL,
-		                   NULL);
-	}
+static void
+_dbus_call_remove_interface (GDBusConnection *dbus_connection,
+                             const char *name_owner,
+                             const char *iface_path)
+{
+	nm_assert (G_IS_DBUS_CONNECTION (dbus_connection));
+	nm_assert (name_owner);
+	nm_assert (iface_path);
+
+	g_dbus_connection_call (dbus_connection,
+	                        name_owner,
+	                        NM_WPAS_DBUS_PATH,
+	                        NM_WPAS_DBUS_INTERFACE,
+	                        "RemoveInterface",
+	                        g_variant_new ("(o)", iface_path),
+	                        G_VARIANT_TYPE ("()"),
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        10000,
+	                        NULL,
+	                        NULL,
+	                        NULL);
+}
 
-	priv->ifaces = g_slist_remove (priv->ifaces, sup_iface);
-	g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self);
+void
+_nm_supplicant_manager_dbus_call_remove_interface (NMSupplicantManager *self,
+                                                   const char *name_owner,
+                                                   const char *iface_path)
+{
+	_dbus_call_remove_interface (NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->dbus_connection,
+	                             name_owner,
+	                             iface_path);
 }
 
+/*****************************************************************************/
+
 static void
 on_supplicant_wfd_ies_set (GObject *source_object,
-                           GAsyncResult *res,
+                           GAsyncResult *result,
                            gpointer user_data)
 {
-	gs_unref_variant GVariant *result = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
-
-	if (!result)
-		_LOGW ("failed to set WFD IEs on wpa_supplicant: %s", error->message);
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), result, &error);
+	if (!res)
+		_LOGD ("failed to set WFD IEs on wpa_supplicant: %s", error->message);
 }
 
 /**
@@ -140,172 +245,677 @@ nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
 {
 	NMSupplicantManagerPrivate *priv;
 	GVariantBuilder params;
-	GVariant *val;
 
 	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
 
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	_LOGD ("setting WFD IEs for P2P operation");
+	if (!priv->name_owner)
+		return;
 
-	if (wfd_ies)
-		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                 g_bytes_get_data (wfd_ies, NULL),
-		                                 g_bytes_get_size (wfd_ies),
-		                                 sizeof (guint8));
-	else
-		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                 NULL, 0, sizeof (guint8));
+	_LOGD ("setting WFD IEs for P2P operation on %s", priv->name_owner->str);
 
 	g_variant_builder_init (&params, G_VARIANT_TYPE ("(ssv)"));
 
-	g_variant_builder_add (&params, "s", g_dbus_proxy_get_interface_name (priv->proxy));
+	g_variant_builder_add (&params, "s", NM_WPAS_DBUS_INTERFACE);
 	g_variant_builder_add (&params, "s", "WFDIEs");
-	g_variant_builder_add_value (&params, g_variant_new_variant (val));
+	g_variant_builder_add_value (&params,
+	                             g_variant_new_variant (nm_utils_gbytes_to_variant_ay (wfd_ies)));
 
-	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
-	                        g_dbus_proxy_get_name (priv->proxy),
-	                        g_dbus_proxy_get_object_path (priv->proxy),
-	                        "org.freedesktop.DBus.Properties",
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner->str,
+	                        NM_WPAS_DBUS_PATH,
+	                        DBUS_INTERFACE_PROPERTIES,
 	                        "Set",
 	                        g_variant_builder_end (&params),
-	                        G_VARIANT_TYPE_UNIT,
+	                        G_VARIANT_TYPE ("()"),
 	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
-	                        1000,
+	                        3000,
 	                        NULL,
 	                        on_supplicant_wfd_ies_set,
 	                        NULL);
 }
 
-/**
- * nm_supplicant_manager_create_interface:
- * @self: the #NMSupplicantManager
- * @ifname: the interface for which to obtain the supplicant interface
- * @is_wireless: whether the interface is supposed to be wireless.
- *
- * Note: the manager owns a reference to the instance and the only way to
- *   get the manager to release it, is by dropping all other references
- *   to the supplicant-interface (or destroying the manager).
- *
- * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL.
- *   Must be unrefed at the end.
- * */
-NMSupplicantInterface *
+/*****************************************************************************/
+
+static gboolean
+_poke_name_owner_timeout_cb (gpointer user_data)
+{
+	NMSupplicantManager *self = user_data;
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
+	gboolean available_changed = FALSE;
+
+	nm_assert (!priv->name_owner);
+
+	priv->poke_name_owner_timeout_id = 0;
+	nm_clear_g_cancellable (&priv->poke_name_owner_cancellable);
+
+	_LOGT ("poke service \"%s\" failed for good with timeout%s",
+	       NM_WPAS_DBUS_SERVICE,
+	         (priv->available == NM_TERNARY_DEFAULT)
+	       ? " (set as not available)"
+	       : "");
+
+	if (priv->available == NM_TERNARY_DEFAULT) {
+		/* the available flag usually only changes together with the name-owner.
+		 * However, if we tries to poke the service but failed to start it (with
+		 * timeout), was also set it as (hard) not available. */
+		priv->available = NM_TERNARY_FALSE;
+		nm_clear_g_source (&priv->available_reset_id);
+		priv->available_reset_id = g_timeout_add_seconds (60,
+		                                                  _available_reset_cb,
+		                                                  self);
+		available_changed = TRUE;
+	}
+
+	nm_utils_error_set (&error,
+	                    NM_UTILS_ERROR_UNKNOWN,
+	                    "Failed to D-Bus activate wpa_supplicant service");
+
+	_create_iface_proceed_all (self, error);
+
+	if (available_changed) {
+		/* We delay the emitting of the notification after aborting all
+		 * create-iface handles. */
+		g_signal_emit (self, signals[AVAILABLE_CHANGED], 0);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_poke_name_owner_cb (GObject *source,
+                     GAsyncResult *result,
+                     gpointer user_data)
+{
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (!res)
+		_LOGT ("poke service \"%s\" failed: %s", NM_WPAS_DBUS_SERVICE, error->message);
+	else
+		_LOGT ("poke service \"%s\" succeeded", NM_WPAS_DBUS_SERVICE);
+
+	/* in both cases, we react the same: we wait for the name owner to appear
+	 * or hit the timeout. */
+}
+
+static void
+_poke_name_owner (NMSupplicantManager *self)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	if (priv->poke_name_owner_cancellable)
+		return;
+
+	_LOGT ("poke service \"%s\"...", NM_WPAS_DBUS_SERVICE);
+
+	priv->poke_name_owner_cancellable = g_cancellable_new ();
+	priv->poke_name_owner_timeout_id = g_timeout_add (3000,
+	                                                  _poke_name_owner_timeout_cb,
+	                                                  self);
+	nm_dbus_connection_call_start_service_by_name (priv->dbus_connection,
+	                                               NM_WPAS_DBUS_SERVICE,
+	                                               5000,
+	                                               priv->poke_name_owner_cancellable,
+	                                               _poke_name_owner_cb,
+	                                               self);
+}
+
+/*****************************************************************************/
+
+static void
+_create_iface_complete (NMSupplMgrCreateIfaceHandle *handle,
+                        NMSupplicantInterface *supp_iface,
+                        GError *error)
+{
+	nm_assert (!supp_iface || NM_IS_SUPPLICANT_INTERFACE (supp_iface));
+	nm_assert ((!!supp_iface) != (!!error));
+
+	c_list_unlink (&handle->create_iface_lst);
+
+	nm_clear_g_source (&handle->fail_on_idle_id);
+
+	if (handle->callback) {
+		NMSupplicantManagerCreateInterfaceCb callback;
+
+		nm_assert (NM_IS_SUPPLICANT_MANAGER (handle->self));
+
+		callback = handle->callback;
+		handle->callback = NULL;
+		callback (handle->self,
+		          handle,
+		          supp_iface,
+		          error,
+		          handle->callback_user_data);
+	}
+
+	g_clear_error (&handle->fail_on_idle_error);
+
+	g_clear_object (&handle->self);
+
+	if (handle->shutdown_handle) {
+		/* we have a pending CreateInterface request. We keep the handle
+		 * instance alive. This is to remove the device again, once the
+		 * request completes. */
+		return;
+	}
+
+	nm_clear_g_cancellable (&handle->cancellable);
+	nm_ref_string_unref (handle->name_owner);
+
+	nm_g_slice_free_fcn (handle);
+}
+
+static void
+_create_iface_add (NMSupplicantManager *self,
+                   NMSupplMgrCreateIfaceHandle *handle,
+                   const char *iface_path_str,
+                   gboolean created_by_us)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *iface_path = NULL;
+	gs_unref_object NMSupplicantInterface *supp_iface = NULL;
+
+	iface_path = nm_ref_string_new (iface_path_str);
+
+	supp_iface = g_hash_table_lookup (priv->supp_ifaces, iface_path);
+	if (supp_iface) {
+		/* Now this is odd... Reuse the same interface. */
+		g_object_ref (supp_iface);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: interface %s on %s created (already existing)",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       iface_path_str,
+		       priv->name_owner->str);
+		_create_iface_complete (handle, supp_iface, NULL);
+		return;
+	}
+
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: interface %s on %s created%s",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       iface_path_str,
+	       priv->name_owner->str,
+	       created_by_us ? " (created by us)" : "");
+
+	supp_iface = nm_supplicant_interface_new (self,
+	                                          iface_path,
+	                                          handle->ifindex,
+	                                          handle->driver);
+
+	_supp_iface_add (self, iface_path, supp_iface);
+
+	_create_iface_complete (handle, supp_iface, NULL);
+}
+
+static void
+_create_iface_dbus_call_get_interface_cb (GObject *source,
+                                          GAsyncResult *result,
+                                          gpointer user_data)
+{
+	GDBusConnection *dbus_connection = G_DBUS_CONNECTION (source);
+	NMSupplMgrCreateIfaceHandle *handle;
+	NMSupplicantManager *self;
+	NMSupplicantManagerPrivate *priv;
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+	const char *iface_path_str;
+
+	res = g_dbus_connection_call_finish (dbus_connection, result, &error);
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	handle = user_data;
+	nm_assert (handle->callback);
+
+	self = handle->self;
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (handle->name_owner == priv->name_owner);
+
+	if (!res) {
+		char ifname[NMP_IFNAMSIZ];
+
+		if (   handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX
+		    && _nm_dbus_error_has_name (error, NM_WPAS_ERROR_UNKNOWN_IFACE)
+		    && nm_platform_if_indextoname (NM_PLATFORM_GET, handle->ifindex, ifname)) {
+			/* Before, supplicant told us the interface existed. Was there a race?
+			 * Try again. */
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call failed to get interface. Try to create it again (ifname \"%s\")",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       ifname);
+			_create_iface_dbus_call_create_interface (self, handle, ifname);
+			return;
+		}
+
+		g_clear_object (&handle->cancellable);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call to get interface failed: %s",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       error->message);
+		_create_iface_complete (handle, NULL, error);
+		return;
+	}
+
+	g_clear_object (&handle->cancellable);
+
+	g_variant_get (res, "(&o)", &iface_path_str);
+
+	_create_iface_add (self, handle, iface_path_str, FALSE);
+}
+
+static void
+_create_iface_dbus_call_create_interface_cb (GObject *source,
+                                             GAsyncResult *result,
+                                             gpointer user_data)
+{
+	GDBusConnection *dbus_connection = G_DBUS_CONNECTION (source);
+	NMSupplMgrCreateIfaceHandle *handle = user_data;
+	NMSupplicantManager *self;
+	NMSupplicantManagerPrivate *priv;
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+	const char *iface_path_str;
+	char ifname[NMP_IFNAMSIZ];
+
+	res = g_dbus_connection_call_finish (dbus_connection, result, &error);
+
+	nm_shutdown_wait_obj_unregister (g_steal_pointer (&handle->shutdown_handle));
+
+	if (!res) {
+		if (   handle->callback
+		    && ({ nm_assert (handle->self); TRUE; })
+		    && _nm_dbus_error_has_name (error, NM_WPAS_ERROR_EXISTS_ERROR)
+		    && nm_platform_if_indextoname (NM_PLATFORM_GET, handle->ifindex, ifname)) {
+			self = handle->self;
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call failed to create interface. Try to get existing interface (ifname \"%s\")",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       ifname);
+			_create_iface_dbus_call_get_interface (self, handle, ifname);
+			return;
+		}
+		g_clear_object (&handle->cancellable);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call failed: %s",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       error->message);
+		_create_iface_complete (handle, NULL, error);
+		return;
+	}
+
+	g_clear_object (&handle->cancellable);
+
+	self = handle->self;
+	priv =   self
+	       ? NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)
+	       : NULL;
+
+	g_variant_get (res, "(&o)", &iface_path_str);
+
+	if (   !handle->callback
+	    || priv->name_owner != handle->name_owner) {
+		if (!handle->callback) {
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: request already cancelled but still remove interface %s in %s",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       iface_path_str,
+			       handle->name_owner->str);
+		} else {
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: name owner changed, still remove interface %s in %s",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       iface_path_str,
+			       handle->name_owner->str);
+			nm_utils_error_set (&error,
+			                    NM_UTILS_ERROR_UNKNOWN,
+			                    "The name owner changed since creating the interface");
+		}
+		_dbus_call_remove_interface (dbus_connection,
+		                             handle->name_owner->str,
+		                             iface_path_str);
+		_create_iface_complete (handle, NULL, error);
+		return;
+	}
+
+	_create_iface_add (self, handle, iface_path_str, TRUE);
+}
+
+static void
+_create_iface_dbus_call_get_interface (NMSupplicantManager *self,
+                                       NMSupplMgrCreateIfaceHandle *handle,
+                                       const char *ifname)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (handle->cancellable);
+	nm_assert (!handle->shutdown_handle);
+
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner->str,
+	                        NM_WPAS_DBUS_PATH,
+	                        NM_WPAS_DBUS_INTERFACE,
+	                        "GetInterface",
+	                        g_variant_new ("(s)", ifname),
+	                        G_VARIANT_TYPE ("(o)"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        5000,
+	                        handle->cancellable,
+	                        _create_iface_dbus_call_get_interface_cb,
+	                        handle);
+}
+
+static void
+_create_iface_dbus_call_create_interface (NMSupplicantManager *self,
+                                          NMSupplMgrCreateIfaceHandle *handle,
+                                          const char *ifname)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	GVariantBuilder builder;
+
+	nm_assert (priv->name_owner == handle->name_owner);
+	nm_assert (handle->cancellable);
+	nm_assert (!handle->shutdown_handle);
+	nm_assert (handle->create_iface_try_count <= CREATE_IFACE_TRY_COUNT_MAX);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&builder,
+	                       "{sv}",
+	                       "Driver",
+	                       g_variant_new_string (nm_supplicant_driver_to_string (handle->driver)));
+	g_variant_builder_add (&builder,
+	                       "{sv}",
+	                       "Ifname",
+	                       g_variant_new_string (ifname));
+
+	handle->shutdown_handle = nm_shutdown_wait_obj_register_cancellable_full (handle->cancellable,
+	                                                                          g_strdup_printf ("wpas-create-" NM_HASH_OBFUSCATE_PTR_FMT,
+	                                                                                           NM_HASH_OBFUSCATE_PTR (handle)),
+	                                                                          TRUE);
+	handle->create_iface_try_count++;
+	g_dbus_connection_call (priv->dbus_connection,
+	                        handle->name_owner->str,
+	                        NM_WPAS_DBUS_PATH,
+	                        NM_WPAS_DBUS_INTERFACE,
+	                        "CreateInterface",
+	                        g_variant_new ("(a{sv})", &builder),
+	                        G_VARIANT_TYPE ("(o)"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        5000,
+	                        handle->cancellable,
+	                        _create_iface_dbus_call_create_interface_cb,
+	                        handle);
+}
+
+static void
+_create_iface_dbus_start (NMSupplicantManager *self,
+                          NMSupplMgrCreateIfaceHandle *handle)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	char ifname[NMP_IFNAMSIZ];
+
+	nm_assert (priv->name_owner);
+	nm_assert (!handle->cancellable);
+
+	if (!nm_platform_if_indextoname (NM_PLATFORM_GET, handle->ifindex, ifname)) {
+		nm_utils_error_set (&handle->fail_on_idle_error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "Cannot find interface %d",
+		                    handle->ifindex);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: creating interface fails to find interface name for ifindex %d",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       handle->ifindex);
+		handle->fail_on_idle_id = g_idle_add (_create_iface_fail_on_idle_cb, handle);
+		return;
+	}
+
+	/* Our handle keeps @self alive. That means, when NetworkManager shall shut
+	 * down, it's the responsibility of the callers to cancel the handles,
+	 * to initiate coordinated shutdown.
+	 *
+	 * However, we now issue a CreateInterface call. Even if the handle gets cancelled
+	 * (because of shutdown, or because the caller is no longer interested in the
+	 * result), we don't want to cancel this request. Instead, we want to get
+	 * the interface path and remove it right away.
+	 *
+	 * That means, the D-Bus call cannot be cancelled (because we always care about
+	 * the result). Only the @handle can be cancelled, but parts of the handle will
+	 * stick around to complete the task.
+	 *
+	 * See also handle->shutdown_handle.
+	 */
+	handle->name_owner = nm_ref_string_ref (priv->name_owner);
+	handle->cancellable = g_cancellable_new ();
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: creating interface (ifname \"%s\")...",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       ifname);
+	_create_iface_dbus_call_create_interface (self, handle, ifname);
+}
+
+static gboolean
+_create_iface_fail_on_idle_cb (gpointer user_data)
+{
+	NMSupplMgrCreateIfaceHandle *handle = user_data;
+
+	handle->fail_on_idle_id = 0;
+
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: fail with internal error: %s",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       handle->fail_on_idle_error->message);
+
+	_create_iface_complete (handle, NULL, handle->fail_on_idle_error);
+	return G_SOURCE_REMOVE;
+}
+
+NMSupplMgrCreateIfaceHandle *
 nm_supplicant_manager_create_interface (NMSupplicantManager *self,
-                                        const char *ifname,
-                                        NMSupplicantDriver driver)
+                                        int ifindex,
+                                        NMSupplicantDriver driver,
+                                        NMSupplicantManagerCreateInterfaceCb callback,
+                                        gpointer user_data)
 {
 	NMSupplicantManagerPrivate *priv;
-	NMSupplicantInterface *iface;
-	GSList *ifaces;
+	NMSupplMgrCreateIfaceHandle *handle;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
-	g_return_val_if_fail (ifname != NULL, NULL);
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (callback, NULL);
+	nm_assert (nm_supplicant_driver_to_string (driver));
 
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	_LOGD ("(%s): creating new supplicant interface", ifname);
+	handle = g_slice_new (NMSupplMgrCreateIfaceHandle);
+	*handle = (NMSupplMgrCreateIfaceHandle) {
+		.self               = g_object_ref (self),
+		.callback           = callback,
+		.callback_user_data = user_data,
+		.driver             = driver,
+		.ifindex            = ifindex,
+	};
+	c_list_link_tail (&priv->create_iface_lst_head, &handle->create_iface_lst);
+
+	if (!priv->dbus_connection) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). Fail bacause no D-Bus connection to talk to wpa_supplicant...",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       ifindex,
+		       nm_supplicant_driver_to_string (driver));
+		nm_utils_error_set (&handle->fail_on_idle_error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "No D-Bus connection to talk to wpa_supplicant");
+		handle->fail_on_idle_id = g_idle_add (_create_iface_fail_on_idle_cb, handle);
+		return handle;
+	}
 
-	/* assert against not requesting duplicate interfaces. */
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
-		if (g_strcmp0 (nm_supplicant_interface_get_ifname (ifaces->data), ifname) == 0)
-			g_return_val_if_reached (NULL);
+	if (!priv->name_owner) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). %s",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       ifindex,
+		       nm_supplicant_driver_to_string (driver),
+		         priv->poke_name_owner_cancellable
+		       ? "Waiting for supplicant..."
+		       : "Poke supplicant...");
+		_poke_name_owner (self);
+		return handle;
 	}
 
-	iface = nm_supplicant_interface_new (ifname,
-	                                     NULL,
-	                                     driver,
-	                                     priv->fast_support,
-	                                     priv->ap_support,
-	                                     priv->pmf_support,
-	                                     priv->fils_support,
-	                                     priv->p2p_support,
-	                                     priv->mesh_support,
-	                                     priv->wfd_support,
-	                                     priv->ft_support,
-	                                     priv->sha384_support);
-
-	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
-	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
-
-	/* If we're making the supplicant take a time out for a bit, don't
-	 * let the supplicant interface start immediately, just let it hang
-	 * around in INIT state until we're ready to talk to the supplicant
-	 * again.
-	 */
-	if (is_available (self))
-		nm_supplicant_interface_set_supplicant_available (iface, TRUE);
+	if (priv->get_capabilities_cancellable) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). Waiting to fetch capabilities for %s...",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       ifindex,
+		       nm_supplicant_driver_to_string (driver),
+		       priv->name_owner->str);
+		return handle;
+	}
 
-	return iface;
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). create interface on %s...",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       ifindex,
+	       nm_supplicant_driver_to_string (driver),
+	       priv->name_owner->str);
+
+	_create_iface_dbus_start (self, handle);
+	return handle;
+}
+
+static void
+_create_iface_proceed_all (NMSupplicantManager *self,
+                           GError *error)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	NMSupplMgrCreateIfaceHandle *handle;
+
+	nm_assert (error || priv->name_owner);
+	nm_assert (error || !priv->get_capabilities_cancellable);
+
+	if (c_list_is_empty (&priv->create_iface_lst_head))
+		return;
+
+	if (error) {
+		CList alt_list;
+
+		/* we move the handles we want to proceed to a alternative list.
+		 * That is, because we invoke callbacks to the caller, who might
+		 * create another request right away. We don't want to proceed
+		 * that one. */
+		c_list_init (&alt_list);
+		c_list_splice (&alt_list, &priv->create_iface_lst_head);
+
+		while ((handle = c_list_last_entry (&alt_list, NMSupplMgrCreateIfaceHandle, create_iface_lst))) {
+			/* We don't need to keep @self alive. Every handle holds a reference already. */
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: create interface failed: %s",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       error->message);
+			_create_iface_complete (handle, NULL, error);
+		}
+		return;
+	}
+
+	/* start all the handles. This does not invoke callbacks, so the list of handles
+	 * cannot be modified while we iterate it. */
+	c_list_for_each_entry (handle, &priv->create_iface_lst_head, create_iface_lst) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: create interface on %s...",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       priv->name_owner->str);
+		_create_iface_dbus_start (self, handle);
+	}
+}
+
+void
+nm_supplicant_manager_create_interface_cancel (NMSupplMgrCreateIfaceHandle *handle)
+{
+	gs_free_error GError *error = NULL;
+
+	if (!handle)
+		return;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (handle->self));
+	g_return_if_fail (handle->callback);
+	nm_assert (!c_list_is_empty (&handle->create_iface_lst));
+
+	nm_utils_error_set_cancelled (&error, FALSE, NULL);
+	_create_iface_complete (handle, NULL, error);
 }
 
-/**
- * nm_supplicant_manager_create_interface_from_path:
- * @self: the #NMSupplicantManager
- * @object_path: the DBus object path for which to obtain the supplicant interface
- *
- * Note: the manager owns a reference to the instance and the only way to
- *   get the manager to release it, is by dropping all other references
- *   to the supplicant-interface (or destroying the manager).
- *
- * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL.
- *   Must be unrefed at the end.
- * */
 NMSupplicantInterface *
 nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
                                                   const char *object_path)
 {
 	NMSupplicantManagerPrivate *priv;
-	NMSupplicantInterface *iface;
-	GSList *ifaces;
+	NMSupplicantInterface *supp_iface;
+	nm_auto_ref_string NMRefString *iface_path = NULL;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
-	g_return_val_if_fail (object_path != NULL, NULL);
+	g_return_val_if_fail (object_path, NULL);
 
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	_LOGD ("creating new supplicant interface for dbus path %s", object_path);
+	iface_path = nm_ref_string_new (object_path);
 
-	/* assert against not requesting duplicate interfaces. */
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
-		if (g_strcmp0 (nm_supplicant_interface_get_object_path (ifaces->data), object_path) == 0)
-			g_return_val_if_reached (NULL);
-	}
+	supp_iface = g_hash_table_lookup (priv->supp_ifaces, iface_path);
 
-	iface = nm_supplicant_interface_new (NULL,
-	                                     object_path,
-	                                     NM_SUPPLICANT_DRIVER_WIRELESS,
-	                                     priv->fast_support,
-	                                     priv->ap_support,
-	                                     priv->pmf_support,
-	                                     priv->fils_support,
-	                                     priv->p2p_support,
-	                                     priv->mesh_support,
-	                                     priv->wfd_support,
-	                                     priv->ft_support,
-	                                     priv->sha384_support);
-
-	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
-	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
-
-	/* If we're making the supplicant take a time out for a bit, don't
-	 * let the supplicant interface start immediately, just let it hang
-	 * around in INIT state until we're ready to talk to the supplicant
-	 * again.
-	 */
-	if (is_available (self))
-		nm_supplicant_interface_set_supplicant_available (iface, TRUE);
+	if (supp_iface)
+		return g_object_ref (supp_iface);
+
+	supp_iface = nm_supplicant_interface_new (self,
+	                                          iface_path,
+	                                          0,
+	                                          NM_SUPPLICANT_DRIVER_UNKNOWN);
+
+	_supp_iface_add (self, iface_path, supp_iface);
 
-	return iface;
+	return supp_iface;
 }
 
+/*****************************************************************************/
+
 static void
-update_capabilities (NMSupplicantManager *self)
+_dbus_interface_removed_cb (GDBusConnection *connection,
+                            const char *sender_name,
+                            const char *object_path,
+                            const char *signal_interface_name,
+                            const char *signal_name,
+                            GVariant *parameters,
+                            gpointer user_data)
 {
+	NMSupplicantManager *self = user_data;
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	GSList *ifaces;
-	const char **array;
-	GVariant *value;
+	NMSupplicantInterface *supp_iface;
+	const char *iface_path_str;
+	nm_auto_ref_string NMRefString *iface_path = NULL;
+
+	nm_assert (nm_streq (sender_name, priv->name_owner->str));
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+		return;
+
+	g_variant_get (parameters, "(&o)", &iface_path_str);
+
+	iface_path = nm_ref_string_new (iface_path_str);
+
+	supp_iface = g_hash_table_lookup (priv->supp_ifaces, iface_path);
+	if (!supp_iface)
+		return;
+
+	_supp_iface_remove_one (self, supp_iface, FALSE, "InterfaceRemoved signal from wpa_supplicant");
+}
+
+/*****************************************************************************/
+
+static void
+_dbus_get_capabilities_cb (GVariant *res,
+                           GError *error,
+                           gpointer user_data)
+{
+	NMSupplicantManager *self;
+	NMSupplicantManagerPrivate *priv;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	self = user_data;
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->get_capabilities_cancellable);
 
 	/* The supplicant only advertises global capabilities if the following
 	 * commit has been applied:
@@ -316,269 +926,398 @@ update_capabilities (NMSupplicantManager *self)
 	 *
 	 * dbus: Add global capabilities property
 	 */
-	priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-	priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-	priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_AP,     NM_TERNARY_DEFAULT);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_PMF,    NM_TERNARY_DEFAULT);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_FILS,   NM_TERNARY_DEFAULT);
+
 	/* Support for the following is newer than the capabilities property */
-	priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
-	priv->ft_support = NM_SUPPLICANT_FEATURE_NO;
-	priv->sha384_support = NM_SUPPLICANT_FEATURE_NO;
-	priv->mesh_support = NM_SUPPLICANT_FEATURE_NO;
-
-	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
-	if (value) {
-		if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
-			array = g_variant_get_strv (value, NULL);
-			priv->ap_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->pmf_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->fils_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->ft_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->sha384_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->mesh_support = NM_SUPPLICANT_FEATURE_NO;
-			if (array) {
-				if (g_strv_contains (array, "ap"))
-					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "pmf"))
-					priv->pmf_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "fils"))
-					priv->fils_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "p2p"))
-					priv->p2p_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "ft"))
-					priv->ft_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "sha384"))
-					priv->sha384_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "mesh"))
-					priv->mesh_support = NM_SUPPLICANT_FEATURE_YES;
-				g_free (array);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_P2P,    NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_FT,     NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_MESH,   NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_FAST,   NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_WFD,    NM_TERNARY_FALSE);
+
+	if (res) {
+		nm_auto_free_variant_iter GVariantIter *res_iter = NULL;
+		const char *res_key;
+		GVariant *res_val;
+
+		g_variant_get (res, "(a{sv})", &res_iter);
+		while (g_variant_iter_loop (res_iter, "{&sv}", &res_key, &res_val)) {
+			if (nm_streq (res_key, "Capabilities")) {
+				if (g_variant_is_of_type (res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
+					gs_free const char **array = NULL;
+					const char **a;
+
+					array = g_variant_get_strv (res_val, NULL);
+					_caps_set (priv, NM_SUPPL_CAP_TYPE_AP,   NM_TERNARY_FALSE);
+					_caps_set (priv, NM_SUPPL_CAP_TYPE_PMF,  NM_TERNARY_FALSE);
+					_caps_set (priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_FALSE);
+					if (array) {
+						for (a = array; *a; a++) {
+							if (nm_streq (*a, "ap"))     { _caps_set (priv, NM_SUPPL_CAP_TYPE_AP,     NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "pmf"))    { _caps_set (priv, NM_SUPPL_CAP_TYPE_PMF,    NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "fils"))   { _caps_set (priv, NM_SUPPL_CAP_TYPE_FILS,   NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "p2p"))    { _caps_set (priv, NM_SUPPL_CAP_TYPE_P2P,    NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "ft"))     { _caps_set (priv, NM_SUPPL_CAP_TYPE_FT,     NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "sha384")) { _caps_set (priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "mesh"))   { _caps_set (priv, NM_SUPPL_CAP_TYPE_MESH,   NM_TERNARY_TRUE); continue; }
+						}
+					}
+				}
+				continue;
 			}
-		}
-		g_variant_unref (value);
-	}
-
-	/* Tell all interfaces about results of the AP/PMF/FILS/P2P/FT/SHA384 check */
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
-		nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support);
-		nm_supplicant_interface_set_pmf_support (ifaces->data, priv->pmf_support);
-		nm_supplicant_interface_set_fils_support (ifaces->data, priv->fils_support);
-		nm_supplicant_interface_set_p2p_support (ifaces->data, priv->p2p_support);
-		nm_supplicant_interface_set_ft_support (ifaces->data, priv->ft_support);
-		nm_supplicant_interface_set_sha384_support (ifaces->data, priv->sha384_support);
-		nm_supplicant_interface_set_mesh_support (ifaces->data, priv->mesh_support);
-	}
-
-	_LOGD ("AP mode is %ssupported",
-	       (priv->ap_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->ap_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("PMF is %ssupported",
-	       (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->pmf_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("FILS is %ssupported",
-	       (priv->fils_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->fils_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("P2P is %ssupported",
-	       (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->p2p_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("FT is %ssupported",
-	       (priv->ft_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->ft_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("SHA384 is %ssupported",
-	       (priv->sha384_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->sha384_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("Mesh is %ssupported",
-	       (priv->mesh_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->mesh_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-
-	/* EAP-FAST */
-	priv->fast_support = NM_SUPPLICANT_FEATURE_NO;
-	value = g_dbus_proxy_get_cached_property (priv->proxy, "EapMethods");
-	if (value) {
-		if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
-			array = g_variant_get_strv (value, NULL);
-			if (array) {
-				const char **a;
-
-				for (a = array; *a; a++) {
-					if (g_ascii_strcasecmp (*a, "FAST") == 0) {
-						priv->fast_support = NM_SUPPLICANT_FEATURE_YES;
-						break;
+			if (nm_streq (res_key, "EapMethods")) {
+				if (g_variant_is_of_type (res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
+					gs_free const char **array = NULL;
+					const char **a;
+
+					array = g_variant_get_strv (res_val, NULL);
+					if (array) {
+						for (a = array; *a; a++) {
+							if (g_ascii_strcasecmp (*a, "FAST") == 0) {
+								_caps_set (priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_TRUE);
+								break;
+							}
+						}
 					}
 				}
-				g_free (array);
+				continue;
+			}
+			if (nm_streq (res_key, "WFDIEs")) {
+				_caps_set (priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_TRUE);
+				continue;
 			}
 		}
-		g_variant_unref (value);
 	}
 
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
-		nm_supplicant_interface_set_fast_support (ifaces->data, priv->fast_support);
+	_LOGD ("supported features:"
+	       " AP%c"
+	       " PMF%c"
+	       " FILS%c"
+	       " P2P%c"
+	       " FT%c"
+	       " SHA384%c"
+	       " MESH%c"
+	       " FAST%c"
+	       " WFD%c"
+	       "",
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_AP),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_PMF),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_FILS),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_P2P),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_FT),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_SHA384),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_MESH),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_FAST),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_WFD));
+
+	nm_assert (g_hash_table_size (priv->supp_ifaces) == 0);
+	nm_assert (c_list_is_empty (&priv->supp_lst_head));
+
+	_create_iface_proceed_all (self, NULL);
+}
 
-	_LOGD ("EAP-FAST is %ssupported",
-	       (priv->fast_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->fast_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+/*****************************************************************************/
 
-	priv->wfd_support = NM_SUPPLICANT_FEATURE_NO;
-	value = g_dbus_proxy_get_cached_property (priv->proxy, "WFDIEs");
-	if (value) {
-		priv->wfd_support = NM_SUPPLICANT_FEATURE_YES;
-		g_variant_unref (value);
-	}
+void
+_nm_supplicant_manager_unregister_interface (NMSupplicantManager *self,
+                                             NMSupplicantInterface *supp_iface)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
-		nm_supplicant_interface_set_wfd_support (ifaces->data, priv->fast_support);
+	nm_assert (NM_IS_SUPPLICANT_INTERFACE (supp_iface));
+	nm_assert (c_list_contains (&NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->supp_lst_head, &supp_iface->supp_lst));
 
-	_LOGD ("WFD is %ssupported",
-	       (priv->wfd_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->wfd_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	c_list_unlink (&supp_iface->supp_lst);
+	if (!g_hash_table_remove (priv->supp_ifaces, nm_supplicant_interface_get_object_path (supp_iface)))
+		nm_assert_not_reached ();
 }
 
 static void
-availability_changed (NMSupplicantManager *self, gboolean available)
+_supp_iface_add (NMSupplicantManager *self,
+                 NMRefString *iface_path,
+                 NMSupplicantInterface *supp_iface)
 {
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	GSList *ifaces, *iter;
-
-	if (!priv->ifaces)
-		return;
 
-	/* setting the supplicant as unavailable might cause the caller to unref
-	 * the supplicant (and thus remove the instance from the list of interfaces.
-	 * Delay that by taking an additional reference first. */
-	ifaces = g_slist_copy (priv->ifaces);
-	for (iter = ifaces; iter; iter = iter->next)
-		g_object_ref (iter->data);
-	for (iter = ifaces; iter; iter = iter->next)
-		nm_supplicant_interface_set_supplicant_available (iter->data, available);
-	g_slist_free_full (ifaces, g_object_unref);
+	c_list_link_tail (&priv->supp_lst_head, &supp_iface->supp_lst);
+	if (!g_hash_table_insert (priv->supp_ifaces, iface_path, supp_iface))
+		nm_assert_not_reached ();
 }
 
 static void
-set_running (NMSupplicantManager *self, gboolean now_running)
+_supp_iface_remove_one (NMSupplicantManager *self,
+                        NMSupplicantInterface *supp_iface,
+                        gboolean force_remove_from_supplicant,
+                        const char *reason)
 {
-	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	gboolean old_available = is_available (self);
-	gboolean new_available;
+#if NM_MORE_ASSERTS
+	_nm_unused gs_unref_object NMSupplicantInterface *supp_iface_keep_alive = g_object_ref (supp_iface);
+#endif
+
+	nm_assert (NM_IS_SUPPLICANT_MANAGER (self));
+	nm_assert (NM_IS_SUPPLICANT_INTERFACE (supp_iface));
+	nm_assert (c_list_contains (&NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->supp_lst_head, &supp_iface->supp_lst));
+
+	_nm_supplicant_interface_set_state_down (supp_iface, force_remove_from_supplicant, reason);
 
-	priv->running = now_running;
-	new_available = is_available (self);
-	if (old_available != new_available)
-		availability_changed (self, new_available);
+	nm_assert (c_list_is_empty (&supp_iface->supp_lst));
 }
 
 static void
-set_die_count (NMSupplicantManager *self, guint new_die_count)
+_supp_iface_remove_all (NMSupplicantManager *self,
+                        gboolean force_remove_from_supplicant,
+                        const char *reason)
 {
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	gboolean old_available = is_available (self);
-	gboolean new_available;
+	NMSupplicantInterface *supp_iface;
 
-	priv->die_count = new_die_count;
-	new_available = is_available (self);
-	if (old_available != new_available)
-		availability_changed (self, new_available);
+	while ((supp_iface = c_list_first_entry (&priv->supp_lst_head, NMSupplicantInterface, supp_lst)))
+		_supp_iface_remove_one (self, supp_iface, force_remove_from_supplicant, reason);
 }
 
+/*****************************************************************************/
+
 static gboolean
-wpas_die_count_reset_cb (gpointer user_data)
+_available_reset_cb (gpointer user_data)
 {
-	NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data);
+	NMSupplicantManager *self = user_data;
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	/* Reset the die count back to zero, which allows use of the supplicant again */
-	priv->die_count_reset_id = 0;
-	set_die_count (self, 0);
-	_LOGI ("wpa_supplicant die count reset");
-	return FALSE;
+	priv->available_reset_id = 0;
+	nm_assert (priv->available == NM_TERNARY_FALSE);
+	priv->available = NM_TERNARY_DEFAULT;
+	g_signal_emit (self, signals[AVAILABLE_CHANGED], 0);
+	return G_SOURCE_REMOVE;
 }
 
+/*****************************************************************************/
+
 static void
-name_owner_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data)
+name_owner_changed (NMSupplicantManager *self,
+                    const char *name_owner,
+                    gboolean first_time)
 {
-	NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data);
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	char *owner;
-
-	g_return_if_fail (proxy == priv->proxy);
-
-	owner = g_dbus_proxy_get_name_owner (proxy);
-	_LOGI ("wpa_supplicant %s", owner ? "running" : "stopped");
-
-	if (owner) {
-		update_capabilities (self);
-		set_running (self, TRUE);
-	} else if (priv->running) {
-		/* Reschedule the die count reset timeout.  Every time the supplicant
-		 * dies we wait 10 seconds before resetting the counter.  If the
-		 * supplicant died more than twice before the timer is reset, then
-		 * we don't try to talk to the supplicant for a while.
-		 */
-		if (priv->die_count_reset_id)
-			g_source_remove (priv->die_count_reset_id);
-		priv->die_count_reset_id = g_timeout_add_seconds (10, wpas_die_count_reset_cb, self);
-		set_die_count (self, priv->die_count + 1);
-
-		if (die_count_exceeded (priv->die_count)) {
-			_LOGI ("wpa_supplicant die count %d; ignoring for 10 seconds",
-			       priv->die_count);
+	NMTernary available;
+	gboolean available_changed = FALSE;
+
+	nm_assert (!priv->get_name_owner_cancellable);
+	nm_assert (   !name_owner
+	           || name_owner[0]);
+	nm_assert (   (   first_time
+	               && !priv->name_owner)
+	           || (   !first_time
+	               && (!!priv->name_owner) != (!!name_owner)));
+
+	if (first_time) {
+		_LOGD ("wpa_supplicant name owner %s%s%s (%srunning)",
+		       NM_PRINT_FMT_QUOTE_STRING (name_owner),
+		       name_owner ? "" : "not ");
+	} else {
+		_LOGD ("wpa_supplicant name owner \"%s\" %s (%srunning)",
+		       name_owner ?: priv->name_owner->str,
+		       name_owner ? "disappeared" : "appeared",
+		       name_owner ? "" : "not ");
+	}
+
+	nm_ref_string_unref (priv->name_owner);
+	priv->name_owner = nm_ref_string_new (name_owner);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->interface_removed_id);
+
+	if (name_owner) {
+		if (nm_clear_g_source (&priv->poke_name_owner_timeout_id))
+			_LOGT ("poke service \"%s\" completed with name owner change", NM_WPAS_DBUS_SERVICE);
+		nm_clear_g_cancellable (&priv->poke_name_owner_cancellable);
+	}
+
+	nm_clear_g_cancellable (&priv->get_capabilities_cancellable);
+
+	priv->capabilities = NM_SUPPL_CAP_MASK_NONE;
+	if (priv->name_owner) {
+		priv->get_capabilities_cancellable = g_cancellable_new ();
+		nm_dbus_connection_call_get_all (priv->dbus_connection,
+		                                 priv->name_owner->str,
+		                                 NM_WPAS_DBUS_PATH,
+		                                 NM_WPAS_DBUS_INTERFACE,
+		                                 5000,
+		                                 priv->get_capabilities_cancellable,
+		                                 _dbus_get_capabilities_cb,
+		                                 self);
+		priv->interface_removed_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+		                                                                 priv->name_owner->str,
+		                                                                 NM_WPAS_DBUS_INTERFACE,
+		                                                                 "InterfaceRemoved",
+		                                                                 NULL,
+		                                                                 NULL,
+		                                                                 G_DBUS_SIGNAL_FLAGS_NONE,
+		                                                                 _dbus_interface_removed_cb,
+		                                                                 self,
+		                                                                 NULL);
+	}
+
+	/* if supplicant is running (has a name owner), we may use it.
+	 * If this is the first time, and supplicant is not running, we
+	 * may also use it (and assume that we probably could D-Bus activate
+	 * it).
+	 *
+	 * Otherwise, somebody else stopped supplicant. It's no longer useable to
+	 * us and we block auto starting it. The user has to start the service...
+	 *
+	 * Actually, below we reset the hard block after a short timeout. This
+	 * causes the caller to notify that supplicant may now by around and
+	 * retry to D-Bus activate it. */
+	if (priv->name_owner)
+		available = NM_TERNARY_TRUE;
+	else if (first_time)
+		available = NM_TERNARY_DEFAULT;
+	else
+		available = NM_TERNARY_FALSE;
+
+	if (priv->available != available) {
+		priv->available = available;
+		_LOGD ("supplicant is now %savailable",
+		         available == FALSE
+		       ? "not "
+		       : (  available == TRUE
+		          ? ""
+		          : "maybe "));
+		available_changed = TRUE;
+
+		nm_clear_g_source (&priv->available_reset_id);
+		if (available == NM_TERNARY_FALSE) {
+			/* reset the availability from a hard "no" to a "maybe" in a bit. */
+			priv->available_reset_id = g_timeout_add_seconds (60,
+			                                                  _available_reset_cb,
+			                                                  self);
 		}
+	}
+
+	_supp_iface_remove_all (self, TRUE, "name-owner changed");
 
-		priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->fast_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->ft_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->sha384_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+	if (!priv->name_owner) {
+		if (priv->poke_name_owner_timeout_id) {
+			/* we are still poking for the service to start. Don't cancel
+			 * the pending create requests just yet. */
+		} else {
+			gs_free_error GError *local_error = NULL;
 
-		set_running (self, FALSE);
+			/* When we loose the name owner, we fail all pending creation requests. */
+			nm_utils_error_set (&local_error,
+			                    NM_UTILS_ERROR_UNKNOWN,
+			                    "Name owner lost");
+			_create_iface_proceed_all (self, local_error);
+		}
+	} else {
+		/* We got a name-owner, but we don't do anything. Instead let
+		 * _dbus_get_capabilities_cb() complete and kick of the create-iface
+		 * handles.
+		 *
+		 * Note that before the first name-owner change, all create-iface
+		 * requests fail right away. So we don't have to handle them here
+		 * (by starting to poke the service). */
 	}
 
-	g_free (owner);
+	if (available_changed)
+		g_signal_emit (self, signals[AVAILABLE_CHANGED], 0);
 }
 
 static void
-on_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data)
+name_owner_changed_cb (GDBusConnection *connection,
+                       const char *sender_name,
+                       const char *object_path,
+                       const char *interface_name,
+                       const char *signal_name,
+                       GVariant *parameters,
+                       gpointer user_data)
 {
-	NMSupplicantManager *self;
-	NMSupplicantManagerPrivate *priv;
-	GError *error = NULL;
-	GDBusProxy *proxy;
+	gs_unref_object NMSupplicantManager *self = g_object_ref (user_data);
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	const char *name_owner;
 
-	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (!proxy) {
-		_LOGW ("failed to acquire wpa_supplicant proxy: Wi-Fi and 802.1x will not be available (%s)",
-		       error->message);
-		g_clear_error (&error);
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
 		return;
+
+	if (priv->get_name_owner_cancellable)
+		return;
+
+	g_variant_get (parameters,
+	               "(&s&s&s)",
+	               NULL,
+	               NULL,
+	               &name_owner);
+
+	name_owner = nm_str_not_empty (name_owner);
+
+	if (nm_streq0 (name_owner, nm_ref_string_get_str (priv->name_owner)))
+		return;
+
+	if (   name_owner
+	    && priv->name_owner) {
+		/* odd, we directly switch from one name owner to the next. Can't allow that.
+		 * First clear the name owner before resetting. */
+		name_owner_changed (self, NULL, FALSE);
 	}
+	name_owner_changed (user_data, name_owner, FALSE);
+}
 
-	self = NM_SUPPLICANT_MANAGER (user_data);
+static void
+get_name_owner_cb (const char *name_owner,
+                   GError *error,
+                   gpointer user_data)
+{
+	NMSupplicantManager *self = user_data;
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	if (   !name_owner
+	    && nm_utils_error_is_cancelled (error))
+		return;
+
+	self = user_data;
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	priv->proxy = proxy;
-	g_signal_connect (priv->proxy, "notify::g-name-owner", G_CALLBACK (name_owner_cb), self);
-	name_owner_cb (priv->proxy, NULL, self);
+	g_clear_object (&priv->get_name_owner_cancellable);
+
+	name_owner_changed (self, nm_str_not_empty (name_owner), TRUE);
 }
 
 /*****************************************************************************/
 
-NM_DEFINE_SINGLETON_GETTER (NMSupplicantManager, nm_supplicant_manager_get, NM_TYPE_SUPPLICANT_MANAGER);
-
 static void
 nm_supplicant_manager_init (NMSupplicantManager *self)
 {
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	priv->cancellable = g_cancellable_new ();
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_NONE,
-	                          NULL,
-	                          WPAS_DBUS_SERVICE,
-	                          WPAS_DBUS_PATH,
-	                          WPAS_DBUS_INTERFACE,
-	                          priv->cancellable,
-	                          (GAsyncReadyCallback) on_proxy_acquired,
-	                          self);
+	nm_assert (priv->capabilities == NM_SUPPL_CAP_MASK_NONE);
+	nm_assert (priv->available == NM_TERNARY_FALSE);
+
+	priv->supp_ifaces = g_hash_table_new (nm_direct_hash, NULL);
+	c_list_init (&priv->supp_lst_head);
+	c_list_init (&priv->create_iface_lst_head);
+
+	priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
+
+	if (!priv->dbus_connection) {
+		_LOGI ("no D-Bus connection to talk to wpa_supplicant");
+		return;
+	}
+
+	priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+	                                                                                      NM_WPAS_DBUS_SERVICE,
+	                                                                                      name_owner_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+	priv->get_name_owner_cancellable = g_cancellable_new ();
+	nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+	                                        NM_WPAS_DBUS_SERVICE,
+	                                        -1,
+	                                        priv->get_name_owner_cancellable,
+	                                        get_name_owner_cb,
+	                                        self);
 }
 
 static void
@@ -586,22 +1325,32 @@ dispose (GObject *object)
 {
 	NMSupplicantManager *self = (NMSupplicantManager *) object;
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	GSList *ifaces;
 
-	nm_clear_g_source (&priv->die_count_reset_id);
+	_supp_iface_remove_all (self, TRUE, "NMSupplicantManager is disposing");
 
-	nm_clear_g_cancellable (&priv->cancellable);
+	nm_assert (c_list_is_empty (&priv->create_iface_lst_head));
 
-	if (priv->ifaces) {
-		for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
-			g_object_remove_toggle_ref (ifaces->data, _sup_iface_last_ref, self);
-		g_slist_free (priv->ifaces);
-		priv->ifaces = NULL;
-	}
+	nm_clear_g_source (&priv->available_reset_id);
+
+	priv->available = NM_TERNARY_FALSE;
+	nm_clear_pointer (&priv->name_owner, nm_ref_string_unref);
+
+	nm_clear_g_source (&priv->poke_name_owner_timeout_id);
+	nm_clear_g_cancellable (&priv->poke_name_owner_cancellable);
 
-	g_clear_object (&priv->proxy);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->interface_removed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->name_owner_changed_id);
+
+	nm_clear_g_cancellable (&priv->get_name_owner_cancellable);
+	nm_clear_g_cancellable (&priv->get_capabilities_cancellable);
 
 	G_OBJECT_CLASS (nm_supplicant_manager_parent_class)->dispose (object);
+
+	g_clear_object (&priv->dbus_connection);
+
+	nm_clear_pointer (&priv->supp_ifaces, g_hash_table_destroy);
 }
 
 static void
@@ -610,5 +1359,11 @@ nm_supplicant_manager_class_init (NMSupplicantManagerClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->dispose = dispose;
-}
 
+	signals[AVAILABLE_CHANGED] =
+	    g_signal_new (NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 0);
+}
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h
index 18ca53b6..b2b814aa 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -17,19 +17,49 @@
 #define NM_IS_SUPPLICANT_MANAGER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_SUPPLICANT_MANAGER))
 #define NM_SUPPLICANT_MANAGER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass))
 
+#define NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED "available-changed"
+
 typedef struct _NMSupplicantManagerClass NMSupplicantManagerClass;
 
 GType nm_supplicant_manager_get_type (void);
 
 NMSupplicantManager *nm_supplicant_manager_get (void);
 
+NMTernary nm_supplicant_manager_is_available (NMSupplicantManager *self);
+
+GDBusConnection *nm_supplicant_manager_get_dbus_connection (NMSupplicantManager *self);
+NMRefString *nm_supplicant_manager_get_dbus_name_owner (NMSupplicantManager *self);
+NMSupplCapMask nm_supplicant_manager_get_global_capabilities (NMSupplicantManager *self);
+
 void nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
                                         GBytes *wfd_ies);
 
-NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr,
-                                                               const char *ifname,
-                                                               NMSupplicantDriver driver);
+typedef struct _NMSupplMgrCreateIfaceHandle NMSupplMgrCreateIfaceHandle;
+
+typedef void (*NMSupplicantManagerCreateInterfaceCb) (NMSupplicantManager *self,
+                                                      NMSupplMgrCreateIfaceHandle *handle,
+                                                      NMSupplicantInterface *iface,
+                                                      GError *error,
+                                                      gpointer user_data);
+
+NMSupplMgrCreateIfaceHandle *nm_supplicant_manager_create_interface (NMSupplicantManager *self,
+                                                                     int ifindex,
+                                                                     NMSupplicantDriver driver,
+                                                                     NMSupplicantManagerCreateInterfaceCb callback,
+                                                                     gpointer user_data);
+
+void nm_supplicant_manager_create_interface_cancel (NMSupplMgrCreateIfaceHandle *handle);
+
 NMSupplicantInterface *nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
                                                                          const char *object_path);
 
+/*****************************************************************************/
+
+void _nm_supplicant_manager_unregister_interface (NMSupplicantManager *self,
+                                                  NMSupplicantInterface *supp_iface);
+
+void _nm_supplicant_manager_dbus_call_remove_interface (NMSupplicantManager *self,
+                                                        const char *name_owner,
+                                                        const char *iface_path);
+
 #endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index bea17ede..58dbd73a 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -56,7 +56,7 @@ static const char *const key_mgmt_allowed[] = { "WPA-PSK", "WPA-PSK-SHA256", "FT
                                                 "WPA-EAP", "WPA-EAP-SHA256", "FT-EAP", "FT-EAP-SHA384",
                                                 "FILS-SHA256", "FILS-SHA384",
                                                 "IEEE8021X", "SAE", "FT-SAE",
-                                                "NONE", NULL };
+                                                "OWE", "NONE", NULL };
 static const char *const auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
 static const char *const eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
                                                 "PSK", "FAST", "PWD", NULL };
@@ -99,6 +99,7 @@ static const struct Opt opt_table[] = {
 	{ "subject_match",      TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "altsubject_match",   TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "domain_suffix_match",TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "domain_match",       TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "ca_cert",            TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "client_cert",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
@@ -110,6 +111,7 @@ static const struct Opt opt_table[] = {
 	{ "subject_match2",     TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "altsubject_match2",  TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "domain_suffix_match2", TYPE_BYTES, 0, 0, FALSE,  NULL },
+	{ "domain_match2",      TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "ca_cert2",           TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "client_cert2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
diff --git a/src/supplicant/nm-supplicant-types.h b/src/supplicant/nm-supplicant-types.h
index 2b356354..734ff156 100644
--- a/src/supplicant/nm-supplicant-types.h
+++ b/src/supplicant/nm-supplicant-types.h
@@ -6,19 +6,116 @@
 #ifndef __NETWORKMANAGER_SUPPLICANT_TYPES_H__
 #define __NETWORKMANAGER_SUPPLICANT_TYPES_H__
 
-#define WPAS_DBUS_SERVICE       "fi.w1.wpa_supplicant1"
-#define WPAS_DBUS_PATH          "/fi/w1/wpa_supplicant1"
-#define WPAS_DBUS_INTERFACE     "fi.w1.wpa_supplicant1"
+#include "c-list/src/c-list.h"
+
+#define NM_WPAS_DBUS_SERVICE       "fi.w1.wpa_supplicant1"
+#define NM_WPAS_DBUS_PATH          "/fi/w1/wpa_supplicant1"
+#define NM_WPAS_DBUS_INTERFACE     "fi.w1.wpa_supplicant1"
+
+#if HAVE_WEXT
+#define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211,wext"
+#else
+#define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211"
+#endif
+
+#define NM_WPAS_DBUS_IFACE_INTERFACE             NM_WPAS_DBUS_INTERFACE ".Interface"
+#define NM_WPAS_DBUS_IFACE_INTERFACE_WPS         NM_WPAS_DBUS_INTERFACE ".Interface.WPS"
+#define NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE  NM_WPAS_DBUS_INTERFACE ".Interface.P2PDevice"
+#define NM_WPAS_DBUS_IFACE_BSS                   NM_WPAS_DBUS_INTERFACE ".BSS"
+#define NM_WPAS_DBUS_IFACE_PEER                  NM_WPAS_DBUS_INTERFACE ".Peer"
+#define NM_WPAS_DBUS_IFACE_GROUP                 NM_WPAS_DBUS_INTERFACE ".Group"
+#define NM_WPAS_DBUS_IFACE_NETWORK               NM_WPAS_DBUS_INTERFACE ".Network"
+#define NM_WPAS_ERROR_INVALID_IFACE              NM_WPAS_DBUS_INTERFACE ".InvalidInterface"
+#define NM_WPAS_ERROR_EXISTS_ERROR               NM_WPAS_DBUS_INTERFACE ".InterfaceExists"
+#define NM_WPAS_ERROR_UNKNOWN_IFACE              NM_WPAS_DBUS_INTERFACE ".InterfaceUnknown"
 
 typedef struct _NMSupplicantManager NMSupplicantManager;
 typedef struct _NMSupplicantInterface NMSupplicantInterface;
 typedef struct _NMSupplicantConfig NMSupplicantConfig;
 
+/*****************************************************************************/
+
 typedef enum {
-	NM_SUPPLICANT_FEATURE_UNKNOWN = 0,  /* Can't detect whether supported or not */
-	NM_SUPPLICANT_FEATURE_NO = 1,       /* Feature definitely not supported */
-	NM_SUPPLICANT_FEATURE_YES = 2,      /* Feature definitely supported */
-} NMSupplicantFeature;
+	NM_SUPPL_CAP_TYPE_AP,
+	NM_SUPPL_CAP_TYPE_PMF,
+	NM_SUPPL_CAP_TYPE_FILS,
+	NM_SUPPL_CAP_TYPE_P2P,
+	NM_SUPPL_CAP_TYPE_FT,
+	NM_SUPPL_CAP_TYPE_SHA384,
+	NM_SUPPL_CAP_TYPE_MESH,
+	NM_SUPPL_CAP_TYPE_FAST,
+	NM_SUPPL_CAP_TYPE_WFD,
+	_NM_SUPPL_CAP_TYPE_NUM,
+} NMSupplCapType;
+
+#define NM_SUPPL_CAP_MASK_NO(type)   ((NMSupplCapMask) (1llu << ((type) * 2u)))
+#define NM_SUPPL_CAP_MASK_YES(type)  ((NMSupplCapMask) (2llu << ((type) * 2u)))
+#define NM_SUPPL_CAP_MASK_MASK(type) ((NMSupplCapMask) (3llu << ((type) * 2u)))
+
+typedef enum {
+	NM_SUPPL_CAP_MASK_NONE = 0,
+	NM_SUPPL_CAP_MASK_ALL = ((1llu << (_NM_SUPPL_CAP_TYPE_NUM * 2)) - 1),
+
+/* usually it's bad to use macros to define enum values (because you cannot find them with ctags/cscope
+ * anymore. In this case, still do it because the alternative is ugly too. */
+#define _NM_SUPPL_CAP_MASK_DEFINE(type) \
+	NM_SUPPL_CAP_MASK_T_##type##_NO   =   (1llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u)), \
+	NM_SUPPL_CAP_MASK_T_##type##_YES  =   (2llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u)), \
+	NM_SUPPL_CAP_MASK_T_##type##_MASK =   (3llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u))
+	_NM_SUPPL_CAP_MASK_DEFINE (AP),
+	_NM_SUPPL_CAP_MASK_DEFINE (FAST),
+	_NM_SUPPL_CAP_MASK_DEFINE (PMF),
+	_NM_SUPPL_CAP_MASK_DEFINE (FILS),
+	_NM_SUPPL_CAP_MASK_DEFINE (P2P),
+	_NM_SUPPL_CAP_MASK_DEFINE (MESH),
+	_NM_SUPPL_CAP_MASK_DEFINE (WFD),
+	_NM_SUPPL_CAP_MASK_DEFINE (FT),
+	_NM_SUPPL_CAP_MASK_DEFINE (SHA384),
+#undef _NM_SUPPL_CAP_MASK_DEFINE
+} NMSupplCapMask;
+
+static inline NMSupplCapMask
+NM_SUPPL_CAP_MASK_SET (NMSupplCapMask features, NMSupplCapType type, NMTernary value)
+{
+	nm_assert (_NM_INT_NOT_NEGATIVE (type));
+	nm_assert (type < _NM_SUPPL_CAP_TYPE_NUM);
+	nm_assert (NM_IN_SET (value, NM_TERNARY_DEFAULT,
+	                             NM_TERNARY_TRUE,
+	                             NM_TERNARY_FALSE));
+	nm_assert (!(features & ~NM_SUPPL_CAP_MASK_ALL));
+
+	features &= ~NM_SUPPL_CAP_MASK_MASK (type);
+	switch (value) {
+	case NM_TERNARY_FALSE:
+		features |= NM_SUPPL_CAP_MASK_NO (type);
+		break;
+	case NM_TERNARY_TRUE:
+		features |= NM_SUPPL_CAP_MASK_YES (type);
+		break;
+	case NM_TERNARY_DEFAULT:
+		break;
+	}
+
+	return features;
+}
+
+static inline NMTernary
+NM_SUPPL_CAP_MASK_GET (NMSupplCapMask features, NMSupplCapType type)
+{
+	int f;
+
+	nm_assert (_NM_INT_NOT_NEGATIVE (type));
+	nm_assert (type < _NM_SUPPL_CAP_TYPE_NUM);
+	nm_assert (!(features & ~NM_SUPPL_CAP_MASK_ALL));
+
+	f = ((int) (features >> (2 * (int) type))) & 0x3;
+
+	nm_assert (NM_IN_SET (f, 0, 1, 2));
+
+	return (NMTernary) (f - 1);
+}
+
+/*****************************************************************************/
 
 /**
  * NMSupplicantError:
@@ -32,12 +129,76 @@ typedef enum {
 } NMSupplicantError;
 
 typedef enum {
+	NM_SUPPLICANT_DRIVER_UNKNOWN,
 	NM_SUPPLICANT_DRIVER_WIRELESS,
 	NM_SUPPLICANT_DRIVER_WIRED,
 	NM_SUPPLICANT_DRIVER_MACSEC,
 } NMSupplicantDriver;
 
+const char *nm_supplicant_driver_to_string (NMSupplicantDriver driver);
+
 #define NM_SUPPLICANT_ERROR (nm_supplicant_error_quark ())
 GQuark nm_supplicant_error_quark (void);
 
+typedef struct _NMSupplicantBssInfo {
+	NMRefString *bss_path;
+
+	NMSupplicantInterface *_self;
+	CList _bss_lst;
+	GCancellable *_init_cancellable;
+
+	GBytes *ssid;
+
+	gint64 last_seen_msec;
+
+	NM80211ApSecurityFlags wpa_flags; /* WPA-related flags */
+	NM80211ApSecurityFlags rsn_flags; /* RSN (WPA2) -related flags */
+
+	guint32 frequency;
+
+	guint32 max_rate;
+
+	guint8 signal_percent;
+
+	guint8 bssid[6 /* ETH_ALEN */];
+
+	NM80211ApFlags ap_flags:5;
+
+	NM80211Mode mode:4;
+
+	bool bssid_valid:1;
+
+	bool metered:1;
+
+	bool _bss_dirty:1;
+
+} NMSupplicantBssInfo;
+
+typedef struct _NMSupplicantPeerInfo{
+	NMRefString *peer_path;
+
+	CList _peer_lst;
+	NMSupplicantInterface *_self;
+	GCancellable *_init_cancellable;
+
+	char *device_name;
+	char *manufacturer;
+	char *model;
+	char *model_number;
+	char *serial;
+
+	GBytes *ies;
+
+	gint64 last_seen_msec;
+
+	guint8 address[6 /* ETH_ALEN */];
+
+	gint8 signal_percent;
+
+	bool address_valid:1;
+
+	bool _peer_dirty:1;
+
+} NMSupplicantPeerInfo;
+
 #endif  /* NM_SUPPLICANT_TYPES_H */
diff --git a/src/supplicant/tests/meson.build b/src/supplicant/tests/meson.build
index 207d22f8..63012d10 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/supplicant/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-supplicant-config'
 
 exe = executable(
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index 008735b4..f9c71f12 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -86,8 +86,7 @@ static GVariant *
 build_supplicant_config (NMConnection *connection,
                          guint mtu,
                          guint fixed_freq,
-                         gboolean support_pmf,
-                         gboolean support_fils)
+                         NMSupplCapMask capabilities)
 {
 	gs_unref_object NMSupplicantConfig *config = NULL;
 	gs_free_error GError *error = NULL;
@@ -96,7 +95,7 @@ build_supplicant_config (NMConnection *connection,
 	NMSetting8021x *s_8021x;
 	gboolean success;
 
-	config = nm_supplicant_config_new (support_pmf, support_fils, FALSE, FALSE);
+	config = nm_supplicant_config_new (capabilities);
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
@@ -195,7 +194,7 @@ test_wifi_open (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
 	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
 	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'NONE'");
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -252,7 +251,7 @@ test_wifi_wep_key (const char *detail,
 	if (!test_bssid)
 		NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-70:86400'*");
 
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -352,7 +351,7 @@ test_wifi_wpa_psk (const char *detail,
 	default:
 		break;
 	}
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
@@ -416,7 +415,7 @@ test_wifi_sae_psk (const char *psk)
 	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'RSN'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
@@ -524,7 +523,7 @@ test_wifi_eap_locked_bssid (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'ca_cert' value '*/test-ca-cert.pem'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
-	config_dict = build_supplicant_config (connection, mtu, 0, FALSE, FALSE);
+	config_dict = build_supplicant_config (connection, mtu, 0, NM_SUPPL_CAP_MASK_NONE);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -565,7 +564,7 @@ test_wifi_eap_unlocked_bssid (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-65:300'");
-	config_dict = build_supplicant_config (connection, mtu, 0, FALSE, TRUE);
+	config_dict = build_supplicant_config (connection, mtu, 0, NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -606,7 +605,7 @@ test_wifi_eap_fils_disabled (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-65:300'");
-	config_dict = build_supplicant_config (connection, mtu, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, mtu, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -621,6 +620,44 @@ test_wifi_eap_fils_disabled (void)
 	validate_opt ("wifi-eap", config_dict, "bgscan", TYPE_BYTES, bgscan);
 }
 
+/*****************************************************************************/
+
+static void
+test_suppl_cap_mask (void)
+{
+	NMSupplCapType type;
+
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_AP_NO,  NM_SUPPL_CAP_TYPE_AP), ==, NM_TERNARY_FALSE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_AP_YES, NM_SUPPL_CAP_TYPE_AP), ==, NM_TERNARY_TRUE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_NONE,     NM_SUPPL_CAP_TYPE_AP), ==, NM_TERNARY_DEFAULT);
+
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_FILS_NO,  NM_SUPPL_CAP_TYPE_FILS), ==, NM_TERNARY_FALSE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_FILS_YES, NM_SUPPL_CAP_TYPE_FILS), ==, NM_TERNARY_TRUE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_NONE,       NM_SUPPL_CAP_TYPE_FILS), ==, NM_TERNARY_DEFAULT);
+
+	for (type = 0; type < _NM_SUPPL_CAP_TYPE_NUM; type++) {
+		NMTernary value;
+		NMSupplCapMask feature;
+		NMSupplCapMask feature2;
+
+		feature =   nmtst_get_rand_bool ()
+		          ? 0u
+		          : nmtst_get_rand_uint64 ();
+		feature &= NM_SUPPL_CAP_MASK_ALL;
+
+		value = nmtst_rand_select (NM_TERNARY_DEFAULT,
+		                           NM_TERNARY_FALSE,
+		                           NM_TERNARY_TRUE);
+
+		feature2 = NM_SUPPL_CAP_MASK_SET (feature, type, value);
+
+		g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (feature2, type), ==, value);
+		g_assert_cmpint (feature & ~NM_SUPPL_CAP_MASK_MASK (type), ==, feature2 & ~NM_SUPPL_CAP_MASK_MASK (type));
+	}
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int main (int argc, char **argv)
@@ -634,6 +671,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/supplicant-config/wifi-eap/unlocked-bssid", test_wifi_eap_unlocked_bssid);
 	g_test_add_func ("/supplicant-config/wifi-eap/fils-disabled", test_wifi_eap_fils_disabled);
 	g_test_add_func ("/supplicant-config/wifi-sae", test_wifi_sae);
+	g_test_add_func ("/supplicant-config/test_suppl_cap_mask", test_suppl_cap_mask);
 
 	return g_test_run ();
 }