summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/adsl/meson.build2
-rw-r--r--src/devices/adsl/nm-atm-manager.c2
-rw-r--r--src/devices/adsl/nm-device-adsl.c117
-rw-r--r--src/devices/bluetooth/meson.build2
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c59
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c118
-rw-r--r--src/devices/bluetooth/nm-device-bt.c16
-rw-r--r--src/devices/meson.build2
-rw-r--r--src/devices/nm-acd-manager.c50
-rw-r--r--src/devices/nm-device-6lowpan.c2
-rw-r--r--src/devices/nm-device-bond.c192
-rw-r--r--src/devices/nm-device-bridge.c392
-rw-r--r--src/devices/nm-device-ethernet.c362
-rw-r--r--src/devices/nm-device-factory.c5
-rw-r--r--src/devices/nm-device-generic.c10
-rw-r--r--src/devices/nm-device-infiniband.c12
-rw-r--r--src/devices/nm-device-ip-tunnel.c191
-rw-r--r--src/devices/nm-device-macsec.c332
-rw-r--r--src/devices/nm-device-macvlan.c10
-rw-r--r--src/devices/nm-device-ppp.c21
-rw-r--r--src/devices/nm-device-private.h19
-rw-r--r--src/devices/nm-device-tun.c2
-rw-r--r--src/devices/nm-device-vlan.c6
-rw-r--r--src/devices/nm-device-vrf.c371
-rw-r--r--src/devices/nm-device-vrf.h22
-rw-r--r--src/devices/nm-device-vxlan.c16
-rw-r--r--src/devices/nm-device-wireguard.c53
-rw-r--r--src/devices/nm-device.c1371
-rw-r--r--src/devices/nm-device.h7
-rw-r--r--src/devices/nm-lldp-listener.c16
-rw-r--r--src/devices/ovs/meson.build2
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c39
-rw-r--r--src/devices/ovs/nm-ovsdb.c172
-rw-r--r--src/devices/ovs/nm-ovsdb.h3
-rw-r--r--src/devices/team/meson.build2
-rw-r--r--src/devices/team/nm-device-team.c4
-rw-r--r--src/devices/tests/meson.build2
-rw-r--r--src/devices/tests/test-acd.c2
-rw-r--r--src/devices/tests/test-lldp.c2
-rw-r--r--src/devices/wifi/meson.build46
-rw-r--r--src/devices/wifi/nm-device-iwd.c248
-rw-r--r--src/devices/wifi/nm-device-iwd.h2
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c53
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.c187
-rw-r--r--src/devices/wifi/nm-device-wifi.c782
-rw-r--r--src/devices/wifi/nm-device-wifi.h9
-rw-r--r--src/devices/wifi/nm-iwd-manager.c3
-rw-r--r--src/devices/wifi/nm-wifi-ap.c651
-rw-r--r--src/devices/wifi/nm-wifi-ap.h26
-rw-r--r--src/devices/wifi/nm-wifi-factory.c4
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.c221
-rw-r--r--src/devices/wifi/nm-wifi-p2p-peer.h11
-rw-r--r--src/devices/wifi/nm-wifi-utils.c25
-rw-r--r--src/devices/wifi/nm-wifi-utils.h2
-rw-r--r--src/devices/wifi/tests/test-devices-wifi.c57
-rw-r--r--src/devices/wwan/meson.build2
-rw-r--r--src/devices/wwan/nm-device-modem.c20
-rw-r--r--src/devices/wwan/nm-modem-broadband.c2
-rw-r--r--src/devices/wwan/nm-modem-ofono.c4
-rw-r--r--src/devices/wwan/nm-modem.c11
-rw-r--r--src/devices/wwan/nm-service-providers.c30
61 files changed, 3421 insertions, 2983 deletions
diff --git a/src/devices/adsl/meson.build b/src/devices/adsl/meson.build
index a5d1c4b4..79834d7f 100644
--- a/src/devices/adsl/meson.build
+++ b/src/devices/adsl/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 sources = files(
   'nm-atm-manager.c',
   'nm-device-adsl.c',
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index 487011ff..46d74a07 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -248,7 +248,7 @@ dispose (GObject *object)
 
 	for (iter = priv->devices; iter; iter = iter->next)
 		g_object_weak_unref (G_OBJECT (iter->data), device_destroyed, self);
-	g_clear_pointer (&priv->devices, g_slist_free);
+	nm_clear_pointer (&priv->devices, g_slist_free);
 
 	priv->udev_client = nm_udev_client_unref (priv->udev_client);
 
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index 7c2b3e20..8f7cba22 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -58,7 +58,7 @@ struct _NMDeviceAdslClass {
 
 G_DEFINE_TYPE (NMDeviceAdsl, nm_device_adsl, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_ADSL_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceAdsl, NM_IS_DEVICE_ADSL)
+#define NM_DEVICE_ADSL_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceAdsl, NM_IS_DEVICE_ADSL, NMDevice)
 
 /*****************************************************************************/
 
@@ -228,7 +228,8 @@ link_changed_cb (NMPlatform *platform,
 
 		/* This only gets called for PPPoE connections and "nas" interfaces */
 
-		if (priv->nas_ifindex > 0 && ifindex == priv->nas_ifindex) {
+		if (   priv->nas_ifindex > 0
+		    && ifindex == priv->nas_ifindex) {
 			/* NAS device went away for some reason; kill the connection */
 			_LOGD (LOGD_ADSL, "br2684 interface disappeared");
 			nm_device_state_changed (device,
@@ -273,11 +274,17 @@ nas_update_cb (gpointer user_data)
 	NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
 
-	g_assert (priv->nas_ifname);
+	nm_assert (priv->nas_ifname);
 
 	priv->nas_update_count++;
 
-	if (priv->nas_update_count > 10) {
+	nm_assert (priv->nas_ifindex <= 0);
+	priv->nas_ifindex = nm_platform_link_get_ifindex (nm_device_get_platform (device), priv->nas_ifname);
+	if (priv->nas_ifindex <= 0) {
+		if (priv->nas_update_count <= 10) {
+			/* Keep waiting for it to appear */
+			return G_SOURCE_CONTINUE;
+		}
 		priv->nas_update_id = 0;
 		_LOGW (LOGD_ADSL, "failed to find br2684 interface %s ifindex after timeout", priv->nas_ifname);
 		nm_device_state_changed (device,
@@ -286,31 +293,22 @@ nas_update_cb (gpointer user_data)
 		return G_SOURCE_REMOVE;
 	}
 
-	g_warn_if_fail (priv->nas_ifindex < 0);
-	priv->nas_ifindex = nm_platform_link_get_ifindex (nm_device_get_platform (device), priv->nas_ifname);
-	if (priv->nas_ifindex < 0) {
-		/* Keep waiting for it to appear */
-		return G_SOURCE_CONTINUE;
-	}
-
 	priv->nas_update_id = 0;
 	_LOGD (LOGD_ADSL, "using br2684 iface '%s' index %d", priv->nas_ifname, priv->nas_ifindex);
 
-	if (pppoe_vcc_config (self)) {
-		nm_device_activate_schedule_stage3_ip_config_start (device);
-	} else {
+	if (!pppoe_vcc_config (self)) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_BR2684_FAILED);
+		return G_SOURCE_REMOVE;
 	}
 
+	nm_device_activate_schedule_stage2_device_config (device, TRUE);
 	return G_SOURCE_REMOVE;
 }
 
-static NMActStageReturn
-br2684_create_iface (NMDeviceAdsl *self,
-                     NMSettingAdsl *s_adsl,
-                     NMDeviceStateReason *out_failure_reason)
+static gboolean
+br2684_create_iface (NMDeviceAdsl *self)
 {
 	NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self);
 	struct atm_newif_br2684 ni;
@@ -318,19 +316,14 @@ br2684_create_iface (NMDeviceAdsl *self,
 	int err, errsv;
 	guint num = 0;
 
-	g_return_val_if_fail (s_adsl != NULL, FALSE);
-
-	if (priv->nas_update_id) {
-		g_warn_if_fail (priv->nas_update_id == 0);
-		nm_clear_g_source (&priv->nas_update_id);
-	}
+	if (nm_clear_g_source (&priv->nas_update_id))
+		nm_assert_not_reached ();
 
 	fd = socket (PF_ATMPVC, SOCK_DGRAM | SOCK_CLOEXEC, ATM_AAL5);
 	if (fd < 0) {
 		errsv = errno;
 		_LOGE (LOGD_ADSL, "failed to open ATM control socket (%d)", errsv);
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_BR2684_FAILED);
-		return NM_ACT_STAGE_RETURN_FAILURE;
+		return FALSE;
 	}
 
 	memset (&ni, 0, sizeof (ni));
@@ -343,36 +336,33 @@ br2684_create_iface (NMDeviceAdsl *self,
 	 * cannot return that name to us.  Since we want to know the name right
 	 * away, just brute-force it.
 	 */
-	while (num < 10000) {
+	while (TRUE) {
 		memset (&ni.ifname, 0, sizeof (ni.ifname));
-		g_snprintf (ni.ifname, sizeof (ni.ifname), "nas%d", num++);
+		g_snprintf (ni.ifname, sizeof (ni.ifname), "nas%u", num++);
 
 		err = ioctl (fd, ATM_NEWBACKENDIF, &ni);
-		if (err == 0) {
-			g_free (priv->nas_ifname);
-			priv->nas_ifname = g_strdup (ni.ifname);
-			_LOGD (LOGD_ADSL, "waiting for br2684 iface '%s' to appear", priv->nas_ifname);
+		if (err != 0) {
+			errsv = errno;
+			if (errsv == EEXIST)
+				continue;
 
-			priv->nas_update_count = 0;
-			priv->nas_update_id = g_timeout_add (100, nas_update_cb, self);
-			return NM_ACT_STAGE_RETURN_POSTPONE;
-		}
-		errsv = errno;
-		if (errsv != EEXIST) {
 			_LOGW (LOGD_ADSL, "failed to create br2684 interface (%d)", errsv);
-			break;
+			return FALSE;
 		}
-	}
 
-	NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_BR2684_FAILED);
-	return NM_ACT_STAGE_RETURN_FAILURE;
+		nm_utils_strdup_reset (&priv->nas_ifname, ni.ifname);
+		_LOGD (LOGD_ADSL, "waiting for br2684 iface '%s' to appear", priv->nas_ifname);
+		priv->nas_update_count = 0;
+		priv->nas_update_id = g_timeout_add (100, nas_update_cb, self);
+		return TRUE;
+	}
 }
 
 static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceAdsl *self = NM_DEVICE_ADSL (device);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE (self);
 	NMSettingAdsl *s_adsl;
 	const char *protocol;
 
@@ -383,16 +373,27 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	protocol = nm_setting_adsl_get_protocol (s_adsl);
 	_LOGD (LOGD_ADSL, "using ADSL protocol '%s'", protocol);
 
-	if (g_strcmp0 (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE) == 0) {
-		/* PPPoE needs RFC2684 bridging before we can do PPP over it */
-		ret = br2684_create_iface (self, s_adsl, out_failure_reason);
-	} else if (g_strcmp0 (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA) == 0) {
+	if (nm_streq0 (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)) {
 		/* PPPoA doesn't need anything special */
-		ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	} else
-		_LOGW (LOGD_ADSL, "unhandled ADSL protocol '%s'", protocol);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (nm_streq0 (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
+		/* PPPoE needs RFC2684 bridging before we can do PPP over it */
+		if (priv->nas_ifindex <= 0) {
+			if (priv->nas_update_id == 0) {
+				if (!br2684_create_iface (self)) {
+					NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_BR2684_FAILED);
+					return NM_ACT_STAGE_RETURN_FAILURE;
+				}
+			}
+			return NM_ACT_STAGE_RETURN_POSTPONE;
+		}
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
 
-	return ret;
+	_LOGW (LOGD_ADSL, "unhandled ADSL protocol '%s'", protocol);
+	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static void
@@ -460,8 +461,8 @@ act_stage3_ip4_config_start (NMDevice *device,
 	g_return_val_if_fail (s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
 
 	/* PPPoE uses the NAS interface, not the ATM interface */
-	if (g_strcmp0 (nm_setting_adsl_get_protocol (s_adsl), NM_SETTING_ADSL_PROTOCOL_PPPOE) == 0) {
-		g_assert (priv->nas_ifname);
+	if (nm_streq0 (nm_setting_adsl_get_protocol (s_adsl), NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
+		nm_assert (priv->nas_ifname);
 		ppp_iface = priv->nas_ifname;
 
 		_LOGD (LOGD_ADSL, "starting PPPoE on br2684 interface %s", priv->nas_ifname);
@@ -540,8 +541,8 @@ adsl_cleanup (NMDeviceAdsl *self)
 	 * so it gets leaked.  It does get destroyed when it's no longer in use,
 	 * but we have no control over that.
 	 */
-	priv->nas_ifindex = -1;
-	g_clear_pointer (&priv->nas_ifname, g_free);
+	priv->nas_ifindex = 0;
+	nm_clear_g_free (&priv->nas_ifname);
 }
 
 static void
@@ -577,7 +578,7 @@ get_property (GObject *object, guint prop_id,
 {
 	switch (prop_id) {
 	case PROP_ATM_INDEX:
-		g_value_set_int (value, NM_DEVICE_ADSL_GET_PRIVATE ((NMDeviceAdsl *) object)->atm_index);
+		g_value_set_int (value, NM_DEVICE_ADSL_GET_PRIVATE (object)->atm_index);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -592,7 +593,7 @@ set_property (GObject *object, guint prop_id,
 	switch (prop_id) {
 	case PROP_ATM_INDEX:
 		/* construct-only */
-		NM_DEVICE_ADSL_GET_PRIVATE ((NMDeviceAdsl *) object)->atm_index = g_value_get_int (value);
+		NM_DEVICE_ADSL_GET_PRIVATE (object)->atm_index = g_value_get_int (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -646,7 +647,7 @@ dispose (GObject *object)
 {
 	adsl_cleanup (NM_DEVICE_ADSL (object));
 
-	nm_clear_g_source (&NM_DEVICE_ADSL_GET_PRIVATE ((NMDeviceAdsl *) object)->carrier_poll_id);
+	nm_clear_g_source (&NM_DEVICE_ADSL_GET_PRIVATE (object)->carrier_poll_id);
 
 	G_OBJECT_CLASS (nm_device_adsl_parent_class)->dispose (object);
 }
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
index f0507c23..638a01a2 100644
--- a/src/devices/bluetooth/meson.build
+++ b/src/devices/bluetooth/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 sources = files(
   'nm-bluez-manager.c',
   'nm-bt-error.c',
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index ef087d83..6ff96c32 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -221,7 +221,7 @@ convert_uuids_to_capabilities (const char *const*strv)
 				continue;
 
 			s_part1 = g_strndup (str, s - str);
-			switch (g_ascii_strtoull (s_part1, NULL, 16)) {
+			switch (_nm_utils_ascii_str_to_int64 (s_part1, 16, 0, G_MAXINT, -1)) {
 			case 0x1103:
 				capabilities |= NM_BT_CAPABILITY_DUN;
 				break;
@@ -1089,7 +1089,7 @@ _network_server_register_cb (GObject *source_object,
 
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	bzobj = user_data;
@@ -2071,9 +2071,12 @@ _dbus_handle_interface_removed (NMBluezManager *self,
 }
 
 static void
-_dbus_managed_objects_changed_cb (const char *object_path,
-                                  GVariant *added_interfaces_and_properties,
-                                  const char *const*removed_interfaces,
+_dbus_managed_objects_changed_cb (GDBusConnection *connection,
+                                  const char *sender_name,
+                                  const char *arg_object_path,
+                                  const char *interface_name,
+                                  const char *signal_name,
+                                  GVariant *parameters,
                                   gpointer user_data)
 {
 	NMBluezManager *self = user_data;
@@ -2081,17 +2084,46 @@ _dbus_managed_objects_changed_cb (const char *object_path,
 	BzDBusObj *bzobj = NULL;
 	gboolean changed;
 
+	nm_assert (nm_streq0 (interface_name, DBUS_INTERFACE_OBJECT_MANAGER));
+
 	if (priv->get_managed_objects_cancellable) {
 		/* we still wait for the initial GetManagedObjects(). Ignore the event. */
 		return;
 	}
 
-	if (!added_interfaces_and_properties) {
-		changed = _dbus_handle_interface_removed (self, object_path, &bzobj, removed_interfaces);
+	if (nm_streq (signal_name, "InterfacesAdded")) {
+		gs_unref_variant GVariant *interfaces_and_properties = NULL;
+		const char *object_path;
+
+		if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sa{sv}})")))
+			return;
+
+		g_variant_get (parameters,
+		               "(&o@a{sa{sv}})",
+		               &object_path,
+		               &interfaces_and_properties);
+
+		_dbus_handle_interface_added (self, object_path, interfaces_and_properties, FALSE);
+		return;
+	}
+
+	if (nm_streq (signal_name, "InterfacesRemoved")) {
+		gs_free const char **interfaces = NULL;
+		const char *object_path;
+
+		if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oas)")))
+			return;
+
+		g_variant_get (parameters,
+		               "(&o^a&s)",
+		               &object_path,
+		               &interfaces);
+
+		changed = _dbus_handle_interface_removed (self, object_path, &bzobj, interfaces);
 		if (changed)
 			_dbus_process_changes (self, bzobj, "dbus-iface-removed");
-	} else
-		_dbus_handle_interface_added (self, object_path, added_interfaces_and_properties, FALSE);
+		return;
+	}
 }
 
 static void
@@ -2140,7 +2172,7 @@ _dbus_get_managed_objects_cb (GVariant *result,
 	GVariant *ifaces;
 
 	if (   !result
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	self = user_data;
@@ -2234,6 +2266,7 @@ name_owner_changed (NMBluezManager *self,
 	priv->managed_objects_changed_id = nm_dbus_connection_signal_subscribe_object_manager (priv->dbus_connection,
 	                                                                                       priv->name_owner,
 	                                                                                       NM_BLUEZ_MANAGER_PATH,
+	                                                                                       NULL,
 	                                                                                       _dbus_managed_objects_changed_cb,
 	                                                                                       self,
 	                                                                                       NULL);
@@ -2442,7 +2475,7 @@ _connect_dun_step2_cb (NMBluez5DunContext *context,
 {
 	BzDBusObj *bzobj;
 
-	if (nm_utils_error_is_cancelled (error, FALSE))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	bzobj = user_data;
@@ -2484,7 +2517,7 @@ _connect_dun_step1_cb (GObject *source_object,
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
 
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	bzobj = user_data;
@@ -2531,7 +2564,7 @@ _connect_nap_cb (GObject *source_object,
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
 
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	if (ret)
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index af463d1a..c253d4e2 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -31,12 +31,12 @@ typedef struct {
 
 	GError *rfcomm_sdp_search_error;
 
+	GSource *source;
+
 	gint64 connect_open_tty_started_at;
 
 	gulong cancelled_id;
 
-	guint source_id;
-
 	guint8 sdp_session_try_count;
 } ConnectData;
 
@@ -50,13 +50,13 @@ struct _NMBluez5DunContext {
 
 	char *rfcomm_tty_path;
 
+	GSource *rfcomm_tty_poll_source;
+
 	int rfcomm_sock_fd;
 	int rfcomm_tty_fd;
 	int rfcomm_tty_no;
 	int rfcomm_channel;
 
-	guint rfcomm_tty_poll_id;
-
 	bdaddr_t src;
 	bdaddr_t dst;
 
@@ -118,7 +118,7 @@ nm_bluez5_dun_context_get_rfcomm_dev (const NMBluez5DunContext *context)
 /*****************************************************************************/
 
 static gboolean
-_rfcomm_tty_poll_cb (GIOChannel *stream,
+_rfcomm_tty_poll_cb (int fd,
                      GIOCondition condition,
                      gpointer user_data)
 {
@@ -129,7 +129,7 @@ _rfcomm_tty_poll_cb (GIOChannel *stream,
 	       NM_FLAGS_ALL (condition, G_IO_HUP | G_IO_ERR) ? ","   : "",
 	       NM_FLAGS_HAS (condition, G_IO_HUP)            ? "HUP" : "");
 
-	context->rfcomm_tty_poll_id = 0;
+	nm_clear_g_source_inst (&context->rfcomm_tty_poll_source);
 	context->notify_tty_hangup_cb (context,
 	                               context->notify_tty_hangup_user_data);
 	return G_SOURCE_REMOVE;
@@ -145,10 +145,10 @@ _connect_open_tty_retry_cb (gpointer user_data)
 	if (r >= 0)
 		return G_SOURCE_REMOVE;
 
-	if (nm_utils_get_monotonic_timestamp_ns () > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NS_PER_MSEC)) {
+	if (nm_utils_get_monotonic_timestamp_nsec () > context->cdat->connect_open_tty_started_at + (30 * 100 * NM_UTILS_NSEC_PER_MSEC)) {
 		gs_free_error GError *error = NULL;
 
-		context->cdat->source_id = 0;
+		nm_clear_g_source_inst (&context->cdat->source);
 		g_set_error (&error,
 		             NM_BT_ERROR,
 		             NM_BT_ERROR_DUN_CONNECT_FAILED,
@@ -166,7 +166,6 @@ _connect_open_tty_retry_cb (gpointer user_data)
 static int
 _connect_open_tty (NMBluez5DunContext *context)
 {
-	nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
 	int fd;
 	int errsv;
 
@@ -174,26 +173,31 @@ _connect_open_tty (NMBluez5DunContext *context)
 	if (fd < 0) {
 		errsv = NM_ERRNO_NATIVE (errno);
 
-		if (context->cdat->source_id == 0) {
+		if (!context->cdat->source) {
 			_LOGD (context, "failed opening tty "RFCOMM_FMT": %s (%d). Start polling...",
 			       context->rfcomm_tty_no,
 			       nm_strerror_native (errsv),
 			       errsv);
-			context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_ns ();
-			context->cdat->source_id = g_timeout_add (100,
-			                                          _connect_open_tty_retry_cb,
-			                                          context);
+			context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_nsec ();
+			context->cdat->source = nm_g_timeout_source_new (100,
+			                                                 G_PRIORITY_DEFAULT,
+			                                                 _connect_open_tty_retry_cb,
+			                                                 context,
+			                                                 NULL);
+			g_source_attach (context->cdat->source, NULL);
 		}
 		return -errsv;
 	}
 
 	context->rfcomm_tty_fd = fd;
 
-	io_channel = g_io_channel_unix_new (context->rfcomm_tty_fd);
-	context->rfcomm_tty_poll_id = g_io_add_watch (io_channel,
-	                                              G_IO_ERR | G_IO_HUP,
-	                                              _rfcomm_tty_poll_cb,
-	                                              context);
+	context->rfcomm_tty_poll_source = nm_g_unix_fd_source_new (context->rfcomm_tty_fd,
+	                                                           G_IO_ERR | G_IO_HUP,
+	                                                           G_PRIORITY_DEFAULT,
+	                                                           _rfcomm_tty_poll_cb,
+	                                                           context,
+	                                                           NULL);
+	g_source_attach (context->rfcomm_tty_poll_source, NULL);
 
 	_context_invoke_callback_success (context);
 	return 0;
@@ -263,7 +267,7 @@ _connect_create_rfcomm (NMBluez5DunContext *context)
 }
 
 static gboolean
-_connect_socket_connect_cb (GIOChannel *stream,
+_connect_socket_connect_cb (int fd,
                             GIOCondition condition,
                             gpointer user_data)
 {
@@ -273,7 +277,7 @@ _connect_socket_connect_cb (GIOChannel *stream,
 	socklen_t slen = sizeof(errsv);
 	int r;
 
-	context->cdat->source_id = 0;
+	nm_clear_g_source_inst (&context->cdat->source);
 
 	r = getsockopt (context->rfcomm_sock_fd, SOL_SOCKET, SO_ERROR, &errsv, &slen);
 
@@ -346,7 +350,6 @@ _connect_socket_connect (NMBluez5DunContext *context)
 	if (connect (context->rfcomm_sock_fd,
 	             (struct sockaddr *) &sa,
 	             sizeof (sa)) != 0) {
-		nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
 
 		errsv = errno;
 		if (errsv != EINPROGRESS) {
@@ -363,11 +366,13 @@ _connect_socket_connect (NMBluez5DunContext *context)
 		       context->dst_str,
 		       context->rfcomm_channel);
 
-		io_channel = g_io_channel_unix_new (context->rfcomm_sock_fd);
-		context->cdat->source_id = g_io_add_watch (io_channel,
-		                                           G_IO_OUT,
-		                                           _connect_socket_connect_cb,
-		                                           context);
+		context->cdat->source = nm_g_unix_fd_source_new (context->rfcomm_sock_fd,
+		                                                 G_IO_OUT,
+		                                                 G_PRIORITY_DEFAULT,
+		                                                 _connect_socket_connect_cb,
+		                                                 context,
+		                                                 NULL);
+		g_source_attach (context->cdat->source, NULL);
 		return;
 	}
 
@@ -466,7 +471,7 @@ _connect_sdp_search_cb (uint8_t type,
 }
 
 static gboolean
-_connect_sdp_search_io_cb (GIOChannel *io_channel,
+_connect_sdp_search_io_cb (int fd,
                            GIOCondition condition,
                            gpointer user_data)
 {
@@ -480,7 +485,7 @@ _connect_sdp_search_io_cb (GIOChannel *io_channel,
 		error = g_error_new (NM_BT_ERROR,
 		                     NM_BT_ERROR_DUN_CONNECT_FAILED,
 		                     "Service Discovery interrupted");
-		context->cdat->source_id = 0;
+		nm_clear_g_source_inst (&context->cdat->source);
 		_context_invoke_callback_fail_and_free (context, error);
 		return G_SOURCE_REMOVE;
 	}
@@ -490,7 +495,7 @@ _connect_sdp_search_io_cb (GIOChannel *io_channel,
 		return G_SOURCE_CONTINUE;
 	}
 
-	context->cdat->source_id = 0;
+	nm_clear_g_source_inst (&context->cdat->source);
 
 	if (   context->rfcomm_channel < 0
 	    && !context->cdat->rfcomm_sdp_search_error) {
@@ -524,7 +529,7 @@ _connect_sdp_session_start_on_idle_cb (gpointer user_data)
 	NMBluez5DunContext *context = user_data;
 	gs_free_error GError *error = NULL;
 
-	context->cdat->source_id = 0;
+	nm_clear_g_source_inst (&context->cdat->source);
 
 	_LOGD (context, "retry starting sdp-session...");
 
@@ -535,7 +540,7 @@ _connect_sdp_session_start_on_idle_cb (gpointer user_data)
 }
 
 static gboolean
-_connect_sdp_io_cb (GIOChannel *io_channel,
+_connect_sdp_io_cb (int fd,
                     GIOCondition condition,
                     gpointer user_data)
 {
@@ -544,16 +549,13 @@ _connect_sdp_io_cb (GIOChannel *io_channel,
 	sdp_list_t *attrs;
 	uuid_t svclass;
 	uint16_t attr;
-	int fd;
 	int errsv;
 	int fd_err = 0;
 	int r;
 	socklen_t len = sizeof (fd_err);
 	gs_free_error GError *error = NULL;
 
-	context->cdat->source_id = 0;
-
-	fd = g_io_channel_unix_get_fd (io_channel);
+	nm_clear_g_source_inst (&context->cdat->source);
 
 	_LOGD (context, "sdp-session ready to connect with fd=%d", fd);
 
@@ -572,10 +574,13 @@ _connect_sdp_io_cb (GIOChannel *io_channel,
 		    && --context->cdat->sdp_session_try_count > 0) {
 			/* *sigh* */
 			_LOGD (context, "sdp-session failed with %s (%d). Retry in a bit", nm_strerror_native (errsv), errsv);
-			nm_clear_g_source (&context->cdat->source_id);
-			context->cdat->source_id = g_timeout_add (1000,
-			                                          _connect_sdp_session_start_on_idle_cb,
-			                                          context);
+			nm_clear_g_source_inst (&context->cdat->source);
+			context->cdat->source = nm_g_timeout_source_new (1000,
+			                                                 G_PRIORITY_DEFAULT,
+			                                                 _connect_sdp_session_start_on_idle_cb,
+			                                                 context,
+			                                                 NULL);
+			g_source_attach (context->cdat->source, NULL);
 			return G_SOURCE_REMOVE;
 		}
 
@@ -615,10 +620,13 @@ _connect_sdp_io_cb (GIOChannel *io_channel,
 	}
 
 	/* Set callback responsible for update the internal SDP transaction */
-	context->cdat->source_id = g_io_add_watch (io_channel,
-	                                           G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-	                                           _connect_sdp_search_io_cb,
-	                                           context);
+	context->cdat->source = nm_g_unix_fd_source_new (fd,
+	                                                 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+	                                                 G_PRIORITY_DEFAULT,
+	                                                 _connect_sdp_search_io_cb,
+	                                                 context,
+	                                                 NULL);
+	g_source_attach (context->cdat->source, NULL);
 
 done:
 	if (error)
@@ -644,11 +652,9 @@ static gboolean
 _connect_sdp_session_start (NMBluez5DunContext *context,
                             GError **error)
 {
-	nm_auto_unref_io_channel GIOChannel *io_channel = NULL;
-
 	nm_assert (context->cdat);
 
-	nm_clear_g_source (&context->cdat->source_id);
+	nm_clear_g_source_inst (&context->cdat->source);
 	nm_clear_pointer (&context->cdat->sdp_session, sdp_close);
 
 	context->cdat->sdp_session = sdp_connect (&context->src, &context->dst, SDP_NON_BLOCKING);
@@ -661,11 +667,13 @@ _connect_sdp_session_start (NMBluez5DunContext *context,
 		return FALSE;
 	}
 
-	io_channel = g_io_channel_unix_new (sdp_get_socket (context->cdat->sdp_session));
-	context->cdat->source_id = g_io_add_watch (io_channel,
-	                                           G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-	                                           _connect_sdp_io_cb,
-	                                           context);
+	context->cdat->source = nm_g_unix_fd_source_new (sdp_get_socket (context->cdat->sdp_session),
+	                                                 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+	                                                 G_PRIORITY_DEFAULT,
+	                                                 _connect_sdp_io_cb,
+	                                                 context,
+	                                                 NULL);
+	g_source_attach (context->cdat->source, NULL);
 	return TRUE;
 }
 
@@ -772,7 +780,7 @@ _context_cleanup_connect_data (NMBluez5DunContext *context)
 
 	nm_clear_g_signal_handler (cdat->cancellable, &cdat->cancelled_id);
 
-	nm_clear_g_source (&cdat->source_id);
+	nm_clear_g_source_inst (&cdat->source);
 
 	nm_clear_pointer (&cdat->sdp_session, sdp_close);
 
@@ -797,7 +805,7 @@ _context_invoke_callback (NMBluez5DunContext *context,
 
 	if (!error)
 		_LOGD (context, "connected via \"%s\"", context->rfcomm_tty_path);
-	else if (nm_utils_error_is_cancelled (error, FALSE))
+	else if (nm_utils_error_is_cancelled (error))
 		_LOGD (context, "cancelled");
 	else
 		_LOGD (context, "failed to connect: %s", error->message);
@@ -836,7 +844,7 @@ _context_free (NMBluez5DunContext *context)
 
 	_context_cleanup_connect_data (context);
 
-	nm_clear_g_source (&context->rfcomm_tty_poll_id);
+	nm_clear_g_source_inst (&context->rfcomm_tty_poll_source);
 
 	if (context->rfcomm_sock_fd >= 0) {
 		if (context->rfcomm_tty_no >= 0) {
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 497810c3..ab18897f 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -484,7 +484,7 @@ modem_auth_result (NMModem *modem, GError *error, gpointer user_data)
 	}
 
 	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_INIT;
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -519,7 +519,7 @@ modem_prepare_result (NMModem *modem,
 	}
 
 	priv->stage1_modem_prepare_state = NM_DEVICE_STAGE_STATE_COMPLETED;
-	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self), FALSE);
 }
 
 static void
@@ -719,7 +719,7 @@ mm_modem_added_cb (NMModemManager *manager,
 	priv = NM_DEVICE_BT_GET_PRIVATE (self);
 
 	if (priv->stage1_bt_state == NM_DEVICE_STAGE_STATE_COMPLETED)
-		nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+		nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self), FALSE);
 }
 
 /*****************************************************************************/
@@ -830,7 +830,7 @@ connect_bz_cb (NMBluezManager *bz_mgr,
 	NMDeviceBtPrivate *priv;
 	char sbuf[100];
 
-	if (nm_utils_error_is_cancelled (error, FALSE))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = user_data;
@@ -903,7 +903,7 @@ connect_bz_cb (NMBluezManager *bz_mgr,
 	}
 
 	priv->stage1_bt_state = NM_DEVICE_STAGE_STATE_COMPLETED;
-	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self), FALSE);
 }
 
 static NMActStageReturn
@@ -1153,7 +1153,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_BT_NAME:
@@ -1172,7 +1172,7 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_BT_BZ_MGR:
@@ -1296,7 +1296,7 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (object);
 
 	g_free (priv->connect_rfcomm_iface);
 	g_free (priv->dbus_path);
diff --git a/src/devices/meson.build b/src/devices/meson.build
index 2d874659..40a5c9dc 100644
--- a/src/devices/meson.build
+++ b/src/devices/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 subdir('adsl')
 
 if enable_modem_manager
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index 6e7a2a0f..735dd25b 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -38,8 +38,7 @@ struct _NMAcdManager {
 	GHashTable    *addresses;
 	guint          completed;
 	NAcd          *acd;
-	GIOChannel    *channel;
-	guint          event_id;
+	GSource       *event_source;
 
 	NMAcdCallbacks callbacks;
 	gpointer user_data;
@@ -157,7 +156,9 @@ nm_acd_manager_add_address (NMAcdManager *self, in_addr_t address)
 }
 
 static gboolean
-acd_event (GIOChannel *source, GIOCondition condition, gpointer data)
+acd_event (int fd,
+           GIOCondition condition,
+           gpointer data)
 {
 	NMAcdManager *self = data;
 	NAcdEvent *event;
@@ -183,12 +184,12 @@ acd_event (GIOChannel *source, GIOCondition condition, gpointer data)
 				r = n_acd_probe_announce (info->probe, N_ACD_DEFEND_ONCE);
 				if (r) {
 					_LOGW ("couldn't announce address %s on interface '%s': %s",
-					       nm_utils_inet4_ntop (info->address, address_str),
+					       _nm_utils_inet4_ntop (info->address, address_str),
 					       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 					       acd_error_to_string (r));
 				} else {
 					_LOGD ("announcing address %s",
-					       nm_utils_inet4_ntop (info->address, address_str));
+					       _nm_utils_inet4_ntop (info->address, address_str));
 				}
 			}
 			check_probing_done = TRUE;
@@ -201,14 +202,14 @@ acd_event (GIOChannel *source, GIOCondition condition, gpointer data)
 		case N_ACD_EVENT_DEFENDED:
 			n_acd_probe_get_userdata (event->defended.probe, (void **) &info);
 			_LOGD ("defended address %s from host %s",
-			       nm_utils_inet4_ntop (info->address, address_str),
+			       _nm_utils_inet4_ntop (info->address, address_str),
 			       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
 			                                           event->defended.n_sender)));
 			break;
 		case N_ACD_EVENT_CONFLICT:
 			n_acd_probe_get_userdata (event->conflict.probe, (void **) &info);
 			_LOGW ("conflict for address %s detected with host %s on interface '%s'",
-			       nm_utils_inet4_ntop (info->address, address_str),
+			       _nm_utils_inet4_ntop (info->address, address_str),
 			       (hwaddr_str = nm_utils_hwaddr_ntoa (event->defended.sender,
 			                                           event->defended.n_sender)),
 			       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex));
@@ -248,7 +249,7 @@ acd_probe_add (NMAcdManager *self,
 	r = n_acd_probe_config_new (&probe_config);
 	if (r) {
 		_LOGW ("could not create probe config for %s on interface '%s': %s",
-		       nm_utils_inet4_ntop (info->address, sbuf),
+		       _nm_utils_inet4_ntop (info->address, sbuf),
 		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 		       acd_error_to_string (r));
 		return FALSE;
@@ -260,7 +261,7 @@ acd_probe_add (NMAcdManager *self,
 	r = n_acd_probe (self->acd, &info->probe, probe_config);
 	if (r) {
 		_LOGW ("could not start probe for %s on interface '%s': %s",
-		       nm_utils_inet4_ntop (info->address, sbuf),
+		       _nm_utils_inet4_ntop (info->address, sbuf),
 		       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 		       acd_error_to_string (r));
 		n_acd_probe_config_free (probe_config);
@@ -334,11 +335,15 @@ nm_acd_manager_start_probe (NMAcdManager *self, guint timeout)
 	if (success)
 		self->state = STATE_PROBING;
 
-	nm_assert (!self->channel);
-	nm_assert (self->event_id == 0);
+	nm_assert (!self->event_source);
 	n_acd_get_fd (self->acd, &fd);
-	self->channel = g_io_channel_unix_new (fd);
-	self->event_id = g_io_add_watch (self->channel, G_IO_IN, acd_event, self);
+	self->event_source = nm_g_unix_fd_source_new (fd,
+	                                              G_IO_IN,
+	                                              G_PRIORITY_DEFAULT,
+	                                              acd_event,
+	                                              self,
+	                                              NULL);
+	g_source_attach (self->event_source, NULL);
 
 	return success ? 0 : -NME_UNSPEC;
 }
@@ -412,20 +417,24 @@ nm_acd_manager_announce_addresses (NMAcdManager *self)
 			r = n_acd_probe_announce (info->probe, N_ACD_DEFEND_ONCE);
 			if (r) {
 				_LOGW ("couldn't announce address %s on interface '%s': %s",
-				       nm_utils_inet4_ntop (info->address, sbuf),
+				       _nm_utils_inet4_ntop (info->address, sbuf),
 				       nm_platform_link_get_name (NM_PLATFORM_GET, self->ifindex),
 				       acd_error_to_string (r));
 				success = FALSE;
 			} else
-				_LOGD ("announcing address %s", nm_utils_inet4_ntop (info->address, sbuf));
+				_LOGD ("announcing address %s", _nm_utils_inet4_ntop (info->address, sbuf));
 		}
 	}
 
-	if (!self->channel) {
-		nm_assert (self->event_id == 0);
+	if (!self->event_source) {
 		n_acd_get_fd (self->acd, &fd);
-		self->channel = g_io_channel_unix_new (fd);
-		self->event_id = g_io_add_watch (self->channel, G_IO_IN, acd_event, self);
+		self->event_source = nm_g_unix_fd_source_new (fd,
+		                                              G_IO_IN,
+		                                              G_PRIORITY_DEFAULT,
+		                                              acd_event,
+		                                              self,
+		                                              NULL);
+		g_source_attach (self->event_source, NULL);
 	}
 
 	return success ? 0 : -NME_UNSPEC;
@@ -479,8 +488,7 @@ nm_acd_manager_free (NMAcdManager *self)
 		self->callbacks.user_data_destroy (self->user_data);
 
 	nm_clear_pointer (&self->addresses, g_hash_table_destroy);
-	nm_clear_pointer (&self->channel, g_io_channel_unref);
-	nm_clear_g_source (&self->event_id);
+	nm_clear_g_source_inst (&self->event_source);
 	nm_clear_pointer (&self->acd, n_acd_unref);
 
 	g_slice_free (NMAcdManager, self);
diff --git a/src/devices/nm-device-6lowpan.c b/src/devices/nm-device-6lowpan.c
index 0289a360..66ed5458 100644
--- a/src/devices/nm-device-6lowpan.c
+++ b/src/devices/nm-device-6lowpan.c
@@ -34,7 +34,7 @@ struct _NMDevice6LowpanClass {
 
 G_DEFINE_TYPE (NMDevice6Lowpan, nm_device_6lowpan, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_6LOWPAN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDevice6Lowpan, NM_IS_DEVICE_6LOWPAN)
+#define NM_DEVICE_6LOWPAN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDevice6Lowpan, NM_IS_DEVICE_6LOWPAN, NMDevice)
 
 /*****************************************************************************/
 
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 36d00328..c15605ce 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -70,16 +70,16 @@ complete_connection (NMDevice *device,
 /*****************************************************************************/
 
 static gboolean
-set_bond_attr (NMDevice *device, NMBondMode mode, const char *attr, const char *value)
+_set_bond_attr (NMDevice *device, const char *attr, const char *value)
 {
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
-	gboolean ret;
 	int ifindex = nm_device_get_ifindex (device);
+	gboolean ret;
 
-	if (!_nm_setting_bond_option_supported (attr, mode))
-		return FALSE;
-
-	ret = nm_platform_sysctl_master_set_option (nm_device_get_platform (device), ifindex, attr, value);
+	ret = nm_platform_sysctl_master_set_option (nm_device_get_platform (device),
+	                                            ifindex,
+	                                            attr,
+	                                            value);
 	if (!ret)
 		_LOGW (LOGD_PLATFORM, "failed to set bonding attribute '%s' to '%s'", attr, value);
 	return ret;
@@ -119,8 +119,10 @@ update_connection (NMDevice *device, NMConnection *connection)
 	/* Read bond options from sysfs and update the Bond setting to match */
 	options = nm_setting_bond_get_valid_options (s_bond);
 	for (; *options; options++) {
-		gs_free char *value = nm_platform_sysctl_master_get_option (nm_device_get_platform (device), ifindex, *options);
 		char *p;
+		gs_free char *value = nm_platform_sysctl_master_get_option (nm_device_get_platform (device),
+		                                                            ifindex,
+		                                                            *options);
 
 		if (   value
 		    && _nm_setting_bond_get_option_type (s_bond, *options) == NM_BOND_OPTION_TYPE_BOTH) {
@@ -129,8 +131,12 @@ update_connection (NMDevice *device, NMConnection *connection)
 				*p = '\0';
 		}
 
-		if (value && nm_streq (*options, NM_SETTING_BOND_OPTION_MODE))
-			mode = _nm_setting_bond_mode_from_string (value);
+		if (mode == NM_BOND_MODE_UNKNOWN) {
+			if (value && nm_streq (*options, NM_SETTING_BOND_OPTION_MODE))
+				mode = _nm_setting_bond_mode_from_string (value);
+			if (mode == NM_BOND_MODE_UNKNOWN)
+				continue;
+		}
 
 		if (!_nm_setting_bond_option_supported (*options, mode))
 			continue;
@@ -181,138 +187,86 @@ set_arp_targets (NMDevice *device,
 		gs_free char *tmp = NULL;
 
 		tmp = g_strdup_printf ("%s%s", prefix, value_v[i]);
-		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, tmp);
+		_set_bond_attr (device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, tmp);
 	}
 }
 
+/*
+ * Sets bond attribute stored in the option hashtable or
+ * the default value if no value was set.
+ */
 static void
-set_simple_option (NMDevice *device,
-                   NMBondMode mode,
-                   NMSettingBond *s_bond,
-                   const char *opt)
+set_bond_attr_or_default (NMDevice *device,
+                          NMSettingBond *s_bond,
+                          const char *opt)
 {
-	const char *value;
+	NMDeviceBond *self = NM_DEVICE_BOND (device);
+	const char *value = nm_setting_bond_get_option_or_default (s_bond, opt);
 
-	value = nm_setting_bond_get_option_by_name (s_bond, opt);
-	if (!value)
-		value = nm_setting_bond_get_option_default (s_bond, opt);
-	set_bond_attr (device, mode, opt, value);
+	if (value) {
+		_set_bond_attr (device, opt, value);
+	} else {
+		_LOGD (LOGD_BOND, "bond option %s rejected due to incompatibility", opt);
+	}
 }
 
 static gboolean
 apply_bonding_config (NMDeviceBond *self)
 {
 	NMDevice *device = NM_DEVICE (self);
-	NMSettingBond *s_bond;
 	int ifindex = nm_device_get_ifindex (device);
-	const char *mode_str, *value;
-	char *contents;
-	gboolean set_arp_interval = TRUE;
+	NMSettingBond *s_bond;
 	NMBondMode mode;
-
-	/* Option restrictions:
-	 *
-	 * arp_interval conflicts miimon > 0
-	 * arp_interval conflicts [ alb, tlb ]
-	 * arp_validate does not work with [ BOND_MODE_8023AD, BOND_MODE_TLB, BOND_MODE_ALB ]
-	 * downdelay needs miimon
-	 * updelay needs miimon
-	 * primary needs [ active-backup, tlb, alb ]
-	 *
-	 * clearing miimon requires that arp_interval be 0, but clearing
-	 *     arp_interval doesn't require miimon to be 0
-	 */
+	const char *mode_str;
+	const char *value;
+	char *contents;
 
 	s_bond = nm_device_get_applied_setting (device, NM_TYPE_SETTING_BOND);
-
 	g_return_val_if_fail (s_bond, FALSE);
 
-	mode_str = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
-	if (!mode_str)
-		mode_str = "balance-rr";
-
+	mode_str = nm_setting_bond_get_option_or_default (s_bond, NM_SETTING_BOND_OPTION_MODE);
 	mode = _nm_setting_bond_mode_from_string (mode_str);
-	if (mode == NM_BOND_MODE_UNKNOWN) {
-		_LOGW (LOGD_BOND, "unknown bond mode '%s'", mode_str);
-		return FALSE;
-	}
+	g_return_val_if_fail (mode != NM_BOND_MODE_UNKNOWN, FALSE);
 
 	/* Set mode first, as some other options (e.g. arp_interval) are valid
 	 * only for certain modes.
 	 */
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_MODE);
 
-	set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_MODE, mode_str);
-
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MIIMON);
-	if (value && atoi (value)) {
-		/* clear arp interval */
-		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_INTERVAL, "0");
-		set_arp_interval = FALSE;
-
-		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_MIIMON, value);
-		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_UPDELAY);
-		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY);
-	} else if (!value) {
-		/* If not given, and arp_interval is not given or disabled, default to 100 */
-		value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-		if (_nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, 0) == 0)
-			set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_MIIMON, "100");
-	}
-
-	if (set_arp_interval) {
-		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
-		/* Just let miimon get cleared automatically; even setting miimon to
-		 * 0 (disabled) clears arp_interval.
-		 */
-	}
-
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_VALIDATE);
-	set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_ARP_VALIDATE, value ?: "0");
-
-	/* Primary */
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
-	set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_PRIMARY, value ?: "");
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_MIIMON);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_UPDELAY);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_ARP_VALIDATE);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
 
 	/* ARP targets: clear and initialize the list */
-	contents = nm_platform_sysctl_master_get_option (nm_device_get_platform (device), ifindex,
+	contents = nm_platform_sysctl_master_get_option (nm_device_get_platform (device),
+	                                                 ifindex,
 	                                                 NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
 	set_arp_targets (device, mode, contents, " \n", "-");
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+	value = nm_setting_bond_get_option_or_default (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
 	set_arp_targets (device, mode, value, ",", "+");
 	g_free (contents);
 
-	/* AD actor system: don't set if empty */
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM);
-	if (value)
-		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, value);
-
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_AD_SELECT);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_LACP_RATE);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER);
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY);
-
-	/* num_grat_arp and num_unsol_na are actually the same attribute
-	 * on kernel side and their value in the bond setting is guaranteed
-	 * to be equal. Write only one of the two.
-	 */
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
-	if (value)
-		set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, value);
-	else
-		set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA);
-
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_AD_SELECT);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_FAIL_OVER_MAC);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_LACP_RATE);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_LP_INTERVAL);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_MIN_LINKS);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_RESEND_IGMP);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_USE_CARRIER);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP);
 	return TRUE;
 }
 
@@ -369,8 +323,9 @@ enslave_slave (NMDevice *device,
 			const char *active;
 
 			if (s_bond) {
-				active = nm_setting_bond_get_option_by_name (s_bond, "active_slave");
-				if (active && nm_streq0 (active, nm_device_get_iface (slave))) {
+				active = nm_setting_bond_get_option_or_default (s_bond,
+				                                                NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
+				if (nm_streq0 (active, nm_device_get_iface (slave))) {
 					nm_platform_sysctl_master_set_option (nm_device_get_platform (device),
 					                                      nm_device_get_ifindex (device),
 					                                      "active_slave",
@@ -407,7 +362,7 @@ release_slave (NMDevice *device,
 	ifindex_slave = nm_device_get_ip_ifindex (slave);
 
 	if (ifindex_slave <= 0)
-		_LOGD (LOGD_TEAM, "bond slave %s is already released", nm_device_get_ip_iface (slave));
+		_LOGD (LOGD_BOND, "bond slave %s is already released", nm_device_get_ip_iface (slave));
 
 	if (configure) {
 		/* When the last slave is released the bond MAC will be set to a random
@@ -568,19 +523,14 @@ reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_n
 	s_bond = nm_connection_get_setting_bond (con_new);
 	g_return_if_fail (s_bond);
 
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_MODE);
-	if (!value)
-		value = "balance-rr";
-
+	value = nm_setting_bond_get_option_or_default (s_bond, NM_SETTING_BOND_OPTION_MODE);
 	mode = _nm_setting_bond_mode_from_string (value);
 	g_return_if_fail (mode != NM_BOND_MODE_UNKNOWN);
 
 	/* Primary */
-	value = nm_setting_bond_get_option_by_name (s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
-	set_bond_attr (device, mode, NM_SETTING_BOND_OPTION_PRIMARY, value ?: "");
-
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
 	/* Active slave */
-	set_simple_option (device, mode, s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
+	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 72a8ce2b..c6c54344 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -158,11 +158,85 @@ complete_connection (NMDevice *device,
 	return TRUE;
 }
 
+static void
+from_sysfs_group_address (const char *value, GValue *out)
+{
+	if (!nm_utils_hwaddr_matches (value, -1, "01:80:C2:00:00:00", -1))
+		g_value_set_string (out, value);
+}
+
+static const char *
+to_sysfs_group_address (GValue *value)
+{
+	return g_value_get_string (value) ?: "01:80:C2:00:00:00";
+}
+
+static void
+from_sysfs_vlan_protocol (const char *value, GValue *out)
+{
+	switch (_nm_utils_ascii_str_to_uint64 (value, 16, 0, G_MAXUINT, -1)) {
+	case ETH_P_8021Q:
+		/* default value */
+		break;
+	case ETH_P_8021AD:
+		g_value_set_string (out, "802.1ad");
+		break;
+	}
+}
+
+static const char *
+to_sysfs_vlan_protocol (GValue *value)
+{
+	const char *str = g_value_get_string (value);
+
+	if (nm_streq0 (str, "802.1ad")) {
+		G_STATIC_ASSERT_EXPR (ETH_P_8021AD == 0x88A8);
+		return "0x88A8";
+	}
+
+	G_STATIC_ASSERT_EXPR (ETH_P_8021Q == 0x8100);
+	return "0x8100";
+}
+
+static const char *
+to_sysfs_multicast_router (GValue *value)
+{
+	const char *str = g_value_get_string (value);
+
+	if (nm_streq0 (str, "disabled"))
+		return "0";
+	if (nm_streq0 (str, "auto"))
+		return "1";
+	if (nm_streq0 (str, "enabled"))
+		return "2";
+
+	return "1";
+}
+
+static void
+from_sysfs_multicast_router (const char *value, GValue *out)
+{
+	switch (_nm_utils_ascii_str_to_uint64 (value, 10, 0, G_MAXUINT, -1)) {
+	case 0:
+		g_value_set_string (out, "disabled");
+		break;
+	case 2:
+		g_value_set_string (out, "enabled");
+		break;
+	case 1:
+	default:
+		/* default value */
+		break;
+	}
+}
+
 /*****************************************************************************/
 
 typedef struct {
 	const char *name;
 	const char *sysname;
+	const char *(*to_sysfs) (GValue *value);
+	void (*from_sysfs) (const char *value, GValue *out);
 	uint nm_min;
 	uint nm_max;
 	uint nm_default;
@@ -172,41 +246,76 @@ typedef struct {
 } Option;
 
 static const Option master_options[] = {
-	{ NM_SETTING_BRIDGE_STP,                "stp_state", /* this must stay as the first item */
-	                                        0, 1, 1,
-	                                        FALSE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_PRIORITY,           "priority",
-	                                        0, G_MAXUINT16, 0x8000,
-	                                        TRUE, FALSE, TRUE },
-	{ NM_SETTING_BRIDGE_FORWARD_DELAY,      "forward_delay",
-	                                        0, NM_BR_MAX_FORWARD_DELAY, 15,
-	                                        TRUE, TRUE, TRUE},
-	{ NM_SETTING_BRIDGE_HELLO_TIME,         "hello_time",
-	                                        0, NM_BR_MAX_HELLO_TIME, 2,
-	                                        TRUE, TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_MAX_AGE,            "max_age",
-	                                        0, NM_BR_MAX_MAX_AGE, 20,
-	                                        TRUE, TRUE, TRUE },
-	{ NM_SETTING_BRIDGE_AGEING_TIME,        "ageing_time",
-	                                        NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, 300,
-	                                        TRUE, TRUE, FALSE },
-	{ NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "group_fwd_mask",
-	                                        0, 0xFFFF, 0,
-	                                        TRUE, FALSE, FALSE },
-	{ NM_SETTING_BRIDGE_MULTICAST_SNOOPING, "multicast_snooping",
-	                                        0, 1, 1,
-	                                        FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_STP,                          "stp_state", /* this must stay as the first item */
+	                                                  NULL, NULL,
+	                                                  0, 1, 1,
+	                                                  FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_PRIORITY,                     "priority",
+	                                                  NULL, NULL,
+	                                                  0, G_MAXUINT16, 0x8000,
+	                                                  TRUE, FALSE, TRUE },
+	{ NM_SETTING_BRIDGE_FORWARD_DELAY,                "forward_delay",
+	                                                  NULL, NULL,
+	                                                  0, NM_BR_MAX_FORWARD_DELAY, 15,
+	                                                  TRUE, TRUE, TRUE},
+	{ NM_SETTING_BRIDGE_HELLO_TIME,                   "hello_time",
+	                                                  NULL, NULL,
+	                                                  0, NM_BR_MAX_HELLO_TIME, 2,
+	                                                  TRUE, TRUE, TRUE },
+	{ NM_SETTING_BRIDGE_MAX_AGE,                      "max_age",
+	                                                  NULL, NULL,
+	                                                  0, NM_BR_MAX_MAX_AGE, 20,
+	                                                  TRUE, TRUE, TRUE },
+	{ NM_SETTING_BRIDGE_AGEING_TIME,                  "ageing_time",
+	                                                  NULL, NULL,
+	                                                  NM_BR_MIN_AGEING_TIME, NM_BR_MAX_AGEING_TIME, 300,
+	                                                  TRUE, TRUE, FALSE },
+	{ NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,           "group_fwd_mask",
+	                                                  NULL, NULL,
+	                                                  0, 0xFFFF, 0,
+	                                                  TRUE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_MULTICAST_QUERIER,            "multicast_querier",
+	                                                  NULL, NULL,
+	                                                  0, 1, 0,
+	                                                  FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR,   "multicast_query_use_ifaddr",
+	                                                  NULL, NULL,
+	                                                  0, 1, 0,
+	                                                  FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_MULTICAST_SNOOPING,           "multicast_snooping",
+	                                                  NULL, NULL,
+	                                                  0, 1, 1,
+	                                                  FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_MULTICAST_ROUTER,             "multicast_router",
+	                                                  to_sysfs_multicast_router, from_sysfs_multicast_router,
+	                                                  0, 0, 0,
+	                                                  FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_GROUP_ADDRESS,                "group_addr",
+	                                                  to_sysfs_group_address, from_sysfs_group_address,
+	                                                  0, 0, 0,
+	                                                  FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_VLAN_PROTOCOL,                "vlan_protocol",
+	                                                  to_sysfs_vlan_protocol, from_sysfs_vlan_protocol,
+	                                                  0, 0, 0,
+	                                                  FALSE, FALSE, FALSE },
+	{ NM_SETTING_BRIDGE_VLAN_STATS_ENABLED,           "vlan_stats_enabled",
+	                                                  NULL, NULL,
+	                                                  0, 1, 0,
+	                                                  FALSE, FALSE, FALSE },
 	{ NULL, NULL }
 };
 
 static const Option slave_options[] = {
 	{ NM_SETTING_BRIDGE_PORT_PRIORITY,     "priority",
+	                                       NULL, NULL,
 	                                       0, NM_BR_PORT_MAX_PRIORITY, NM_BR_PORT_DEF_PRIORITY,
 	                                       TRUE, FALSE },
 	{ NM_SETTING_BRIDGE_PORT_PATH_COST,    "path_cost",
+	                                       NULL, NULL,
 	                                       0, NM_BR_PORT_MAX_PATH_COST, 100,
 	                                       TRUE, FALSE },
 	{ NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode",
+	                                       NULL, NULL,
 	                                       0, 1, 0,
 	                                       FALSE, FALSE },
 	{ NULL, NULL }
@@ -216,50 +325,82 @@ static void
 commit_option (NMDevice *device, NMSetting *setting, const Option *option, gboolean slave)
 {
 	int ifindex = nm_device_get_ifindex (device);
+	nm_auto_unset_gvalue GValue val = G_VALUE_INIT;
 	GParamSpec *pspec;
-	GValue val = G_VALUE_INIT;
-	guint32 uval = 0;
-	char value[100];
+	const char *value;
 
-	g_assert (setting);
+	if (slave)
+		nm_assert (NM_IS_SETTING_BRIDGE_PORT (setting));
+	else
+		nm_assert (NM_IS_SETTING_BRIDGE (setting));
 
 	pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (setting), option->name);
-	g_assert (pspec);
+	nm_assert (pspec);
 
-	/* Get the property's value */
 	g_value_init (&val, G_PARAM_SPEC_VALUE_TYPE (pspec));
 	g_object_get_property ((GObject *) setting, option->name, &val);
-	if (G_VALUE_HOLDS_BOOLEAN (&val))
-		uval = g_value_get_boolean (&val) ? 1 : 0;
-	else if (G_VALUE_HOLDS_UINT (&val)) {
-		uval = g_value_get_uint (&val);
-
-		/* zero means "unspecified" for some NM properties but isn't in the
-		 * allowed kernel range, so reset the property to the default value.
-		 */
-		if (option->default_if_zero && uval == 0) {
-			g_value_unset (&val);
-			g_value_init (&val, G_PARAM_SPEC_VALUE_TYPE (pspec));
-			g_param_value_set_default (pspec, &val);
+
+	if (option->to_sysfs) {
+		value = option->to_sysfs (&val);
+		goto out;
+	}
+
+	switch (pspec->value_type) {
+	case G_TYPE_BOOLEAN:
+		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);
-		}
 
-		/* Linux kernel bridge interfaces use 'centiseconds' for time-based values.
-		 * In reality it's not centiseconds, but depends on HZ and USER_HZ, which
-		 * is almost always works out to be a multiplier of 100, so we can assume
-		 * centiseconds.  See clock_t_to_jiffies().
-		 */
-		if (option->user_hz_compensate)
-			uval *= 100;
-	} else
+			/* zero means "unspecified" for some NM properties but isn't in the
+			 * allowed kernel range, so reset the property to the default value.
+			 */
+			if (option->default_if_zero && uval == 0) {
+				g_value_unset (&val);
+				g_value_init (&val, G_PARAM_SPEC_VALUE_TYPE (pspec));
+				g_param_value_set_default (pspec, &val);
+				uval = g_value_get_uint (&val);
+			}
+
+			/* Linux kernel bridge interfaces use 'centiseconds' for time-based values.
+			 * In reality it's not centiseconds, but depends on HZ and USER_HZ, which
+			 * is almost always works out to be a multiplier of 100, so we can assume
+			 * centiseconds.  See clock_t_to_jiffies().
+			 */
+			if (option->user_hz_compensate)
+				uval *= 100;
+
+			nm_sprintf_buf (value_buf, "%u", uval);
+			value = value_buf;
+		}
+		break;
+	case G_TYPE_STRING:
+		value = g_value_get_string (&val);
+		break;
+	default:
 		nm_assert_not_reached ();
-	g_value_unset (&val);
+		value = NULL;
+		break;
+	}
 
-	nm_sprintf_buf (value, "%u", uval);
-	if (slave)
-		nm_platform_sysctl_slave_set_option (nm_device_get_platform (device), ifindex, option->sysname, value);
-	else
-		nm_platform_sysctl_master_set_option (nm_device_get_platform (device), ifindex, option->sysname, value);
+out:
+	if (!value)
+		return;
+
+	if (slave) {
+		nm_platform_sysctl_slave_set_option (nm_device_get_platform (device),
+		                                     ifindex,
+		                                     option->sysname,
+		                                     value);
+	} else {
+		nm_platform_sysctl_master_set_option (nm_device_get_platform (device),
+		                                      ifindex,
+		                                      option->sysname,
+		                                      value);
+	}
 }
 
 static const NMPlatformBridgeVlan **
@@ -335,29 +476,68 @@ update_connection (NMDevice *device, NMConnection *connection)
 	option++;
 
 	for (; option->name; option++) {
-		gs_free char *str = nm_platform_sysctl_master_get_option (nm_device_get_platform (device), ifindex, option->sysname);
-		uint value;
+		nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
+		gs_free char *str = NULL;
+		GParamSpec *pspec;
+
+		str = nm_platform_sysctl_master_get_option (nm_device_get_platform (device), ifindex, option->sysname);
+		pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (s_bridge), option->name);
 
 		if (!stp_value && option->only_with_stp)
 			continue;
 
-		if (str) {
-			/* See comments in set_sysfs_uint() about centiseconds. */
-			if (option->user_hz_compensate) {
-				value = _nm_utils_ascii_str_to_int64 (str, 10,
-				                                      option->nm_min * 100,
-				                                      option->nm_max * 100,
-				                                      option->nm_default * 100);
-				value /= 100;
-			} else {
-				value = _nm_utils_ascii_str_to_int64 (str, 10,
-				                                      option->nm_min,
-				                                      option->nm_max,
-				                                      option->nm_default);
-			}
-			g_object_set (s_bridge, option->name, value, NULL);
-		} else
+		if (!str) {
 			_LOGW (LOGD_BRIDGE, "failed to read bridge setting '%s'", option->sysname);
+			continue;
+		}
+
+		g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+		if (option->from_sysfs) {
+			option->from_sysfs (str, &value);
+			goto out;
+		}
+
+		switch (pspec->value_type) {
+		case G_TYPE_UINT: {
+				guint uvalue;
+
+				/* See comments in set_sysfs_uint() about centiseconds. */
+				if (option->user_hz_compensate) {
+					uvalue = _nm_utils_ascii_str_to_int64 (str, 10,
+					                                       option->nm_min * 100,
+					                                       option->nm_max * 100,
+					                                       option->nm_default * 100);
+					uvalue /= 100;
+				} else {
+					uvalue = _nm_utils_ascii_str_to_int64 (str, 10,
+					                                       option->nm_min,
+					                                       option->nm_max,
+					                                       option->nm_default);
+				}
+				g_value_set_uint (&value, uvalue);
+			}
+			break;
+		case G_TYPE_BOOLEAN: {
+				gboolean bvalue;
+
+				bvalue = _nm_utils_ascii_str_to_int64 (str, 10,
+				                                       option->nm_min,
+				                                       option->nm_max,
+				                                       option->nm_default);
+				g_value_set_boolean (&value, bvalue);
+			}
+			break;
+		case G_TYPE_STRING:
+			g_value_set_string (&value, str);
+			break;
+		default:
+			nm_assert_not_reached ();
+			break;
+		}
+
+out:
+		g_object_set_property (G_OBJECT (s_bridge), option->name, &value);
 	}
 }
 
@@ -518,7 +698,7 @@ _bt_register_bridge_cb (GError *error,
 {
 	NMDeviceBridge *self;
 
-	if (nm_utils_error_is_cancelled (error, FALSE))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = user_data;
@@ -532,7 +712,7 @@ _bt_register_bridge_cb (GError *error,
 		return;
 	}
 
-	nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+	nm_device_activate_schedule_stage2_device_config (NM_DEVICE (self), FALSE);
 }
 
 void
@@ -561,40 +741,41 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
 	NMConnection *connection;
 	NMSettingBluetooth *s_bt;
+	gs_free_error GError *error = NULL;
 
 	connection = nm_device_get_applied_connection (device);
 
 	s_bt = _nm_connection_get_setting_bluetooth_for_nap (connection);
-	if (s_bt) {
-		gs_free_error GError *error = NULL;
+	if (!s_bt)
+		return NM_ACT_STAGE_RETURN_SUCCESS;
 
-		if (!nm_bt_vtable_network_server) {
-			_LOGD (LOGD_DEVICE, "bluetooth NAP server failed because bluetooth plugin not available");
-			*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		}
+	if (!nm_bt_vtable_network_server) {
+		_LOGD (LOGD_DEVICE, "bluetooth NAP server failed because bluetooth plugin not available");
+		*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
 
-		if (self->bt_cancellable)
-			return NM_ACT_STAGE_RETURN_POSTPONE;
-
-		self->bt_cancellable = g_cancellable_new ();
-		if (!nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server,
-		                                                   nm_setting_bluetooth_get_bdaddr (s_bt),
-		                                                   device,
-		                                                   self->bt_cancellable,
-		                                                   _bt_register_bridge_cb,
-		                                                   device,
-		                                                   &error)) {
-			_LOGD (LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message);
-			*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		}
+	if (self->bt_cancellable)
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 
-		self->bt_registered = TRUE;
+	if (self->bt_registered)
 		return NM_ACT_STAGE_RETURN_POSTPONE;
+
+	self->bt_cancellable = g_cancellable_new ();
+	if (!nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server,
+	                                                   nm_setting_bluetooth_get_bdaddr (s_bt),
+	                                                   device,
+	                                                   self->bt_cancellable,
+	                                                   _bt_register_bridge_cb,
+	                                                   device,
+	                                                   &error)) {
+		_LOGD (LOGD_DEVICE, "bluetooth NAP server failed to register bridge: %s", error->message);
+		*out_failure_reason = NM_DEVICE_STATE_REASON_BT_FAILED;
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	return NM_ACT_STAGE_RETURN_SUCCESS;
+	self->bt_registered = TRUE;
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 static void
@@ -736,10 +917,11 @@ create_and_realize (NMDevice *device,
 	if (   !hwaddr
 	    && nm_device_hw_addr_get_cloned (device, connection, FALSE,
 	                                     &hwaddr_cloned, NULL, NULL)) {
-		/* The cloned MAC address might by dynamic, for example with stable-id="${RANDOM}".
-		 * It's a bit odd that we first create the device with one dynamic address,
-		 * and later on may reset it to another. That is, because we don't cache
-		 * the dynamic address in @device, like we do during nm_device_hw_addr_set_cloned(). */
+		/* FIXME: we set the MAC address when creating the interface, while the
+		 * NMDevice is still unrealized. As we afterwards realize the device, it
+		 * forgets the parameters for the cloned MAC address, and in stage 1
+		 * it might create a different MAC address. That should be fixed by
+		 * better handling device realization. */
 		hwaddr = hwaddr_cloned;
 	}
 
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 86e50c52..7556bb27 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -43,19 +43,9 @@ _LOG_DECLARE_SELF(NMDeviceEthernet);
 #define PPPOE_RECONNECT_DELAY 7
 #define PPPOE_ENCAP_OVERHEAD  8 /* 2 bytes for PPP, 6 for PPPoE */
 
-/*****************************************************************************/
-
-typedef struct Supplicant {
-	NMSupplicantManager *mgr;
-	NMSupplicantInterface *iface;
-
-	/* signal handler ids */
-	gulong iface_state_id;
-	gulong auth_state_id;
+#define SUPPLICANT_LNK_TIMEOUT_SEC 15
 
-	/* Timeouts and idles */
-	guint con_timeout_id;
-} Supplicant;
+/*****************************************************************************/
 
 typedef enum {
 	DCB_WAIT_UNKNOWN = 0,
@@ -72,12 +62,6 @@ typedef enum {
 } DcbWait;
 
 typedef struct _NMDeviceEthernetPrivate {
-	guint32             speed;
-	gulong              carrier_id;
-
-	Supplicant          supplicant;
-	guint               supplicant_timeout_id;
-
 	/* s390 */
 	char *              subchan1;
 	char *              subchan2;
@@ -87,6 +71,24 @@ typedef struct _NMDeviceEthernetPrivate {
 	char *              s390_nettype;
 	GHashTable *        s390_options;
 
+	guint32             speed;
+	gulong              carrier_id;
+
+	struct {
+		NMSupplicantManager *mgr;
+		NMSupplMgrCreateIfaceHandle *create_handle;
+		NMSupplicantInterface *iface;
+
+		gulong iface_state_id;
+		gulong auth_state_id;
+
+		guint con_timeout_id;
+
+		guint lnk_timeout_id;
+
+		bool is_associated:1;
+	} supplicant;
+
 	NMActRequestGetSecretsCallId *wired_secrets_id;
 
 	/* PPPoE */
@@ -110,7 +112,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceEthernet,
 
 G_DEFINE_TYPE (NMDeviceEthernet, nm_device_ethernet, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_ETHERNET_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDeviceEthernet, NM_IS_DEVICE_ETHERNET)
+#define NM_DEVICE_ETHERNET_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDeviceEthernet, NM_IS_DEVICE_ETHERNET, NMDevice)
 
 /*****************************************************************************/
 
@@ -400,7 +402,9 @@ supplicant_interface_release (NMDeviceEthernet *self)
 {
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->supplicant_timeout_id);
+	nm_clear_pointer (&priv->supplicant.create_handle, nm_supplicant_manager_create_interface_cancel);
+
+	nm_clear_g_source (&priv->supplicant.lnk_timeout_id);
 	nm_clear_g_source (&priv->supplicant.con_timeout_id);
 	nm_clear_g_signal_handler (priv->supplicant.iface, &priv->supplicant.iface_state_id);
 	nm_clear_g_signal_handler (priv->supplicant.iface, &priv->supplicant.auth_state_id);
@@ -501,7 +505,7 @@ wired_secrets_cb (NMActRequest *req,
 	}
 
 	supplicant_interface_release (self);
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -538,7 +542,7 @@ wired_secrets_get_secrets (NMDeviceEthernet *self,
 }
 
 static gboolean
-link_timeout_cb (gpointer user_data)
+supplicant_lnk_timeout_cb (gpointer user_data)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
@@ -547,13 +551,13 @@ link_timeout_cb (gpointer user_data)
 	NMConnection *applied_connection;
 	const char *setting_name;
 
-	priv->supplicant_timeout_id = 0;
+	priv->supplicant.lnk_timeout_id = 0;
 
 	req = nm_device_get_act_request (device);
 
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED) {
 		wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
-		return FALSE;
+		return G_SOURCE_REMOVE;
 	}
 
 	/* Disconnect event during initial authentication and credentials
@@ -578,13 +582,13 @@ link_timeout_cb (gpointer user_data)
 	nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
 	wired_secrets_get_secrets (self, setting_name, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
 
-	return FALSE;
+	return G_SOURCE_REMOVE;
 
 time_out:
 	_LOGW (LOGD_DEVICE | LOGD_ETHER, "link timed out.");
 	wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
 
-	return FALSE;
+	return G_SOURCE_REMOVE;
 }
 
 static NMSupplicantConfig *
@@ -605,7 +609,7 @@ build_supplicant_config (NMDeviceEthernet *self,
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
 
-	config = nm_supplicant_config_new (FALSE, FALSE, FALSE, FALSE);
+	config = nm_supplicant_config_new (NM_SUPPL_CAP_MASK_NONE);
 
 	security = nm_connection_get_setting_802_1x (connection);
 	if (!nm_supplicant_config_add_setting_8021x (config, security, con_uuid, mtu, TRUE, error)) {
@@ -617,18 +621,86 @@ build_supplicant_config (NMDeviceEthernet *self,
 }
 
 static void
+supplicant_iface_state_is_completed (NMDeviceEthernet *self,
+                                     NMSupplicantInterfaceState state)
+{
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+
+	if (state == NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) {
+		nm_clear_g_source (&priv->supplicant.lnk_timeout_id);
+		nm_clear_g_source (&priv->supplicant.con_timeout_id);
+
+		/* If this is the initial association during device activation,
+		 * schedule the next activation stage.
+		 */
+		if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_CONFIG) {
+			_LOGI (LOGD_DEVICE | LOGD_ETHER,
+			       "Activation: (ethernet) Stage 2 of 5 (Device Configure) successful.");
+			nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+		}
+		return;
+	}
+
+	if (   !priv->supplicant.lnk_timeout_id
+	    && !priv->supplicant.con_timeout_id)
+		priv->supplicant.lnk_timeout_id = g_timeout_add_seconds (SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self);
+}
+
+static void
 supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
                            GError *error,
                            gpointer user_data)
 {
-	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data);
+	NMDeviceEthernet *self;
+	NMDeviceEthernetPrivate *priv;
 
-	if (error && !nm_utils_error_is_cancelled (error, TRUE)) {
+	if (nm_utils_error_is_cancelled_or_disposing (error))
+		return;
+
+	self = NM_DEVICE_ETHERNET (user_data);
+	priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+
+	if (error) {
 		supplicant_interface_release (self);
 		nm_device_queue_state (NM_DEVICE (self),
 		                       NM_DEVICE_STATE_FAILED,
 		                       NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
+		return;
+	}
+
+	nm_assert (!priv->supplicant.lnk_timeout_id);
+	nm_assert (!priv->supplicant.is_associated);
+
+	priv->supplicant.is_associated = TRUE;
+	supplicant_iface_state_is_completed (self,
+	                                     nm_supplicant_interface_get_state (priv->supplicant.iface));
+}
+
+static gboolean
+supplicant_iface_start (NMDeviceEthernet *self)
+{
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+	gs_unref_object NMSupplicantConfig *config = NULL;
+	gs_free_error GError *error = NULL;
+
+	config = build_supplicant_config (self, &error);
+	if (!config) {
+		_LOGE (LOGD_DEVICE | LOGD_ETHER,
+		       "Activation: (ethernet) couldn't build security configuration: %s",
+		       error->message);
+		supplicant_interface_release (self);
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
+		return FALSE;
 	}
+
+	nm_supplicant_interface_disconnect (priv->supplicant.iface);
+	nm_supplicant_interface_assoc (priv->supplicant.iface,
+	                               config,
+	                               supplicant_iface_assoc_cb,
+	                               self);
+	return TRUE;
 }
 
 static void
@@ -640,72 +712,29 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (user_data);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-	NMDevice *device = NM_DEVICE (self);
-	NMSupplicantConfig *config;
-	NMDeviceState devstate;
-	GError *error = NULL;
 	NMSupplicantInterfaceState new_state = new_state_i;
 	NMSupplicantInterfaceState old_state = old_state_i;
 
-	if (new_state == old_state)
-		return;
-
 	_LOGI (LOGD_DEVICE | LOGD_ETHER, "supplicant interface state: %s -> %s",
 	       nm_supplicant_interface_state_to_string (old_state),
 	       nm_supplicant_interface_state_to_string (new_state));
 
-	devstate = nm_device_get_state (device);
-
-	switch (new_state) {
-	case NM_SUPPLICANT_INTERFACE_STATE_READY:
-		config = build_supplicant_config (self, &error);
-		if (config) {
-			nm_supplicant_interface_assoc (priv->supplicant.iface, config,
-			                               supplicant_iface_assoc_cb, self);
-			g_object_unref (config);
-		} else {
-			_LOGE (LOGD_DEVICE | LOGD_ETHER,
-			       "Activation: (ethernet) couldn't build security configuration: %s",
-			       error->message);
-			g_clear_error (&error);
-
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
-		}
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
-		nm_clear_g_source (&priv->supplicant_timeout_id);
-		nm_clear_g_source (&priv->supplicant.con_timeout_id);
-
-		/* If this is the initial association during device activation,
-		 * schedule the next activation stage.
-		 */
-		if (devstate == NM_DEVICE_STATE_CONFIG) {
-			_LOGI (LOGD_DEVICE | LOGD_ETHER,
-			       "Activation: (ethernet) Stage 2 of 5 (Device Configure) successful.");
-			nm_device_activate_schedule_stage3_ip_config_start (device);
-		}
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED:
-		if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) {
-			/* Start the link timeout so we allow some time for reauthentication */
-			if (!priv->supplicant_timeout_id)
-				priv->supplicant_timeout_id = g_timeout_add_seconds (15, link_timeout_cb, device);
-		}
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
 		supplicant_interface_release (self);
+		wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
+	}
 
-		if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device))
-			wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		break;
-	default:
-		break;
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		if (!supplicant_iface_start (self))
+			return;
 	}
+
+	if (priv->supplicant.is_associated)
+		supplicant_iface_state_is_completed (self, new_state);
 }
 
-static NMActStageReturn
+static gboolean
 handle_auth_or_fail (NMDeviceEthernet *self,
                      NMActRequest *req,
                      gboolean new_secrets)
@@ -714,7 +743,7 @@ handle_auth_or_fail (NMDeviceEthernet *self,
 	NMConnection *applied_connection;
 
 	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
-		return NM_ACT_STAGE_RETURN_FAILURE;
+		return FALSE;
 
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
@@ -724,7 +753,7 @@ handle_auth_or_fail (NMDeviceEthernet *self,
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (!setting_name) {
 		_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
-		return NM_ACT_STAGE_RETURN_FAILURE;
+		return FALSE;
 	}
 
 	_LOGI (LOGD_DEVICE | LOGD_ETHER, "Activation: (ethernet) asking for new secrets");
@@ -739,7 +768,7 @@ handle_auth_or_fail (NMDeviceEthernet *self,
 	wired_secrets_get_secrets (self, setting_name,
 	                             NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
 	                           | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return TRUE;
 }
 
 static gboolean
@@ -771,43 +800,70 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	if (nm_settings_connection_get_timestamp (connection, &timestamp))
 		new_secrets = !timestamp;
 
-	if (handle_auth_or_fail (self, req, new_secrets) == NM_ACT_STAGE_RETURN_FAILURE)
+	if (!handle_auth_or_fail (self, req, new_secrets)) {
 		wired_auth_cond_fail (self, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		return G_SOURCE_REMOVE;
+	}
+
+	if (   !priv->supplicant.lnk_timeout_id
+	    && priv->supplicant.iface) {
+		NMSupplicantInterfaceState state;
 
-	return FALSE;
+		state = nm_supplicant_interface_get_state (priv->supplicant.iface);
+		if (state != NM_SUPPLICANT_INTERFACE_STATE_COMPLETED
+		    && nm_supplicant_interface_state_is_operational (state))
+			priv->supplicant.lnk_timeout_id = g_timeout_add_seconds (SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self);
+	}
+
+	return G_SOURCE_REMOVE;
 }
 
-static gboolean
-supplicant_interface_init (NMDeviceEthernet *self)
+static void
+supplicant_interface_create_cb (NMSupplicantManager *supplicant_manager,
+                                NMSupplMgrCreateIfaceHandle *handle,
+                                NMSupplicantInterface *iface,
+                                GError *error,
+                                gpointer user_data)
 {
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+	NMDeviceEthernet *self;
+	NMDeviceEthernetPrivate *priv;
 	guint timeout;
 
-	supplicant_interface_release (self);
+	if (nm_utils_error_is_cancelled (error))
+		return;
 
-	priv->supplicant.iface = nm_supplicant_manager_create_interface (priv->supplicant.mgr,
-	                                                                 nm_device_get_iface (NM_DEVICE (self)),
-	                                                                 NM_SUPPLICANT_DRIVER_WIRED);
+	self = user_data;
+	priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+
+	nm_assert (priv->supplicant.create_handle == handle);
+	priv->supplicant.create_handle = NULL;
 
-	if (!priv->supplicant.iface) {
+	if (error) {
 		_LOGE (LOGD_DEVICE | LOGD_ETHER,
-		       "Couldn't initialize supplicant interface");
-		return FALSE;
+		       "Couldn't initialize supplicant interface: %s",
+		       error->message);
+		supplicant_interface_release (self);
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
 	}
 
-	/* Listen for its state signals */
+	priv->supplicant.iface = g_object_ref (iface);
+	priv->supplicant.is_associated = FALSE;
+
 	priv->supplicant.iface_state_id = g_signal_connect (priv->supplicant.iface,
 	                                                    NM_SUPPLICANT_INTERFACE_STATE,
 	                                                    G_CALLBACK (supplicant_iface_state_cb),
 	                                                    self);
 
-	/* Set up a timeout on the connection attempt */
 	timeout = nm_device_get_supplicant_timeout (NM_DEVICE (self));
 	priv->supplicant.con_timeout_id = g_timeout_add_seconds (timeout,
 	                                                         supplicant_connection_timeout_cb,
 	                                                         self);
 
-	return TRUE;
+	if (nm_supplicant_interface_state_is_operational (nm_supplicant_interface_get_state (iface)))
+		supplicant_iface_start (self);
 }
 
 static NMPlatformLinkDuplexType
@@ -898,7 +954,7 @@ pppoe_reconnect_delay (gpointer user_data)
 	priv->pppoe_wait_id = 0;
 	priv->last_pppoe_time = 0;
 	_LOGI (LOGD_DEVICE, "PPPoE reconnect delay complete, resuming connection...");
-	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self), FALSE);
 	return G_SOURCE_REMOVE;
 }
 
@@ -920,7 +976,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	 * otherwise after restart the device won't work for the first seconds.
 	 */
 	if (priv->last_pppoe_time != 0) {
-		gint32 delay = nm_utils_get_monotonic_timestamp_s () - priv->last_pppoe_time;
+		gint32 delay = nm_utils_get_monotonic_timestamp_sec () - priv->last_pppoe_time;
 
 		if (   delay < PPPOE_RECONNECT_DELAY
 		    && nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE)) {
@@ -947,7 +1003,6 @@ supplicant_check_secrets_needed (NMDeviceEthernet *self, NMDeviceStateReason *ou
 	NMConnection *connection;
 	NMSetting8021x *security;
 	const char *setting_name;
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
@@ -956,7 +1011,7 @@ supplicant_check_secrets_needed (NMDeviceEthernet *self, NMDeviceStateReason *ou
 	if (!security) {
 		_LOGE (LOGD_DEVICE, "Invalid or missing 802.1X security");
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
-		return ret;
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
 	if (!priv->supplicant.mgr)
@@ -971,21 +1026,25 @@ supplicant_check_secrets_needed (NMDeviceEthernet *self, NMDeviceStateReason *ou
 		       "Activation: (ethernet) connection '%s' has security, but secrets are required.",
 		       nm_connection_get_id (connection));
 
-		ret = handle_auth_or_fail (self, req, FALSE);
-		if (ret != NM_ACT_STAGE_RETURN_POSTPONE)
+		if (!handle_auth_or_fail (self, req, FALSE)) {
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else {
-		_LOGI (LOGD_DEVICE | LOGD_ETHER,
-		       "Activation: (ethernet) connection '%s' requires no security. No secrets needed.",
-		       nm_connection_get_id (connection));
-
-		if (supplicant_interface_init (self))
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		else
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	return ret;
+	_LOGI (LOGD_DEVICE | LOGD_ETHER,
+	       "Activation: (ethernet) connection '%s' requires no security. No secrets needed.",
+	       nm_connection_get_id (connection));
+
+	supplicant_interface_release (self);
+
+	priv->supplicant.create_handle = nm_supplicant_manager_create_interface (priv->supplicant.mgr,
+	                                                                         nm_device_get_ifindex (NM_DEVICE (self)),
+	                                                                         NM_SUPPLICANT_DRIVER_WIRED,
+	                                                                         supplicant_interface_create_cb,
+	                                                                         self);
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 static void
@@ -997,33 +1056,17 @@ carrier_changed (NMSupplicantInterface *iface,
 	NMDeviceStateReason reason;
 	NMActStageReturn ret;
 
-	if (nm_device_has_carrier (NM_DEVICE (self))) {
-		_LOGD (LOGD_DEVICE | LOGD_ETHER, "got carrier, initializing supplicant");
-		nm_clear_g_signal_handler (self, &priv->carrier_id);
-		ret = supplicant_check_secrets_needed (self, &reason);
-		if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-			nm_device_state_changed (NM_DEVICE (self),
-			                         NM_DEVICE_STATE_FAILED,
-			                         reason);
-		}
-	}
-}
-
-static NMActStageReturn
-nm_8021x_stage2_config (NMDeviceEthernet *self, NMDeviceStateReason *out_failure_reason)
-{
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+	if (!nm_device_has_carrier (NM_DEVICE (self)))
+		return;
 
-	if (!nm_device_has_carrier (NM_DEVICE (self))) {
-		_LOGD (LOGD_DEVICE | LOGD_ETHER, "delay supplicant initialization until carrier goes up");
-		priv->carrier_id = g_signal_connect (self,
-		                                     "notify::" NM_DEVICE_CARRIER,
-		                                     G_CALLBACK (carrier_changed),
-		                                     self);
-		return NM_ACT_STAGE_RETURN_POSTPONE;
+	_LOGD (LOGD_DEVICE | LOGD_ETHER, "got carrier, initializing supplicant");
+	nm_clear_g_signal_handler (self, &priv->carrier_id);
+	ret = supplicant_check_secrets_needed (self, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         reason);
 	}
-
-	return supplicant_check_secrets_needed (self, out_failure_reason);
 }
 
 /*****************************************************************************/
@@ -1329,7 +1372,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMSettingConnection *s_con;
 	const char *connection_type;
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
+	gboolean do_postpone = FALSE;
 	NMSettingDcb *s_dcb;
 
 	s_con = nm_device_get_applied_setting (device, NM_TYPE_SETTING_CONNECTION);
@@ -1343,14 +1386,23 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	 * process opens the port up for normal traffic.
 	 */
 	connection_type = nm_setting_connection_get_connection_type (s_con);
-	if (!strcmp (connection_type, NM_SETTING_WIRED_SETTING_NAME)) {
+	if (nm_streq (connection_type, NM_SETTING_WIRED_SETTING_NAME)) {
 		NMSetting8021x *security;
 
 		security = nm_device_get_applied_setting (device, NM_TYPE_SETTING_802_1X);
 
 		if (security) {
 			/* FIXME: for now 802.1x is mutually exclusive with DCB */
-			return nm_8021x_stage2_config (self, out_failure_reason);
+			if (!nm_device_has_carrier (NM_DEVICE (self))) {
+				_LOGD (LOGD_DEVICE | LOGD_ETHER, "delay supplicant initialization until carrier goes up");
+				priv->carrier_id = g_signal_connect (self,
+				                                     "notify::" NM_DEVICE_CARRIER,
+				                                     G_CALLBACK (carrier_changed),
+				                                     self);
+				return NM_ACT_STAGE_RETURN_POSTPONE;
+			}
+
+			return supplicant_check_secrets_needed (self, out_failure_reason);
 		}
 	}
 
@@ -1372,7 +1424,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		}
 
 		priv->dcb_handle_carrier_changes = TRUE;
-		ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		do_postpone = TRUE;
 	}
 
 	/* PPPoE setup */
@@ -1382,11 +1434,13 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 		s_ppp = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPP);
 		if (s_ppp) {
-			guint32 mtu = 0, mru = 0, mxu;
+			guint32 mtu;
+			guint32 mru;
+			guint32 mxu;
 
 			mtu = nm_setting_ppp_get_mtu (s_ppp);
 			mru = nm_setting_ppp_get_mru (s_ppp);
-			mxu = mru > mtu ? mru : mtu;
+			mxu = MAX (mru, mtu);
 			if (mxu) {
 				_LOGD (LOGD_PPP, "set MTU to %u (PPP interface MRU %u, MTU %u)",
 				       mxu + PPPOE_ENCAP_OVERHEAD, mru, mtu);
@@ -1397,7 +1451,9 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		}
 	}
 
-	return ret;
+	return   do_postpone
+	       ? NM_ACT_STAGE_RETURN_POSTPONE
+	       : NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static NMActStageReturn
@@ -1428,7 +1484,7 @@ get_configured_mtu (NMDevice *device,
                     gboolean *out_force)
 {
 	/* MTU only set for plain ethernet */
-	if (NM_DEVICE_ETHERNET_GET_PRIVATE ((NMDeviceEthernet *) device)->ppp_manager)
+	if (NM_DEVICE_ETHERNET_GET_PRIVATE (device)->ppp_manager)
 		return 0;
 
 	return nm_device_get_configured_mtu_for_wired (device, out_source, out_force);
@@ -1468,7 +1524,7 @@ deactivate (NMDevice *device)
 
 	/* Set last PPPoE connection time */
 	if (nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE))
-		priv->last_pppoe_time = nm_utils_get_monotonic_timestamp_s ();
+		priv->last_pppoe_time = nm_utils_get_monotonic_timestamp_sec ();
 }
 
 static gboolean
@@ -1589,13 +1645,13 @@ get_s390_subchannels (NMDevice *device)
 {
 	nm_assert (NM_IS_DEVICE_ETHERNET (device));
 
-	return NM_DEVICE_ETHERNET_GET_PRIVATE ((NMDeviceEthernet *) device)->subchannels;
+	return NM_DEVICE_ETHERNET_GET_PRIVATE (device)->subchannels;
 }
 
 static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE ((NMDeviceEthernet *) device);
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (device);
 	NMSettingWired *s_wired = nm_connection_get_setting_wired (connection);
 	gboolean perm_hw_addr_is_fake;
 	const char *perm_hw_addr;
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index 1c1d5eaa..a7db2669 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -173,8 +173,8 @@ static GHashTable *factories_by_setting = NULL;
 static void __attribute__((destructor))
 _cleanup (void)
 {
-	g_clear_pointer (&factories_by_link, g_hash_table_unref);
-	g_clear_pointer (&factories_by_setting, g_hash_table_unref);
+	nm_clear_pointer (&factories_by_link, g_hash_table_unref);
+	nm_clear_pointer (&factories_by_setting, g_hash_table_unref);
 }
 
 NMDeviceFactory *
@@ -385,6 +385,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 	_ADD_INTERNAL (nm_tun_device_factory_get_type);
 	_ADD_INTERNAL (nm_veth_device_factory_get_type);
 	_ADD_INTERNAL (nm_vlan_device_factory_get_type);
+	_ADD_INTERNAL (nm_vrf_device_factory_get_type);
 	_ADD_INTERNAL (nm_vxlan_device_factory_get_type);
 	_ADD_INTERNAL (nm_wireguard_device_factory_get_type);
 	_ADD_INTERNAL (nm_wpan_device_factory_get_type);
diff --git a/src/devices/nm-device-generic.c b/src/devices/nm-device-generic.c
index 5f9e13c3..9883dc4e 100644
--- a/src/devices/nm-device-generic.c
+++ b/src/devices/nm-device-generic.c
@@ -32,7 +32,7 @@ struct _NMDeviceGenericClass {
 
 G_DEFINE_TYPE (NMDeviceGeneric, nm_device_generic, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_GENERIC_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceGeneric, NM_IS_DEVICE_GENERIC)
+#define NM_DEVICE_GENERIC_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceGeneric, NM_IS_DEVICE_GENERIC, NMDevice)
 
 /*****************************************************************************/
 
@@ -50,8 +50,8 @@ get_generic_capabilities (NMDevice *device)
 static const char *
 get_type_description (NMDevice *device)
 {
-	if (NM_DEVICE_GENERIC_GET_PRIVATE ((NMDeviceGeneric *) device)->type_description)
-		return NM_DEVICE_GENERIC_GET_PRIVATE ((NMDeviceGeneric *) device)->type_description;
+	if (NM_DEVICE_GENERIC_GET_PRIVATE (device)->type_description)
+		return NM_DEVICE_GENERIC_GET_PRIVATE (device)->type_description;
 	return NM_DEVICE_CLASS (nm_device_generic_parent_class)->get_type_description (device);
 }
 
@@ -64,7 +64,7 @@ realize_start_notify (NMDevice *device, const NMPlatformLink *plink)
 
 	NM_DEVICE_CLASS (nm_device_generic_parent_class)->realize_start_notify (device, plink);
 
-	g_clear_pointer (&priv->type_description, g_free);
+	nm_clear_g_free (&priv->type_description);
 	ifindex = nm_device_get_ip_ifindex (NM_DEVICE (self));
 	if (ifindex > 0)
 		priv->type_description = g_strdup (nm_platform_link_get_type_name (nm_device_get_platform (device), ifindex));
@@ -181,7 +181,7 @@ dispose (GObject *object)
 	NMDeviceGeneric *self = NM_DEVICE_GENERIC (object);
 	NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE (self);
 
-	g_clear_pointer (&priv->type_description, g_free);
+	nm_clear_g_free (&priv->type_description);
 
 	G_OBJECT_CLASS (nm_device_generic_parent_class)->dispose (object);
 }
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 9f79d9bb..0ef74d25 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -43,7 +43,7 @@ struct _NMDeviceInfinibandClass {
 
 G_DEFINE_TYPE (NMDeviceInfiniband, nm_device_infiniband, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_INFINIBAND_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceInfiniband, NM_IS_DEVICE_INFINIBAND)
+#define NM_DEVICE_INFINIBAND_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceInfiniband, NM_IS_DEVICE_INFINIBAND, NMDevice)
 
 /*****************************************************************************/
 
@@ -52,7 +52,7 @@ get_generic_capabilities (NMDevice *device)
 {
 	guint32 caps = NM_DEVICE_CAP_CARRIER_DETECT;
 
-	if (NM_DEVICE_INFINIBAND_GET_PRIVATE ((NMDeviceInfiniband *) device)->is_partition)
+	if (NM_DEVICE_INFINIBAND_GET_PRIVATE (device)->is_partition)
 		caps |= NM_DEVICE_CAP_IS_SOFTWARE;
 
 	return caps;
@@ -223,7 +223,7 @@ create_and_realize (NMDevice *device,
                     const NMPlatformLink **out_plink,
                     GError **error)
 {
-	NMDeviceInfinibandPrivate *priv = NM_DEVICE_INFINIBAND_GET_PRIVATE ((NMDeviceInfiniband *) device);
+	NMDeviceInfinibandPrivate *priv = NM_DEVICE_INFINIBAND_GET_PRIVATE (device);
 	NMSettingInfiniband *s_infiniband;
 	int r;
 
@@ -281,7 +281,7 @@ unrealize (NMDevice *device, GError **error)
 
 	g_return_val_if_fail (NM_IS_DEVICE_INFINIBAND (device), FALSE);
 
-	priv = NM_DEVICE_INFINIBAND_GET_PRIVATE ((NMDeviceInfiniband *) device);
+	priv = NM_DEVICE_INFINIBAND_GET_PRIVATE (device);
 
 	if (priv->p_key < 0) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
@@ -309,7 +309,7 @@ get_property (GObject *object, guint prop_id,
 {
 	switch (prop_id) {
 	case PROP_IS_PARTITION:
-		g_value_set_boolean (value, NM_DEVICE_INFINIBAND_GET_PRIVATE ((NMDeviceInfiniband *) object)->is_partition);
+		g_value_set_boolean (value, NM_DEVICE_INFINIBAND_GET_PRIVATE (object)->is_partition);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -323,7 +323,7 @@ set_property (GObject *object, guint prop_id,
 {
 	switch (prop_id) {
 	case PROP_IS_PARTITION:
-		NM_DEVICE_INFINIBAND_GET_PRIVATE ((NMDeviceInfiniband *) object)->is_partition = g_value_get_boolean (value);
+		NM_DEVICE_INFINIBAND_GET_PRIVATE (object)->is_partition = g_value_get_boolean (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 0becb5e5..0a33c82e 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -67,7 +67,7 @@ struct _NMDeviceIPTunnelClass {
 
 G_DEFINE_TYPE (NMDeviceIPTunnel, nm_device_ip_tunnel, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceIPTunnel, NM_IS_DEVICE_IP_TUNNEL)
+#define NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceIPTunnel, NM_IS_DEVICE_IP_TUNNEL, NMDevice)
 
 /*****************************************************************************/
 
@@ -96,35 +96,63 @@ ip6tnl_flags_plat_to_setting (guint32 flags)
 /*****************************************************************************/
 
 static gboolean
-address_equal_pp (int family, const char *a, const char *b)
+address_equal_pp (int addr_family, const char *a, const char *b)
 {
-	char buffer1[sizeof (struct in6_addr)] = { };
-	char buffer2[sizeof (struct in6_addr)] = { };
+	const NMIPAddr *addr_a = &nm_ip_addr_zero;
+	const NMIPAddr *addr_b = &nm_ip_addr_zero;
+	NMIPAddr addr_a_val;
+	NMIPAddr addr_b_val;
 
-	g_return_val_if_fail (family == AF_INET || family == AF_INET6, FALSE);
+	nm_assert_addr_family (addr_family);
 
-	if (a)
-		inet_pton (family, a, buffer1);
-	if (b)
-		inet_pton (family, b, buffer2);
+	if (a) {
+		if (!nm_utils_parse_inaddr_bin (addr_family, a, NULL, &addr_a_val))
+			nm_assert_not_reached ();
+		addr_a = &addr_a_val;
+	}
+	if (b) {
+		if (!nm_utils_parse_inaddr_bin (addr_family, b, NULL, &addr_b_val))
+			nm_assert_not_reached ();
+		addr_b = &addr_b_val;
+	}
 
-	return !memcmp (buffer1, buffer2,
-	                family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr));
+	return nm_ip_addr_equal (addr_family, addr_a, addr_b);
 }
 
 static gboolean
-address_equal_pn (int family, const char *a, const void *b)
+address_set (int addr_family,
+             char **p_addr,
+             const NMIPAddr *addr_new)
 {
-	char buffer1[sizeof (struct in6_addr)] = { };
+	nm_assert_addr_family (addr_family);
+	nm_assert (p_addr);
+	nm_assert (   !*p_addr
+	           || nm_utils_ipaddr_is_normalized (addr_family, *p_addr));
+
+	if (   !addr_new
+	    || nm_ip_addr_is_null (addr_family, addr_new)) {
+		if (nm_clear_g_free (p_addr))
+			return TRUE;
+		return FALSE;
+	}
 
-	g_return_val_if_fail (family == AF_INET || family == AF_INET6, FALSE);
+	if (*p_addr) {
+		NMIPAddr addr_val;
 
-	if (a)
-		inet_pton (family, a, buffer1);
+		if (!nm_utils_parse_inaddr_bin (addr_family,
+		                                *p_addr,
+		                                NULL,
+		                                &addr_val))
+			nm_assert_not_reached ();
 
-	return !memcmp (buffer1, b,
-	                family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr));
+		if (nm_ip_addr_equal (addr_family, &addr_val, addr_new))
+			return FALSE;
+
+		g_free (*p_addr);
+	}
 
+	*p_addr = nm_utils_inet_ntop_dup (addr_family, addr_new);
+	return TRUE;
 }
 
 static void
@@ -133,10 +161,8 @@ update_properties_from_ifindex (NMDevice *device, int ifindex)
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
 	int parent_ifindex = 0;
-	in_addr_t local4 = 0;
-	in_addr_t remote4 = 0;
-	struct in6_addr local6 = IN6ADDR_ANY_INIT;
-	struct in6_addr remote6 = IN6ADDR_ANY_INIT;
+	NMIPAddr local = NM_IP_ADDR_INIT;
+	NMIPAddr remote = NM_IP_ADDR_INIT;
 	guint8 ttl = 0;
 	guint8 tos = 0;
 	guint8 encap_limit = 0;
@@ -149,19 +175,19 @@ update_properties_from_ifindex (NMDevice *device, int ifindex)
 clear:
 		nm_device_parent_set_ifindex (device, 0);
 		if (priv->local) {
-			g_clear_pointer (&priv->local, g_free);
+			nm_clear_g_free (&priv->local);
 			_notify (self, PROP_LOCAL);
 		}
 		if (priv->remote) {
-			g_clear_pointer (&priv->remote, g_free);
+			nm_clear_g_free (&priv->remote);
 			_notify (self, PROP_REMOTE);
 		}
 		if (priv->input_key) {
-			g_clear_pointer (&priv->input_key, g_free);
+			nm_clear_g_free (&priv->input_key);
 			_notify (self, PROP_INPUT_KEY);
 		}
 		if (priv->output_key) {
-			g_clear_pointer (&priv->output_key, g_free);
+			nm_clear_g_free (&priv->output_key);
 			_notify (self, PROP_OUTPUT_KEY);
 		}
 
@@ -181,8 +207,8 @@ clear:
 		}
 
 		parent_ifindex = lnk->parent_ifindex;
-		local4 = lnk->local;
-		remote4 = lnk->remote;
+		local.addr4 = lnk->local;
+		remote.addr4 = lnk->remote;
 		ttl = lnk->ttl;
 		tos = lnk->tos;
 		pmtud = lnk->path_mtu_discovery;
@@ -197,7 +223,7 @@ clear:
 				g_free (key);
 		} else {
 			if (priv->input_key) {
-				g_clear_pointer (&priv->input_key, g_free);
+				nm_clear_g_free (&priv->input_key);
 				_notify (self, PROP_INPUT_KEY);
 			}
 		}
@@ -212,7 +238,7 @@ clear:
 				g_free (key);
 		} else {
 			if (priv->output_key) {
-				g_clear_pointer (&priv->output_key, g_free);
+				nm_clear_g_free (&priv->output_key);
 				_notify (self, PROP_OUTPUT_KEY);
 			}
 		}
@@ -226,8 +252,8 @@ clear:
 		}
 
 		parent_ifindex = lnk->parent_ifindex;
-		local4 = lnk->local;
-		remote4 = lnk->remote;
+		local.addr4 = lnk->local;
+		remote.addr4 = lnk->remote;
 		ttl = lnk->ttl;
 		tos = lnk->tos;
 		pmtud = lnk->path_mtu_discovery;
@@ -241,8 +267,8 @@ clear:
 		}
 
 		parent_ifindex = lnk->parent_ifindex;
-		local4 = lnk->local;
-		remote4 = lnk->remote;
+		local.addr4 = lnk->local;
+		remote.addr4 = lnk->remote;
 		ttl = lnk->ttl;
 		tos = lnk->tos;
 		pmtud = lnk->path_mtu_discovery;
@@ -267,8 +293,8 @@ clear:
 		}
 
 		parent_ifindex = lnk->parent_ifindex;
-		local6 = lnk->local;
-		remote6 = lnk->remote;
+		local.addr6 = lnk->local;
+		remote.addr6 = lnk->remote;
 		ttl = lnk->ttl;
 		tos = lnk->tclass;
 		encap_limit = lnk->encap_limit;
@@ -288,7 +314,7 @@ clear:
 					g_free (key);
 			} else {
 				if (priv->input_key) {
-					g_clear_pointer (&priv->input_key, g_free);
+					nm_clear_g_free (&priv->input_key);
 					_notify (self, PROP_INPUT_KEY);
 				}
 			}
@@ -303,7 +329,7 @@ clear:
 					g_free (key);
 			} else {
 				if (priv->output_key) {
-					g_clear_pointer (&priv->output_key, g_free);
+					nm_clear_g_free (&priv->output_key);
 					_notify (self, PROP_OUTPUT_KEY);
 				}
 			}
@@ -313,35 +339,10 @@ clear:
 
 	nm_device_parent_set_ifindex (device, parent_ifindex);
 
-	if (priv->addr_family == AF_INET) {
-		if (!address_equal_pn (AF_INET, priv->local, &local4)) {
-			g_clear_pointer (&priv->local, g_free);
-			if (local4)
-				priv->local = nm_utils_inet4_ntop_dup (local4);
-			_notify (self, PROP_LOCAL);
-		}
-
-		if (!address_equal_pn (AF_INET, priv->remote, &remote4)) {
-			g_clear_pointer (&priv->remote, g_free);
-			if (remote4)
-				priv->remote = nm_utils_inet4_ntop_dup (remote4);
-			_notify (self, PROP_REMOTE);
-		}
-	} else {
-		if (!address_equal_pn (AF_INET6, priv->local, &local6)) {
-			g_clear_pointer (&priv->local, g_free);
-			if (memcmp (&local6, &in6addr_any, sizeof (in6addr_any)))
-				priv->local = nm_utils_inet6_ntop_dup (&local6);
-			_notify (self, PROP_LOCAL);
-		}
-
-		if (!address_equal_pn (AF_INET6, priv->remote, &remote6)) {
-			g_clear_pointer (&priv->remote, g_free);
-			if (memcmp (&remote6, &in6addr_any, sizeof (in6addr_any)))
-				priv->remote = nm_utils_inet6_ntop_dup (&remote6);
-			_notify (self, PROP_REMOTE);
-		}
-	}
+	if (address_set (priv->addr_family, &priv->local, &local))
+		_notify (self, PROP_LOCAL);
+	if (address_set (priv->addr_family, &priv->remote, &remote))
+		_notify (self, PROP_REMOTE);
 
 out:
 
@@ -657,11 +658,44 @@ create_and_realize (NMDevice *device,
 	gint64 val;
 	NMIPTunnelMode mode;
 	int r;
+	gs_free char *hwaddr = NULL;
+	guint8 mac_address[ETH_ALEN];
+	gboolean mac_address_valid = FALSE;
 
 	s_ip_tunnel = nm_connection_get_setting_ip_tunnel (connection);
-	g_assert (s_ip_tunnel);
+	nm_assert (NM_IS_SETTING_IP_TUNNEL (s_ip_tunnel));
 
 	mode = nm_setting_ip_tunnel_get_mode (s_ip_tunnel);
+
+	if (   nm_device_hw_addr_get_cloned (device,
+	                                     connection,
+	                                     FALSE,
+	                                     &hwaddr,
+	                                     NULL,
+	                                     NULL)
+	    && hwaddr) {
+		/* FIXME: we set the MAC address when creating the interface, while the
+		 * NMDevice is still unrealized. As we afterwards realize the device, it
+		 * forgets the parameters for the cloned MAC address, and in stage 1
+		 * it might create a different MAC address. That should be fixed by
+		 * better handling device realization. */
+		if (!nm_utils_hwaddr_aton (hwaddr, mac_address, ETH_ALEN)) {
+			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+			             "Invalid hardware address '%s'",
+			             hwaddr);
+			g_return_val_if_reached (FALSE);
+		}
+
+		if (NM_IN_SET (mode, NM_IP_TUNNEL_MODE_GRE)) {
+			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+			             "Invalid hardware address '%s' for tunnel type",
+			             hwaddr);
+			return FALSE;
+		}
+
+		mac_address_valid = TRUE;
+	}
+
 	switch (mode) {
 	case NM_IP_TUNNEL_MODE_GRETAP:
 		lnk_gre.is_tap = TRUE;
@@ -702,7 +736,12 @@ create_and_realize (NMDevice *device,
 			lnk_gre.output_flags = NM_GRE_KEY;
 		}
 
-		r = nm_platform_link_gre_add (nm_device_get_platform (device), iface, &lnk_gre, out_plink);
+		r = nm_platform_link_gre_add (nm_device_get_platform (device),
+		                              iface,
+		                              mac_address_valid ? mac_address : NULL,
+		                              mac_address_valid ? ETH_ALEN : 0,
+		                              &lnk_gre,
+		                              out_plink);
 		if (r < 0) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 			             "Failed to create GRE interface '%s' for '%s': %s",
@@ -896,7 +935,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE ((NMDeviceIPTunnel *) object);
+	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_MODE:
@@ -942,7 +981,7 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE ((NMDeviceIPTunnel *) object);
+	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_MODE:
@@ -963,7 +1002,7 @@ nm_device_ip_tunnel_init (NMDeviceIPTunnel *self)
 static void
 constructed (GObject *object)
 {
-	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE ((NMDeviceIPTunnel *) object);
+	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (object);
 
 	if (NM_IN_SET (priv->mode,
 	               NM_IP_TUNNEL_MODE_IPIP6,
@@ -983,10 +1022,10 @@ dispose (GObject *object)
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (object);
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
 
-	g_clear_pointer (&priv->local, g_free);
-	g_clear_pointer (&priv->remote, g_free);
-	g_clear_pointer (&priv->input_key, g_free);
-	g_clear_pointer (&priv->output_key, g_free);
+	nm_clear_g_free (&priv->local);
+	nm_clear_g_free (&priv->remote);
+	nm_clear_g_free (&priv->input_key);
+	nm_clear_g_free (&priv->output_key);
 
 	G_OBJECT_CLASS (nm_device_ip_tunnel_parent_class)->dispose (object);
 }
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index c9592a49..f1878078 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -23,16 +23,9 @@ _LOG_DECLARE_SELF(NMDeviceMacsec);
 
 /*****************************************************************************/
 
-typedef struct Supplicant {
-	NMSupplicantManager *mgr;
-	NMSupplicantInterface *iface;
+#define SUPPLICANT_LNK_TIMEOUT_SEC 15
 
-	/* signal handler ids */
-	gulong iface_state_id;
-
-	/* Timeouts and idles */
-	guint con_timeout_id;
-} Supplicant;
+/*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceMacsec,
 	PROP_SCI,
@@ -53,8 +46,20 @@ typedef struct {
 	NMPlatformLnkMacsec props;
 	gulong parent_state_id;
 	gulong parent_mtu_id;
-	Supplicant supplicant;
-	guint supplicant_timeout_id;
+
+	struct {
+		NMSupplicantManager *mgr;
+		NMSupplMgrCreateIfaceHandle *create_handle;
+		NMSupplicantInterface *iface;
+
+		gulong iface_state_id;
+
+		guint con_timeout_id;
+		guint lnk_timeout_id;
+
+		bool is_associated:1;
+	} supplicant;
+
 	NMActRequestGetSecretsCallId *macsec_secrets_id;
 } NMDeviceMacsecPrivate;
 
@@ -69,7 +74,7 @@ struct _NMDeviceMacsecClass {
 
 G_DEFINE_TYPE (NMDeviceMacsec, nm_device_macsec, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_MACSEC_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceMacsec, NM_IS_DEVICE_MACSEC)
+#define NM_DEVICE_MACSEC_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceMacsec, NM_IS_DEVICE_MACSEC, NMDevice)
 
 /******************************************************************/
 
@@ -77,7 +82,8 @@ static void macsec_secrets_cancel (NMDeviceMacsec *self);
 
 /******************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (validation_mode_to_string, guint8,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (validation_mode_to_string, guint8,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("<unknown>"),
 	NM_UTILS_LOOKUP_STR_ITEM (0, "disable"),
 	NM_UTILS_LOOKUP_STR_ITEM (1, "check"),
@@ -226,7 +232,7 @@ build_supplicant_config (NMDeviceMacsec *self, GError **error)
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
 
-	config = nm_supplicant_config_new (FALSE, FALSE, FALSE, FALSE);
+	config = nm_supplicant_config_new (NM_SUPPL_CAP_MASK_NONE);
 
 	s_macsec = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
 
@@ -253,7 +259,9 @@ supplicant_interface_release (NMDeviceMacsec *self)
 {
 	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->supplicant_timeout_id);
+	nm_clear_pointer (&priv->supplicant.create_handle, nm_supplicant_manager_create_interface_cancel);
+
+	nm_clear_g_source (&priv->supplicant.lnk_timeout_id);
 	nm_clear_g_source (&priv->supplicant.con_timeout_id);
 	nm_clear_g_signal_handler (priv->supplicant.iface, &priv->supplicant.iface_state_id);
 
@@ -264,21 +272,6 @@ supplicant_interface_release (NMDeviceMacsec *self)
 }
 
 static void
-supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
-                           GError *error,
-                           gpointer user_data)
-{
-	NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
-
-	if (error && !nm_utils_error_is_cancelled (error, TRUE)) {
-		supplicant_interface_release (self);
-		nm_device_queue_state (NM_DEVICE (self),
-		                       NM_DEVICE_STATE_FAILED,
-		                       NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
-	}
-}
-
-static void
 macsec_secrets_cb (NMActRequest *req,
                    NMActRequestGetSecretsCallId *call_id,
                    NMSettingsConnection *connection,
@@ -313,7 +306,7 @@ macsec_secrets_cb (NMActRequest *req,
 		return;
 	}
 
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -350,7 +343,7 @@ macsec_secrets_get_secrets (NMDeviceMacsec *self,
 }
 
 static gboolean
-link_timeout_cb (gpointer user_data)
+supplicant_lnk_timeout_cb (gpointer user_data)
 {
 	NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
 	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
@@ -359,7 +352,7 @@ link_timeout_cb (gpointer user_data)
 	NMConnection *applied_connection;
 	const char *setting_name;
 
-	priv->supplicant_timeout_id = 0;
+	priv->supplicant.lnk_timeout_id = 0;
 
 	req = nm_device_get_act_request (dev);
 
@@ -367,7 +360,7 @@ link_timeout_cb (gpointer user_data)
 		nm_device_state_changed (dev,
 		                         NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
-		return FALSE;
+		return G_SOURCE_REMOVE;
 	}
 
 	/* Disconnect event during initial authentication and credentials
@@ -391,13 +384,98 @@ link_timeout_cb (gpointer user_data)
 	nm_device_state_changed (dev, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
 	macsec_secrets_get_secrets (self, setting_name, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
 
-	return FALSE;
+	return G_SOURCE_REMOVE;
 
 time_out:
 	_LOGW (LOGD_DEVICE | LOGD_ETHER, "link timed out.");
 	nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
 
-	return FALSE;
+	return G_SOURCE_REMOVE;
+}
+
+static void
+supplicant_iface_state_is_completed (NMDeviceMacsec *self,
+                                     NMSupplicantInterfaceState state)
+{
+	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
+
+	if (state == NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) {
+		nm_clear_g_source (&priv->supplicant.lnk_timeout_id);
+		nm_clear_g_source (&priv->supplicant.con_timeout_id);
+
+		nm_device_bring_up (NM_DEVICE (self), TRUE, NULL);
+
+		/* If this is the initial association during device activation,
+		 * schedule the next activation stage.
+		 */
+		if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_CONFIG) {
+			_LOGI (LOGD_DEVICE,
+			       "Activation: Stage 2 of 5 (Device Configure) successful.");
+			nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
+		}
+		return;
+	}
+
+	if (   !priv->supplicant.lnk_timeout_id
+	    && !priv->supplicant.con_timeout_id)
+		priv->supplicant.lnk_timeout_id = g_timeout_add_seconds (SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self);
+}
+
+static void
+supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
+                           GError *error,
+                           gpointer user_data)
+{
+	NMDeviceMacsec *self;
+	NMDeviceMacsecPrivate *priv;
+
+	if (nm_utils_error_is_cancelled_or_disposing (error))
+		return;
+
+	self = user_data;
+	priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
+
+	if (error) {
+		supplicant_interface_release (self);
+		nm_device_queue_state (NM_DEVICE (self),
+		                       NM_DEVICE_STATE_FAILED,
+		                       NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
+		return;
+	}
+
+	nm_assert (!priv->supplicant.lnk_timeout_id);
+	nm_assert (!priv->supplicant.is_associated);
+
+	priv->supplicant.is_associated = TRUE;
+	supplicant_iface_state_is_completed (self,
+	                                     nm_supplicant_interface_get_state (priv->supplicant.iface));
+}
+
+static gboolean
+supplicant_iface_start (NMDeviceMacsec *self)
+{
+	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
+	gs_unref_object NMSupplicantConfig *config = NULL;
+	gs_free_error GError *error = NULL;
+
+	config = build_supplicant_config (self, &error);
+	if (!config) {
+		_LOGE (LOGD_DEVICE,
+		       "Activation: couldn't build security configuration: %s",
+		       error->message);
+		supplicant_interface_release (self);
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
+		return FALSE;
+	}
+
+	nm_supplicant_interface_disconnect (priv->supplicant.iface);
+	nm_supplicant_interface_assoc (priv->supplicant.iface,
+	                               config,
+	                               supplicant_iface_assoc_cb,
+	                               self);
+	return TRUE;
 }
 
 static void
@@ -409,76 +487,31 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 {
 	NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
 	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
-	NMDevice *device = NM_DEVICE (self);
-	NMSupplicantConfig *config;
-	NMDeviceState devstate;
-	GError *error = NULL;
 	NMSupplicantInterfaceState new_state = new_state_i;
 	NMSupplicantInterfaceState old_state = old_state_i;
 
-	if (new_state == old_state)
-		return;
-
 	_LOGI (LOGD_DEVICE, "supplicant interface state: %s -> %s",
 	       nm_supplicant_interface_state_to_string (old_state),
 	       nm_supplicant_interface_state_to_string (new_state));
 
-	devstate = nm_device_get_state (device);
-
-	switch (new_state) {
-	case NM_SUPPLICANT_INTERFACE_STATE_READY:
-		config = build_supplicant_config (self, &error);
-		if (config) {
-			nm_supplicant_interface_assoc (priv->supplicant.iface, config,
-			                               supplicant_iface_assoc_cb, self);
-			g_object_unref (config);
-		} else {
-			_LOGE (LOGD_DEVICE,
-			       "Activation: couldn't build security configuration: %s",
-			       error->message);
-			g_clear_error (&error);
-
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
-		}
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
-		nm_clear_g_source (&priv->supplicant_timeout_id);
-		nm_clear_g_source (&priv->supplicant.con_timeout_id);
-		nm_device_bring_up (device, TRUE, NULL);
-
-		/* If this is the initial association during device activation,
-		 * schedule the next activation stage.
-		 */
-		if (devstate == NM_DEVICE_STATE_CONFIG) {
-			_LOGI (LOGD_DEVICE,
-			       "Activation: Stage 2 of 5 (Device Configure) successful.");
-			nm_device_activate_schedule_stage3_ip_config_start (device);
-		}
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED:
-		if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) {
-			/* Start the link timeout so we allow some time for reauthentication */
-			if (!priv->supplicant_timeout_id)
-				priv->supplicant_timeout_id = g_timeout_add_seconds (15, link_timeout_cb, device);
-		}
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
 		supplicant_interface_release (self);
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
+	}
 
-		if ((devstate == NM_DEVICE_STATE_ACTIVATED) || nm_device_is_activating (device)) {
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		}
-		break;
-	default:
-		;
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		if (!supplicant_iface_start (self))
+			return;
 	}
+
+	if (priv->supplicant.is_associated)
+		supplicant_iface_state_is_completed (self, new_state);
 }
 
-static NMActStageReturn
+static gboolean
 handle_auth_or_fail (NMDeviceMacsec *self,
                      NMActRequest *req,
                      gboolean new_secrets)
@@ -487,7 +520,7 @@ handle_auth_or_fail (NMDeviceMacsec *self,
 	NMConnection *applied_connection;
 
 	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
-		return NM_ACT_STAGE_RETURN_FAILURE;
+		return FALSE;
 
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
@@ -497,13 +530,13 @@ handle_auth_or_fail (NMDeviceMacsec *self,
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (!setting_name) {
 		_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
-		return NM_ACT_STAGE_RETURN_FAILURE;
+		return FALSE;
 	}
 
 	macsec_secrets_get_secrets (self, setting_name,
 	                              NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
 	                            | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
-	return NM_ACT_STAGE_RETURN_POSTPONE;
+	return TRUE;
 }
 
 static gboolean
@@ -526,11 +559,10 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	       "Activation: (macsec) association took too long.");
 
 	supplicant_interface_release (self);
-	req = nm_device_get_act_request (device);
-	g_assert (req);
 
+	req = nm_device_get_act_request (device);
 	connection = nm_act_request_get_settings_connection (req);
-	g_assert (connection);
+	g_return_val_if_fail (connection, G_SOURCE_REMOVE);
 
 	/* Ask for new secrets only if we've never activated this connection
 	 * before.  If we've connected before, don't bother the user with dialogs,
@@ -539,48 +571,73 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	if (nm_settings_connection_get_timestamp (connection, &timestamp))
 		new_secrets = !timestamp;
 
-	if (handle_auth_or_fail (self, req, new_secrets) == NM_ACT_STAGE_RETURN_POSTPONE)
-		_LOGW (LOGD_DEVICE, "Activation: (macsec) asking for new secrets");
-	else
+	if (!handle_auth_or_fail (self, req, new_secrets)) {
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
+		return G_SOURCE_REMOVE;
+	}
+
+	_LOGW (LOGD_DEVICE, "Activation: (macsec) asking for new secrets");
 
-	return FALSE;
+	if (   !priv->supplicant.lnk_timeout_id
+	    && priv->supplicant.iface) {
+		NMSupplicantInterfaceState state;
+
+		state = nm_supplicant_interface_get_state (priv->supplicant.iface);
+		if (state != NM_SUPPLICANT_INTERFACE_STATE_COMPLETED
+		    && nm_supplicant_interface_state_is_operational (state))
+			priv->supplicant.lnk_timeout_id = g_timeout_add_seconds (SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self);
+	}
+
+	return G_SOURCE_REMOVE;
 }
 
-static gboolean
-supplicant_interface_init (NMDeviceMacsec *self)
+static void
+supplicant_interface_create_cb (NMSupplicantManager *supplicant_manager,
+                                NMSupplMgrCreateIfaceHandle *handle,
+                                NMSupplicantInterface *iface,
+                                GError *error,
+                                gpointer user_data)
 {
-	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
-	NMDevice *parent;
+	NMDeviceMacsec *self;
+	NMDeviceMacsecPrivate *priv;
 	guint timeout;
 
-	parent = nm_device_parent_get_device (NM_DEVICE (self));
-	g_return_val_if_fail (parent, FALSE);
+	if (nm_utils_error_is_cancelled (error))
+		return;
 
-	supplicant_interface_release (self);
+	self = user_data;
+	priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
 
-	priv->supplicant.iface = nm_supplicant_manager_create_interface (priv->supplicant.mgr,
-	                                                                 nm_device_get_iface (parent),
-	                                                                 NM_SUPPLICANT_DRIVER_MACSEC);
+	nm_assert (priv->supplicant.create_handle == handle);
 
-	if (!priv->supplicant.iface) {
+	priv->supplicant.create_handle = NULL;
+
+	if (error) {
 		_LOGE (LOGD_DEVICE,
-		       "Couldn't initialize supplicant interface");
-		return FALSE;
+		       "Couldn't initialize supplicant interface: %s",
+		       error->message);
+		supplicant_interface_release (self);
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
 	}
 
-	/* Listen for its state signals */
+	priv->supplicant.iface = g_object_ref (iface);
+	priv->supplicant.is_associated = FALSE;
+
 	priv->supplicant.iface_state_id = g_signal_connect (priv->supplicant.iface,
 	                                                    NM_SUPPLICANT_INTERFACE_STATE,
 	                                                    G_CALLBACK (supplicant_iface_state_cb),
 	                                                    self);
 
-	/* Set up a timeout on the connection attempt  */
 	timeout = nm_device_get_supplicant_timeout (NM_DEVICE (self));
 	priv->supplicant.con_timeout_id = g_timeout_add_seconds (timeout,
 	                                                         supplicant_connection_timeout_cb,
 	                                                         self);
-	return TRUE;
+
+	if (nm_supplicant_interface_state_is_operational (nm_supplicant_interface_get_state (iface)))
+		supplicant_iface_start (self);
 }
 
 static NMActStageReturn
@@ -589,8 +646,9 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceMacsec *self = NM_DEVICE_MACSEC (device);
 	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
 	NMConnection *connection;
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDevice *parent;
 	const char *setting_name;
+	int ifindex;
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
 
@@ -608,21 +666,31 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		       "Activation: connection '%s' has security, but secrets are required.",
 		       nm_connection_get_id (connection));
 
-		ret = handle_auth_or_fail (self, req, FALSE);
-		if (ret != NM_ACT_STAGE_RETURN_POSTPONE)
+		if (!handle_auth_or_fail (self, req, FALSE)) {
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-	} else {
-		_LOGI (LOGD_DEVICE | LOGD_ETHER,
-		       "Activation: connection '%s' requires no security. No secrets needed.",
-		       nm_connection_get_id (connection));
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
 
-		if (supplicant_interface_init (self))
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		else
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
-	return ret;
+	_LOGI (LOGD_DEVICE | LOGD_ETHER,
+	       "Activation: connection '%s' requires no security. No secrets needed.",
+	       nm_connection_get_id (connection));
+
+	supplicant_interface_release (self);
+
+	parent = nm_device_parent_get_device (NM_DEVICE (self));
+	g_return_val_if_fail (parent, NM_ACT_STAGE_RETURN_FAILURE);
+	ifindex = nm_device_get_ifindex (parent);
+	g_return_val_if_fail (ifindex > 0, NM_ACT_STAGE_RETURN_FAILURE);
+
+	priv->supplicant.create_handle = nm_supplicant_manager_create_interface (priv->supplicant.mgr,
+	                                                                         ifindex,
+	                                                                         NM_SUPPLICANT_DRIVER_MACSEC,
+	                                                                         supplicant_interface_create_cb,
+	                                                                         self);
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 static void
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 3633f02d..2f8c4c32 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -49,7 +49,7 @@ struct _NMDeviceMacvlanClass {
 
 G_DEFINE_TYPE (NMDeviceMacvlan, nm_device_macvlan, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_MACVLAN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceMacvlan, NM_IS_DEVICE_MACVLAN)
+#define NM_DEVICE_MACVLAN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceMacvlan, NM_IS_DEVICE_MACVLAN, NMDevice)
 
 /*****************************************************************************/
 
@@ -292,7 +292,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE ((NMDeviceMacvlan *) device);
+	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (device);
 	NMSettingMacvlan *s_macvlan;
 	const char *parent = NULL;
 
@@ -390,7 +390,7 @@ complete_connection (NMDevice *device,
 static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
-	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE ((NMDeviceMacvlan *) device);
+	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (device);
 	NMSettingMacvlan *s_macvlan = nm_connection_get_setting_macvlan (connection);
 	int new_mode;
 
@@ -422,7 +422,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE ((NMDeviceMacvlan *) object);
+	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_MODE:
@@ -444,7 +444,7 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE ((NMDeviceMacvlan *) object);
+	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_TAP:
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 32403ff6..52784143 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -39,7 +39,7 @@ struct _NMDevicePppClass {
 
 G_DEFINE_TYPE (NMDevicePpp, nm_device_ppp, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_PPP_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDevicePpp, NM_IS_DEVICE_PPP)
+#define NM_DEVICE_PPP_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDevicePpp, NM_IS_DEVICE_PPP, NMDevice)
 
 static NMDeviceCapabilities
 get_generic_capabilities (NMDevice *device)
@@ -137,11 +137,9 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	GError *error = NULL;
 
 	req = nm_device_get_act_request (device);
-
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
 
 	s_pppoe = nm_device_get_applied_setting (device, NM_TYPE_SETTING_PPPOE);
-
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
 	g_clear_object (&priv->ip4_config);
@@ -157,9 +155,12 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	}
 
 	if (   !priv->ppp_manager
-	    || !nm_ppp_manager_start (priv->ppp_manager, req,
+	    || !nm_ppp_manager_start (priv->ppp_manager,
+	                              req,
 	                              nm_setting_pppoe_get_username (s_pppoe),
-	                              30, 0, &error)) {
+	                              30,
+	                              0,
+	                              &error)) {
 		_LOGW (LOGD_DEVICE | LOGD_PPP, "PPPoE failed to start: %s", error->message);
 		g_error_free (error);
 
@@ -169,16 +170,18 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
+	g_signal_connect (priv->ppp_manager,
+	                  NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
 	                  G_CALLBACK (ppp_state_changed),
 	                  self);
-	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
+	g_signal_connect (priv->ppp_manager,
+	                  NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
 	                  G_CALLBACK (ppp_ifindex_set),
 	                  self);
-	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
+	g_signal_connect (priv->ppp_manager,
+	                  NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
 	                  G_CALLBACK (ppp_ip4_config),
 	                  self);
-
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index e87733ef..8d539026 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -71,8 +71,10 @@ gboolean nm_device_hw_addr_reset (NMDevice *device, const char *detail);
 
 void nm_device_set_firmware_missing (NMDevice *self, gboolean missing);
 
-void nm_device_activate_schedule_stage1_device_prepare (NMDevice *device);
-void nm_device_activate_schedule_stage2_device_config (NMDevice *device);
+void nm_device_activate_schedule_stage1_device_prepare (NMDevice *device,
+                                                        gboolean do_sync);
+void nm_device_activate_schedule_stage2_device_config (NMDevice *device,
+                                                       gboolean do_sync);
 
 void nm_device_activate_schedule_ip_config_result (NMDevice *device,
                                                    int addr_family,
@@ -180,15 +182,14 @@ void nm_device_commit_mtu (NMDevice *self);
 	((NM_NARG (__VA_ARGS__) == 0) \
 	  ? NULL \
 	  : ({ \
-	      static const struct { \
-	          const NMLinkType types[NM_NARG (__VA_ARGS__)]; \
-	          const NMLinkType sentinel; \
-	      } _link_types = { \
-	          .types = { __VA_ARGS__ }, \
-	          .sentinel = NM_LINK_TYPE_NONE, \
+	      static const NMLinkType _types[NM_NARG (__VA_ARGS__) + 1] = { \
+	          __VA_ARGS__ \
+	          _NM_MACRO_COMMA_IF_ARGS (__VA_ARGS__) \
+	          NM_LINK_TYPE_NONE, \
 	      }; \
 	      \
-	      _link_types.types; \
+	      nm_assert (_types[NM_NARG (__VA_ARGS__)] == NM_LINK_TYPE_NONE); \
+	      _types; \
 	    })\
 	)
 
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index 77b46a59..8d4680e2 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -48,7 +48,7 @@ struct _NMDeviceTunClass {
 
 G_DEFINE_TYPE (NMDeviceTun, nm_device_tun, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_TUN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceTun, NM_IS_DEVICE_TUN)
+#define NM_DEVICE_TUN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceTun, NM_IS_DEVICE_TUN, NMDevice)
 
 /*****************************************************************************/
 
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index b6efeb81..183e9eff 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -182,7 +182,7 @@ update_properties (NMDevice *device)
 
 	g_return_if_fail (NM_IS_DEVICE_VLAN (device));
 
-	priv = NM_DEVICE_VLAN_GET_PRIVATE ((NMDeviceVlan *) device);
+	priv = NM_DEVICE_VLAN_GET_PRIVATE (device);
 
 	ifindex = nm_device_get_ifindex (device);
 
@@ -221,7 +221,7 @@ create_and_realize (NMDevice *device,
                     const NMPlatformLink **out_plink,
                     GError **error)
 {
-	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE ((NMDeviceVlan *) device);
+	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (device);
 	const char *iface = nm_device_get_iface (device);
 	NMSettingVlan *s_vlan;
 	int parent_ifindex;
@@ -318,7 +318,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE ((NMDeviceVlan *) device);
+	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (device);
 	NMSettingVlan *s_vlan;
 	const char *parent;
 
diff --git a/src/devices/nm-device-vrf.c b/src/devices/nm-device-vrf.c
new file mode 100644
index 00000000..05ee7a8c
--- /dev/null
+++ b/src/devices/nm-device-vrf.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include "nm-default.h"
+
+#include "nm-device-vrf.h"
+
+#include "nm-core-internal.h"
+#include "nm-device-factory.h"
+#include "nm-device-private.h"
+#include "nm-manager.h"
+#include "nm-setting-vrf.h"
+#include "platform/nm-platform.h"
+#include "settings/nm-settings.h"
+
+#include "nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceVrf);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceVrf,
+	PROP_TABLE,
+);
+
+typedef struct {
+	NMPlatformLnkVrf props;
+} NMDeviceVrfPrivate;
+
+struct _NMDeviceVrf {
+	NMDevice parent;
+	NMDeviceVrfPrivate _priv;
+};
+
+struct _NMDeviceVrfClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceVrf, nm_device_vrf, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_VRF_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceVrf, NM_IS_DEVICE_VRF, NMDevice)
+
+/*****************************************************************************/
+
+static void
+do_update_properties (NMDeviceVrf *self, const NMPlatformLnkVrf *props)
+{
+	NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE (self);
+	GObject *object = G_OBJECT (self);
+	NMPlatformLnkVrf props_null;
+
+	if (!props) {
+		props_null = (NMPlatformLnkVrf) { };
+		props = &props_null;
+	}
+
+	g_object_freeze_notify (object);
+
+#define CHECK_PROPERTY_CHANGED(field, prop) \
+	G_STMT_START { \
+		if (priv->props.field != props->field) { \
+			priv->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+	CHECK_PROPERTY_CHANGED (table, PROP_TABLE);
+
+	g_object_thaw_notify (object);
+}
+
+static void
+update_properties (NMDevice *device)
+{
+	NMDeviceVrf *self = NM_DEVICE_VRF (device);
+	const NMPlatformLnkVrf *props;
+
+	props = nm_platform_link_get_lnk_vrf (nm_device_get_platform (device), nm_device_get_ifindex (device), NULL);
+	if (!props) {
+		_LOGW (LOGD_PLATFORM, "could not get vrf properties");
+		return;
+	}
+
+	do_update_properties (self, props);
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *dev)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static void
+link_changed (NMDevice *device,
+              const NMPlatformLink *pllink)
+{
+	NM_DEVICE_CLASS (nm_device_vrf_parent_class)->link_changed (device, pllink);
+	update_properties (device);
+}
+
+static void
+unrealize_notify (NMDevice *device)
+{
+	NMDeviceVrf *self = NM_DEVICE_VRF (device);
+
+	NM_DEVICE_CLASS (nm_device_vrf_parent_class)->unrealize_notify (device);
+
+	do_update_properties (self, NULL);
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	const char *iface = nm_device_get_iface (device);
+	NMPlatformLnkVrf props = { };
+	NMSettingVrf *s_vrf;
+	int r;
+
+	s_vrf = _nm_connection_get_setting (connection, NM_TYPE_SETTING_VRF);
+	nm_assert (s_vrf);
+
+	props.table = nm_setting_vrf_get_table (s_vrf);
+
+	r = nm_platform_link_vrf_add (nm_device_get_platform (device), iface, &props, out_plink);
+	if (r < 0) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
+		             "Failed to create VRF interface '%s' for '%s': %s",
+		             iface,
+		             nm_connection_get_id (connection),
+		             nm_strerror (r));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
+{
+	NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE (device);
+	NMSettingVrf *s_vrf;
+
+	if (!NM_DEVICE_CLASS (nm_device_vrf_parent_class)->check_connection_compatible (device, connection, error))
+		return FALSE;
+
+	if (nm_device_is_real (device)) {
+		s_vrf = _nm_connection_get_setting (connection, NM_TYPE_SETTING_VRF);
+
+		if (priv->props.table != nm_setting_vrf_get_table (s_vrf)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vrf table mismatches");
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMSettingVrf *s_vrf;
+
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_VRF_SETTING_NAME,
+	                           existing_connections,
+	                           NULL,
+	                           _("VRF connection"),
+	                           NULL,
+	                           NULL,
+	                           TRUE);
+
+	s_vrf = _nm_connection_get_setting (connection, NM_TYPE_SETTING_VRF);
+	if (!s_vrf) {
+		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "A 'vrf' setting is required.");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+update_connection (NMDevice *device, NMConnection *connection)
+{
+	NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE (device);
+	NMSettingVrf *s_vrf = _nm_connection_get_setting (connection, NM_TYPE_SETTING_VRF);
+
+	if (!s_vrf) {
+		s_vrf = (NMSettingVrf *) nm_setting_vrf_new ();
+		nm_connection_add_setting (connection, (NMSetting *) s_vrf);
+	}
+
+	if (priv->props.table != nm_setting_vrf_get_table (s_vrf))
+		g_object_set (G_OBJECT (s_vrf), NM_SETTING_VRF_TABLE, priv->props.table, NULL);
+}
+
+static gboolean
+enslave_slave (NMDevice *device,
+               NMDevice *slave,
+               NMConnection *connection,
+               gboolean configure)
+{
+	NMDeviceVrf *self = NM_DEVICE_VRF (device);
+	gboolean success = TRUE;
+	const char *slave_iface = nm_device_get_ip_iface (slave);
+
+	nm_device_master_check_slave_physical_port (device, slave, LOGD_DEVICE);
+
+	if (configure) {
+		nm_device_take_down (slave, TRUE);
+		success = nm_platform_link_enslave (nm_device_get_platform (device),
+		                                    nm_device_get_ip_ifindex (device),
+		                                    nm_device_get_ip_ifindex (slave));
+		nm_device_bring_up (slave, TRUE, NULL);
+
+		if (!success)
+			return FALSE;
+
+		_LOGI (LOGD_DEVICE, "enslaved VRF slave %s", slave_iface);
+	} else
+		_LOGI (LOGD_BOND, "VRF slave %s was enslaved", slave_iface);
+
+	return TRUE;
+}
+
+static void
+release_slave (NMDevice *device,
+               NMDevice *slave,
+               gboolean configure)
+{
+	NMDeviceVrf *self = NM_DEVICE_VRF (device);
+	gboolean success;
+	int ifindex_slave;
+	int ifindex;
+
+	if (configure) {
+		ifindex = nm_device_get_ifindex (device);
+		if (   ifindex <= 0
+		    || !nm_platform_link_get (nm_device_get_platform (device), ifindex))
+			configure = FALSE;
+	}
+
+	ifindex_slave = nm_device_get_ip_ifindex (slave);
+
+	if (ifindex_slave <= 0)
+		_LOGD (LOGD_DEVICE, "VRF slave %s is already released", nm_device_get_ip_iface (slave));
+
+	if (configure) {
+		if (ifindex_slave > 0) {
+			success = nm_platform_link_release (nm_device_get_platform (device),
+			                                    nm_device_get_ip_ifindex (device),
+			                                    ifindex_slave);
+
+			if (success) {
+				_LOGI (LOGD_DEVICE, "released VRF slave %s",
+				       nm_device_get_ip_iface (slave));
+			} else {
+				_LOGW (LOGD_DEVICE, "failed to release VRF slave %s",
+				       nm_device_get_ip_iface (slave));
+			}
+		}
+	} else {
+		if (ifindex_slave > 0) {
+			_LOGI (LOGD_DEVICE, "VRF slave %s was released",
+			       nm_device_get_ip_iface (slave));
+		}
+	}
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE (object);
+
+	switch (prop_id) {
+	case PROP_TABLE:
+		g_value_set_uint (value, priv->props.table);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_vrf_init (NMDeviceVrf *self)
+{
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_vrf = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_VRF,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Table", "u", NM_DEVICE_VRF_TABLE),
+		),
+	),
+};
+
+static void
+nm_device_vrf_class_init (NMDeviceVrfClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	object_class->get_property = get_property;
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_vrf);
+
+	device_class->connection_type_supported = NM_SETTING_VRF_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_VRF_SETTING_NAME;
+	device_class->is_master = TRUE;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_VRF);
+
+	device_class->enslave_slave = enslave_slave;
+	device_class->release_slave = release_slave;
+	device_class->link_changed = link_changed;
+	device_class->unrealize_notify = unrealize_notify;
+	device_class->create_and_realize = create_and_realize;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->complete_connection = complete_connection;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->update_connection = update_connection;
+
+	obj_properties[PROP_TABLE] =
+	     g_param_spec_uint (NM_DEVICE_VRF_TABLE, "", "",
+	                        0, G_MAXUINT32, 0,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_VRF_DEVICE_FACTORY (nm_vrf_device_factory_get_type ())
+#define NM_VRF_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_VRF_DEVICE_FACTORY, NMVrfDeviceFactory))
+
+static NMDevice *
+create_device (NMDeviceFactory *factory,
+               const char *iface,
+               const NMPlatformLink *plink,
+               NMConnection *connection,
+               gboolean *out_ignore)
+{
+	return g_object_new (NM_TYPE_DEVICE_VRF,
+	                     NM_DEVICE_IFACE, iface,
+	                     NM_DEVICE_TYPE_DESC, "Vrf",
+	                     NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_VRF,
+	                     NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_VRF,
+	                     NULL);
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL (VRF, Vrf, vrf,
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_VRF)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_VRF_SETTING_NAME),
+	factory_class->create_device = create_device;
+);
diff --git a/src/devices/nm-device-vrf.h b/src/devices/nm-device-vrf.h
new file mode 100644
index 00000000..89ab0f13
--- /dev/null
+++ b/src/devices/nm-device-vrf.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#ifndef __NETWORKMANAGER_DEVICE_VRF_H__
+#define __NETWORKMANAGER_DEVICE_VRF_H__
+
+#include "nm-device-generic.h"
+
+#define NM_TYPE_DEVICE_VRF            (nm_device_vrf_get_type ())
+#define NM_DEVICE_VRF(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_VRF, NMDeviceVrf))
+#define NM_DEVICE_VRF_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_VRF, NMDeviceVrfClass))
+#define NM_IS_DEVICE_VRF(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_VRF))
+#define NM_IS_DEVICE_VRF_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_VRF))
+#define NM_DEVICE_VRF_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_VRF, NMDeviceVrfClass))
+
+#define NM_DEVICE_VRF_TABLE          "table"
+
+typedef struct _NMDeviceVrf NMDeviceVrf;
+typedef struct _NMDeviceVrfClass NMDeviceVrfClass;
+
+GType nm_device_vrf_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_VRF_H__ */
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index 29be6854..b49e7c3e 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -57,7 +57,7 @@ struct _NMDeviceVxlanClass {
 
 G_DEFINE_TYPE (NMDeviceVxlan, nm_device_vxlan, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_VXLAN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceVxlan, NM_IS_DEVICE_VXLAN)
+#define NM_DEVICE_VXLAN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceVxlan, NM_IS_DEVICE_VXLAN, NMDevice)
 
 /*****************************************************************************/
 
@@ -232,7 +232,7 @@ address_matches (const char *str, in_addr_t addr4, struct in6_addr *addr6)
 static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE ((NMDeviceVxlan *) device);
+	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE (device);
 	NMSettingVxlan *s_vxlan;
 	const char *parent;
 
@@ -369,7 +369,7 @@ complete_connection (NMDevice *device,
 static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
-	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE ((NMDeviceVxlan *) device);
+	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE (device);
 	NMSettingVxlan *s_vxlan = nm_connection_get_setting_vxlan (connection);
 	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
@@ -390,11 +390,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (!address_matches (nm_setting_vxlan_get_remote (s_vxlan), priv->props.group, &priv->props.group6)) {
 		if (priv->props.group) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_REMOTE,
-			              nm_utils_inet4_ntop (priv->props.group, sbuf),
+			              _nm_utils_inet4_ntop (priv->props.group, sbuf),
 			              NULL);
 		} else {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_REMOTE,
-			              nm_utils_inet6_ntop (&priv->props.group6, sbuf),
+			              _nm_utils_inet6_ntop (&priv->props.group6, sbuf),
 			              NULL);
 		}
 	}
@@ -402,11 +402,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (!address_matches (nm_setting_vxlan_get_local (s_vxlan), priv->props.local, &priv->props.local6)) {
 		if (priv->props.local) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_LOCAL,
-			              nm_utils_inet4_ntop (priv->props.local, sbuf),
+			              _nm_utils_inet4_ntop (priv->props.local, sbuf),
 			              NULL);
 		} else if (memcmp (&priv->props.local6, &in6addr_any, sizeof (in6addr_any))) {
 			g_object_set (s_vxlan, NM_SETTING_VXLAN_LOCAL,
-			              nm_utils_inet6_ntop (&priv->props.local6, sbuf),
+			              _nm_utils_inet6_ntop (&priv->props.local6, sbuf),
 			              NULL);
 		}
 	}
@@ -473,7 +473,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE ((NMDeviceVxlan *) object);
+	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_ID:
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index c916fa46..bb7a5950 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -51,7 +51,7 @@ G_STATIC_ASSERT (NM_WIREGUARD_SYMMETRIC_KEY_LEN == NMP_WIREGUARD_SYMMETRIC_KEY_L
 
 /*****************************************************************************/
 
-#define LINK_CONFIG_RATE_LIMIT_NSEC (50 * NM_UTILS_NS_PER_MSEC)
+#define LINK_CONFIG_RATE_LIMIT_NSEC (50 * NM_UTILS_NSEC_PER_MSEC)
 
 /* a special @next_try_at_nsec timestamp indicating that we should try again as soon as possible. */
 #define NEXT_TRY_AT_NSEC_ASAP ((gint64) G_MAXINT64)
@@ -80,7 +80,7 @@ typedef struct {
 
 	NMSockAddrUnion sockaddr;
 
-	/* the timestamp (in nm_utils_get_monotonic_timestamp_ns() scale) when we want
+	/* the timestamp (in nm_utils_get_monotonic_timestamp_nsec() scale) when we want
 	 * to retry resolving the endpoint (again).
 	 *
 	 * It may be set to %NEXT_TRY_AT_NSEC_ASAP to indicate to re-resolve as soon as possible.
@@ -168,7 +168,8 @@ static gboolean link_config_delayed_ratelimit_cb (gpointer user_data);
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_link_config_mode_to_string, LinkConfigMode,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_link_config_mode_to_string, LinkConfigMode,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (NULL),
 	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_FULL,      "full"),
 	NM_UTILS_LOOKUP_ITEM (LINK_CONFIG_MODE_REAPPLY,   "reapply"),
@@ -555,7 +556,7 @@ _peers_resolve_retry_timeout (gpointer user_data)
 
 	_LOGT (LOGD_DEVICE, "wireguard-peers: rechecking peer endpoints...");
 
-	now = nm_utils_get_monotonic_timestamp_ns ();
+	now = nm_utils_get_monotonic_timestamp_nsec ();
 	next = G_MAXINT64;
 	c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
 		if (peer_data->ep_resolv.next_try_at_nsec <= 0)
@@ -606,11 +607,11 @@ _peers_resolve_retry_reschedule (NMDeviceWireGuard *self,
 		return;
 	}
 
-	now = nm_utils_get_monotonic_timestamp_ns ();
+	now = nm_utils_get_monotonic_timestamp_nsec ();
 
 	/* schedule at most one day ahead. No problem if we expire earlier
 	 * than expected. Also, rate-limit to 500 msec. */
-	interval_ms = NM_CLAMP ((new_next_try_at_nsec - now) / NM_UTILS_NS_PER_MSEC,
+	interval_ms = NM_CLAMP ((new_next_try_at_nsec - now) / NM_UTILS_NSEC_PER_MSEC,
 	                        (gint64) 500,
 	                        (gint64) (24*60*60*1000));
 
@@ -636,8 +637,8 @@ _peers_resolve_retry_reschedule_for_peer (NMDeviceWireGuard *self,
 		return;
 	}
 
-	peer_data->ep_resolv.next_try_at_nsec =   nm_utils_get_monotonic_timestamp_ns ()
-	                                        + (retry_in_msec * NM_UTILS_NS_PER_MSEC);
+	peer_data->ep_resolv.next_try_at_nsec =   nm_utils_get_monotonic_timestamp_nsec ()
+	                                        + (retry_in_msec * NM_UTILS_NSEC_PER_MSEC);
 	_peers_resolve_retry_reschedule (self, peer_data->ep_resolv.next_try_at_nsec);
 }
 
@@ -684,7 +685,7 @@ _peers_resolve_cb (GObject *source_object,
 
 	list = g_resolver_lookup_by_name_finish (G_RESOLVER (source_object), res, &resolv_error);
 
-	if (nm_utils_error_is_cancelled (resolv_error, FALSE))
+	if (nm_utils_error_is_cancelled (resolv_error))
 		return;
 
 	peer_data = user_data;
@@ -1265,7 +1266,7 @@ _secrets_cb (NMActRequest *req,
 		return;
 	}
 
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -1370,7 +1371,7 @@ link_config (NMDeviceWireGuard *self,
 	s_wg = NM_SETTING_WIREGUARD (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD));
 	g_return_val_if_fail (s_wg, NM_ACT_STAGE_RETURN_FAILURE);
 
-	priv->link_config_last_at = nm_utils_get_monotonic_timestamp_ns ();
+	priv->link_config_last_at = nm_utils_get_monotonic_timestamp_nsec ();
 
 	_LOGT (LOGD_DEVICE, "wireguard link config (%s, %s)...",
 	       reason, _link_config_mode_to_string (config_mode));
@@ -1475,12 +1476,12 @@ link_config_delayed (NMDeviceWireGuard *self,
 	priv->link_config_delayed_id = 0;
 
 	if (priv->link_config_last_at != 0) {
-		now = nm_utils_get_monotonic_timestamp_ns ();
+		now = nm_utils_get_monotonic_timestamp_nsec ();
 		if (now < priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC) {
 			/* we ratelimit calls to link_config(), because we call this whenever a resolver
 			 * completes. */
 			_LOGT (LOGD_DEVICE, "wireguard link config (%s) (postponed)", reason);
-			priv->link_config_delayed_id = g_timeout_add (NM_MAX ((priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC - now) / NM_UTILS_NS_PER_MSEC,
+			priv->link_config_delayed_id = g_timeout_add (NM_MAX ((priv->link_config_last_at + LINK_CONFIG_RATE_LIMIT_NSEC - now) / NM_UTILS_NSEC_PER_MSEC,
 			                                                      (gint64) 1),
 			                                              link_config_delayed_ratelimit_cb,
 			                                              self);
@@ -1521,28 +1522,26 @@ act_stage2_config (NMDevice *device,
 	}
 
 	ret = link_config (NM_DEVICE_WIREGUARD (device),
-	                                        "configure",
-	                                          (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
-	                                        ? LINK_CONFIG_MODE_ASSUME
-	                                        : LINK_CONFIG_MODE_FULL,
-	                                        &failure_reason);
+	                   "configure",
+	                     (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
+	                   ? LINK_CONFIG_MODE_ASSUME
+	                   : LINK_CONFIG_MODE_FULL,
+	                   &failure_reason);
 
 	if (sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_ASSUME) {
 		/* this never fails. */
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 	}
 
-	if (ret != NM_ACT_STAGE_RETURN_FAILURE) {
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NONE);
-		return ret;
+	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_FAILED,
+		                         failure_reason);
+		NM_SET_OUT (out_failure_reason, failure_reason);
+		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	nm_device_state_changed (device,
-	                         NM_DEVICE_STATE_FAILED,
-	                         failure_reason);
-	NM_SET_OUT (out_failure_reason, failure_reason);
-	return NM_ACT_STAGE_RETURN_FAILURE;
+	return ret;
 }
 
 static NMIPConfig *
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 72ec29a1..c3483e4d 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -43,8 +43,7 @@
 #include "nm-ip6-config.h"
 #include "nm-pacrunner-manager.h"
 #include "dnsmasq/nm-dnsmasq-manager.h"
-#include "nm-dhcp4-config.h"
-#include "nm-dhcp6-config.h"
+#include "nm-dhcp-config.h"
 #include "nm-rfkill-manager.h"
 #include "nm-firewall-manager.h"
 #include "settings/nm-settings-connection.h"
@@ -159,6 +158,15 @@ typedef struct {
 	                          that the original configuration didn't change. */
 } AppliedConfig;
 
+typedef struct {
+	NMDhcpClient *client;
+	NMDhcpConfig *config;
+	gulong state_sigid;
+	guint grace_id;
+	bool grace_pending:1;
+	bool was_active:1;
+} DhcpData;
+
 struct _NMDeviceConnectivityHandle {
 	CList concheck_lst;
 	NMDevice *self;
@@ -233,9 +241,9 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_LLDP_NEIGHBORS,
 	PROP_REAL,
 	PROP_SLAVES,
-	PROP_REFRESH_RATE_MS,
-	PROP_TX_BYTES,
-	PROP_RX_BYTES,
+	PROP_STATISTICS_REFRESH_RATE_MS,
+	PROP_STATISTICS_TX_BYTES,
+	PROP_STATISTICS_RX_BYTES,
 	PROP_IP4_CONNECTIVITY,
 	PROP_IP6_CONNECTIVITY,
 	PROP_INTERFACE_FLAGS,
@@ -475,17 +483,29 @@ typedef struct _NMDevicePrivate {
 
 	/* DHCPv4 tracking */
 	struct {
-		NMDhcpClient *  client;
-		NMDhcp4Config * config;
 		char *          pac_url;
 		char *          root_path;
-		gulong          state_sigid;
-		guint           grace_id;
-		bool            was_active:1;
-		bool            grace_pending:1;
 	} dhcp4;
 
 	struct {
+		/* IP6 config from DHCP */
+		AppliedConfig    ip6_config;
+		/* Event ID of the current IP6 config from DHCP */
+		char *           event_id;
+		gulong           prefix_sigid;
+		NMNDiscDHCPLevel mode;
+		guint            needed_prefixes;
+	} dhcp6;
+
+	union {
+		struct {
+			DhcpData dhcp_data_6;
+			DhcpData dhcp_data_4;
+		};
+		DhcpData dhcp_data_x[2];
+	};
+
+	struct {
 		NMLogDomain log_domain;
 		guint timeout;
 		guint watch;
@@ -545,22 +565,6 @@ typedef struct _NMDevicePrivate {
 
 	EthtoolState  *ethtool_state;
 
-	struct {
-		NMDhcpClient *   client;
-		NMDhcp6Config *  config;
-		/* IP6 config from DHCP */
-		AppliedConfig    ip6_config;
-		/* Event ID of the current IP6 config from DHCP */
-		char *           event_id;
-		gulong           state_sigid;
-		gulong           prefix_sigid;
-		NMNDiscDHCPLevel mode;
-		guint            needed_prefixes;
-		guint            grace_id;
-		bool             was_active:1;
-		bool             grace_pending:1;
-	} dhcp6;
-
 	gboolean needs_ip6_subnet;
 
 	/* master interface for bridge/bond/team slave */
@@ -687,8 +691,6 @@ static void (*const activate_stage4_ip_config_timeout_x[2]) (NMDevice *self) = {
 
 static void sriov_op_cb (GError *error, gpointer user_data);
 
-static void activate_stage2_device_config (NMDevice *self);
-
 static void activate_stage5_ip_config_result_4 (NMDevice *self);
 static void activate_stage5_ip_config_result_6 (NMDevice *self);
 
@@ -699,7 +701,8 @@ static void (*const activate_stage5_ip_config_result_x[2]) (NMDevice *self) = {
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (queued_state_to_string, NMDeviceState,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (queued_state_to_string, NMDeviceState,
 	NM_UTILS_LOOKUP_DEFAULT  (                              NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "???"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_UNKNOWN,      NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_UNMANAGED,    NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unmanaged"),
@@ -796,7 +799,8 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_device_state_reason_to_str, NMDeviceStateReason,
 
 #define reason_to_string_a(reason) NM_UTILS_LOOKUP_STR_A (nm_device_state_reason_to_str, reason)
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (mtu_source_to_str, NMDeviceMtuSource,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (mtu_source_to_str, NMDeviceMtuSource,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_MTU_SOURCE_NONE,       "none"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_MTU_SOURCE_PARENT,     "parent"),
@@ -886,6 +890,22 @@ is_loopback (NMDevice *self)
 	       && NM_DEVICE_GET_PRIVATE (self)->ifindex == 1;
 }
 
+gboolean
+nm_device_is_vpn (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	/* NetworkManager currently treats VPN connections (loaded from NetworkManager VPN plugins)
+	 * differently. Those are considered VPNs.
+	 * However, some native device types may also be considered VPNs...
+	 *
+	 * We should avoid distinguishing between is-vpn and "regular" devices. Is an (unencrypted)
+	 * IP tunnel a VPN? Is MACSec on top of an IP tunnel a VPN?
+	 * Sometimes we differentiate, but avoid unless reasonable. */
+
+	return NM_IS_DEVICE_WIREGUARD (self);
+}
+
 NMSettings *
 nm_device_get_settings (NMDevice *self)
 {
@@ -1041,7 +1061,8 @@ applied_config_reset_nameservers (AppliedConfig *config)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_sys_iface_state_to_str, NMDeviceSysIfaceState,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_sys_iface_state_to_str, NMDeviceSysIfaceState,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, "external"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_SYS_IFACE_STATE_ASSUME,   "assume"),
@@ -1183,7 +1204,14 @@ init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 	                                                        G_MININT,
 	                                                        G_MAXINT,
 	                                                        0);
-	nm_ip_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
+
+	if (priority == 0) {
+		priority =   nm_device_is_vpn (self)
+		           ? NM_DNS_PRIORITY_DEFAULT_VPN
+		           : NM_DNS_PRIORITY_DEFAULT_NORMAL;
+	}
+
+	nm_ip_config_set_dns_priority (config, priority);
 }
 
 /*****************************************************************************/
@@ -1372,7 +1400,8 @@ _get_stable_id (NMDevice *self,
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_ip_state_to_string, NMDeviceIPState,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_ip_state_to_string, NMDeviceIPState,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_NONE, "none"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_IP_STATE_WAIT, "wait"),
@@ -1837,11 +1866,11 @@ _stats_update_counters (NMDevice *self,
 
 	if (priv->stats.tx_bytes != tx_bytes) {
 		priv->stats.tx_bytes = tx_bytes;
-		_notify (self, PROP_TX_BYTES);
+		_notify (self, PROP_STATISTICS_TX_BYTES);
 	}
 	if (priv->stats.rx_bytes != rx_bytes) {
 		priv->stats.rx_bytes = rx_bytes;
-		_notify (self, PROP_RX_BYTES);
+		_notify (self, PROP_STATISTICS_RX_BYTES);
 	}
 }
 
@@ -1898,7 +1927,7 @@ _stats_set_refresh_rate (NMDevice *self, guint refresh_rate_ms)
 
 	old_rate = priv->stats.refresh_rate_ms;
 	priv->stats.refresh_rate_ms = refresh_rate_ms;
-	_notify (self, PROP_REFRESH_RATE_MS);
+	_notify (self, PROP_STATISTICS_REFRESH_RATE_MS);
 
 	_LOGD (LOGD_DEVICE, "stats: set refresh to %u ms", priv->stats.refresh_rate_ms);
 
@@ -2124,6 +2153,8 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 		return 450;
 	case NM_DEVICE_TYPE_PPP:
 		return 460;
+	case NM_DEVICE_TYPE_VRF:
+		return 470;
 	case NM_DEVICE_TYPE_VXLAN:
 		return 500;
 	case NM_DEVICE_TYPE_DUMMY:
@@ -2289,6 +2320,8 @@ _get_route_table (NMDevice *self,
 	NMSettingIPConfig *s_ip;
 	guint32 route_table = 0;
 	gboolean is_user_config = TRUE;
+	NMSettingConnection *s_con;
+	NMSettingVrf *s_vrf;
 
 	nm_assert_addr_family (addr_family);
 
@@ -2325,6 +2358,28 @@ _get_route_table (NMDevice *self,
 		}
 	}
 
+	if (   route_table == 0u
+	    && connection
+	    && (s_con = nm_connection_get_setting_connection (connection))
+	    && (nm_streq0 (nm_setting_connection_get_slave_type (s_con), NM_SETTING_VRF_SETTING_NAME)
+	    && priv->master
+	    && nm_device_get_device_type (priv->master) == NM_DEVICE_TYPE_VRF)) {
+		const NMPlatformLnkVrf *lnk;
+
+		lnk = nm_platform_link_get_lnk_vrf (nm_device_get_platform (self),
+		                                    nm_device_get_ifindex (priv->master),
+		                                    NULL);
+
+		if (lnk)
+			route_table = lnk->table;
+	}
+
+	if (   route_table == 0u
+	    && connection
+	    && (s_vrf = (NMSettingVrf *) nm_connection_get_setting (connection, NM_TYPE_SETTING_VRF))) {
+		route_table = nm_setting_vrf_get_table (s_vrf);
+	}
+
 	klass = NM_DEVICE_GET_CLASS (self);
 	if (klass->coerce_route_table)
 		route_table = klass->coerce_route_table (self, addr_family, route_table, is_user_config);
@@ -2656,17 +2711,17 @@ concheck_periodic_schedule_do (NMDevice *self, int addr_family, gint64 now_ns)
 	 * Before calling concheck_periodic_schedule_do(), make sure that these properties are
 	 * correct. */
 
-	expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
 	tdiff = expiry - now_ns;
 
 	_LOGT (LOGD_CONCHECK, "connectivity: [IPv%c] periodic-check: %sscheduled in %lld milliseconds (%u seconds interval)",
 	       nm_utils_addr_family_to_char (addr_family),
 	       periodic_check_disabled ? "re-" : "",
-	       (long long) (tdiff / NM_UTILS_NS_PER_MSEC),
+	       (long long) (tdiff / NM_UTILS_NSEC_PER_MSEC),
 	       priv->concheck_x[IS_IPv4].p_cur_interval);
 
 	priv->concheck_x[IS_IPv4].p_cur_id =
-		g_timeout_add (NM_MAX ((gint64) 0, tdiff) / NM_UTILS_NS_PER_MSEC,
+		g_timeout_add (NM_MAX ((gint64) 0, tdiff) / NM_UTILS_NSEC_PER_MSEC,
 	                       IS_IPv4 ? concheck_ip4_periodic_timeout_cb : concheck_ip6_periodic_timeout_cb,
 	                       self);
 	return TRUE;
@@ -2708,7 +2763,7 @@ concheck_periodic_schedule_set (NMDevice *self, int addr_family, ConcheckSchedul
 	switch (mode) {
 	case CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART:
 		priv->concheck_x[IS_IPv4].p_cur_interval = NM_MIN (priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL);
-		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_nsec_cached (&now_ns);
 		if (concheck_periodic_schedule_do (self, addr_family, now_ns))
 			concheck_start (self, addr_family, NULL, NULL, TRUE);
 		return;
@@ -2728,8 +2783,8 @@ concheck_periodic_schedule_set (NMDevice *self, int addr_family, ConcheckSchedul
 			return;
 		}
 
-		cur_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_max_interval * NM_UTILS_NS_PER_SECOND);
-		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		cur_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_max_interval * NM_UTILS_NSEC_PER_SEC);
+		nm_utils_get_monotonic_timestamp_nsec_cached (&now_ns);
 
 		priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].p_max_interval;
 		if (cur_expiry <= now_ns) {
@@ -2752,7 +2807,7 @@ concheck_periodic_schedule_set (NMDevice *self, int addr_family, ConcheckSchedul
 
 	case CONCHECK_SCHEDULE_CHECK_EXTERNAL:
 		/* a external connectivity check delays our periodic check. We reset the counter. */
-		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = nm_utils_get_monotonic_timestamp_nsec_cached (&now_ns);
 		concheck_periodic_schedule_do (self, addr_family, now_ns);
 		return;
 
@@ -2783,11 +2838,11 @@ concheck_periodic_schedule_set (NMDevice *self, int addr_family, ConcheckSchedul
 		 * pretty close to now_ns.
 		 *
 		 * We want to reschedule the timeout at exp_expiry (aka now) + cur_interval. */
-		nm_utils_get_monotonic_timestamp_ns_cached (&now_ns);
-		exp_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (old_interval * NM_UTILS_NS_PER_SECOND);
-		new_expiry = exp_expiry + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		nm_utils_get_monotonic_timestamp_nsec_cached (&now_ns);
+		exp_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (old_interval * NM_UTILS_NSEC_PER_SEC);
+		new_expiry = exp_expiry + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
 		tdiff = NM_MAX (new_expiry - now_ns, 0);
-		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = (now_ns + tdiff) - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+		priv->concheck_x[IS_IPv4].p_cur_basetime_ns = (now_ns + tdiff) - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
 		if (concheck_periodic_schedule_do (self, addr_family, now_ns)) {
 			handle = concheck_start (self, addr_family, NULL, NULL, TRUE);
 			if (old_interval != priv->concheck_x[IS_IPv4].p_cur_interval) {
@@ -2821,9 +2876,9 @@ concheck_periodic_schedule_set (NMDevice *self, int addr_family, ConcheckSchedul
 	 * last check, instead of counting from now. The reason is that we want that the times
 	 * when we schedule checks be at precise intervals, without including the time it took for
 	 * the connectivity check. */
-	new_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
-	tdiff = NM_MAX (new_expiry - nm_utils_get_monotonic_timestamp_ns_cached (&now_ns), 0);
-	priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns + tdiff - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NS_PER_SECOND);
+	new_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
+	tdiff = NM_MAX (new_expiry - nm_utils_get_monotonic_timestamp_nsec_cached (&now_ns), 0);
+	priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns + tdiff - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
 	concheck_periodic_schedule_do (self, addr_family, now_ns);
 }
 
@@ -3481,7 +3536,7 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
-	if (priv->dhcp4.client) {
+	if (priv->dhcp_data_4.client) {
 		if (!nm_device_dhcp4_renew (self, FALSE)) {
 			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_FAILED,
@@ -3489,7 +3544,7 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 			return;
 		}
 	}
-	if (priv->dhcp6.client) {
+	if (priv->dhcp_data_6.client) {
 		if (!nm_device_dhcp6_renew (self, FALSE)) {
 			nm_device_state_changed (self,
 			                         NM_DEVICE_STATE_FAILED,
@@ -3661,7 +3716,7 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 		} else {
 			gint64 now_ms, until_ms;
 
-			now_ms = nm_utils_get_monotonic_timestamp_ms ();
+			now_ms = nm_utils_get_monotonic_timestamp_msec ();
 			until_ms = NM_MAX (now_ms + _get_carrier_wait_ms (self), priv->carrier_wait_until_ms);
 			priv->carrier_defer_id = g_timeout_add (until_ms - now_ms, carrier_disconnected_action_cb, self);
 			_LOGD (LOGD_DEVICE, "carrier: link disconnected (deferring action for %ld milliseconds) (id=%u)",
@@ -3757,7 +3812,7 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 	if (nm_ndisc_get_node_type (ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
 		return;
 
-	now = nm_utils_get_monotonic_timestamp_s ();
+	now = nm_utils_get_monotonic_timestamp_sec ();
 
 	head_entry = nm_ip6_config_lookup_addresses (priv->ip_config_6);
 	addresses = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscAddress),
@@ -4800,19 +4855,19 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 	_set_mtu (self, 0);
 
 	if (priv->driver_version) {
-		g_clear_pointer (&priv->driver_version, g_free);
+		nm_clear_g_free (&priv->driver_version);
 		_notify (self, PROP_DRIVER_VERSION);
 	}
 	if (priv->firmware_version) {
-		g_clear_pointer (&priv->firmware_version, g_free);
+		nm_clear_g_free (&priv->firmware_version);
 		_notify (self, PROP_FIRMWARE_VERSION);
 	}
 	if (priv->udi) {
-		g_clear_pointer (&priv->udi, g_free);
+		nm_clear_g_free (&priv->udi);
 		_notify (self, PROP_UDI);
 	}
 	if (priv->physical_port_id) {
-		g_clear_pointer (&priv->physical_port_id, g_free);
+		nm_clear_g_free (&priv->physical_port_id);
 		_notify (self, PROP_PHYSICAL_PORT_ID);
 	}
 
@@ -4825,7 +4880,7 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 	priv->hw_addr_type = HW_ADDR_TYPE_UNSET;
 	if (nm_clear_g_free (&priv->hw_addr_perm))
 		_notify (self, PROP_PERM_HW_ADDRESS);
-	g_clear_pointer (&priv->hw_addr_initial, g_free);
+	nm_clear_g_free (&priv->hw_addr_initial);
 
 	priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
 	if (NM_DEVICE_GET_CLASS (self)->get_generic_capabilities)
@@ -5374,10 +5429,10 @@ nm_device_removed (NMDevice *self, gboolean unconfigure_ip_config)
 		nm_device_set_ip_config (self, AF_INET, NULL, FALSE, NULL);
 		nm_device_set_ip_config (self, AF_INET6, NULL, FALSE, NULL);
 	} else {
-		if (priv->dhcp4.client)
-			nm_dhcp_client_stop (priv->dhcp4.client, FALSE);
-		if (priv->dhcp6.client)
-			nm_dhcp_client_stop (priv->dhcp6.client, FALSE);
+		if (priv->dhcp_data_4.client)
+			nm_dhcp_client_stop (priv->dhcp_data_4.client, FALSE);
+		if (priv->dhcp_data_6.client)
+			nm_dhcp_client_stop (priv->dhcp_data_6.client, FALSE);
 	}
 }
 
@@ -5456,7 +5511,8 @@ nm_device_set_enabled (NMDevice *self, gboolean enabled)
 		NM_DEVICE_GET_CLASS (self)->set_enabled (self, enabled);
 }
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_autoconnect_blocked_flags_to_string, NMDeviceAutoconnectBlockedFlags,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_autoconnect_blocked_flags_to_string, NMDeviceAutoconnectBlockedFlags,
 	NM_UTILS_FLAGS2STR (NM_DEVICE_AUTOCONNECT_BLOCKED_NONE,              "none"),
 	NM_UTILS_FLAGS2STR (NM_DEVICE_AUTOCONNECT_BLOCKED_USER,              "user"),
 	NM_UTILS_FLAGS2STR (NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN,         "wrong-pin"),
@@ -6380,7 +6436,7 @@ master_ready_cb (NMActiveConnection *active,
 	nm_assert (nm_active_connection_get_master_ready (active));
 
 	if (priv->state == NM_DEVICE_STATE_PREPARE)
-		nm_device_activate_schedule_stage1_device_prepare (self);
+		nm_device_activate_schedule_stage1_device_prepare (self, FALSE);
 }
 
 static void
@@ -6503,7 +6559,7 @@ sriov_params_cb (GError *error, gpointer data)
 
 	nm_utils_user_data_unpack (data, &self, &plat_vfs);
 
-	if (nm_utils_error_is_cancelled (error, TRUE))
+	if (nm_utils_error_is_cancelled_or_disposing (error))
 		return;
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
@@ -6528,7 +6584,7 @@ sriov_params_cb (GError *error, gpointer data)
 
 	priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED;
 
-	nm_device_activate_schedule_stage1_device_prepare (self);
+	nm_device_activate_schedule_stage1_device_prepare (self, FALSE);
 }
 
 /*
@@ -6668,26 +6724,22 @@ activate_stage1_device_prepare (NMDevice *self)
 	if (master)
 		master_ready (self, active);
 
-	activation_source_invoke_sync (self, activate_stage2_device_config, AF_INET);
+	nm_device_activate_schedule_stage2_device_config (self, TRUE);
 }
 
-/*
- * nm_device_activate_schedule_stage1_device_prepare
- *
- * Prepare a device for activation
- *
- */
 void
-nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
+nm_device_activate_schedule_stage1_device_prepare (NMDevice *self,
+                                                   gboolean do_sync)
 {
-	NMDevicePrivate *priv;
-
 	g_return_if_fail (NM_IS_DEVICE (self));
+	g_return_if_fail (NM_DEVICE_GET_PRIVATE (self)->act_request.obj);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	g_return_if_fail (priv->act_request.obj);
+	if (!do_sync) {
+		activation_source_schedule (self, activate_stage1_device_prepare, AF_INET);
+		return;
+	}
 
-	activation_source_schedule (self, activate_stage1_device_prepare, AF_INET);
+	activation_source_invoke_sync (self, activate_stage1_device_prepare, AF_INET);
 }
 
 static NMActStageReturn
@@ -7009,10 +7061,11 @@ activate_stage2_device_config (NMDevice *self)
 
 	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		if (!nm_device_bring_up (self, FALSE, &no_firmware)) {
-			if (no_firmware)
-				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING);
-			else
-				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+			nm_device_state_changed (self,
+			                         NM_DEVICE_STATE_FAILED,
+			                           no_firmware
+			                         ? NM_DEVICE_STATE_REASON_FIRMWARE_MISSING
+			                         : NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 			return;
 		}
 	}
@@ -7050,18 +7103,18 @@ activate_stage2_device_config (NMDevice *self)
 	nm_device_activate_schedule_stage3_ip_config_start (self);
 }
 
-/*
- * nm_device_activate_schedule_stage2_device_config
- *
- * Schedule setup of the hardware device
- *
- */
 void
-nm_device_activate_schedule_stage2_device_config (NMDevice *self)
+nm_device_activate_schedule_stage2_device_config (NMDevice *self,
+                                                  gboolean do_sync)
 {
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	activation_source_schedule (self, activate_stage2_device_config, AF_INET);
+	if (!do_sync) {
+		activation_source_schedule (self, activate_stage2_device_config, AF_INET);
+		return;
+	}
+
+	activation_source_invoke_sync (self, activate_stage2_device_config, AF_INET);
 }
 
 void
@@ -7170,7 +7223,7 @@ acd_manager_probe_terminated (NMAcdManager *acd_manager, gpointer user_data)
 			_NMLOG (result ? LOGL_DEBUG : LOGL_WARN,
 			        LOGD_DEVICE,
 			        "IPv4 DAD result: address %s is %s",
-			        nm_utils_inet4_ntop (address->address, sbuf),
+			        _nm_utils_inet4_ntop (address->address, sbuf),
 			        result ? "unique" : "duplicate");
 		}
 	}
@@ -7548,25 +7601,25 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	priv->dhcp4.was_active = FALSE;
-	nm_clear_g_source (&priv->dhcp4.grace_id);
-	priv->dhcp4.grace_pending = FALSE;
-	g_clear_pointer (&priv->dhcp4.pac_url, g_free);
-	g_clear_pointer (&priv->dhcp4.root_path, g_free);
+	priv->dhcp_data_4.was_active = FALSE;
+	nm_clear_g_source (&priv->dhcp_data_4.grace_id);
+	priv->dhcp_data_4.grace_pending = FALSE;
+	nm_clear_g_free (&priv->dhcp4.pac_url);
+	nm_clear_g_free (&priv->dhcp4.root_path);
 
-	if (priv->dhcp4.client) {
+	if (priv->dhcp_data_4.client) {
 		/* Stop any ongoing DHCP transaction on this device */
-		nm_clear_g_signal_handler (priv->dhcp4.client, &priv->dhcp4.state_sigid);
+		nm_clear_g_signal_handler (priv->dhcp_data_4.client, &priv->dhcp_data_4.state_sigid);
 
 		if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
 		    || cleanup_type == CLEANUP_TYPE_REMOVED)
-			nm_dhcp_client_stop (priv->dhcp4.client, release);
+			nm_dhcp_client_stop (priv->dhcp_data_4.client, release);
 
-		g_clear_object (&priv->dhcp4.client);
+		g_clear_object (&priv->dhcp_data_4.client);
 	}
 
-	if (priv->dhcp4.config) {
-		nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
+	if (priv->dhcp_data_4.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp_data_4.config);
 		_notify (self, PROP_DHCP4_CONFIG);
 	}
 }
@@ -7811,7 +7864,7 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config, gboolean bound)
 	 * new lease after an expiry. But what should we do if
 	 * a duplicate address is detected? Fail the connection;
 	 * restart DHCP; continue without an address? */
-	if (bound && !nm_dhcp_client_accept (priv->dhcp4.client, &error)) {
+	if (bound && !nm_dhcp_client_accept (priv->dhcp_data_4.client, &error)) {
 		_LOGW (LOGD_DHCP4, "error accepting lease: %s", error->message);
 		return FALSE;
 	}
@@ -7825,22 +7878,78 @@ dhcp4_lease_change (NMDevice *self, NMIP4Config *config, gboolean bound)
 }
 
 static gboolean
-dhcp4_grace_period_expired (gpointer user_data)
+dhcp_grace_period_expired (NMDevice *self, int addr_family)
 {
-	NMDevice *self = user_data;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+
+	priv->dhcp_data_x[IS_IPv4].grace_id = 0;
+	priv->dhcp_data_x[IS_IPv4].grace_pending = FALSE;
 
-	priv->dhcp4.grace_id = 0;
-	priv->dhcp4.grace_pending = FALSE;
-	_LOGI (LOGD_DHCP4, "DHCPv4: grace period expired");
+	_LOGI (LOGD_DHCP_from_addr_family (addr_family),
+	       "DHCPv%c: grace period expired",
+	       nm_utils_addr_family_to_char (addr_family));
 
-	nm_device_ip_method_failed (self, AF_INET,
+	nm_device_ip_method_failed (self,
+	                            addr_family,
 	                            NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
 	/* If the device didn't fail, the DHCP client will continue */
 
 	return G_SOURCE_REMOVE;
 }
 
+static gboolean
+dhcp_grace_period_expired_4 (gpointer user_data)
+{
+	return dhcp_grace_period_expired (user_data, AF_INET);
+}
+
+static gboolean
+dhcp_grace_period_expired_6 (gpointer user_data)
+{
+	return dhcp_grace_period_expired (user_data, AF_INET6);
+}
+
+static gboolean
+dhcp_grace_period_start (NMDevice *self, int addr_family)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+	guint32 timeout;
+
+	/* In any other case (expired lease, assumed connection, etc.),
+	 * wait for some time before failing the IP method.
+	 */
+	if (priv->dhcp_data_x[IS_IPv4].grace_pending) {
+		/* already pending. */
+		return FALSE;
+	}
+
+	/* Start a grace period equal to the DHCP timeout multiplied
+	 * by a constant factor. */
+	timeout = get_dhcp_timeout (self, addr_family);
+	if (timeout == NM_DHCP_TIMEOUT_INFINITY)
+		_LOGI (LOGD_DHCP_from_addr_family (addr_family),
+		       "DHCPv%c: trying to acquire a new lease",
+		       nm_utils_addr_family_to_char (addr_family));
+	else {
+		timeout = dhcp_grace_period_from_timeout (timeout);
+		_LOGI (LOGD_DHCP_from_addr_family (addr_family),
+		       "DHCPv%c: trying to acquire a new lease within %u seconds",
+		       nm_utils_addr_family_to_char (addr_family),
+		       timeout);
+		nm_assert (!priv->dhcp_data_x[IS_IPv4].grace_id);
+		priv->dhcp_data_x[IS_IPv4].grace_id = g_timeout_add_seconds (timeout,
+		                                                               IS_IPv4
+		                                                             ? dhcp_grace_period_expired_4
+		                                                             : dhcp_grace_period_expired_6,
+		                                                             self);
+	}
+
+	priv->dhcp_data_x[IS_IPv4].grace_pending = TRUE;
+
+	return TRUE;
+}
 static void
 dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 {
@@ -7848,7 +7957,7 @@ dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 
 	_LOGD (LOGD_DHCP4, "DHCPv4 failed (ip_state %s, was_active %d)",
 	       _ip_state_to_string (priv->ip_state_4),
-	       priv->dhcp4.was_active);
+	       priv->dhcp_data_4.was_active);
 
 	/* The client is always left running after a failure. */
 
@@ -7871,43 +7980,21 @@ dhcp4_fail (NMDevice *self, NMDhcpState dhcp_state)
 	 *    not active before.
 	 */
 	if (   dhcp_state == NM_DHCP_STATE_TERMINATED
-	    || (!priv->dhcp4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) {
+	    || (!priv->dhcp_data_4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) {
 		nm_device_activate_schedule_ip_config_timeout (self, AF_INET);
 		return;
 	}
 
-	/* In any other case (expired lease, assumed connection, etc.),
-	 * wait for some time before failing the IP method.
-	 */
-	if (!priv->dhcp4.grace_pending) {
-		guint32 timeout;
-
-		/* Start a grace period equal to the DHCP timeout multiplied
-		 * by a constant factor. */
-		timeout = get_dhcp_timeout (self, AF_INET);
-		if (timeout == NM_DHCP_TIMEOUT_INFINITY) {
-			_LOGI (LOGD_DHCP4, "DHCPv4: trying to acquire a new lease");
-		} else {
-			timeout = dhcp_grace_period_from_timeout (timeout);
-			_LOGI (LOGD_DHCP4,
-			       "DHCPv4: trying to acquire a new lease within %u seconds",
-			       timeout);
-			nm_assert (!priv->dhcp4.grace_id);
-			priv->dhcp4.grace_id = g_timeout_add_seconds (timeout,
-			                                              dhcp4_grace_period_expired,
-			                                              self);
-		}
-
-		priv->dhcp4.grace_pending = TRUE;
+	if (dhcp_grace_period_start (self, AF_INET))
 		goto clear_config;
-	}
+
 	return;
 
 clear_config:
 	/* The previous configuration is no longer valid */
-	if (priv->dhcp4.config) {
-		nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
-		priv->dhcp4.config = nm_dhcp4_config_new ();
+	if (priv->dhcp_data_4.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp_data_4.config);
+		priv->dhcp_data_4.config = nm_dhcp_config_new (AF_INET);
 		_notify (self, PROP_DHCP4_CONFIG);
 	}
 }
@@ -7921,7 +8008,7 @@ dhcp4_dad_cb (NMDevice *self, NMIP4Config **configs, gboolean success)
 		nm_device_activate_schedule_ip_config_result (self, AF_INET,
 		                                              NM_IP_CONFIG_CAST (configs[1]));
 	} else {
-		nm_dhcp_client_decline (priv->dhcp4.client, "Address conflict detected", NULL);
+		nm_dhcp_client_decline (priv->dhcp_data_4.client, "Address conflict detected", NULL);
 		nm_device_ip_method_failed (self, AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
 	}
@@ -7954,8 +8041,8 @@ dhcp4_state_changed (NMDhcpClient *client,
 			break;
 		}
 
-		nm_clear_g_source (&priv->dhcp4.grace_id);
-		priv->dhcp4.grace_pending = FALSE;
+		nm_clear_g_source (&priv->dhcp_data_4.grace_id);
+		priv->dhcp_data_4.grace_pending = FALSE;
 
 		/* After some failures, we have been able to renew the lease:
 		 * update the ip state
@@ -7970,7 +8057,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 		g_free (priv->dhcp4.root_path);
 		priv->dhcp4.root_path = g_strdup (g_hash_table_lookup (options, "root_path"));
 
-		nm_dhcp4_config_set_options (priv->dhcp4.config, options);
+		nm_dhcp_config_set_options (priv->dhcp_data_4.config, options);
 		_notify (self, PROP_DHCP4_CONFIG);
 
 		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
@@ -8349,8 +8436,8 @@ dhcp4_start (NMDevice *self)
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
 	/* Clear old exported DHCP options */
-	nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
-	priv->dhcp4.config = nm_dhcp4_config_new ();
+	nm_dbus_object_clear_and_unexport (&priv->dhcp_data_4.config);
+	priv->dhcp_data_4.config = nm_dhcp_config_new (AF_INET);
 
 	pllink = nm_platform_link_get (nm_device_get_platform (self), nm_device_get_ip_ifindex (self));
 	if (pllink) {
@@ -8360,38 +8447,38 @@ dhcp4_start (NMDevice *self)
 
 	client_id = dhcp4_get_client_id (self, connection, hwaddr);
 
-	g_warn_if_fail (priv->dhcp4.client == NULL);
-	priv->dhcp4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
-	                                                nm_netns_get_multi_idx (nm_device_get_netns (self)),
-	                                                nm_device_get_ip_iface (self),
-	                                                nm_device_get_ip_ifindex (self),
-	                                                hwaddr,
-	                                                bcast_hwaddr,
-	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_route_table (self, AF_INET),
-	                                                nm_device_get_route_metric (self, AF_INET),
-	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip4),
-	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip4),
-	                                                nm_setting_ip4_config_get_dhcp_fqdn (NM_SETTING_IP4_CONFIG (s_ip4)),
-	                                                get_dhcp_hostname_flags (self, AF_INET),
-	                                                client_id,
-	                                                get_dhcp_timeout (self, AF_INET),
-	                                                priv->dhcp_anycast_address,
-	                                                NULL,
-	                                                &error);
-	if (!priv->dhcp4.client) {
+	g_warn_if_fail (priv->dhcp_data_4.client == NULL);
+	priv->dhcp_data_4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
+	                                                      nm_netns_get_multi_idx (nm_device_get_netns (self)),
+	                                                      nm_device_get_ip_iface (self),
+	                                                      nm_device_get_ip_ifindex (self),
+	                                                      hwaddr,
+	                                                      bcast_hwaddr,
+	                                                      nm_connection_get_uuid (connection),
+	                                                      nm_device_get_route_table (self, AF_INET),
+	                                                      nm_device_get_route_metric (self, AF_INET),
+	                                                      nm_setting_ip_config_get_dhcp_send_hostname (s_ip4),
+	                                                      nm_setting_ip_config_get_dhcp_hostname (s_ip4),
+	                                                      nm_setting_ip4_config_get_dhcp_fqdn (NM_SETTING_IP4_CONFIG (s_ip4)),
+	                                                      get_dhcp_hostname_flags (self, AF_INET),
+	                                                      client_id,
+	                                                      get_dhcp_timeout (self, AF_INET),
+	                                                      priv->dhcp_anycast_address,
+	                                                      NULL,
+	                                                      &error);
+	if (!priv->dhcp_data_4.client) {
 		_LOGW (LOGD_DHCP4, "failure to start DHCP: %s", error->message);
 		g_clear_error (&error);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	priv->dhcp4.state_sigid = g_signal_connect (priv->dhcp4.client,
-	                                            NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
-	                                            G_CALLBACK (dhcp4_state_changed),
-	                                            self);
+	priv->dhcp_data_4.state_sigid = g_signal_connect (priv->dhcp_data_4.client,
+	                                                  NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
+	                                                  G_CALLBACK (dhcp4_state_changed),
+	                                                  self);
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self))
-		priv->dhcp4.was_active = TRUE;
+		priv->dhcp_data_4.was_active = TRUE;
 
 	/* DHCP devices will be notified by the DHCP manager when stuff happens */
 	return NM_ACT_STAGE_RETURN_POSTPONE;
@@ -8402,7 +8489,7 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	g_return_val_if_fail (priv->dhcp4.client != NULL, FALSE);
+	g_return_val_if_fail (priv->dhcp_data_4.client != NULL, FALSE);
 
 	_LOGI (LOGD_DHCP4, "DHCPv4 lease renewal requested");
 
@@ -8422,7 +8509,7 @@ shared_ip_release (gpointer data)
 {
 	g_hash_table_remove (shared_ips, data);
 	if (!g_hash_table_size (shared_ips))
-		g_clear_pointer (&shared_ips, g_hash_table_unref);
+		nm_clear_pointer (&shared_ips, g_hash_table_unref);
 }
 
 static NMIP4Config *
@@ -8577,26 +8664,26 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	priv->dhcp6.was_active = FALSE;
+	priv->dhcp_data_6.was_active = FALSE;
 	priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
 	applied_config_clear (&priv->dhcp6.ip6_config);
-	g_clear_pointer (&priv->dhcp6.event_id, g_free);
-	nm_clear_g_source (&priv->dhcp6.grace_id);
-	priv->dhcp6.grace_pending = FALSE;
+	nm_clear_g_free (&priv->dhcp6.event_id);
+	nm_clear_g_source (&priv->dhcp_data_6.grace_id);
+	priv->dhcp_data_6.grace_pending = FALSE;
 
-	if (priv->dhcp6.client) {
-		nm_clear_g_signal_handler (priv->dhcp6.client, &priv->dhcp6.state_sigid);
-		nm_clear_g_signal_handler (priv->dhcp6.client, &priv->dhcp6.prefix_sigid);
+	if (priv->dhcp_data_6.client) {
+		nm_clear_g_signal_handler (priv->dhcp_data_6.client, &priv->dhcp_data_6.state_sigid);
+		nm_clear_g_signal_handler (priv->dhcp_data_6.client, &priv->dhcp6.prefix_sigid);
 
 		if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
 		    || cleanup_type == CLEANUP_TYPE_REMOVED)
-			nm_dhcp_client_stop (priv->dhcp6.client, release);
+			nm_dhcp_client_stop (priv->dhcp_data_6.client, release);
 
-		g_clear_object (&priv->dhcp6.client);
+		g_clear_object (&priv->dhcp_data_6.client);
 	}
 
-	if (priv->dhcp6.config) {
-		nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
+	if (priv->dhcp_data_6.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp_data_6.config);
 		_notify (self, PROP_DHCP6_CONFIG);
 	}
 }
@@ -8612,7 +8699,7 @@ dhcp6_lease_change (NMDevice *self)
 		return FALSE;
 	}
 
-	g_assert (priv->dhcp6.client);  /* sanity check */
+	g_assert (priv->dhcp_data_6.client);  /* sanity check */
 
 	settings_connection = nm_device_get_settings_connection (self);
 	g_assert (settings_connection);
@@ -8631,23 +8718,6 @@ dhcp6_lease_change (NMDevice *self)
 	return TRUE;
 }
 
-static gboolean
-dhcp6_grace_period_expired (gpointer user_data)
-{
-	NMDevice *self = user_data;
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	priv->dhcp6.grace_id = 0;
-	priv->dhcp6.grace_pending = FALSE;
-	_LOGI (LOGD_DHCP6, "DHCPv6: grace period expired");
-
-	nm_device_ip_method_failed (self, AF_INET6,
-	                            NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-	/* If the device didn't fail, the DHCP client will continue */
-
-	return G_SOURCE_REMOVE;
-}
-
 static void
 dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 {
@@ -8656,7 +8726,7 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 
 	_LOGD (LOGD_DHCP6, "DHCPv6 failed (ip_state %s, was_active %d)",
 	       _ip_state_to_string (priv->ip_state_6),
-	       priv->dhcp6.was_active);
+	       priv->dhcp_data_6.was_active);
 
 	/* The client is always left running after a failure. */
 
@@ -8682,36 +8752,13 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 		 *    not active before.
 		 */
 		if (   dhcp_state == NM_DHCP_STATE_TERMINATED
-		    || (!priv->dhcp6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) {
+		    || (!priv->dhcp_data_6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) {
 			nm_device_activate_schedule_ip_config_timeout (self, AF_INET6);
 			return;
 		}
 
-		/* In any other case (expired lease, assumed connection, etc.),
-		 * wait for some time before failing the IP method.
-		 */
-		if (!priv->dhcp6.grace_pending) {
-			guint32 timeout;
-
-			/* Start a grace period equal to the DHCP timeout multiplied
-			 * by a constant factor. */
-			timeout = get_dhcp_timeout (self, AF_INET6);
-			if (timeout == NM_DHCP_TIMEOUT_INFINITY)
-				_LOGI (LOGD_DHCP6, "DHCPv6: trying to acquire a new lease");
-			else {
-				timeout = dhcp_grace_period_from_timeout (timeout);
-				_LOGI (LOGD_DHCP6,
-				       "DHCPv6: trying to acquire a new lease within %u seconds",
-				       timeout);
-				nm_assert (!priv->dhcp6.grace_id);
-				priv->dhcp6.grace_id = g_timeout_add_seconds (timeout,
-				                                              dhcp6_grace_period_expired,
-				                                              self);
-			}
-
-			priv->dhcp6.grace_pending = TRUE;
+		if (dhcp_grace_period_start (self, AF_INET6))
 			goto clear_config;
-		}
 	} else {
 		/* not a hard failure; just live with the RA info */
 		dhcp6_cleanup (self, CLEANUP_TYPE_DECONFIGURE, FALSE);
@@ -8722,9 +8769,9 @@ dhcp6_fail (NMDevice *self, NMDhcpState dhcp_state)
 
 clear_config:
 	/* The previous configuration is no longer valid */
-	if (priv->dhcp6.config) {
-		nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
-		priv->dhcp6.config = nm_dhcp6_config_new ();
+	if (priv->dhcp_data_6.config) {
+		nm_dbus_object_clear_and_unexport (&priv->dhcp_data_6.config);
+		priv->dhcp_data_6.config = nm_dhcp_config_new (AF_INET6);
 		_notify (self, PROP_DHCP6_CONFIG);
 	}
 }
@@ -8748,8 +8795,8 @@ dhcp6_state_changed (NMDhcpClient *client,
 	switch (state) {
 	case NM_DHCP_STATE_BOUND:
 	case NM_DHCP_STATE_EXTENDED:
-		nm_clear_g_source (&priv->dhcp6.grace_id);
-		priv->dhcp6.grace_pending = FALSE;
+		nm_clear_g_source (&priv->dhcp_data_6.grace_id);
+		priv->dhcp_data_6.grace_pending = FALSE;
 		/* If the server sends multiple IPv6 addresses, we receive a state
 		 * changed event for each of them. Use the event ID to merge IPv6
 		 * addresses from the same transaction into a single configuration.
@@ -8764,11 +8811,11 @@ dhcp6_state_changed (NMDhcpClient *client,
 			nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6_config, &a)
 				applied_config_add_address (&priv->dhcp6.ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (a));
 		} else {
-			g_clear_pointer (&priv->dhcp6.event_id, g_free);
+			nm_clear_g_free (&priv->dhcp6.event_id);
 			if (ip6_config) {
 				applied_config_init (&priv->dhcp6.ip6_config, ip6_config);
 				priv->dhcp6.event_id = g_strdup (event_id);
-				nm_dhcp6_config_set_options (priv->dhcp6.config, options);
+				nm_dhcp_config_set_options (priv->dhcp_data_6.config, options);
 				_notify (self, PROP_DHCP6_CONFIG);
 			} else
 				applied_config_clear (&priv->dhcp6.ip6_config);
@@ -8999,7 +9046,7 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 			duid_out = generate_duid_ll (arp_type, hwaddr_bin, hwaddr_len);
 		else {
 			duid_out = generate_duid_llt (arp_type, hwaddr_bin, hwaddr_len,
-			                              nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NS_PER_SECOND);
+			                              nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NSEC_PER_SEC);
 		}
 
 		goto out_good;
@@ -9094,7 +9141,7 @@ dhcp6_get_duid (NMDevice *self, NMConnection *connection, GBytes *hwaddr, gboole
 			 * before. Let's compute the time (in seconds) from 0 to 3 years; then we'll
 			 * subtract it from the host_id timestamp.
 			 */
-			time = nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NS_PER_SECOND;
+			time = nm_utils_host_id_get_timestamp_ns () / NM_UTILS_NSEC_PER_SEC;
 
 			/* don't use too old timestamps. They cannot be expressed in DUID-LLT and
 			 * would all be truncated to zero. */
@@ -9191,48 +9238,48 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	iaid = dhcp_get_iaid (self, AF_INET6, connection, &iaid_explicit);
 
 	duid = dhcp6_get_duid (self, connection, hwaddr, &enforce_duid);
-	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
-	                                                nm_device_get_multi_index (self),
-	                                                nm_device_get_ip_iface (self),
-	                                                nm_device_get_ip_ifindex (self),
-	                                                hwaddr,
-	                                                bcast_hwaddr,
-	                                                &ll_addr->address,
-	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_route_table (self, AF_INET6),
-	                                                nm_device_get_route_metric (self, AF_INET6),
-	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
-	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
-	                                                get_dhcp_hostname_flags (self, AF_INET6),
-	                                                duid,
-	                                                enforce_duid,
-	                                                iaid,
-	                                                iaid_explicit,
-	                                                get_dhcp_timeout (self, AF_INET6),
-	                                                priv->dhcp_anycast_address,
-	                                                (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
-	                                                nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)),
-	                                                priv->dhcp6.needed_prefixes,
-	                                                &error);
-	if (!priv->dhcp6.client) {
+	priv->dhcp_data_6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
+	                                                      nm_device_get_multi_index (self),
+	                                                      nm_device_get_ip_iface (self),
+	                                                      nm_device_get_ip_ifindex (self),
+	                                                      hwaddr,
+	                                                      bcast_hwaddr,
+	                                                      &ll_addr->address,
+	                                                      nm_connection_get_uuid (connection),
+	                                                      nm_device_get_route_table (self, AF_INET6),
+	                                                      nm_device_get_route_metric (self, AF_INET6),
+	                                                      nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
+	                                                      nm_setting_ip_config_get_dhcp_hostname (s_ip6),
+	                                                      get_dhcp_hostname_flags (self, AF_INET6),
+	                                                      duid,
+	                                                      enforce_duid,
+	                                                      iaid,
+	                                                      iaid_explicit,
+	                                                      get_dhcp_timeout (self, AF_INET6),
+	                                                      priv->dhcp_anycast_address,
+	                                                      (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
+	                                                      nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)),
+	                                                      priv->dhcp6.needed_prefixes,
+	                                                      &error);
+	if (!priv->dhcp_data_6.client) {
 		_LOGW (LOGD_DHCP6, "failure to start DHCPv6: %s", error->message);
 		g_clear_error (&error);
 		if (nm_device_sys_iface_state_is_external_or_assume (self))
-			priv->dhcp6.was_active = TRUE;
+			priv->dhcp_data_6.was_active = TRUE;
 		return FALSE;
 	}
 
-	priv->dhcp6.state_sigid = g_signal_connect (priv->dhcp6.client,
-	                                            NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
-	                                            G_CALLBACK (dhcp6_state_changed),
-	                                            self);
-	priv->dhcp6.prefix_sigid = g_signal_connect (priv->dhcp6.client,
+	priv->dhcp_data_6.state_sigid = g_signal_connect (priv->dhcp_data_6.client,
+	                                                  NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
+	                                                  G_CALLBACK (dhcp6_state_changed),
+	                                                  self);
+	priv->dhcp6.prefix_sigid = g_signal_connect (priv->dhcp_data_6.client,
 	                                             NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED,
 	                                             G_CALLBACK (dhcp6_prefix_delegated),
 	                                             self);
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self))
-		priv->dhcp6.was_active = TRUE;
+		priv->dhcp_data_6.was_active = TRUE;
 
 	return TRUE;
 }
@@ -9243,12 +9290,12 @@ dhcp6_start (NMDevice *self, gboolean wait_for_ll)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 
-	nm_dbus_object_clear_and_unexport (&priv->dhcp6.config);
-	priv->dhcp6.config = nm_dhcp6_config_new ();
+	nm_dbus_object_clear_and_unexport (&priv->dhcp_data_6.config);
+	priv->dhcp_data_6.config = nm_dhcp_config_new (AF_INET6);
 
 	nm_assert (!applied_config_get_current (&priv->dhcp6.ip6_config));
 	applied_config_clear (&priv->dhcp6.ip6_config);
-	g_clear_pointer (&priv->dhcp6.event_id, g_free);
+	nm_clear_g_free (&priv->dhcp6.event_id);
 
 	connection = nm_device_get_applied_connection (self);
 	g_return_val_if_fail (connection, FALSE);
@@ -9273,7 +9320,7 @@ nm_device_dhcp6_renew (NMDevice *self, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	g_return_val_if_fail (priv->dhcp6.client != NULL, FALSE);
+	g_return_val_if_fail (priv->dhcp_data_6.client != NULL, FALSE);
 
 	_LOGI (LOGD_DHCP6, "DHCPv6 lease renewal requested");
 
@@ -9297,7 +9344,7 @@ nm_device_request_ip6_prefixes (NMDevice *self, int needed_prefixes)
 
 	priv->dhcp6.needed_prefixes = needed_prefixes;
 
-	if (priv->dhcp6.client) {
+	if (priv->dhcp_data_6.client) {
 		_LOGD (LOGD_IP6, "ipv6-pd: asking DHCPv6 for %d prefixes", needed_prefixes);
 		nm_device_dhcp6_renew (self, FALSE);
 	} else {
@@ -9330,7 +9377,7 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 	applied_config_add_address (&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST (&address));
 
 	_LOGD (LOGD_IP6, "ipv6-pd: using %s address (preferred for %u seconds)",
-	       nm_utils_inet6_ntop (&address.address, sbuf),
+	       _nm_utils_inet6_ntop (&address.address, sbuf),
 	       subnet->preferred);
 
 	/* This also updates the ndisc if there are actual changes. */
@@ -9510,7 +9557,7 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 	}
 
 	_LOGD (LOGD_IP6, "linklocal6: generated %s IPv6LL address %s",
-	       addr_type, nm_utils_inet6_ntop (&lladdr, sbuf));
+	       addr_type, _nm_utils_inet6_ntop (&lladdr, sbuf));
 	priv->ipv6ll_has = TRUE;
 	priv->ipv6ll_addr = lladdr;
 	ip_config_merge_and_apply (self, AF_INET6, TRUE);
@@ -9692,6 +9739,17 @@ _set_mtu (NMDevice *self, guint32 mtu)
 	}
 }
 
+static gboolean
+set_platform_mtu (NMDevice *self, guint32 mtu)
+{
+	int r;
+
+	r = nm_platform_link_set_mtu (nm_device_get_platform (self),
+	                              nm_device_get_ip_ifindex (self),
+	                              mtu);
+	return (r != -NME_PL_CANT_SET_MTU);
+}
+
 static void
 _commit_mtu (NMDevice *self, const NMIP4Config *config)
 {
@@ -9851,10 +9909,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		}
 
 		if (mtu_desired && mtu_desired != mtu_plat) {
-			int r;
-
-			r = nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired);
-			if (r == -NME_PL_CANT_SET_MTU) {
+			if (!NM_DEVICE_GET_CLASS (self)->set_platform_mtu (self, mtu_desired)) {
 				anticipated_failure = TRUE;
 				success = FALSE;
 				_LOGW (LOGD_DEVICE, "mtu: failure to set MTU. %s",
@@ -9864,7 +9919,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 				              ? "Are the MTU sizes of the slaves large enough?"
 				              : "Did you configure the MTU correctly?"));
 			}
-			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_msec () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
 		}
 
 		if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS ()) {
@@ -9880,7 +9935,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 				           : "");
 				success = FALSE;
 			}
-			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_msec () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
 		}
 	}
 
@@ -10152,7 +10207,7 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	nm_assert (!applied_config_get_current (&priv->ac_ip6_config));
 	applied_config_clear (&priv->ac_ip6_config);
 
-	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+	nm_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 
 	s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
@@ -10203,7 +10258,7 @@ addrconf6_cleanup (NMDevice *self)
 	nm_clear_g_signal_handler (priv->ndisc, &priv->ndisc_timeout_id);
 
 	applied_config_clear (&priv->ac_ip6_config);
-	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+	nm_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 	g_clear_object (&priv->ndisc);
 }
@@ -10752,7 +10807,7 @@ fw_change_zone_cb (NMFirewallManager *firewall_manager,
 
 	priv->fw_call = NULL;
 
-	if (nm_utils_error_is_cancelled (error, FALSE))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	switch (priv->fw_state) {
@@ -10996,10 +11051,10 @@ start_sharing (NMDevice *self, NMIP4Config *config, GError **error)
 	g_return_val_if_fail (req, FALSE);
 
 	netmask = _nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
-	nm_utils_inet4_ntop (netmask, str_mask);
+	_nm_utils_inet4_ntop (netmask, str_mask);
 
 	network = ip4_addr->address & netmask;
-	nm_utils_inet4_ntop (network, str_addr);
+	_nm_utils_inet4_ntop (network, str_addr);
 
 	add_share_rule (req, "nat", "POSTROUTING --source %s/%s ! --destination %s/%s --jump MASQUERADE", str_addr, str_mask, str_addr, str_mask);
 	add_share_rule (req, "filter", "FORWARD --destination %s/%s --out-interface %s --match state --state ESTABLISHED,RELATED --jump ACCEPT", str_addr, str_mask, ip_iface);
@@ -11153,10 +11208,10 @@ activate_stage5_ip_config_result_4 (NMDevice *self)
 		}
 	}
 
-	if (priv->dhcp4.client) {
+	if (priv->dhcp_data_4.client) {
 		gs_free_error GError *error = NULL;
 
-		if (!nm_dhcp_client_accept (priv->dhcp4.client, &error)) {
+		if (!nm_dhcp_client_accept (priv->dhcp_data_4.client, &error)) {
 			_LOGW (LOGD_DHCP4,
 			       "Activation: Stage 5 of 5 (IPv4 Commit) error accepting lease: %s",
 			       error->message);
@@ -11168,7 +11223,7 @@ activate_stage5_ip_config_result_4 (NMDevice *self)
 	/* If IPv4 wasn't the first to complete, and DHCP was used, then ensure
 	 * dispatcher scripts get the DHCP lease information.
 	 */
-	if (   priv->dhcp4.client
+	if (   priv->dhcp_data_4.client
 	    && nm_device_activate_ip4_state_in_conf (self)
 	    && (nm_device_get_state (self) > NM_DEVICE_STATE_IP_CONFIG)) {
 		nm_dispatcher_call_device (NM_DISPATCHER_ACTION_DHCP4_CHANGE,
@@ -11710,8 +11765,8 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev2_ip_config_4.orig,
 					                                    nm_device_get_route_metric (self, AF_INET));
 				}
-				if (priv->dhcp4.client) {
-					nm_dhcp_client_set_route_metric (priv->dhcp4.client,
+				if (priv->dhcp_data_4.client) {
+					nm_dhcp_client_set_route_metric (priv->dhcp_data_4.client,
 					                                 nm_device_get_route_metric (self, AF_INET));
 				}
 			}
@@ -11784,8 +11839,8 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dev2_ip_config_6.orig,
 					                                    nm_device_get_route_metric (self, AF_INET6));
 				}
-				if (priv->dhcp6.client) {
-					nm_dhcp_client_set_route_metric (priv->dhcp6.client,
+				if (priv->dhcp_data_6.client) {
+					nm_dhcp_client_set_route_metric (priv->dhcp_data_6.client,
 					                                 nm_device_get_route_metric (self, AF_INET6));
 				}
 			}
@@ -12318,7 +12373,7 @@ _rt6_temporary_not_available_set (NMDevice *self,
 	if (   !temporary_not_available
 	    || !temporary_not_available->len) {
 		/* nothing outstanding. Clear tracking the routes. */
-		g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+		nm_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 		nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 		return success;
 	}
@@ -12334,7 +12389,7 @@ _rt6_temporary_not_available_set (NMDevice *self,
 		                                                           nm_g_slice_free_fcn (IP6RoutesTemporaryNotAvailableData));
 	}
 
-	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	now_ms = nm_utils_get_monotonic_timestamp_msec ();
 	oldest_ms = now_ms;
 
 	for (i = 0; i < temporary_not_available->len; i++) {
@@ -12547,7 +12602,7 @@ _device_activate (NMDevice *self, NMActRequest *req)
 
 	act_request_set (self, req);
 
-	nm_device_activate_schedule_stage1_device_prepare (self);
+	nm_device_activate_schedule_stage1_device_prepare (self, FALSE);
 }
 
 static void
@@ -12771,12 +12826,16 @@ nm_device_set_proxy_config (NMDevice *self, const char *pac_url)
 }
 
 /* IP Configuration stuff */
-NMDhcp4Config *
-nm_device_get_dhcp4_config (NMDevice *self)
+NMDhcpConfig *
+nm_device_get_dhcp_config (NMDevice *self, int addr_family)
 {
+	const gboolean IS_IPv4 = (addr_family == AF_INET);
+
 	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
 
-	return NM_DEVICE_GET_PRIVATE (self)->dhcp4.config;
+	nm_assert_addr_family (addr_family);
+
+	return NM_DEVICE_GET_PRIVATE (self)->dhcp_data_x[IS_IPv4].config;
 }
 
 NMIP4Config *
@@ -13030,14 +13089,6 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 		_LOGW (LOGD_IP6, "failed to set VPN routes for device");
 }
 
-NMDhcp6Config *
-nm_device_get_dhcp6_config (NMDevice *self)
-{
-	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
-
-	return NM_DEVICE_GET_PRIVATE (self)->dhcp6.config;
-}
-
 NMIP6Config *
 nm_device_get_ip6_config (NMDevice *self)
 {
@@ -13114,8 +13165,8 @@ ip_check_gw_ping_cleanup (NMDevice *self)
 		priv->gw_ping.pid = 0;
 	}
 
-	g_clear_pointer (&priv->gw_ping.binary, g_free);
-	g_clear_pointer (&priv->gw_ping.address, g_free);
+	nm_clear_g_free (&priv->gw_ping.binary);
+	nm_clear_g_free (&priv->gw_ping.address);
 }
 
 static gboolean
@@ -13280,14 +13331,14 @@ nm_device_start_ip_check (NMDevice *self)
 		if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip4_config_best_default_route_get (priv->ip_config_4);
 			if (gw) {
-				nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
+				_nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
 				log_domain = LOGD_IP4;
 			}
 		} else if (priv->ip_config_6 && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
 			gw = nm_ip6_config_best_default_route_get (priv->ip_config_6);
 			if (gw) {
-				nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
+				_nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
 				ping_binary = nm_utils_find_helper ("ping6", "/usr/bin/ping6", NULL);
 				log_domain = LOGD_IP6;
 			}
@@ -13371,14 +13422,14 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 
 	device_is_up = nm_device_is_up (self);
 	if (block && !device_is_up) {
-		gint64 wait_until = nm_utils_get_monotonic_timestamp_us () + 10000 /* microseconds */;
+		gint64 wait_until = nm_utils_get_monotonic_timestamp_usec () + 10000 /* microseconds */;
 
 		do {
 			g_usleep (200);
 			if (!nm_platform_link_refresh (nm_device_get_platform (self), ifindex))
 				return FALSE;
 			device_is_up = nm_device_is_up (self);
-		} while (!device_is_up && nm_utils_get_monotonic_timestamp_us () < wait_until);
+		} while (!device_is_up && nm_utils_get_monotonic_timestamp_usec () < wait_until);
 	}
 
 	if (!device_is_up) {
@@ -13413,7 +13464,7 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 		if (!priv->carrier)
 			nm_device_add_pending_action (self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
 
-		now_ms = nm_utils_get_monotonic_timestamp_ms ();
+		now_ms = nm_utils_get_monotonic_timestamp_msec ();
 		until_ms = NM_MAX (now_ms + _get_carrier_wait_ms (self), priv->carrier_wait_until_ms);
 		priv->carrier_wait_id = g_timeout_add (until_ms - now_ms, carrier_wait_timeout, self);
 	}
@@ -13456,14 +13507,14 @@ nm_device_take_down (NMDevice *self, gboolean block)
 
 	device_is_up = nm_device_is_up (self);
 	if (block && device_is_up) {
-		gint64 wait_until = nm_utils_get_monotonic_timestamp_us () + 10000 /* microseconds */;
+		gint64 wait_until = nm_utils_get_monotonic_timestamp_usec () + 10000 /* microseconds */;
 
 		do {
 			g_usleep (200);
 			if (!nm_platform_link_refresh (nm_device_get_platform (self), ifindex))
 				return;
 			device_is_up = nm_device_is_up (self);
-		} while (device_is_up && nm_utils_get_monotonic_timestamp_us () < wait_until);
+		} while (device_is_up && nm_utils_get_monotonic_timestamp_usec () < wait_until);
 	}
 
 	if (device_is_up) {
@@ -15123,7 +15174,7 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	priv->ipv6ll_has = FALSE;
 	memset (&priv->ipv6ll_addr, 0, sizeof (priv->ipv6ll_addr));
 
-	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+	nm_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
 	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 
 	g_slist_free_full (priv->vpn_configs_4, g_object_unref);
@@ -15255,7 +15306,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 			       (guint) priv->mtu_initial, (guint) priv->ip6_mtu_initial, ifindex);
 			if (priv->mtu_initial) {
 				nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, priv->mtu_initial);
-				priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+				priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_msec () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
 			}
 			if (priv->ip6_mtu_initial) {
 				char sbuf[64];
@@ -15366,11 +15417,11 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		if (nm_setting_ip_config_get_may_fail (s_ip4) == FALSE)
 			g_ptr_array_add (argv, g_strdup ("--dhcp4-required"));
 
-		if (priv->dhcp4.client) {
+		if (priv->dhcp_data_4.client) {
 			const char *hostname;
 			GBytes *client_id;
 
-			client_id = nm_dhcp_client_get_client_id (priv->dhcp4.client);
+			client_id = nm_dhcp_client_get_client_id (priv->dhcp_data_4.client);
 			if (client_id) {
 				g_ptr_array_add (argv, g_strdup ("--dhcp4-clientid"));
 				g_ptr_array_add (argv,
@@ -15381,9 +15432,9 @@ nm_device_spawn_iface_helper (NMDevice *self)
 				                                           NULL));
 			}
 
-			hostname = nm_dhcp_client_get_hostname (priv->dhcp4.client);
+			hostname = nm_dhcp_client_get_hostname (priv->dhcp_data_4.client);
 			if (hostname) {
-				if (nm_dhcp_client_get_use_fqdn (priv->dhcp4.client))
+				if (nm_dhcp_client_get_use_fqdn (priv->dhcp_data_4.client))
 					g_ptr_array_add (argv, g_strdup ("--dhcp4-fqdn"));
 				else
 					g_ptr_array_add (argv, g_strdup ("--dhcp4-hostname"));
@@ -15505,7 +15556,7 @@ sriov_deactivate_cb (GError *error, gpointer user_data)
 	NMDevice *self;
 	gpointer reason;
 
-	if (nm_utils_error_is_cancelled (error, TRUE))
+	if (nm_utils_error_is_cancelled_or_disposing (error))
 		return;
 
 	nm_utils_user_data_unpack (user_data, &self, &reason);
@@ -15556,6 +15607,31 @@ deactivate_dispatcher_complete (NMDispatcherCallId *call_id, gpointer user_data)
 		nm_assert_not_reached ();
 
 	if (NM_DEVICE_GET_CLASS (self)->deactivate_async) {
+		/* FIXME: the virtual function deactivate_async() has only this caller here.
+		 * And the NMDevice subtypes are well aware of the circumstances when they
+		 * are called. We shall make the function less generic and thus (as the scope
+		 * is narrower) more convenient.
+		 *
+		 * - Drop the callback argument. Instead, when deactivate_async() completes, the
+		 *   subtype shall call a method _nm_device_deactivate_async_done(). Because as
+		 *   it is currently, subtypes need to pretend this callback and the user-data
+		 *   would be opaque, and carry it around. When it's in fact very clear what this
+		 *   is.
+		 *
+		 * - Also drop the GCancellable argument. Upon cancellation, NMDevice shall
+		 *   call another virtual function deactivate_async_abort(). As it is currently,
+		 *   callers need to register to the cancelled signal of the cancellable. It
+		 *   seems simpler to just implement the deactivate_async_abort() function.
+		 *   On the other hand, some implementations actually use the GCancellable.
+		 *   So, NMDevice shall do both: it shall both pass a cancellable, but also
+		 *   invoke deactivate_async_abort(). It allow the implementation to honor
+		 *   whatever is simpler for their purpose.
+		 *
+		 * - sometimes, the subclass can complete right away. Scheduling the completion
+		 *   in an idle handler is cumbersome. Allow the function to return FALSE to
+		 *   indicate that the device is already deactivated and the callback (or
+		 *   _nm_device_deactivate_async_done()) won't be invoked.
+		 */
 		priv->deactivating_cancellable = g_cancellable_new ();
 		NM_DEVICE_GET_CLASS (self)->deactivate_async (self,
 		                                              priv->deactivating_cancellable,
@@ -15573,7 +15649,7 @@ _set_state_full (NMDevice *self,
 {
 	NMDevicePrivate *priv;
 	NMDeviceState old_state;
-	NMActRequest *req;
+	gs_unref_object NMActRequest *req = NULL;
 	gboolean no_firmware = FALSE;
 	NMSettingsConnection *sett_conn;
 	NMSettingSriov *s_sriov;
@@ -15915,8 +15991,9 @@ _set_state_full (NMDevice *self,
 	/* IP-related properties are only valid when the device has IP configuration.
 	 * If it no longer does, ensure their change notifications are emitted.
 	 */
-	if (ip_config_valid (old_state) && !ip_config_valid (state))
-	    notify_ip_properties (self);
+	if (   ip_config_valid (old_state)
+	    && !ip_config_valid (state))
+		notify_ip_properties (self);
 
 	concheck_now =    NM_IN_SET (state, NM_DEVICE_STATE_ACTIVATED,
 	                                    NM_DEVICE_STATE_DISCONNECTED)
@@ -15924,10 +16001,6 @@ _set_state_full (NMDevice *self,
 	concheck_update_interval (self, AF_INET, concheck_now);
 	concheck_update_interval (self, AF_INET6, concheck_now);
 
-	/* Dispose of the cached activation request */
-	if (req)
-		g_object_unref (req);
-
 	priv->in_state_changed = FALSE;
 	nm_device_remove_pending_action (self,
 	                                 NM_PENDING_ACTION_IN_STATE_CHANGE,
@@ -16376,8 +16449,10 @@ _hw_addr_set (NMDevice *self,
 	}
 
 	if (   priv->hw_addr_len
-	    && priv->hw_addr_len != addr_len)
-		g_return_val_if_reached (FALSE);
+	    && priv->hw_addr_len != addr_len) {
+		_LOGT (LOGD_DEVICE, "set-hw-addr: setting MAC address to '%s' (%s, %s) failed because of wrong address length (should be %u bytes)", addr, operation, detail, priv->hw_addr_len);
+		return FALSE;
+	}
 
 	_LOGT (LOGD_DEVICE, "set-hw-addr: setting MAC address to '%s' (%s, %s)...", addr, operation, detail);
 
@@ -16432,7 +16507,7 @@ again:
 			 *
 			 * wait/poll up to 100 msec until it changes. */
 
-			poll_end = nm_utils_get_monotonic_timestamp_us () + (100 * 1000);
+			poll_end = nm_utils_get_monotonic_timestamp_usec () + (100 * 1000);
 			for (;;) {
 				if (!nm_platform_link_refresh (nm_device_get_platform (self), nm_device_get_ip_ifindex (self)))
 					goto handle_fail;
@@ -16443,7 +16518,7 @@ again:
 
 				break;
 handle_wait:
-				now = nm_utils_get_monotonic_timestamp_us ();
+				now = nm_utils_get_monotonic_timestamp_usec ();
 				if (now < poll_end) {
 					g_usleep (NM_MIN (poll_end - now, 500));
 					continue;
@@ -16920,6 +16995,297 @@ _activation_func_to_string (ActivationHandleFunc func)
 /*****************************************************************************/
 
 static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDevice *self = NM_DEVICE (object);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	GVariantBuilder array_builder;
+
+	switch (prop_id) {
+	case PROP_UDI:
+		/* UDI is (depending on the device type) a path to sysfs and can contain
+		 * non-UTF-8.
+		 *   ip link add name $'d\xccf\\c' type dummy  */
+		g_value_take_string (value,
+		                     nm_utils_str_utf8safe_escape_cp (priv->udi,
+		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_NONE));
+		break;
+	case PROP_IFACE:
+		g_value_take_string (value,
+		                     nm_utils_str_utf8safe_escape_cp (priv->iface,
+		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
+		break;
+	case PROP_IP_IFACE:
+		if (ip_config_valid (priv->state)) {
+			g_value_take_string (value,
+			                     nm_utils_str_utf8safe_escape_cp (nm_device_get_ip_iface (self),
+			                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
+		} else
+			g_value_set_string (value, NULL);
+		break;
+	case PROP_IFINDEX:
+		g_value_set_int (value, priv->ifindex);
+		break;
+	case PROP_DRIVER:
+		g_value_take_string (value,
+		                     nm_utils_str_utf8safe_escape_cp (priv->driver,
+		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
+		break;
+	case PROP_DRIVER_VERSION:
+		g_value_take_string (value,
+		                     nm_utils_str_utf8safe_escape_cp (priv->driver_version,
+		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
+		break;
+	case PROP_FIRMWARE_VERSION:
+		g_value_take_string (value,
+		                     nm_utils_str_utf8safe_escape_cp (priv->firmware_version,
+		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
+		break;
+	case PROP_CAPABILITIES:
+		g_value_set_uint (value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK));
+		break;
+	case PROP_IP4_ADDRESS:
+		g_value_set_uint (value, priv->ip4_address);
+		break;
+	case PROP_CARRIER:
+		g_value_set_boolean (value, priv->carrier);
+		break;
+	case PROP_MTU:
+		g_value_set_uint (value, priv->mtu);
+		break;
+	case PROP_IP4_CONFIG:
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_4 : NULL);
+		break;
+	case PROP_DHCP4_CONFIG:
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp_data_4.config : NULL);
+		break;
+	case PROP_IP6_CONFIG:
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_6 : NULL);
+		break;
+	case PROP_DHCP6_CONFIG:
+		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp_data_6.config : NULL);
+		break;
+	case PROP_STATE:
+		g_value_set_uint (value, priv->state);
+		break;
+	case PROP_STATE_REASON:
+		g_value_take_variant (value,
+		                      g_variant_new ("(uu)", priv->state, priv->state_reason));
+		break;
+	case PROP_ACTIVE_CONNECTION:
+		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->act_request));
+		break;
+	case PROP_DEVICE_TYPE:
+		g_value_set_uint (value, priv->type);
+		break;
+	case PROP_LINK_TYPE:
+		g_value_set_uint (value, priv->link_type);
+		break;
+	case PROP_MANAGED:
+		/* The managed state exposed on D-Bus only depends on the current device state alone. */
+		g_value_set_boolean (value, nm_device_get_state (self) > NM_DEVICE_STATE_UNMANAGED);
+		break;
+	case PROP_AUTOCONNECT:
+		g_value_set_boolean (value,
+		                     nm_device_autoconnect_blocked_get (self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)
+		                       ? FALSE
+		                       : TRUE);
+		break;
+	case PROP_FIRMWARE_MISSING:
+		g_value_set_boolean (value, priv->firmware_missing);
+		break;
+	case PROP_NM_PLUGIN_MISSING:
+		g_value_set_boolean (value, priv->nm_plugin_missing);
+		break;
+	case PROP_TYPE_DESC:
+		g_value_set_string (value, priv->type_desc);
+		break;
+	case PROP_RFKILL_TYPE:
+		g_value_set_uint (value, priv->rfkill_type);
+		break;
+	case PROP_AVAILABLE_CONNECTIONS:
+		nm_dbus_utils_g_value_set_object_path_from_hash (value,
+		                                                 priv->available_connections,
+		                                                 TRUE);
+		break;
+	case PROP_PHYSICAL_PORT_ID:
+		g_value_set_string (value, priv->physical_port_id);
+		break;
+	case PROP_MASTER:
+		g_value_set_object (value, nm_device_get_master (self));
+		break;
+	case PROP_PARENT:
+		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->parent_device));
+		break;
+	case PROP_HW_ADDRESS:
+		g_value_set_string (value, priv->hw_addr);
+		break;
+	case PROP_PERM_HW_ADDRESS: {
+		const char *perm_hw_addr;
+		gboolean perm_hw_addr_is_fake;
+
+		perm_hw_addr = nm_device_get_permanent_hw_address_full (self, FALSE, &perm_hw_addr_is_fake);
+		/* this property is exposed on D-Bus for NMDeviceEthernet and NMDeviceWifi. */
+		g_value_set_string (value, perm_hw_addr && !perm_hw_addr_is_fake ? perm_hw_addr : NULL);
+		break;
+	}
+	case PROP_HAS_PENDING_ACTION:
+		g_value_set_boolean (value, nm_device_has_pending_action (self));
+		break;
+	case PROP_METERED:
+		g_value_set_uint (value, priv->metered);
+		break;
+	case PROP_LLDP_NEIGHBORS:
+		if (priv->lldp_listener)
+			g_value_set_variant (value, nm_lldp_listener_get_neighbors (priv->lldp_listener));
+		else {
+			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
+			g_value_take_variant (value, g_variant_builder_end (&array_builder));
+		}
+		break;
+	case PROP_REAL:
+		g_value_set_boolean (value, nm_device_is_real (self));
+		break;
+	case PROP_SLAVES: {
+		CList *slave_iter;
+		char **slave_list;
+		gsize i, n;
+
+		n = c_list_length (&priv->slaves);
+		slave_list = g_new (char *, n + 1);
+		i = 0;
+		c_list_for_each (slave_iter, &priv->slaves) {
+			SlaveInfo *info = c_list_entry (slave_iter, SlaveInfo, lst_slave);
+			const char *path;
+
+			if (!NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
+				continue;
+			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (info->slave));
+			if (path)
+				slave_list[i++] = g_strdup (path);
+		}
+		nm_assert (i <= n);
+		slave_list[i] = NULL;
+		g_value_take_boxed (value, slave_list);
+		break;
+	}
+	case PROP_STATISTICS_REFRESH_RATE_MS:
+		g_value_set_uint (value, priv->stats.refresh_rate_ms);
+		break;
+	case PROP_STATISTICS_TX_BYTES:
+		g_value_set_uint64 (value, priv->stats.tx_bytes);
+		break;
+	case PROP_STATISTICS_RX_BYTES:
+		g_value_set_uint64 (value, priv->stats.rx_bytes);
+		break;
+	case PROP_IP4_CONNECTIVITY:
+		g_value_set_uint (value, priv->concheck_x[1].state);
+		break;
+	case PROP_IP6_CONNECTIVITY:
+		g_value_set_uint (value, priv->concheck_x[0].state);
+		break;
+	case PROP_INTERFACE_FLAGS:
+		g_value_set_uint (value, priv->interface_flags);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMDevice *self = (NMDevice *) object;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_UDI:
+		/* construct-only */
+		priv->udi = g_value_dup_string (value);
+		break;
+	case PROP_IFACE:
+		/* construct-only */
+		priv->iface = g_value_dup_string (value);
+		break;
+	case PROP_DRIVER:
+		/* construct-only */
+		priv->driver = g_value_dup_string (value);
+		break;
+	case PROP_MANAGED:
+		/* via D-Bus */
+		if (nm_device_is_real (self)) {
+			gboolean managed;
+			NMDeviceStateReason reason;
+
+			managed = g_value_get_boolean (value);
+			if (managed) {
+				reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
+				if (NM_IN_SET_TYPED (NMDeviceSysIfaceState,
+				                     priv->sys_iface_state,
+				                     NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
+				                     NM_DEVICE_SYS_IFACE_STATE_REMOVED))
+					nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_ASSUME);
+			} else {
+				reason = NM_DEVICE_STATE_REASON_REMOVED;
+				nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
+			}
+			nm_device_set_unmanaged_by_flags (self,
+			                                  NM_UNMANAGED_USER_EXPLICIT,
+			                                  !managed,
+			                                  reason);
+		}
+		break;
+	case PROP_AUTOCONNECT:
+		/* via D-Bus */
+		if (g_value_get_boolean (value))
+			nm_device_autoconnect_blocked_unset (self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL);
+		else
+			nm_device_autoconnect_blocked_set (self, NM_DEVICE_AUTOCONNECT_BLOCKED_USER);
+		break;
+	case PROP_NM_PLUGIN_MISSING:
+		/* construct-only */
+		priv->nm_plugin_missing = g_value_get_boolean (value);
+		break;
+	case PROP_DEVICE_TYPE:
+		/* construct-only */
+		nm_assert (priv->type == NM_DEVICE_TYPE_UNKNOWN);
+		priv->type = g_value_get_uint (value);
+		nm_assert (priv->type > NM_DEVICE_TYPE_UNKNOWN);
+		nm_assert (priv->type <= NM_DEVICE_TYPE_VRF);
+		break;
+	case PROP_LINK_TYPE:
+		/* construct-only */
+		nm_assert (priv->link_type == NM_LINK_TYPE_NONE);
+		priv->link_type = g_value_get_uint (value);
+		break;
+	case PROP_TYPE_DESC:
+		/* construct-only */
+		priv->type_desc = g_value_dup_string (value);
+		break;
+	case PROP_RFKILL_TYPE:
+		/* construct-only */
+		priv->rfkill_type = g_value_get_uint (value);
+		break;
+	case PROP_PERM_HW_ADDRESS:
+		/* construct-only */
+		priv->hw_addr_perm = g_value_dup_string (value);
+		break;
+	case PROP_STATISTICS_REFRESH_RATE_MS:
+		/* via D-Bus */
+		_stats_set_refresh_rate (self, g_value_get_uint (value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
 nm_device_init (NMDevice *self)
 {
 	NMDevicePrivate *priv;
@@ -16994,7 +17360,7 @@ constructor (GType type,
 		gsize l;
 
 		if (!_nm_utils_hwaddr_aton (priv->hw_addr_perm, buf, sizeof (buf), &l)) {
-			g_clear_pointer (&priv->hw_addr_perm, g_free);
+			nm_clear_g_free (&priv->hw_addr_perm);
 			g_return_val_if_reached (object);
 		}
 
@@ -17160,7 +17526,7 @@ finalize (GObject *object)
 	g_free (priv->hw_addr_initial);
 	g_slist_free (priv->pending_actions);
 	g_slist_free_full (priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
-	g_clear_pointer (&priv->physical_port_id, g_free);
+	nm_clear_g_free (&priv->physical_port_id);
 	g_free (priv->udi);
 	g_free (priv->iface);
 	g_free (priv->ip_iface);
@@ -17188,306 +17554,7 @@ finalize (GObject *object)
 	g_object_unref (priv->netns);
 }
 
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMDevice *self = (NMDevice *) object;
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-
-	switch (prop_id) {
-	case PROP_UDI:
-		/* construct-only */
-		priv->udi = g_value_dup_string (value);
-		break;
-	case PROP_IFACE:
-		/* construct-only */
-		priv->iface = g_value_dup_string (value);
-		break;
-	case PROP_DRIVER:
-		/* construct-only */
-		priv->driver = g_value_dup_string (value);
-		break;
-	case PROP_DRIVER_VERSION:
-		/* construct-only */
-		priv->driver_version = g_value_dup_string (value);
-		break;
-	case PROP_FIRMWARE_VERSION:
-		/* construct-only */
-		priv->firmware_version = g_value_dup_string (value);
-		break;
-	case PROP_IP4_ADDRESS:
-		priv->ip4_address = g_value_get_uint (value);
-		break;
-	case PROP_MANAGED:
-		if (nm_device_is_real (self)) {
-			gboolean managed;
-			NMDeviceStateReason reason;
-
-			managed = g_value_get_boolean (value);
-			if (managed) {
-				reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
-				if (NM_IN_SET_TYPED (NMDeviceSysIfaceState,
-				                     priv->sys_iface_state,
-				                     NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
-				                     NM_DEVICE_SYS_IFACE_STATE_REMOVED))
-					nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_ASSUME);
-			} else {
-				reason = NM_DEVICE_STATE_REASON_REMOVED;
-				nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
-			}
-			nm_device_set_unmanaged_by_flags (self,
-			                                  NM_UNMANAGED_USER_EXPLICIT,
-			                                  !managed,
-			                                  reason);
-		}
-		break;
-	case PROP_AUTOCONNECT:
-		if (g_value_get_boolean (value))
-			nm_device_autoconnect_blocked_unset (self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL);
-		else
-			nm_device_autoconnect_blocked_set (self, NM_DEVICE_AUTOCONNECT_BLOCKED_USER);
-		break;
-	case PROP_FIRMWARE_MISSING:
-		/* construct-only */
-		priv->firmware_missing = g_value_get_boolean (value);
-		break;
-	case PROP_NM_PLUGIN_MISSING:
-		/* construct-only */
-		priv->nm_plugin_missing = g_value_get_boolean (value);
-		break;
-	case PROP_DEVICE_TYPE:
-		/* construct-only */
-		nm_assert (priv->type == NM_DEVICE_TYPE_UNKNOWN);
-		priv->type = g_value_get_uint (value);
-		nm_assert (priv->type > NM_DEVICE_TYPE_UNKNOWN);
-		nm_assert (priv->type <= NM_DEVICE_TYPE_WIFI_P2P);
-		break;
-	case PROP_LINK_TYPE:
-		/* construct-only */
-		nm_assert (priv->link_type == NM_LINK_TYPE_NONE);
-		priv->link_type = g_value_get_uint (value);
-		break;
-	case PROP_TYPE_DESC:
-		/* construct-only */
-		priv->type_desc = g_value_dup_string (value);
-		break;
-	case PROP_RFKILL_TYPE:
-		/* construct-only */
-		priv->rfkill_type = g_value_get_uint (value);
-		break;
-	case PROP_PERM_HW_ADDRESS:
-		/* construct-only */
-		priv->hw_addr_perm = g_value_dup_string (value);
-		break;
-	case PROP_REFRESH_RATE_MS:
-		_stats_set_refresh_rate (self, g_value_get_uint (value));
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMDevice *self = NM_DEVICE (object);
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	GVariantBuilder array_builder;
-
-	switch (prop_id) {
-	case PROP_UDI:
-		/* UDI is (depending on the device type) a path to sysfs and can contain
-		 * non-UTF-8.
-		 *   ip link add name $'d\xccf\\c' type dummy  */
-		g_value_take_string (value,
-		                     nm_utils_str_utf8safe_escape_cp (priv->udi,
-		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_NONE));
-		break;
-	case PROP_IFACE:
-		g_value_take_string (value,
-		                     nm_utils_str_utf8safe_escape_cp (priv->iface,
-		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
-		break;
-	case PROP_IP_IFACE:
-		if (ip_config_valid (priv->state)) {
-			g_value_take_string (value,
-			                     nm_utils_str_utf8safe_escape_cp (nm_device_get_ip_iface (self),
-			                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
-		} else
-			g_value_set_string (value, NULL);
-		break;
-	case PROP_IFINDEX:
-		g_value_set_int (value, priv->ifindex);
-		break;
-	case PROP_DRIVER:
-		g_value_take_string (value,
-		                     nm_utils_str_utf8safe_escape_cp (priv->driver,
-		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
-		break;
-	case PROP_DRIVER_VERSION:
-		g_value_take_string (value,
-		                     nm_utils_str_utf8safe_escape_cp (priv->driver_version,
-		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
-		break;
-	case PROP_FIRMWARE_VERSION:
-		g_value_take_string (value,
-		                     nm_utils_str_utf8safe_escape_cp (priv->firmware_version,
-		                                                      NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
-		break;
-	case PROP_CAPABILITIES:
-		g_value_set_uint (value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK));
-		break;
-	case PROP_IP4_ADDRESS:
-		g_value_set_uint (value, priv->ip4_address);
-		break;
-	case PROP_CARRIER:
-		g_value_set_boolean (value, priv->carrier);
-		break;
-	case PROP_MTU:
-		g_value_set_uint (value, priv->mtu);
-		break;
-	case PROP_IP4_CONFIG:
-		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_4 : NULL);
-		break;
-	case PROP_DHCP4_CONFIG:
-		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp4.config : NULL);
-		break;
-	case PROP_IP6_CONFIG:
-		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->ip_config_6 : NULL);
-		break;
-	case PROP_DHCP6_CONFIG:
-		nm_dbus_utils_g_value_set_object_path (value, ip_config_valid (priv->state) ? priv->dhcp6.config : NULL);
-		break;
-	case PROP_STATE:
-		g_value_set_uint (value, priv->state);
-		break;
-	case PROP_STATE_REASON:
-		g_value_take_variant (value,
-		                      g_variant_new ("(uu)", priv->state, priv->state_reason));
-		break;
-	case PROP_ACTIVE_CONNECTION:
-		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->act_request));
-		break;
-	case PROP_DEVICE_TYPE:
-		g_value_set_uint (value, priv->type);
-		break;
-	case PROP_LINK_TYPE:
-		g_value_set_uint (value, priv->link_type);
-		break;
-	case PROP_MANAGED:
-		/* The managed state exposed on D-Bus only depends on the current device state alone. */
-		g_value_set_boolean (value, nm_device_get_state (self) > NM_DEVICE_STATE_UNMANAGED);
-		break;
-	case PROP_AUTOCONNECT:
-		g_value_set_boolean (value,
-		                     nm_device_autoconnect_blocked_get (self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)
-		                       ? FALSE
-		                       : TRUE);
-		break;
-	case PROP_FIRMWARE_MISSING:
-		g_value_set_boolean (value, priv->firmware_missing);
-		break;
-	case PROP_NM_PLUGIN_MISSING:
-		g_value_set_boolean (value, priv->nm_plugin_missing);
-		break;
-	case PROP_TYPE_DESC:
-		g_value_set_string (value, priv->type_desc);
-		break;
-	case PROP_RFKILL_TYPE:
-		g_value_set_uint (value, priv->rfkill_type);
-		break;
-	case PROP_AVAILABLE_CONNECTIONS:
-		nm_dbus_utils_g_value_set_object_path_from_hash (value,
-		                                                 priv->available_connections,
-		                                                 TRUE);
-		break;
-	case PROP_PHYSICAL_PORT_ID:
-		g_value_set_string (value, priv->physical_port_id);
-		break;
-	case PROP_MASTER:
-		g_value_set_object (value, nm_device_get_master (self));
-		break;
-	case PROP_PARENT:
-		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->parent_device));
-		break;
-	case PROP_HW_ADDRESS:
-		g_value_set_string (value, priv->hw_addr);
-		break;
-	case PROP_PERM_HW_ADDRESS: {
-		const char *perm_hw_addr;
-		gboolean perm_hw_addr_is_fake;
-
-		perm_hw_addr = nm_device_get_permanent_hw_address_full (self, FALSE, &perm_hw_addr_is_fake);
-		/* this property is exposed on D-Bus for NMDeviceEthernet and NMDeviceWifi. */
-		g_value_set_string (value, perm_hw_addr && !perm_hw_addr_is_fake ? perm_hw_addr : NULL);
-		break;
-	}
-	case PROP_HAS_PENDING_ACTION:
-		g_value_set_boolean (value, nm_device_has_pending_action (self));
-		break;
-	case PROP_METERED:
-		g_value_set_uint (value, priv->metered);
-		break;
-	case PROP_LLDP_NEIGHBORS:
-		if (priv->lldp_listener)
-			g_value_set_variant (value, nm_lldp_listener_get_neighbors (priv->lldp_listener));
-		else {
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
-			g_value_take_variant (value, g_variant_builder_end (&array_builder));
-		}
-		break;
-	case PROP_REAL:
-		g_value_set_boolean (value, nm_device_is_real (self));
-		break;
-	case PROP_SLAVES: {
-		CList *slave_iter;
-		char **slave_list;
-		gsize i, n;
-
-		n = c_list_length (&priv->slaves);
-		slave_list = g_new (char *, n + 1);
-		i = 0;
-		c_list_for_each (slave_iter, &priv->slaves) {
-			SlaveInfo *info = c_list_entry (slave_iter, SlaveInfo, lst_slave);
-			const char *path;
-
-			if (!NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
-				continue;
-			path = nm_dbus_object_get_path (NM_DBUS_OBJECT (info->slave));
-			if (path)
-				slave_list[i++] = g_strdup (path);
-		}
-		nm_assert (i <= n);
-		slave_list[i] = NULL;
-		g_value_take_boxed (value, slave_list);
-		break;
-	}
-	case PROP_REFRESH_RATE_MS:
-		g_value_set_uint (value, priv->stats.refresh_rate_ms);
-		break;
-	case PROP_TX_BYTES:
-		g_value_set_uint64 (value, priv->stats.tx_bytes);
-		break;
-	case PROP_RX_BYTES:
-		g_value_set_uint64 (value, priv->stats.rx_bytes);
-		break;
-	case PROP_IP4_CONNECTIVITY:
-		g_value_set_uint (value, priv->concheck_x[1].state);
-		break;
-	case PROP_IP6_CONNECTIVITY:
-		g_value_set_uint (value, priv->concheck_x[0].state);
-		break;
-	case PROP_INTERFACE_FLAGS:
-		g_value_set_uint (value, priv->interface_flags);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
+/*****************************************************************************/
 
 static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
 	"StateChanged",
@@ -17572,6 +17639,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("Ip4Connectivity",      "u",      NM_DEVICE_IP4_CONNECTIVITY),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("Ip6Connectivity",      "u",      NM_DEVICE_IP6_CONNECTIVITY),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("InterfaceFlags",       "u",      NM_DEVICE_INTERFACE_FLAGS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("HwAddress",            "s",      NM_DEVICE_HW_ADDRESS),
 		),
 	),
 };
@@ -17631,6 +17699,7 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->parent_changed_notify = parent_changed_notify;
 	klass->can_reapply_change = can_reapply_change;
 	klass->reapply_connection = reapply_connection;
+	klass->set_platform_mtu = set_platform_mtu;
 
 	obj_properties[PROP_UDI] =
 	    g_param_spec_string (NM_DEVICE_UDI, "", "",
@@ -17655,12 +17724,12 @@ nm_device_class_init (NMDeviceClass *klass)
 	obj_properties[PROP_DRIVER_VERSION] =
 	    g_param_spec_string (NM_DEVICE_DRIVER_VERSION, "", "",
 	                         NULL,
-	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_FIRMWARE_VERSION] =
 	    g_param_spec_string (NM_DEVICE_FIRMWARE_VERSION, "", "",
 	                         NULL,
-	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_CAPABILITIES] =
 	    g_param_spec_uint (NM_DEVICE_CAPABILITIES, "", "",
@@ -17680,27 +17749,27 @@ nm_device_class_init (NMDeviceClass *klass)
 	obj_properties[PROP_IP4_ADDRESS] =
 	    g_param_spec_uint (NM_DEVICE_IP4_ADDRESS, "", "",
 	                       0, G_MAXUINT32, 0, /* FIXME */
-	                       G_PARAM_READWRITE |
+	                       G_PARAM_READABLE |
 	                       G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_IP4_CONFIG] =
 	    g_param_spec_string (NM_DEVICE_IP4_CONFIG, "", "",
 	                         NULL,
-	                         G_PARAM_READWRITE |
+	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_DHCP4_CONFIG] =
 	    g_param_spec_string (NM_DEVICE_DHCP4_CONFIG, "", "",
 	                         NULL,
-	                         G_PARAM_READWRITE |
+	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_IP6_CONFIG] =
 	    g_param_spec_string (NM_DEVICE_IP6_CONFIG, "", "",
 	                         NULL,
-	                         G_PARAM_READWRITE |
+	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_DHCP6_CONFIG] =
 	    g_param_spec_string (NM_DEVICE_DHCP6_CONFIG, "", "",
 	                         NULL,
-	                         G_PARAM_READWRITE |
+	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_STATE] =
 	    g_param_spec_uint (NM_DEVICE_STATE, "", "",
@@ -17731,17 +17800,17 @@ nm_device_class_init (NMDeviceClass *klass)
 	obj_properties[PROP_MANAGED] =
 	    g_param_spec_boolean (NM_DEVICE_MANAGED, "", "",
 	                          FALSE,
-	                          G_PARAM_READWRITE |
+	                          G_PARAM_READWRITE | /* via D-Bus */
 	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_AUTOCONNECT] =
 	    g_param_spec_boolean (NM_DEVICE_AUTOCONNECT, "", "",
 	                          DEFAULT_AUTOCONNECT,
-	                          G_PARAM_READWRITE |
+	                          G_PARAM_READWRITE | /* via D-Bus */
 	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_FIRMWARE_MISSING] =
 	    g_param_spec_boolean (NM_DEVICE_FIRMWARE_MISSING, "", "",
 	                          FALSE,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_NM_PLUGIN_MISSING] =
 	    g_param_spec_boolean (NM_DEVICE_NM_PLUGIN_MISSING, "", "",
@@ -17822,17 +17891,17 @@ nm_device_class_init (NMDeviceClass *klass)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_REFRESH_RATE_MS] =
+	obj_properties[PROP_STATISTICS_REFRESH_RATE_MS] =
 	    g_param_spec_uint (NM_DEVICE_STATISTICS_REFRESH_RATE_MS, "", "",
 	                       0, UINT32_MAX, 0,
-	                       G_PARAM_READWRITE |
+	                       G_PARAM_READWRITE | /* via D-Bus */
 	                       G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_TX_BYTES] =
+	obj_properties[PROP_STATISTICS_TX_BYTES] =
 	    g_param_spec_uint64 (NM_DEVICE_STATISTICS_TX_BYTES, "", "",
 	                         0, UINT64_MAX, 0,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_RX_BYTES] =
+	obj_properties[PROP_STATISTICS_RX_BYTES] =
 	    g_param_spec_uint64 (NM_DEVICE_STATISTICS_RX_BYTES, "", "",
 	                         0, UINT64_MAX, 0,
 	                         G_PARAM_READABLE |
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 8c6c856e..c18301b2 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -444,6 +444,8 @@ typedef struct _NMDeviceClass {
 
 	gboolean        (* can_update_from_platform_link) (NMDevice *self, const NMPlatformLink *plink);
 
+	gboolean        (* set_platform_mtu) (NMDevice *self, guint32 mtu);
+
 	/* Controls, whether to call act_stage2_config() callback also for assuming
 	 * a device or for external activations. In this case, act_stage2_config() must
 	 * take care not to touch the device's configuration. */
@@ -504,8 +506,7 @@ const char *    nm_device_get_initial_hw_address (NMDevice *dev);
 
 NMProxyConfig * nm_device_get_proxy_config      (NMDevice *dev);
 
-NMDhcp4Config * nm_device_get_dhcp4_config      (NMDevice *dev);
-NMDhcp6Config * nm_device_get_dhcp6_config      (NMDevice *dev);
+NMDhcpConfig *  nm_device_get_dhcp_config       (NMDevice *dev, int addr_family);
 NMIP4Config *   nm_device_get_ip4_config        (NMDevice *dev);
 void            nm_device_replace_vpn4_config   (NMDevice *dev,
                                                  NMIP4Config *old,
@@ -883,4 +884,6 @@ struct _NMBtVTableNetworkServer {
 const char *nm_device_state_to_str (NMDeviceState state);
 const char *nm_device_state_reason_to_str (NMDeviceStateReason reason);
 
+gboolean nm_device_is_vpn (NMDevice *self);
+
 #endif /* __NETWORKMANAGER_DEVICE_H__ */
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index 4c9e7705..b9caed50 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -17,7 +17,7 @@
 #include "systemd/nm-sd.h"
 
 #define MAX_NEIGHBORS         4096
-#define MIN_UPDATE_INTERVAL_NS (2 * NM_UTILS_NS_PER_SECOND)
+#define MIN_UPDATE_INTERVAL_NS (2 * NM_UTILS_NSEC_PER_SEC)
 
 #define LLDP_MAC_NEAREST_BRIDGE          ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }))
 #define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE ((const struct ether_addr *) ((uint8_t[ETH_ALEN]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }))
@@ -154,7 +154,8 @@ ether_addr_equal (const struct ether_addr *a1, const struct ether_addr *a2)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_lldp_attr_id_to_name, LldpAttrId,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_lldp_attr_id_to_name, LldpAttrId,
 	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
 	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,         NM_LLDP_ATTR_PORT_DESCRIPTION),
 	NM_UTILS_LOOKUP_STR_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,              NM_LLDP_ATTR_SYSTEM_NAME),
@@ -174,7 +175,8 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_lldp_attr_id_to_name, LldpAttrId,
 	NM_UTILS_LOOKUP_ITEM_IGNORE (_LLDP_ATTR_ID_COUNT),
 );
 
-_NM_UTILS_LOOKUP_DEFINE (static, _lldp_attr_id_to_type, LldpAttrId, LldpAttrType,
+static
+NM_UTILS_LOOKUP_DEFINE (_lldp_attr_id_to_type, LldpAttrId, LldpAttrType,
 	NM_UTILS_LOOKUP_DEFAULT_WARN (LLDP_ATTR_TYPE_NONE),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_PORT_DESCRIPTION,            LLDP_ATTR_TYPE_STRING),
 	NM_UTILS_LOOKUP_ITEM (LLDP_ATTR_ID_SYSTEM_NAME,                 LLDP_ATTR_TYPE_STRING),
@@ -347,7 +349,7 @@ lldp_neighbor_free (LldpNeighbor *neighbor)
 				;
 			}
 		}
-		g_clear_pointer (&neighbor->variant, g_variant_unref);
+		nm_clear_pointer (&neighbor->variant, g_variant_unref);
 		g_slice_free (LldpNeighbor, neighbor);
 	}
 }
@@ -828,7 +830,7 @@ data_changed_timeout (gpointer user_data)
 	priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 
 	priv->ratelimit_id = 0;
-	priv->ratelimit_next = nm_utils_get_monotonic_timestamp_ns() + MIN_UPDATE_INTERVAL_NS;
+	priv->ratelimit_next = nm_utils_get_monotonic_timestamp_nsec() + MIN_UPDATE_INTERVAL_NS;
 	data_changed_notify (self, priv);
 	return G_SOURCE_REMOVE;
 }
@@ -839,13 +841,13 @@ data_changed_schedule (NMLldpListener *self)
 	NMLldpListenerPrivate *priv = NM_LLDP_LISTENER_GET_PRIVATE (self);
 	gint64 now;
 
-	now = nm_utils_get_monotonic_timestamp_ns ();
+	now = nm_utils_get_monotonic_timestamp_nsec ();
 	if (now >= priv->ratelimit_next) {
 		nm_clear_g_source (&priv->ratelimit_id);
 		priv->ratelimit_next = now + MIN_UPDATE_INTERVAL_NS;
 		data_changed_notify (self, priv);
 	} else if (!priv->ratelimit_id)
-		priv->ratelimit_id = g_timeout_add (NM_UTILS_NS_TO_MSEC_CEIL (priv->ratelimit_next - now), data_changed_timeout, self);
+		priv->ratelimit_id = g_timeout_add (NM_UTILS_NSEC_TO_MSEC_CEIL (priv->ratelimit_next - now), data_changed_timeout, self);
 }
 
 static void
diff --git a/src/devices/ovs/meson.build b/src/devices/ovs/meson.build
index 27e1b4d0..8457e68a 100644
--- a/src/devices/ovs/meson.build
+++ b/src/devices/ovs/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 sources = files(
   'nm-device-ovs-bridge.c',
   'nm-device-ovs-interface.c',
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 2868dee0..951b5788 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -121,6 +121,43 @@ _is_internal_interface (NMDevice *device)
 	return nm_streq (nm_setting_ovs_interface_get_interface_type (s_ovs_iface), "internal");
 }
 
+static void
+set_platform_mtu_cb (GError *error, gpointer user_data)
+{
+	NMDevice *device = user_data;
+	NMDeviceOvsInterface *self = NM_DEVICE_OVS_INTERFACE (device);
+
+	if (   error
+	    && !g_error_matches (error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
+		_LOGW (LOGD_DEVICE, "could not change mtu of '%s': %s",
+		       nm_device_get_iface (device), error->message);
+	}
+
+	g_object_unref (device);
+}
+
+static gboolean
+set_platform_mtu (NMDevice *device, guint32 mtu)
+{
+	/*
+	 * If the MTU is not set in ovsdb, Open vSwitch will change
+	 * the MTU of an internal interface to match the minimum of
+	 * the other interfaces in the bridge.
+	 */
+	/* FIXME(shutdown): the function should become cancellable so
+	 * that it doesn't need to hold a reference to the device, and
+	 * it can be stopped during shutdown.
+	 */
+	if (_is_internal_interface (device)) {
+		nm_ovsdb_set_interface_mtu (nm_ovsdb_get (),
+		                            nm_device_get_ip_iface (device),
+		                            mtu, set_platform_mtu_cb,
+		                            g_object_ref (device));
+	}
+
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->set_platform_mtu (device, mtu);
+}
+
 static NMActStageReturn
 act_stage3_ip_config_start (NMDevice *device,
                             int addr_family,
@@ -351,4 +388,6 @@ nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 	device_class->link_changed = link_changed;
 	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
+	device_class->set_platform_mtu = set_platform_mtu;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
 }
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index ec4f5c74..b6550b25 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -64,6 +64,7 @@ typedef struct {
 	GHashTable *ports;              /* port uuid => OpenvswitchPort */
 	GHashTable *bridges;            /* bridge uuid => OpenvswitchBridge */
 	char *db_uuid;
+	guint num_failures;
 } NMOvsdbPrivate;
 
 struct _NMOvsdb {
@@ -87,7 +88,7 @@ NM_DEFINE_SINGLETON_GETTER (NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB);
 /*****************************************************************************/
 
 static void ovsdb_try_connect (NMOvsdb *self);
-static void ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing);
+static void ovsdb_disconnect (NMOvsdb *self, gboolean retry, gboolean is_disposing);
 static void ovsdb_read (NMOvsdb *self);
 static void ovsdb_write (NMOvsdb *self);
 static void ovsdb_next_command (NMOvsdb *self);
@@ -103,6 +104,7 @@ typedef enum {
 	OVSDB_MONITOR,
 	OVSDB_ADD_INTERFACE,
 	OVSDB_DEL_INTERFACE,
+	OVSDB_SET_INTERFACE_MTU,
 } OvsdbCommand;
 
 typedef struct {
@@ -112,7 +114,10 @@ typedef struct {
 	OvsdbMethodCallback callback;
 	gpointer user_data;
 	union {
-		char *ifname;
+		struct {
+			char *ifname;
+			guint32 mtu;
+		};
 		struct {
 			NMConnection *bridge;
 			NMConnection *port;
@@ -123,6 +128,8 @@ typedef struct {
 	};
 } OvsdbMethodCall;
 
+#define OVSDB_MAX_FAILURES    3
+
 static void
 _call_trace (const char *comment, OvsdbMethodCall *call, json_t *msg)
 {
@@ -154,6 +161,13 @@ _call_trace (const char *comment, OvsdbMethodCall *call, json_t *msg)
 		       msg ? ": " : "",
 		       msg ? str : "");
 		break;
+	case OVSDB_SET_INTERFACE_MTU:
+		_LOGT ("%s: set-iface-mtu interface=%s%s%s mtu=%u",
+		       comment, call->ifname,
+		       msg ? ": " : "",
+		       msg ? str : "",
+		       call->mtu);
+		break;
 	}
 
 	if (msg)
@@ -172,7 +186,8 @@ ovsdb_call_method (NMOvsdb *self, OvsdbCommand command,
                    const char *ifname,
                    NMConnection *bridge, NMConnection *port, NMConnection *interface,
                    NMDevice *bridge_device, NMDevice *interface_device,
-                   OvsdbMethodCallback callback, gpointer user_data)
+                   guint32 mtu, OvsdbMethodCallback callback, gpointer user_data,
+                   gboolean add_first)
 {
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
 	OvsdbMethodCall *call;
@@ -180,8 +195,13 @@ ovsdb_call_method (NMOvsdb *self, OvsdbCommand command,
 	/* Ensure we're not unsynchronized before we queue the method call. */
 	ovsdb_try_connect (self);
 
-	g_array_set_size (priv->calls, priv->calls->len + 1);
-	call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
+	if (add_first) {
+		g_array_prepend_val (priv->calls, (OvsdbMethodCall) {});
+		call = &g_array_index (priv->calls, OvsdbMethodCall, 0);
+	} else {
+		g_array_set_size (priv->calls, priv->calls->len + 1);
+		call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
+	}
 	call->id = COMMAND_PENDING;
 	call->command = command;
 	call->callback = callback;
@@ -200,6 +220,10 @@ ovsdb_call_method (NMOvsdb *self, OvsdbCommand command,
 	case OVSDB_DEL_INTERFACE:
 		call->ifname = g_strdup (ifname);
 		break;
+	case OVSDB_SET_INTERFACE_MTU:
+		call->ifname = g_strdup (ifname);
+		call->mtu = mtu;
+		break;
 	}
 
 	_call_trace ("enqueue", call, NULL);
@@ -338,11 +362,20 @@ _insert_interface (json_t *params, NMConnection *interface, NMDevice *interface_
 	gs_free char *cloned_mac = NULL;
 	gs_free_error GError *error = NULL;
 	json_t *row;
+	guint32 mtu = 0;
 
 	s_ovs_iface = nm_connection_get_setting_ovs_interface (interface);
 	if (s_ovs_iface)
 		type = nm_setting_ovs_interface_get_interface_type (s_ovs_iface);
 
+	if (nm_streq0 (type, "internal")) {
+		NMSettingWired *s_wired;
+
+		s_wired = _nm_connection_get_setting (interface, NM_TYPE_SETTING_WIRED);
+		if (s_wired)
+			mtu = nm_setting_wired_get_mtu (s_wired);
+	}
+
 	if (!nm_device_hw_addr_get_cloned (interface_device,
 	                                   interface,
 	                                   FALSE,
@@ -384,6 +417,9 @@ _insert_interface (json_t *params, NMConnection *interface, NMDevice *interface_
 	if (cloned_mac)
 		json_object_set_new (row, "mac", json_string (cloned_mac));
 
+	if (mtu != 0)
+		json_object_set_new (row, "mtu_request", json_integer (mtu));
+
 	json_array_append_new (params,
 	        json_pack ("{s:s, s:s, s:o, s:s}",
 	                   "op", "insert",
@@ -820,6 +856,22 @@ ovsdb_next_command (NMOvsdb *self)
 		                 "id", call->id,
 		                 "method", "transact", "params", params);
 		break;
+	case OVSDB_SET_INTERFACE_MTU:
+		params = json_array ();
+		json_array_append_new (params, json_string ("Open_vSwitch"));
+		json_array_append_new (params, _inc_next_cfg (priv->db_uuid));
+
+		json_array_append_new (params,
+		                       json_pack ("{s:s, s:s, s:{s: i}, s:[[s, s, s]]}",
+		                                  "op", "update",
+		                                  "table", "Interface",
+		                                  "row", "mtu_request", call->mtu,
+		                                  "where", "name", "==", call->ifname));
+
+		msg = json_pack ("{s:i, s:s, s:o}",
+		                 "id", call->id,
+		                 "method", "transact", "params", params);
+		break;
 	}
 
 	g_return_if_fail (msg);
@@ -1153,7 +1205,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 	                    "result", &result,
 	                    "error", &error) == -1) {
 		_LOGW ("couldn't grok the message: %s", json_error.text);
-		ovsdb_disconnect (self, FALSE);
+		ovsdb_disconnect (self, FALSE, FALSE);
 		return;
 	}
 
@@ -1164,7 +1216,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 		/* It's a method call! */
 		if (!params) {
 			_LOGW ("a method call with no params: '%s'", method);
-			ovsdb_disconnect (self, FALSE);
+			ovsdb_disconnect (self, FALSE, FALSE);
 			return;
 		}
 
@@ -1184,13 +1236,13 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 		/* This is a response to a method call. */
 		if (!priv->calls->len) {
 			_LOGE ("there are no queued calls expecting response %" G_GUINT64_FORMAT, id);
-			ovsdb_disconnect (self, FALSE);
+			ovsdb_disconnect (self, FALSE, FALSE);
 			return;
 		}
 		call = &g_array_index (priv->calls, OvsdbMethodCall, 0);
 		if (call->id != id) {
 			_LOGE ("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, call->id, id);
-			ovsdb_disconnect (self, FALSE);
+			ovsdb_disconnect (self, FALSE, FALSE);
 			return;
 		}
 		/* Cool, we found a corresponding call. Finish it. */
@@ -1208,6 +1260,7 @@ ovsdb_got_msg (NMOvsdb *self, json_t *msg)
 		user_data = call->user_data;
 		g_array_remove_index (priv->calls, 0);
 		callback (self, result, local, user_data);
+		priv->num_failures = 0;
 
 		/* Don't progress further commands in case the callback hit an error
 		 * and disconnected us. */
@@ -1266,9 +1319,11 @@ ovsdb_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
 
 	size = g_input_stream_read_finish (stream, res, &error);
 	if (size == -1) {
+		/* ovsdb-server was possibly restarted */
 		_LOGW ("short read from ovsdb: %s", error->message);
+		priv->num_failures++;
 		g_clear_error (&error);
-		ovsdb_disconnect (self, FALSE);
+		ovsdb_disconnect (self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
 		return;
 	}
 
@@ -1314,9 +1369,11 @@ ovsdb_write_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
 
 	size = g_output_stream_write_finish (stream, res, &error);
 	if (size == -1) {
+		/* ovsdb-server was possibly restarted */
 		_LOGW ("short write to ovsdb: %s", error->message);
+		priv->num_failures++;
 		g_clear_error (&error);
-		ovsdb_disconnect (self, FALSE);
+		ovsdb_disconnect (self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
 		return;
 	}
 
@@ -1359,7 +1416,7 @@ ovsdb_write (NMOvsdb *self)
  * puts us back in sync.
  */
 static void
-ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
+ovsdb_disconnect (NMOvsdb *self, gboolean retry, gboolean is_disposing)
 {
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
 	OvsdbMethodCall *call;
@@ -1367,18 +1424,26 @@ ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
 	gpointer user_data;
 	gs_free_error GError *error = NULL;
 
+	nm_assert (!retry || !is_disposing);
+
 	if (!priv->client)
 		return;
 
-	_LOGD ("disconnecting from ovsdb");
-	nm_utils_error_set_cancelled (&error, is_disposing, "NMOvsdb");
+	_LOGD ("disconnecting from ovsdb, retry %d", retry);
 
-	while (priv->calls->len) {
-		call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
-		callback = call->callback;
-		user_data = call->user_data;
-		g_array_remove_index (priv->calls, priv->calls->len - 1);
-		callback (self, NULL, error, user_data);
+	if (retry) {
+		if (priv->calls->len != 0)
+			g_array_index (priv->calls, OvsdbMethodCall, 0).id = COMMAND_PENDING;
+	} else {
+		nm_utils_error_set_cancelled (&error, is_disposing, "NMOvsdb");
+
+		while (priv->calls->len) {
+			call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
+			callback = call->callback;
+			user_data = call->user_data;
+			g_array_remove_index (priv->calls, priv->calls->len - 1);
+			callback (self, NULL, error, user_data);
+		}
 	}
 
 	priv->bufp = 0;
@@ -1386,17 +1451,20 @@ ovsdb_disconnect (NMOvsdb *self, gboolean is_disposing)
 	g_string_truncate (priv->output, 0);
 	g_clear_object (&priv->client);
 	g_clear_object (&priv->conn);
-	g_clear_pointer (&priv->db_uuid, g_free);
+	nm_clear_g_free (&priv->db_uuid);
 	nm_clear_g_cancellable (&priv->cancellable);
+
+	if (retry)
+		ovsdb_try_connect (self);
 }
 
 static void
 _monitor_bridges_cb (NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
 {
 	if (error) {
-		if (!nm_utils_error_is_cancelled (error, TRUE)) {
+		if (!nm_utils_error_is_cancelled_or_disposing (error)) {
 			_LOGI ("%s", error->message);
-			ovsdb_disconnect (self, FALSE);
+			ovsdb_disconnect (self, FALSE, FALSE);
 		}
 		return;
 	}
@@ -1420,7 +1488,7 @@ _client_connect_cb (GObject *source_object, GAsyncResult *res, gpointer user_dat
 		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 			_LOGI ("%s", error->message);
 
-		ovsdb_disconnect (self, FALSE);
+		ovsdb_disconnect (self, FALSE, FALSE);
 		g_clear_error (&error);
 		return;
 	}
@@ -1461,7 +1529,8 @@ ovsdb_try_connect (NMOvsdb *self)
 	/* Queue a monitor call before any other command, ensuring that we have an up
 	 * to date view of existing bridged that we need for add and remove ops. */
 	ovsdb_call_method (self, OVSDB_MONITOR, NULL,
-	                   NULL, NULL, NULL, NULL, NULL, _monitor_bridges_cb, NULL);
+	                   NULL, NULL, NULL, NULL, NULL, 0,
+	                   _monitor_bridges_cb, NULL, TRUE);
 }
 
 /*****************************************************************************/
@@ -1499,11 +1568,8 @@ out:
 	g_slice_free (OvsdbCall, call);
 }
 
-void
-nm_ovsdb_add_interface (NMOvsdb *self,
-                        NMConnection *bridge, NMConnection *port, NMConnection *interface,
-                        NMDevice *bridge_device, NMDevice *interface_device,
-                        NMOvsdbCallback callback, gpointer user_data)
+static OvsdbCall *
+ovsdb_call_new (NMOvsdbCallback callback, gpointer user_data)
 {
 	OvsdbCall *call;
 
@@ -1511,24 +1577,43 @@ nm_ovsdb_add_interface (NMOvsdb *self,
 	call->callback = callback;
 	call->user_data = user_data;
 
+	return call;
+}
+
+void
+nm_ovsdb_add_interface (NMOvsdb *self,
+                        NMConnection *bridge, NMConnection *port, NMConnection *interface,
+                        NMDevice *bridge_device, NMDevice *interface_device,
+                        NMOvsdbCallback callback, gpointer user_data)
+{
 	ovsdb_call_method (self, OVSDB_ADD_INTERFACE, NULL,
 	                   bridge, port, interface,
 	                   bridge_device, interface_device,
-	                   _transact_cb, call);
+	                   0,
+	                   _transact_cb,
+	                   ovsdb_call_new (callback, user_data),
+	                   FALSE);
 }
 
 void
 nm_ovsdb_del_interface (NMOvsdb *self, const char *ifname,
                         NMOvsdbCallback callback, gpointer user_data)
 {
-	OvsdbCall *call;
-
-	call = g_slice_new (OvsdbCall);
-	call->callback = callback;
-	call->user_data = user_data;
-
 	ovsdb_call_method (self, OVSDB_DEL_INTERFACE, ifname,
-	                   NULL, NULL, NULL, NULL, NULL, _transact_cb, call);
+	                   NULL, NULL, NULL, NULL, NULL, 0,
+	                   _transact_cb,
+	                   ovsdb_call_new (callback, user_data),
+	                   FALSE);
+}
+
+void nm_ovsdb_set_interface_mtu (NMOvsdb *self, const char *ifname, guint32 mtu,
+                                 NMOvsdbCallback callback, gpointer user_data)
+{
+	ovsdb_call_method (self, OVSDB_SET_INTERFACE_MTU, ifname,
+	                   NULL, NULL, NULL, NULL, NULL, mtu,
+	                   _transact_cb,
+	                   ovsdb_call_new (callback, user_data),
+	                   FALSE);
 }
 
 /*****************************************************************************/
@@ -1549,7 +1634,8 @@ _clear_call (gpointer data)
 		g_clear_object (&call->interface_device);
 		break;
 	case OVSDB_DEL_INTERFACE:
-		g_clear_pointer (&call->ifname, g_free);
+	case OVSDB_SET_INTERFACE_MTU:
+		nm_clear_g_free (&call->ifname);
 		break;
 	}
 }
@@ -1609,7 +1695,7 @@ dispose (GObject *object)
 	NMOvsdb *self = NM_OVSDB (object);
 	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
 
-	ovsdb_disconnect (self, TRUE);
+	ovsdb_disconnect (self, FALSE, TRUE);
 
 	if (priv->input) {
 		g_string_free (priv->input, TRUE);
@@ -1624,9 +1710,9 @@ dispose (GObject *object)
 		priv->calls = NULL;
 	}
 
-	g_clear_pointer (&priv->bridges, g_hash_table_destroy);
-	g_clear_pointer (&priv->ports, g_hash_table_destroy);
-	g_clear_pointer (&priv->interfaces, g_hash_table_destroy);
+	nm_clear_pointer (&priv->bridges, g_hash_table_destroy);
+	nm_clear_pointer (&priv->ports, g_hash_table_destroy);
+	nm_clear_pointer (&priv->interfaces, g_hash_table_destroy);
 
 	G_OBJECT_CLASS (nm_ovsdb_parent_class)->dispose (object);
 }
diff --git a/src/devices/ovs/nm-ovsdb.h b/src/devices/ovs/nm-ovsdb.h
index 59f46206..72a2dc73 100644
--- a/src/devices/ovs/nm-ovsdb.h
+++ b/src/devices/ovs/nm-ovsdb.h
@@ -34,4 +34,7 @@ void nm_ovsdb_add_interface (NMOvsdb *self,
 void nm_ovsdb_del_interface (NMOvsdb *self, const char *ifname,
                              NMOvsdbCallback callback, gpointer user_data);
 
+void nm_ovsdb_set_interface_mtu (NMOvsdb *self, const char *ifname, guint32 mtu,
+                                 NMOvsdbCallback callback, gpointer user_data);
+
 #endif /* __NETWORKMANAGER_OVSDB_H__ */
diff --git a/src/devices/team/meson.build b/src/devices/team/meson.build
index 0e63183b..c2b77cb9 100644
--- a/src/devices/team/meson.build
+++ b/src/devices/team/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 sources = files(
   'nm-device-team.c',
   'nm-team-factory.c',
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index a0749c21..06f8840d 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -416,7 +416,7 @@ teamd_dbus_appeared (GDBusConnection *connection,
 	}
 
 	priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED;
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -932,7 +932,7 @@ dispose (GObject *object)
 	}
 
 	teamd_cleanup (self, TRUE);
-	g_clear_pointer (&priv->config, g_free);
+	nm_clear_g_free (&priv->config);
 
 	G_OBJECT_CLASS (nm_device_team_parent_class)->dispose (object);
 }
diff --git a/src/devices/tests/meson.build b/src/devices/tests/meson.build
index bb53d1ee..1ddd4f85 100644
--- a/src/devices/tests/meson.build
+++ b/src/devices/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_units = [
   'test-acd',
   'test-lldp',
diff --git a/src/devices/tests/test-acd.c b/src/devices/tests/test-acd.c
index da5e4f2d..32d6f669 100644
--- a/src/devices/tests/test-acd.c
+++ b/src/devices/tests/test-acd.c
@@ -157,7 +157,7 @@ again:
 		}
 
 		g_error ("expected check for address #%d (%s) to %s, but it didn't",
-		         i, nm_utils_inet4_ntop (info->addresses[i], sbuf),
+		         i, _nm_utils_inet4_ntop (info->addresses[i], sbuf),
 		         info->expected_result[i] ? "detect no duplicated" : "detect a duplicate");
 	}
 }
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index bd71c9e3..8fc4ad67 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -501,7 +501,7 @@ test_recv (TestRecvFixture *fixture, gconstpointer user_data)
 	data->check (loop, listener);
 
 	nm_clear_g_source (&sd_id);
-	g_clear_pointer (&loop, g_main_loop_unref);
+	nm_clear_pointer (&loop, g_main_loop_unref);
 }
 
 static void
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
index 6566f201..00404c71 100644
--- a/src/devices/wifi/meson.build
+++ b/src/devices/wifi/meson.build
@@ -1,28 +1,38 @@
-common_sources = files(
-  'nm-wifi-ap.c',
-  'nm-wifi-p2p-peer.c',
-  'nm-wifi-utils.c',
-)
-
-sources = common_sources + files(
-  'nm-device-olpc-mesh.c',
-  'nm-device-wifi-p2p.c',
-  'nm-device-wifi.c',
-  'nm-wifi-common.c',
-  'nm-wifi-factory.c',
-)
+# SPDX-License-Identifier: LGPL-2.1+
 
+iwd_sources = files()
 if enable_iwd
-  sources += files(
+  iwd_sources += files(
     'nm-device-iwd.c',
     'nm-iwd-manager.c',
   )
 endif
 
+libnm_wifi_base = static_library(
+  'nm-wifi-base',
+  sources: files(
+    'nm-device-olpc-mesh.c',
+    'nm-device-wifi-p2p.c',
+    'nm-device-wifi.c',
+    'nm-wifi-ap.c',
+    'nm-wifi-common.c',
+    'nm-wifi-p2p-peer.c',
+    'nm-wifi-utils.c',
+  ) + iwd_sources,
+  dependencies: daemon_nm_default_dep,
+  c_args: daemon_c_flags,
+)
+
+libnm_wifi_base_dep = declare_dependency(
+  link_with: libnm_wifi_base,
+)
+
 libnm_device_plugin_wifi = shared_module(
   'nm-device-plugin-wifi',
-  sources: sources,
-  dependencies: daemon_nm_default_dep,
+  sources: files(
+    'nm-wifi-factory.c',
+  ),
+  dependencies: [ daemon_nm_default_dep, libnm_wifi_base_dep ],
   c_args: daemon_c_flags,
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
@@ -43,8 +53,8 @@ if enable_tests
 
   exe = executable(
     test_unit,
-    ['tests/' + test_unit + '.c'] + common_sources,
-    dependencies: libnetwork_manager_test_dep,
+    'tests/' + test_unit + '.c',
+    dependencies: [ libnetwork_manager_test_dep, libnm_wifi_base_dep ],
     c_args: test_c_flags,
   )
 
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 6b587e3f..3c1d5b22 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -7,24 +7,26 @@
 
 #include "nm-device-iwd.h"
 
-#include "nm-libnm-core-intern/nm-common-macros.h"
-#include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
-#include "nm-utils.h"
+#include "devices/nm-device.h"
 #include "nm-act-request.h"
+#include "nm-config.h"
+#include "nm-core-internal.h"
+#include "nm-dbus-manager.h"
+#include "nm-glib-aux/nm-ref-string.h"
+#include "nm-iwd-manager.h"
+#include "nm-libnm-core-intern/nm-common-macros.h"
+#include "nm-setting-8021x.h"
 #include "nm-setting-connection.h"
-#include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
-#include "nm-setting-8021x.h"
+#include "nm-setting-wireless.h"
+#include "nm-std-aux/nm-dbus-compat.h"
+#include "nm-utils.h"
+#include "nm-wifi-common.h"
+#include "nm-wifi-utils.h"
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
-#include "nm-wifi-utils.h"
-#include "nm-wifi-common.h"
-#include "nm-core-internal.h"
-#include "nm-config.h"
-#include "nm-iwd-manager.h"
-#include "nm-dbus-manager.h"
-#include "nm-std-aux/nm-dbus-compat.h"
+#include "supplicant/nm-supplicant-types.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceIwd);
@@ -41,14 +43,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceIwd,
 	PROP_LAST_SCAN,
 );
 
-enum {
-	SCANNING_PROHIBITED,
-
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
 typedef struct {
 	GDBusObject *   dbus_obj;
 	GDBusProxy *    dbus_device_proxy;
@@ -77,37 +71,35 @@ struct _NMDeviceIwd {
 
 struct _NMDeviceIwdClass {
 	NMDeviceClass parent;
-
-	/* Signals */
-	gboolean (*scanning_prohibited) (NMDeviceIwd *device, gboolean periodic);
 };
 
 /*****************************************************************************/
 
 G_DEFINE_TYPE (NMDeviceIwd, nm_device_iwd, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_IWD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceIwd, NM_IS_DEVICE_IWD)
+#define NM_DEVICE_IWD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceIwd, NM_IS_DEVICE_IWD, NMDevice)
 
 /*****************************************************************************/
 
 static void schedule_periodic_scan (NMDeviceIwd *self,
                                     gboolean initial_scan);
 
+static gboolean check_scanning_prohibited (NMDeviceIwd *self, gboolean periodic);
+
 /*****************************************************************************/
 
 static void
 _ap_dump (NMDeviceIwd *self,
           NMLogLevel log_level,
           const NMWifiAP *ap,
-          const char *prefix,
-          gint32 now_s)
+          const char *prefix)
 {
 	char buf[1024];
 
 	buf[0] = '\0';
 	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-ap: %-7s %s",
 	        prefix,
-	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_s));
+	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), 0));
 }
 
 /* Callers ensure we're not removing current_ap */
@@ -124,12 +116,12 @@ ap_add_remove (NMDeviceIwd *self,
 		ap->wifi_device = NM_DEVICE (self);
 		c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst);
 		nm_dbus_object_export (NM_DBUS_OBJECT (ap));
-		_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
+		_ap_dump (self, LOGL_DEBUG, ap, "added");
 		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE);
 	} else {
 		ap->wifi_device = NULL;
 		c_list_unlink (&ap->aps_lst);
-		_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
+		_ap_dump (self, LOGL_DEBUG, ap, "removed");
 	}
 
 	_notify (self, PROP_ACCESS_POINTS);
@@ -191,45 +183,43 @@ remove_all_aps (NMDeviceIwd *self)
 	nm_device_recheck_available_connections (NM_DEVICE (self));
 }
 
-static GVariant *
-vardict_from_network_type (const char *type)
+static NM80211ApSecurityFlags
+ap_security_flags_from_network_type (const char *type)
 {
-	GVariantBuilder builder;
-	const char *key_mgmt = "";
-	const char *pairwise = "ccmp";
+	NM80211ApSecurityFlags flags;
 
-	if (!strcmp (type, "psk"))
-		key_mgmt = "wpa-psk";
-	else if (!strcmp (type, "8021x"))
-		key_mgmt = "wpa-eap";
+	if (nm_streq (type, "psk"))
+		flags = NM_802_11_AP_SEC_KEY_MGMT_PSK;
+	else if (nm_streq (type, "8021x"))
+		flags = NM_802_11_AP_SEC_KEY_MGMT_802_1X;
 	else
-		return NULL;
+		return NM_802_11_AP_SEC_NONE;
 
-	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
-	g_variant_builder_add (&builder, "{sv}", "KeyMgmt",
-	                       g_variant_new_strv (&key_mgmt, 1));
-	g_variant_builder_add (&builder, "{sv}", "Pairwise",
-	                       g_variant_new_strv (&pairwise, 1));
-	g_variant_builder_add (&builder, "{sv}", "Group",
-	                       g_variant_new_string ("ccmp"));
-	return g_variant_new ("a{sv}", &builder);
+	flags |= NM_802_11_AP_SEC_PAIR_CCMP;
+	flags |= NM_802_11_AP_SEC_GROUP_CCMP;
+	return flags;
 }
 
 static void
 insert_ap_from_network (NMDeviceIwd *self,
                         GHashTable *aps,
                         const char *path,
+                        gint64 last_seen_msec,
                         int16_t signal,
                         uint32_t ap_id)
 {
 	gs_unref_object GDBusProxy *network_proxy = NULL;
-	gs_unref_variant GVariant *name_value = NULL, *type_value = NULL;
-	const char *name, *type;
-	GVariantBuilder builder;
-	gs_unref_variant GVariant *props = NULL;
-	GVariant *rsn;
+	gs_unref_variant GVariant *name_value = NULL;
+	gs_unref_variant GVariant *type_value = NULL;
+	nm_auto_ref_string NMRefString *bss_path = NULL;
+	const char *name;
+	const char *type;
+	NMSupplicantBssInfo bss_info;
 	uint8_t bssid[6];
 	NMWifiAP *ap;
+	gs_unref_bytes GBytes *ssid = NULL;
+
+	bss_path = nm_ref_string_new (path);
 
 	if (g_hash_table_lookup (aps, path)) {
 		_LOGD (LOGD_WIFI, "Duplicate network at %s", path);
@@ -253,6 +243,11 @@ insert_ap_from_network (NMDeviceIwd *self,
 	name = g_variant_get_string (name_value, NULL);
 	type = g_variant_get_string (type_value, NULL);
 
+	if (nm_streq (type, "wep")) {
+		/* WEP not supported */
+		return;
+	}
+
 	/* What we get from IWD are networks, or ESSs, that may contain
 	 * multiple APs, or BSSs, each.  We don't get information about any
 	 * specific BSSs within an ESS but we can safely present each ESS
@@ -268,32 +263,26 @@ insert_ap_from_network (NMDeviceIwd *self,
 	bssid[4] = ap_id >> 8;
 	bssid[5] = ap_id;
 
-	/* WEP not supported */
-	if (nm_streq (type, "wep"))
-		return;
-
-	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
-	g_variant_builder_add (&builder, "{sv}", "BSSID",
-	                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, bssid, 6, 1));
-	g_variant_builder_add (&builder, "{sv}", "Mode",
-	                       g_variant_new_string ("infrastructure"));
-
-	rsn = vardict_from_network_type (type);
-	if (rsn)
-		g_variant_builder_add (&builder, "{sv}", "RSN", rsn);
+	ssid = g_bytes_new (name, NM_MIN (32u, strlen (name)));
 
-	props = g_variant_new ("a{sv}", &builder);
+	bss_info = (NMSupplicantBssInfo) {
+		.bss_path       = bss_path,
+		.last_seen_msec = last_seen_msec,
+		.bssid_valid    = TRUE,
+		.mode           = NM_802_11_MODE_INFRA,
+		.rsn_flags      = ap_security_flags_from_network_type (type),
+		.ssid           = ssid,
+		.signal_percent = nm_wifi_utils_level_to_quality (signal / 100),
+		.frequency      = 2417,
+		.max_rate       = 65000,
+	};
+	memcpy (bss_info.bssid, bssid, sizeof (bssid));
 
-	ap = nm_wifi_ap_new_from_properties (path, props);
+	ap = nm_wifi_ap_new_from_properties (&bss_info);
 
-	nm_wifi_ap_set_ssid_arr (ap,
-	                         (const guint8 *) name,
-	                         NM_MIN (32, strlen (name)));
+	nm_assert (bss_path == nm_wifi_ap_get_supplicant_path (ap));
 
-	nm_wifi_ap_set_strength (ap, nm_wifi_utils_level_to_quality (signal / 100));
-	nm_wifi_ap_set_freq (ap, 2417);
-	nm_wifi_ap_set_max_bitrate (ap, 65000);
-	g_hash_table_insert (aps, (gpointer) nm_wifi_ap_get_supplicant_path (ap), ap);
+	g_hash_table_insert (aps, bss_path, ap);
 }
 
 static void
@@ -313,6 +302,7 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	gboolean compat;
 	const char *return_sig;
 	static uint32_t ap_id = 0;
+	gint64 last_seen_msec;
 
 	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
 	if (!variant) {
@@ -336,16 +326,17 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		return;
 	}
 
-	new_aps = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
+	new_aps = g_hash_table_new_full (nm_direct_hash, NULL, NULL, g_object_unref);
 
 	g_variant_get (variant, return_sig, &networks);
 
+	last_seen_msec = nm_utils_get_monotonic_timestamp_msec ();
 	if (compat) {
 		while (g_variant_iter_next (networks, "(&o&sn&s)", &path, &name, &signal, &type))
-			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, last_seen_msec, signal, ap_id++);
 	} else {
 		while (g_variant_iter_next (networks, "(&on)", &path, &signal))
-			insert_ap_from_network (self, new_aps, path, signal, ap_id++);
+			insert_ap_from_network (self, new_aps, path, last_seen_msec, signal, ap_id++);
 	}
 
 	g_variant_iter_free (networks);
@@ -356,7 +347,7 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		                              nm_wifi_ap_get_supplicant_path (ap));
 		if (new_ap) {
 			if (nm_wifi_ap_set_strength (ap, nm_wifi_ap_get_strength (new_ap))) {
-				_ap_dump (self, LOGL_TRACE, ap, "updated", 0);
+				_ap_dump (self, LOGL_TRACE, ap, "updated");
 				changed = TRUE;
 			}
 			g_hash_table_remove (new_aps,
@@ -566,20 +557,17 @@ is_connection_known_network (NMConnection *connection)
 static gboolean
 is_ap_known_network (NMWifiAP *ap)
 {
-	GDBusProxy *network_proxy;
+	gs_unref_object GDBusProxy *network_proxy = NULL;
 	gs_unref_variant GVariant *known_network = NULL;
 
 	network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
-	                                                   nm_wifi_ap_get_supplicant_path (ap),
+	                                                   nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)),
 	                                                   NM_IWD_NETWORK_INTERFACE);
 	if (!network_proxy)
 		return FALSE;
 
 	known_network = g_dbus_proxy_get_cached_property (network_proxy, "KnownNetwork");
-	g_object_unref (network_proxy);
-
-	return    known_network
-	       && g_variant_is_of_type (known_network, G_VARIANT_TYPE_OBJECT_PATH);
+	return nm_g_variant_is_of_type (known_network, G_VARIANT_TYPE_OBJECT_PATH);
 }
 
 static gboolean
@@ -975,15 +963,6 @@ _nm_device_iwd_get_aps (NMDeviceIwd *self)
 	return &NM_DEVICE_IWD_GET_PRIVATE (self)->aps_lst_head;
 }
 
-static gboolean
-check_scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
-{
-	gboolean prohibited = FALSE;
-
-	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
-	return prohibited;
-}
-
 static void
 scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
@@ -993,12 +972,12 @@ scan_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	gs_free_error GError *error = NULL;
 
 	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
-	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
+	if (!variant && nm_utils_error_is_cancelled (error))
 		return;
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	priv->scan_requested = FALSE;
-	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+	priv->last_scan = nm_utils_get_monotonic_timestamp_msec ();
 	_notify (self, PROP_LAST_SCAN);
 
 	/* On success, priv->scanning becomes true right before or right
@@ -1093,7 +1072,7 @@ _nm_device_iwd_request_scan (NMDeviceIwd *self,
 }
 
 static gboolean
-scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
+check_scanning_prohibited (NMDeviceIwd *self, gboolean periodic)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
@@ -1148,7 +1127,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 
 	*replied = FALSE;
 
-	if (!strcmp (method_name, "RequestPassphrase")) {
+	if (nm_streq (method_name, "RequestPassphrase")) {
 		const char *psk;
 
 		if (!s_wireless_sec)
@@ -1168,7 +1147,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 		*setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
 		*setting_key = NM_SETTING_WIRELESS_SECURITY_PSK;
 		return TRUE;
-	} else if (!strcmp (method_name, "RequestPrivateKeyPassphrase")) {
+	} else if (nm_streq (method_name, "RequestPrivateKeyPassphrase")) {
 		const char *password;
 
 		if (!s_8021x)
@@ -1188,7 +1167,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 		*setting_name = NM_SETTING_802_1X_SETTING_NAME;
 		*setting_key = NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD;
 		return TRUE;
-	} else if (!strcmp (method_name, "RequestUserNameAndPassword")) {
+	} else if (nm_streq (method_name, "RequestUserNameAndPassword")) {
 		const char *identity, *password;
 
 		if (!s_8021x)
@@ -1212,7 +1191,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 		else
 			*setting_key = NM_SETTING_802_1X_PASSWORD;
 		return TRUE;
-	} else if (!strcmp (method_name, "RequestUserPassword")) {
+	} else if (nm_streq (method_name, "RequestUserPassword")) {
 		const char *password;
 
 		if (!s_8021x)
@@ -1270,7 +1249,7 @@ wifi_secrets_cb (NMActRequest *req,
 
 	priv->wifi_secrets_id = NULL;
 
-	if (nm_utils_error_is_cancelled (error, FALSE)) {
+	if (nm_utils_error_is_cancelled (error)) {
 		g_dbus_method_invocation_return_error_literal (invocation, NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                                               "NM secrets request cancelled");
@@ -1366,7 +1345,7 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		       "Activation: (wifi) Network.Connect failed: %s",
 		       error->message);
 
-		if (nm_utils_error_is_cancelled (error, FALSE))
+		if (nm_utils_error_is_cancelled (error))
 			return;
 
 		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
@@ -1438,7 +1417,7 @@ act_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	gs_free_error GError *error = NULL;
 
 	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
-	if (!variant && nm_utils_error_is_cancelled (error, FALSE))
+	if (!variant && nm_utils_error_is_cancelled (error))
 		return;
 
 	/* Change state to FAILED unless already done by state_changed
@@ -1467,7 +1446,7 @@ act_start_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		       "Activation: (wifi) Network.Connect failed: %s",
 		       error->message);
 
-		if (nm_utils_error_is_cancelled (error, FALSE))
+		if (nm_utils_error_is_cancelled (error))
 			return;
 
 		if (!NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG))
@@ -1587,7 +1566,7 @@ act_set_mode_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		       "Activation: (wifi) Setting Device.Mode failed: %s",
 		       error->message);
 
-		if (nm_utils_error_is_cancelled (error, FALSE))
+		if (nm_utils_error_is_cancelled (error))
 			return;
 
 		if (   !NM_IN_SET (nm_device_get_state (device), NM_DEVICE_STATE_CONFIG)
@@ -1647,7 +1626,7 @@ act_psk_cb (NMActRequest *req,
 	NMDeviceIwdPrivate *priv;
 	NMDevice *device;
 
-	if (nm_utils_error_is_cancelled (error, FALSE))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	priv = NM_DEVICE_IWD_GET_PRIVATE (self);
@@ -1772,29 +1751,25 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMActRequest *req;
 	NMConnection *connection;
 	NMSettingWireless *s_wireless;
 	const char *mode;
 
 	req = nm_device_get_act_request (device);
-	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
-
 	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
-
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
 	mode = nm_setting_wireless_get_mode (s_wireless);
+
 	if (NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
-		GDBusProxy *network_proxy;
+		gs_unref_object GDBusProxy *network_proxy = NULL;
 		NMWifiAP *ap = priv->current_ap;
 
 		if (!ap) {
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-			goto out;
+			goto out_fail;
 		}
 
 		/* 802.1x networks that are not IWD Known Networks will definitely
@@ -1808,18 +1783,18 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 			       nm_connection_get_id (connection));
 
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-			goto out;
+			goto out_fail;
 		}
 
 		network_proxy = nm_iwd_manager_get_dbus_interface (nm_iwd_manager_get (),
-		                                                   nm_wifi_ap_get_supplicant_path (ap),
+		                                                   nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)),
 		                                                   NM_IWD_NETWORK_INTERFACE);
 		if (!network_proxy) {
 			_LOGE (LOGD_DEVICE | LOGD_WIFI,
 			       "Activation: (wifi) could not get Network interface proxy for %s",
-			       nm_wifi_ap_get_supplicant_path (ap));
+			       nm_ref_string_get_str (nm_wifi_ap_get_supplicant_path (ap)));
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-			goto out;
+			goto out_fail;
 		}
 
 		if (!priv->cancellable)
@@ -1832,12 +1807,15 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		                   NULL, G_DBUS_CALL_FLAGS_NONE, G_MAXINT,
 		                   priv->cancellable, network_connect_cb, self);
 
-		g_object_unref (network_proxy);
-	} else if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) {
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	if (NM_IN_STRSET (mode, NM_SETTING_WIRELESS_MODE_AP, NM_SETTING_WIRELESS_MODE_ADHOC)) {
 		NMSettingWirelessSecurity *s_wireless_sec;
 
 		s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
-		if (s_wireless_sec && !nm_setting_wireless_security_get_psk (s_wireless_sec)) {
+		if (   s_wireless_sec
+		    && !nm_setting_wireless_security_get_psk (s_wireless_sec)) {
 			/* PSK is missing from the settings, have to request it */
 
 			wifi_secrets_cancel (self);
@@ -1852,16 +1830,18 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 			nm_device_state_changed (device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 		} else
 			act_set_mode (self);
-	}
 
-	/* We'll get stage3 started when the supplicant connects */
-	ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
 
-out:
-	if (ret == NM_ACT_STAGE_RETURN_FAILURE)
-		cleanup_association_attempt (self, FALSE);
+	_LOGW (LOGD_DEVICE | LOGD_WIFI,
+	       "Activation: (wifi) iwd cannot handle mode %s",
+	       mode);
+	NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
 
-	return ret;
+out_fail:
+	cleanup_association_attempt (self, FALSE);
+	return NM_ACT_STAGE_RETURN_FAILURE;
 }
 
 static guint32
@@ -1987,7 +1967,7 @@ device_state_changed (NMDevice *device,
 static gboolean
 get_enabled (NMDevice *device)
 {
-	return NM_DEVICE_IWD_GET_PRIVATE ((NMDeviceIwd *) device)->enabled;
+	return NM_DEVICE_IWD_GET_PRIVATE (device)->enabled;
 }
 
 static void
@@ -2099,7 +2079,7 @@ get_property (GObject *object, guint prop_id,
 	case PROP_LAST_SCAN:
 		g_value_set_int64 (value,
 		                   priv->last_scan > 0
-		                       ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan, NM_UTILS_NS_PER_MSEC)
+		                       ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan, NM_UTILS_NSEC_PER_MSEC)
 		                       : (gint64) -1);
 		break;
 	default:
@@ -2582,8 +2562,6 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 
 	device_class->state_changed = device_state_changed;
 
-	klass->scanning_prohibited = scanning_prohibited;
-
 	obj_properties[PROP_MODE] =
 	    g_param_spec_uint (NM_DEVICE_IWD_MODE, "", "",
 	                       NM_802_11_MODE_UNKNOWN,
@@ -2628,12 +2606,4 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 	                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[SCANNING_PROHIBITED] =
-	    g_signal_new (NM_DEVICE_IWD_SCANNING_PROHIBITED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  G_STRUCT_OFFSET (NMDeviceIwdClass, scanning_prohibited),
-	                  NULL, NULL, NULL,
-	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
 }
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
index 586e02f4..1f15d3f4 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -25,8 +25,6 @@
 #define NM_DEVICE_IWD_SCANNING            NM_DEVICE_WIFI_SCANNING
 #define NM_DEVICE_IWD_LAST_SCAN           NM_DEVICE_WIFI_LAST_SCAN
 
-#define NM_DEVICE_IWD_SCANNING_PROHIBITED  NM_DEVICE_WIFI_SCANNING_PROHIBITED
-
 typedef struct _NMDeviceIwd NMDeviceIwd;
 typedef struct _NMDeviceIwdClass NMDeviceIwdClass;
 
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 516dc78b..770b53f6 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -57,7 +57,7 @@ struct _NMDeviceOlpcMeshClass {
 
 G_DEFINE_TYPE (NMDeviceOlpcMesh, nm_device_olpc_mesh, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_OLPC_MESH_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceOlpcMesh, NM_IS_DEVICE_OLPC_MESH)
+#define NM_DEVICE_OLPC_MESH_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceOlpcMesh, NM_IS_DEVICE_OLPC_MESH, NMDevice)
 
 /*****************************************************************************/
 
@@ -130,7 +130,6 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (device);
 	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
-	gboolean scanning;
 
 	/* disconnect companion device, if it is connected */
 	if (nm_device_get_act_request (NM_DEVICE (priv->companion))) {
@@ -145,8 +144,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	}
 
 	/* wait with continuing configuration until the companion device is done scanning */
-	g_object_get (priv->companion, NM_DEVICE_WIFI_SCANNING, &scanning, NULL);
-	if (scanning) {
+	if (nm_device_wifi_get_scanning (NM_DEVICE_WIFI (priv->companion))) {
 		priv->stage1_waiting = TRUE;
 		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
@@ -189,10 +187,10 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	gboolean success;
 
 	s_mesh = nm_device_get_applied_setting (device, NM_TYPE_SETTING_OLPC_MESH);
-
 	g_return_val_if_fail (s_mesh, NM_ACT_STAGE_RETURN_FAILURE);
 
 	ssid = nm_setting_olpc_mesh_get_ssid (s_mesh);
+
 	nm_device_take_down (NM_DEVICE (self), TRUE);
 	success = nm_platform_mesh_set_ssid (nm_device_get_platform (device),
 	                                     nm_device_get_ifindex (device),
@@ -237,6 +235,9 @@ companion_cleanup (NMDeviceOlpcMesh *self)
 	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
 
 	if (priv->companion) {
+		nm_device_wifi_scanning_prohibited_track (NM_DEVICE_WIFI (priv->companion),
+		                                          self,
+		                                          FALSE);
 		g_signal_handlers_disconnect_by_data (priv->companion, self);
 		g_clear_object (&priv->companion);
 	}
@@ -248,15 +249,16 @@ companion_notify_cb (NMDeviceWifi *companion, GParamSpec *pspec, gpointer user_d
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
 	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
-	gboolean scanning;
+
+	nm_assert (NM_IS_DEVICE_WIFI (companion));
+	nm_assert (priv->companion == (gpointer) companion);
 
 	if (!priv->stage1_waiting)
 		return;
 
-	g_object_get (companion, NM_DEVICE_WIFI_SCANNING, &scanning, NULL);
-	if (!scanning) {
+	if (!nm_device_wifi_get_scanning (NM_DEVICE_WIFI (companion))) {
 		priv->stage1_waiting = FALSE;
-		nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+		nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self), FALSE);
 	}
 }
 
@@ -290,16 +292,6 @@ companion_state_changed_cb (NMDeviceWifi *companion,
 }
 
 static gboolean
-companion_scan_prohibited_cb (NMDeviceWifi *companion, gboolean periodic, gpointer user_data)
-{
-	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
-	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
-
-	/* Don't allow the companion to scan while configuring the mesh interface */
-	return (state >= NM_DEVICE_STATE_PREPARE) && (state <= NM_DEVICE_STATE_IP_CONFIG);
-}
-
-static gboolean
 companion_autoconnect_allowed_cb (NMDeviceWifi *companion, gpointer user_data)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
@@ -324,7 +316,7 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	if (!nm_utils_hwaddr_matches (my_addr, -1, their_addr, -1))
 		return FALSE;
 
-	g_assert (priv->companion == NULL);
+	nm_assert (priv->companion == NULL);
 	priv->companion = g_object_ref (other);
 
 	_LOGI (LOGD_OLPC, "found companion Wi-Fi device %s",
@@ -336,9 +328,6 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	g_signal_connect (G_OBJECT (other), "notify::" NM_DEVICE_WIFI_SCANNING,
 	                  G_CALLBACK (companion_notify_cb), self);
 
-	g_signal_connect (G_OBJECT (other), NM_DEVICE_WIFI_SCANNING_PROHIBITED,
-	                  G_CALLBACK (companion_scan_prohibited_cb), self);
-
 	g_signal_connect (G_OBJECT (other), NM_DEVICE_AUTOCONNECT_ALLOWED,
 	                  G_CALLBACK (companion_autoconnect_allowed_cb), self);
 
@@ -400,8 +389,24 @@ state_changed (NMDevice *device,
                NMDeviceState old_state,
                NMDeviceStateReason reason)
 {
+	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (device);
+	NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE (self);
+
 	if (new_state == NM_DEVICE_STATE_UNAVAILABLE)
-		find_companion (NM_DEVICE_OLPC_MESH (device));
+		find_companion (self);
+
+	if (priv->companion) {
+		gboolean temporarily_prohibited = FALSE;
+
+		if (   new_state >= NM_DEVICE_STATE_PREPARE
+		    && new_state <= NM_DEVICE_STATE_IP_CONFIG) {
+			/* Don't allow the companion to scan while configuring the mesh interface */
+			temporarily_prohibited = TRUE;
+		}
+		nm_device_wifi_scanning_prohibited_track (NM_DEVICE_WIFI (priv->companion),
+		                                          self,
+		                                          temporarily_prohibited);
+	}
 }
 
 static guint32
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
index 34ff70fa..497bc436 100644
--- a/src/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -12,19 +12,20 @@
 #include "supplicant/nm-supplicant-manager.h"
 #include "supplicant/nm-supplicant-interface.h"
 
-#include "nm-manager.h"
-#include "nm-utils.h"
-#include "nm-wifi-p2p-peer.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
-#include "settings/nm-settings.h"
-#include "nm-setting-wifi-p2p.h"
 #include "nm-act-request.h"
+#include "nm-core-internal.h"
+#include "nm-glib-aux/nm-ref-string.h"
 #include "nm-ip4-config.h"
-#include "platform/nm-platform.h"
 #include "nm-manager.h"
-#include "nm-core-internal.h"
+#include "nm-manager.h"
+#include "nm-setting-wifi-p2p.h"
+#include "nm-utils.h"
+#include "nm-wifi-p2p-peer.h"
+#include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
+#include "settings/nm-settings.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWifiP2P);
@@ -104,7 +105,7 @@ peer_list_dump (gpointer user_data)
 
 	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
 		NMWifiP2PPeer *peer;
-		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
+		gint32 now_s = nm_utils_get_monotonic_timestamp_sec ();
 
 		_LOGD (LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s);
 		c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst)
@@ -227,11 +228,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 		return FALSE;
 
 	supplicant_state = nm_supplicant_interface_get_state (priv->mgmt_iface);
-	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_READY
-	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
-		return FALSE;
-
-	return TRUE;
+	return nm_supplicant_interface_state_is_operational (supplicant_state);
 }
 
 static gboolean
@@ -432,7 +429,6 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	connection = nm_device_get_applied_connection (device);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
-
 	nm_assert (NM_IS_SETTING_WIFI_P2P (nm_connection_get_setting (connection, NM_TYPE_SETTING_WIFI_P2P)));
 
 	/* The prepare stage ensures that the peer has been found */
@@ -452,7 +448,8 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	/* TODO: Fix "pbc" being hardcoded here! */
 	nm_supplicant_interface_p2p_connect (priv->mgmt_iface,
 	                                     nm_wifi_p2p_peer_get_supplicant_path (peer),
-	                                     "pbc", NULL);
+	                                     "pbc",
+	                                     NULL);
 
 	/* Set up a timeout on the connect attempt */
 	if (priv->sup_timeout_id == 0) {
@@ -526,7 +523,7 @@ peer_add_remove (NMDeviceWifiP2P *self,
 			if (peer) {
 				/* A peer for the connection was found, cancel the timeout and go to configure state. */
 				nm_clear_g_source (&priv->find_peer_timeout_id);
-				nm_device_activate_schedule_stage1_device_prepare (device);
+				nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 			}
 		}
 
@@ -649,52 +646,49 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	NMSupplicantInterfaceState new_state = new_state_i;
 	NMSupplicantInterfaceState old_state = old_state_i;
 
-	if (new_state == old_state)
-		return;
-
 	_LOGI (LOGD_DEVICE | LOGD_WIFI,
 	       "supplicant management interface state: %s -> %s",
 	       nm_supplicant_interface_state_to_string (old_state),
 	       nm_supplicant_interface_state_to_string (new_state));
 
-	switch (new_state) {
-	case NM_SUPPLICANT_INTERFACE_STATE_READY:
-		_LOGD (LOGD_WIFI, "supplicant ready");
+	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
+		supplicant_interfaces_release (self, TRUE);
 		nm_device_queue_recheck_available (device,
 		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
 		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
+	}
 
-		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
-			_set_is_waiting_for_supplicant (self, FALSE);
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
-		supplicant_interfaces_release (self, TRUE);
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		_LOGD (LOGD_WIFI, "supplicant ready");
 		nm_device_queue_recheck_available (device,
 		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
 		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		break;
-	default:
-		break;
+		_set_is_waiting_for_supplicant (self, FALSE);
 	}
 }
 
 static void
-supplicant_iface_peer_updated_cb (NMSupplicantInterface *iface,
-                                  const char *object_path,
-                                  GVariant *properties,
+supplicant_iface_peer_changed_cb (NMSupplicantInterface *iface,
+                                  NMSupplicantPeerInfo *peer_info,
+                                  gboolean is_present,
                                   NMDeviceWifiP2P *self)
 {
-	NMDeviceWifiP2PPrivate *priv;
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
 	NMWifiP2PPeer *found_peer;
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (object_path != NULL);
+	found_peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, peer_info->peer_path->str);
 
-	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+	if (!is_present) {
+		if (!found_peer)
+			return;
+
+		peer_add_remove (self, FALSE, found_peer, TRUE);
+		goto out;
+	}
 
-	found_peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
 	if (found_peer) {
-		if (!nm_wifi_p2p_peer_update_from_properties (found_peer, object_path, properties))
+		if (!nm_wifi_p2p_peer_update_from_properties (found_peer, peer_info))
 			return;
 
 		update_disconnect_on_connection_peer_missing (self);
@@ -702,35 +696,11 @@ supplicant_iface_peer_updated_cb (NMSupplicantInterface *iface,
 	} else {
 		gs_unref_object NMWifiP2PPeer *peer = NULL;
 
-		peer = nm_wifi_p2p_peer_new_from_properties (object_path, properties);
-		if (!peer) {
-			_LOGD (LOGD_WIFI, "invalid P2P peer properties received for %s", object_path);
-			return;
-		}
-
+		peer = nm_wifi_p2p_peer_new_from_properties (peer_info);
 		peer_add_remove (self, TRUE, peer, TRUE);
 	}
 
-	schedule_peer_list_dump (self);
-}
-
-static void
-supplicant_iface_peer_removed_cb (NMSupplicantInterface *iface,
-                                  const char *object_path,
-                                  NMDeviceWifiP2P *self)
-{
-	NMDeviceWifiP2PPrivate *priv;
-	NMWifiP2PPeer *peer;
-
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (object_path != NULL);
-
-	priv  = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
-	peer = nm_wifi_p2p_peers_find_by_supplicant_path (&priv->peers_lst_head, object_path);
-	if (!peer)
-		return;
-
-	peer_add_remove (self, FALSE, peer, TRUE);
+out:
 	schedule_peer_list_dump (self);
 }
 
@@ -742,7 +712,7 @@ check_group_iface_ready (NMDeviceWifiP2P *self)
 	if (!priv->group_iface)
 		return;
 
-	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+	if (!nm_supplicant_interface_state_is_operational (nm_supplicant_interface_get_state (priv->group_iface)))
 		return;
 
 	if (!nm_supplicant_interface_get_p2p_group_joined (priv->group_iface))
@@ -755,6 +725,24 @@ check_group_iface_ready (NMDeviceWifiP2P *self)
 }
 
 static void
+supplicant_group_iface_is_ready (NMDeviceWifiP2P *self)
+{
+	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
+
+	_LOGD (LOGD_WIFI, "P2P Group supplicant ready");
+
+	if (!nm_device_set_ip_iface (NM_DEVICE (self), nm_supplicant_interface_get_ifname (priv->group_iface))) {
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		return;
+	}
+
+	_set_is_waiting_for_supplicant (self, FALSE);
+	check_group_iface_ready (self);
+}
+
+static void
 supplicant_group_iface_state_cb (NMSupplicantInterface *iface,
                                  int new_state_i,
                                  int old_state_i,
@@ -762,44 +750,26 @@ supplicant_group_iface_state_cb (NMSupplicantInterface *iface,
                                  gpointer user_data)
 {
 	NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P (user_data);
-	NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
-	NMDevice *device = NM_DEVICE (self);
 	NMSupplicantInterfaceState new_state = new_state_i;
 	NMSupplicantInterfaceState old_state = old_state_i;
 
-	if (new_state == old_state)
-		return;
-
 	_LOGI (LOGD_DEVICE | LOGD_WIFI,
 	       "P2P Group supplicant interface state: %s -> %s",
 	       nm_supplicant_interface_state_to_string (old_state),
 	       nm_supplicant_interface_state_to_string (new_state));
 
-	switch (new_state) {
-	case NM_SUPPLICANT_INTERFACE_STATE_READY:
-		_LOGD (LOGD_WIFI, "P2P Group supplicant ready");
-
-		if (!nm_device_set_ip_iface (device, nm_supplicant_interface_get_ifname (priv->group_iface))) {
-			nm_device_state_changed (device,
-			                         NM_DEVICE_STATE_FAILED,
-			                         NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-			break;
-		}
-
-		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
-			_set_is_waiting_for_supplicant (self, FALSE);
-
-		check_group_iface_ready (self);
-		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
+	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
 		supplicant_group_interface_release (self);
 
-		nm_device_state_changed (device,
+		nm_device_state_changed (NM_DEVICE (self),
 		                         NM_DEVICE_STATE_DISCONNECTED,
 		                         NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
-		break;
-	default:
-		break;
+		return;
+	}
+
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		supplicant_group_iface_is_ready (self);
+		return;
 	}
 }
 
@@ -833,8 +803,9 @@ supplicant_iface_group_started_cb (NMSupplicantInterface *iface,
                                    NMDeviceWifiP2P *self)
 {
 	NMDeviceWifiP2PPrivate *priv;
+	NMSupplicantInterfaceState state;
 
-	g_return_if_fail (self != NULL);
+	g_return_if_fail (self);
 
 	if (!nm_device_is_activating (NM_DEVICE (self))) {
 		_LOGW (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant notified a group start but we are not trying to connect! Ignoring the event.");
@@ -844,6 +815,7 @@ supplicant_iface_group_started_cb (NMSupplicantInterface *iface,
 	priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE (self);
 
 	supplicant_group_interface_release (self);
+
 	priv->group_iface = g_object_ref (group_iface);
 
 	/* We need to wait for the interface to be ready and the group
@@ -862,10 +834,13 @@ supplicant_iface_group_started_cb (NMSupplicantInterface *iface,
 	                  G_CALLBACK (supplicant_group_iface_group_finished_cb),
 	                  self);
 
-	if (nm_supplicant_interface_get_state (priv->group_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+	state = nm_supplicant_interface_get_state (priv->group_iface);
+	if (state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
 		_set_is_waiting_for_supplicant (self, TRUE);
+		return;
+	}
 
-	check_group_iface_ready (self);
+	supplicant_group_iface_is_ready (self);
 }
 
 static void
@@ -935,9 +910,8 @@ device_state_changed (NMDevice *device,
 		break;
 	case NM_DEVICE_STATE_UNAVAILABLE:
 		if (   !priv->mgmt_iface
-		    || nm_supplicant_interface_get_state (priv->mgmt_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
+		    || !nm_supplicant_interface_state_is_operational (nm_supplicant_interface_get_state (priv->mgmt_iface)))
 			_set_is_waiting_for_supplicant (self, TRUE);
-
 		break;
 	case NM_DEVICE_STATE_NEED_AUTH:
 		/* Disconnect? */
@@ -1084,20 +1058,20 @@ nm_device_wifi_p2p_set_mgmt_iface (NMDeviceWifiP2P *self,
 		goto done;
 
 	_LOGD (LOGD_DEVICE | LOGD_WIFI, "P2P: WPA supplicant management interface changed to %s.",
-	       nm_supplicant_interface_get_object_path (iface));
+	       nm_ref_string_get_str (nm_supplicant_interface_get_object_path (iface)));
 
 	priv->mgmt_iface = g_object_ref (iface);
 
-	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_STATE,
+	g_signal_connect (priv->mgmt_iface,
+	                  NM_SUPPLICANT_INTERFACE_STATE,
 	                  G_CALLBACK (supplicant_iface_state_cb),
 	                  self);
-	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
-	                  G_CALLBACK (supplicant_iface_peer_updated_cb),
-	                  self);
-	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
-	                  G_CALLBACK (supplicant_iface_peer_removed_cb),
+	g_signal_connect (priv->mgmt_iface,
+	                  NM_SUPPLICANT_INTERFACE_PEER_CHANGED,
+	                  G_CALLBACK (supplicant_iface_peer_changed_cb),
 	                  self);
-	g_signal_connect (priv->mgmt_iface, NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
+	g_signal_connect (priv->mgmt_iface,
+	                  NM_SUPPLICANT_INTERFACE_GROUP_STARTED,
 	                  G_CALLBACK (supplicant_iface_group_started_cb),
 	                  self);
 done:
@@ -1106,8 +1080,7 @@ done:
 	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
 	_set_is_waiting_for_supplicant (self,
 	                                   !priv->mgmt_iface
-	                                || (  nm_supplicant_interface_get_state (priv->mgmt_iface)
-	                                    < NM_SUPPLICANT_INTERFACE_STATE_READY));
+	                                || !nm_supplicant_interface_state_is_operational (nm_supplicant_interface_get_state (priv->mgmt_iface)));
 }
 
 void
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 98f9df25..266a8425 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -11,6 +11,8 @@
 #include <netinet/in.h>
 #include <unistd.h>
 
+#include "nm-glib-aux/nm-ref-string.h"
+#include "nm-glib-aux/nm-c-list.h"
 #include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
 #include "nm-libnm-core-intern/nm-common-macros.h"
@@ -42,10 +44,9 @@
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceWifi);
 
-/* All of these are in seconds */
-#define SCAN_INTERVAL_MIN 3
-#define SCAN_INTERVAL_STEP 20
-#define SCAN_INTERVAL_MAX 120
+#define SCAN_INTERVAL_SEC_MIN   3
+#define SCAN_INTERVAL_SEC_STEP 20
+#define SCAN_INTERVAL_SEC_MAX 120
 
 #define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5
 
@@ -62,7 +63,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 );
 
 enum {
-	SCANNING_PROHIBITED,
 	P2P_DEVICE_CREATED,
 
 	LAST_SIGNAL
@@ -71,9 +71,10 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
-	gint8             invalid_strength_counter;
-
 	CList             aps_lst_head;
+	GHashTable       *aps_idx_by_supplicant_path;
+
+	CList             scanning_prohibited_lst_head;
 
 	NMWifiAP *        current_ap;
 	guint32           rate;
@@ -83,13 +84,15 @@ typedef struct {
 	bool              is_scanning:1;
 	bool              hidden_probe_scan_warn:1;
 
-	gint64            last_scan; /* milliseconds */
+	gint64            last_scan_msec;
+
 	gint32            scheduled_scan_time; /* seconds */
-	guint8            scan_interval; /* seconds */
+	guint8            scan_interval_sec;
 	guint             pending_scan_id;
 	guint             ap_dump_id;
 
 	NMSupplicantManager   *sup_mgr;
+	NMSupplMgrCreateIfaceHandle *sup_create_handle;
 	NMSupplicantInterface *sup_iface;
 	guint                  sup_timeout_id; /* supplicant association timeout */
 
@@ -97,7 +100,7 @@ typedef struct {
 
 	NMActRequestGetSecretsCallId *wifi_secrets_id;
 
-	guint             periodic_source_id;
+	guint             periodic_update_id;
 	guint             link_timeout_id;
 	guint32           failed_iface_count;
 	guint             reacquire_iface_id;
@@ -122,53 +125,46 @@ struct _NMDeviceWifi
 struct _NMDeviceWifiClass
 {
 	NMDeviceClass parent;
-
-	/* Signals */
-	gboolean (*scanning_prohibited) (NMDeviceWifi *device, gboolean periodic);
 };
 
 /*****************************************************************************/
 
 G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
 
-#define NM_DEVICE_WIFI_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceWifi, NM_IS_DEVICE_WIFI)
+#define NM_DEVICE_WIFI_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceWifi, NM_IS_DEVICE_WIFI, NMDevice)
 
 /*****************************************************************************/
 
 static gboolean check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic);
 
+static void supplicant_iface_state_down (NMDeviceWifi *self);
+
 static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
 
 static void cleanup_association_attempt (NMDeviceWifi * self,
                                          gboolean disconnect);
 
+static void supplicant_iface_state (NMDeviceWifi *self,
+                                    NMSupplicantInterfaceState new_state,
+                                    NMSupplicantInterfaceState old_state,
+                                    int disconnect_reason,
+                                    gboolean is_real_signal);
+
 static void supplicant_iface_state_cb (NMSupplicantInterface *iface,
                                        int new_state_i,
                                        int old_state_i,
                                        int disconnect_reason,
                                        gpointer user_data);
 
-static void supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
-                                             const char *object_path,
-                                             GVariant *properties,
-                                             NMDeviceWifi *self);
-
-static void supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
-                                             const char *object_path,
+static void supplicant_iface_bss_changed_cb (NMSupplicantInterface *iface,
+                                             NMSupplicantBssInfo *bss_info,
+                                             gboolean is_present,
                                              NMDeviceWifi *self);
 
-static void supplicant_iface_scan_done_cb (NMSupplicantInterface * iface,
-                                           gboolean success,
-                                           NMDeviceWifi * self);
-
 static void supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
                                                  GVariant *credentials,
                                                  NMDeviceWifi *self);
 
-static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface,
-                                                 GParamSpec * pspec,
-                                                 NMDeviceWifi * self);
-
 static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
@@ -177,6 +173,10 @@ static void supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
                                                    GParamSpec *pspec,
                                                    NMDeviceWifi *self);
 
+static void _requested_scan_set (NMDeviceWifi *self, gboolean value);
+
+static void periodic_update (NMDeviceWifi *self);
+
 static void request_wireless_scan (NMDeviceWifi *self,
                                    gboolean periodic,
                                    gboolean force_if_scanning,
@@ -193,19 +193,66 @@ static void recheck_p2p_availability (NMDeviceWifi *self);
 
 /*****************************************************************************/
 
+void
+nm_device_wifi_scanning_prohibited_track (NMDeviceWifi *self,
+                                          gpointer tag,
+                                          gboolean temporarily_prohibited)
+{
+	NMDeviceWifiPrivate *priv;
+	NMCListElem *elem;
+
+	g_return_if_fail (NM_IS_DEVICE_WIFI (self));
+	nm_assert (tag);
+
+	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	/* We track these with a simple CList. This would be not efficient, if
+	 * there would be many users that need to be tracked at the same time (there
+	 * aren't). In fact, most of the time there is no NMDeviceOlpcMesh and
+	 * nobody tracks itself here. Optimize for that and simplicity. */
+
+	elem = nm_c_list_elem_find_first (&priv->scanning_prohibited_lst_head,
+	                                  iter,
+	                                  iter == tag);
+
+	if (!temporarily_prohibited) {
+		if (!elem)
+			return;
+
+		nm_c_list_elem_free (elem);
+		return;
+	}
+
+	if (elem)
+		return;
+
+	c_list_link_tail (&priv->scanning_prohibited_lst_head,
+	                  &nm_c_list_elem_new_stale (tag)->lst);
+}
+
+/*****************************************************************************/
+
 static void
 _ap_dump (NMDeviceWifi *self,
           NMLogLevel log_level,
           const NMWifiAP *ap,
           const char *prefix,
-          gint32 now_s)
+          gint64 now_msec)
 {
 	char buf[1024];
 
 	buf[0] = '\0';
 	_NMLOG (log_level, LOGD_WIFI_SCAN, "wifi-ap: %-7s %s",
 	        prefix,
-	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_s));
+	        nm_wifi_ap_to_string (ap, buf, sizeof (buf), now_msec));
+}
+
+gboolean
+nm_device_wifi_get_scanning (NMDeviceWifi *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE);
+
+	return NM_DEVICE_WIFI_GET_PRIVATE (self)->is_scanning;
 }
 
 static void
@@ -213,6 +260,7 @@ _notify_scanning (NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	gboolean scanning;
+	gboolean last_scan_changed = FALSE;
 
 	scanning =    priv->sup_iface
 	           && nm_supplicant_interface_get_scanning (priv->sup_iface);
@@ -220,9 +268,42 @@ _notify_scanning (NMDeviceWifi *self)
 	if (scanning == priv->is_scanning)
 		return;
 
-	_LOGD (LOGD_WIFI, "wifi-scan: scanning-state: %s", scanning ? "scanning" : "idle");
 	priv->is_scanning = scanning;
-	_notify (self, PROP_SCANNING);
+
+	if (   !scanning
+	    || priv->last_scan_msec == 0) {
+		last_scan_changed = TRUE;
+		priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec ();
+	}
+
+	_LOGD (LOGD_WIFI,
+	       "wifi-scan: scanning-state: %s%s",
+	       scanning ? "scanning" : "idle",
+	       last_scan_changed ? " (notify last-scan)" : "");
+
+	schedule_scan (self, TRUE);
+
+	nm_gobject_notify_together (self,
+	                            PROP_SCANNING,
+	                              last_scan_changed
+	                            ? PROP_LAST_SCAN
+	                            : PROP_0);
+
+	if (!priv->is_scanning) {
+		_requested_scan_set (self, FALSE);
+		if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED) {
+			/* Run a quick update of current AP when coming out of a scan */
+			periodic_update (self);
+		}
+	}
+}
+
+static void
+supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
+                                     GParamSpec *pspec,
+                                     NMDeviceWifi *self)
+{
+	_notify_scanning (self);
 }
 
 static gboolean
@@ -237,40 +318,40 @@ unmanaged_on_quit (NMDevice *self)
 	return TRUE;
 }
 
-static gboolean
-supplicant_interface_acquire (NMDeviceWifi *self)
+static void
+supplicant_interface_acquire_cb (NMSupplicantManager *supplicant_manager,
+                                 NMSupplMgrCreateIfaceHandle *handle,
+                                 NMSupplicantInterface *iface,
+                                 GError *error,
+                                 gpointer user_data)
 {
+	NMDeviceWifi *self = user_data;
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	g_return_val_if_fail (self != NULL, FALSE);
-	g_return_val_if_fail (!priv->sup_iface, TRUE);
+	if (nm_utils_error_is_cancelled (error))
+		return;
 
-	priv->sup_iface = nm_supplicant_manager_create_interface (priv->sup_mgr,
-	                                                          nm_device_get_iface (NM_DEVICE (self)),
-	                                                          NM_SUPPLICANT_DRIVER_WIRELESS);
-	if (!priv->sup_iface) {
-		_LOGE (LOGD_WIFI, "Couldn't initialize supplicant interface");
-		return FALSE;
+	nm_assert (priv->sup_create_handle == handle);
+
+	priv->sup_create_handle = NULL;
+
+	if (error) {
+		_LOGE (LOGD_WIFI, "Couldn't initialize supplicant interface: %s",
+		       error->message);
+		supplicant_iface_state_down (self);
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+		return;
 	}
 
-	if (nm_supplicant_interface_get_state (priv->sup_iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
-		nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, FALSE);
+	priv->sup_iface = g_object_ref (iface);
 
 	g_signal_connect (priv->sup_iface,
 	                  NM_SUPPLICANT_INTERFACE_STATE,
 	                  G_CALLBACK (supplicant_iface_state_cb),
 	                  self);
 	g_signal_connect (priv->sup_iface,
-	                  NM_SUPPLICANT_INTERFACE_BSS_UPDATED,
-	                  G_CALLBACK (supplicant_iface_bss_updated_cb),
-	                  self);
-	g_signal_connect (priv->sup_iface,
-	                  NM_SUPPLICANT_INTERFACE_BSS_REMOVED,
-	                  G_CALLBACK (supplicant_iface_bss_removed_cb),
-	                  self);
-	g_signal_connect (priv->sup_iface,
-	                  NM_SUPPLICANT_INTERFACE_SCAN_DONE,
-	                  G_CALLBACK (supplicant_iface_scan_done_cb),
+	                  NM_SUPPLICANT_INTERFACE_BSS_CHANGED,
+	                  G_CALLBACK (supplicant_iface_bss_changed_cb),
 	                  self);
 	g_signal_connect (priv->sup_iface,
 	                  NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
@@ -291,7 +372,30 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 
 	_notify_scanning (self);
 
-	return TRUE;
+	if (nm_supplicant_interface_get_state (priv->sup_iface) != NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		/* fake an initial state change. */
+		supplicant_iface_state (user_data,
+		                        NM_SUPPLICANT_INTERFACE_STATE_STARTING,
+		                        nm_supplicant_interface_get_state (priv->sup_iface),
+		                        0,
+		                        FALSE);
+	}
+}
+
+static void
+supplicant_interface_acquire (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	nm_assert (!priv->sup_iface);
+	nm_assert (!priv->sup_create_handle);
+
+	priv->sup_create_handle = nm_supplicant_manager_create_interface (priv->sup_mgr,
+	                                                                  nm_device_get_ifindex (NM_DEVICE (self)),
+	                                                                  NM_SUPPLICANT_DRIVER_WIRELESS,
+	                                                                  supplicant_interface_acquire_cb,
+	                                                                  self);
+	nm_device_add_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
 }
 
 static void
@@ -310,27 +414,26 @@ _requested_scan_set (NMDeviceWifi *self, gboolean value)
 		nm_device_add_pending_action ((NMDevice *) self, NM_PENDING_ACTION_WIFI_SCAN, TRUE);
 	else {
 		nm_device_emit_recheck_auto_activate (NM_DEVICE (self));
-		nm_device_remove_pending_action ((NMDevice *) self, NM_PENDING_ACTION_WIFI_SCAN, TRUE);
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WIFI_SCAN, TRUE);
 	}
 }
 
 static void
 supplicant_interface_release (NMDeviceWifi *self)
 {
-	NMDeviceWifiPrivate *priv;
-
-	g_return_if_fail (self != NULL);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	if (nm_clear_pointer (&priv->sup_create_handle, nm_supplicant_manager_create_interface_cancel))
+		nm_device_remove_pending_action (NM_DEVICE (self), NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
 
 	_requested_scan_set (self, FALSE);
 
 	nm_clear_g_source (&priv->pending_scan_id);
 
 	/* Reset the scan interval to be pretty frequent when disconnected */
-	priv->scan_interval = SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP;
+	priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP;
 	_LOGD (LOGD_WIFI, "wifi-scan: reset interval to %u seconds",
-	       (unsigned) priv->scan_interval);
+	       (unsigned) priv->scan_interval_sec);
 
 	nm_clear_g_source (&priv->ap_dump_id);
 
@@ -410,44 +513,48 @@ set_current_ap (NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available
 static void
 periodic_update (NMDeviceWifi *self)
 {
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	int ifindex = nm_device_get_ifindex (NM_DEVICE (self));
+	NMDeviceWifiPrivate *priv;
+	int ifindex;
 	guint32 new_rate;
-	int percent;
-	NMDeviceState state;
-	NMSupplicantInterfaceState supplicant_state;
 
-	/* BSSID and signal strength have meaningful values only if the device
-	 * is activated and not scanning.
-	 */
-	state = nm_device_get_state (NM_DEVICE (self));
-	if (state != NM_DEVICE_STATE_ACTIVATED)
+	if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_ACTIVATED) {
+		/* BSSID and signal strength have meaningful values only if the device
+		 * is activated and not scanning.
+		 */
 		return;
+	}
 
-	/* Only update current AP if we're actually talking to something, otherwise
-	 * assume the old one (if any) is still valid until we're told otherwise or
-	 * the connection fails.
-	 */
-	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
-	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING
-	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED
-	    || nm_supplicant_interface_get_scanning (priv->sup_iface))
+	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	if (   !nm_supplicant_interface_state_is_associated (nm_supplicant_interface_get_state (priv->sup_iface))
+	    || nm_supplicant_interface_get_scanning (priv->sup_iface)) {
+		/* Only update current AP if we're actually talking to something, otherwise
+		 * assume the old one (if any) is still valid until we're told otherwise or
+		 * the connection fails.
+		 */
 		return;
+	}
 
-	/* In AP mode we currently have nothing to do. */
-	if (priv->mode == NM_802_11_MODE_AP)
+	if (priv->mode == NM_802_11_MODE_AP) {
+		/* In AP mode we currently have nothing to do. */
 		return;
+	}
+
+	ifindex = nm_device_get_ifindex (NM_DEVICE (self));
+	if (ifindex <= 0)
+		g_return_if_reached ();
 
 	if (priv->current_ap) {
-		/* Smooth out the strength to work around crappy drivers */
+		int percent;
+
 		percent = nm_platform_wifi_get_quality (nm_device_get_platform (NM_DEVICE (self)), ifindex);
-		if (percent >= 0 || ++priv->invalid_strength_counter > 3) {
+		if (   percent >= 0
+		    && percent <= 100) {
 			if (nm_wifi_ap_set_strength (priv->current_ap, (gint8) percent)) {
 #if NM_MORE_LOGGING
 				_ap_dump (self, LOGL_TRACE, priv->current_ap, "updated", 0);
 #endif
 			}
-			priv->invalid_strength_counter = 0;
 		}
 	}
 
@@ -461,7 +568,7 @@ periodic_update (NMDeviceWifi *self)
 static gboolean
 periodic_update_cb (gpointer user_data)
 {
-	periodic_update (NM_DEVICE_WIFI (user_data));
+	periodic_update (user_data);
 	return TRUE;
 }
 
@@ -477,12 +584,16 @@ ap_add_remove (NMDeviceWifi *self,
 		g_object_ref (ap);
 		ap->wifi_device = NM_DEVICE (self);
 		c_list_link_tail (&priv->aps_lst_head, &ap->aps_lst);
+		if (!g_hash_table_insert (priv->aps_idx_by_supplicant_path, nm_wifi_ap_get_supplicant_path (ap), ap))
+			nm_assert_not_reached ();
 		nm_dbus_object_export (NM_DBUS_OBJECT (ap));
 		_ap_dump (self, LOGL_DEBUG, ap, "added", 0);
 		nm_device_wifi_emit_signal_access_point (NM_DEVICE (self), ap, TRUE);
 	} else {
 		ap->wifi_device = NULL;
 		c_list_unlink (&ap->aps_lst);
+		if (!g_hash_table_remove (priv->aps_idx_by_supplicant_path, nm_wifi_ap_get_supplicant_path (ap)))
+			nm_assert_not_reached ();
 		_ap_dump (self, LOGL_DEBUG, ap, "removed", 0);
 	}
 
@@ -593,7 +704,7 @@ deactivate (NMDevice *device)
 	int ifindex = nm_device_get_ifindex (device);
 	NM80211Mode old_mode = priv->mode;
 
-	nm_clear_g_source (&priv->periodic_source_id);
+	nm_clear_g_source (&priv->periodic_update_id);
 
 	cleanup_association_attempt (self, TRUE);
 
@@ -694,7 +805,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		}
 
 		if (priv->sup_iface) {
-			if (nm_supplicant_interface_get_ap_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_NO) {
+			if (nm_supplicant_interface_get_capability (priv->sup_iface, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_FALSE) {
 				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 				                            "wpa_supplicant does not support Access Point mode");
 				return FALSE;
@@ -708,7 +819,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 		}
 
 		if (priv->sup_iface) {
-			if (nm_supplicant_interface_get_mesh_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_NO) {
+			if (nm_supplicant_interface_get_capability (priv->sup_iface, NM_SUPPL_CAP_TYPE_MESH) == NM_TERNARY_FALSE) {
 				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
 				                            "wpa_supplicant does not support Mesh mode");
 				return FALSE;
@@ -936,7 +1047,7 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 		return FALSE;
 
 	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
-	if (   supplicant_state < NM_SUPPLICANT_INTERFACE_STATE_READY
+	if (   supplicant_state <= NM_SUPPLICANT_INTERFACE_STATE_STARTING
 	    || supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		return FALSE;
 
@@ -1047,7 +1158,7 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 		return;
 	}
 
-	now = nm_utils_get_monotonic_timestamp_s ();
+	now = nm_utils_get_monotonic_timestamp_sec ();
 
 	if (now >= priv->hw_addr_scan_expire) {
 		gs_free char *generate_mac_address_mask = NULL;
@@ -1074,16 +1185,17 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 static GPtrArray *
 ssids_options_to_ptrarray (GVariant *value, GError **error)
 {
-	GPtrArray *ssids = NULL;
-	const guint8 *bytes;
-	gsize len;
-	int num_ssids, i;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
+	gsize num_ssids;
+	gsize i;
+
+	nm_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("aay")));
 
 	num_ssids = g_variant_n_children (value);
 	if (num_ssids > 32) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
-		                     NM_DEVICE_ERROR_NOT_ALLOWED,
+		                     NM_DEVICE_ERROR_INVALID_ARGUMENT,
 		                     "too many SSIDs requested to scan");
 		return NULL;
 	}
@@ -1092,22 +1204,30 @@ ssids_options_to_ptrarray (GVariant *value, GError **error)
 		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_bytes_unref);
 		for (i = 0; i < num_ssids; i++) {
 			gs_unref_variant GVariant *v = NULL;
+			gsize len;
+			const guint8 *bytes;
 
 			v = g_variant_get_child_value (value, i);
 			bytes = g_variant_get_fixed_array (v, &len, sizeof (guint8));
 			if (len > 32) {
 				g_set_error (error,
 				             NM_DEVICE_ERROR,
-				             NM_DEVICE_ERROR_NOT_ALLOWED,
-				             "SSID at index %d more than 32 bytes", i);
-				g_ptr_array_unref (ssids);
+				             NM_DEVICE_ERROR_INVALID_ARGUMENT,
+				             "SSID at index %d more than 32 bytes", (int) i);
 				return NULL;
 			}
 
 			g_ptr_array_add (ssids, g_bytes_new (bytes, len));
 		}
 	}
-	return ssids;
+
+	return g_steal_pointer (&ssids);
+}
+
+GPtrArray *
+nmtst_ssids_options_to_ptrarray (GVariant *value, GError **error)
+{
+	return ssids_options_to_ptrarray (value, error);
 }
 
 static void
@@ -1118,8 +1238,7 @@ dbus_request_scan_cb (NMDevice *device,
                       gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
-	gs_unref_variant GVariant *scan_options = user_data;
-	gs_unref_ptrarray GPtrArray *ssids = NULL;
+	gs_unref_ptrarray GPtrArray *ssids = user_data;
 
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
@@ -1134,41 +1253,42 @@ dbus_request_scan_cb (NMDevice *device,
 		return;
 	}
 
-	if (scan_options) {
-		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
+	request_wireless_scan (self, FALSE, FALSE, ssids);
+	g_dbus_method_invocation_return_value (context, NULL);
+}
+
+void
+_nm_device_wifi_request_scan (NMDeviceWifi *self,
+                              GVariant *options,
+                              GDBusMethodInvocation *invocation)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+	gint64 last_scan;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
+
+	if (options) {
+		gs_unref_variant GVariant *val = g_variant_lookup_value (options, "ssids", NULL);
 
 		if (val) {
 			gs_free_error GError *ssid_error = NULL;
 
 			if (!g_variant_is_of_type (val, G_VARIANT_TYPE ("aay"))) {
-				g_dbus_method_invocation_return_error_literal (context,
+				g_dbus_method_invocation_return_error_literal (invocation,
 				                                               NM_DEVICE_ERROR,
-				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               NM_DEVICE_ERROR_INVALID_ARGUMENT,
 				                                               "Invalid 'ssid' scan option");
 				return;
 			}
 
 			ssids = ssids_options_to_ptrarray (val, &ssid_error);
 			if (ssid_error) {
-				g_dbus_method_invocation_return_gerror (context, ssid_error);
+				g_dbus_method_invocation_return_gerror (invocation, ssid_error);
 				return;
 			}
 		}
 	}
 
-	request_wireless_scan (self, FALSE, FALSE, ssids);
-	g_dbus_method_invocation_return_value (context, NULL);
-}
-
-void
-_nm_device_wifi_request_scan (NMDeviceWifi *self,
-                              GVariant *options,
-                              GDBusMethodInvocation *invocation)
-{
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMDevice *device = NM_DEVICE (self);
-	gint64 last_scan;
-
 	if (   !priv->enabled
 	    || !priv->sup_iface
 	    || nm_device_get_state (device) < NM_DEVICE_STATE_DISCONNECTED
@@ -1189,7 +1309,13 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	}
 
 	last_scan = nm_supplicant_interface_get_last_scan (priv->sup_iface);
-	if (last_scan && (nm_utils_get_monotonic_timestamp_ms () - last_scan) < 10 * NM_UTILS_MSEC_PER_SECOND) {
+	if (   last_scan > 0
+	    && nm_utils_get_monotonic_timestamp_msec () < last_scan + (10 * NM_UTILS_MSEC_PER_SEC)) {
+		/* FIXME: we really should not outright reject a scan request in this case. We should
+		 * ensure to start a scan request soon, possibly with rate limiting. And there is no
+		 * need to tell the caller that we aren't going to scan...
+		 *
+		 * Same above, if we are currently scanning... */
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1204,16 +1330,19 @@ _nm_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NM_AUTH_PERMISSION_WIFI_SCAN,
 	                       TRUE,
 	                       dbus_request_scan_cb,
-	                       options ? g_variant_ref (options) : NULL);
+	                       g_steal_pointer (&ssids));
 }
 
 static gboolean
-scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
+check_scanning_prohibited (NMDeviceWifi *self,
+                           gboolean periodic)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMSupplicantInterfaceState supplicant_state;
 
-	g_return_val_if_fail (priv->sup_iface != NULL, TRUE);
+	nm_assert (NM_IS_SUPPLICANT_INTERFACE (priv->sup_iface));
+
+	if (!c_list_is_empty (&priv->scanning_prohibited_lst_head))
+		return TRUE;
 
 	/* Don't scan when a an AP or Ad-Hoc connection is active as it will
 	 * disrupt connected clients or peers.
@@ -1250,11 +1379,11 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 	}
 
 	/* Prohibit scans if the supplicant is busy */
-	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
-	if (   supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
-	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED
-	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE
-	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE
+	if (   NM_IN_SET (nm_supplicant_interface_get_state (priv->sup_iface),
+	                  NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,
+	                  NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,
+	                  NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
+	                  NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE)
 	    || nm_supplicant_interface_get_scanning (priv->sup_iface))
 		return TRUE;
 
@@ -1263,15 +1392,6 @@ scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 }
 
 static gboolean
-check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
-{
-	gboolean prohibited = FALSE;
-
-	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
-	return prohibited;
-}
-
-static gboolean
 hidden_filter_func (NMSettings *settings,
                     NMSettingsConnection *set_con,
                     gpointer user_data)
@@ -1420,16 +1540,17 @@ static void
 schedule_scan (NMDeviceWifi *self, gboolean backoff)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 
-	/* Cancel the pending scan if it would happen later than (now + the scan_interval) */
+	/* Cancel the pending scan if it would happen later than (now + the scan_interval_sec) */
 	if (priv->pending_scan_id) {
-		if (now + priv->scan_interval < priv->scheduled_scan_time)
+		if (now + priv->scan_interval_sec < priv->scheduled_scan_time)
 			nm_clear_g_source (&priv->pending_scan_id);
 	}
 
 	if (!priv->pending_scan_id) {
-		guint factor = 2, next_scan = priv->scan_interval;
+		guint factor = 2;
+		guint next_scan = priv->scan_interval_sec;
 
 		if (    nm_device_is_activating (NM_DEVICE (self))
 		    || (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED))
@@ -1439,41 +1560,25 @@ schedule_scan (NMDeviceWifi *self, gboolean backoff)
 		                                               request_wireless_scan_periodic,
 		                                               self);
 
-		priv->scheduled_scan_time = now + priv->scan_interval;
-		if (backoff && (priv->scan_interval < (SCAN_INTERVAL_MAX / factor))) {
-				priv->scan_interval += (SCAN_INTERVAL_STEP / factor);
+		priv->scheduled_scan_time = now + priv->scan_interval_sec;
+		if (backoff && (priv->scan_interval_sec < (SCAN_INTERVAL_SEC_MAX / factor))) {
+				priv->scan_interval_sec += (SCAN_INTERVAL_SEC_STEP / factor);
 				/* Ensure the scan interval will never be less than 20s... */
-				priv->scan_interval = MAX(priv->scan_interval, SCAN_INTERVAL_MIN + SCAN_INTERVAL_STEP);
+				priv->scan_interval_sec = MAX(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MIN + SCAN_INTERVAL_SEC_STEP);
 				/* ... or more than 120s */
-				priv->scan_interval = MIN(priv->scan_interval, SCAN_INTERVAL_MAX);
-		} else if (!backoff && (priv->scan_interval == 0)) {
+				priv->scan_interval_sec = MIN(priv->scan_interval_sec, SCAN_INTERVAL_SEC_MAX);
+		} else if (!backoff && (priv->scan_interval_sec == 0)) {
 			/* Invalid combination; would cause continual rescheduling of
 			 * the scan and hog CPU.  Reset to something minimally sane.
 			 */
-			priv->scan_interval = 5;
+			priv->scan_interval_sec = 5;
 		}
 
 		_LOGD (LOGD_WIFI, "wifi-scan: scheduled in %d seconds (interval now %d seconds)",
-		       next_scan, priv->scan_interval);
+		       next_scan, priv->scan_interval_sec);
 	}
 }
 
-static void
-supplicant_iface_scan_done_cb (NMSupplicantInterface *iface,
-                               gboolean success,
-                               NMDeviceWifi *self)
-{
-	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-
-	_LOGD (LOGD_WIFI, "wifi-scan: scan-done callback: %s", success ? "successful" : "failed");
-
-	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
-	_notify (self, PROP_LAST_SCAN);
-	schedule_scan (self, success);
-
-	_requested_scan_set (self, FALSE);
-}
-
 /****************************************************************************
  * WPA Supplicant control stuff
  *
@@ -1489,14 +1594,21 @@ ap_list_dump (gpointer user_data)
 
 	if (_LOGD_ENABLED (LOGD_WIFI_SCAN)) {
 		NMWifiAP *ap;
-		gint32 now_s = nm_utils_get_monotonic_timestamp_s ();
-
-		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u last:%" G_GINT64_FORMAT " next:%u]",
-		       now_s,
-		       priv->last_scan / NM_UTILS_MSEC_PER_SECOND,
+		gint64 now_msec = nm_utils_get_monotonic_timestamp_msec ();
+		char str_buf[100];
+
+		_LOGD (LOGD_WIFI_SCAN, "APs: [now:%u.%03u, last:%s, next:%u]",
+		       (guint) (now_msec / NM_UTILS_MSEC_PER_SEC),
+		       (guint) (now_msec % NM_UTILS_MSEC_PER_SEC),
+		         priv->last_scan_msec > 0
+		       ? nm_sprintf_buf (str_buf,
+		                         "%u.%03u",
+		                         (guint) (priv->last_scan_msec / NM_UTILS_MSEC_PER_SEC),
+		                         (guint) (priv->last_scan_msec % NM_UTILS_MSEC_PER_SEC))
+		       : "-1",
 		       priv->scheduled_scan_time);
 		c_list_for_each_entry (ap, &priv->aps_lst_head, aps_lst)
-			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_s);
+			_ap_dump (self, LOGL_DEBUG, ap, "dump", now_msec);
 	}
 	return G_SOURCE_REMOVE;
 }
@@ -1543,40 +1655,43 @@ try_fill_ssid_for_hidden_ap (NMDeviceWifi *self,
 }
 
 static void
-supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
-                                 const char *object_path,
-                                 GVariant *properties,
+supplicant_iface_bss_changed_cb (NMSupplicantInterface *iface,
+                                 NMSupplicantBssInfo *bss_info,
+                                 gboolean is_present,
                                  NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMDeviceState state;
-	NMWifiAP *found_ap = NULL;
+	NMWifiAP *found_ap;
 	GBytes *ssid;
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (properties != NULL);
-	g_return_if_fail (iface != NULL);
+	found_ap = g_hash_table_lookup (priv->aps_idx_by_supplicant_path, bss_info->bss_path);
 
-	/* Ignore new APs when unavailable, unmanaged, or in AP mode */
-	state = nm_device_get_state (NM_DEVICE (self));
-	if (state <= NM_DEVICE_STATE_UNAVAILABLE)
-		return;
-	if (NM_DEVICE_WIFI_GET_PRIVATE (self)->mode == NM_802_11_MODE_AP)
+	if (!is_present) {
+		if (!found_ap)
+			return;
+		if (found_ap == priv->current_ap) {
+			/* The current AP cannot be removed (to prevent NM indicating that
+			 * it is connected, but to nothing), but it must be removed later
+			 * when the current AP is changed or cleared.  Set 'fake' to
+			 * indicate that this AP is now unknown to the supplicant.
+			 */
+			if (nm_wifi_ap_set_fake (found_ap, TRUE))
+				_ap_dump (self, LOGL_DEBUG, found_ap, "updated", 0);
+		} else {
+			ap_add_remove (self, FALSE, found_ap, TRUE);
+			schedule_ap_list_dump (self);
+		}
 		return;
+	}
 
-	found_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
 	if (found_ap) {
-		if (!nm_wifi_ap_update_from_properties (found_ap, object_path, properties))
+		if (!nm_wifi_ap_update_from_properties (found_ap, bss_info))
 			return;
 		_ap_dump (self, LOGL_DEBUG, found_ap, "updated", 0);
 	} else {
 		gs_unref_object NMWifiAP *ap = NULL;
 
-		ap = nm_wifi_ap_new_from_properties (object_path, properties);
-		if (!ap) {
-			_LOGD (LOGD_WIFI, "invalid AP properties received for %s", object_path);
-			return;
-		}
+		ap = nm_wifi_ap_new_from_properties (bss_info);
 
 		/* Let the manager try to fill in the SSID from seen-bssids lists */
 		ssid = nm_wifi_ap_get_ssid (ap);
@@ -1606,43 +1721,13 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 	/* Update the current AP if the supplicant notified a current BSS change
 	 * before it sent the current BSS's scan result.
 	 */
-	if (g_strcmp0 (nm_supplicant_interface_get_current_bss (iface), object_path) == 0)
+	if (nm_supplicant_interface_get_current_bss (iface) == bss_info->bss_path)
 		supplicant_iface_notify_current_bss (priv->sup_iface, NULL, self);
 
 	schedule_ap_list_dump (self);
 }
 
 static void
-supplicant_iface_bss_removed_cb (NMSupplicantInterface *iface,
-                                 const char *object_path,
-                                 NMDeviceWifi *self)
-{
-	NMDeviceWifiPrivate *priv;
-	NMWifiAP *ap;
-
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (object_path != NULL);
-
-	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, object_path);
-	if (!ap)
-		return;
-
-	if (ap == priv->current_ap) {
-		/* The current AP cannot be removed (to prevent NM indicating that
-		 * it is connected, but to nothing), but it must be removed later
-		 * when the current AP is changed or cleared.  Set 'fake' to
-		 * indicate that this AP is now unknown to the supplicant.
-		 */
-		if (nm_wifi_ap_set_fake (ap, TRUE))
-			_ap_dump (self, LOGL_DEBUG, ap, "updated", 0);
-	} else {
-		ap_add_remove (self, FALSE, ap, TRUE);
-		schedule_ap_list_dump (self);
-	}
-}
-
-static void
 cleanup_association_attempt (NMDeviceWifi *self, gboolean disconnect)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
@@ -1725,7 +1810,7 @@ wifi_secrets_cb (NMActRequest *req,
 		return;
 	}
 
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -1792,7 +1877,7 @@ supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
 	}
 
 	wifi_secrets_cancel (self);
-	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+	nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self), FALSE);
 }
 
 static gboolean
@@ -2020,50 +2105,71 @@ reacquire_interface_cb (gpointer user_data)
 }
 
 static void
-supplicant_iface_state_cb (NMSupplicantInterface *iface,
-                           int new_state_i,
-                           int old_state_i,
-                           int disconnect_reason,
-                           gpointer user_data)
+supplicant_iface_state_down (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMDevice *device = NM_DEVICE (self);
+
+	nm_device_queue_recheck_available (device,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+	                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+	cleanup_association_attempt (self, FALSE);
+
+	/* If the device is already in UNAVAILABLE state then the state change
+	 * is a NOP and the interface won't be re-acquired in the device state
+	 * change handler.  So ensure we have a new one here so that we're
+	 * ready if the supplicant comes back.
+	 */
+	supplicant_interface_release (self);
+	if (priv->failed_iface_count < 5)
+		priv->reacquire_iface_id = g_timeout_add_seconds (10, reacquire_interface_cb, self);
+	else
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "supplicant interface keeps failing, giving up");
+}
+
+static void
+supplicant_iface_state (NMDeviceWifi *self,
+                        NMSupplicantInterfaceState new_state,
+                        NMSupplicantInterfaceState old_state,
+                        int disconnect_reason,
+                        gboolean is_real_signal)
 {
-	NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
 	NMDeviceState devstate;
 	gboolean scanning;
-	NMSupplicantInterfaceState new_state = new_state_i;
-	NMSupplicantInterfaceState old_state = old_state_i;
-
-	if (new_state == old_state)
-		return;
 
 	_LOGI (LOGD_DEVICE | LOGD_WIFI,
-	       "supplicant interface state: %s -> %s",
+	       "supplicant interface state: %s -> %s%s",
 	       nm_supplicant_interface_state_to_string (old_state),
-	       nm_supplicant_interface_state_to_string (new_state));
+	       nm_supplicant_interface_state_to_string (new_state),
+	       is_real_signal ? "" : " (simulated signal)");
+
+	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
+		supplicant_iface_state_down (self);
+		goto out;
+	}
 
 	devstate = nm_device_get_state (device);
-	scanning = nm_supplicant_interface_get_scanning (iface);
+	scanning = nm_supplicant_interface_get_scanning (priv->sup_iface);
+
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+		_LOGD (LOGD_WIFI, "supplicant ready");
+		nm_device_queue_recheck_available (NM_DEVICE (device),
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+		priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN;
+	}
 
 	/* In these states we know the supplicant is actually talking to something */
 	if (   new_state >= NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
 	    && new_state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
 		priv->ssid_found = TRUE;
 
-	if (   old_state < NM_SUPPLICANT_INTERFACE_STATE_READY
-	    && new_state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
 		recheck_p2p_availability (self);
 
 	switch (new_state) {
-	case NM_SUPPLICANT_INTERFACE_STATE_READY:
-		_LOGD (LOGD_WIFI, "supplicant ready");
-		nm_device_queue_recheck_available (NM_DEVICE (device),
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		priv->scan_interval = SCAN_INTERVAL_MIN;
-		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
-			nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
-		break;
 	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
 		nm_clear_g_source (&priv->sup_timeout_id);
 		nm_clear_g_source (&priv->link_timeout_id);
@@ -2118,26 +2224,6 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 			}
 		}
 		break;
-	case NM_SUPPLICANT_INTERFACE_STATE_DOWN:
-		nm_device_queue_recheck_available (NM_DEVICE (device),
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
-		                                   NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		cleanup_association_attempt (self, FALSE);
-
-		if (old_state < NM_SUPPLICANT_INTERFACE_STATE_READY)
-			nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
-
-		/* If the device is already in UNAVAILABLE state then the state change
-		 * is a NOP and the interface won't be re-acquired in the device state
-		 * change handler.  So ensure we have a new one here so that we're
-		 * ready if the supplicant comes back.
-		 */
-		supplicant_interface_release (self);
-		if (priv->failed_iface_count < 5)
-			priv->reacquire_iface_id = g_timeout_add_seconds (10, reacquire_interface_cb, self);
-		else
-			_LOGI (LOGD_DEVICE | LOGD_WIFI, "supplicant interface keeps failing, giving up");
-		break;
 	case NM_SUPPLICANT_INTERFACE_STATE_INACTIVE:
 		/* we would clear _requested_scan_set() and trigger a new scan.
 		 * However, we don't want to cancel the current pending action, so force
@@ -2148,10 +2234,25 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		break;
 	}
 
-	/* Signal scanning state changes */
-	if (   new_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
-	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		_notify_scanning (self);
+out:
+	_notify_scanning (self);
+
+	if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING)
+		nm_device_remove_pending_action (device, NM_PENDING_ACTION_WAITING_FOR_SUPPLICANT, TRUE);
+}
+
+static void
+supplicant_iface_state_cb (NMSupplicantInterface *iface,
+                           int new_state_i,
+                           int old_state_i,
+                           int disconnect_reason,
+                           gpointer user_data)
+{
+	supplicant_iface_state (user_data,
+	                        new_state_i,
+	                        old_state_i,
+	                        disconnect_reason,
+	                        TRUE);
 }
 
 static void
@@ -2162,7 +2263,7 @@ supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
 	NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
 	NMDevice *device = NM_DEVICE (self);
 
-	if (   error && !nm_utils_error_is_cancelled (error, TRUE)
+	if (   error && !nm_utils_error_is_cancelled_or_disposing (error)
 	    && nm_device_is_activating (device)) {
 		cleanup_association_attempt (self, TRUE);
 		nm_device_queue_state (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
@@ -2170,30 +2271,17 @@ supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
 }
 
 static void
-supplicant_iface_notify_scanning_cb (NMSupplicantInterface *iface,
-                                     GParamSpec *pspec,
-                                     NMDeviceWifi *self)
-{
-	_notify_scanning (self);
-
-	/* Run a quick update of current AP when coming out of a scan */
-	if (   !NM_DEVICE_WIFI_GET_PRIVATE (self)->is_scanning
-	    && nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_ACTIVATED)
-		periodic_update (self);
-}
-
-static void
 supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                      GParamSpec *pspec,
                                      NMDeviceWifi *self)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	const char *current_bss;
+	NMRefString *current_bss;
 	NMWifiAP *new_ap = NULL;
 
 	current_bss = nm_supplicant_interface_get_current_bss (iface);
 	if (current_bss)
-		new_ap = nm_wifi_aps_find_by_supplicant_path (&priv->aps_lst_head, current_bss);
+		new_ap = g_hash_table_lookup (priv->aps_idx_by_supplicant_path, current_bss);
 
 	if (new_ap != priv->current_ap) {
 		const char *new_bssid = NULL;
@@ -2284,11 +2372,8 @@ supplicant_iface_notify_p2p_available (NMSupplicantInterface *iface,
                                        GParamSpec *pspec,
                                        NMDeviceWifi *self)
 {
-	/* Do not update when the interface is still initializing. */
-	if (nm_supplicant_interface_get_state (iface) < NM_SUPPLICANT_INTERFACE_STATE_READY)
-		return;
-
-	recheck_p2p_availability (self);
+	if (nm_supplicant_interface_get_state (iface) > NM_SUPPLICANT_INTERFACE_STATE_STARTING)
+		recheck_p2p_availability (self);
 }
 
 static gboolean
@@ -2472,15 +2557,11 @@ build_supplicant_config (NMDeviceWifi *self,
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless != NULL, NULL);
 
-	config = nm_supplicant_config_new (
-		nm_supplicant_interface_get_pmf_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
-		nm_supplicant_interface_get_fils_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
-		nm_supplicant_interface_get_ft_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
-		nm_supplicant_interface_get_sha384_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES);
+	config = nm_supplicant_config_new (nm_supplicant_interface_get_capabilities (priv->sup_iface));
 
 	/* Warn if AP mode may not be supported */
-	if (   g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0
-	    && nm_supplicant_interface_get_ap_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_UNKNOWN) {
+	if (   nm_streq0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP)
+	    && nm_supplicant_interface_get_capability (priv->sup_iface, NM_SUPPL_CAP_TYPE_AP) != NM_TERNARY_TRUE) {
 		_LOGW (LOGD_WIFI, "Supplicant may not support AP mode; connection may time out.");
 	}
 
@@ -2753,8 +2834,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
-	NMSupplicantConfig *config = NULL;
+	gs_unref_object NMSupplicantConfig *config = NULL;
 	NM80211Mode ap_mode;
 	NMActRequest *req;
 	NMWifiAP *ap;
@@ -2774,15 +2854,14 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	ap = priv->current_ap;
 	if (!ap) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
-		goto out;
+		goto out_fail;
 	}
+
 	ap_mode = nm_wifi_ap_get_mode (ap);
 
 	connection = nm_act_request_get_applied_connection (req);
-	g_assert (connection);
-
 	s_wireless = nm_connection_get_setting_wireless (connection);
-	g_assert (s_wireless);
+	nm_assert (s_wireless);
 
 	/* If we need secrets, get them */
 	setting_name = nm_connection_need_secrets (connection, NULL);
@@ -2791,13 +2870,12 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		       "Activation: (wifi) access point '%s' has security, but secrets are required.",
 		       nm_connection_get_id (connection));
 
-		if (handle_auth_or_fail (self, req, FALSE))
-			ret = NM_ACT_STAGE_RETURN_POSTPONE;
-		else {
+		if (!handle_auth_or_fail (self, req, FALSE)) {
 			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-			ret = NM_ACT_STAGE_RETURN_FAILURE;
+			goto out_fail;
 		}
-		goto out;
+
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
 	if (!wake_on_wlan_enable (self))
@@ -2830,17 +2908,19 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	/* Build up the supplicant configuration */
 	config = build_supplicant_config (self, connection, nm_wifi_ap_get_freq (ap), &error);
-	if (config == NULL) {
+	if (!config) {
 		_LOGE (LOGD_DEVICE | LOGD_WIFI,
 		       "Activation: (wifi) couldn't build wireless configuration: %s",
 		       error->message);
 		g_clear_error (&error);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
-		goto out;
+		goto out_fail;
 	}
 
-	nm_supplicant_interface_assoc (priv->sup_iface, config,
-	                               supplicant_iface_assoc_cb, self);
+	nm_supplicant_interface_assoc (priv->sup_iface,
+	                               config,
+	                               supplicant_iface_assoc_cb,
+	                               self);
 
 	/* Set up a timeout on the association attempt */
 	timeout = nm_device_get_supplicant_timeout (NM_DEVICE (self));
@@ -2848,25 +2928,16 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	                                              supplicant_connection_timeout_cb,
 	                                              self);
 
-	if (!priv->periodic_source_id)
-		priv->periodic_source_id = g_timeout_add_seconds (6, periodic_update_cb, self);
+	if (!priv->periodic_update_id)
+		priv->periodic_update_id = g_timeout_add_seconds (6, periodic_update_cb, self);
 
 	/* We'll get stage3 started when the supplicant connects */
-	ret = NM_ACT_STAGE_RETURN_POSTPONE;
-
-out:
-	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-		cleanup_association_attempt (self, TRUE);
-		wake_on_wlan_restore (self);
-	}
+	return NM_ACT_STAGE_RETURN_POSTPONE;
 
-	if (config) {
-		/* Supplicant interface object refs the config; we no longer care about
-		 * it after this function.
-		 */
-		g_object_unref (config);
-	}
-	return ret;
+out_fail:
+	cleanup_association_attempt (self, TRUE);
+	wake_on_wlan_restore (self);
+	return NM_ACT_STAGE_RETURN_FAILURE;
 }
 
 static NMActStageReturn
@@ -3033,7 +3104,7 @@ activation_success_handler (NMDevice *device)
 	update_seen_bssids_cache (self, priv->current_ap);
 
 	/* Reset scan interval to something reasonable */
-	priv->scan_interval = SCAN_INTERVAL_MIN + (SCAN_INTERVAL_STEP * 2);
+	priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN + (SCAN_INTERVAL_SEC_STEP * 2);
 }
 
 static void
@@ -3053,10 +3124,9 @@ device_state_changed (NMDevice *device,
 		/* Clean up the supplicant interface because in these states the
 		 * device cannot be used.
 		 */
-		if (priv->sup_iface)
-			supplicant_interface_release (self);
+		supplicant_interface_release (self);
 
-		nm_clear_g_source (&priv->periodic_source_id);
+		nm_clear_g_source (&priv->periodic_update_id);
 
 		cleanup_association_attempt (self, TRUE);
 		cleanup_supplicant_failures (self);
@@ -3095,7 +3165,7 @@ device_state_changed (NMDevice *device,
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		/* Kick off a scan to get latest results */
-		priv->scan_interval = SCAN_INTERVAL_MIN;
+		priv->scan_interval_sec = SCAN_INTERVAL_SEC_MIN;
 		request_wireless_scan (self, FALSE, FALSE, NULL);
 		break;
 	default:
@@ -3109,7 +3179,7 @@ device_state_changed (NMDevice *device,
 static gboolean
 get_enabled (NMDevice *device)
 {
-	return NM_DEVICE_WIFI_GET_PRIVATE ((NMDeviceWifi *) device)->enabled;
+	return NM_DEVICE_WIFI_GET_PRIVATE (device)->enabled;
 }
 
 static void
@@ -3155,8 +3225,7 @@ set_enabled (NMDevice *device, gboolean enabled)
 
 		/* Re-initialize the supplicant interface and wait for it to be ready */
 		cleanup_supplicant_failures (self);
-		if (priv->sup_iface)
-			supplicant_interface_release (self);
+		supplicant_interface_release (self);
 		supplicant_interface_acquire (self);
 
 		_LOGD (LOGD_WIFI, "enable waiting on supplicant state");
@@ -3251,13 +3320,13 @@ get_property (GObject *object, guint prop_id,
 		nm_dbus_utils_g_value_set_object_path (value, priv->current_ap);
 		break;
 	case PROP_SCANNING:
-		g_value_set_boolean (value, priv->is_scanning);
+		g_value_set_boolean (value, nm_device_wifi_get_scanning (self));
 		break;
 	case PROP_LAST_SCAN:
 		g_value_set_int64 (value,
-		                   priv->last_scan > 0
-		                       ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan, NM_UTILS_NS_PER_MSEC)
-		                       : (gint64) -1);
+		                     priv->last_scan_msec > 0
+		                   ? nm_utils_monotonic_timestamp_as_boottime (priv->last_scan_msec, NM_UTILS_NSEC_PER_MSEC)
+		                   : (gint64) -1);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -3291,6 +3360,8 @@ nm_device_wifi_init (NMDeviceWifi *self)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	c_list_init (&priv->aps_lst_head);
+	c_list_init (&priv->scanning_prohibited_lst_head);
+	priv->aps_idx_by_supplicant_path = g_hash_table_new (nm_direct_hash, NULL);
 
 	priv->hidden_probe_scan_warn = TRUE;
 	priv->mode = NM_802_11_MODE_INFRA;
@@ -3331,7 +3402,9 @@ dispose (GObject *object)
 	NMDeviceWifi *self = NM_DEVICE_WIFI (object);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	nm_clear_g_source (&priv->periodic_source_id);
+	nm_assert (c_list_is_empty (&priv->scanning_prohibited_lst_head));
+
+	nm_clear_g_source (&priv->periodic_update_id);
 
 	wifi_secrets_cancel (self);
 
@@ -3359,6 +3432,9 @@ finalize (GObject *object)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	nm_assert (c_list_is_empty (&priv->aps_lst_head));
+	nm_assert (g_hash_table_size (priv->aps_idx_by_supplicant_path) == 0);
+
+	g_hash_table_unref (priv->aps_idx_by_supplicant_path);
 
 	G_OBJECT_CLASS (nm_device_wifi_parent_class)->finalize (object);
 }
@@ -3406,8 +3482,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	device_class->state_changed = device_state_changed;
 
-	klass->scanning_prohibited = scanning_prohibited;
-
 	obj_properties[PROP_MODE] =
 	    g_param_spec_uint (NM_DEVICE_WIFI_MODE, "", "",
 	                       NM_802_11_MODE_UNKNOWN,
@@ -3454,14 +3528,6 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[SCANNING_PROHIBITED] =
-	    g_signal_new (NM_DEVICE_WIFI_SCANNING_PROHIBITED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
-	                  NULL, NULL, NULL,
-	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
-
 	signals[P2P_DEVICE_CREATED] =
 	    g_signal_new (NM_DEVICE_WIFI_P2P_DEVICE_CREATED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index aaf47143..c9fce4e2 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -24,7 +24,6 @@
 #define NM_DEVICE_WIFI_SCANNING            "scanning"
 #define NM_DEVICE_WIFI_LAST_SCAN           "last-scan"
 
-#define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
 #define NM_DEVICE_WIFI_P2P_DEVICE_CREATED     "p2p-device-created"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
@@ -40,4 +39,12 @@ void _nm_device_wifi_request_scan (NMDeviceWifi *self,
                                    GVariant *options,
                                    GDBusMethodInvocation *invocation);
 
+GPtrArray *nmtst_ssids_options_to_ptrarray (GVariant *value, GError **error);
+
+gboolean nm_device_wifi_get_scanning (NMDeviceWifi *self);
+
+void nm_device_wifi_scanning_prohibited_track (NMDeviceWifi *self,
+                                               gpointer tag,
+                                               gboolean temporarily_prohibited);
+
 #endif /* __NETWORKMANAGER_DEVICE_WIFI_H__ */
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index 470cb1c9..39c863a3 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -874,7 +874,8 @@ nm_iwd_manager_is_known_network (NMIwdManager *self, const char *name,
 }
 
 GDBusProxy *
-nm_iwd_manager_get_dbus_interface (NMIwdManager *self, const char *path,
+nm_iwd_manager_get_dbus_interface (NMIwdManager *self,
+                                   const char *path,
                                    const char *name)
 {
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index ee7dc236..e427c86f 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -10,15 +10,16 @@
 
 #include <stdlib.h>
 
-#include "nm-setting-wireless.h"
-
-#include "nm-wifi-utils.h"
 #include "NetworkManagerUtils.h"
-#include "nm-utils.h"
-#include "nm-core-internal.h"
-#include "platform/nm-platform.h"
 #include "devices/nm-device.h"
+#include "nm-core-internal.h"
 #include "nm-dbus-manager.h"
+#include "nm-glib-aux/nm-ref-string.h"
+#include "nm-setting-wireless.h"
+#include "nm-utils.h"
+#include "nm-wifi-utils.h"
+#include "platform/nm-platform.h"
+#include "supplicant/nm-supplicant-interface.h"
 
 #define PROTO_WPA "wpa"
 #define PROTO_RSN "rsn"
@@ -39,8 +40,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMWifiAP,
 );
 
 struct _NMWifiAPPrivate {
-	char *supplicant_path;   /* D-Bus object path of this AP from wpa_supplicant */
-
 	/* Scanned or cached values */
 	GBytes *           ssid;
 	char *             address;
@@ -49,6 +48,9 @@ struct _NMWifiAPPrivate {
 	guint32            freq;        /* Frequency in MHz; ie 2412 (== 2.412 GHz) */
 	guint32            max_bitrate; /* Maximum bitrate of the AP in Kbit/s (ie 54000 Kb/s == 54Mbit/s) */
 
+	gint64             last_seen_msec; /* Timestamp when the AP was seen lastly (in nm_utils_get_monotonic_timestamp_*() scale).
+	                                    * Note that this value might be negative! */
+
 	NM80211ApFlags         flags;      /* General flags */
 	NM80211ApSecurityFlags wpa_flags;  /* WPA-related flags */
 	NM80211ApSecurityFlags rsn_flags;  /* RSN (WPA2) -related flags */
@@ -58,7 +60,6 @@ struct _NMWifiAPPrivate {
 	/* Non-scanned attributes */
 	bool               fake:1;       /* Whether or not the AP is from a scan */
 	bool               hotspot:1;    /* Whether the AP is a local device's hotspot network */
-	gint32             last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
 };
 
 typedef struct _NMWifiAPPrivate NMWifiAPPrivate;
@@ -73,14 +74,6 @@ G_DEFINE_TYPE (NMWifiAP, nm_wifi_ap, NM_TYPE_DBUS_OBJECT)
 
 /*****************************************************************************/
 
-const char *
-nm_wifi_ap_get_supplicant_path (NMWifiAP *ap)
-{
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);
-
-	return NM_WIFI_AP_GET_PRIVATE (ap)->supplicant_path;
-}
-
 GBytes *
 nm_wifi_ap_get_ssid (const NMWifiAP *ap)
 {
@@ -148,11 +141,7 @@ nm_wifi_ap_set_ssid (NMWifiAP *ap, GBytes *ssid)
 static gboolean
 nm_wifi_ap_set_flags (NMWifiAP *ap, NM80211ApFlags flags)
 {
-	NMWifiAPPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
-
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
 	if (priv->flags != flags) {
 		priv->flags = flags;
@@ -165,11 +154,8 @@ nm_wifi_ap_set_flags (NMWifiAP *ap, NM80211ApFlags flags)
 static gboolean
 nm_wifi_ap_set_wpa_flags (NMWifiAP *ap, NM80211ApSecurityFlags flags)
 {
-	NMWifiAPPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
 	if (priv->wpa_flags != flags) {
 		priv->wpa_flags = flags;
 		_notify (ap, PROP_WPA_FLAGS);
@@ -181,11 +167,8 @@ nm_wifi_ap_set_wpa_flags (NMWifiAP *ap, NM80211ApSecurityFlags flags)
 static gboolean
 nm_wifi_ap_set_rsn_flags (NMWifiAP *ap, NM80211ApSecurityFlags flags)
 {
-	NMWifiAPPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
 	if (priv->rsn_flags != flags) {
 		priv->rsn_flags = flags;
 		_notify (ap, PROP_RSN_FLAGS);
@@ -203,11 +186,9 @@ nm_wifi_ap_get_address (const NMWifiAP *ap)
 }
 
 static gboolean
-nm_wifi_ap_set_address_bin (NMWifiAP *ap, const guint8 *addr /* ETH_ALEN bytes */)
+nm_wifi_ap_set_address_bin (NMWifiAP *ap, const guint8 addr[static 6 /* ETH_ALEN */])
 {
-	NMWifiAPPrivate *priv;
-
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
 	if (   !priv->address
 	    || !nm_utils_hwaddr_matches (addr, ETH_ALEN, priv->address, -1)) {
@@ -241,16 +222,14 @@ nm_wifi_ap_get_mode (NMWifiAP *ap)
 }
 
 static gboolean
-nm_wifi_ap_set_mode (NMWifiAP *ap, const NM80211Mode mode)
+nm_wifi_ap_set_mode (NMWifiAP *ap, NM80211Mode mode)
 {
-	NMWifiAPPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
-	g_return_val_if_fail (   mode == NM_802_11_MODE_ADHOC
-	                      || mode == NM_802_11_MODE_INFRA
-	                      || mode == NM_802_11_MODE_MESH, FALSE);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	nm_assert (NM_IN_SET (mode, NM_802_11_MODE_UNKNOWN,
+	                            NM_802_11_MODE_ADHOC,
+	                            NM_802_11_MODE_INFRA,
+	                            NM_802_11_MODE_MESH));
 
 	if (priv->mode != mode) {
 		priv->mode = mode;
@@ -277,13 +256,9 @@ nm_wifi_ap_get_strength (NMWifiAP *ap)
 }
 
 gboolean
-nm_wifi_ap_set_strength (NMWifiAP *ap, const gint8 strength)
+nm_wifi_ap_set_strength (NMWifiAP *ap, gint8 strength)
 {
-	NMWifiAPPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
-
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
 	if (priv->strength != strength) {
 		priv->strength = strength;
@@ -303,13 +278,9 @@ nm_wifi_ap_get_freq (NMWifiAP *ap)
 
 gboolean
 nm_wifi_ap_set_freq (NMWifiAP *ap,
-                     const guint32 freq)
+                     guint32 freq)
 {
-	NMWifiAPPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
-
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
 	if (priv->freq != freq) {
 		priv->freq = freq;
@@ -378,16 +349,12 @@ nm_wifi_ap_get_flags (const NMWifiAP *ap)
 }
 
 static gboolean
-nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen)
+nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen_msec)
 {
-	NMWifiAPPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
-
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
+	NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
-	if (priv->last_seen != last_seen) {
-		priv->last_seen = last_seen;
+	if (priv->last_seen_msec != last_seen_msec) {
+		priv->last_seen_msec = last_seen_msec;
 		_notify (ap, PROP_LAST_SEEN);
 		return TRUE;
 	}
@@ -402,501 +369,53 @@ nm_wifi_ap_get_metered (const NMWifiAP *self)
 
 /*****************************************************************************/
 
-static NM80211ApSecurityFlags
-security_from_vardict (GVariant *security)
-{
-	NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE;
-	const char **array, *tmp;
-
-	g_return_val_if_fail (g_variant_is_of_type (security, G_VARIANT_TYPE_VARDICT), NM_802_11_AP_SEC_NONE);
-
-	if (   g_variant_lookup (security, "KeyMgmt", "^a&s", &array)
-	    && array) {
-		if (g_strv_contains (array, "wpa-psk") ||
-		    g_strv_contains (array, "wpa-ft-psk"))
-			flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK;
-		if (g_strv_contains (array, "wpa-eap") ||
-		    g_strv_contains (array, "wpa-ft-eap") ||
-		    g_strv_contains (array, "wpa-fils-sha256") ||
-		    g_strv_contains (array, "wpa-fils-sha384"))
-			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
-		if (g_strv_contains (array, "sae"))
-			flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE;
-		g_free (array);
-	}
-
-	if (   g_variant_lookup (security, "Pairwise", "^a&s", &array)
-	    && array) {
-		if (g_strv_contains (array, "tkip"))
-			flags |= NM_802_11_AP_SEC_PAIR_TKIP;
-		if (g_strv_contains (array, "ccmp"))
-			flags |= NM_802_11_AP_SEC_PAIR_CCMP;
-		g_free (array);
-	}
-
-	if (g_variant_lookup (security, "Group", "&s", &tmp)) {
-		if (strcmp (tmp, "wep40") == 0)
-			flags |= NM_802_11_AP_SEC_GROUP_WEP40;
-		if (strcmp (tmp, "wep104") == 0)
-			flags |= NM_802_11_AP_SEC_GROUP_WEP104;
-		if (strcmp (tmp, "tkip") == 0)
-			flags |= NM_802_11_AP_SEC_GROUP_TKIP;
-		if (strcmp (tmp, "ccmp") == 0)
-			flags |= NM_802_11_AP_SEC_GROUP_CCMP;
-	}
-
-	return flags;
-}
-
-/*****************************************************************************/
-
-static guint32
-get_max_rate_ht_20 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 6500000;
-	case 1:
-	case 8:  return 13000000;
-	case 2:
-	case 16: return 19500000;
-	case 3:
-	case 9:
-	case 24: return 26000000;
-	case 4:
-	case 10:
-	case 17: return 39000000;
-	case 5:
-	case 11:
-	case 25: return 52000000;
-	case 6:
-	case 18: return 58500000;
-	case 7:  return 65000000;
-	case 12:
-	case 19:
-	case 26: return 78000000;
-	case 13:
-	case 27: return 104000000;
-	case 14:
-	case 20: return 117000000;
-	case 15: return 130000000;
-	case 21:
-	case 28: return 156000000;
-	case 22: return 175500000;
-	case 23: return 195000000;
-	case 29: return 208000000;
-	case 30: return 234000000;
-	case 31: return 260000000;
-	}
-	return 0;
-}
-
-static guint32
-get_max_rate_ht_40 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 13500000;
-	case 1:
-	case 8:  return 27000000;
-	case 2:  return 40500000;
-	case 3:
-	case 9:
-	case 24: return 54000000;
-	case 4:
-	case 10:
-	case 17: return 81000000;
-	case 5:
-	case 11:
-	case 25: return 108000000;
-	case 6:
-	case 18: return 121500000;
-	case 7:  return 135000000;
-	case 12:
-	case 19:
-	case 26: return 162000000;
-	case 13:
-	case 27: return 216000000;
-	case 14:
-	case 20: return 243000000;
-	case 15: return 270000000;
-	case 16: return 40500000;
-	case 21:
-	case 28: return 324000000;
-	case 22: return 364500000;
-	case 23: return 405000000;
-	case 29: return 432000000;
-	case 30: return 486000000;
-	case 31: return 540000000;
-	}
-	return 0;
-}
-
-static guint32
-get_max_rate_vht_80_ss1 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 29300000;
-	case 1:  return 58500000;
-	case 2:  return 87800000;
-	case 3:  return 117000000;
-	case 4:  return 175500000;
-	case 5:  return 234000000;
-	case 6:  return 263300000;
-	case 7:  return 292500000;
-	case 8:  return 351000000;
-	case 9:  return 390000000;
-	}
-	return 0;
-}
-
-static guint32
-get_max_rate_vht_80_ss2 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 58500000;
-	case 1:  return 117000000;
-	case 2:  return 175500000;
-	case 3:  return 234000000;
-	case 4:  return 351000000;
-	case 5:  return 468000000;
-	case 6:  return 526500000;
-	case 7:  return 585000000;
-	case 8:  return 702000000;
-	case 9:  return 780000000;
-	}
-	return 0;
-}
-
-static guint32
-get_max_rate_vht_80_ss3 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 87800000;
-	case 1:  return 175500000;
-	case 2:  return 263300000;
-	case 3:  return 351000000;
-	case 4:  return 526500000;
-	case 5:  return 702000000;
-	case 6:  return 0;
-	case 7:  return 877500000;
-	case 8:  return 105300000;
-	case 9:  return 117000000;
-	}
-	return 0;
-}
-
-static guint32
-get_max_rate_vht_160_ss1 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 58500000;
-	case 1:  return 117000000;
-	case 2:  return 175500000;
-	case 3:  return 234000000;
-	case 4:  return 351000000;
-	case 5:  return 468000000;
-	case 6:  return 526500000;
-	case 7:  return 585000000;
-	case 8:  return 702000000;
-	case 9:  return 780000000;
-	}
-	return 0;
-}
-
-static guint32
-get_max_rate_vht_160_ss2 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 117000000;
-	case 1:  return 234000000;
-	case 2:  return 351000000;
-	case 3:  return 468000000;
-	case 4:  return 702000000;
-	case 5:  return 936000000;
-	case 6:  return 1053000000;
-	case 7:  return 1170000000;
-	case 8:  return 1404000000;
-	case 9:  return 1560000000;
-	}
-	return 0;
-}
-
-static guint32
-get_max_rate_vht_160_ss3 (int mcs)
-{
-	switch (mcs) {
-	case 0:  return 175500000;
-	case 1:  return 351000000;
-	case 2:  return 526500000;
-	case 3:  return 702000000;
-	case 4:  return 1053000000;
-	case 5:  return 1404000000;
-	case 6:  return 1579500000;
-	case 7:  return 1755000000;
-	case 8:  return 2106000000;
-	case 9:  return 0;
-	}
-	return 0;
-}
-
-static gboolean
-get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
-{
-	guint32 i;
-	guint8 ht_cap_info;
-	const guint8 *supported_mcs_set;
-	guint32 rate;
-
-	/* http://standards.ieee.org/getieee802/download/802.11-2012.pdf
-	 * https://mrncciew.com/2014/10/19/cwap-ht-capabilities-ie/
-	 */
-
-	if (len != 26)
-		return FALSE;
-
-	ht_cap_info = bytes[0];
-	supported_mcs_set = &bytes[3];
-	*out_maxrate = 0;
-
-	/* Find the maximum supported mcs rate */
-	for (i = 0; i <= 76; i++) {
-		unsigned int mcs_octet = i / 8;
-		unsigned int MCS_RATE_BIT = 1 << i % 8;
-
-		if (supported_mcs_set[mcs_octet] & MCS_RATE_BIT) {
-			/* Check for 40Mhz wide channel support */
-			if (ht_cap_info & (1 << 1))
-				rate = get_max_rate_ht_40 (i);
-			else
-				rate = get_max_rate_ht_20 (i);
-
-			if (rate > *out_maxrate)
-				*out_maxrate = rate;
-		}
-	}
-
-	return TRUE;
-}
-
-static gboolean
-get_max_rate_vht (const guint8 *bytes, guint len, guint32 *out_maxrate)
-{
-	guint32 mcs, m;
-	guint8 vht_cap, tx_map;
-
-	/* https://tda802dot11.blogspot.it/2014/10/vht-capabilities-element-vht.html
-	 * http://chimera.labs.oreilly.com/books/1234000001739/ch03.html#management_frames */
-
-	if (len != 12)
-		return FALSE;
-
-	vht_cap = bytes[0];
-	tx_map = bytes[8];
-
-	/* Check for mcs rates 8 and 9 support */
-	if (tx_map & 0x2a)
-		mcs = 9;
-	else if (tx_map & 0x15)
-		mcs = 8;
-	else
-		mcs = 7;
-
-	/* Check for 160Mhz wide channel support and
-	 * spatial stream support */
-	if (vht_cap & (1 << 2)) {
-		if (tx_map & 0x30)
-			m = get_max_rate_vht_160_ss3 (mcs);
-		else if (tx_map & 0x0C)
-			m = get_max_rate_vht_160_ss2 (mcs);
-		else
-			m = get_max_rate_vht_160_ss1 (mcs);
-	} else {
-		if (tx_map & 0x30)
-			m = get_max_rate_vht_80_ss3 (mcs);
-		else if (tx_map & 0x0C)
-			m = get_max_rate_vht_80_ss2 (mcs);
-		else
-			m = get_max_rate_vht_80_ss1 (mcs);
-	}
-
-	*out_maxrate = m;
-	return TRUE;
-}
-
-/* Management Frame Information Element IDs, ieee80211_eid */
-#define WLAN_EID_HT_CAPABILITY       45
-#define WLAN_EID_VHT_CAPABILITY     191
-#define WLAN_EID_VENDOR_SPECIFIC    221
-
-static void
-parse_ies (const guint8 *bytes, gsize len, guint32 *out_max_rate, gboolean *out_metered)
-{
-	guint8 id, elem_len;
-	guint32 m;
-
-	*out_max_rate = 0;
-	*out_metered = FALSE;
-
-	while (len) {
-		if (len < 2)
-			break;
-
-		id = *bytes++;
-		elem_len = *bytes++;
-		len -= 2;
-
-		if (elem_len > len)
-			break;
-
-		switch (id) {
-		case WLAN_EID_HT_CAPABILITY:
-			if (get_max_rate_ht (bytes, elem_len, &m))
-				*out_max_rate = NM_MAX (*out_max_rate, m);
-			break;
-		case WLAN_EID_VHT_CAPABILITY:
-			if (get_max_rate_vht (bytes, elem_len, &m))
-				*out_max_rate = NM_MAX (*out_max_rate, m);
-			break;
-		case WLAN_EID_VENDOR_SPECIFIC:
-			if (   len == 8
-			    && bytes[0] == 0x00            /* OUI: Microsoft */
-			    && bytes[1] == 0x50
-			    && bytes[2] == 0xf2
-			    && bytes[3] == 0x11)           /* OUI type: Network cost */
-				*out_metered = (bytes[7] > 1); /* Cost level > 1 */
-			break;
-		}
-
-		len -= elem_len;
-		bytes += elem_len;
-	}
-}
-
-/*****************************************************************************/
-
 gboolean
 nm_wifi_ap_update_from_properties (NMWifiAP *ap,
-                                   const char *supplicant_path,
-                                   GVariant *properties)
+                                   const NMSupplicantBssInfo *bss_info)
 {
 	NMWifiAPPrivate *priv;
-	const guint8 *bytes;
-	GVariant *v;
-	gsize len;
-	gsize i;
-	gboolean b = FALSE;
-	const char *s;
-	gint16 i16;
-	guint16 u16;
 	gboolean changed = FALSE;
-	gboolean metered;
-	guint32 max_rate, rate;
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
-	g_return_val_if_fail (properties, FALSE);
+	g_return_val_if_fail (bss_info, FALSE);
+	nm_assert (NM_IS_REF_STRING (bss_info->bss_path));
 
 	priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
-	g_object_freeze_notify (G_OBJECT (ap));
-
-	if (g_variant_lookup (properties, "Privacy", "b", &b) && b)
-		changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_PRIVACY);
-
-	v = g_variant_lookup_value (properties, "WPS", G_VARIANT_TYPE_VARDICT);
-	if (v) {
-		if (g_variant_lookup (v, "Type", "&s", &s)) {
-			changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS);
-			if (strcmp (s, "pbc") == 0)
-				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PBC);
-			else if (strcmp (s, "pin") == 0)
-				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PIN);
-		}
-		g_variant_unref (v);
-	}
-
-	if (g_variant_lookup (properties, "Mode", "&s", &s)) {
-		if (!g_strcmp0 (s, "infrastructure"))
-			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
-		else if (!g_strcmp0 (s, "ad-hoc"))
-			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_ADHOC);
-		else if (!g_strcmp0 (s, "mesh"))
-			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_MESH);
-	}
-
-	if (g_variant_lookup (properties, "Signal", "n", &i16))
-		changed |= nm_wifi_ap_set_strength (ap, nm_wifi_utils_level_to_quality (i16));
-
-	if (g_variant_lookup (properties, "Frequency", "q", &u16))
-		changed |= nm_wifi_ap_set_freq (ap, u16);
-
-	v = g_variant_lookup_value (properties, "SSID", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		len = MIN (32, len);
+	nm_assert (   !ap->_supplicant_path
+	           || ap->_supplicant_path == bss_info->bss_path);
 
-		/* Stupid ieee80211 layer uses <hidden> */
-		if (   bytes
-		    && len
-		    && !(   NM_IN_SET (len, 8, 9)
-		         && memcmp (bytes, "<hidden>", len) == 0)
-		    && !nm_utils_is_empty_ssid (bytes, len)) {
-			/* good */
-		} else
-			len = 0;
-
-		changed |= nm_wifi_ap_set_ssid_arr (ap, bytes, len);
-
-		g_variant_unref (v);
-	}
+	g_object_freeze_notify (G_OBJECT (ap));
 
-	v = g_variant_lookup_value (properties, "BSSID", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		if (   len == ETH_ALEN
-		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
-		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
-			changed |= nm_wifi_ap_set_address_bin (ap, bytes);
-		g_variant_unref (v);
+	if (!ap->_supplicant_path) {
+		ap->_supplicant_path = nm_ref_string_ref (bss_info->bss_path);
+		changed = TRUE;
 	}
 
-	max_rate = 0;
-	v = g_variant_lookup_value (properties, "Rates", G_VARIANT_TYPE ("au"));
-	if (v) {
-		const guint32 *rates = g_variant_get_fixed_array (v, &len, sizeof (guint32));
+	changed |= nm_wifi_ap_set_flags (ap, bss_info->ap_flags);
+	changed |= nm_wifi_ap_set_mode (ap, bss_info->mode);
+	changed |= nm_wifi_ap_set_strength (ap, bss_info->signal_percent);
+	changed |= nm_wifi_ap_set_freq (ap, bss_info->frequency);
+	changed |= nm_wifi_ap_set_ssid (ap, bss_info->ssid);
 
-		for (i = 0; i < len; i++)
-			max_rate = NM_MAX (max_rate, rates[i]);
-		g_variant_unref (v);
-	}
-	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		parse_ies (bytes, len, &rate, &metered);
-		max_rate = NM_MAX (max_rate, rate);
-		g_variant_unref (v);
-		priv->metered = metered;
+	if (bss_info->bssid_valid)
+		changed |= nm_wifi_ap_set_address_bin (ap, bss_info->bssid);
+	else {
+		/* we don't actually clear the value. */
 	}
 
-	if (max_rate)
-		changed |= nm_wifi_ap_set_max_bitrate (ap, max_rate / 1000);
+	changed |= nm_wifi_ap_set_max_bitrate (ap, bss_info->max_rate);
 
-	v = g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
-	if (v) {
-		changed |= nm_wifi_ap_set_wpa_flags (ap, priv->wpa_flags | security_from_vardict (v));
-		g_variant_unref (v);
+	if (priv->metered != bss_info->metered) {
+		priv->metered = bss_info->metered;
+		changed = TRUE;
 	}
 
-	v = g_variant_lookup_value (properties, "RSN", G_VARIANT_TYPE_VARDICT);
-	if (v) {
-		changed |= nm_wifi_ap_set_rsn_flags (ap, priv->rsn_flags | security_from_vardict (v));
-		g_variant_unref (v);
-	}
+	changed |= nm_wifi_ap_set_wpa_flags (ap, bss_info->wpa_flags);
+	changed |= nm_wifi_ap_set_rsn_flags (ap, bss_info->rsn_flags);
 
-	if (!priv->supplicant_path) {
-		priv->supplicant_path = g_strdup (supplicant_path);
-		changed = TRUE;
-	}
+	changed |= nm_wifi_ap_set_last_seen (ap, bss_info->last_seen_msec);
 
-	changed |= nm_wifi_ap_set_last_seen (ap, nm_utils_get_monotonic_timestamp_s ());
 	changed |= nm_wifi_ap_set_fake (ap, FALSE);
 
 	g_object_thaw_notify (G_OBJECT (ap));
@@ -984,20 +503,22 @@ const char *
 nm_wifi_ap_to_string (const NMWifiAP *self,
                       char *str_buf,
                       gulong buf_len,
-                      gint32 now_s)
+                      gint64 now_msec)
 {
 	const NMWifiAPPrivate *priv;
 	const char *supplicant_id = "-";
 	const char *export_path;
 	guint32 chan;
 	gs_free char *ssid_to_free = NULL;
+	char str_buf_ts[100];
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (self), NULL);
 
 	priv = NM_WIFI_AP_GET_PRIVATE (self);
+
 	chan = nm_utils_wifi_freq_to_channel (priv->freq);
-	if (priv->supplicant_path)
-		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
+	if (self->_supplicant_path)
+		supplicant_id = strrchr (self->_supplicant_path->str, '/') ?: supplicant_id;
 
 	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
 	if (export_path)
@@ -1005,8 +526,10 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 	else
 		export_path = "/";
 
+	nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
+
 	g_snprintf (str_buf, buf_len,
-	            "%17s %-35s [ %c %3u %3u%% %c%c W:%04X R:%04X ] %3us sup:%s [nm:%s]",
+	            "%17s %-35s [ %c %3u %3u%% %c%c W:%04X R:%04X ] %s sup:%s [nm:%s]",
 	            priv->address ?: "(none)",
 	            (ssid_to_free = _nm_utils_ssid_to_string (priv->ssid)),
 	            (priv->mode == NM_802_11_MODE_ADHOC
@@ -1024,7 +547,12 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 	            priv->metered ? 'M' : '_',
 	            priv->wpa_flags & 0xFFFF,
 	            priv->rsn_flags & 0xFFFF,
-	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,
+	              priv->last_seen_msec != G_MININT64
+	            ? nm_sprintf_buf (str_buf_ts,
+	                              "%3u.%03us",
+	                              (guint) ((now_msec - priv->last_seen_msec) / 1000),
+	                              (guint) ((now_msec - priv->last_seen_msec) % 1000))
+	            : "        ",
 	            supplicant_id,
 	            export_path);
 	return str_buf;
@@ -1177,9 +705,9 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_LAST_SEEN:
 		g_value_set_int (value,
-		                 priv->last_seen > 0
-		                     ? (int) nm_utils_monotonic_timestamp_as_boottime (priv->last_seen, NM_UTILS_NS_PER_SECOND)
-		                     : -1);
+		                   priv->last_seen_msec != G_MININT64
+		                 ? (int) NM_MAX (nm_utils_monotonic_timestamp_as_boottime (priv->last_seen_msec, NM_UTILS_NSEC_PER_MSEC) / 1000, 1)
+		                 : -1);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1204,26 +732,16 @@ nm_wifi_ap_init (NMWifiAP *self)
 	priv->flags = NM_802_11_AP_FLAGS_NONE;
 	priv->wpa_flags = NM_802_11_AP_SEC_NONE;
 	priv->rsn_flags = NM_802_11_AP_SEC_NONE;
-	priv->last_seen = -1;
+	priv->last_seen_msec = G_MININT64;
 }
 
 NMWifiAP *
-nm_wifi_ap_new_from_properties (const char *supplicant_path, GVariant *properties)
+nm_wifi_ap_new_from_properties (const NMSupplicantBssInfo *bss_info)
 {
 	NMWifiAP *ap;
 
-	g_return_val_if_fail (supplicant_path != NULL, NULL);
-	g_return_val_if_fail (properties != NULL, NULL);
-
-	ap = (NMWifiAP *) g_object_new (NM_TYPE_WIFI_AP, NULL);
-	nm_wifi_ap_update_from_properties (ap, supplicant_path, properties);
-
-	/* ignore APs with invalid or missing BSSIDs */
-	if (!nm_wifi_ap_get_address (ap)) {
-		g_object_unref (ap);
-		return NULL;
-	}
-
+	ap = g_object_new (NM_TYPE_WIFI_AP, NULL);
+	nm_wifi_ap_update_from_properties (ap, bss_info);
 	return ap;
 }
 
@@ -1349,7 +867,7 @@ finalize (GObject *object)
 	nm_assert (!self->wifi_device);
 	nm_assert (c_list_is_empty (&self->aps_lst));
 
-	g_free (priv->supplicant_path);
+	nm_ref_string_unref (self->_supplicant_path);
 	if (priv->ssid)
 		g_bytes_unref (priv->ssid);
 	g_free (priv->address);
@@ -1394,7 +912,8 @@ nm_wifi_ap_class_init (NMWifiAPClass *ap_class)
 	| NM_802_11_AP_SEC_GROUP_CCMP \
 	| NM_802_11_AP_SEC_KEY_MGMT_PSK \
 	| NM_802_11_AP_SEC_KEY_MGMT_802_1X \
-	| NM_802_11_AP_SEC_KEY_MGMT_SAE )
+	| NM_802_11_AP_SEC_KEY_MGMT_SAE \
+	| NM_802_11_AP_SEC_KEY_MGMT_OWE )
 
 	GObjectClass *object_class = G_OBJECT_CLASS (ap_class);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (ap_class);
@@ -1515,20 +1034,6 @@ nm_wifi_aps_find_first_compatible (const CList *aps_lst_head,
 	return NULL;
 }
 
-NMWifiAP *
-nm_wifi_aps_find_by_supplicant_path (const CList *aps_lst_head, const char *path)
-{
-	NMWifiAP *ap;
-
-	g_return_val_if_fail (path != NULL, NULL);
-
-	c_list_for_each_entry (ap, aps_lst_head, aps_lst) {
-		if (nm_streq0 (path, nm_wifi_ap_get_supplicant_path (ap)))
-			return ap;
-	}
-	return NULL;
-}
-
 /*****************************************************************************/
 
 NMWifiAP *
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 472dfdf9..1bf4e604 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -33,20 +33,21 @@ typedef struct {
 	NMDBusObject parent;
 	NMDevice *wifi_device;
 	CList aps_lst;
+	NMRefString *_supplicant_path;
 	struct _NMWifiAPPrivate *_priv;
 } NMWifiAP;
 
+struct _NMSupplicantBssInfo;
+
 typedef struct _NMWifiAPClass NMWifiAPClass;
 
 GType nm_wifi_ap_get_type (void);
 
-NMWifiAP *   nm_wifi_ap_new_from_properties      (const char *supplicant_path,
-                                                  GVariant *properties);
-NMWifiAP *   nm_wifi_ap_new_fake_from_connection (NMConnection *connection);
+NMWifiAP *nm_wifi_ap_new_from_properties (const struct _NMSupplicantBssInfo *bss_info);
+NMWifiAP *nm_wifi_ap_new_fake_from_connection (NMConnection *connection);
 
-gboolean          nm_wifi_ap_update_from_properties   (NMWifiAP *ap,
-                                                       const char *supplicant_path,
-                                                       GVariant *properties);
+gboolean nm_wifi_ap_update_from_properties (NMWifiAP *ap,
+                                            const struct _NMSupplicantBssInfo *bss_info);
 
 gboolean          nm_wifi_ap_check_compatible         (NMWifiAP *self,
                                                        NMConnection *connection);
@@ -56,7 +57,14 @@ gboolean          nm_wifi_ap_complete_connection      (NMWifiAP *self,
                                                        gboolean lock_bssid,
                                                        GError **error);
 
-const char *      nm_wifi_ap_get_supplicant_path      (NMWifiAP *ap);
+static inline NMRefString *
+nm_wifi_ap_get_supplicant_path (NMWifiAP *ap)
+{
+	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);
+
+	return ap->_supplicant_path;
+}
+
 GBytes           *nm_wifi_ap_get_ssid                 (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_ssid_arr             (NMWifiAP *ap,
                                                        const guint8 *ssid,
@@ -86,7 +94,7 @@ gboolean          nm_wifi_ap_get_metered              (const NMWifiAP *self);
 const char       *nm_wifi_ap_to_string                (const NMWifiAP *self,
                                                        char *str_buf,
                                                        gulong buf_len,
-                                                       gint32 now_s);
+                                                       gint64 now_msec);
 
 const char      **nm_wifi_aps_get_paths        (const CList *aps_lst_head,
                                                 gboolean include_without_ssid);
@@ -94,8 +102,6 @@ const char      **nm_wifi_aps_get_paths        (const CList *aps_lst_head,
 NMWifiAP         *nm_wifi_aps_find_first_compatible (const CList *aps_lst_head,
                                                      NMConnection *connection);
 
-NMWifiAP         *nm_wifi_aps_find_by_supplicant_path (const CList *aps_lst_head, const char *path);
-
 NMWifiAP         *nm_wifi_ap_lookup_for_device (NMDevice *device, const char *exported_path);
 
 #endif /* __NM_WIFI_AP_H__ */
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index 821460a5..9b87bbbf 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -95,7 +95,7 @@ create_device (NMDeviceFactory *factory,
 	            iface,
 	            NM_PRINT_FMT_QUOTE_STRING (backend),
 	            WITH_IWD ? " (iwd support enabled)" : "");
-	if (!backend || !strcasecmp (backend, "wpa_supplicant")) {
+	if (!backend || !g_ascii_strcasecmp (backend, "wpa_supplicant")) {
 		NMDevice *device;
 		NMDeviceWifiCapabilities capabilities;
 		NM80211Mode mode;
@@ -129,7 +129,7 @@ create_device (NMDeviceFactory *factory,
 		return device;
 	}
 #if WITH_IWD
-	else if (!strcasecmp (backend, "iwd"))
+	else if (!g_ascii_strcasecmp (backend, "iwd"))
 		return nm_device_iwd_new (iface);
 #endif
 
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.c b/src/devices/wifi/nm-wifi-p2p-peer.c
index f8da0046..c58bd95e 100644
--- a/src/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/devices/wifi/nm-wifi-p2p-peer.c
@@ -9,15 +9,16 @@
 
 #include <stdlib.h>
 
-#include "nm-setting-wireless.h"
-
-#include "nm-wifi-utils.h"
 #include "NetworkManagerUtils.h"
-#include "nm-utils.h"
-#include "nm-core-internal.h"
-#include "platform/nm-platform.h"
 #include "devices/nm-device.h"
+#include "nm-core-internal.h"
 #include "nm-dbus-manager.h"
+#include "nm-glib-aux/nm-ref-string.h"
+#include "nm-setting-wireless.h"
+#include "nm-utils.h"
+#include "nm-wifi-utils.h"
+#include "platform/nm-platform.h"
+#include "supplicant/nm-supplicant-types.h"
 
 /*****************************************************************************/
 
@@ -28,7 +29,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMWifiP2PPeer,
 	PROP_MODEL_NUMBER,
 	PROP_SERIAL,
 	PROP_WFD_IES,
-	PROP_GROUPS,
 	PROP_HW_ADDRESS,
 	PROP_STRENGTH,
 	PROP_LAST_SEEN,
@@ -36,7 +36,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMWifiP2PPeer,
 );
 
 struct _NMWifiP2PPeerPrivate {
-	char *supplicant_path;   /* D-Bus object path of this Peer from wpa_supplicant */
+	NMRefString *supplicant_path;   /* D-Bus object path of this Peer from wpa_supplicant */
 
 	/* Scanned or cached values */
 	char *             name;
@@ -55,7 +55,7 @@ struct _NMWifiP2PPeerPrivate {
 	NM80211ApFlags     flags;      /* General flags */
 
 	/* Non-scanned attributes */
-	gint32             last_seen;    /* Timestamp when the Peer was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+	gint32             last_seen;    /* Timestamp when the Peer was seen lastly (obtained via nm_utils_get_monotonic_timestamp_sec()) */
 };
 
 typedef struct _NMWifiP2PPeerPrivate NMWifiP2PPeerPrivate;
@@ -151,7 +151,7 @@ nm_wifi_p2p_peer_get_supplicant_path (NMWifiP2PPeer *peer)
 {
 	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
 
-	return NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->supplicant_path;
+	return nm_ref_string_get_str (NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->supplicant_path);
 }
 
 const char *
@@ -163,18 +163,12 @@ nm_wifi_p2p_peer_get_name (const NMWifiP2PPeer *peer)
 }
 
 gboolean
-nm_wifi_p2p_peer_set_name (NMWifiP2PPeer *peer, const char *name)
+nm_wifi_p2p_peer_set_name (NMWifiP2PPeer *peer, const char *str)
 {
 	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
-	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
 
-	if (g_strcmp0 (name, priv->name) == 0)
+	if (!nm_utils_strdup_reset (&priv->name, str))
 		return FALSE;
-
-	g_clear_pointer (&priv->name, g_free);
-	if (name)
-		priv->name = g_strdup (name);
-
 	_notify (peer, PROP_NAME);
 	return TRUE;
 }
@@ -188,18 +182,12 @@ nm_wifi_p2p_peer_get_manufacturer (const NMWifiP2PPeer *peer)
 }
 
 gboolean
-nm_wifi_p2p_peer_set_manufacturer (NMWifiP2PPeer *peer, const char *manufacturer)
+nm_wifi_p2p_peer_set_manufacturer (NMWifiP2PPeer *peer, const char *str)
 {
 	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
-	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
 
-	if (g_strcmp0 (manufacturer, priv->manufacturer) == 0)
+	if (!nm_utils_strdup_reset (&priv->manufacturer, str))
 		return FALSE;
-
-	g_clear_pointer (&priv->manufacturer, g_free);
-	if (manufacturer)
-		priv->manufacturer = g_strdup (manufacturer);
-
 	_notify (peer, PROP_MANUFACTURER);
 	return TRUE;
 }
@@ -213,18 +201,12 @@ nm_wifi_p2p_peer_get_model (const NMWifiP2PPeer *peer)
 }
 
 gboolean
-nm_wifi_p2p_peer_set_model (NMWifiP2PPeer *peer, const char *model)
+nm_wifi_p2p_peer_set_model (NMWifiP2PPeer *peer, const char *str)
 {
 	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
-	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
 
-	if (g_strcmp0 (model, priv->model) == 0)
+	if (!nm_utils_strdup_reset (&priv->model, str))
 		return FALSE;
-
-	g_clear_pointer (&priv->model, g_free);
-	if (model)
-		priv->model = g_strdup (model);
-
 	_notify (peer, PROP_MODEL);
 	return TRUE;
 }
@@ -238,18 +220,12 @@ nm_wifi_p2p_peer_get_model_number (const NMWifiP2PPeer *peer)
 }
 
 gboolean
-nm_wifi_p2p_peer_set_model_number (NMWifiP2PPeer *peer, const char *model_number)
+nm_wifi_p2p_peer_set_model_number (NMWifiP2PPeer *peer, const char *str)
 {
 	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
-	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
 
-	if (g_strcmp0 (model_number, priv->model_number) == 0)
+	if (!nm_utils_strdup_reset (&priv->model_number, str))
 		return FALSE;
-
-	g_clear_pointer (&priv->model_number, g_free);
-	if (model_number)
-		priv->model_number = g_strdup (model_number);
-
 	_notify (peer, PROP_MODEL_NUMBER);
 	return TRUE;
 }
@@ -263,18 +239,12 @@ nm_wifi_p2p_peer_get_serial (const NMWifiP2PPeer *peer)
 }
 
 gboolean
-nm_wifi_p2p_peer_set_serial (NMWifiP2PPeer *peer, const char *serial)
+nm_wifi_p2p_peer_set_serial (NMWifiP2PPeer *peer, const char *str)
 {
 	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
-	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
 
-	if (g_strcmp0 (serial, priv->serial) == 0)
+	if (!nm_utils_strdup_reset (&priv->serial, str))
 		return FALSE;
-
-	g_clear_pointer (&priv->serial, g_free);
-	if (serial)
-		priv->serial = g_strdup (serial);
-
 	_notify (peer, PROP_SERIAL);
 	return TRUE;
 }
@@ -291,6 +261,7 @@ gboolean
 nm_wifi_p2p_peer_set_wfd_ies (NMWifiP2PPeer *peer, GBytes *wfd_ies)
 {
 	NMWifiP2PPeerPrivate *priv;
+	gs_unref_bytes GBytes *wfd_ies_old = NULL;
 
 	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
 
@@ -299,7 +270,7 @@ nm_wifi_p2p_peer_set_wfd_ies (NMWifiP2PPeer *peer, GBytes *wfd_ies)
 	if (nm_gbytes_equal0 (priv->wfd_ies, wfd_ies))
 		return FALSE;
 
-	g_bytes_unref (priv->wfd_ies);
+	wfd_ies_old = g_steal_pointer (&priv->wfd_ies);
 	priv->wfd_ies = wfd_ies ? g_bytes_ref (wfd_ies) : NULL;
 
 	_notify (peer, PROP_WFD_IES);
@@ -311,27 +282,7 @@ nm_wifi_p2p_peer_get_groups (const NMWifiP2PPeer *peer)
 {
 	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), NULL);
 
-	return (const char * const*) NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->groups;
-}
-
-static gboolean
-nm_wifi_p2p_peer_set_groups (NMWifiP2PPeer *peer, const char** groups)
-{
-	NMWifiP2PPeerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
-	g_return_val_if_fail (groups != NULL, FALSE);
-
-	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
-
-	if (_nm_utils_strv_equal (priv->groups, (char **) groups))
-		return FALSE;
-
-	g_strfreev (priv->groups);
-	priv->groups = g_strdupv ((char**) groups);
-
-	_notify (peer, PROP_GROUPS);
-	return TRUE;
+	return (const char *const*) NM_WIFI_P2P_PEER_GET_PRIVATE (peer)->groups;
 }
 
 const char *
@@ -382,11 +333,7 @@ nm_wifi_p2p_peer_get_strength (NMWifiP2PPeer *peer)
 gboolean
 nm_wifi_p2p_peer_set_strength (NMWifiP2PPeer *peer, const gint8 strength)
 {
-	NMWifiP2PPeerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
-
-	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
+	NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
 
 	if (priv->strength != strength) {
 		priv->strength = strength;
@@ -426,80 +373,42 @@ nm_wifi_p2p_peer_set_last_seen (NMWifiP2PPeer *peer, gint32 last_seen)
 
 gboolean
 nm_wifi_p2p_peer_update_from_properties (NMWifiP2PPeer *peer,
-                                         const char *supplicant_path,
-                                         GVariant *properties)
+                                         const NMSupplicantPeerInfo *peer_info)
 {
 	NMWifiP2PPeerPrivate *priv;
-	const guint8 *bytes;
-	GVariant *v;
-	gsize len;
-	const char *s;
-	const char **sv;
-	gint32 i32;
 	gboolean changed = FALSE;
 
 	g_return_val_if_fail (NM_IS_WIFI_P2P_PEER (peer), FALSE);
-	g_return_val_if_fail (properties, FALSE);
+	g_return_val_if_fail (peer_info, FALSE);
+	nm_assert (NM_IS_REF_STRING (peer_info->peer_path));
 
 	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (peer);
 
-	g_object_freeze_notify (G_OBJECT (peer));
-
-	if (g_variant_lookup (properties, "level", "i", &i32))
-		changed |= nm_wifi_p2p_peer_set_strength (peer, nm_wifi_utils_level_to_quality (i32));
-
-	if (g_variant_lookup (properties, "DeviceName", "&s", &s))
-		changed |= nm_wifi_p2p_peer_set_name (peer, s);
-
-	if (g_variant_lookup (properties, "Manufacturer", "&s", &s))
-		changed |= nm_wifi_p2p_peer_set_manufacturer (peer, s);
-
-	if (g_variant_lookup (properties, "Model", "&s", &s))
-		changed |= nm_wifi_p2p_peer_set_model (peer, s);
-
-	if (g_variant_lookup (properties, "ModelNumber", "&s", &s))
-		changed |= nm_wifi_p2p_peer_set_model_number (peer, s);
-
-	if (g_variant_lookup (properties, "Serial", "&s", &s))
-		changed |= nm_wifi_p2p_peer_set_serial (peer, s);
+	nm_assert (   !priv->supplicant_path
+	           || priv->supplicant_path == peer_info->peer_path);
 
-	v = g_variant_lookup_value (properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		if (   len == ETH_ALEN
-		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
-		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
-			changed |= nm_wifi_p2p_peer_set_address_bin (peer, bytes);
-		g_variant_unref (v);
-	}
-
-	/* The IEs property contains the WFD R1 subelements */
-	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		gs_unref_bytes GBytes *b = NULL;
-
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		b = g_bytes_new (bytes, len);
-		changed |= nm_wifi_p2p_peer_set_wfd_ies (peer, b);
-		g_variant_unref (v);
-	}
-
-	v = g_variant_lookup_value (properties, "Groups", G_VARIANT_TYPE_OBJECT_PATH_ARRAY);
-	if (v) {
-		sv = g_variant_get_objv (v, NULL);
-		changed |= nm_wifi_p2p_peer_set_groups (peer, sv);
-		g_free (sv);
-	}
-
-	/*if (max_rate)
-		changed |= nm_wifi_p2p_peer_set_max_bitrate (peer, max_rate / 1000);*/
+	g_object_freeze_notify (G_OBJECT (peer));
 
 	if (!priv->supplicant_path) {
-		priv->supplicant_path = g_strdup (supplicant_path);
+		priv->supplicant_path = nm_ref_string_ref (peer_info->peer_path);
 		changed = TRUE;
 	}
 
-	changed |= nm_wifi_p2p_peer_set_last_seen (peer, nm_utils_get_monotonic_timestamp_s ());
+	changed |= nm_wifi_p2p_peer_set_strength (peer, peer_info->signal_percent);
+	changed |= nm_wifi_p2p_peer_set_name (peer, peer_info->device_name);
+	changed |= nm_wifi_p2p_peer_set_manufacturer (peer, peer_info->manufacturer);
+	changed |= nm_wifi_p2p_peer_set_model (peer, peer_info->model);
+	changed |= nm_wifi_p2p_peer_set_model_number (peer, peer_info->model_number);
+	changed |= nm_wifi_p2p_peer_set_serial (peer, peer_info->serial);
+
+	if (peer_info->address_valid)
+		changed |= nm_wifi_p2p_peer_set_address_bin (peer, peer_info->address);
+	else {
+		/* we don't reset the address. */
+	}
+
+	changed |= nm_wifi_p2p_peer_set_wfd_ies (peer, peer_info->ies);
+	changed |= nm_wifi_p2p_peer_set_last_seen (peer, peer_info->last_seen_msec / 1000u);
 
 	g_object_thaw_notify (G_OBJECT (peer));
 
@@ -521,7 +430,7 @@ nm_wifi_p2p_peer_to_string (const NMWifiP2PPeer *self,
 	priv = NM_WIFI_P2P_PEER_GET_PRIVATE (self);
 
 	if (priv->supplicant_path)
-		supplicant_id = strrchr (priv->supplicant_path, '/') ?: supplicant_id;
+		supplicant_id = strrchr (priv->supplicant_path->str, '/') ?: supplicant_id;
 
 	export_path = nm_dbus_object_get_path (NM_DBUS_OBJECT (self));
 	if (export_path)
@@ -537,7 +446,7 @@ nm_wifi_p2p_peer_to_string (const NMWifiP2PPeer *self,
 	            priv->model,
 	            priv->model_number,
 	            priv->serial,
-	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_s ()) - priv->last_seen) : -1,
+	            priv->last_seen > 0 ? ((now_s > 0 ? now_s : nm_utils_get_monotonic_timestamp_sec ()) - priv->last_seen) : -1,
 	            supplicant_id,
 	            export_path);
 
@@ -601,12 +510,6 @@ get_property (GObject *object, guint prop_id,
 	case PROP_WFD_IES:
 		g_value_take_variant (value, nm_utils_gbytes_to_variant_ay (priv->wfd_ies));
 		break;
-	case PROP_GROUPS:
-		g_value_set_variant (value,
-		                      g_variant_new_strv (   (const char*const*) priv->groups
-		                                          ?: NM_PTRARRAY_EMPTY (const char *),
-		                                          -1));
-		break;
 	case PROP_HW_ADDRESS:
 		g_value_set_string (value, priv->address);
 		break;
@@ -616,7 +519,7 @@ get_property (GObject *object, guint prop_id,
 	case PROP_LAST_SEEN:
 		g_value_set_int (value,
 		                 priv->last_seen > 0
-		                     ? (int) nm_utils_monotonic_timestamp_as_boottime (priv->last_seen, NM_UTILS_NS_PER_SECOND)
+		                     ? (int) nm_utils_monotonic_timestamp_as_boottime (priv->last_seen, NM_UTILS_NSEC_PER_SEC)
 		                     : -1);
 		break;
 	default:
@@ -643,22 +546,14 @@ nm_wifi_p2p_peer_init (NMWifiP2PPeer *self)
 }
 
 NMWifiP2PPeer *
-nm_wifi_p2p_peer_new_from_properties (const char *supplicant_path, GVariant *properties)
+nm_wifi_p2p_peer_new_from_properties (const NMSupplicantPeerInfo *peer_info)
 {
 	NMWifiP2PPeer *peer;
 
-	g_return_val_if_fail (supplicant_path != NULL, NULL);
-	g_return_val_if_fail (properties != NULL, NULL);
-
-	peer = (NMWifiP2PPeer *) g_object_new (NM_TYPE_WIFI_P2P_PEER, NULL);
-	nm_wifi_p2p_peer_update_from_properties (peer, supplicant_path, properties);
-
-	/* ignore peers with invalid or missing address */
-	if (!nm_wifi_p2p_peer_get_address (peer)) {
-		g_object_unref (peer);
-		return NULL;
-	}
+	g_return_val_if_fail (peer_info, NULL);
 
+	peer = g_object_new (NM_TYPE_WIFI_P2P_PEER, NULL);
+	nm_wifi_p2p_peer_update_from_properties (peer, peer_info);
 	return peer;
 }
 
@@ -671,7 +566,7 @@ finalize (GObject *object)
 	nm_assert (!self->wifi_device);
 	nm_assert (c_list_is_empty (&self->peers_lst));
 
-	g_free (priv->supplicant_path);
+	nm_ref_string_unref (priv->supplicant_path);
 	g_free (priv->name);
 	g_free (priv->manufacturer);
 	g_free (priv->model);
@@ -688,6 +583,7 @@ static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
 	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
 		NM_DBUS_INTERFACE_WIFI_P2P_PEER,
 		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			/* Before 1.24, we wrongly exposed a property "Groups" of type "as". Don't reuse that property name. */
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Flags",        "u",  NM_WIFI_P2P_PEER_FLAGS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Name",         "s",  NM_WIFI_P2P_PEER_NAME),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Manufacturer", "s",  NM_WIFI_P2P_PEER_MANUFACTURER),
@@ -695,7 +591,6 @@ static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("ModelNumber",  "s",  NM_WIFI_P2P_PEER_MODEL_NUMBER),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Serial",       "s",  NM_WIFI_P2P_PEER_SERIAL),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("WfdIEs",       "ay", NM_WIFI_P2P_PEER_WFD_IES),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Groups",       "as", NM_WIFI_P2P_PEER_GROUPS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress",    "s",  NM_WIFI_P2P_PEER_HW_ADDRESS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Strength",     "y",  NM_WIFI_P2P_PEER_STRENGTH),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("LastSeen",     "i",  NM_WIFI_P2P_PEER_LAST_SEEN),
@@ -756,12 +651,6 @@ nm_wifi_p2p_peer_class_init (NMWifiP2PPeerClass *klass)
 	                          NULL,
 	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_GROUPS] =
-	    g_param_spec_variant (NM_WIFI_P2P_PEER_GROUPS, "", "",
-	                          G_VARIANT_TYPE ("as"),
-	                          NULL,
-	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
-
 	obj_properties[PROP_HW_ADDRESS] =
 	    g_param_spec_string (NM_WIFI_P2P_PEER_HW_ADDRESS, "", "",
 	                         NULL,
diff --git a/src/devices/wifi/nm-wifi-p2p-peer.h b/src/devices/wifi/nm-wifi-p2p-peer.h
index 07f25cc1..59f9c5d2 100644
--- a/src/devices/wifi/nm-wifi-p2p-peer.h
+++ b/src/devices/wifi/nm-wifi-p2p-peer.h
@@ -24,7 +24,6 @@
 #define NM_WIFI_P2P_PEER_MODEL_NUMBER         "model-number"
 #define NM_WIFI_P2P_PEER_SERIAL               "serial"
 #define NM_WIFI_P2P_PEER_WFD_IES              "wfd-ies"
-#define NM_WIFI_P2P_PEER_GROUPS               "groups"
 #define NM_WIFI_P2P_PEER_HW_ADDRESS           "hw-address"
 #define NM_WIFI_P2P_PEER_STRENGTH             "strength"
 #define NM_WIFI_P2P_PEER_LAST_SEEN            "last-seen"
@@ -38,14 +37,14 @@ typedef struct {
 
 typedef struct _NMWifiP2PPeerClass NMWifiP2PPeerClass;
 
+struct _NMSupplicantPeerInfo;
+
 GType nm_wifi_p2p_peer_get_type (void);
 
-NMWifiP2PPeer *   nm_wifi_p2p_peer_new_from_properties      (const char *supplicant_path,
-                                                             GVariant *properties);
+NMWifiP2PPeer *nm_wifi_p2p_peer_new_from_properties (const struct _NMSupplicantPeerInfo *peer_info);
 
-gboolean          nm_wifi_p2p_peer_update_from_properties   (NMWifiP2PPeer *peer,
-                                                             const char *supplicant_path,
-                                                             GVariant *properties);
+gboolean nm_wifi_p2p_peer_update_from_properties (NMWifiP2PPeer *peer,
+                                                  const struct _NMSupplicantPeerInfo *peer_info);
 
 gboolean          nm_wifi_p2p_peer_check_compatible         (NMWifiP2PPeer *self,
                                                              NMConnection *connection);
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index b9b7ec42..0903dc8b 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -759,6 +759,12 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae",
 		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
 		              NULL);
+	} else if (   (key_mgmt && !strcmp (key_mgmt, "owe"))
+	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_OWE)) {
+		g_object_set (s_wsec,
+		              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe",
+		              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
+		              NULL);
 	} else if (   (key_mgmt && !strcmp (key_mgmt, "wpa-psk"))
 	           || (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)
 	           || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK)) {
@@ -780,25 +786,6 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 	return TRUE;
 }
 
-guint32
-nm_wifi_utils_level_to_quality (int val)
-{
-	if (val < 0) {
-		/* Assume dBm already; rough conversion: best = -40, worst = -100 */
-		val = abs (CLAMP (val, -100, -40) + 40);  /* normalize to 0 */
-		val = 100 - (int) ((100.0 * (double) val) / 60.0);
-	} else if (val > 110 && val < 256) {
-		/* assume old-style WEXT 8-bit unsigned signal level */
-		val -= 256;  /* subtract 256 to convert to dBm */
-		val = abs (CLAMP (val, -100, -40) + 40);  /* normalize to 0 */
-		val = 100 - (int) ((100.0 * (double) val) / 60.0);
-	} else {
-		/* Assume signal is a "quality" percentage */
-	}
-
-	return CLAMP (val, 0, 100);
-}
-
 gboolean
 nm_wifi_utils_is_manf_default_ssid (GBytes *ssid)
 {
diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h
index 982080b9..505baa84 100644
--- a/src/devices/wifi/nm-wifi-utils.h
+++ b/src/devices/wifi/nm-wifi-utils.h
@@ -30,8 +30,6 @@ gboolean nm_wifi_utils_complete_connection (GBytes *ssid,
                                             gboolean lock_bssid,
                                             GError **error);
 
-guint32 nm_wifi_utils_level_to_quality (int val);
-
 gboolean nm_wifi_utils_is_manf_default_ssid (GBytes *ssid);
 
 NMIwdNetworkSecurity nm_wifi_connection_get_iwd_security (NMConnection *connection,
diff --git a/src/devices/wifi/tests/test-devices-wifi.c b/src/devices/wifi/tests/test-devices-wifi.c
index a960e7a2..bde36307 100644
--- a/src/devices/wifi/tests/test-devices-wifi.c
+++ b/src/devices/wifi/tests/test-devices-wifi.c
@@ -6,6 +6,7 @@
 #include "nm-default.h"
 
 #include "devices/wifi/nm-wifi-utils.h"
+#include "devices/wifi/nm-device-wifi.h"
 #include "nm-core-internal.h"
 
 #include "nm-test-utils-core.h"
@@ -1337,6 +1338,60 @@ test_strength_all (void)
 
 /*****************************************************************************/
 
+static void
+do_test_ssids_options_to_ptrarray (const char *const*ssids)
+{
+	GVariantBuilder builder;
+	gs_unref_variant GVariant *variant = NULL;
+	gs_unref_ptrarray GPtrArray *ssids_arr = NULL;
+	gs_free_error GError *error = NULL;
+	gsize len;
+	gsize i;
+
+	g_assert (ssids);
+
+	len = NM_PTRARRAY_LEN (ssids);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay"));
+	for (i = 0; i < len; i++) {
+		const char *ssid = ssids[i];
+
+		g_variant_builder_add (&builder,
+		                       "@ay",
+		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ssid, strlen (ssid), 1));
+	}
+	variant = g_variant_builder_end (&builder);
+
+	if (nmtst_get_rand_bool ())
+		g_variant_ref_sink (variant);
+
+	ssids_arr = nmtst_ssids_options_to_ptrarray (variant, &error);
+	g_assert (!error);
+	if (len == 0) {
+		g_assert (!ssids_arr);
+		return;
+	}
+	g_assert_cmpint (len, ==, ssids_arr->len);
+	for (i = 0; i < len; i++) {
+		const char *ssid = ssids[i];
+		GBytes *bytes = ssids_arr->pdata[i];
+
+		g_assert (nm_utils_gbytes_equal_mem (bytes,
+		                                     ssid,
+		                                     strlen (ssid)));
+	}
+}
+
+static void
+test_ssids_options_to_ptrarray (void)
+{
+	do_test_ssids_options_to_ptrarray (NM_PTRARRAY_EMPTY (const char *));
+	do_test_ssids_options_to_ptrarray (NM_MAKE_STRV ("ab"));
+	do_test_ssids_options_to_ptrarray (NM_MAKE_STRV ("ab", "cd", "fsdfdsf"));
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -1501,5 +1556,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/wifi/strength/all",
 	                 test_strength_all);
 
+	g_test_add_func ("/wifi/ssids_options_to_ptrarray", test_ssids_options_to_ptrarray);
+
 	return g_test_run ();
 }
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
index ed6f8010..97252edc 100644
--- a/src/devices/wwan/meson.build
+++ b/src/devices/wwan/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 nm_service_providers_source = files('nm-service-providers.c')
 
 sources = nm_service_providers_source + files(
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 3de5ae07..a2f2e7a6 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -156,7 +156,7 @@ modem_prepare_result (NMModem *modem,
 	}
 
 	priv->stage1_state = NM_DEVICE_STAGE_STATE_COMPLETED;
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -191,7 +191,7 @@ modem_auth_result (NMModem *modem, GError *error, gpointer user_data)
 	}
 
 	priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT;
-	nm_device_activate_schedule_stage1_device_prepare (device);
+	nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
 }
 
 static void
@@ -384,6 +384,15 @@ modem_state_cb (NMModem *modem,
 		 */
 		nm_modem_set_mm_enabled (priv->modem, priv->rf_enabled);
 
+		if (dev_state == NM_DEVICE_STATE_NEED_AUTH) {
+			/* The modem was unlocked externally to NetworkManager,
+			   deactivate so the default connection can be
+			   automatically activated again */
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_DEACTIVATING,
+			                         NM_DEVICE_STATE_REASON_MODEM_AVAILABLE);
+		}
+
 		/* Now allow connections without a PIN to be available */
 		nm_device_recheck_available_connections (device);
 	}
@@ -605,7 +614,6 @@ static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
 	nm_modem_act_stage2_config (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem);
-
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -843,9 +851,9 @@ dispose (GObject *object)
 		nm_clear_pointer (&priv->modem, nm_modem_unclaim);
 	}
 
-	g_clear_pointer (&priv->device_id, g_free);
-	g_clear_pointer (&priv->operator_code, g_free);
-	g_clear_pointer (&priv->apn, g_free);
+	nm_clear_g_free (&priv->device_id);
+	nm_clear_g_free (&priv->operator_code);
+	nm_clear_g_free (&priv->apn);
 
 	G_OBJECT_CLASS (nm_device_modem_parent_class)->dispose (object);
 }
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 947aa4c8..f89ec86c 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -324,7 +324,7 @@ connect_context_clear (NMModemBroadband *self)
 		ConnectContext *ctx = self->_priv.ctx;
 
 		g_clear_error (&ctx->first_error);
-		g_clear_pointer (&ctx->ip_types, g_array_unref);
+		nm_clear_pointer (&ctx->ip_types, g_array_unref);
 		nm_clear_g_cancellable (&ctx->cancellable);
 		g_clear_object (&ctx->connection);
 		g_clear_object (&ctx->connect_properties);
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index b68cd7e8..2fc44881 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -449,7 +449,7 @@ handle_sim_iface (NMModemOfono *self, gboolean found)
 			g_signal_handlers_disconnect_by_data (priv->sim_proxy, self);
 			g_clear_object (&priv->sim_proxy);
 		}
-		g_clear_pointer (&priv->imsi, g_free);
+		nm_clear_g_free (&priv->imsi);
 		update_modem_state (self);
 	} else if (found && (!priv->sim_proxy && !priv->sim_proxy_cancellable)) {
 		_LOGI ("found new SimManager interface");
@@ -769,7 +769,7 @@ stage1_prepare_done (GObject *source,
 
 	g_clear_object (&priv->context_proxy_cancellable);
 
-	g_clear_pointer (&priv->connect_properties, g_hash_table_destroy);
+	nm_clear_pointer (&priv->connect_properties, g_hash_table_destroy);
 
 	if (error) {
 		_LOGW ("connection failed: %s", error->message);
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index ad6449b1..bedc3844 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -975,7 +975,8 @@ modem_secrets_cb (NMActRequest *req,
 
 	priv->secrets_id = NULL;
 
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
+	    g_error_matches (error, NM_AGENT_MANAGER_ERROR, NM_AGENT_MANAGER_ERROR_NO_SECRETS))
 		return;
 
 	if (error)
@@ -1710,7 +1711,7 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (object);
 	const char *s;
 
 	switch (prop_id) {
@@ -1747,7 +1748,7 @@ set_property (GObject *object, guint prop_id,
 		priv->ip_types = g_value_get_uint (value);
 		break;
 	case PROP_SIM_OPERATOR_ID:
-		g_clear_pointer (&priv->sim_operator_id, g_free);
+		nm_clear_g_free (&priv->sim_operator_id);
 		s = g_value_get_string (value);
 		if (s && s[0])
 			priv->sim_operator_id = g_strdup (s);
@@ -1796,7 +1797,7 @@ constructed (GObject *object)
 static void
 dispose (GObject *object)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (object);
 
 	g_clear_object (&priv->act_request);
 
@@ -1806,7 +1807,7 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (object);
 
 	g_free (priv->uid);
 	g_free (priv->path);
diff --git a/src/devices/wwan/nm-service-providers.c b/src/devices/wwan/nm-service-providers.c
index 5140f7ed..5694c99e 100644
--- a/src/devices/wwan/nm-service-providers.c
+++ b/src/devices/wwan/nm-service-providers.c
@@ -118,11 +118,11 @@ parser_gsm_start (ParseContext *parse_context,
 		}
 	} else if (strcmp (name, "apn") == 0) {
 		parse_context->found_internet_apn = FALSE;
-		g_clear_pointer (&parse_context->apn, g_free);
-		g_clear_pointer (&parse_context->username, g_free);
-		g_clear_pointer (&parse_context->password, g_free);
-		g_clear_pointer (&parse_context->gateway, g_free);
-		g_clear_pointer (&parse_context->auth_method, g_free);
+		nm_clear_g_free (&parse_context->apn);
+		nm_clear_g_free (&parse_context->username);
+		nm_clear_g_free (&parse_context->password);
+		nm_clear_g_free (&parse_context->gateway);
+		nm_clear_g_free (&parse_context->auth_method);
 		g_slist_free_full (parse_context->dns, g_free);
 		parse_context->dns = NULL;
 
@@ -155,7 +155,7 @@ parser_gsm_apn_start (ParseContext *parse_context,
 	} else if (strcmp (name, "authentication") == 0) {
 		for (i = 0; attribute_names && attribute_names[i]; i++) {
 			if (strcmp (attribute_names[i], "method") == 0) {
-				g_clear_pointer (&parse_context->auth_method, g_free);
+				nm_clear_g_free (&parse_context->auth_method);
 				parse_context->auth_method = g_strstrip (g_strdup (attribute_values[i]));
 				break;
 			}
@@ -173,7 +173,7 @@ parser_start_element (GMarkupParseContext *context,
 {
 	ParseContext *parse_context = user_data;
 
-	g_clear_pointer (&parse_context->text_buffer, g_free);
+	nm_clear_g_free (&parse_context->text_buffer);
 
 	switch (parse_context->state) {
 	case PARSER_TOPLEVEL:
@@ -205,7 +205,7 @@ parser_country_end (ParseContext *parse_context,
                     const char *name)
 {
 	if (strcmp (name, "country") == 0) {
-		g_clear_pointer (&parse_context->text_buffer, g_free);
+		nm_clear_g_free (&parse_context->text_buffer);
 		parse_context->state = PARSER_TOPLEVEL;
 	}
 }
@@ -215,7 +215,7 @@ parser_provider_end (ParseContext *parse_context,
                      const char *name)
 {
 	if (strcmp (name, "provider") == 0) {
-		g_clear_pointer (&parse_context->text_buffer, g_free);
+		nm_clear_g_free (&parse_context->text_buffer);
 		parse_context->state = PARSER_COUNTRY;
 	}
 }
@@ -225,7 +225,7 @@ parser_gsm_end (ParseContext *parse_context,
                 const char *name)
 {
 	if (strcmp (name, "gsm") == 0) {
-		g_clear_pointer (&parse_context->text_buffer, g_free);
+		nm_clear_g_free (&parse_context->text_buffer);
 		parse_context->state = PARSER_PROVIDER;
 	}
 }
@@ -235,19 +235,19 @@ parser_gsm_apn_end (ParseContext *parse_context,
                     const char *name)
 {
 	if (strcmp (name, "username") == 0) {
-		g_clear_pointer (&parse_context->username, g_free);
+		nm_clear_g_free (&parse_context->username);
 		parse_context->username = g_steal_pointer (&parse_context->text_buffer);
 	} else if (strcmp (name, "password") == 0) {
-		g_clear_pointer (&parse_context->password, g_free);
+		nm_clear_g_free (&parse_context->password);
 		parse_context->password = g_steal_pointer (&parse_context->text_buffer);
 	} else if (strcmp (name, "dns") == 0) {
 		parse_context->dns = g_slist_prepend (parse_context->dns,
 		                                   g_steal_pointer (&parse_context->text_buffer));
 	} else if (strcmp (name, "gateway") == 0) {
-		g_clear_pointer (&parse_context->gateway, g_free);
+		nm_clear_g_free (&parse_context->gateway);
 		parse_context->gateway = g_steal_pointer (&parse_context->text_buffer);
 	} else if (strcmp (name, "apn") == 0) {
-		g_clear_pointer (&parse_context->text_buffer, g_free);
+		nm_clear_g_free (&parse_context->text_buffer);
 
 		if (parse_context->mccmnc_matched && parse_context->found_internet_apn)
 			parse_context->state = PARSER_DONE;
@@ -262,7 +262,7 @@ parser_cdma_end (ParseContext *parse_context,
                  const char *name)
 {
 	if (strcmp (name, "cdma") == 0) {
-		g_clear_pointer (&parse_context->text_buffer, g_free);
+		nm_clear_g_free (&parse_context->text_buffer);
 		parse_context->state = PARSER_PROVIDER;
 	}
 }