summary refs log tree commit diff
path: root/src/devices/wifi/nm-device-wifi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/wifi/nm-device-wifi.c')
-rw-r--r--src/devices/wifi/nm-device-wifi.c782
1 files changed, 424 insertions, 358 deletions
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 98f9df25..266a8425 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -11,6 +11,8 @@
 #include <netinet/in.h>
 #include <unistd.h>
 
+#include "nm-glib-aux/nm-ref-string.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
 #include "nm-libnm-core-intern/nm-common-macros.h"
@@ -42,10 +44,9 @@
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWifi);
 
-/* All of these are in seconds */
-#define SCAN_INTERVAL_MIN 3
-#define SCAN_INTERVAL_STEP 20
-#define SCAN_INTERVAL_MAX 120
+#define SCAN_INTERVAL_SEC_MIN   3
+#define SCAN_INTERVAL_SEC_STEP 20
+#define SCAN_INTERVAL_SEC_MAX 120
 
 #define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5
 
@@ -62,7 +63,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 );
 
 enum {
-	SCANNING_PROHIBITED,
 	P2P_DEVICE_CREATED,
 
 	LAST_SIGNAL
@@ -71,9 +71,10 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
-	gint8             invalid_strength_counter;
-
 	CList             aps_lst_head;
+	GHashTable       *aps_idx_by_supplicant_path;
+
+	CList             scanning_prohibited_lst_head;
 
 	NMWifiAP *        current_ap;
 	guint32           rate;
@@ -83,13 +84,15 @@ typedef struct {
 	bool              is_scanning:1;
 	bool              hidden_probe_scan_warn:1;
 
-	gint64            last_scan; /* milliseconds */
+	gint64            last_scan_msec;
+
 	gint32            scheduled_scan_time; /* seconds */
-	guint8            scan_interval; /* seconds */
+	guint8            scan_interval_sec;
 	guint             pending_scan_id;
 	guint             ap_dump_id;
 
 	NMSupplicantManager   *sup_mgr;
+	NMSupplMgrCreateIfaceHandle *sup_create_handle;
 	NMSupplicantInterface *sup_iface;
 	guint                  sup_timeout_id; /* supplicant association timeout */
 
@@ -97,7 +100,7 @@ typedef struct {
 
 	NMActRequestGetSecretsCallId *wifi_secrets_id;
 
-	guint             periodic_source_id;
+	guint             periodic_update_id;
 	guint             link_timeout_id;
 	guint32           failed_iface_count;
 	guint             reacquire_iface_id;
@@ -122,53 +125,46 @@ struct _NMDeviceWifi
 struct _NMDeviceWifiClass
 {
 	NMDeviceClass parent;
-
-	/* Signals */
-	gboolean (*scanning_prohibited) (NMDeviceWifi *device, gboolean periodic);
 };
 
 /*****************************************************************************/
 
 G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_WIFI_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceWifi, NM_IS_DEVICE_WIFI)
+#define NM_DEVICE_WIFI_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceWifi, NM_IS_DEVICE_WIFI, NMDevice)
 
 /*****************************************************************************/
 
 static gboolean check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic);
 
+static void supplicant_iface_state_down (NMDeviceWifi *self);
+
 static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
 
 static void cleanup_association_attempt (NMDeviceWifi * self,
                                          gboolean disconnect);
 
+static void supplicant_iface_state (NMDeviceWifi *self,
+                                    NMSupplicantInterfaceState new_state,
+                                    NMSupplicantInterfaceState old_state,
+                                    int disconnect_reason,
+                                    gboolean is_real_signal);
+
 static void supplicant_iface_state_cb (NMSupplicantInterface *iface,
                                        int new_state_i,
                                        int old_state_i,
                                        int disconnect_reason,
                                        gpointer user_data);
 
-static void supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
-                                             const char *object_path,
-                                             GVariant *properties,
-                                             NMDeviceWifi *self);
-
-static void supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
-                                             const char *object_path,
+static void supplicant_iface_bss_changed_cb (NMSupplicantInterface *iface,
+                                             NMSupplicantBssInfo *bss_info,
+                                             gboolean is_present,
                                              NMDeviceWifi *self);
 
-static void supplicant_iface_scan_done_cb (NMSupplicantInterface * iface,
-                                           gboolean success,
-                                           NMDeviceWifi * self);
-
 static void supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
                                                  GVariant *credentials,
                                                  NMDeviceWifi *self);
 
-static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface,
-                                                 GParamSpec * pspec,
-                                                 NMDeviceWifi * self);
-
 static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
@@ -177,6 +173,10 @@ static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
                                                    GParamSpec *pspec,
                                                    NMDeviceWifi *self);
 
+static void _requested_scan_set (NMDeviceWifi *self, gboolean value);
+
+static void periodic_update (NMDeviceWifi *self);
+
 static void request_wireless_scan (NMDeviceWifi *self,
                                    gboolean periodic,
                                    gboolean force_if_scanning,
@@ -193,19 +193,66 @@ static void recheck_p2p_availability (NMDeviceWifi *self);
 
 /*****************************************************************************/
 
+void
+nm_device_wifi_scanning_prohibited_track (NMDeviceWifi *self,
+                                          gpointer tag,
+                                          gboolean temporarily_prohibited)
+{
+	NMDeviceWifiPrivate *priv;
+	NMCListElem *elem;
+
+	g_return_if_fail (NM_IS_DEVICE_WIFI (self));
+	nm_assert (tag);
+
+	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	/* We track these with a simple CList. This would be not efficient, if
+	 * there would be many users that need to be tracked at the same time (there
+	 * aren't). In fact, most of the time there is no NMDeviceOlpcMesh and
+	 * nobody tracks itself here. Optimize for that and simplicity. */
+
+	elem = nm_c_list_elem_find_first (&priv->scanning_prohibited_lst_head,
+	                                  iter,
+	                                  iter == tag);
+
+	if (!temporarily_prohibited) {
+		if (!elem)
+			return;
+
+		nm_c_list_elem_free (elem);
+		return;
+	}
+
+	if (elem)
+		return;
+
+	c_list_link_tail (&priv->scanning_prohibited_lst_head,
+	                  &nm_c_list_elem_new_stale (tag)->lst);
+}
+
+/*****************************************************************************/
+
 static void
 _ap_dump (NMDeviceWifi *self,
           NMLogLevel log_level,
           const NMWifiAP *ap,
           const char *prefix,
-          gint32 now_s)
+          gint64 now_msec)
 {
 	char buf[1024];
 
 	buf[0] = '\0';
 	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-ap: %-7s %s",
 	        prefix,
-	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_s));
+	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_msec));
+}
+
+gboolean
+nm_device_wifi_get_scanning (NMDeviceWifi *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE);
+
+	return NM_DEVICE_WIFI_GET_PRIVATE (self)->is_scanning;
 }
 
 static void
@@ -213,6 +260,7 @@ _notify_scanning (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	gboolean scanning;
+	gboolean last_scan_changed = FALSE;
 
 	scanning =    priv->sup_iface
 	           && nm_supplicant_interface_get_scanning (priv->sup_iface);
@@ -220,9 +268,42 @@ _notify_scanning (NMDeviceWifi *self)
 	if (scanning == priv->is_scanning)
 		return;
 
-	_LOGD (LOGD_WIFI, "wifi-scan: scanning-state: %s", scanning ? "scanning" : "idle");
 	priv->is_scanning = scanning;
-	_notify (self, PROP_SCANNING);
+
+	if (   !scanning
+	    || priv->last_scan_msec == 0) {
+		last_scan_changed = TRUE;
+		priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec ();
+	}
+
+	_LOGD (LOGD_WIFI,
+	       "wifi-scan: scanning-state: %s%s",
+	       scanning ? "scanning" : "idle",
+	       last_scan_changed ? " (notify last-scan)" : "");
+
+	schedule_scan (self, TRUE);
+
+	nm_gobject_notify_together (self,
+	                            PROP_SCANNING,
+	                              last_scan_changed
+	                            ? PROP_LAST_SCAN
+	                            : PROP_0);
+
+	if (!priv->is_scanning) {
+		_requested_scan_set (self, FALSE);
+		if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED) {
+			/* Run a quick update of current AP when coming out of a scan */
+			periodic_update (self);
+		}
+	}
+}
+
+static void
+supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
+                                     GParamSpec *pspec,
+                                     NMDeviceWifi *self)
+{
+	_notify_scanning (self);
 }
 
 static gboolean
@@ -237,40 +318,40 @@ unmanaged_on_quit (NMDevice *self)
 	return TRUE;
 }
 
-static gboolean
-supplicant_interface_acquire (NMDeviceWifi *self)
+static void
+supplicant_interface_acquire_cb (NMSupplicantManager *supplicant_manager,
+                                 NMSupplMgrCreateIfaceHandle *handle,
+                                 NMSupplicantInterface *iface,
+                                 GError *error,
+                                 gpointer user_data)
 {
+	NMDeviceWifi *self = user_data;
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	g_return_val_if_fail (self != NULL, FALSE);
-	g_return_val_if_fail (!priv->sup_iface, TRUE);
+	if (nm_utils_error_is_cancelled (error))
+		return;
 
-	priv->sup_iface = nm_supplicant_manager_create_interface (priv->sup_mgr,
-	                                                          nm_device_get_iface (NM_DEVICE (self)),
-	                                                          NM_SUPPLICANT_DRIVER_WIRELESS);
-	if (!priv->sup_iface) {
-		_LOGE (LOGD_WIFI, "Couldn't initialize supplicant interface");
-		return FALSE;
+	nm_assert (priv->sup_create_handle == handle);
+
+	priv->sup_create_handle = NULL;
+
+	if (error) {
+		_LOGE (LOGD_WIFI, "Couldn't initialize supplicant interface: %s",
+		       error->message);
+		supplicant_iface_state_down (self);
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+		return;
 	}
 
-	if (nm_supplicant_interface_get_state (priv->sup_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
-		nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, FALSE);
+	priv->sup_iface = g_object_ref (iface);
 
 	g_signal_connect (priv->sup_iface,
 	                  NM_SUPPLICANT_INTERFACE_STATE,
 	                  G_CALLBACK (supplicant_iface_state_cb),
 	                  self);
 	g_signal_connect (priv->sup_iface,
-	                  NM_SUPPLICANT_INTERFACE_BSS_UPDATED,
-	                  G_CALLBACK (supplicant_iface_bss_updated_cb),
-	                  self);
-	g_signal_connect (priv->sup_iface,
-	                  NM_SUPPLICANT_INTERFACE_BSS_REMOVED,
-	                  G_CALLBACK (supplicant_iface_bss_removed_cb),
-	                  self);
-	g_signal_connect (priv->sup_iface,
-	                  NM_SUPPLICANT_INTERFACE_SCAN_DONE,
-	                  G_CALLBACK (supplicant_iface_scan_done_cb),
+	                  NM_SUPPLICANT_INTERFACE_BSS_CHANGED,
+	                  G_CALLBACK (supplicant_iface_bss_changed_cb),
 	                  self);
 	g_signal_connect (priv->sup_iface,
 	                  NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
@@ -291,7 +372,30 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 
 	_notify_scanning (self);
 
-	return TRUE;
+	if (nm_supplicant_interface_get_state (priv->sup_iface) != NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		/* fake an initial state change. */
+		supplicant_iface_state (user_data,
+		                        NM_SUPPLICANT_INTERFACE_STATE_STARTING,
+		                        nm_supplicant_interface_get_state (priv->sup_iface),
+		                        0,
+		                        FALSE);
+	}
+}
+
+static void
+supplicant_interface_acquire (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	nm_assert (!priv->sup_iface);
+	nm_assert (!priv->sup_create_handle);
+
+	priv->sup_create_handle = nm_supplicant_manager_create_interface (priv->sup_mgr,
+	                                                                  nm_device_get_ifindex (NM_DEVICE (self)),
+	                                                                  NM_SUPPLICANT_DRIVER_WIRELESS,
+	                                                                  supplicant_interface_acquire_cb,
+	                                                                  self);
+	nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
 }
 
 static void
@@ -310,27 +414,26 @@ _requested_scan_set (NMDeviceWifi *self, gboolean value)
 		nm_device_add_pending_action ((NMDevice *) self, NM_PENDING_ACTION_WIFI_SCAN, TRUE);
 	else {
 		nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
-		nm_device_remove_pending_action ((NMDevice *) self, NM_PENDING_ACTION_WIFI_SCAN, TRUE);
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, TRUE);
 	}
 }
 
 static void
 supplicant_interface_release (NMDeviceWifi *self)
 {
-	NMDeviceWifiPrivate *priv;
-
-	g_return_if_fail (self != NULL);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	if (nm_clear_pointer (&priv->sup_create_handle, nm_supplicant_manager_create_interface_cancel))
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
 
 	_requested_scan_set (self, FALSE);
 
 	nm_clear_g_source (&priv->pending_scan_id);
 
 	/* Reset the scan interval to be pretty frequent when disconnected */
-	priv->scan_interval = SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP;
+	priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP;
 	_LOGD (LOGD_WIFI, "wifi-scan: reset interval to %u seconds",
-	       (unsigned) priv->scan_interval);
+	       (unsigned) priv->scan_interval_sec);
 
 	nm_clear_g_source (&priv->ap_dump_id);
 
@@ -410,44 +513,48 @@ set_current_ap (NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available
 static void
 periodic_update (NMDeviceWifi *self)
 {
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	int ifindex = nm_device_get_ifindex (NM_DEVICE (self));
+	NMDeviceWifiPrivate *priv;
+	int ifindex;
 	guint32 new_rate;
-	int percent;
-	NMDeviceState state;
-	NMSupplicantInterfaceState supplicant_state;
 
-	/* BSSID and signal strength have meaningful values only if the device
-	 * is activated and not scanning.
-	 */
-	state = nm_device_get_state (NM_DEVICE (self));
-	if (state != NM_DEVICE_STATE_ACTIVATED)
+	if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_ACTIVATED) {
+		/* BSSID and signal strength have meaningful values only if the device
+		 * is activated and not scanning.
+		 */
 		return;
+	}
 
-	/* Only update current AP if we're actually talking to something, otherwise
-	 * assume the old one (if any) is still valid until we're told otherwise or
-	 * the connection fails.
-	 */
-	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
-	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING
-	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED
-	    || nm_supplicant_interface_get_scanning (priv->sup_iface))
+	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	if (   !nm_supplicant_interface_state_is_associated (nm_supplicant_interface_get_state (priv->sup_iface))
+	    || nm_supplicant_interface_get_scanning (priv->sup_iface)) {
+		/* Only update current AP if we're actually talking to something, otherwise
+		 * assume the old one (if any) is still valid until we're told otherwise or
+		 * the connection fails.
+		 */
 		return;
+	}
 
-	/* In AP mode we currently have nothing to do. */
-	if (priv->mode == NM_802_11_MODE_AP)
+	if (priv->mode == NM_802_11_MODE_AP) {
+		/* In AP mode we currently have nothing to do. */
 		return;
+	}
+
+	ifindex = nm_device_get_ifindex (NM_DEVICE (self));
+	if (ifindex <= 0)
+		g_return_if_reached ();
 
 	if (priv->current_ap) {
-		/* Smooth out the strength to work around crappy drivers */
+		int percent;
+
 		percent = nm_platform_wifi_get_quality (nm_device_get_platform (NM_DEVICE (self)), ifindex);
-		if (percent >= 0 || ++priv->invalid_strength_counter > 3) {
+		if (   percent >= 0
+		    && percent <= 100) {
 			if (nm_wifi_ap_set_strength (priv->current_ap, (gint8) percent)) {
 #if NM_MORE_LOGGING
 				_ap_dump (self, LOGL_TRACE, priv->current_ap, "updated", 0);
 #endif
 			}
-			priv->invalid_strength_counter = 0;
 		}
 	}
 
@@ -461,7 +568,7 @@ periodic_update (NMDeviceWifi *self)
 static gboolean
 periodic_update_cb (gpointer user_data)
 {
-	periodic_update (NM_DEVICE_WIFI (user_data));
+	periodic_update (user_data);
 	return TRUE;
 }
 
@@ -477,12 +584,16 @@ ap_add_remove (NMDeviceWifi *self,
 		g_object_ref (ap);
 		ap->wifi_device = NM_DEVICE (self);
 		c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst);
+		if (!g_hash_table_insert (priv->aps_idx_by_supplicant_path, nm_wifi_ap_get_supplicant_path (ap), ap))
+			nm_assert_not_reached ();
 		nm_dbus_object_export (NM_DBUS_OBJECT (ap));
 		_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
 		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE);
 	} else {
 		ap->wifi_device = NULL;
 		c_list_unlink (&ap->aps_lst);
+		if (!g_hash_table_remove (priv->aps_idx_by_supplicant_path, nm_wifi_ap_get_supplicant_path (ap)))
+			nm_assert_not_reached ();
 		_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
 	}
 
@@ -593,7 +704,7 @@ deactivate (NMDevice *device)
 	int ifindex = nm_device_get_ifindex (device);
 	NM80211Mode old_mode = priv->mode;
 
-	nm_clear_g_source (&priv->periodic_source_id);
+	nm_clear_g_source (&priv->periodic_update_id);
 
 	cleanup_association_attempt (self, TRUE);
 
@@ -694,7 +805,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		}
 
 		if (priv->sup_iface) {
-			if (nm_supplicant_interface_get_ap_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_NO) {
+			if (nm_supplicant_interface_get_capability (priv->sup_iface, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_FALSE) {
 				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 				                            "wpa_supplicant does not support Access Point mode");
 				return FALSE;
@@ -708,7 +819,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		}
 
 		if (priv->sup_iface) {
-			if (nm_supplicant_interface_get_mesh_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_NO) {
+			if (nm_supplicant_interface_get_capability (priv->sup_iface, NM_SUPPL_CAP_TYPE_MESH) == NM_TERNARY_FALSE) {
 				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 				                            "wpa_supplicant does not support Mesh mode");
 				return FALSE;
@@ -936,7 +1047,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 		return FALSE;
 
 	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
-	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	if (   supplicant_state <= NM_SUPPLICANT_INTERFACE_STATE_STARTING
 	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		return FALSE;
 
@@ -1047,7 +1158,7 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 		return;
 	}
 
-	now = nm_utils_get_monotonic_timestamp_s ();
+	now = nm_utils_get_monotonic_timestamp_sec ();
 
 	if (now >= priv->hw_addr_scan_expire) {
 		gs_free char *generate_mac_address_mask = NULL;
@@ -1074,16 +1185,17 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 static GPtrArray *
 ssids_options_to_ptrarray (GVariant *value, GError **error)
 {
-	GPtrArray *ssids = NULL;
-	const guint8 *bytes;
-	gsize len;
-	int num_ssids, i;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
+	gsize num_ssids;
+	gsize i;
+
+	nm_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("aay")));
 
 	num_ssids = g_variant_n_children (value);
 	if (num_ssids > 32) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
-		                     NM_DEVICE_ERROR_NOT_ALLOWED,
+		                     NM_DEVICE_ERROR_INVALID_ARGUMENT,
 		                     "too many SSIDs requested to scan");
 		return NULL;
 	}
@@ -1092,22 +1204,30 @@ ssids_options_to_ptrarray (GVariant *value, GError **error)
 		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_bytes_unref);
 		for (i = 0; i < num_ssids; i++) {
 			gs_unref_variant GVariant *v = NULL;
+			gsize len;
+			const guint8 *bytes;
 
 			v = g_variant_get_child_value (value, i);
 			bytes = g_variant_get_fixed_array (v, &len, sizeof (guint8));
 			if (len > 32) {
 				g_set_error (error,
 				             NM_DEVICE_ERROR,
-				             NM_DEVICE_ERROR_NOT_ALLOWED,
-				             "SSID at index %d more than 32 bytes", i);
-				g_ptr_array_unref (ssids);
+				             NM_DEVICE_ERROR_INVALID_ARGUMENT,
+				             "SSID at index %d more than 32 bytes", (int) i);
 				return NULL;
 			}
 
 			g_ptr_array_add (ssids, g_bytes_new (bytes, len));
 		}
 	}
-	return ssids;
+
+	return g_steal_pointer (&ssids);
+}
+
+GPtrArray *
+nmtst_ssids_options_to_ptrarray (GVariant *value, GError **error)
+{
+	return ssids_options_to_ptrarray (value, error);
 }
 
 static void
@@ -1118,8 +1238,7 @@ dbus_request_scan_cb (NMDevice *device,
                       gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
-	gs_unref_variant GVariant *scan_options = user_data;
-	gs_unref_ptrarray GPtrArray *ssids = NULL;
+	gs_unref_ptrarray GPtrArray *ssids = user_data;
 
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
@@ -1134,41 +1253,42 @@ dbus_request_scan_cb (NMDevice *device,
 		return;
 	}
 
-	if (scan_options) {
-		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
+	request_wireless_scan (self, FALSE, FALSE, ssids);
+	g_dbus_method_invocation_return_value (context, NULL);
+}
+
+void
+_nm_device_wifi_request_scan (NMDeviceWifi *self,
+                              GVariant *options,
+                              GDBusMethodInvocation *invocation)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gint64 last_scan;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
+
+	if (options) {
+		gs_unref_variant GVariant *val = g_variant_lookup_value (options, "ssids", NULL);
 
 		if (val) {
 			gs_free_error GError *ssid_error = NULL;
 
 			if (!g_variant_is_of_type (val, G_VARIANT_TYPE ("aay"))) {
-				g_dbus_method_invocation_return_error_literal (context,
+				g_dbus_method_invocation_return_error_literal (invocation,
 				                                               NM_DEVICE_ERROR,
-				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               NM_DEVICE_ERROR_INVALID_ARGUMENT,
 				                                               "Invalid 'ssid' scan option");
 				return;
 			}
 
 			ssids = ssids_options_to_ptrarray (val, &ssid_error);
 			if (ssid_error) {
-				g_dbus_method_invocation_return_gerror (context, ssid_error);
+				g_dbus_method_invocation_return_gerror (invocation, ssid_error);
 				return;
 			}
 		}
 	}
 
-	request_wireless_scan (self, FALSE, FALSE, ssids);
-	g_dbus_method_invocation_return_value (context, NULL);
-}
-
-void
-_nm_device_wifi_request_scan (NMDeviceWifi *self,
-                              GVariant *options,
-                              GDBusMethodInvocation *invocation)
-{
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMDevice *device = NM_DEVICE (self);
-	gint64 last_scan;
-
 	if (   !priv->enabled
 	    || !priv->sup_iface
 	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
@@ -1189,7 +1309,13 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	}
 
 	last_scan = nm_supplicant_interface_get_last_scan (priv->sup_iface);
-	if (last_scan && (nm_utils_get_monotonic_timestamp_ms () - last_scan) < 10 * NM_UTILS_MSEC_PER_SECOND) {
+	if (   last_scan > 0
+	    && nm_utils_get_monotonic_timestamp_msec () < last_scan + (10 * NM_UTILS_MSEC_PER_SEC)) {
+		/* FIXME: we really should not outright reject a scan request in this case. We should
+		 * ensure to start a scan request soon, possibly with rate limiting. And there is no
+		 * need to tell the caller that we aren't going to scan...
+		 *
+		 * Same above, if we are currently scanning... */
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1204,16 +1330,19 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
-	                       options ? g_variant_ref (options) : NULL);
+	                       g_steal_pointer (&ssids));
 }
 
 static gboolean
-scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
+check_scanning_prohibited (NMDeviceWifi *self,
+                           gboolean periodic)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMSupplicantInterfaceState supplicant_state;
 
-	g_return_val_if_fail (priv->sup_iface != NULL, TRUE);
+	nm_assert (NM_IS_SUPPLICANT_INTERFACE (priv->sup_iface));
+
+	if (!c_list_is_empty (&priv->scanning_prohibited_lst_head))
+		return TRUE;
 
 	/* Don't scan when a an AP or Ad-Hoc connection is active as it will
 	 * disrupt connected clients or peers.
@@ -1250,11 +1379,11 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 	}
 
 	/* Prohibit scans if the supplicant is busy */
-	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
-	if (   supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
-	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED
-	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE
-	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE
+	if (   NM_IN_SET (nm_supplicant_interface_get_state (priv->sup_iface),
+	                  NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,
+	                  NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,
+	                  NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
+	                  NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE)
 	    || nm_supplicant_interface_get_scanning (priv->sup_iface))
 		return TRUE;
 
@@ -1263,15 +1392,6 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 }
 
 static gboolean
-check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
-{
-	gboolean prohibited = FALSE;
-
-	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
-	return prohibited;
-}
-
-static gboolean
 hidden_filter_func (NMSettings *settings,
                     NMSettingsConnection *set_con,
                     gpointer user_data)
@@ -1420,16 +1540,17 @@ static void
 schedule_scan (NMDeviceWifi *self, gboolean backoff)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 
-	/* Cancel the pending scan if it would happen later than (now + the scan_interval) */
+	/* Cancel the pending scan if it would happen later than (now + the scan_interval_sec) */
 	if (priv->pending_scan_id) {
-		if (now + priv->scan_interval < priv->scheduled_scan_time)
+		if (now + priv->scan_interval_sec < priv->scheduled_scan_time)
 			nm_clear_g_source (&priv->pending_scan_id);
 	}
 
 	if (!priv->pending_scan_id) {
-		guint factor = 2, next_scan = priv->scan_interval;
+		guint factor = 2;
+		guint next_scan = priv->scan_interval_sec;
 
 		if (    nm_device_is_activating (NM_DEVICE (self))
 		    || (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED))
@@ -1439,41 +1560,25 @@ schedule_scan (NMDeviceWifi *self, gboolean backoff)
 		                                               request_wireless_scan_periodic,
 		                                               self);
 
-		priv->scheduled_scan_time = now + priv->scan_interval;
-		if (backoff && (priv->scan_interval < (SCAN_INTERVAL_MAX / factor))) {
-				priv->scan_interval += (SCAN_INTERVAL_STEP / factor);
+		priv->scheduled_scan_time = now + priv->scan_interval_sec;
+		if (backoff && (priv->scan_interval_sec < (SCAN_INTERVAL_SEC_MAX / factor))) {
+				priv->scan_interval_sec += (SCAN_INTERVAL_SEC_STEP / factor);
 				/* Ensure the scan interval will never be less than 20s... */
-				priv->scan_interval = MAX(priv->scan_interval, SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP);
+				priv->scan_interval_sec = MAX(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP);
 				/* ... or more than 120s */
-				priv->scan_interval = MIN(priv->scan_interval, SCAN_INTERVAL_MAX);
-		} else if (!backoff && (priv->scan_interval == 0)) {
+				priv->scan_interval_sec = MIN(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MAX);
+		} else if (!backoff && (priv->scan_interval_sec == 0)) {
 			/* Invalid combination; would cause continual rescheduling of
 			 * the scan and hog CPU.  Reset to something minimally sane.
 			 */
-			priv->scan_interval = 5;
+			priv->scan_interval_sec = 5;
 		}
 
 		_LOGD (LOGD_WIFI, "wifi-scan: scheduled in %d seconds (interval now %d seconds)",
-		       next_scan, priv->scan_interval);
+		       next_scan, priv->scan_interval_sec);
 	}
 }
 
-static void
-supplicant_iface_scan_done_cb (NMSupplicantInterface *iface,
-                               gboolean success,
-                               NMDeviceWifi *self)
-{
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
-	_LOGD (LOGD_WIFI, "wifi-scan: scan-done callback: %s", success ? "successful" : "failed");
-
-	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
-	_notify (self, PROP_LAST_SCAN);
-	schedule_scan (self, success);
-
-	_requested_scan_set (self, FALSE);
-}
-
 /****************************************************************************
  * WPA Supplicant control stuff
  *
@@ -1489,14 +1594,21 @@ ap_list_dump (gpointer user_data)
 
 	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
 		NMWifiAP *ap;
-		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
-
-		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u last:%" G_GINT64_FORMAT " next:%u]",
-		       now_s,
-		       priv->last_scan / NM_UTILS_MSEC_PER_SECOND,
+		gint64 now_msec = nm_utils_get_monotonic_timestamp_msec ();
+		char str_buf[100];
+
+		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u.%03u, last:%s, next:%u]",
+		       (guint) (now_msec / NM_UTILS_MSEC_PER_SEC),
+		       (guint) (now_msec % NM_UTILS_MSEC_PER_SEC),
+		         priv->last_scan_msec > 0
+		       ? nm_sprintf_buf (str_buf,
+		                         "%u.%03u",
+		                         (guint) (priv->last_scan_msec / NM_UTILS_MSEC_PER_SEC),
+		                         (guint) (priv->last_scan_msec % NM_UTILS_MSEC_PER_SEC))
+		       : "-1",
 		       priv->scheduled_scan_time);
 		c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
-			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_s);
+			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_msec);
 	}
 	return G_SOURCE_REMOVE;
 }
@@ -1543,40 +1655,43 @@ try_fill_ssid_for_hidden_ap (NMDeviceWifi *self,
 }
 
 static void
-supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
-                                 const char *object_path,
-                                 GVariant *properties,
+supplicant_iface_bss_changed_cb (NMSupplicantInterface *iface,
+                                 NMSupplicantBssInfo *bss_info,
+                                 gboolean is_present,
                                  NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMDeviceState state;
-	NMWifiAP *found_ap = NULL;
+	NMWifiAP *found_ap;
 	GBytes *ssid;
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (properties != NULL);
-	g_return_if_fail (iface != NULL);
+	found_ap = g_hash_table_lookup (priv->aps_idx_by_supplicant_path, bss_info->bss_path);
 
-	/* Ignore new APs when unavailable, unmanaged, or in AP mode */
-	state = nm_device_get_state (NM_DEVICE (self));
-	if (state <= NM_DEVICE_STATE_UNAVAILABLE)
-		return;
-	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP)
+	if (!is_present) {
+		if (!found_ap)
+			return;
+		if (found_ap == priv->current_ap) {
+			/* The current AP cannot be removed (to prevent NM indicating that
+			 * it is connected, but to nothing), but it must be removed later
+			 * when the current AP is changed or cleared.  Set 'fake' to
+			 * indicate that this AP is now unknown to the supplicant.
+			 */
+			if (nm_wifi_ap_set_fake (found_ap, TRUE))
+				_ap_dump (self, LOGL_DEBUG, found_ap, "updated", 0);
+		} else {
+			ap_add_remove (self, FALSE, found_ap, TRUE);
+			schedule_ap_list_dump (self);
+		}
 		return;
+	}
 
-	found_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
 	if (found_ap) {
-		if (!nm_wifi_ap_update_from_properties (found_ap, object_path, properties))
+		if (!nm_wifi_ap_update_from_properties (found_ap, bss_info))
 			return;
 		_ap_dump (self, LOGL_DEBUG, found_ap, "updated", 0);
 	} else {
 		gs_unref_object NMWifiAP *ap = NULL;
 
-		ap = nm_wifi_ap_new_from_properties (object_path, properties);
-		if (!ap) {
-			_LOGD (LOGD_WIFI, "invalid AP properties received for %s", object_path);
-			return;
-		}
+		ap = nm_wifi_ap_new_from_properties (bss_info);
 
 		/* Let the manager try to fill in the SSID from seen-bssids lists */
 		ssid = nm_wifi_ap_get_ssid (ap);
@@ -1606,43 +1721,13 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 	/* Update the current AP if the supplicant notified a current BSS change
 	 * before it sent the current BSS's scan result.
 	 */
-	if (g_strcmp0 (nm_supplicant_interface_get_current_bss (iface), object_path) == 0)
+	if (nm_supplicant_interface_get_current_bss (iface) == bss_info->bss_path)
 		supplicant_iface_notify_current_bss (priv->sup_iface, NULL, self);
 
 	schedule_ap_list_dump (self);
 }
 
 static void
-supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
-                                 const char *object_path,
-                                 NMDeviceWifi *self)
-{
-	NMDeviceWifiPrivate *priv;
-	NMWifiAP *ap;
-
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (object_path != NULL);
-
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
-	if (!ap)
-		return;
-
-	if (ap == priv->current_ap) {
-		/* The current AP cannot be removed (to prevent NM indicating that
-		 * it is connected, but to nothing), but it must be removed later
-		 * when the current AP is changed or cleared.  Set 'fake' to
-		 * indicate that this AP is now unknown to the supplicant.
-		 */
-		if (nm_wifi_ap_set_fake (ap, TRUE))
-			_ap_dump (self, LOGL_DEBUG, ap, "updated", 0);
-	} else {
-		ap_add_remove (self, FALSE, ap, TRUE);
-		schedule_ap_list_dump (self);
-	}
-}
-
-static void
 cleanup_association_attempt (NMDeviceWifi *self, gboolean disconnect)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
@@ -1725,7 +1810,7 @@ wifi_secrets_cb (NMActRequest *req,
 		return;
 	}
 
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -1792,7 +1877,7 @@ supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
 	}
 
 	wifi_secrets_cancel (self);
-	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self), FALSE);
 }
 
 static gboolean
@@ -2020,50 +2105,71 @@ reacquire_interface_cb (gpointer user_data)
 }
 
 static void
-supplicant_iface_state_cb (NMSupplicantInterface *iface,
-                           int new_state_i,
-                           int old_state_i,
-                           int disconnect_reason,
-                           gpointer user_data)
+supplicant_iface_state_down (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+
+	nm_device_queue_recheck_available (device,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	cleanup_association_attempt (self, FALSE);
+
+	/* If the device is already in UNAVAILABLE state then the state change
+	 * is a NOP and the interface won't be re-acquired in the device state
+	 * change handler.  So ensure we have a new one here so that we're
+	 * ready if the supplicant comes back.
+	 */
+	supplicant_interface_release (self);
+	if (priv->failed_iface_count < 5)
+		priv->reacquire_iface_id = g_timeout_add_seconds (10, reacquire_interface_cb, self);
+	else
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "supplicant interface keeps failing, giving up");
+}
+
+static void
+supplicant_iface_state (NMDeviceWifi *self,
+                        NMSupplicantInterfaceState new_state,
+                        NMSupplicantInterfaceState old_state,
+                        int disconnect_reason,
+                        gboolean is_real_signal)
 {
-	NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceState devstate;
 	gboolean scanning;
-	NMSupplicantInterfaceState new_state = new_state_i;
-	NMSupplicantInterfaceState old_state = old_state_i;
-
-	if (new_state == old_state)
-		return;
 
 	_LOGI (LOGD_DEVICE | LOGD_WIFI,
-	       "supplicant interface state: %s -> %s",
+	       "supplicant interface state: %s -> %s%s",
 	       nm_supplicant_interface_state_to_string (old_state),
-	       nm_supplicant_interface_state_to_string (new_state));
+	       nm_supplicant_interface_state_to_string (new_state),
+	       is_real_signal ? "" : " (simulated signal)");
+
+	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
+		supplicant_iface_state_down (self);
+		goto out;
+	}
 
 	devstate = nm_device_get_state (device);
-	scanning = nm_supplicant_interface_get_scanning (iface);
+	scanning = nm_supplicant_interface_get_scanning (priv->sup_iface);
+
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		_LOGD (LOGD_WIFI, "supplicant ready");
+		nm_device_queue_recheck_available (NM_DEVICE (device),
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN;
+	}
 
 	/* In these states we know the supplicant is actually talking to something */
 	if (   new_state >= NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
 	    && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		priv->ssid_found = TRUE;
 
-	if (   old_state < NM_SUPPLICANT_INTERFACE_STATE_READY
-	    && new_state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
 		recheck_p2p_availability (self);
 
 	switch (new_state) {
-	case NM_SUPPLICANT_INTERFACE_STATE_READY:
-		_LOGD (LOGD_WIFI, "supplicant ready");
-		nm_device_queue_recheck_available (NM_DEVICE (device),
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		priv->scan_interval = SCAN_INTERVAL_MIN;
-		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
-			nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
-		break;
 	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
 		nm_clear_g_source (&priv->sup_timeout_id);
 		nm_clear_g_source (&priv->link_timeout_id);
@@ -2118,26 +2224,6 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 			}
 		}
 		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
-		nm_device_queue_recheck_available (NM_DEVICE (device),
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		cleanup_association_attempt (self, FALSE);
-
-		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
-			nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
-
-		/* If the device is already in UNAVAILABLE state then the state change
-		 * is a NOP and the interface won't be re-acquired in the device state
-		 * change handler.  So ensure we have a new one here so that we're
-		 * ready if the supplicant comes back.
-		 */
-		supplicant_interface_release (self);
-		if (priv->failed_iface_count < 5)
-			priv->reacquire_iface_id = g_timeout_add_seconds (10, reacquire_interface_cb, self);
-		else
-			_LOGI (LOGD_DEVICE | LOGD_WIFI, "supplicant interface keeps failing, giving up");
-		break;
 	case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
 		/* we would clear _requested_scan_set() and trigger a new scan.
 		 * However, we don't want to cancel the current pending action, so force
@@ -2148,10 +2234,25 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		break;
 	}
 
-	/* Signal scanning state changes */
-	if (   new_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
-	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		_notify_scanning (self);
+out:
+	_notify_scanning (self);
+
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
+		nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+}
+
+static void
+supplicant_iface_state_cb (NMSupplicantInterface *iface,
+                           int new_state_i,
+                           int old_state_i,
+                           int disconnect_reason,
+                           gpointer user_data)
+{
+	supplicant_iface_state (user_data,
+	                        new_state_i,
+	                        old_state_i,
+	                        disconnect_reason,
+	                        TRUE);
 }
 
 static void
@@ -2162,7 +2263,7 @@ supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
 	NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
 	NMDevice *device = NM_DEVICE (self);
 
-	if (   error && !nm_utils_error_is_cancelled (error, TRUE)
+	if (   error && !nm_utils_error_is_cancelled_or_disposing (error)
 	    && nm_device_is_activating (device)) {
 		cleanup_association_attempt (self, TRUE);
 		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
@@ -2170,30 +2271,17 @@ supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
 }
 
 static void
-supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
-                                     GParamSpec *pspec,
-                                     NMDeviceWifi *self)
-{
-	_notify_scanning (self);
-
-	/* Run a quick update of current AP when coming out of a scan */
-	if (   !NM_DEVICE_WIFI_GET_PRIVATE (self)->is_scanning
-	    && nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED)
-		periodic_update (self);
-}
-
-static void
 supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                      GParamSpec *pspec,
                                      NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	const char *current_bss;
+	NMRefString *current_bss;
 	NMWifiAP *new_ap = NULL;
 
 	current_bss = nm_supplicant_interface_get_current_bss (iface);
 	if (current_bss)
-		new_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, current_bss);
+		new_ap = g_hash_table_lookup (priv->aps_idx_by_supplicant_path, current_bss);
 
 	if (new_ap != priv->current_ap) {
 		const char *new_bssid = NULL;
@@ -2284,11 +2372,8 @@ supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
                                        GParamSpec *pspec,
                                        NMDeviceWifi *self)
 {
-	/* Do not update when the interface is still initializing. */
-	if (nm_supplicant_interface_get_state (iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
-		return;
-
-	recheck_p2p_availability (self);
+	if (nm_supplicant_interface_get_state (iface) > NM_SUPPLICANT_INTERFACE_STATE_STARTING)
+		recheck_p2p_availability (self);
 }
 
 static gboolean
@@ -2472,15 +2557,11 @@ build_supplicant_config (NMDeviceWifi *self,
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless != NULL, NULL);
 
-	config = nm_supplicant_config_new (
-		nm_supplicant_interface_get_pmf_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
-		nm_supplicant_interface_get_fils_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
-		nm_supplicant_interface_get_ft_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
-		nm_supplicant_interface_get_sha384_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES);
+	config = nm_supplicant_config_new (nm_supplicant_interface_get_capabilities (priv->sup_iface));
 
 	/* Warn if AP mode may not be supported */
-	if (   g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0
-	    && nm_supplicant_interface_get_ap_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_UNKNOWN) {
+	if (   nm_streq0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP)
+	    && nm_supplicant_interface_get_capability (priv->sup_iface, NM_SUPPL_CAP_TYPE_AP) != NM_TERNARY_TRUE) {
 		_LOGW (LOGD_WIFI, "Supplicant may not support AP mode; connection may time out.");
 	}
 
@@ -2753,8 +2834,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
-	NMSupplicantConfig *config = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
 	NM80211Mode ap_mode;
 	NMActRequest *req;
 	NMWifiAP *ap;
@@ -2774,15 +2854,14 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	ap = priv->current_ap;
 	if (!ap) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		goto out;
+		goto out_fail;
 	}
+
 	ap_mode = nm_wifi_ap_get_mode (ap);
 
 	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
-
 	s_wireless = nm_connection_get_setting_wireless (connection);
-	g_assert (s_wireless);
+	nm_assert (s_wireless);
 
 	/* If we need secrets, get them */
 	setting_name = nm_connection_need_secrets (connection, NULL);
@@ -2791,13 +2870,12 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		       "Activation: (wifi) access point '%s' has security, but secrets are required.",
 		       nm_connection_get_id (connection));
 
-		if (handle_auth_or_fail (self, req, FALSE))
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		else {
+		if (!handle_auth_or_fail (self, req, FALSE)) {
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-			ret = NM_ACT_STAGE_RETURN_FAILURE;
+			goto out_fail;
 		}
-		goto out;
+
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
 	if (!wake_on_wlan_enable (self))
@@ -2830,17 +2908,19 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	/* Build up the supplicant configuration */
 	config = build_supplicant_config (self, connection, nm_wifi_ap_get_freq (ap), &error);
-	if (config == NULL) {
+	if (!config) {
 		_LOGE (LOGD_DEVICE | LOGD_WIFI,
 		       "Activation: (wifi) couldn't build wireless configuration: %s",
 		       error->message);
 		g_clear_error (&error);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
-		goto out;
+		goto out_fail;
 	}
 
-	nm_supplicant_interface_assoc (priv->sup_iface, config,
-	                               supplicant_iface_assoc_cb, self);
+	nm_supplicant_interface_assoc (priv->sup_iface,
+	                               config,
+	                               supplicant_iface_assoc_cb,
+	                               self);
 
 	/* Set up a timeout on the association attempt */
 	timeout = nm_device_get_supplicant_timeout (NM_DEVICE (self));
@@ -2848,25 +2928,16 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	                                              supplicant_connection_timeout_cb,
 	                                              self);
 
-	if (!priv->periodic_source_id)
-		priv->periodic_source_id = g_timeout_add_seconds (6, periodic_update_cb, self);
+	if (!priv->periodic_update_id)
+		priv->periodic_update_id = g_timeout_add_seconds (6, periodic_update_cb, self);
 
 	/* We'll get stage3 started when the supplicant connects */
-	ret = NM_ACT_STAGE_RETURN_POSTPONE;
-
-out:
-	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-		cleanup_association_attempt (self, TRUE);
-		wake_on_wlan_restore (self);
-	}
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 
-	if (config) {
-		/* Supplicant interface object refs the config; we no longer care about
-		 * it after this function.
-		 */
-		g_object_unref (config);
-	}
-	return ret;
+out_fail:
+	cleanup_association_attempt (self, TRUE);
+	wake_on_wlan_restore (self);
+	return NM_ACT_STAGE_RETURN_FAILURE;
 }
 
 static NMActStageReturn
@@ -3033,7 +3104,7 @@ activation_success_handler (NMDevice *device)
 	update_seen_bssids_cache (self, priv->current_ap);
 
 	/* Reset scan interval to something reasonable */
-	priv->scan_interval = SCAN_INTERVAL_MIN + (SCAN_INTERVAL_STEP * 2);
+	priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + (SCAN_INTERVAL_SEC_STEP * 2);
 }
 
 static void
@@ -3053,10 +3124,9 @@ device_state_changed (NMDevice *device,
 		/* Clean up the supplicant interface because in these states the
 		 * device cannot be used.
 		 */
-		if (priv->sup_iface)
-			supplicant_interface_release (self);
+		supplicant_interface_release (self);
 
-		nm_clear_g_source (&priv->periodic_source_id);
+		nm_clear_g_source (&priv->periodic_update_id);
 
 		cleanup_association_attempt (self, TRUE);
 		cleanup_supplicant_failures (self);
@@ -3095,7 +3165,7 @@ device_state_changed (NMDevice *device,
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		/* Kick off a scan to get latest results */
-		priv->scan_interval = SCAN_INTERVAL_MIN;
+		priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN;
 		request_wireless_scan (self, FALSE, FALSE, NULL);
 		break;
 	default:
@@ -3109,7 +3179,7 @@ device_state_changed (NMDevice *device,
 static gboolean
 get_enabled (NMDevice *device)
 {
-	return NM_DEVICE_WIFI_GET_PRIVATE ((NMDeviceWifi *) device)->enabled;
+	return NM_DEVICE_WIFI_GET_PRIVATE (device)->enabled;
 }
 
 static void
@@ -3155,8 +3225,7 @@ set_enabled (NMDevice *device, gboolean enabled)
 
 		/* Re-initialize the supplicant interface and wait for it to be ready */
 		cleanup_supplicant_failures (self);
-		if (priv->sup_iface)
-			supplicant_interface_release (self);
+		supplicant_interface_release (self);
 		supplicant_interface_acquire (self);
 
 		_LOGD (LOGD_WIFI, "enable waiting on supplicant state");
@@ -3251,13 +3320,13 @@ get_property (GObject *object, guint prop_id,
 		nm_dbus_utils_g_value_set_object_path (value, priv->current_ap);
 		break;
 	case PROP_SCANNING:
-		g_value_set_boolean (value, priv->is_scanning);
+		g_value_set_boolean (value, nm_device_wifi_get_scanning (self));
 		break;
 	case PROP_LAST_SCAN:
 		g_value_set_int64 (value,
-		                   priv->last_scan > 0
-		                       ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan, NM_UTILS_NS_PER_MSEC)
-		                       : (gint64) -1);
+		                     priv->last_scan_msec > 0
+		                   ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan_msec, NM_UTILS_NSEC_PER_MSEC)
+		                   : (gint64) -1);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -3291,6 +3360,8 @@ nm_device_wifi_init (NMDeviceWifi *self)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	c_list_init (&priv->aps_lst_head);
+	c_list_init (&priv->scanning_prohibited_lst_head);
+	priv->aps_idx_by_supplicant_path = g_hash_table_new (nm_direct_hash, NULL);
 
 	priv->hidden_probe_scan_warn = TRUE;
 	priv->mode = NM_802_11_MODE_INFRA;
@@ -3331,7 +3402,9 @@ dispose (GObject *object)
 	NMDeviceWifi *self = NM_DEVICE_WIFI (object);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->periodic_source_id);
+	nm_assert (c_list_is_empty (&priv->scanning_prohibited_lst_head));
+
+	nm_clear_g_source (&priv->periodic_update_id);
 
 	wifi_secrets_cancel (self);
 
@@ -3359,6 +3432,9 @@ finalize (GObject *object)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	nm_assert (c_list_is_empty (&priv->aps_lst_head));
+	nm_assert (g_hash_table_size (priv->aps_idx_by_supplicant_path) == 0);
+
+	g_hash_table_unref (priv->aps_idx_by_supplicant_path);
 
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->finalize (object);
 }
@@ -3406,8 +3482,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	device_class->state_changed = device_state_changed;
 
-	klass->scanning_prohibited = scanning_prohibited;
-
 	obj_properties[PROP_MODE] =
 	    g_param_spec_uint (NM_DEVICE_WIFI_MODE, "", "",
 	                       NM_802_11_MODE_UNKNOWN,
@@ -3454,14 +3528,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[SCANNING_PROHIBITED] =
-	    g_signal_new (NM_DEVICE_WIFI_SCANNING_PROHIBITED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
-	                  NULL, NULL, NULL,
-	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
-
 	signals[P2P_DEVICE_CREATED] =
 	    g_signal_new (NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
 	                  G_OBJECT_CLASS_TYPE (object_class),