about summary refs log tree commit diff
path: root/src
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
parent1e5977b62f896e844b548c3007ace9e1dfa7f9ed (diff)
New upstream version 1.24.0 upstream/1.24.0
Diffstat (limited to 'src')
-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
-rw-r--r--src/dhcp/nm-dhcp-helper.c45
-rw-r--r--src/dns/nm-dns-manager.c41
-rw-r--r--src/ndisc/nm-lndp-ndisc.c27
-rw-r--r--src/ndisc/nm-ndisc.c39
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c5
-rw-r--r--src/nm-auth-utils.c250
-rw-r--r--src/nm-auth-utils.h4
-rw-r--r--src/nm-ip4-config.c26
-rw-r--r--src/nm-ip4-config.h23
-rw-r--r--src/nm-ip6-config.c26
-rw-r--r--src/nm-ip6-config.h5
-rw-r--r--src/nm-manager.c115
-rw-r--r--src/nm-manager.h12
-rw-r--r--src/nm-types.h10
-rw-r--r--src/platform/nm-linux-platform.c8
-rw-r--r--src/platform/nmp-object.c8
-rw-r--r--src/platform/tests/test-link.c77
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c4
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c7
-rw-r--r--src/supplicant/nm-supplicant-interface.c168
-rw-r--r--src/supplicant/nm-supplicant-interface.h9
-rw-r--r--src/supplicant/nm-supplicant-manager.c3
-rw-r--r--src/vpn/nm-vpn-connection.c40
38 files changed, 1500 insertions, 694 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;
 	}
 
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 17f9db7f..365e32e3 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -115,19 +115,42 @@ main (int argc, char *argv[])
 	gs_unref_variant GVariant *parameters = NULL;
 	gs_unref_variant GVariant *result = NULL;
 	gboolean success = FALSE;
-	guint try_count = 0;
+	guint try_count;
+	gint64 time_start;
 	gint64 time_end;
 
-	/* FIXME: g_dbus_connection_new_for_address_sync() tries to connect to the socket in
-	 * non-blocking mode, which can easily fail with EAGAIN, causing the creation of the
-	 * socket to fail with "Could not connect: Resource temporarily unavailable".
-	 *
-	 * We should instead create the GIOStream ourself and block on connecting to
-	 * the socket. */
+	/* Connecting to the unix socket can fail with EAGAIN if there are too
+	 * many pending connections and the server can't accept them in time
+	 * before reaching backlog capacity. Ideally the server should increase
+	 * the backlog length, but GLib doesn't provide a way to change it for a
+	 * GDBus server. Retry for up to 5 seconds in case of failure. */
+	time_start = g_get_monotonic_time ();
+	time_end = time_start + (5000 * 1000L);
+	try_count = 0;
+
+do_connect:
+	try_count++;
 	connection = g_dbus_connection_new_for_address_sync ("unix:path=" NMRUNDIR "/private-dhcp",
 	                                                     G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
 	                                                     NULL, NULL, &error);
 	if (!connection) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
+			gint64 time_remaining = time_end - g_get_monotonic_time ();
+			gint64 interval;
+
+			if (time_remaining > 0) {
+				_LOGi ("failure to connect: %s (retry %u, waited %lld ms)",
+				       error->message, try_count,
+				       (long long) (time_end - time_remaining - time_start) / 1000);
+				interval = NM_CLAMP ((gint64) (100L * (1L << NM_MIN (try_count, 31))),
+				                     5000,
+				                     100000);
+				g_usleep (NM_MIN (interval, time_remaining));
+				g_clear_error (&error);
+				goto do_connect;
+			}
+		}
+
 		g_dbus_error_strip_remote_error (error);
 		_LOGE ("could not connect to NetworkManager D-Bus socket: %s",
 		       error->message);
@@ -135,8 +158,8 @@ main (int argc, char *argv[])
 	}
 
 	parameters = build_signal_parameters ();
-
 	time_end = g_get_monotonic_time () + (200 * 1000L); /* retry for at most 200 milliseconds */
+	try_count = 0;
 
 do_notify:
 	try_count++;
@@ -158,6 +181,7 @@ do_notify:
 		s_err = g_dbus_error_get_remote_error (error);
 		if (NM_IN_STRSET (s_err, "org.freedesktop.DBus.Error.UnknownMethod")) {
 			gint64 remaining_time = time_end - g_get_monotonic_time ();
+			gint64 interval;
 
 			/* I am not sure that a race can actually happen, as we register the object
 			 * on the server side during GDBusServer:new-connection signal.
@@ -166,7 +190,10 @@ do_notify:
 			 * do some retry. */
 			if (remaining_time > 0) {
 				_LOGi ("failure to call notify: %s (retry %u)", error->message, try_count);
-				g_usleep (NM_MIN (NM_CLAMP ((gint64) (100L * (1L << try_count)), 5000, 25000), remaining_time));
+				interval = NM_CLAMP ((gint64) (100L * (1L << NM_MIN (try_count, 31))),
+				                     5000,
+				                     25000);
+				g_usleep (NM_MIN (interval, remaining_time));
 				g_clear_error (&error);
 				goto do_notify;
 			}
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index b40dd6b6..666bad60 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -62,6 +62,7 @@ typedef struct {
 	GPtrArray *options;
 	const char *nis_domain;
 	GPtrArray *nis_servers;
+	NMTernary has_trust_ad;
 } NMResolvConfData;
 
 /*****************************************************************************/
@@ -410,8 +411,11 @@ merge_one_ip_config (NMResolvConfData *rc,
                      const NMIPConfig *ip_config)
 {
 	int addr_family;
-	guint num, i;
 	char buf[NM_UTILS_INET_ADDRSTRLEN + 50];
+	gboolean has_trust_ad;
+	guint num_nameservers;
+	guint num;
+	guint i;
 
 	addr_family = nm_ip_config_get_addr_family (ip_config);
 
@@ -419,8 +423,8 @@ merge_one_ip_config (NMResolvConfData *rc,
 	nm_assert (ifindex > 0);
 	nm_assert (ifindex == nm_ip_config_get_ifindex (ip_config));
 
-	num = nm_ip_config_get_num_nameservers (ip_config);
-	for (i = 0; i < num; i++) {
+	num_nameservers = nm_ip_config_get_num_nameservers (ip_config);
+	for (i = 0; i < num_nameservers; i++) {
 		const NMIPAddr *addr;
 
 		addr = nm_ip_config_get_nameserver (ip_config, i);
@@ -446,11 +450,28 @@ merge_one_ip_config (NMResolvConfData *rc,
 
 	add_dns_domains (rc->searches, ip_config, FALSE, TRUE);
 
+	has_trust_ad = FALSE;
 	num = nm_ip_config_get_num_dns_options (ip_config);
 	for (i = 0; i < num; i++) {
+		const char *option = nm_ip_config_get_dns_option (ip_config, i);
+
+		if (nm_streq (option, NM_SETTING_DNS_OPTION_TRUST_AD)) {
+			has_trust_ad = TRUE;
+			continue;
+		}
 		add_dns_option_item (rc->options,
 		                     nm_ip_config_get_dns_option (ip_config, i));
 	}
+	if (num_nameservers == 0) {
+		/* If the @ip_config contributes no DNS servers, ignore whether trust-ad is set or unset
+		 * for this @ip_config. */
+	} else if (has_trust_ad) {
+		/* We only set has_trust_ad to TRUE, if all IP configs agree (or don't contribute).
+		 * Once set to FALSE, it doesn't get reset. */
+		if (rc->has_trust_ad == NM_TERNARY_DEFAULT)
+			rc->has_trust_ad = NM_TERNARY_TRUE;
+	} else
+		rc->has_trust_ad = NM_TERNARY_FALSE;
 
 	if (addr_family == AF_INET) {
 		const NMIP4Config *ip4_config = (const NMIP4Config *) ip_config;
@@ -1106,11 +1127,12 @@ _collect_resolv_conf_data (NMDnsManager *self,
 {
 	NMDnsManagerPrivate *priv;
 	NMResolvConfData rc = {
-		.nameservers = g_ptr_array_new (),
-		.searches = g_ptr_array_new (),
-		.options = g_ptr_array_new (),
-		.nis_domain = NULL,
-		.nis_servers = g_ptr_array_new (),
+		.nameservers  = g_ptr_array_new (),
+		.searches     = g_ptr_array_new (),
+		.options      = g_ptr_array_new (),
+		.nis_domain   = NULL,
+		.nis_servers  = g_ptr_array_new (),
+		.has_trust_ad = NM_TERNARY_DEFAULT,
 	};
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
@@ -1174,6 +1196,9 @@ _collect_resolv_conf_data (NMDnsManager *self,
 		}
 	}
 
+	if (rc.has_trust_ad == NM_TERNARY_TRUE)
+		g_ptr_array_add (rc.options, g_strdup (NM_SETTING_DNS_OPTION_TRUST_AD));
+
 	*out_searches = _ptrarray_to_strv (rc.searches);
 	*out_options = _ptrarray_to_strv (rc.options);
 	*out_nameservers = _ptrarray_to_strv (rc.nameservers);
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index b698489c..b10adc91 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -101,8 +101,6 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	int offset;
 	int hop_limit;
 	guint32 val;
-	guint32 clamp_pltime;
-	guint32 clamp_vltime;
 
 	/* Router discovery is subject to the following RFC documents:
 	 *
@@ -167,22 +165,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			changed |= NM_NDISC_CONFIG_GATEWAYS;
 	}
 
-	/* Addresses & Routes
-	 *
-	 * The Preferred Lifetime and Valid Lifetime of PIOs are capped to Router Lifetime
-	 * and NM_NDISC_VLTIME_MULT * Preferred Lifetime, respectively.
-	 *
-	 * The Lifetime of RIOs is capped to the Router Lifetime (there is no point in
-	 * maintaining a route if it employs a dead router).
-	 *
-	 * See draft-gont-6man-slaac-renum
-	 */
-	#define NM_NDISC_VLTIME_MULT ((guint32) 48)
-	clamp_pltime = ndp_msgra_router_lifetime (msgra);
-	clamp_vltime =   (clamp_pltime < G_MAXUINT32 / NM_NDISC_VLTIME_MULT)
-	               ? clamp_pltime * NM_NDISC_VLTIME_MULT
-	               : G_MAXUINT32;
-
+	/* Addresses & Routes */
 	ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREFIX) {
 		guint8 r_plen;
 		struct in6_addr r_network;
@@ -203,7 +186,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 				.network = r_network,
 				.plen = r_plen,
 				.timestamp = now,
-				.lifetime = NM_MIN (ndp_msg_opt_prefix_valid_time (msg, offset), clamp_vltime),
+				.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
 			};
 
 			if (nm_ndisc_add_route (ndisc, &route))
@@ -216,8 +199,8 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			NMNDiscAddress address = {
 				.address = r_network,
 				.timestamp = now,
-				.lifetime = NM_MIN (ndp_msg_opt_prefix_valid_time (msg, offset), clamp_vltime),
-				.preferred = NM_MIN (ndp_msg_opt_prefix_preferred_time (msg, offset), clamp_pltime),
+				.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
+				.preferred = ndp_msg_opt_prefix_preferred_time (msg, offset),
 			};
 
 			if (address.preferred <= address.lifetime) {
@@ -231,7 +214,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			.gateway = gateway_addr,
 			.plen = ndp_msg_opt_route_prefix_len (msg, offset),
 			.timestamp = now,
-			.lifetime = NM_MIN (ndp_msg_opt_route_lifetime (msg, offset), clamp_pltime),
+			.lifetime = ndp_msg_opt_route_lifetime (msg, offset),
 			.preference = _route_preference_coerce (ndp_msg_opt_route_preference (msg, offset)),
 		};
 
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index bb3a92b0..5e556084 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -442,15 +442,8 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 	}
 
 	if (existing) {
-		/* A Valid Lifetime of 0 eliminates the corresponding address(es). This deviates
-		 * from RFC4862 Section 5.5.3, item e), as recommended in IETF draft draft-gont-6man-slaac-renum.
-		 */
-		if (new->lifetime == 0) {
-			g_array_remove_index (rdata->addresses, i);
-			return TRUE;
-		}
-
 		if (from_ra) {
+			const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */
 			gint64 old_expiry_lifetime, old_expiry_preferred;
 
 			old_expiry_lifetime = get_expiry (existing);
@@ -459,16 +452,25 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 			if (new->lifetime == NM_NDISC_INFINITY)
 				existing->lifetime = NM_NDISC_INFINITY;
 			else {
-				gint64 new_lifetime;
+				gint64 new_lifetime, remaining_lifetime;
 
-				/* Honor small valid lifetimes, as discussed in
-				 * draft-gont-6man-slaac-renum, to allow for more timelier
-				 * reaction to renumbering events. This deviates from
-				 * RFC4862 Section 5.5.3, item e).
-				 */
+				/* see RFC4862 5.5.3.e */
+				if (existing->lifetime == NM_NDISC_INFINITY)
+					remaining_lifetime = G_MAXINT64;
+				else
+					remaining_lifetime = ((gint64) existing->timestamp) + ((gint64) existing->lifetime) - ((gint64) now_s);
 				new_lifetime = ((gint64) new->timestamp) + ((gint64) new->lifetime) - ((gint64) now_s);
-				existing->timestamp = now_s;
-				existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1));
+
+				if (   new_lifetime > (gint64) NM_NDISC_PREFIX_LFT_MIN
+				    || new_lifetime > remaining_lifetime) {
+					existing->timestamp = now_s;
+					existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1));
+				} else if (remaining_lifetime <= (gint64) NM_NDISC_PREFIX_LFT_MIN) {
+					/* keep the current lifetime. */
+				} else {
+					existing->timestamp = now_s;
+					existing->lifetime = NM_NDISC_PREFIX_LFT_MIN;
+				}
 			}
 
 			if (new->preferred == NM_NDISC_INFINITY) {
@@ -485,6 +487,11 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 			       || old_expiry_preferred != get_expiry_preferred (existing);
 		}
 
+		if (new->lifetime == 0) {
+			g_array_remove_index (rdata->addresses, i);
+			return TRUE;
+		}
+
 		if (   get_expiry (existing) == get_expiry (new)
 		    && get_expiry_preferred (existing) == get_expiry_preferred (new))
 			return FALSE;
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index 7a5eea01..91fe9802 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -217,8 +217,9 @@ test_everything_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 
 		g_assert_cmpint (rdata->gateways_n, ==, 1);
 		match_gateway (rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-		g_assert_cmpint (rdata->addresses_n, ==, 1);
-		match_address (rdata, 0, "2001:db8:a:b::1", data->timestamp1, 10, 10);
+		g_assert_cmpint (rdata->addresses_n, ==, 2);
+		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0);
+		match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10);
 		g_assert_cmpint (rdata->routes_n, ==, 1);
 		match_route (rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10);
 		g_assert_cmpint (rdata->dns_servers_n, ==, 1);
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index ca2870db..b9efff48 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -16,22 +16,38 @@
 
 /*****************************************************************************/
 
+typedef struct {
+	const char *tag;
+	gpointer data;
+	GDestroyNotify destroy;
+} ChainData;
+
 struct _NMAuthChain {
 
 	CList parent_lst;
 
-	CList data_lst_head;
+	ChainData *data_arr;
+	guint data_len;
+	guint data_alloc;
 
 	CList auth_call_lst_head;
 
 	GDBusMethodInvocation *context;
 	NMAuthSubject *subject;
 
+	GCancellable *cancellable;
+
+	/* if set, it also means that the chain is already started and was cancelled. */
+	GSource *cancellable_idle_source;
+
 	NMAuthChainResultFunc done_func;
 	gpointer user_data;
 
+	gulong cancellable_id;
+
 	guint num_pending_auth_calls;
 
+	bool is_started:1;
 	bool is_destroyed:1;
 	bool is_finishing:1;
 };
@@ -78,6 +94,82 @@ _ASSERT_call (AuthCall *call)
 /*****************************************************************************/
 
 static void
+_done_and_destroy (NMAuthChain *self)
+{
+	self->is_finishing = TRUE;
+	self->done_func (self, self->context, self->user_data);
+	nm_assert (self->is_finishing);
+	_auth_chain_destroy (self);
+}
+
+static gboolean
+_cancellable_idle_cb (gpointer user_data)
+{
+	NMAuthChain *self = user_data;
+	AuthCall *call;
+
+	nm_assert (g_cancellable_is_cancelled (self->cancellable));
+	nm_assert (self->cancellable_idle_source);
+
+	c_list_for_each_entry (call, &self->auth_call_lst_head, auth_call_lst) {
+		if (call->call_id) {
+			self->num_pending_auth_calls--;
+			nm_auth_manager_check_authorization_cancel (g_steal_pointer (&call->call_id));
+		}
+	}
+
+	_done_and_destroy (self);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_cancellable_on_idle (NMAuthChain *self)
+{
+	if (self->cancellable_idle_source)
+		return;
+
+	self->cancellable_idle_source = nm_g_idle_source_new (G_PRIORITY_DEFAULT,
+	                                                      _cancellable_idle_cb,
+	                                                      self,
+	                                                      NULL);
+	g_source_attach (self->cancellable_idle_source, NULL);
+}
+
+GCancellable *
+nm_auth_chain_get_cancellable (NMAuthChain *self)
+{
+	return self->cancellable;
+}
+
+static void
+_cancellable_cancelled (GCancellable *cancellable,
+                        NMAuthChain *self)
+{
+	_cancellable_on_idle (self);
+}
+
+void
+nm_auth_chain_set_cancellable (NMAuthChain *self,
+                               GCancellable *cancellable)
+{
+	g_return_if_fail (self);
+	g_return_if_fail (G_IS_CANCELLABLE (cancellable));
+
+	/* after the chain is started, the cancellable can no longer be changed.
+	 * No need to handle the complexity of swapping the cancellable *after*
+	 * requests are already started. */
+	g_return_if_fail (!self->is_started);
+	nm_assert (c_list_is_empty (&self->auth_call_lst_head));
+
+	/* also no need to allow setting different cancellables. */
+	g_return_if_fail (!self->cancellable);
+
+	self->cancellable = g_object_ref (cancellable);
+}
+
+/*****************************************************************************/
+
+static void
 auth_call_free (AuthCall *call)
 {
 	_ASSERT_call (call);
@@ -87,7 +179,7 @@ auth_call_free (AuthCall *call)
 		call->chain->num_pending_auth_calls--;
 		nm_auth_manager_check_authorization_cancel (call->call_id);
 	}
-	g_slice_free (AuthCall, call);
+	nm_g_slice_free (call);
 }
 
 static AuthCall *
@@ -104,29 +196,16 @@ _find_auth_call (NMAuthChain *self, const char *permission)
 
 /*****************************************************************************/
 
-typedef struct {
-	CList data_lst;
-	const char *tag;
-	gpointer data;
-	GDestroyNotify destroy;
-} ChainData;
-
-static void
-chain_data_free (ChainData *chain_data)
-{
-	c_list_unlink_stale (&chain_data->data_lst);
-	if (chain_data->destroy)
-		chain_data->destroy (chain_data->data);
-	g_slice_free (ChainData, chain_data);
-}
-
 static ChainData *
 _get_data (NMAuthChain *self, const char *tag)
 {
-	ChainData *chain_data;
+	guint i;
 
-	c_list_for_each_entry (chain_data, &self->data_lst_head, data_lst) {
-		if (nm_streq (chain_data->tag, tag))
+	for (i = 0; i < self->data_len; i++) {
+		ChainData *chain_data = &self->data_arr[i];
+
+		if (   chain_data->tag
+		    && nm_streq (chain_data->tag, tag))
 			return chain_data;
 	}
 	return NULL;
@@ -159,7 +238,6 @@ gpointer
 nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
 {
 	ChainData *chain_data;
-	gpointer value;
 
 	g_return_val_if_fail (self, NULL);
 	g_return_val_if_fail (tag, NULL);
@@ -168,12 +246,13 @@ nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
 	if (!chain_data)
 		return NULL;
 
-	value = chain_data->data;
-
-	/* Make sure the destroy handler isn't called when freeing */
+	/* Make sure the destroy handler isn't called when freeing.
+	 *
+	 * We don't bother to really remove the element from the array.
+	 * Just mark the entry as unused by clearing the tag. */
 	chain_data->destroy = NULL;
-	chain_data_free (chain_data);
-	return value;
+	chain_data->tag = NULL;
+	return chain_data->data;
 }
 
 /**
@@ -184,7 +263,7 @@ nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
  *   and nothing is attached.
  * @data_destroy: (allow-none): the destroy function for the data pointer.
  *
- * @tag string is not cloned and must outlife @self. That is why
+ * @tag string is not cloned and must outlive @self. That is why
  * the function is "unsafe". Use nm_auth_chain_set_data() with a C literal
  * instead.
  *
@@ -201,12 +280,8 @@ nm_auth_chain_set_data_unsafe (NMAuthChain *self,
 	g_return_if_fail (self);
 	g_return_if_fail (tag);
 
-	/* we should not track a large number of elements via a linked list. If this becomes
-	 * necessary, revert the code to use GHashTable again. */
-	nm_assert (c_list_length (&self->data_lst_head) < 25);
-
-	/* The tag must not yet exist. Otherwise we'd have to first search the linked
-	 * list for an existing entry. */
+	/* The tag must not yet exist. Otherwise we'd have to first search the
+	 * list for an existing entry. That usage pattern is not supported. */
 	nm_assert (!_get_data (self, tag));
 
 	if (!data) {
@@ -218,17 +293,20 @@ nm_auth_chain_set_data_unsafe (NMAuthChain *self,
 		return;
 	}
 
-	chain_data = g_slice_new (ChainData);
+	if (self->data_len + 1 > self->data_alloc) {
+		if (self->data_alloc == 0)
+			self->data_alloc = 8;
+		else
+			self->data_alloc *= 2;
+		self->data_arr = g_realloc (self->data_arr, sizeof (self->data_arr[0]) * self->data_alloc);
+	}
+
+	chain_data = &self->data_arr[self->data_len++];
 	*chain_data = (ChainData) {
 		.tag     = tag,
 		.data    = data,
 		.destroy = data_destroy,
 	};
-
-	/* we assert that no duplicate tags are added. But still, add the new
-	 * element to the front, so that it would shadow the duplicate element
-	 * in the list. */
-	c_list_link_front (&self->data_lst_head, &chain_data->data_lst);
 }
 
 /*****************************************************************************/
@@ -255,6 +333,12 @@ nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
 
 	nm_assert (!auth_call->call_id);
 
+	if (self->cancellable_idle_source) {
+		/* already cancelled. We always return unknown (even if we happen to
+		 * have already received the response. */
+		return NM_AUTH_CALL_RESULT_UNKNOWN;
+	}
+
 	return auth_call->result;
 }
 
@@ -314,10 +398,7 @@ pk_call_cb (NMAuthManager *auth_manager,
 	if (call->chain->num_pending_auth_calls == 0) {
 		/* we are on an idle-handler or a clean call-stack (non-reentrant) so it's safe
 		 * to invoke the callback right away. */
-		self->is_finishing = TRUE;
-		self->done_func (self, self->context, self->user_data);
-		nm_assert (self->is_finishing);
-		_auth_chain_destroy (self);
+		_done_and_destroy (self);
 	}
 }
 
@@ -350,23 +431,36 @@ nm_auth_chain_add_call_unsafe (NMAuthChain *self,
 	g_return_if_fail (!self->is_finishing);
 	g_return_if_fail (!self->is_destroyed);
 	g_return_if_fail (permission && *permission);
-	nm_assert (   nm_auth_subject_get_subject_type (self->subject)
-	           == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS
-	           || nm_auth_subject_get_subject_type (self->subject)
-	           == NM_AUTH_SUBJECT_TYPE_INTERNAL);
+	nm_assert (NM_IN_SET (nm_auth_subject_get_subject_type (self->subject), NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
+	                                                                        NM_AUTH_SUBJECT_TYPE_INTERNAL));
 
 	/* duplicate permissions are not supported, also because nm_auth_chain_get_result()
 	 * can only return one-permission. */
 	nm_assert (!_find_auth_call (self, permission));
 
-	call = g_slice_new (AuthCall);
+	if (!self->is_started) {
+		self->is_started = TRUE;
+		nm_assert (!self->cancellable_id);
+		if (self->cancellable) {
+			if (g_cancellable_is_cancelled (self->cancellable)) {
+				/* the operation is already cancelled. Schedule the callback on idle. */
+				_cancellable_on_idle (self);
+			} else {
+				self->cancellable_id = g_signal_connect (self->cancellable,
+				                                         "cancelled",
+				                                         G_CALLBACK (_cancellable_cancelled),
+				                                         self);
+			}
+		}
+	}
 
+	call = g_slice_new (AuthCall);
 	*call = (AuthCall) {
 		.chain      = self,
 		.call_id    = NULL,
 		.result     = NM_AUTH_CALL_RESULT_UNKNOWN,
 
-		/* we don't clone the permission string. It's the callers responsiblity. */
+		/* we don't clone the permission string. It's the callers responsibility. */
 		.permission = permission,
 	};
 
@@ -375,21 +469,27 @@ nm_auth_chain_add_call_unsafe (NMAuthChain *self,
 	 * call. */
 	c_list_link_front (&self->auth_call_lst_head, &call->auth_call_lst);
 
-	call->call_id = nm_auth_manager_check_authorization (nm_auth_manager_get (),
-	                                                     self->subject,
-	                                                     permission,
-	                                                     allow_interaction,
-	                                                     pk_call_cb,
-	                                                     call);
-
-	self->num_pending_auth_calls++;
+	if (self->cancellable_idle_source) {
+		/* already cancelled. No need to actually start the request. */
+		nm_assert (call->result == NM_AUTH_CALL_RESULT_UNKNOWN);
+	} else {
+		call->call_id = nm_auth_manager_check_authorization (nm_auth_manager_get (),
+		                                                     self->subject,
+		                                                     permission,
+		                                                     allow_interaction,
+		                                                     pk_call_cb,
+		                                                     call);
+
+		self->num_pending_auth_calls++;
+	}
 
 	_ASSERT_call (call);
 
 	/* we track auth-calls in a linked list. If we end up requesting too many permissions this
 	 * becomes inefficient. If that ever happens, consider a more efficient data structure for
 	 * a large number of requests. */
-	nm_assert (self->num_pending_auth_calls < 25);
+	nm_assert (c_list_length (&self->auth_call_lst_head) < 25);
+	G_STATIC_ASSERT_EXPR (NM_CLIENT_PERMISSION_LAST < 25);
 }
 
 /*****************************************************************************/
@@ -427,10 +527,8 @@ nm_auth_chain_new_subject (NMAuthSubject *subject,
 	NMAuthChain *self;
 
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
-	nm_assert (   nm_auth_subject_get_subject_type (subject)
-	           == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS
-	           || nm_auth_subject_get_subject_type (subject)
-	           == NM_AUTH_SUBJECT_TYPE_INTERNAL);
+	nm_assert (NM_IN_SET (nm_auth_subject_get_subject_type (subject), NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
+	                                                                  NM_AUTH_SUBJECT_TYPE_INTERNAL));
 	nm_assert (done_func);
 
 	self = g_slice_new (NMAuthChain);
@@ -440,7 +538,6 @@ nm_auth_chain_new_subject (NMAuthSubject *subject,
 		.context            = nm_g_object_ref (context),
 		.subject            = g_object_ref (subject),
 		.parent_lst         = C_LIST_INIT (self->parent_lst),
-		.data_lst_head      = C_LIST_INIT (self->data_lst_head),
 		.auth_call_lst_head = C_LIST_INIT (self->auth_call_lst_head),
 	};
 	return self;
@@ -482,24 +579,33 @@ static void
 _auth_chain_destroy (NMAuthChain *self)
 {
 	AuthCall *call;
-	ChainData *chain_data;
 
 	c_list_unlink (&self->parent_lst);
 
 	nm_clear_g_object (&self->subject);
 	nm_clear_g_object (&self->context);
 
-	/* we must first destry all AuthCall instances before ChainData. The reason is
+	nm_clear_g_signal_handler (self->cancellable, &self->cancellable_id);
+	nm_clear_g_source_inst (&self->cancellable_idle_source);
+
+	/* we must first destroy all AuthCall instances before ChainData. The reason is
 	 * that AuthData.permission is not cloned and the lifetime of the string must
 	 * be ensured by the caller. A sensible thing to do for the caller is attach the
 	 * permission string via nm_auth_chain_set_data(). Hence, first free the AuthCall. */
 	while ((call = c_list_first_entry (&self->auth_call_lst_head, AuthCall, auth_call_lst)))
 		auth_call_free (call);
 
-	while ((chain_data = c_list_first_entry (&self->data_lst_head, ChainData, data_lst)))
-		chain_data_free (chain_data);
+	while (self->data_len > 0) {
+		ChainData *chain_data = &self->data_arr[--self->data_len];
+
+		if (chain_data->destroy)
+			chain_data->destroy (chain_data->data);
+	}
+	g_free (self->data_arr);
+
+	nm_g_object_unref (self->cancellable);
 
-	g_slice_free (NMAuthChain, self);
+	nm_g_slice_free (self);
 }
 
 /******************************************************************************
@@ -517,10 +623,8 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 
 	g_return_val_if_fail (connection, FALSE);
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), FALSE);
-	nm_assert (   nm_auth_subject_get_subject_type (subject)
-	              == NM_AUTH_SUBJECT_TYPE_INTERNAL
-	           || nm_auth_subject_get_subject_type (subject)
-	              == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
+	nm_assert (NM_IN_SET (nm_auth_subject_get_subject_type (subject), NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
+	                                                                  NM_AUTH_SUBJECT_TYPE_INTERNAL));
 
 	if (nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL)
 		return TRUE;
diff --git a/src/nm-auth-utils.h b/src/nm-auth-utils.h
index 978d607a..a6c6d659 100644
--- a/src/nm-auth-utils.h
+++ b/src/nm-auth-utils.h
@@ -27,6 +27,10 @@ NMAuthChain *nm_auth_chain_new_subject (NMAuthSubject *subject,
                                         NMAuthChainResultFunc done_func,
                                         gpointer user_data);
 
+GCancellable *nm_auth_chain_get_cancellable (NMAuthChain *self);
+void nm_auth_chain_set_cancellable (NMAuthChain *self,
+                                    GCancellable *cancellable);
+
 gpointer nm_auth_chain_get_data (NMAuthChain *chain, const char *tag);
 
 gpointer nm_auth_chain_steal_data (NMAuthChain *chain, const char *tag);
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index ae637aee..0feb7b2c 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -303,6 +303,7 @@ typedef struct {
 		NMIPConfigDedupMultiIdxType idx_ip4_routes_;
 		NMDedupMultiIdxType idx_ip4_routes;
 	};
+	NMIPConfigFlags config_flags;
 } NMIP4ConfigPrivate;
 
 struct _NMIP4Config {
@@ -1202,7 +1203,8 @@ nm_ip4_config_merge (NMIP4Config *dst,
 
 		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r_src) {
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r_src)) {
-				if (NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES))
+				if (   NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES)
+				    && !NM_FLAGS_HAS (src_priv->config_flags, NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES))
 					continue;
 				if (default_route_metric_penalty) {
 					NMPlatformIP4Route r = *r_src;
@@ -2659,6 +2661,28 @@ nm_ip4_config_llmnr_set (NMIP4Config *self,
 
 /*****************************************************************************/
 
+NMIPConfigFlags
+nm_ip4_config_get_config_flags (const NMIP4Config *self)
+{
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->config_flags;
+}
+
+void
+nm_ip4_config_set_config_flags (NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask)
+{
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	if (mask == 0) {
+		/* for convenience, accept 0 mask to set any flags. */
+		mask = flags;
+	}
+
+	nm_assert (!NM_FLAGS_ANY (flags, ~mask));
+	priv->config_flags = (flags & mask) | (priv->config_flags & ~mask);
+}
+
+/*****************************************************************************/
+
 void
 nm_ip4_config_set_dns_priority (NMIP4Config *self, int priority)
 {
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 01a42e54..d4694d93 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -15,6 +15,14 @@
 
 /*****************************************************************************/
 
+typedef enum _NMIPConfigFlags {
+	NM_IP_CONFIG_FLAG_NONE                            = 0,
+
+	/* if set, then the merge flag NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES gets
+	 * ignored during merge. */
+	NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES = (1ull << 0),
+} NMIPConfigFlags;
+
 typedef struct {
 	NMDedupMultiIdxType parent;
 	NMPObjectType obj_type;
@@ -196,6 +204,9 @@ NMSettingConnectionLlmnr nm_ip4_config_llmnr_get (const NMIP4Config *self);
 void                     nm_ip4_config_llmnr_set (NMIP4Config *self,
                                                   NMSettingConnectionLlmnr llmnr);
 
+void nm_ip4_config_set_config_flags (NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask);
+NMIPConfigFlags nm_ip4_config_get_config_flags (const NMIP4Config *self);
+
 const NMDedupMultiHeadEntry *nm_ip4_config_lookup_addresses (const NMIP4Config *self);
 void nm_ip4_config_reset_addresses (NMIP4Config *self);
 void nm_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *address);
@@ -513,6 +524,18 @@ nm_ip_config_best_default_route_get (const NMIPConfig *self)
 	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_best_default_route_get, nm_ip6_config_best_default_route_get);
 }
 
+static inline NMIPConfigFlags
+nm_ip_config_get_config_flags (const NMIPConfig *self)
+{
+	_NM_IP_CONFIG_DISPATCH (self, nm_ip4_config_get_config_flags, nm_ip6_config_get_config_flags);
+}
+
+static inline void
+nm_ip_config_set_config_flags (NMIPConfig *self, NMIPConfigFlags flags, NMIPConfigFlags mask)
+{
+	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_set_config_flags, nm_ip6_config_set_config_flags, flags, mask);
+}
+
 #define _NM_IP_CONFIG_DISPATCH_SET_OP(_return, dst, src, v4_func, v6_func, ...) \
 	G_STMT_START { \
 		gpointer _dst = (dst); \
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 5acb3932..b60e8057 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -63,6 +63,7 @@ typedef struct {
 		NMIPConfigDedupMultiIdxType idx_ip6_routes_;
 		NMDedupMultiIdxType idx_ip6_routes;
 	};
+	NMIPConfigFlags config_flags;
 	bool ipv6_disabled;
 } NMIP6ConfigPrivate;
 
@@ -875,7 +876,8 @@ nm_ip6_config_merge (NMIP6Config *dst,
 
 		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r_src) {
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r_src)) {
-				if (NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES))
+				if (   NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES)
+				    && !NM_FLAGS_HAS (src_priv->config_flags, NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES))
 					continue;
 				if (default_route_metric_penalty) {
 					NMPlatformIP6Route r = *r_src;
@@ -2296,6 +2298,28 @@ nm_ip6_config_get_dns_option (const NMIP6Config *self, guint i)
 
 /*****************************************************************************/
 
+NMIPConfigFlags
+nm_ip6_config_get_config_flags (const NMIP6Config *self)
+{
+	return NM_IP6_CONFIG_GET_PRIVATE (self)->config_flags;
+}
+
+void
+nm_ip6_config_set_config_flags (NMIP6Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask)
+{
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+
+	if (mask == 0) {
+		/* for convenience, accept 0 mask to set any flags. */
+		mask = flags;
+	}
+
+	nm_assert (!NM_FLAGS_ANY (flags, ~mask));
+	priv->config_flags = (flags & mask) | (priv->config_flags & ~mask);
+}
+
+/*****************************************************************************/
+
 void
 nm_ip6_config_set_dns_priority (NMIP6Config *self, int priority)
 {
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index a9fa8f14..36e8518a 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -128,6 +128,11 @@ gboolean nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboole
 const NMPObject *nm_ip6_config_best_default_route_get (const NMIP6Config *self);
 const NMPObject *_nm_ip6_config_best_default_route_find (const NMIP6Config *self);
 
+enum _NMIPConfigFlags;
+
+void nm_ip6_config_set_config_flags (NMIP6Config *self, enum _NMIPConfigFlags flags, enum _NMIPConfigFlags mask);
+enum _NMIPConfigFlags nm_ip6_config_get_config_flags (const NMIP6Config *self);
+
 const NMDedupMultiHeadEntry *nm_ip6_config_lookup_addresses (const NMIP6Config *self);
 void nm_ip6_config_reset_addresses (NMIP6Config *self);
 void nm_ip6_config_add_address (NMIP6Config *self, const NMPlatformIP6Address *address);
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 1f3ba1f6..bbe50125 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -2375,8 +2375,9 @@ device_auth_done_cb (NMAuthChain *chain,
 	gs_free_error GError *error = NULL;
 	NMAuthCallResult result;
 	NMDevice *device;
+	GCancellable *cancellable;
 	const char *permission;
-	NMDeviceAuthRequestFunc callback;
+	NMManagerDeviceAuthRequestFunc callback;
 	NMAuthSubject *subject;
 
 	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
@@ -2390,18 +2391,26 @@ device_auth_done_cb (NMAuthChain *chain,
 	device = nm_auth_chain_get_data (chain, "device");
 	nm_assert (NM_IS_DEVICE (device));
 
+	cancellable = nm_auth_chain_get_cancellable (chain);
+	nm_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
 	result = nm_auth_chain_get_result (chain, permission);
 	subject = nm_auth_chain_get_subject (chain);
 
-	if (result != NM_AUTH_CALL_RESULT_YES) {
-		_LOGD (LOGD_CORE, "%s request failed: not authorized", permission);
-		error = g_error_new (NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     "%s request failed: not authorized",
-		                     permission);
-	}
+	if (   cancellable
+	    && g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		/* pass. */
+	} else {
+		if (result != NM_AUTH_CALL_RESULT_YES) {
+			_LOGD (LOGD_CORE, "%s request failed: not authorized", permission);
+			error = g_error_new (NM_MANAGER_ERROR,
+			                     NM_MANAGER_ERROR_PERMISSION_DENIED,
+			                     "%s request failed: not authorized",
+			                     permission);
+		}
 
-	nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES));
+		nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES));
+	}
 
 	callback (device,
 	          context,
@@ -2411,28 +2420,53 @@ device_auth_done_cb (NMAuthChain *chain,
 }
 
 static void
-device_auth_request_cb (NMDevice *device,
-                        GDBusMethodInvocation *context,
-                        NMConnection *connection,
-                        const char *permission,
-                        gboolean allow_interaction,
-                        NMDeviceAuthRequestFunc callback,
-                        gpointer user_data,
-                        NMManager *self)
+_device_auth_done_fail_on_idle (gpointer user_data, GCancellable *cancellable)
+{
+	gs_unref_object NMManager *self = NULL;
+	gs_unref_object NMDevice *device = NULL;
+	gs_unref_object GDBusMethodInvocation *context = NULL;
+	gs_unref_object NMAuthSubject *subject = NULL;
+	gs_free_error GError *error_original = NULL;
+	gs_free_error GError *error_cancelled = NULL;
+	NMManagerDeviceAuthRequestFunc callback;
+	gpointer callback_user_data;
+
+	nm_utils_user_data_unpack (&self, &device, &context, &subject, &error_original, &callback, &callback_user_data);
+
+	g_cancellable_set_error_if_cancelled (cancellable, &error_cancelled);
+
+	callback (device,
+	          context,
+	          subject,
+	          error_cancelled ?: error_original,
+	          callback_user_data);
+}
+
+void
+nm_manager_device_auth_request (NMManager *self,
+                                NMDevice *device,
+                                GDBusMethodInvocation *context,
+                                NMConnection *connection,
+                                const char *permission,
+                                gboolean allow_interaction,
+                                GCancellable *cancellable,
+                                NMManagerDeviceAuthRequestFunc callback,
+                                gpointer user_data)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GError *error = NULL;
-	NMAuthSubject *subject = NULL;
+	gs_free_error GError *error = NULL;
+	gs_unref_object NMAuthSubject *subject = NULL;
 	NMAuthChain *chain;
 	char *permission_dup;
 
 	/* Validate the caller */
 	subject = nm_dbus_manager_new_auth_subject_from_context (context);
 	if (!subject) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
-		goto done;
+		g_set_error_literal (&error,
+		                     NM_MANAGER_ERROR,
+		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
+		                     NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
+		goto fail_on_idle;
 	}
 
 	/* Ensure the subject has permissions for this connection */
@@ -2442,17 +2476,21 @@ device_auth_request_cb (NMDevice *device,
 	                                             NM_MANAGER_ERROR,
 	                                             NM_MANAGER_ERROR_PERMISSION_DENIED,
 	                                             &error))
-		goto done;
+		goto fail_on_idle;
 
 	/* Validate the request */
 	chain = nm_auth_chain_new_subject (subject, context, device_auth_done_cb, self);
 	if (!chain) {
-		error = g_error_new_literal (NM_MANAGER_ERROR,
-		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
-		goto done;
+		g_set_error (&error,
+		             NM_MANAGER_ERROR,
+		             NM_MANAGER_ERROR_PERMISSION_DENIED,
+		             NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED);
+		goto fail_on_idle;
 	}
 
+	if (cancellable)
+		nm_auth_chain_set_cancellable (chain, cancellable);
+
 	permission_dup = g_strdup (permission);
 
 	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
@@ -2461,13 +2499,18 @@ device_auth_request_cb (NMDevice *device,
 	nm_auth_chain_set_data (chain, "user-data", user_data, NULL);
 	nm_auth_chain_set_data (chain, "perm", permission_dup /* transfer ownership */, g_free);
 	nm_auth_chain_add_call_unsafe (chain, permission_dup, allow_interaction);
+	return;
 
-done:
-	if (error)
-		callback (device, context, subject, error, user_data);
-
-	g_clear_object (&subject);
-	g_clear_error (&error);
+fail_on_idle:
+	nm_utils_invoke_on_idle (cancellable,
+	                         _device_auth_done_fail_on_idle,
+	                         nm_utils_user_data_pack (g_object_ref (self),
+	                                                  g_object_ref (device),
+	                                                  g_object_ref (context),
+	                                                  g_steal_pointer (&subject),
+	                                                  g_steal_pointer (&error),
+	                                                  callback,
+	                                                  user_data));
 }
 
 static gboolean
@@ -3130,10 +3173,6 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 	                  G_CALLBACK (manager_device_state_changed),
 	                  self);
 
-	g_signal_connect (device, NM_DEVICE_AUTH_REQUEST,
-	                  G_CALLBACK (device_auth_request_cb),
-	                  self);
-
 	g_signal_connect (device, NM_DEVICE_REMOVED,
 	                  G_CALLBACK (device_removed_cb),
 	                  self);
diff --git a/src/nm-manager.h b/src/nm-manager.h
index 5873abd2..ab08eaa8 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -193,4 +193,16 @@ NMMetered nm_manager_get_metered (NMManager *self);
 
 void nm_manager_notify_device_availibility_maybe_changed (NMManager *self);
 
+/*****************************************************************************/
+
+void nm_manager_device_auth_request (NMManager *self,
+                                     NMDevice *device,
+                                     GDBusMethodInvocation *context,
+                                     NMConnection *connection,
+                                     const char *permission,
+                                     gboolean allow_interaction,
+                                     GCancellable *cancellable,
+                                     NMManagerDeviceAuthRequestFunc callback,
+                                     gpointer user_data);
+
 #endif /* __NETWORKMANAGER_MANAGER_H__ */
diff --git a/src/nm-types.h b/src/nm-types.h
index 4db9a19b..d2c999fa 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -40,6 +40,12 @@ typedef struct _NMSleepMonitor       NMSleepMonitor;
 typedef struct _NMLldpListener       NMLldpListener;
 typedef struct _NMConfigDeviceStateData NMConfigDeviceStateData;
 
+typedef void (*NMManagerDeviceAuthRequestFunc) (NMDevice *device,
+                                                GDBusMethodInvocation *context,
+                                                NMAuthSubject *subject,
+                                                GError *error,
+                                                gpointer user_data);
+
 struct _NMDedupMultiIndex;
 
 typedef struct _NMRefString NMRefString;
@@ -233,7 +239,9 @@ typedef enum {
  * NMIPConfigMergeFlags:
  * @NM_IP_CONFIG_MERGE_DEFAULT: no flags set
  * @NM_IP_CONFIG_MERGE_NO_ROUTES: don't merge routes
- * @NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES: don't merge default routes
+ * @NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES: don't merge default routes.
+ *   Note that if the source IP config has NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES
+ *   set, this flag gets ignored during merge.
  * @NM_IP_CONFIG_MERGE_NO_DNS: don't merge DNS information
  * @NM_IP_CONFIG_MERGE_EXTERNAL: mark new addresses as external
  */
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index f7249bb7..014cca71 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -5131,9 +5131,9 @@ sysctl_set_async (NMPlatform *platform,
 			                                  callback,
 			                                  data,
 			                                  error);
-			nm_utils_invoke_on_idle (sysctl_set_async_return_idle,
-			                         packed,
-			                         cancellable);
+			nm_utils_invoke_on_idle (cancellable,
+			                         sysctl_set_async_return_idle,
+			                         packed);
 			return;
 		}
 	} else
@@ -7390,7 +7390,7 @@ out_idle:
 		                                  g_steal_pointer (&error),
 		                                  callback,
 		                                  data);
-		nm_utils_invoke_on_idle (sriov_idle_cb, packed, cancellable);
+		nm_utils_invoke_on_idle (cancellable, sriov_idle_cb, packed);
 	}
 }
 
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 541211cd..a6aa5e22 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -1405,15 +1405,15 @@ nmp_object_id_cmp (const NMPObject *obj1, const NMPObject *obj2)
 		nm_assert (klass2);
 		NM_CMP_DIRECT (klass->obj_type, klass2->obj_type);
 		/* resort to pointer comparison */
-		if (klass < klass2)
-			return -1;
-		return 1;
+		NM_CMP_DIRECT_PTR (klass, klass2);
+		return 0;
 	}
 
 	if (!klass->cmd_plobj_id_cmp) {
 		/* the klass doesn't implement ID cmp(). That means, different objects
 		 * never compare equal, but the cmp() according to their pointer value. */
-		return (obj1 < obj2) ? -1 : 1;
+		NM_CMP_DIRECT_PTR (obj1, obj2);
+		return 0;
 	}
 
 	return klass->cmd_plobj_id_cmp (&obj1->object, &obj2->object);
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 2661914b..ea4a2c5a 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -3050,18 +3050,29 @@ test_sysctl_netns_switch (void)
 	nmtstp_link_delete (PL, FALSE, ifindex, NULL, TRUE);
 }
 
-static void
-sysctl_set_async_cb_assert_success (GError *error, gpointer data)
-{
-	g_assert_no_error (error);
-	g_main_loop_quit (data);
-}
+typedef struct {
+	GMainLoop *loop;
+	const char *path;
+	gboolean expected_success;
+	gint32 expected_value;
+} SetAsyncData;
 
 static void
-sysctl_set_async_cb_assert_failure (GError *error, gpointer data)
+sysctl_set_async_cb (GError *error, gpointer user_data)
 {
-	g_assert (error);
-	g_main_loop_quit (data);
+	SetAsyncData *data = user_data;
+
+	if (data->expected_success) {
+		g_assert_no_error (error);
+		g_assert_cmpint (nm_platform_sysctl_get_int32 (NM_PLATFORM_GET,
+		                                               NMP_SYSCTL_PATHID_ABSOLUTE (data->path),
+		                                               -1),
+		                 ==,
+		                 data->expected_value);
+	} else
+		g_assert (error);
+
+	g_main_loop_quit (data->loop);
 }
 
 static void
@@ -3070,43 +3081,53 @@ test_sysctl_set_async (void)
 	NMPlatform *const PL = NM_PLATFORM_GET;
 	const char *const IFNAME = "nm-dummy-0";
 	const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/rp_filter";
-	gs_free GMainLoop *loop = NULL;
+	GMainLoop *loop;
 	gs_unref_object GCancellable *cancellable = NULL;
+	gboolean proc_writable;
+	SetAsyncData data;
 	int ifindex;
 
 	ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex;
 	loop = g_main_loop_new (NULL, FALSE);
 	cancellable = g_cancellable_new ();
+	proc_writable = access (PATH, W_OK) == 0;
+
+	data = (SetAsyncData) {
+		.loop = loop,
+		.path = PATH,
+		.expected_success = proc_writable,
+		.expected_value = 2,
+	};
 
 	nm_platform_sysctl_set_async (PL,
 	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
 	                              (const char *[]) { "2", NULL},
-	                              sysctl_set_async_cb_assert_success,
-	                              loop,
+	                              sysctl_set_async_cb,
+	                              &data,
 	                              cancellable);
 
 	if (!nmtst_main_loop_run (loop, 1000))
 		g_assert_not_reached ();
 
-	g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1),
-	                 ==,
-	                 2);
+	data = (SetAsyncData) {
+		.loop = loop,
+		.path = PATH,
+		.expected_success = proc_writable,
+		.expected_value = 1,
+	};
 
 	nm_platform_sysctl_set_async (PL,
 	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
 	                              (const char *[]) { "2", "0", "1", "0", "1", NULL},
-	                              sysctl_set_async_cb_assert_success,
-	                              loop,
+	                              sysctl_set_async_cb,
+	                              &data,
 	                              cancellable);
 
 	if (!nmtst_main_loop_run (loop, 2000))
 		g_assert_not_reached ();
 
-	g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1),
-	                 ==,
-	                 1);
-
 	nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE);
+	g_main_loop_unref (loop);
 }
 
 static void
@@ -3115,25 +3136,33 @@ test_sysctl_set_async_fail (void)
 	NMPlatform *const PL = NM_PLATFORM_GET;
 	const char *const IFNAME = "nm-dummy-0";
 	const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/does-not-exist";
-	gs_free GMainLoop *loop = NULL;
+	GMainLoop *loop;
 	gs_unref_object GCancellable *cancellable = NULL;
+	SetAsyncData data;
 	int ifindex;
 
 	ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex;
 	loop = g_main_loop_new (NULL, FALSE);
 	cancellable = g_cancellable_new ();
 
+	data = (SetAsyncData) {
+		.loop = loop,
+		.path = PATH,
+		.expected_success = FALSE,
+	};
+
 	nm_platform_sysctl_set_async (PL,
 	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
 	                              (const char *[]) { "2", NULL},
-	                              sysctl_set_async_cb_assert_failure,
-	                              loop,
+	                              sysctl_set_async_cb,
+	                              &data,
 	                              cancellable);
 
 	if (!nmtst_main_loop_run (loop, 1000))
 		g_assert_not_reached ();
 
 	nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE);
+	g_main_loop_unref (loop);
 }
 
 /*****************************************************************************/
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 8e153412..7adafb58 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -431,8 +431,8 @@ make_connection_setting (const char *file,
 	              (int) svGetValueInt64 (ifcfg, "AUTOCONNECT_RETRIES", 10,
 	                                      -1, G_MAXINT32, -1),
 	              NM_SETTING_CONNECTION_MULTI_CONNECT,
-	              (gint) svGetValueInt64 (ifcfg, "MULTI_CONNECT", 10,
-	                                      G_MININT32, G_MAXINT32, NM_CONNECTION_MULTI_CONNECT_DEFAULT),
+	              (int) svGetValueInt64 (ifcfg, "MULTI_CONNECT", 10,
+	                                     G_MININT32, G_MAXINT32, NM_CONNECTION_MULTI_CONNECT_DEFAULT),
 	              NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
 	              svGetValueBoolean (ifcfg, "AUTOCONNECT_SLAVES", NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT),
 	              NM_SETTING_CONNECTION_LLDP, lldp,
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 28f57f50..d3243617 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -910,6 +910,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
 	_KEY_TYPE ("PEERROUTES",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("PHYSDEV",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("PKEY",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PKEY_ID",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("PMF",                                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("PORTNAME",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
 	_KEY_TYPE ("POWERSAVE",                                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 8941fc19..056d03fa 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
 	NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[231];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[232];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info (const char *key, gssize *out_idx);
 
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index f2a1dd78..cd63661d 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -1492,6 +1492,13 @@ svWriteFile (shvarFile *s, int mode, GError **error)
 			return FALSE;
 		}
 		f = fdopen (tmpfd, "w");
+		if (!f) {
+			errsv = errno;
+			g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
+			             "Internal error writing file '%s': %s",
+			             s->fileName, nm_strerror_native (errsv));
+			return FALSE;
+		}
 		fseek (f, 0, SEEK_SET);
 		c_list_for_each (current, &s->lst_head) {
 			const shvarLine *line = c_list_entry (current, shvarLine, lst);
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index cc3d109e..1c7b9a42 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -154,6 +154,9 @@ typedef struct _NMSupplicantInterfacePrivate {
 	bool           is_ready_main:1;
 	bool           is_ready_p2p_device:1;
 
+	bool           prop_scan_active:1;
+	bool           prop_scan_ssid:1;
+
 } NMSupplicantInterfacePrivate;
 
 struct _NMSupplicantInterfaceClass {
@@ -1123,7 +1126,7 @@ set_state (NMSupplicantInterface *self, NMSupplicantInterfaceState new_state)
 	if (new_state == priv->state)
 		return;
 
-	_LOGT ("set state \"%s\" (was \"%s\")",
+	_LOGT ("state: set state \"%s\" (was \"%s\")",
 	       nm_supplicant_interface_state_to_string (new_state),
 	       nm_supplicant_interface_state_to_string (priv->state));
 
@@ -1167,10 +1170,11 @@ static void
 parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	gboolean have_active = FALSE;
-	gboolean have_ssid = FALSE;
+	const gboolean old_prop_scan_active = priv->prop_scan_active;
+	const gboolean old_prop_scan_ssid = priv->prop_scan_ssid;
+	const guint32 old_max_scan_ssids = priv->max_scan_ssids;
 	gboolean have_ft = FALSE;
-	gint32 max_scan_ssids = -1;
+	gint32 max_scan_ssids;
 	const char **array;
 
 	nm_assert (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
@@ -1195,23 +1199,37 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 	}
 
 	if (g_variant_lookup (capabilities, "Scan", "^a&s", &array)) {
-		if (g_strv_contains (array, "active"))
-			have_active = TRUE;
-		if (g_strv_contains (array, "ssid"))
-			have_ssid = TRUE;
+		const char **a;
+
+		priv->prop_scan_active = FALSE;
+		priv->prop_scan_ssid = FALSE;
+		for (a = array; *a; a++) {
+			if (nm_streq (*a, "active"))
+				priv->prop_scan_active = TRUE;
+			else if (nm_streq (*a, "ssid"))
+				priv->prop_scan_ssid = TRUE;
+		}
 		g_free (array);
 	}
 
 	if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) {
-		/* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */
-		if (max_scan_ssids > 0 && have_active && have_ssid) {
-			/* wpa_supplicant's NM_WPAS_MAX_SCAN_SSIDS value is 16, but for speed
-			 * and to ensure we don't disclose too many SSIDs from the hidden
-			 * list, we'll limit to 5.
-			 */
-			priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, 5);
-			_LOGD ("supports %d scan SSIDs", priv->max_scan_ssids);
-		}
+		const gint32 WPAS_MAX_SCAN_SSIDS = 16;
+
+		/* Even if supplicant claims that 20 SSIDs are supported, the Scan request
+		 * still only accepts WPAS_MAX_SCAN_SSIDS SSIDs. Otherwise the D-Bus
+		 * request will be rejected with "fi.w1.wpa_supplicant1.InvalidArgs"
+		 * Body: ('Did not receive correct message arguments.', 'Too many ssids specified. Specify at most four')
+		 * */
+		priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, WPAS_MAX_SCAN_SSIDS);
+	}
+
+	if (   old_max_scan_ssids != priv->max_scan_ssids
+	    || old_prop_scan_active != priv->prop_scan_active
+	    || old_prop_scan_ssid != priv->prop_scan_ssid) {
+		_LOGD ("supports %u scan SSIDs (scan: %cactive %cssid)",
+		       (guint32) priv->max_scan_ssids,
+		       priv->prop_scan_active ? '+' : '-',
+		       priv->prop_scan_ssid ? '+' : '-');
 	}
 }
 
@@ -1789,8 +1807,12 @@ _properties_changed_main (NMSupplicantInterface *self,
 		g_variant_unref (v_v);
 	}
 
-	if (nm_g_variant_lookup (properties, "Scanning", "b", &v_b))
-		priv->scanning_property = v_b;
+	if (nm_g_variant_lookup (properties, "Scanning", "b", &v_b)) {
+		if (priv->scanning_property != (!!v_b)) {
+			_LOGT ("scanning: %s (plain property)", v_b ? "yes" : "no");
+			priv->scanning_property = v_b;
+		}
+	}
 
 	if (nm_g_variant_lookup (properties, "Ifname", "&s", &v_s)) {
 		if (nm_utils_strdup_reset (&priv->ifname, v_s))
@@ -1816,8 +1838,13 @@ _properties_changed_main (NMSupplicantInterface *self,
 
 		state = wpas_state_string_to_enum (v_s);
 		if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID)
-			_LOGT ("ignore unknown supplicant state '%s'", v_s);
+			_LOGT ("state: ignore unknown supplicant state '%s' (is %s, plain property)",
+			       v_s,
+			       nm_supplicant_interface_state_to_string (priv->supp_state));
 		else if (priv->supp_state != state) {
+			_LOGT ("state: %s (was %s, plain property)",
+			       nm_supplicant_interface_state_to_string (state),
+			       nm_supplicant_interface_state_to_string (priv->supp_state));
 			priv->supp_state = state;
 			if (priv->state > NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
 				/* Only transition to actual wpa_supplicant interface states (ie,
@@ -2328,40 +2355,82 @@ nm_supplicant_interface_assoc (NMSupplicantInterface *self,
 
 /*****************************************************************************/
 
+typedef struct {
+	NMSupplicantInterface *self;
+	GCancellable *cancellable;
+	NMSupplicantInterfaceRequestScanCallback callback;
+	gpointer user_data;
+} ScanRequestData;
+
 static void
 scan_request_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
+	gs_unref_object NMSupplicantInterface *self_keep_alive = NULL;
 	NMSupplicantInterface *self;
 	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
+	ScanRequestData *data = user_data;
+	gboolean cancelled = FALSE;
 
 	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
-	if (nm_utils_error_is_cancelled (error))
-		return;
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	if (error) {
-		if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError"))
-			_LOGD ("request-scan: could not get scan request result: %s", error->message);
-		else {
-			g_dbus_error_strip_remote_error (error);
-			_LOGW ("request-scan: could not get scan request result: %s", error->message);
+	if (nm_utils_error_is_cancelled (error)) {
+		if (!data->callback) {
+			/* the self instance was not kept alive. We also must not touch it. Return. */
+			nm_g_object_unref (data->cancellable);
+			nm_g_slice_free (data);
+			return;
 		}
-	} else
-		_LOGT ("request-scan: request scanning success");
+		cancelled = TRUE;
+	}
+
+	self = data->self;
+	if (data->callback) {
+		/* the self instance was kept alive. Balance the reference count. */
+		self_keep_alive = self;
+	}
+
+	/* we don't propagate the error/success. That is, because either answer is not
+	 * reliable. What is important to us is whether the request completed, and
+	 * the current nm_supplicant_interface_get_scanning() state. */
+	if (cancelled)
+		_LOGD ("request-scan: request cancelled");
+	else {
+		if (error) {
+			if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError"))
+				_LOGD ("request-scan: could not get scan request result: %s", error->message);
+			else {
+				g_dbus_error_strip_remote_error (error);
+				_LOGW ("request-scan: could not get scan request result: %s", error->message);
+			}
+		} else
+			_LOGT ("request-scan: request scanning success");
+	}
+
+	if (data->callback)
+		data->callback (self, data->cancellable, data->user_data);
+
+	nm_g_object_unref (data->cancellable);
+	nm_g_slice_free (data);
 }
 
 void
 nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
                                       GBytes *const*ssids,
-                                      guint ssids_len)
+                                      guint ssids_len,
+                                      GCancellable *cancellable,
+                                      NMSupplicantInterfaceRequestScanCallback callback,
+                                      gpointer user_data)
 {
 	NMSupplicantInterfacePrivate *priv;
 	GVariantBuilder builder;
+	ScanRequestData *data;
 	guint i;
 
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 
+	nm_assert (   (!cancellable && !callback)
+	           || (G_IS_CANCELLABLE (cancellable) && callback));
+
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	_LOGT ("request-scan: request scanning (%u ssids)...", ssids_len);
@@ -2381,6 +2450,26 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
 		g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder));
 	}
 
+	data = g_slice_new (ScanRequestData);
+	*data = (ScanRequestData) {
+		.self            = self,
+		.callback        = callback,
+		.user_data       = user_data,
+		.cancellable     = nm_g_object_ref (cancellable),
+	};
+
+	if (callback) {
+		/* A callback was provided. This keeps @self alive. The caller
+		 * must provide a cancellable as the caller must never leave an asynchronous
+		 * operation pending indefinitely. */
+		nm_assert (G_IS_CANCELLABLE (cancellable));
+		g_object_ref (self);
+	} else {
+		/* We don't keep @self alive, and we don't accept a cancellable either. */
+		nm_assert (!cancellable);
+		cancellable = priv->main_cancellable;
+	}
+
 	_dbus_connection_call (self,
 	                       NM_WPAS_DBUS_IFACE_INTERFACE,
 	                       "Scan",
@@ -2388,9 +2477,9 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
 	                       G_VARIANT_TYPE ("()"),
 	                       G_DBUS_CALL_FLAGS_NONE,
 	                       DBUS_TIMEOUT_MSEC,
-	                       priv->main_cancellable,
+	                       cancellable,
 	                       scan_request_cb,
-	                       self);
+	                       data);
 }
 
 /*****************************************************************************/
@@ -2438,9 +2527,14 @@ nm_supplicant_interface_get_ifname (NMSupplicantInterface *self)
 guint
 nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self)
 {
+	NMSupplicantInterfacePrivate *priv;
+
 	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), 0);
 
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->max_scan_ssids;
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	return   priv->prop_scan_active && priv->prop_scan_ssid
+	       ? priv->max_scan_ssids
+	       : 0u;
 }
 
 /*****************************************************************************/
@@ -2504,7 +2598,7 @@ nm_supplicant_interface_p2p_connect (NMSupplicantInterface *self,
 	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
 	                              "Connect",
 	                              g_variant_new ("(a{sv})", &builder),
-	                              G_VARIANT_TYPE ("()"),
+	                              G_VARIANT_TYPE ("(s)"),
 	                              "p2p-connect");
 }
 
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index eb414f26..9ef19816 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -128,9 +128,16 @@ nm_supplicant_interface_disconnect_async (NMSupplicantInterface * self,
                                           NMSupplicantInterfaceDisconnectCb callback,
                                           gpointer user_data);
 
+typedef void (*NMSupplicantInterfaceRequestScanCallback) (NMSupplicantInterface *self,
+                                                          GCancellable *cancellable,
+                                                          gpointer user_data);
+
 void nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
                                            GBytes *const*ssids,
-                                           guint ssids_len);
+                                           guint ssids_len,
+                                           GCancellable *cancellable,
+                                           NMSupplicantInterfaceRequestScanCallback callback,
+                                           gpointer user_data);
 
 NMSupplicantInterfaceState nm_supplicant_interface_get_state (NMSupplicantInterface * self);
 
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
index cf5cf111..b1a2dea8 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -562,6 +562,9 @@ _create_iface_dbus_call_create_interface_cb (GObject *source,
 			       NM_HASH_OBFUSCATE_PTR (handle),
 			       iface_path_str,
 			       handle->name_owner->str);
+			nm_utils_error_set (&error,
+			                    NM_UTILS_ERROR_UNKNOWN,
+			                    "Request already cancelled");
 		} else {
 			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: name owner changed, still remove interface %s in %s",
 			       NM_HASH_OBFUSCATE_PTR (handle),
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index 35bf4afb..6d995dc4 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -1595,13 +1595,18 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 				route.plen = plen;
 				route.network = nm_utils_ip4_address_clear_host_address (route.network, plen);
 
-				/* Ignore host routes to the VPN gateway since NM adds one itself
-				 * below.  Since NM knows more about the routing situation than
-				 * the VPN server, we want to use the NM created route instead of
-				 * whatever the server provides.
-				 */
-				if (!(priv->ip4_external_gw && route.network == priv->ip4_external_gw && route.plen == 32))
-					nm_ip4_config_add_route (config, &route, NULL);
+				if (   priv->ip4_external_gw
+				    && route.network == priv->ip4_external_gw
+				    && route.plen == 32) {
+					/* Ignore host routes to the VPN gateway since NM adds one itself
+					 * below.  Since NM knows more about the routing situation than
+					 * the VPN server, we want to use the NM created route instead of
+					 * whatever the server provides.
+					 */
+					break;
+				}
+
+				nm_ip4_config_add_route (config, &route, NULL);
 				break;
 			default:
 				break;
@@ -1790,13 +1795,20 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 			route.metric = route_metric;
 			route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
 
-			/* Ignore host routes to the VPN gateway since NM adds one itself.
-			 * Since NM knows more about the routing situation than the VPN
-			 * server, we want to use the NM created route instead of whatever
-			 * the server provides.
-			 */
-			if (!(priv->ip6_external_gw && IN6_ARE_ADDR_EQUAL (&route.network, priv->ip6_external_gw) && route.plen == 128))
-				nm_ip6_config_add_route (config, &route, NULL);
+			nm_utils_ip6_address_clear_host_address (&route.network, &route.network, route.plen);
+
+			if (   priv->ip6_external_gw
+			    && IN6_ARE_ADDR_EQUAL (&route.network, priv->ip6_external_gw)
+			    && route.plen == 128) {
+				/* Ignore host routes to the VPN gateway since NM adds one itself.
+				 * Since NM knows more about the routing situation than the VPN
+				 * server, we want to use the NM created route instead of whatever
+				 * the server provides.
+				 */
+				goto next;
+			}
+
+			nm_ip6_config_add_route (config, &route, NULL);
 
 next:
 			g_variant_unref (dest);