summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-05-08 21:40:53 +0200
committerMichael Biebl <biebl@debian.org>2020-05-08 21:40:53 +0200
commitd460892bbfece74fb6d3cd846bf6ef548290be41 (patch)
tree0474e5be0b5e5fac0d2f3a1e554382e8d1aa6397 /src/devices
parent1e5977b62f896e844b548c3007ace9e1dfa7f9ed (diff)
New upstream version 1.24.0 upstream/1.24.0
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c6
-rw-r--r--src/devices/bluetooth/nm-device-bt.c1
-rw-r--r--src/devices/nm-acd-manager.c18
-rw-r--r--src/devices/nm-device-bridge.c2
-rw-r--r--src/devices/nm-device-private.h13
-rw-r--r--src/devices/nm-device-wireguard.c14
-rw-r--r--src/devices/nm-device.c158
-rw-r--r--src/devices/nm-device.h7
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c2
-rw-r--r--src/devices/wifi/nm-device-iwd.c77
-rw-r--r--src/devices/wifi/nm-device-wifi.c929
-rw-r--r--src/devices/wwan/nm-modem-broadband.c6
-rw-r--r--src/devices/wwan/nm-modem-ofono.c6
13 files changed, 803 insertions, 436 deletions
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 6ff96c32..0bbac4c4 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -1251,9 +1251,9 @@ _network_server_unregister_bridge (NMBluezManager *self,
 
 	if (r_req_data) {
 		nm_clear_g_cancellable (&r_req_data->int_cancellable);
-		nm_utils_invoke_on_idle (_network_server_unregister_bridge_complete_on_idle_cb,
-		                         nm_utils_user_data_pack (r_req_data, g_strdup (reason)),
-		                         r_req_data->ext_cancellable);
+		nm_utils_invoke_on_idle (r_req_data->ext_cancellable,
+		                         _network_server_unregister_bridge_complete_on_idle_cb,
+		                         nm_utils_user_data_pack (r_req_data, g_strdup (reason)));
 	}
 
 	_nm_device_bridge_notify_unregister_bt_nap (device, reason);
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index ab18897f..a02ce34f 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -1282,6 +1282,7 @@ dispose (GObject *object)
 
 	if (priv->modem_manager) {
 		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), self);
+		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_modem_added_cb), self);
 		nm_modem_manager_name_owner_unref (priv->modem_manager);
 		g_clear_object (&priv->modem_manager);
 	}
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index 735dd25b..1c6c42a0 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -81,7 +81,20 @@ _acd_event_to_string (unsigned int event)
 	return NULL;
 }
 
-#define acd_event_to_string_a(event) NM_UTILS_LOOKUP_STR_A (_acd_event_to_string, event)
+#define ACD_EVENT_TO_STRING_BUF_SIZE 50
+
+static const char *
+_acd_event_to_string_buf (unsigned event, char buffer[static ACD_EVENT_TO_STRING_BUF_SIZE])
+{
+	const char *s;
+
+	s = _acd_event_to_string (event);
+	if (s)
+		return s;
+
+	g_snprintf (buffer, ACD_EVENT_TO_STRING_BUF_SIZE, "(%u)", event);
+	return buffer;
+}
 
 static const char *
 acd_error_to_string (int error)
@@ -172,6 +185,7 @@ acd_event (int fd,
 
 	while (   !n_acd_pop_event (self->acd, &event)
 	       && event) {
+		char to_string_buffer[ACD_EVENT_TO_STRING_BUF_SIZE];
 		gs_free char *hwaddr_str = NULL;
 		gboolean check_probing_done = FALSE;
 
@@ -215,7 +229,7 @@ acd_event (int fd,
 			       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex));
 			break;
 		default:
-			_LOGD ("unhandled event '%s'", acd_event_to_string_a (event->event));
+			_LOGD ("unhandled event '%s'", _acd_event_to_string_buf (event->event, to_string_buffer));
 			break;
 		}
 
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index c6c54344..c93c6a54 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -328,6 +328,7 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool
 	nm_auto_unset_gvalue GValue val = G_VALUE_INIT;
 	GParamSpec *pspec;
 	const char *value;
+	char value_buf[100];
 
 	if (slave)
 		nm_assert (NM_IS_SETTING_BRIDGE_PORT (setting));
@@ -350,7 +351,6 @@ commit_option (NMDevice *device, NMSetting *setting, const Option *option, gbool
 		value = g_value_get_boolean (&val) ? "1" : "0";
 		break;
 	case G_TYPE_UINT: {
-			char value_buf[100];
 			guint uval;
 
 			uval = g_value_get_uint (&val);
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 8d539026..4e260da2 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -46,6 +46,8 @@ void nm_device_arp_announce (NMDevice *self);
 
 NMSettings *nm_device_get_settings (NMDevice *self);
 
+NMManager *nm_device_get_manager (NMDevice *self);
+
 gboolean nm_device_set_ip_ifindex (NMDevice *self, int ifindex);
 
 gboolean nm_device_set_ip_iface (NMDevice *self, const char *iface);
@@ -203,4 +205,15 @@ gboolean nm_device_match_parent_hwaddr (NMDevice *device,
                                         NMConnection *connection,
                                         gboolean fail_if_no_hwaddr);
 
+/*****************************************************************************/
+
+void nm_device_auth_request (NMDevice *self,
+                             GDBusMethodInvocation *context,
+                             NMConnection *connection,
+                             const char *permission,
+                             gboolean allow_interaction,
+                             GCancellable *cancellable,
+                             NMManagerDeviceAuthRequestFunc callback,
+                             gpointer user_data);
+
 #endif /* NM_DEVICE_PRIVATE_H */
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index bb7a5950..9f01a763 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -1631,8 +1631,20 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
 			if (prefix < 0)
 				prefix = (addr_family == AF_INET) ? 32 : 128;
 
-			if (!ip_config)
+			if (prefix == 0) {
+				NMSettingIPConfig *s_ip;
+
+				s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+				if (nm_setting_ip_config_get_never_default (s_ip))
+					continue;
+			}
+
+			if (!ip_config) {
 				ip_config = nm_device_ip_config_new (NM_DEVICE (self), addr_family);
+				nm_ip_config_set_config_flags (ip_config,
+				                               NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES,
+				                               0);
+			}
 
 			nm_utils_ipx_address_clear_host_address (addr_family, &addrbin, NULL, prefix);
 
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index c3483e4d..8a22cba8 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -191,7 +191,6 @@ typedef struct {
 enum {
 	STATE_CHANGED,
 	AUTOCONNECT_ALLOWED,
-	AUTH_REQUEST,
 	IP4_CONFIG_CHANGED,
 	IP6_CONFIG_CHANGED,
 	IP6_PREFIX_DELEGATED,
@@ -577,6 +576,7 @@ typedef struct _NMDevicePrivate {
 	NMMetered       metered;
 
 	NMSettings *settings;
+	NMManager *manager;
 
 	NMNetns *netns;
 
@@ -912,6 +912,12 @@ nm_device_get_settings (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->settings;
 }
 
+NMManager *
+nm_device_get_manager (NMDevice *self)
+{
+	return NM_DEVICE_GET_PRIVATE (self)->manager;
+}
+
 NMNetns *
 nm_device_get_netns (NMDevice *self)
 {
@@ -6257,6 +6263,27 @@ dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer us
 	}
 }
 
+void
+nm_device_auth_request (NMDevice *self,
+                        GDBusMethodInvocation *context,
+                        NMConnection *connection,
+                        const char *permission,
+                        gboolean allow_interaction,
+                        GCancellable *cancellable,
+                        NMManagerDeviceAuthRequestFunc callback,
+                        gpointer user_data)
+{
+	nm_manager_device_auth_request (nm_device_get_manager (self),
+	                                self,
+	                                context,
+	                                connection,
+	                                permission,
+	                                allow_interaction,
+	                                cancellable,
+	                                callback,
+	                                user_data);
+}
+
 /*****************************************************************************/
 
 static void
@@ -10557,6 +10584,8 @@ act_stage3_ip_config_start (NMDevice *self,
 	} else {
 		NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 		const char *ip6_privacy_str = "0";
+		NMPlatform *platform;
+		int ifindex;
 
 		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
 			nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
@@ -10564,18 +10593,35 @@ act_stage3_ip_config_start (NMDevice *self,
 		}
 
 		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
-			if (   !priv->master
-			    && !nm_device_sys_iface_state_is_external (self)) {
-				gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
-
-				/* When activating an IPv6 'ignore' connection we need to revert back
-				 * to kernel IPv6LL, but the kernel won't actually assign an address
-				 * to the interface until disable_ipv6 is bounced.
-				 */
-				set_nm_ipv6ll (self, FALSE);
-				if (ipv6ll_handle_old)
-					nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
-				restore_ip6_properties (self);
+			if (!nm_device_sys_iface_state_is_external (self)) {
+				if (priv->master) {
+					/* If a device only has an IPv6 link-local address,
+					 * we don't generate an assumed connection. Therefore,
+					 * when a new slave connection (without IP configuration)
+					 * is activated on the device, the link-local address
+					 * remains configured. The IP configuration of an activated
+					 * slave should not depend on the previous state. Flush
+					 * addresses and routes on activation.
+					 */
+					ifindex = nm_device_get_ip_ifindex (self);
+					platform = nm_device_get_platform (self);
+
+					if (ifindex > 0) {
+						nm_platform_ip_route_flush (platform, AF_INET6, ifindex);
+						nm_platform_ip_address_flush (platform, AF_INET6, ifindex);
+					}
+				} else {
+					gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
+
+					/* When activating an IPv6 'ignore' connection we need to revert back
+					 * to kernel IPv6LL, but the kernel won't actually assign an address
+					 * to the interface until disable_ipv6 is bounced.
+					 */
+					set_nm_ipv6ll (self, FALSE);
+					if (ipv6ll_handle_old)
+						nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
+					restore_ip6_properties (self);
+				}
 			}
 			return NM_ACT_STAGE_RETURN_IP_DONE;
 		}
@@ -12234,13 +12280,14 @@ impl_device_reapply (NMDBusObject *obj,
 	} else
 		reapply_data = NULL;
 
-	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               invocation,
-	               nm_device_get_applied_connection (self),
-	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
-	               TRUE,
-	               reapply_cb,
-	               reapply_data);
+	nm_device_auth_request (self,
+	                        invocation,
+	                        nm_device_get_applied_connection (self),
+	                        NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                        TRUE,
+	                        NULL,
+	                        reapply_cb,
+	                        reapply_data);
 }
 
 /*****************************************************************************/
@@ -12277,13 +12324,14 @@ get_applied_connection_cb (NMDevice *self,
 
 	if (applied_connection != user_data) {
 		/* The applied connection changed due to a race. Reauthenticate. */
-		g_signal_emit (self, signals[AUTH_REQUEST], 0,
-		               context,
-		               applied_connection,
-		               NM_AUTH_PERMISSION_NETWORK_CONTROL,
-		               TRUE,
-		               get_applied_connection_cb,
-		               applied_connection /* no need take a ref. We will not dereference this pointer. */);
+		nm_device_auth_request (self,
+		                        context,
+		                        applied_connection,
+		                        NM_AUTH_PERMISSION_NETWORK_CONTROL,
+		                        TRUE,
+		                        NULL,
+		                        get_applied_connection_cb,
+		                        applied_connection /* no need take a ref. We will not dereference this pointer. */);
 		return;
 	}
 
@@ -12330,13 +12378,14 @@ impl_device_get_applied_connection (NMDBusObject *obj,
 		return;
 	}
 
-	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               invocation,
-	               applied_connection,
-	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
-	               TRUE,
-	               get_applied_connection_cb,
-	               applied_connection /* no need take a ref. We will not dereference this pointer. */);
+	nm_device_auth_request (self,
+	                        invocation,
+	                        applied_connection,
+	                        NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                        TRUE,
+	                        NULL,
+	                        get_applied_connection_cb,
+	                        applied_connection /* no need take a ref. We will not dereference this pointer. */);
 }
 
 /*****************************************************************************/
@@ -12504,13 +12553,14 @@ impl_device_disconnect (NMDBusObject *obj,
 	connection = nm_device_get_applied_connection (self);
 	nm_assert (connection);
 
-	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               invocation,
-	               connection,
-	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
-	               TRUE,
-	               disconnect_cb,
-	               NULL);
+	nm_device_auth_request (self,
+	                        invocation,
+	                        connection,
+	                        NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                        TRUE,
+	                        NULL,
+	                        disconnect_cb,
+	                        NULL);
 }
 
 static void
@@ -12556,13 +12606,14 @@ impl_device_delete (NMDBusObject *obj,
 		return;
 	}
 
-	g_signal_emit (self, signals[AUTH_REQUEST], 0,
-	               invocation,
-	               NULL,
-	               NM_AUTH_PERMISSION_NETWORK_CONTROL,
-	               TRUE,
-	               delete_cb,
-	               NULL);
+	nm_device_auth_request (self,
+	                        invocation,
+	                        NULL,
+	                        NM_AUTH_PERMISSION_NETWORK_CONTROL,
+	                        TRUE,
+	                        NULL,
+	                        delete_cb,
+	                        NULL);
 }
 
 static void
@@ -17390,8 +17441,8 @@ constructed (GObject *object)
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self);
 
+	priv->manager = g_object_ref (NM_MANAGER_GET);
 	priv->settings = g_object_ref (NM_SETTINGS_GET);
-	g_assert (priv->settings);
 
 	g_signal_connect (priv->settings,
 	                  NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
@@ -17548,6 +17599,7 @@ finalize (GObject *object)
 	/* for testing, NMDeviceTest does not invoke NMDevice::constructed,
 	 * and thus @settings might be unset. */
 	nm_g_object_unref (priv->settings);
+	nm_g_object_unref (priv->manager);
 
 	nm_g_object_unref (priv->concheck_mgr);
 
@@ -17944,14 +17996,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                  autoconnect_allowed_accumulator, NULL, NULL,
 	                  G_TYPE_BOOLEAN, 0);
 
-	signals[AUTH_REQUEST] =
-	    g_signal_new (NM_DEVICE_AUTH_REQUEST,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  /* context, connection, permission, allow_interaction, callback, user_data */
-	                  G_TYPE_NONE, 6, G_TYPE_DBUS_METHOD_INVOCATION, NM_TYPE_CONNECTION, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER, G_TYPE_POINTER);
-
 	signals[IP4_CONFIG_CHANGED] =
 	    g_signal_new (NM_DEVICE_IP4_CONFIG_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index c18301b2..8d51c707 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -115,7 +115,6 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */
 
 /* Internal signals */
-#define NM_DEVICE_AUTH_REQUEST          "auth-request"
 #define NM_DEVICE_IP4_CONFIG_CHANGED    "ip4-config-changed"
 #define NM_DEVICE_IP6_CONFIG_CHANGED    "ip6-config-changed"
 #define NM_DEVICE_IP6_PREFIX_DELEGATED  "ip6-prefix-delegated"
@@ -455,12 +454,6 @@ typedef struct _NMDeviceClass {
 
 } NMDeviceClass;
 
-typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
-                                         GDBusMethodInvocation *context,
-                                         NMAuthSubject *subject,
-                                         GError *error,
-                                         gpointer user_data);
-
 GType nm_device_get_type (void);
 
 struct _NMDedupMultiIndex *nm_device_get_multi_index (NMDevice *self);
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 951b5788..10f9fa94 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -310,7 +310,7 @@ deactivate_async (NMDevice *device,
 	                                        nm_device_get_iface (device))) {
 		_LOGT (LOGD_CORE, "deactivate: link not present, proceeding");
 		nm_device_update_from_platform_link (NM_DEVICE (self), NULL);
-		nm_utils_invoke_on_idle (deactivate_cb_on_idle, data, cancellable);
+		nm_utils_invoke_on_idle (cancellable, deactivate_cb_on_idle, data);
 		return;
 	}
 
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 3c1d5b22..4b57fa38 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -507,7 +507,7 @@ deactivate_async (NMDevice *device,
 	user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data);
 
 	if (!priv->dbus_obj) {
-		nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable);
+		nm_utils_invoke_on_idle (cancellable, disconnect_cb_on_idle, user_data);
 		return;
 	}
 
@@ -615,13 +615,6 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		return FALSE;
 	}
 
-	/* Hidden SSIDs not supported in any mode (client or AP) */
-	if (nm_setting_wireless_get_hidden (s_wireless)) {
-		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-		                            "hidden networks not supported by the IWD backend");
-		return FALSE;
-	}
-
 	security = nm_wifi_connection_get_iwd_security (connection, &mapped);
 	if (!mapped) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
@@ -630,6 +623,15 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 	}
 
 	mode = nm_setting_wireless_get_mode (s_wireless);
+
+	/* Hidden SSIDs only supported in client mode */
+	if (   nm_setting_wireless_get_hidden (s_wireless)
+	    && !NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "non-infrastructure hidden networks not supported by the IWD backend");
+		return FALSE;
+	}
+
 	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
 		/* 8021x networks can only be used if they've been provisioned on the IWD side and
 		 * thus are Known Networks.
@@ -757,6 +759,7 @@ complete_connection (NMDevice *device,
 	NMWifiAP *ap;
 	GBytes *ssid;
 	GBytes *setting_ssid = NULL;
+	gboolean hidden = FALSE;
 	const char *mode;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
@@ -789,16 +792,14 @@ complete_connection (NMDevice *device,
 		/* Find a compatible AP in the scan list */
 		ap = nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
 		if (!ap) {
-			if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
-				g_set_error_literal (error,
-				                     NM_DEVICE_ERROR,
-				                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-				                     "No compatible AP in the scan list and hidden SSIDs not supported.");
-				return FALSE;
-			}
-
+			/* If we still don't have an AP, then the WiFI settings needs to be
+			 * fully specified by the client.  Might not be able to find an AP
+			 * if the network isn't broadcasting the SSID for example.
+			 */
 			if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
 				return FALSE;
+
+			hidden = TRUE;
 		}
 	} else {
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
@@ -849,6 +850,9 @@ complete_connection (NMDevice *device,
 	                           nm_setting_wireless_get_mac_address (s_wifi) ? NULL : nm_device_get_iface (device),
 	                           TRUE);
 
+	if (hidden)
+		g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
+
 	return TRUE;
 }
 
@@ -1061,14 +1065,14 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 		return;
 	}
 
-	g_signal_emit_by_name (device,
-	                       NM_DEVICE_AUTH_REQUEST,
-	                       invocation,
-	                       NULL,
-	                       NM_AUTH_PERMISSION_WIFI_SCAN,
-	                       TRUE,
-	                       dbus_request_scan_cb,
-	                       options ? g_variant_ref (options) : NULL);
+	nm_device_auth_request (device,
+	                        invocation,
+	                        NULL,
+	                        NM_AUTH_PERMISSION_WIFI_SCAN,
+	                        TRUE,
+	                        NULL,
+	                        dbus_request_scan_cb,
+	                        nm_g_variant_ref (options));
 }
 
 static gboolean
@@ -1786,11 +1790,34 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 			goto out_fail;
 		}
 
+		if (   !is_connection_known_network (connection)
+		    && nm_setting_wireless_get_hidden (s_wireless)) {
+			gs_free char *ssid_str = NULL;
+
+			/* Use Station.ConnectHiddenNetwork method instead of Network proxy. */
+			ssid_str = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wireless));
+			g_dbus_proxy_call (priv->dbus_station_proxy,
+			                   "ConnectHiddenNetwork",
+			                   g_variant_new ("(s)", ssid_str),
+			                   G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
+			                   priv->cancellable,
+			                   network_connect_cb,
+			                   self);
+			return NM_ACT_STAGE_RETURN_POSTPONE;
+		}
+
+		if (!nm_wifi_ap_get_supplicant_path (ap)) {
+			_LOGW (LOGD_DEVICE | LOGD_WIFI,
+			       "Activation: (wifi) network is provisioned but dbus supplicant path for AP unknown");
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+			goto out_fail;
+		}
+
 		network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
 		                                                   nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)),
 		                                                   NM_IWD_NETWORK_INTERFACE);
 		if (!network_proxy) {
-			_LOGE (LOGD_DEVICE | LOGD_WIFI,
+			_LOGW (LOGD_DEVICE | LOGD_WIFI,
 			       "Activation: (wifi) could not get Network interface proxy for %s",
 			       nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)));
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 266a8425..f5b62989 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -48,7 +48,14 @@ _LOG_DECLARE_SELF(NMDeviceWifi);
 #define SCAN_INTERVAL_SEC_STEP 20
 #define SCAN_INTERVAL_SEC_MAX 120
 
-#define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5
+#define SCAN_EXTRA_DELAY_MSEC 500
+
+#define SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC (5*60)
+
+#define SCAN_REQUEST_SSIDS_MAX_NUM      32u
+#define SCAN_REQUEST_SSIDS_MAX_AGE_MSEC (3 * 60 * NM_UTILS_MSEC_PER_SEC)
+
+#define _LOGT_scan(...) _LOGT (LOGD_WIFI_SCAN, "wifi-scan: " __VA_ARGS__)
 
 /*****************************************************************************/
 
@@ -76,44 +83,58 @@ typedef struct {
 
 	CList             scanning_prohibited_lst_head;
 
+	GCancellable     *scan_request_cancellable;
+
+	GSource          *scan_request_delay_source;
+
 	NMWifiAP *        current_ap;
-	guint32           rate;
-	bool              enabled:1; /* rfkilled or not */
-	bool              requested_scan:1;
-	bool              ssid_found:1;
-	bool              is_scanning:1;
-	bool              hidden_probe_scan_warn:1;
 
-	gint64            last_scan_msec;
+	GHashTable       *scan_request_ssids_hash;
+	CList             scan_request_ssids_lst_head;
 
-	gint32            scheduled_scan_time; /* seconds */
-	guint8            scan_interval_sec;
-	guint             pending_scan_id;
-	guint             ap_dump_id;
+	NMActRequestGetSecretsCallId *wifi_secrets_id;
 
-	NMSupplicantManager   *sup_mgr;
+	NMSupplicantManager *sup_mgr;
 	NMSupplMgrCreateIfaceHandle *sup_create_handle;
 	NMSupplicantInterface *sup_iface;
-	guint                  sup_timeout_id; /* supplicant association timeout */
 
-	NM80211Mode       mode;
+	gint64            scan_last_complete_msec;
+	gint64            scan_periodic_next_msec;
 
-	NMActRequestGetSecretsCallId *wifi_secrets_id;
+	gint64            scan_last_request_started_at_msec;
+
+	guint             scan_kickoff_timeout_id;
+
+	guint             ap_dump_id;
 
 	guint             periodic_update_id;
+
 	guint             link_timeout_id;
-	guint32           failed_iface_count;
 	guint             reacquire_iface_id;
+	guint             wps_timeout_id;
+	guint             sup_timeout_id; /* supplicant association timeout */
 
 	NMDeviceWifiCapabilities capabilities;
+	NMSettingWirelessWakeOnWLan wowlan_restore;
 
-	gint32 hw_addr_scan_expire;
+	NMDeviceWifiP2P  *p2p_device;
+	NM80211Mode       mode;
 
-	guint             wps_timeout_id;
+	guint32           failed_iface_count;
+	gint32            hw_addr_scan_expire;
 
-	NMSettingWirelessWakeOnWLan wowlan_restore;
+	guint32           rate;
+
+	guint8            scan_periodic_interval_sec;
+
+	bool              enabled:1; /* rfkilled or not */
+	bool              scan_is_scanning:1;
+	bool              scan_periodic_allowed:1;
+	bool              scan_explicit_allowed:1;
+	bool              scan_explicit_requested:1;
+	bool              ssid_found:1;
+	bool              hidden_probe_scan_warn:1;
 
-	NMDeviceWifiP2P  *p2p_device;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -135,12 +156,8 @@ G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
-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);
 
@@ -173,15 +190,8 @@ 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,
-                                   const GPtrArray *ssids);
-
 static void ap_add_remove (NMDeviceWifi *self,
                            gboolean is_adding,
                            NMWifiAP *ap,
@@ -191,6 +201,158 @@ static void _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset);
 
 static void recheck_p2p_availability (NMDeviceWifi *self);
 
+static void _scan_kickoff (NMDeviceWifi *self);
+
+static gboolean _scan_notify_allowed (NMDeviceWifi *self, NMTernary do_kickoff);
+
+/*****************************************************************************/
+
+typedef struct {
+	GBytes *ssid;
+	CList lst;
+	gint64 timestamp_msec;
+} ScanRequestSsidData;
+
+static void
+_scan_request_ssids_remove (ScanRequestSsidData *srs_data)
+{
+	c_list_unlink_stale (&srs_data->lst);
+	g_bytes_unref (srs_data->ssid);
+	nm_g_slice_free (srs_data);
+}
+
+static void
+_scan_request_ssids_remove_with_hash (NMDeviceWifiPrivate *priv,
+                                      ScanRequestSsidData *srs_data)
+{
+	nm_assert (srs_data);
+	nm_assert (nm_g_hash_table_lookup (priv->scan_request_ssids_hash, srs_data) == srs_data);
+	if (!g_hash_table_remove (priv->scan_request_ssids_hash, srs_data))
+		nm_assert_not_reached ();
+	_scan_request_ssids_remove (srs_data);
+}
+
+static void
+_scan_request_ssids_remove_all (NMDeviceWifiPrivate *priv,
+                                gint64 cutoff_with_now_msec,
+                                guint cutoff_at_len)
+{
+	ScanRequestSsidData *srs_data;
+
+	nm_assert ((!priv->scan_request_ssids_hash) == c_list_is_empty (&priv->scan_request_ssids_lst_head));
+	if (!priv->scan_request_ssids_hash)
+		return;
+
+	if (cutoff_at_len == 0) {
+		nm_clear_pointer (&priv->scan_request_ssids_hash, g_hash_table_destroy);
+		while ((srs_data = c_list_first_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst)))
+			_scan_request_ssids_remove (srs_data);
+		return;
+	}
+
+	if (cutoff_with_now_msec != 0) {
+		gint64 cutoff_time_msec;
+
+		/* remove all entries that are older than a max-age. */
+		nm_assert (cutoff_with_now_msec > 0);
+		cutoff_time_msec = cutoff_with_now_msec - SCAN_REQUEST_SSIDS_MAX_AGE_MSEC;
+		while ((srs_data = c_list_last_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) {
+			if (srs_data->timestamp_msec > cutoff_time_msec)
+				break;
+			_scan_request_ssids_remove_with_hash (priv, srs_data);
+		}
+	}
+
+	if (cutoff_at_len != G_MAXUINT) {
+		guint i;
+
+		/* trim the list to cutoff_at_len elements. */
+		i = nm_g_hash_table_size (priv->scan_request_ssids_hash);
+		for (; i > cutoff_at_len; i--) {
+			ScanRequestSsidData *d;
+
+			d = c_list_last_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst);
+			_scan_request_ssids_remove_with_hash (priv, d);
+		}
+	}
+
+	nm_assert (nm_g_hash_table_size (priv->scan_request_ssids_hash) <= SCAN_REQUEST_SSIDS_MAX_NUM);
+	nm_assert (nm_g_hash_table_size (priv->scan_request_ssids_hash) == c_list_length (&priv->scan_request_ssids_lst_head));
+	if (c_list_is_empty (&priv->scan_request_ssids_lst_head))
+		nm_clear_pointer (&priv->scan_request_ssids_hash, g_hash_table_destroy);
+}
+
+static GPtrArray *
+_scan_request_ssids_fetch (NMDeviceWifiPrivate *priv, gint64 now_msec)
+{
+	ScanRequestSsidData *srs_data;
+	GPtrArray *ssids;
+	guint len;
+
+	_scan_request_ssids_remove_all (priv, now_msec, G_MAXUINT);
+
+	len = nm_g_hash_table_size (priv->scan_request_ssids_hash);
+	if (len == 0)
+		return NULL;
+
+	ssids = g_ptr_array_new_full (len, (GDestroyNotify) g_bytes_unref);
+	nm_clear_pointer (&priv->scan_request_ssids_hash, g_hash_table_destroy);
+	while ((srs_data = c_list_first_entry (&priv->scan_request_ssids_lst_head, ScanRequestSsidData, lst))) {
+		g_ptr_array_add (ssids, g_steal_pointer (&srs_data->ssid));
+		_scan_request_ssids_remove (srs_data);
+	}
+	return ssids;
+}
+
+static void
+_scan_request_ssids_track (NMDeviceWifiPrivate *priv,
+                           const GPtrArray *ssids)
+{
+	CList old_lst_head;
+	gint64 now_msec;
+	guint i;
+
+	if (   !ssids
+	    || ssids->len == 0)
+		return;
+
+	now_msec = nm_utils_get_monotonic_timestamp_msec ();
+
+	if (!priv->scan_request_ssids_hash)
+		priv->scan_request_ssids_hash = g_hash_table_new (nm_pgbytes_hash, nm_pgbytes_equal);
+
+	/* Do a little dance. New elements shall keep their order as in @ssids, but all
+	 * new elements should be sorted in the list preexisting elements of the list.
+	 * First move the old elements away, and splice them back afterwards. */
+	c_list_init (&old_lst_head);
+	c_list_splice (&old_lst_head, &priv->scan_request_ssids_lst_head);
+
+	for (i = 0; i < ssids->len; i++) {
+		GBytes *ssid = ssids->pdata[i];
+		ScanRequestSsidData *d;
+
+		G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (ScanRequestSsidData, ssid) == 0);
+		d = g_hash_table_lookup (priv->scan_request_ssids_hash, &ssid);
+		if (!d) {
+			d = g_slice_new (ScanRequestSsidData);
+			*d = (ScanRequestSsidData) {
+				.lst            = C_LIST_INIT (d->lst),
+				.timestamp_msec = now_msec,
+				.ssid           = g_bytes_ref (ssid),
+			};
+			g_hash_table_add (priv->scan_request_ssids_hash, d);
+		} else
+			d->timestamp_msec = now_msec;
+		c_list_link_tail (&priv->scan_request_ssids_lst_head, &d->lst);
+	}
+
+	c_list_splice (&priv->scan_request_ssids_lst_head, &old_lst_head);
+
+	/* Trim the excess. After our splice with old_lst_head, the list contains the new
+	 * elements (from @ssids) at the front (in there original order), followed by older elements. */
+	_scan_request_ssids_remove_all (priv, now_msec, SCAN_REQUEST_SSIDS_MAX_NUM);
+}
+
 /*****************************************************************************/
 
 void
@@ -218,16 +380,15 @@ nm_device_wifi_scanning_prohibited_track (NMDeviceWifi *self,
 	if (!temporarily_prohibited) {
 		if (!elem)
 			return;
-
 		nm_c_list_elem_free (elem);
-		return;
+	} else {
+		if (elem)
+			return;
+		c_list_link_tail (&priv->scanning_prohibited_lst_head,
+		                  &nm_c_list_elem_new_stale (tag)->lst);
 	}
 
-	if (elem)
-		return;
-
-	c_list_link_tail (&priv->scanning_prohibited_lst_head,
-	                  &nm_c_list_elem_new_stale (tag)->lst);
+	_scan_notify_allowed (self, NM_TERNARY_DEFAULT);
 }
 
 /*****************************************************************************/
@@ -252,28 +413,36 @@ 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;
+	return NM_DEVICE_WIFI_GET_PRIVATE (self)->scan_is_scanning;
 }
 
-static void
-_notify_scanning (NMDeviceWifi *self)
+static gboolean
+_scan_is_scanning_eval (NMDeviceWifiPrivate *priv)
+{
+	return    priv->scan_request_cancellable
+	       || priv->scan_request_delay_source
+	       || (    priv->sup_iface
+	           && nm_supplicant_interface_get_scanning (priv->sup_iface));
+}
+
+static gboolean
+_scan_notify_is_scanning (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	gboolean scanning;
 	gboolean last_scan_changed = FALSE;
+	NMDeviceState state;
+	gboolean scanning;
 
-	scanning =    priv->sup_iface
-	           && nm_supplicant_interface_get_scanning (priv->sup_iface);
-
-	if (scanning == priv->is_scanning)
-		return;
+	scanning = _scan_is_scanning_eval (priv);
+	if (scanning == priv->scan_is_scanning)
+		return FALSE;
 
-	priv->is_scanning = scanning;
+	priv->scan_is_scanning = scanning;
 
 	if (   !scanning
-	    || priv->last_scan_msec == 0) {
+	    || priv->scan_last_complete_msec == 0) {
 		last_scan_changed = TRUE;
-		priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec ();
+		priv->scan_last_complete_msec = nm_utils_get_monotonic_timestamp_msec ();
 	}
 
 	_LOGD (LOGD_WIFI,
@@ -281,7 +450,16 @@ _notify_scanning (NMDeviceWifi *self)
 	       scanning ? "scanning" : "idle",
 	       last_scan_changed ? " (notify last-scan)" : "");
 
-	schedule_scan (self, TRUE);
+	state = nm_device_get_state (NM_DEVICE (self));
+
+	if (scanning) {
+		/* while the device is activating/activated, we don't need the pending
+		 * action. The pending action exists to delay startup complete, while
+		 * activating that is already achieved via other means. */
+		if (   state <= NM_DEVICE_STATE_DISCONNECTED
+		    || state > NM_DEVICE_STATE_ACTIVATED)
+			nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, FALSE);
+	}
 
 	nm_gobject_notify_together (self,
 	                            PROP_SCANNING,
@@ -289,13 +467,73 @@ _notify_scanning (NMDeviceWifi *self)
 	                            ? 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);
-		}
+	_scan_kickoff (self);
+
+	if (!_scan_is_scanning_eval (priv)) {
+		if (   state <= NM_DEVICE_STATE_DISCONNECTED
+		    || state > NM_DEVICE_STATE_ACTIVATED)
+			nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, FALSE);
 	}
+
+	return TRUE;
+}
+
+static gboolean
+_scan_notify_allowed (NMDeviceWifi *self, NMTernary do_kickoff)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	gboolean explicit_allowed;
+	gboolean periodic_allowed;
+	NMDeviceState state;
+	gboolean changed = FALSE;
+
+	state = nm_device_get_state (NM_DEVICE (self));
+
+	explicit_allowed = FALSE;
+	periodic_allowed = FALSE;
+
+	if (!c_list_is_empty (&priv->scanning_prohibited_lst_head)) {
+		/* something prohibits scanning. */
+	} else if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC,
+	                                  NM_802_11_MODE_AP)) {
+		/* Don't scan when a an AP or Ad-Hoc connection is active as it will
+		 * disrupt connected clients or peers. */
+	} else if (NM_IN_SET (state, NM_DEVICE_STATE_DISCONNECTED,
+	                             NM_DEVICE_STATE_FAILED)) {
+		/* Can always scan when disconnected */
+		explicit_allowed = TRUE;
+		periodic_allowed = TRUE;
+	} else if (NM_IN_SET (state, NM_DEVICE_STATE_ACTIVATED)) {
+		/* Prohibit periodic scans when connected; we ask the supplicant to
+		 * background scan for us, unless the connection is locked to a specific
+		 * BSSID (in which case scanning is effectively disabled). */
+		periodic_allowed = FALSE;
+
+		/* Prohibit scans if the supplicant is busy */
+		explicit_allowed = !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);
+	}
+
+	if (   explicit_allowed != priv->scan_explicit_allowed
+	    || periodic_allowed != priv->scan_periodic_allowed) {
+		priv->scan_periodic_allowed = periodic_allowed;
+		priv->scan_explicit_allowed = explicit_allowed;
+		_LOGT_scan ("scan-periodic-allowed=%d, scan-explicit-allowed=%d",
+		            periodic_allowed,
+		            explicit_allowed);
+		changed = TRUE;
+	}
+
+	if (   do_kickoff == NM_TERNARY_TRUE
+	    || (   do_kickoff == NM_TERNARY_DEFAULT
+	        && changed))
+		_scan_kickoff (self);
+
+	return changed;
 }
 
 static void
@@ -303,7 +541,7 @@ supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
                                      GParamSpec *pspec,
                                      NMDeviceWifi *self)
 {
-	_notify_scanning (self);
+	_scan_notify_is_scanning (self);
 }
 
 static gboolean
@@ -370,7 +608,7 @@ supplicant_interface_acquire_cb (NMSupplicantManager *supplicant_manager,
 	                  G_CALLBACK (supplicant_iface_notify_p2p_available),
 	                  self);
 
-	_notify_scanning (self);
+	_scan_notify_is_scanning (self);
 
 	if (nm_supplicant_interface_get_state (priv->sup_iface) != NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
 		/* fake an initial state change. */
@@ -399,26 +637,6 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 }
 
 static void
-_requested_scan_set (NMDeviceWifi *self, gboolean value)
-{
-	NMDeviceWifiPrivate *priv;
-
-	value = !!value;
-
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	if (priv->requested_scan == value)
-		return;
-
-	priv->requested_scan = value;
-	if (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 (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, TRUE);
-	}
-}
-
-static void
 supplicant_interface_release (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
@@ -426,14 +644,14 @@ supplicant_interface_release (NMDeviceWifi *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->scan_kickoff_timeout_id);
+	nm_clear_g_source_inst (&priv->scan_request_delay_source);
+	nm_clear_g_cancellable (&priv->scan_request_cancellable);
 
-	nm_clear_g_source (&priv->pending_scan_id);
+	_scan_request_ssids_remove_all (priv, 0, 0);
 
-	/* Reset the scan interval to be pretty frequent when disconnected */
-	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_sec);
+	priv->scan_periodic_interval_sec = 0;
+	priv->scan_periodic_next_msec = 0;
 
 	nm_clear_g_source (&priv->ap_dump_id);
 
@@ -452,7 +670,7 @@ supplicant_interface_release (NMDeviceWifi *self)
 		nm_device_wifi_p2p_set_mgmt_iface (priv->p2p_device, NULL);
 	}
 
-	_notify_scanning (self);
+	_scan_notify_is_scanning (self);
 }
 
 static void
@@ -684,7 +902,7 @@ deactivate_async (NMDevice *device,
 
 	user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data);
 	if (!priv->sup_iface) {
-		nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable);
+		nm_utils_invoke_on_idle (cancellable, disconnect_cb_on_idle, user_data);
 		return;
 	}
 
@@ -702,7 +920,6 @@ deactivate (NMDevice *device)
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	int ifindex = nm_device_get_ifindex (device);
-	NM80211Mode old_mode = priv->mode;
 
 	nm_clear_g_source (&priv->periodic_update_id);
 
@@ -732,9 +949,7 @@ deactivate (NMDevice *device)
 		_notify (self, PROP_MODE);
 	}
 
-	/* Ensure we trigger a scan after deactivating a Hotspot */
-	if (old_mode == NM_802_11_MODE_AP)
-		request_wireless_scan (self, FALSE, FALSE, NULL);
+	_scan_notify_allowed (self, NM_TERNARY_TRUE);
 }
 
 static void
@@ -1057,10 +1272,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 static gboolean
 get_autoconnect_allowed (NMDevice *device)
 {
-	NMDeviceWifiPrivate *priv;
-
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (NM_DEVICE_WIFI (device));
-	return !priv->requested_scan;
+	return !NM_DEVICE_WIFI_GET_PRIVATE (NM_DEVICE_WIFI (device))->scan_is_scanning;
 }
 
 static gboolean
@@ -1153,8 +1365,12 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 		/* expire the temporary MAC address used during scanning */
 		priv->hw_addr_scan_expire = 0;
 
-		if (do_reset)
+		if (do_reset) {
+			priv->scan_last_request_started_at_msec = G_MININT64;
+			priv->scan_periodic_next_msec = 0;
+			priv->scan_periodic_interval_sec = 0;
 			nm_device_hw_addr_reset (device, "scanning");
+		}
 		return;
 	}
 
@@ -1169,13 +1385,16 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 		 * We don't bother with to update the MAC address exactly when
 		 * it expires, instead on the next scan request, we will generate
 		 * a new one.*/
-		priv->hw_addr_scan_expire = now + (SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN * 60);
+		priv->hw_addr_scan_expire = now + SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC;
 
 		generate_mac_address_mask = nm_config_data_get_device_config (NM_CONFIG_GET_DATA,
 		                                                              "wifi.scan-generate-mac-address-mask",
 		                                                              device,
 		                                                              NULL);
 
+		priv->scan_last_request_started_at_msec = G_MININT64;
+		priv->scan_periodic_next_msec = 0;
+		priv->scan_periodic_interval_sec = 0;
 		hw_addr_scan = nm_utils_hw_addr_gen_random_eth (nm_device_get_initial_hw_address (device),
 		                                                generate_mac_address_mask);
 		nm_device_hw_addr_set (device, hw_addr_scan, "scanning", TRUE);
@@ -1238,6 +1457,7 @@ dbus_request_scan_cb (NMDevice *device,
                       gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	gs_unref_ptrarray GPtrArray *ssids = user_data;
 
 	if (error) {
@@ -1245,15 +1465,9 @@ dbus_request_scan_cb (NMDevice *device,
 		return;
 	}
 
-	if (check_scanning_prohibited (self, FALSE)) {
-		g_dbus_method_invocation_return_error_literal (context,
-		                                               NM_DEVICE_ERROR,
-		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
-		                                               "Scanning not allowed at this time");
-		return;
-	}
-
-	request_wireless_scan (self, FALSE, FALSE, ssids);
+	_scan_request_ssids_track (priv, ssids);
+	priv->scan_explicit_requested = TRUE;
+	_scan_kickoff (self);
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
@@ -1264,7 +1478,6 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
-	gint64 last_scan;
 	gs_unref_ptrarray GPtrArray *ssids = NULL;
 
 	if (options) {
@@ -1291,104 +1504,22 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 
 	if (   !priv->enabled
 	    || !priv->sup_iface
-	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
-	    || nm_device_is_activating (device)) {
+	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED) {
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
-		                                               "Scanning not allowed while unavailable or activating");
+		                                               "Scanning not allowed while unavailable");
 		return;
 	}
 
-	if (nm_supplicant_interface_get_scanning (priv->sup_iface)) {
-		g_dbus_method_invocation_return_error_literal (invocation,
-		                                               NM_DEVICE_ERROR,
-		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
-		                                               "Scanning not allowed while already scanning");
-		return;
-	}
-
-	last_scan = nm_supplicant_interface_get_last_scan (priv->sup_iface);
-	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,
-		                                               "Scanning not allowed immediately following previous scan");
-		return;
-	}
-
-	g_signal_emit_by_name (device,
-	                       NM_DEVICE_AUTH_REQUEST,
-	                       invocation,
-	                       NULL,
-	                       NM_AUTH_PERMISSION_WIFI_SCAN,
-	                       TRUE,
-	                       dbus_request_scan_cb,
-	                       g_steal_pointer (&ssids));
-}
-
-static gboolean
-check_scanning_prohibited (NMDeviceWifi *self,
-                           gboolean periodic)
-{
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
-	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.
-	 */
-	if (NM_IN_SET (priv->mode, NM_802_11_MODE_ADHOC,
-	                           NM_802_11_MODE_AP))
-		return TRUE;
-
-	switch (nm_device_get_state (NM_DEVICE (self))) {
-	case NM_DEVICE_STATE_UNKNOWN:
-	case NM_DEVICE_STATE_UNMANAGED:
-	case NM_DEVICE_STATE_UNAVAILABLE:
-	case NM_DEVICE_STATE_PREPARE:
-	case NM_DEVICE_STATE_CONFIG:
-	case NM_DEVICE_STATE_NEED_AUTH:
-	case NM_DEVICE_STATE_IP_CONFIG:
-	case NM_DEVICE_STATE_IP_CHECK:
-	case NM_DEVICE_STATE_SECONDARIES:
-	case NM_DEVICE_STATE_DEACTIVATING:
-		/* Prohibit scans when unusable or activating */
-		return TRUE;
-	case NM_DEVICE_STATE_DISCONNECTED:
-	case NM_DEVICE_STATE_FAILED:
-		/* Can always scan when disconnected */
-		return FALSE;
-	case NM_DEVICE_STATE_ACTIVATED:
-		/* Prohibit periodic scans when connected; we ask the supplicant to
-		 * background scan for us, unless the connection is locked to a specific
-		 * BSSID.
-		 */
-		if (periodic)
-			return TRUE;
-		break;
-	}
-
-	/* Prohibit scans if the supplicant is busy */
-	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;
-
-	/* Allow the scan */
-	return FALSE;
+	nm_device_auth_request (device,
+	                        invocation,
+	                        NULL,
+	                        NM_AUTH_PERMISSION_WIFI_SCAN,
+	                        TRUE,
+	                        NULL,
+	                        dbus_request_scan_cb,
+	                        g_steal_pointer (&ssids));
 }
 
 static gboolean
@@ -1410,173 +1541,299 @@ hidden_filter_func (NMSettings *settings,
 }
 
 static GPtrArray *
-build_hidden_probe_list (NMDeviceWifi *self)
+_scan_request_ssids_build_hidden (NMDeviceWifi *self,
+                                  gint64 now_msec,
+                                  gboolean *out_has_hidden_profiles)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	guint max_scan_ssids = nm_supplicant_interface_get_max_scan_ssids (priv->sup_iface);
 	gs_free NMSettingsConnection **connections = NULL;
-	guint i, len;
-	GPtrArray *ssids = NULL;
-	static GBytes *nullssid = NULL;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
+	gs_unref_hashtable GHashTable *unique_ssids = NULL;
+	guint connections_len;
+	guint n_hidden;
+	guint i;
+
+	NM_SET_OUT (out_has_hidden_profiles, FALSE);
+
+	/* collect all pending explicit SSIDs. */
+	ssids = _scan_request_ssids_fetch (priv, now_msec);
 
-	/* Need at least two: wildcard SSID and one or more hidden SSIDs */
-	if (max_scan_ssids < 2)
+	if (max_scan_ssids == 0) {
+		/* no space. @ssids will be ignored. */
 		return NULL;
+	}
+
+	if (ssids) {
+		if (ssids->len < max_scan_ssids) {
+			/* Add wildcard SSID using a static wildcard SSID used for every scan */
+			g_ptr_array_insert (ssids, 0, g_bytes_ref (nm_gbytes_get_empty ()));
+		}
+		if (ssids->len >= max_scan_ssids) {
+			/* there is no more space. Use what we have. */
+			g_ptr_array_set_size (ssids, max_scan_ssids);
+			return g_steal_pointer (&ssids);
+		}
+	}
 
 	connections = nm_settings_get_connections_clone (nm_device_get_settings ((NMDevice *) self),
-	                                                 &len,
-	                                                 hidden_filter_func, NULL,
-	                                                 NULL, NULL);
+	                                                 &connections_len,
+	                                                 hidden_filter_func,
+	                                                 NULL,
+	                                                 NULL,
+	                                                 NULL);
 	if (!connections[0])
-		return NULL;
+		return g_steal_pointer (&ssids);
 
-	g_qsort_with_data (connections, len, sizeof (NMSettingsConnection *), nm_settings_connection_cmp_timestamp_p_with_data, NULL);
+	if (!ssids) {
+		ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_bytes_unref);
+		/* Add wildcard SSID using a static wildcard SSID used for every scan */
+		g_ptr_array_insert (ssids, 0, g_bytes_ref (nm_gbytes_get_empty ()));
+	}
 
-	ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_bytes_unref);
+	unique_ssids = g_hash_table_new (nm_gbytes_hash, nm_gbytes_equal);
+	for (i = 1; i < ssids->len; i++) {
+		if (!g_hash_table_add (unique_ssids, ssids->pdata[i]))
+			nm_assert_not_reached ();
+	}
 
-	/* Add wildcard SSID using a static wildcard SSID used for every scan */
-	if (G_UNLIKELY (nullssid == NULL))
-		nullssid = g_bytes_new_static ("", 0);
-	g_ptr_array_add (ssids, g_bytes_ref (nullssid));
+	g_qsort_with_data (connections,
+	                   connections_len,
+	                   sizeof (NMSettingsConnection *),
+	                   nm_settings_connection_cmp_timestamp_p_with_data,
+	                   NULL);
 
-	for (i = 0; connections[i]; i++) {
+	n_hidden = 0;
+	for (i = 0; i < connections_len; i++) {
 		NMSettingWireless *s_wifi;
 		GBytes *ssid;
 
-		if (i >= max_scan_ssids - 1)
+		if (ssids->len >= max_scan_ssids)
+			break;
+
+		if (n_hidden > 4) {
+			/* we allow at most 4 hidden profiles to be actively scanned. The
+			 * reason is speed and to not disclose too many SSIDs. */
 			break;
+		}
 
-		s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (nm_settings_connection_get_connection (connections[i]));
+		s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (connections[i]));
 		ssid = nm_setting_wireless_get_ssid (s_wifi);
+
+		if (!g_hash_table_add (unique_ssids, ssid))
+			continue;
+
 		g_ptr_array_add (ssids, g_bytes_ref (ssid));
+		n_hidden++;
 	}
 
-	return ssids;
+	NM_SET_OUT (out_has_hidden_profiles, n_hidden > 0);
+	return g_steal_pointer (&ssids);
 }
 
-static void
-request_wireless_scan (NMDeviceWifi *self,
-                       gboolean periodic,
-                       gboolean force_if_scanning,
-                       const GPtrArray *ssids)
+static gboolean
+_scan_request_delay_cb (gpointer user_data)
 {
+	NMDeviceWifi *self = user_data;
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	gboolean request_started = FALSE;
 
-	nm_clear_g_source (&priv->pending_scan_id);
+	nm_clear_g_source_inst (&priv->scan_request_delay_source);
 
-	if (!force_if_scanning && priv->requested_scan) {
-		/* There's already a scan in progress */
-		return;
-	}
+	_LOGT_scan ("scan request completed (after extra delay)");
 
-	if (!check_scanning_prohibited (self, periodic)) {
-		gs_unref_ptrarray GPtrArray *hidden_ssids = NULL;
-
-		_LOGD (LOGD_WIFI, "wifi-scan: scanning requested");
-
-		if (!ssids) {
-			hidden_ssids = build_hidden_probe_list (self);
-			if (hidden_ssids) {
-				if (priv->hidden_probe_scan_warn) {
-					priv->hidden_probe_scan_warn = FALSE;
-					_LOGW (LOGD_WIFI, "wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. This makes you trackable");
-				}
-				ssids = hidden_ssids;
-			} else
-				priv->hidden_probe_scan_warn = TRUE;
-		}
-
-		if (_LOGD_ENABLED (LOGD_WIFI)) {
-			if (ssids) {
-				guint i;
+	_scan_notify_is_scanning (self);
+	return G_SOURCE_REMOVE;
+}
 
-				for (i = 0; i < ssids->len; i++) {
-					gs_free char *ssid_str = NULL;
-					GBytes *ssid = ssids->pdata[i];
+static void
+_scan_supplicant_request_scan_cb (NMSupplicantInterface *supp_iface,
+                                  GCancellable *cancellable,
+                                  gpointer user_data)
+{
+	NMDeviceWifi *self;
+	NMDeviceWifiPrivate *priv;
 
-					ssid_str = g_bytes_get_size (ssid) > 0
-					           ? _nm_utils_ssid_to_string (ssid)
-					           : NULL;
-					_LOGD (LOGD_WIFI, "wifi-scan: (%u) probe scanning SSID %s",
-					       i, ssid_str ?: "*any*");
-				}
-			} else
-				_LOGD (LOGD_WIFI, "wifi-scan: no SSIDs to probe scan");
-		}
+	if (g_cancellable_is_cancelled (cancellable))
+		return;
 
-		_hw_addr_set_scanning (self, FALSE);
+	self = user_data;
+	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-		nm_supplicant_interface_request_scan (priv->sup_iface,
-		                                      ssids ? (GBytes *const*) ssids->pdata : NULL,
-		                                      ssids ? ssids->len : 0u);
-		request_started = TRUE;
-	} else
-		_LOGD (LOGD_WIFI, "wifi-scan: scanning requested but not allowed at this time");
+	_LOGT_scan ("scan request completed (D-Bus request)");
 
-	_requested_scan_set (self, request_started);
+	/* we just completed a scan request, but possibly the supplicant's state is not yet toggled
+	 * to "scanning". That means, our internal scanning state "priv->scan_is_scanning" would already
+	 * flip to idle, while in a moment the supplicant would toggle the state again.
+	 *
+	 * Artificially keep the scanning state on, for another SCAN_EXTRA_DELAY_MSEC msec. */
+	nm_clear_g_source_inst (&priv->scan_request_delay_source);
+	priv->scan_request_delay_source = nm_g_source_attach (nm_g_timeout_source_new (SCAN_EXTRA_DELAY_MSEC,
+	                                                                               G_PRIORITY_DEFAULT,
+	                                                                               _scan_request_delay_cb,
+	                                                                               self,
+	                                                                               NULL),
+	                                                      NULL);
 
-	schedule_scan (self, request_started);
+	g_clear_object (&priv->scan_request_cancellable);
+	_scan_notify_is_scanning (self);
 }
 
 static gboolean
-request_wireless_scan_periodic (gpointer user_data)
+_scan_kickoff_timeout_cb (gpointer user_data)
 {
 	NMDeviceWifi *self = user_data;
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	priv->pending_scan_id = 0;
-	request_wireless_scan (self, TRUE, FALSE, NULL);
+	priv->scan_kickoff_timeout_id = 0;
+	_scan_kickoff (self);
 	return G_SOURCE_REMOVE;
 }
 
-/*
- * schedule_scan
- *
- * Schedule a wireless scan.
- *
- */
 static void
-schedule_scan (NMDeviceWifi *self, gboolean backoff)
+_scan_kickoff (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
-
-	/* 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_sec < priv->scheduled_scan_time)
-			nm_clear_g_source (&priv->pending_scan_id);
-	}
-
-	if (!priv->pending_scan_id) {
-		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))
-			factor = 1;
-
-		priv->pending_scan_id = g_timeout_add_seconds (next_scan,
-		                                               request_wireless_scan_periodic,
-		                                               self);
-
-		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_sec = MAX(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP);
-				/* ... or more than 120s */
-				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_sec = 5;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
+	gboolean is_explict = FALSE;
+	NMDeviceState device_state;
+	gboolean has_hidden_profiles;
+	gint64 now_msec;
+	gint64 ratelimit_duration_msec;
+
+	if (!priv->sup_iface) {
+		_LOGT_scan ("kickoff: don't scan (has no supplicant interface)");
+		return;
+	}
+
+	if (priv->scan_request_cancellable) {
+		_LOGT_scan ("kickoff: don't scan (has scan_request_cancellable)");
+		/* We are currently waiting for a scan request to complete. Wait longer. */
+		return;
+	}
+
+	now_msec = nm_utils_get_monotonic_timestamp_msec ();
+
+	_scan_request_ssids_remove_all (priv, now_msec, G_MAXUINT);
+
+	device_state = nm_device_get_state (NM_DEVICE (self));
+	if (   device_state > NM_DEVICE_STATE_DISCONNECTED
+	    && device_state <= NM_DEVICE_STATE_ACTIVATED) {
+		/* while we are activated, we rate limit more. */
+		ratelimit_duration_msec = 8000;
+	} else
+		ratelimit_duration_msec = 1500;
+
+	if (priv->scan_last_request_started_at_msec + ratelimit_duration_msec > now_msec) {
+		_LOGT_scan ("kickoff: don't scan (rate limited for another %d.%03d sec%s)",
+		            (int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec) / 1000),
+		            (int) ((priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec) % 1000),
+		            !priv->scan_kickoff_timeout_id ? ", schedule timeout" : "");
+		if (   !priv->scan_kickoff_timeout_id
+		    && (   priv->scan_explicit_allowed
+		        || priv->scan_periodic_allowed)) {
+			priv->scan_kickoff_timeout_id = g_timeout_add (priv->scan_last_request_started_at_msec + ratelimit_duration_msec - now_msec,
+			                                               _scan_kickoff_timeout_cb,
+			                                               self);
+		}
+		return;
+	}
+
+	if (priv->scan_explicit_requested) {
+		if (!priv->scan_explicit_allowed) {
+			_LOGT_scan ("kickoff: don't scan (explicit scan requested but not allowed)");
+			return;
+		}
+		priv->scan_explicit_requested = FALSE;
+		is_explict = TRUE;
+	} else {
+
+		if (!priv->scan_periodic_allowed) {
+			_LOGT_scan ("kickoff: don't scan (periodic scan currently not allowed)");
+			priv->scan_periodic_next_msec = 0;
+			priv->scan_periodic_interval_sec = 0;
+			nm_clear_g_source (&priv->scan_kickoff_timeout_id);
+			return;
+		}
+
+		nm_assert (priv->scan_explicit_allowed);
+
+		if (now_msec < priv->scan_periodic_next_msec) {
+			_LOGT_scan ("kickoff: don't scan (periodic scan waiting for another %d.%03d sec%s)",
+			            (int) ((priv->scan_periodic_next_msec - now_msec) / 1000),
+			            (int) ((priv->scan_periodic_next_msec - now_msec) % 1000),
+			            !priv->scan_kickoff_timeout_id ? ", schedule timeout" : "");
+			if (!priv->scan_kickoff_timeout_id) {
+				priv->scan_kickoff_timeout_id = g_timeout_add_seconds ((priv->scan_periodic_next_msec - now_msec + 999) / 1000,
+				                                                       _scan_kickoff_timeout_cb,
+				                                                       self);
+			}
+			return;
 		}
 
-		_LOGD (LOGD_WIFI, "wifi-scan: scheduled in %d seconds (interval now %d seconds)",
-		       next_scan, priv->scan_interval_sec);
+		priv->scan_periodic_interval_sec = NM_CLAMP (((int) priv->scan_periodic_interval_sec) * 3 / 2,
+		                                             SCAN_INTERVAL_SEC_MIN,
+		                                             SCAN_INTERVAL_SEC_MAX);
+		priv->scan_periodic_next_msec = now_msec + 1000 * priv->scan_periodic_interval_sec;
 	}
+
+	ssids = _scan_request_ssids_build_hidden (self, now_msec, &has_hidden_profiles);
+	if (has_hidden_profiles) {
+		if (priv->hidden_probe_scan_warn) {
+			priv->hidden_probe_scan_warn = FALSE;
+			_LOGW (LOGD_WIFI, "wifi-scan: active scanning for networks due to profiles with wifi.hidden=yes. This makes you trackable");
+		}
+	} else if (!is_explict)
+		priv->hidden_probe_scan_warn = TRUE;
+
+	if (_LOGD_ENABLED (LOGD_WIFI)) {
+		gs_free char *ssids_str = NULL;
+		guint ssids_len = 0;
+
+		if (ssids) {
+			gs_strfreev char **strv = NULL;
+			guint i;
+
+			strv = g_new (char *, ssids->len + 1u);
+			for (i = 0; i < ssids->len; i++)
+				strv[i] = _nm_utils_ssid_to_string (ssids->pdata[i]);
+			strv[i] = NULL;
+
+			nm_assert (ssids->len > 0);
+			nm_assert (ssids->len == NM_PTRARRAY_LEN (strv));
+
+			ssids_str = g_strjoinv (", ", strv);
+			ssids_len = ssids->len;
+		}
+		_LOGD (LOGD_WIFI, "wifi-scan: start %s scan (%u SSIDs to probe scan%s%s%s)",
+		       is_explict ? "explicit" : "periodic",
+		       ssids_len,
+		       NM_PRINT_FMT_QUOTED (ssids_str, " [", ssids_str, "]", ""));
+	}
+
+	priv->scan_last_request_started_at_msec = now_msec;
+
+	if (is_explict)
+		_LOGT_scan ("kickoff: explicit scan starting");
+	else {
+		_LOGT_scan ("kickoff: periodic scan starting (next scan is scheduled in %d.%03d sec)",
+		            (int) ((priv->scan_periodic_next_msec - now_msec) / 1000),
+		            (int) ((priv->scan_periodic_next_msec - now_msec) % 1000));
+	}
+
+	_hw_addr_set_scanning (self, FALSE);
+
+	priv->scan_request_cancellable = g_cancellable_new ();
+	nm_supplicant_interface_request_scan (priv->sup_iface,
+	                                      ssids ? (GBytes *const*) ssids->pdata : NULL,
+	                                      ssids ? ssids->len : 0u,
+	                                      priv->scan_request_cancellable,
+	                                      _scan_supplicant_request_scan_cb,
+	                                      self);
+
+	/* It's OK to call _scan_notify_is_scanning() again. They mutually call each other,
+	 * but _scan_kickoff() sets "priv->scan_request_cancellable" which will stop
+	 * them from recursing indefinitely. */
+	_scan_notify_is_scanning (self);
 }
 
 /****************************************************************************
@@ -1597,16 +1854,15 @@ ap_list_dump (gpointer user_data)
 		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]",
+		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u.%03u, last:%s]",
 		       (guint) (now_msec / NM_UTILS_MSEC_PER_SEC),
 		       (guint) (now_msec % NM_UTILS_MSEC_PER_SEC),
-		         priv->last_scan_msec > 0
+		         priv->scan_last_complete_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);
+		                         (guint) (priv->scan_last_complete_msec / NM_UTILS_MSEC_PER_SEC),
+		                         (guint) (priv->scan_last_complete_msec % NM_UTILS_MSEC_PER_SEC))
+		       : "-1");
 		c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
 			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_msec);
 	}
@@ -2138,6 +2394,7 @@ supplicant_iface_state (NMDeviceWifi *self,
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceState devstate;
 	gboolean scanning;
+	gboolean scan_changed;
 
 	_LOGI (LOGD_DEVICE | LOGD_WIFI,
 	       "supplicant interface state: %s -> %s%s",
@@ -2158,7 +2415,8 @@ supplicant_iface_state (NMDeviceWifi *self,
 		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;
+		priv->scan_periodic_interval_sec = 0;
+		priv->scan_periodic_next_msec = 0;
 	}
 
 	/* In these states we know the supplicant is actually talking to something */
@@ -2225,17 +2483,19 @@ supplicant_iface_state (NMDeviceWifi *self,
 		}
 		break;
 	case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
-		/* we would clear _requested_scan_set() and trigger a new scan.
+		/* we would clear _scan_has_pending_action_set() and trigger a new scan.
 		 * However, we don't want to cancel the current pending action, so force
 		 * a new scan request. */
-		request_wireless_scan (self, FALSE, TRUE, NULL);
 		break;
 	default:
 		break;
 	}
 
 out:
-	_notify_scanning (self);
+	scan_changed  = _scan_notify_allowed (self, NM_TERNARY_FALSE);
+	scan_changed |= _scan_notify_is_scanning (self);
+	if (scan_changed)
+		_scan_kickoff (self);
 
 	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
 		nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
@@ -2764,6 +3024,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		ap = ap_fake;
 	}
 
+	_scan_notify_allowed (self, NM_TERNARY_DEFAULT);
+
 	set_current_ap (self, ap, FALSE);
 	nm_active_connection_set_specific_object (NM_ACTIVE_CONNECTION (req),
 	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (ap)));
@@ -3103,8 +3365,8 @@ activation_success_handler (NMDevice *device)
 
 	update_seen_bssids_cache (self, priv->current_ap);
 
-	/* Reset scan interval to something reasonable */
-	priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + (SCAN_INTERVAL_SEC_STEP * 2);
+	priv->scan_periodic_interval_sec = 0;
+	priv->scan_periodic_next_msec = 0;
 }
 
 static void
@@ -3164,9 +3426,6 @@ device_state_changed (NMDevice *device,
 		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
-		/* Kick off a scan to get latest results */
-		priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN;
-		request_wireless_scan (self, FALSE, FALSE, NULL);
 		break;
 	default:
 		break;
@@ -3174,6 +3433,8 @@ device_state_changed (NMDevice *device,
 
 	if (clear_aps)
 		remove_all_aps (self);
+
+	_scan_notify_allowed (self, NM_TERNARY_DEFAULT);
 }
 
 static gboolean
@@ -3324,8 +3585,8 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_LAST_SCAN:
 		g_value_set_int64 (value,
-		                     priv->last_scan_msec > 0
-		                   ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan_msec, NM_UTILS_NSEC_PER_MSEC)
+		                     priv->scan_last_complete_msec > 0
+		                   ? nm_utils_monotonic_timestamp_as_boottime (priv->scan_last_complete_msec, NM_UTILS_NSEC_PER_MSEC)
 		                   : (gint64) -1);
 		break;
 	default:
@@ -3361,8 +3622,10 @@ nm_device_wifi_init (NMDeviceWifi *self)
 
 	c_list_init (&priv->aps_lst_head);
 	c_list_init (&priv->scanning_prohibited_lst_head);
+	c_list_init (&priv->scan_request_ssids_lst_head);
 	priv->aps_idx_by_supplicant_path = g_hash_table_new (nm_direct_hash, NULL);
 
+	priv->scan_last_request_started_at_msec = G_MININT64;
 	priv->hidden_probe_scan_warn = TRUE;
 	priv->mode = NM_802_11_MODE_INFRA;
 	priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index f89ec86c..3fdc5ac5 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -1254,9 +1254,9 @@ disconnect (NMModem *modem,
 	/* Already cancelled or no simple-iface? We are done. */
 	if (   !ctx->self->_priv.simple_iface
 	    || g_cancellable_is_cancelled (cancellable)) {
-		nm_utils_invoke_on_idle (disconnect_context_complete_on_idle,
-		                         ctx,
-		                         cancellable);
+		nm_utils_invoke_on_idle (cancellable,
+		                         disconnect_context_complete_on_idle,
+		                         ctx);
 		return;
 	}
 
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 2fc44881..96b08e7c 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -211,9 +211,9 @@ disconnect (NMModem *modem,
 
 	if (   state != NM_MODEM_STATE_CONNECTED
 	    || g_cancellable_is_cancelled (cancellable)) {
-		nm_utils_invoke_on_idle (disconnect_context_complete_on_idle,
-		                         ctx,
-		                         cancellable);
+		nm_utils_invoke_on_idle (cancellable,
+		                         disconnect_context_complete_on_idle,
+		                         ctx);
 		return;
 	}