summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
committerMichael Biebl <biebl@debian.org>2020-04-11 21:28:04 +0200
commit1e5977b62f896e844b548c3007ace9e1dfa7f9ed (patch)
tree7a7416ed410e72b6200f3d860fd315ec11cc106b /src
parentb012fa6e1d808e0736c009799c62d835cbfcc1dd (diff)
New upstream version 1.23.90 upstream/1.23.90
Diffstat (limited to 'src')
-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
-rw-r--r--src/dhcp/meson.build2
-rw-r--r--src/dhcp/nm-dhcp-client.c18
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c2
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c2
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c2
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c2
-rw-r--r--src/dhcp/nm-dhcp-listener.c4
-rw-r--r--src/dhcp/nm-dhcp-manager.c2
-rw-r--r--src/dhcp/nm-dhcp-manager.h2
-rw-r--r--src/dhcp/nm-dhcp-nettools.c58
-rw-r--r--src/dhcp/nm-dhcp-systemd.c32
-rw-r--r--src/dhcp/nm-dhcp-utils.c12
-rw-r--r--src/dhcp/tests/meson.build2
-rw-r--r--src/dns/nm-dns-dnsmasq.c18
-rw-r--r--src/dns/nm-dns-manager.c210
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c6
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.c5
-rw-r--r--src/dnsmasq/tests/meson.build2
-rw-r--r--src/initrd/meson.build2
-rw-r--r--src/initrd/nm-initrd-generator.c4
-rw-r--r--src/initrd/nmi-cmdline-reader.c556
-rw-r--r--src/initrd/nmi-dt-reader.c4
-rw-r--r--src/initrd/nmi-ibft-reader.c10
-rw-r--r--src/initrd/tests/meson.build2
-rw-r--r--src/initrd/tests/test-cmdline-reader.c101
-rw-r--r--src/main-utils.c2
-rw-r--r--src/meson.build8
-rw-r--r--src/ndisc/nm-fake-ndisc.c8
-rw-r--r--src/ndisc/nm-lndp-ndisc.c84
-rw-r--r--src/ndisc/nm-ndisc.c73
-rw-r--r--src/ndisc/tests/meson.build2
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c17
-rw-r--r--src/nm-act-request.c2
-rw-r--r--src/nm-active-connection.c39
-rw-r--r--src/nm-audit-manager.c8
-rw-r--r--src/nm-auth-manager.c6
-rw-r--r--src/nm-auth-manager.h16
-rw-r--r--src/nm-auth-subject.c432
-rw-r--r--src/nm-auth-subject.h53
-rw-r--r--src/nm-auth-utils.c35
-rw-r--r--src/nm-auth-utils.h4
-rw-r--r--src/nm-checkpoint.c12
-rw-r--r--src/nm-config-data.c12
-rw-r--r--src/nm-config.c27
-rw-r--r--src/nm-connectivity.c17
-rw-r--r--src/nm-core-utils.c455
-rw-r--r--src/nm-core-utils.h14
-rw-r--r--src/nm-dbus-manager.c70
-rw-r--r--src/nm-dbus-manager.h5
-rw-r--r--src/nm-dbus-object.c2
-rw-r--r--src/nm-dhcp-config.c251
-rw-r--r--src/nm-dhcp-config.h35
-rw-r--r--src/nm-dhcp4-config.c158
-rw-r--r--src/nm-dhcp4-config.h31
-rw-r--r--src/nm-dhcp6-config.c156
-rw-r--r--src/nm-dhcp6-config.h31
-rw-r--r--src/nm-dispatcher.c18
-rw-r--r--src/nm-firewall-manager.c2
-rw-r--r--src/nm-iface-helper.c4
-rw-r--r--src/nm-ip4-config.c22
-rw-r--r--src/nm-ip6-config.c14
-rw-r--r--src/nm-logging.c16
-rw-r--r--src/nm-manager.c116
-rw-r--r--src/nm-pacrunner-manager.c10
-rw-r--r--src/nm-policy.c106
-rw-r--r--src/nm-proxy-config.c2
-rw-r--r--src/nm-session-monitor.c28
-rw-r--r--src/nm-test-utils-core.h2
-rw-r--r--src/nm-types.h49
-rw-r--r--src/platform/nm-fake-platform.c178
-rw-r--r--src/platform/nm-linux-platform.c1170
-rw-r--r--src/platform/nm-platform-utils.c3
-rw-r--r--src/platform/nm-platform.c813
-rw-r--r--src/platform/nm-platform.h340
-rw-r--r--src/platform/nmp-netns.c3
-rw-r--r--src/platform/nmp-object.c28
-rw-r--r--src/platform/nmp-object.h8
-rw-r--r--src/platform/tests/meson.build2
-rw-r--r--src/platform/tests/test-cleanup.c3
-rw-r--r--src/platform/tests/test-common.c124
-rw-r--r--src/platform/tests/test-common.h23
-rw-r--r--src/platform/tests/test-link.c33
-rw-r--r--src/platform/tests/test-route.c11
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c2
-rw-r--r--src/ppp/meson.build2
-rw-r--r--src/ppp/nm-ppp-manager.c12
-rw-r--r--src/settings/nm-agent-manager.c399
-rw-r--r--src/settings/nm-agent-manager.h5
-rw-r--r--src/settings/nm-secret-agent.c36
-rw-r--r--src/settings/nm-secret-agent.h13
-rw-r--r--src/settings/nm-settings-connection.c23
-rw-r--r--src/settings/nm-settings.c24
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build2
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c14
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c244
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c529
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h75
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c464
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c338
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h18
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/meson.build2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe5
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-424
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected14
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c240
-rw-r--r--src/settings/plugins/ifupdown/meson.build2
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c15
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c2
-rw-r--r--src/settings/plugins/ifupdown/tests/meson.build2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c6
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build2
-rw-r--r--src/settings/plugins/meson.build2
-rw-r--r--src/supplicant/nm-supplicant-config.c84
-rw-r--r--src/supplicant/nm-supplicant-config.h3
-rw-r--r--src/supplicant/nm-supplicant-interface.c4308
-rw-r--r--src/supplicant/nm-supplicant-interface.h156
-rw-r--r--src/supplicant/nm-supplicant-manager.c1525
-rw-r--r--src/supplicant/nm-supplicant-manager.h36
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c4
-rw-r--r--src/supplicant/nm-supplicant-types.h175
-rw-r--r--src/supplicant/tests/meson.build2
-rw-r--r--src/supplicant/tests/test-supplicant-config.c58
-rw-r--r--src/systemd/meson.build2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h6
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c55
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h7
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c120
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c58
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c63
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c6
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c2
-rw-r--r--src/systemd/src/libsystemd/sd-event/event-source.h8
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c531
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c15
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.h6
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c17
-rw-r--r--src/systemd/src/systemd/_sd-common.h12
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h10
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h2
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-client.h4
-rw-r--r--src/systemd/src/systemd/sd-event.h12
-rw-r--r--src/systemd/src/systemd/sd-ipv4acd.h3
-rw-r--r--src/tests/config/meson.build2
-rw-r--r--src/tests/config/test-config.c6
-rw-r--r--src/tests/meson.build2
-rw-r--r--src/tests/test-core-with-expect.c16
-rw-r--r--src/tests/test-core.c2
-rw-r--r--src/tests/test-ip6-config.c2
-rw-r--r--src/tests/test-systemd.c2
-rw-r--r--src/vpn/nm-vpn-connection.c67
-rw-r--r--src/vpn/nm-vpn-connection.h2
-rw-r--r--src/vpn/nm-vpn-manager.c2
221 files changed, 12869 insertions, 9710 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;
 	}
 }
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
index 609fe663..67f055d9 100644
--- a/src/dhcp/meson.build
+++ b/src/dhcp/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 name = 'nm-dhcp-helper'
 
 c_flags = [
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 81c4ad9c..90a64aca 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -925,7 +925,7 @@ nm_dhcp_client_handle_event (gpointer unused,
 		    && !ip_config) {
 			_LOGW ("client bound but IP config not received");
 			new_state = NM_DHCP_STATE_FAIL;
-			g_clear_pointer (&str_options, g_hash_table_unref);
+			nm_clear_pointer (&str_options, g_hash_table_unref);
 		}
 
 		nm_dhcp_client_set_state (self, new_state, ip_config, str_options);
@@ -940,7 +940,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_IFACE:
@@ -989,7 +989,7 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (object);
 	guint flags;
 
 	switch (prop_id) {
@@ -1099,12 +1099,12 @@ dispose (GObject *object)
 	watch_cleanup (self);
 	timeout_cleanup (self);
 
-	g_clear_pointer (&priv->iface, g_free);
-	g_clear_pointer (&priv->hostname, g_free);
-	g_clear_pointer (&priv->uuid, g_free);
-	g_clear_pointer (&priv->client_id, g_bytes_unref);
-	g_clear_pointer (&priv->hwaddr, g_bytes_unref);
-	g_clear_pointer (&priv->bcast_hwaddr, g_bytes_unref);
+	nm_clear_g_free (&priv->iface);
+	nm_clear_g_free (&priv->hostname);
+	nm_clear_g_free (&priv->uuid);
+	nm_clear_pointer (&priv->client_id, g_bytes_unref);
+	nm_clear_pointer (&priv->hwaddr, g_bytes_unref);
+	nm_clear_pointer (&priv->bcast_hwaddr, g_bytes_unref);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index f31c493c..da28abad 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -385,7 +385,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 				/* Otherwise capture and return the existing client id */
 				if (out_new_client_id)
-					g_clear_pointer (out_new_client_id, g_bytes_unref);
+					nm_clear_pointer (out_new_client_id, g_bytes_unref);
 				NM_SET_OUT (out_new_client_id, read_client_id (p));
 			}
 
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 869966fb..617ce236 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -665,7 +665,7 @@ nm_dhcp_dhclient_init (NMDhcpDhclient *self)
 static void
 dispose (GObject *object)
 {
-	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE ((NMDhcpDhclient *) object);
+	NMDhcpDhclientPrivate *priv = NM_DHCP_DHCLIENT_GET_PRIVATE (object);
 
 	if (priv->dhcp_listener) {
 		g_signal_handlers_disconnect_by_func (priv->dhcp_listener,
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index f9cc0f0a..63484a4b 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -207,7 +207,7 @@ nm_dhcp_dhcpcanon_init (NMDhcpDhcpcanon *self)
 static void
 dispose (GObject *object)
 {
-	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE ((NMDhcpDhcpcanon *) object);
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (object);
 
 	if (priv->dhcp_listener) {
 		g_signal_handlers_disconnect_by_func (priv->dhcp_listener,
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index 1690bce5..ff695dbe 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -200,7 +200,7 @@ nm_dhcp_dhcpcd_init (NMDhcpDhcpcd *self)
 static void
 dispose (GObject *object)
 {
-	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE ((NMDhcpDhcpcd *) object);
+	NMDhcpDhcpcdPrivate *priv = NM_DHCP_DHCPCD_GET_PRIVATE (object);
 
 	if (priv->dhcp_listener) {
 		g_signal_handlers_disconnect_by_func (priv->dhcp_listener,
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index a54b9643..79dea898 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -287,12 +287,12 @@ nm_dhcp_listener_init (NMDhcpListener *self)
 static void
 dispose (GObject *object)
 {
-	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE ((NMDhcpListener *) object);
+	NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE (object);
 
 	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->new_conn_id);
 	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->dis_conn_id);
 
-	g_clear_pointer (&priv->connections, g_hash_table_destroy);
+	nm_clear_pointer (&priv->connections, g_hash_table_destroy);
 
 	g_clear_object (&priv->dbus_mgr);
 
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index 10ed9589..333744dd 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -515,7 +515,7 @@ nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager, const char *hostna
 {
 	NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (manager);
 
-	g_clear_pointer (&priv->default_hostname, g_free);
+	nm_clear_g_free (&priv->default_hostname);
 
 	/* Never send 'localhost'-type names to the DHCP server */
 	if (!nm_utils_is_specific_hostname (hostname))
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index fb1c9834..1b793c22 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -9,7 +9,7 @@
 
 #include "nm-dhcp-client.h"
 #include "nm-ip4-config.h"
-#include "nm-dhcp4-config.h"
+#include "nm-dhcp-config.h"
 
 #define NM_TYPE_DHCP_MANAGER            (nm_dhcp_manager_get_type ())
 #define NM_DHCP_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_MANAGER, NMDhcpManager))
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/dhcp/nm-dhcp-nettools.c
index 622251a3..00b416da 100644
--- a/src/dhcp/nm-dhcp-nettools.c
+++ b/src/dhcp/nm-dhcp-nettools.c
@@ -49,8 +49,7 @@ typedef struct {
 	NDhcp4Client *client;
 	NDhcp4ClientProbe *probe;
 	NDhcp4ClientLease *lease;
-	GIOChannel *channel;
-	guint event_id;
+	GSource *event_source;
 	char *lease_file;
 } NMDhcpNettoolsPrivate;
 
@@ -376,8 +375,8 @@ lease_parse_address (NDhcp4ClientLease *lease,
 		 * Here we still do it... it seems safe enough. */
 		nm_assert (nettools_basetime > 0);
 		nm_assert (nettools_lifetime >= nettools_basetime);
-		nm_assert (((nettools_lifetime - nettools_basetime) % NM_UTILS_NS_PER_SECOND) == 0);
-		nm_assert ((nettools_lifetime - nettools_basetime) / NM_UTILS_NS_PER_SECOND <= G_MAXUINT32);
+		nm_assert (((nettools_lifetime - nettools_basetime) % NM_UTILS_NSEC_PER_SEC) == 0);
+		nm_assert ((nettools_lifetime - nettools_basetime) / NM_UTILS_NSEC_PER_SEC <= G_MAXUINT32);
 
 		if (nettools_lifetime <= nettools_basetime) {
 			/* A lease time of 0 is allowed on some dhcp servers, so, let's accept it. */
@@ -387,7 +386,7 @@ lease_parse_address (NDhcp4ClientLease *lease,
 
 			/* we "ceil" the value to the next second. In practice, we don't expect any sub-second values
 			 * from n-dhcp4 anyway, so this should have no effect. */
-			lifetime += NM_UTILS_NS_PER_SECOND - 1;
+			lifetime += NM_UTILS_NSEC_PER_SEC - 1;
 		}
 
 		ts = nm_utils_monotonic_timestamp_from_boottime (nettools_basetime, 1);
@@ -395,11 +394,11 @@ lease_parse_address (NDhcp4ClientLease *lease,
 		/* the timestamp must be positive, because we only started nettools DHCP client
 		 * after obtaining the first monotonic timestamp. Hence, the lease must have been
 		 * received afterwards. */
-		nm_assert (ts >= NM_UTILS_NS_PER_SECOND);
+		nm_assert (ts >= NM_UTILS_NSEC_PER_SEC);
 
-		a_timestamp = ts / NM_UTILS_NS_PER_SECOND;
-		a_lifetime = NM_MIN (lifetime / NM_UTILS_NS_PER_SECOND, NM_PLATFORM_LIFETIME_PERMANENT - 1);
-		a_expiry = time (NULL) + ((lifetime - (nm_utils_clock_gettime_ns (CLOCK_BOOTTIME) - nettools_basetime)) / NM_UTILS_NS_PER_SECOND);
+		a_timestamp = ts / NM_UTILS_NSEC_PER_SEC;
+		a_lifetime = NM_MIN (lifetime / NM_UTILS_NSEC_PER_SEC, NM_PLATFORM_LIFETIME_PERMANENT - 1);
+		a_expiry = time (NULL) + ((lifetime - (nm_utils_clock_gettime_nsec (CLOCK_BOOTTIME) - nettools_basetime)) / NM_UTILS_NSEC_PER_SEC);
 	}
 
 	if (!lease_get_in_addr (lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) {
@@ -407,7 +406,7 @@ lease_parse_address (NDhcp4ClientLease *lease,
 		return FALSE;
 	}
 
-	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
+	_nm_utils_inet4_ntop (a_address.s_addr, addr_str);
 	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
 
 	nm_dhcp_option_add_option (options,
@@ -417,7 +416,7 @@ lease_parse_address (NDhcp4ClientLease *lease,
 	nm_dhcp_option_add_option (options,
 	                           _nm_dhcp_option_dhcp4_options,
 	                           NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
-	                           nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+	                           _nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
 
 	nm_dhcp_option_add_option_u64 (options,
 	                               _nm_dhcp_option_dhcp4_options,
@@ -434,7 +433,7 @@ lease_parse_address (NDhcp4ClientLease *lease,
 
 	n_dhcp4_client_lease_get_siaddr (lease, &a_next_server);
 	if (a_next_server.s_addr != INADDR_ANY) {
-		nm_utils_inet4_ntop (a_next_server.s_addr, addr_str);
+		_nm_utils_inet4_ntop (a_next_server.s_addr, addr_str);
 		nm_dhcp_option_add_option (options,
 		                           _nm_dhcp_option_dhcp4_options,
 		                           NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
@@ -475,7 +474,7 @@ lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
 
 	while (lease_option_next_in_addr (&addr, &data, &n_data)) {
 
-		nm_utils_inet4_ntop (addr.s_addr, addr_str);
+		_nm_utils_inet4_ntop (addr.s_addr, addr_str);
 		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 
 		if (   addr.s_addr == 0
@@ -521,8 +520,8 @@ lease_parse_routes (NDhcp4ClientLease *lease,
 
 		while (lease_option_next_route (&dest, &plen, &gateway, TRUE, &data, &n_data)) {
 
-			nm_utils_inet4_ntop (dest.s_addr, dest_str);
-			nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
+			_nm_utils_inet4_ntop (dest.s_addr, dest_str);
+			_nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
 
 			g_string_append_printf (nm_gstring_add_space_delimiter (str),
 			                        "%s/%d %s",
@@ -565,8 +564,8 @@ lease_parse_routes (NDhcp4ClientLease *lease,
 
 		while (lease_option_next_route (&dest, &plen, &gateway, FALSE, &data, &n_data)) {
 
-			nm_utils_inet4_ntop (dest.s_addr, dest_str);
-			nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
+			_nm_utils_inet4_ntop (dest.s_addr, dest_str);
+			_nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
 
 			g_string_append_printf (nm_gstring_add_space_delimiter (str),
 			                        "%s/%d %s",
@@ -611,7 +610,7 @@ lease_parse_routes (NDhcp4ClientLease *lease,
 		nm_gstring_prepare (&str);
 
 		while (lease_option_next_in_addr (&gateway, &data, &n_data)) {
-			s = nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
+			s = _nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
 			g_string_append (nm_gstring_add_space_delimiter (str), s);
 
 			if (gateway.s_addr == 0) {
@@ -710,7 +709,7 @@ lease_parse_ntps (NDhcp4ClientLease *lease,
 	nm_gstring_prepare (&str);
 
 	while (lease_option_next_in_addr (&addr, &data, &n_data)) {
-		nm_utils_inet4_ntop (addr.s_addr, addr_str);
+		_nm_utils_inet4_ntop (addr.s_addr, addr_str);
 		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 	}
 
@@ -967,7 +966,7 @@ lease_save (NMDhcpNettools *self, NDhcp4ClientLease *lease, const char *lease_fi
 		return;
 
 	g_string_append_printf (new_contents,
-	                        "ADDRESS=%s\n", nm_utils_inet4_ntop (a_address.s_addr, sbuf));
+	                        "ADDRESS=%s\n", _nm_utils_inet4_ntop (a_address.s_addr, sbuf));
 
 	if (!g_file_set_contents (lease_file,
 	                          new_contents->str,
@@ -1054,7 +1053,7 @@ dhcp4_event_handle (NMDhcpNettools *self,
 }
 
 static gboolean
-dhcp4_event_cb (GIOChannel *source,
+dhcp4_event_cb (int fd,
                 GIOCondition condition,
                 gpointer data)
 {
@@ -1073,7 +1072,7 @@ dhcp4_event_cb (GIOChannel *source,
 		 * a predefined number of times (possibly infinite).
 		 */
 		_LOGE ("error %d dispatching events", r);
-		priv->event_id = 0;
+		nm_clear_g_source_inst (&priv->event_source);
 		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
 		return G_SOURCE_REMOVE;
 	}
@@ -1199,8 +1198,14 @@ nettools_create (NMDhcpNettools *self,
 	client = NULL;
 
 	n_dhcp4_client_get_fd (priv->client, &fd);
-	priv->channel = g_io_channel_unix_new (fd);
-	priv->event_id = g_io_add_watch (priv->channel, G_IO_IN, dhcp4_event_cb, self);
+
+	priv->event_source = nm_g_unix_fd_source_new (fd,
+	                                              G_IO_IN,
+	                                              G_PRIORITY_DEFAULT,
+	                                              dhcp4_event_cb,
+	                                              self,
+	                                              NULL);
+	g_source_attach (priv->event_source, NULL);
 
 	return TRUE;
 }
@@ -1427,11 +1432,10 @@ nm_dhcp_nettools_init (NMDhcpNettools *self)
 static void
 dispose (GObject *object)
 {
-	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE ((NMDhcpNettools *) object);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (object);
 
 	nm_clear_pointer (&priv->lease_file, g_free);
-	nm_clear_pointer (&priv->channel, g_io_channel_unref);
-	nm_clear_g_source (&priv->event_id);
+	nm_clear_g_source_inst (&priv->event_source);
 	nm_clear_pointer (&priv->lease, n_dhcp4_client_lease_unref);
 	nm_clear_pointer (&priv->probe, n_dhcp4_client_probe_free);
 	nm_clear_pointer (&priv->client, n_dhcp4_client_unref);
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 6e6aa243..23862e9e 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -93,7 +93,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	gboolean has_router_from_classless = FALSE;
 	gboolean has_classless_route = FALSE;
 	gboolean has_static_route = FALSE;
-	const gint32 ts = nm_utils_get_monotonic_timestamp_s ();
+	const gint32 ts = nm_utils_get_monotonic_timestamp_sec ();
 	gint64 ts_time = time (NULL);
 	struct in_addr a_address;
 	struct in_addr a_netmask;
@@ -128,7 +128,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 
 	options = out_options ? nm_dhcp_option_create_options_dict () : NULL;
 
-	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
+	_nm_utils_inet4_ntop (a_address.s_addr, addr_str);
 	nm_dhcp_option_add_option (options,
 	                           _nm_dhcp_option_dhcp4_options,
 	                           NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
@@ -138,7 +138,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	nm_dhcp_option_add_option (options,
 	                           _nm_dhcp_option_dhcp4_options,
 	                           NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
-	                           nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+	                           _nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
 
 	nm_dhcp_option_add_option_u64 (options,
 	                               _nm_dhcp_option_dhcp4_options,
@@ -150,7 +150,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	                               (guint64) (ts_time + a_lifetime));
 
 	if (sd_dhcp_lease_get_next_server (lease, &a_next_server) == 0) {
-		nm_utils_inet4_ntop (a_next_server.s_addr, addr_str);
+		_nm_utils_inet4_ntop (a_next_server.s_addr, addr_str);
 		nm_dhcp_option_add_option (options,
 		                           _nm_dhcp_option_dhcp4_options,
 		                           NM_DHCP_OPTION_DHCP4_NM_NEXT_SERVER,
@@ -169,7 +169,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	                           }));
 
 	if (sd_dhcp_lease_get_server_identifier (lease, &server_id) >= 0) {
-		nm_utils_inet4_ntop (server_id.s_addr, addr_str);
+		_nm_utils_inet4_ntop (server_id.s_addr, addr_str);
 		nm_dhcp_option_add_option (options,
 		                           _nm_dhcp_option_dhcp4_options,
 		                           NM_DHCP_OPTION_DHCP4_SERVER_ID,
@@ -177,7 +177,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	}
 
 	if (sd_dhcp_lease_get_broadcast (lease, &broadcast) >= 0) {
-		nm_utils_inet4_ntop (broadcast.s_addr, addr_str);
+		_nm_utils_inet4_ntop (broadcast.s_addr, addr_str);
 		nm_dhcp_option_add_option (options,
 		                           _nm_dhcp_option_dhcp4_options,
 		                           NM_DHCP_OPTION_DHCP4_BROADCAST,
@@ -188,7 +188,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
+			_nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
 			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 
 			if (   addr_list[i].s_addr == 0
@@ -288,8 +288,8 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 
 			network_net = nm_utils_ip4_address_clear_host_address (r_network.s_addr,
 			                                                       r_plen);
-			nm_utils_inet4_ntop (network_net, network_net_str);
-			nm_utils_inet4_ntop (r_gateway.s_addr, gateway_str);
+			_nm_utils_inet4_ntop (network_net, network_net_str);
+			_nm_utils_inet4_ntop (r_gateway.s_addr, gateway_str);
 
 			g_string_append_printf (nm_gstring_add_space_delimiter (  option == NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE
 			                                                        ? str_classless
@@ -359,7 +359,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 		for (i = 0; i < num; i++) {
 			guint32 m;
 
-			s = nm_utils_inet4_ntop (a_router[i].s_addr, addr_str);
+			s = _nm_utils_inet4_ntop (a_router[i].s_addr, addr_str);
 			g_string_append (nm_gstring_add_space_delimiter (str), s);
 
 			if (a_router[i].s_addr == 0) {
@@ -411,7 +411,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
+			_nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
 			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 		}
 		nm_dhcp_option_add_option (options,
@@ -752,7 +752,7 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 
 		nm_ip6_config_add_address (ip6_config, &address);
 
-		nm_utils_inet6_ntop (&tmp_addr, addr_str);
+		_nm_utils_inet6_ntop (&tmp_addr, addr_str);
 		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 	};
 	if (str->len)
@@ -774,7 +774,7 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 	if (num > 0) {
 		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			nm_utils_inet6_ntop (&dns[i], addr_str);
+			_nm_utils_inet6_ntop (&dns[i], addr_str);
 			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
 		}
@@ -805,7 +805,7 @@ static void
 bound6_handle (NMDhcpSystemd *self)
 {
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
-	const gint32 ts = nm_utils_get_monotonic_timestamp_s ();
+	const gint32 ts = nm_utils_get_monotonic_timestamp_sec ();
 	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
 	gs_unref_object NMIP6Config *ip6_config = NULL;
 	gs_unref_hashtable GHashTable *options = NULL;
@@ -1056,9 +1056,9 @@ nm_dhcp_systemd_init (NMDhcpSystemd *self)
 static void
 dispose (GObject *object)
 {
-	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE ((NMDhcpSystemd *) object);
+	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (object);
 
-	g_clear_pointer (&priv->lease_file, g_free);
+	nm_clear_g_free (&priv->lease_file);
 
 	if (priv->client4) {
 		sd_dhcp_client_stop (priv->client4);
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index c5da3e02..d8e5a653 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -181,9 +181,9 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 			nm_ip4_config_add_route (ip4_config, &route, NULL);
 
 			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s",
-			        nm_utils_inet4_ntop (route.network, b1),
+			        _nm_utils_inet4_ntop (route.network, b1),
 			        route.plen,
-			        nm_utils_inet4_ntop (route.gateway, b2));
+			        _nm_utils_inet4_ntop (route.gateway, b2));
 		}
 	}
 
@@ -388,7 +388,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 
 	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 	memset (&address, 0, sizeof (address));
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.timestamp = nm_utils_get_monotonic_timestamp_sec ();
 
 	str = g_hash_table_lookup (options, "ip_address");
 	if (str && (inet_pton (AF_INET, str, &addr) > 0))
@@ -414,7 +414,7 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 		process_classful_routes (iface, options, route_table, route_metric, ip4_config);
 
 	if (gateway) {
-		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, sbuf));
+		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", _nm_utils_inet4_ntop (gateway, sbuf));
 		gateway_has = TRUE;
 	} else {
 		/* If the gateway wasn't provided as a classless static route with a
@@ -601,7 +601,7 @@ nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options)
 	address.address = tmp_addr;
 	address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
 	address.plen = prefix;
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.timestamp = nm_utils_get_monotonic_timestamp_sec ();
 
 	str = g_hash_table_lookup (options, "max_life");
 	if (str)
@@ -630,7 +630,7 @@ nm_dhcp_utils_ip6_config_from_options (NMDedupMultiIndex *multi_idx,
 
 	memset (&address, 0, sizeof (address));
 	address.plen = 128;
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.timestamp = nm_utils_get_monotonic_timestamp_sec ();
 
 	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
 
diff --git a/src/dhcp/tests/meson.build b/src/dhcp/tests/meson.build
index 031e2efd..59a647cc 100644
--- a/src/dhcp/tests/meson.build
+++ b/src/dhcp/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_units = [
   'test-dhcp-dhclient',
   'test-dhcp-utils',
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index a5028e42..fdff3af1 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -129,7 +129,7 @@ _gl_pid_kill_external_timeout_cb (gpointer user_data)
 		goto process_gone;
 	}
 
-	now = nm_utils_get_monotonic_timestamp_ms ();
+	now = nm_utils_get_monotonic_timestamp_msec ();
 
 	if (gl_pid.kill_external_data->started_at + WAIT_MSEC_AFTER_SIGTERM < now) {
 		if (!gl_pid.kill_external_data->sigkilled) {
@@ -240,7 +240,7 @@ handle_kill:
 	gl_pid.kill_external_data = g_slice_new (GlPidKillExternalData);
 	*gl_pid.kill_external_data = (GlPidKillExternalData) {
 		.shutdown_wait_handle = nm_shutdown_wait_obj_register_handle_full (g_strdup_printf ("kill-external-dnsmasq-process-%"G_PID_FORMAT, pid), TRUE),
-		.started_at           = nm_utils_get_monotonic_timestamp_ms (),
+		.started_at           = nm_utils_get_monotonic_timestamp_msec (),
 		.pid                  = pid,
 		.p_start_time         = p_start_time,
 	};
@@ -304,7 +304,7 @@ _gl_pid_spawn_notify (GlPidSpawnAsyncData *sdata,
 	if (error) {
 		nm_assert (pid == 0);
 		nm_assert (!p_exit_code);
-		if (!nm_utils_error_is_cancelled (error, FALSE))
+		if (!nm_utils_error_is_cancelled (error))
 			_LOGD ("spawn: dnsmasq failed: %s", error->message);
 	} else if (p_exit_code) {
 		/* the only caller already logged about this condition extensively. */
@@ -726,9 +726,9 @@ ip_addr_to_string (int addr_family, gconstpointer addr, const char *iface, char
 		separator = "@";
 	} else {
 		if (IN6_IS_ADDR_V4MAPPED (addr))
-			nm_utils_inet4_ntop (((const struct in6_addr *) addr)->s6_addr32[3], buf2);
+			_nm_utils_inet4_ntop (((const struct in6_addr *) addr)->s6_addr32[3], buf2);
 		else
-			nm_utils_inet6_ntop (addr, buf2);
+			_nm_utils_inet6_ntop (addr, buf2);
 		/* Need to scope link-local addresses with %<zone-id>. Before dnsmasq 2.58,
 		 * only '@' was supported as delimiter. Since 2.58, '@' and '%' are
 		 * supported. Due to a bug, since 2.73 only '%' works properly as "server"
@@ -838,7 +838,7 @@ dnsmasq_update_done (GObject *source_object, GAsyncResult *res, gpointer user_da
 
 	response = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
 
-	if (nm_utils_error_is_cancelled (error, FALSE))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = user_data;
@@ -987,7 +987,7 @@ spawn_notify (GCancellable *cancellable,
 	NMDnsDnsmasq *self;
 	NMDnsDnsmasqPrivate *priv;
 
-	if (nm_utils_error_is_cancelled (error, FALSE))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = notify_user_data;
@@ -1058,7 +1058,7 @@ start_dnsmasq (NMDnsDnsmasq *self, gboolean force_start, GError **error)
 		}
 	}
 
-	now = nm_utils_get_monotonic_timestamp_ms ();
+	now = nm_utils_get_monotonic_timestamp_msec ();
 	if (   force_start
 	    || priv->burst_start_at == 0
 	    || priv->burst_start_at + RATELIMIT_INTERVAL_MSEC <= now) {
@@ -1161,7 +1161,7 @@ dispose (GObject *object)
 
 	_main_cleanup (self, FALSE);
 
-	g_clear_pointer (&priv->set_server_ex_args, g_variant_unref);
+	nm_clear_pointer (&priv->set_server_ex_args, g_variant_unref);
 
 	G_OBJECT_CLASS (nm_dns_dnsmasq_parent_class)->dispose (object);
 
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index d731e7bf..b40dd6b6 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -178,7 +178,8 @@ domain_is_routing (const char *domain)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConfManager,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_rc_manager_to_string, NMDnsManagerResolvConfManager,
 	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_UNKNOWN,        "unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED,      "unmanaged"),
@@ -189,7 +190,8 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_rc_manager_to_string, NMDnsManagerResolvConf
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG,      "netconfig"),
 );
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_config_type_to_string, NMDnsIPConfigType,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_config_type_to_string, NMDnsIPConfigType,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("<unknown>"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_REMOVED, "removed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DNS_IP_CONFIG_TYPE_DEFAULT, "default"),
@@ -425,9 +427,9 @@ merge_one_ip_config (NMResolvConfData *rc,
 		if (addr_family == AF_INET)
 			nm_utils_inet_ntop (addr_family, addr, buf);
 		else if (IN6_IS_ADDR_V4MAPPED (addr))
-			nm_utils_inet4_ntop (addr->addr6.s6_addr32[3], buf);
+			_nm_utils_inet4_ntop (addr->addr6.s6_addr32[3], buf);
 		else {
-			nm_utils_inet6_ntop (&addr->addr6, buf);
+			_nm_utils_inet6_ntop (&addr->addr6, buf);
 			if (IN6_IS_ADDR_LINKLOCAL (addr)) {
 				const char *ifname;
 
@@ -457,7 +459,7 @@ merge_one_ip_config (NMResolvConfData *rc,
 		num = nm_ip4_config_get_num_nis_servers (ip4_config);
 		for (i = 0; i < num; i++) {
 			add_string_item (rc->nis_servers,
-			                 nm_utils_inet4_ntop (nm_ip4_config_get_nis_server (ip4_config, i), buf),
+			                 _nm_utils_inet4_ntop (nm_ip4_config_get_nis_server (ip4_config, i), buf),
 			                 TRUE);
 		}
 
@@ -858,9 +860,11 @@ update_resolv_conf (NMDnsManager *self,
 		if (!g_file_set_contents (rc_path, content, -1, &local)) {
 			_LOGT ("update-resolv-conf: write to %s failed (rc-manager=%s, %s)",
 			       rc_path, _rc_manager_to_string (rc_manager), local->message);
-			write_file_result = SR_ERROR;
 			g_propagate_error (error, local);
+			/* clear @error, so that we don't try reset it. This is the error
+			 * we want to propagate to the caller. */
 			error = NULL;
+			write_file_result = SR_ERROR;
 		} else {
 			_LOGT ("update-resolv-conf: write to %s succeeded (rc-manager=%s)",
 			       rc_path, _rc_manager_to_string (rc_manager));
@@ -928,7 +932,7 @@ update_resolv_conf (NMDnsManager *self,
 	if (   rc_manager != NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK
 	    || !_read_link_cached (_PATH_RESCONF, &resconf_link_cached, &resconf_link)) {
 		_LOGT ("update-resolv-conf: write internal file %s succeeded", MY_RESOLV_CONF);
-		return SR_SUCCESS;
+		return write_file_result;
 	}
 
 	if (!nm_streq0 (_read_link_cached (_PATH_RESCONF, &resconf_link_cached, &resconf_link),
@@ -936,7 +940,7 @@ update_resolv_conf (NMDnsManager *self,
 		_LOGT ("update-resolv-conf: write internal file %s succeeded (don't touch symlink %s linking to %s)",
 		       MY_RESOLV_CONF, _PATH_RESCONF,
 		       _read_link_cached (_PATH_RESCONF, &resconf_link_cached, &resconf_link));
-		return SR_SUCCESS;
+		return write_file_result;
 	}
 
 	/* By this point, /etc/resolv.conf exists and is a symlink to our internal
@@ -989,7 +993,7 @@ update_resolv_conf (NMDnsManager *self,
 
 	_LOGT ("update-resolv-conf: write internal file %s succeeded and update symlink %s",
 	       MY_RESOLV_CONF, _PATH_RESCONF);
-	return SR_SUCCESS;
+	return write_file_result;
 }
 
 static void
@@ -1161,7 +1165,7 @@ _collect_resolv_conf_data (NMDnsManager *self,
 		const char *hostdomain = strchr (priv->hostname, '.');
 
 		if (   hostdomain
-		    && !nm_utils_ipaddr_valid (AF_UNSPEC, priv->hostname)) {
+		    && !nm_utils_ipaddr_is_valid (AF_UNSPEC, priv->hostname)) {
 			hostdomain++;
 			if (domain_is_valid (hostdomain, TRUE))
 				add_string_item (rc.searches, hostdomain, TRUE);
@@ -1227,26 +1231,34 @@ get_ip_rdns_domains (NMIPConfig *ip_config)
 /* Check if the domain is shadowed by a parent domain with more negative priority */
 static gboolean
 domain_is_shadowed (GHashTable *ht,
-                    const char *domain, int priority,
-                    const char **out_parent, int *out_parent_priority)
+                    const char *domain,
+                    int priority,
+                    const char **out_parent,
+                    int *out_parent_priority)
 {
 	char *parent;
 	int parent_priority;
 
+	if (!ht)
+		return FALSE;
+
 	nm_assert (!g_hash_table_contains (ht, domain));
 
 	parent_priority = GPOINTER_TO_INT (g_hash_table_lookup (ht, ""));
-	if (parent_priority < 0 && parent_priority < priority) {
+	if (   parent_priority < 0
+	    && parent_priority < priority) {
 		*out_parent = "";
 		*out_parent_priority = parent_priority;
 		return TRUE;
 	}
 
 	parent = strchr (domain, '.');
-	while (parent && parent[1]) {
+	while (   parent
+	       && parent[1]) {
 		parent++;
 		parent_priority = GPOINTER_TO_INT (g_hash_table_lookup (ht, parent));
-		if (parent_priority < 0 && parent_priority < priority) {
+		if (   parent_priority < 0
+		    && parent_priority < priority) {
 			*out_parent = parent;
 			*out_parent_priority = parent_priority;
 			return TRUE;
@@ -1265,8 +1277,6 @@ rebuild_domain_lists (NMDnsManager *self)
 	gboolean default_route_found = FALSE;
 	CList *head;
 
-	ht = g_hash_table_new (nm_str_hash, g_str_equal);
-
 	head = _ip_config_lst_head (self);
 	c_list_for_each_entry (ip_data, head, ip_config_lst) {
 		NMIPConfig *ip_config = ip_data->ip_config;
@@ -1281,55 +1291,71 @@ rebuild_domain_lists (NMDnsManager *self)
 
 	c_list_for_each_entry (ip_data, head, ip_config_lst) {
 		NMIPConfig *ip_config = ip_data->ip_config;
-		int priority, old_priority;
-		guint i, n, n_domains = 0;
+		int priority;
 		const char **domains;
+		guint n_searches;
+		guint n_domains;
+		guint num_dom1;
+		guint num_dom2;
+		guint cap_dom;
+		guint i;
 
 		if (!nm_ip_config_get_num_nameservers (ip_config))
 			continue;
 
+		n_searches = nm_ip_config_get_num_searches (ip_config);
+		n_domains = nm_ip_config_get_num_domains (ip_config);
+
 		priority = nm_ip_config_get_dns_priority (ip_config);
 		nm_assert (priority != 0);
+
+		cap_dom = 2u + NM_MAX (n_domains, n_searches);
+
 		g_free (ip_data->domains.search);
-		domains = g_new0 (const char *,
-		                  2 + NM_MAX (nm_ip_config_get_num_searches (ip_config),
-		                              nm_ip_config_get_num_domains (ip_config)));
+		domains = g_new (const char *, cap_dom);
 		ip_data->domains.search = domains;
 
+		num_dom1 = 0;
+
 		/* Add wildcard lookup domain to connections with the default route.
 		 * If there is no default route, add the wildcard domain to all non-VPN
 		 * connections */
 		if (default_route_found) {
+			/* FIXME: this heuristic of which device has a default route does
+			 * not work with policy routing (as used by default with WireGuard).
+			 * We should have a more stable mechanism where an NMIPConfig indicates
+			 * whether it is suitable for certain operations (like having an automatically
+			 * added "~" domain). */
 			if (nm_ip_config_best_default_route_get (ip_config))
-				domains[n_domains++] = "~";
+				domains[num_dom1++] = "~";
 		} else {
 			if (ip_data->ip_config_type != NM_DNS_IP_CONFIG_TYPE_VPN)
-				domains[n_domains++] = "~";
+				domains[num_dom1++] = "~";
 		}
 
 		/* searches are preferred over domains */
-		n = nm_ip_config_get_num_searches (ip_config);
-		for (i = 0; i < n; i++)
-			domains[n_domains++] = nm_ip_config_get_search (ip_config, i);
-
-		if (n == 0) {
-			/* If not searches, use any domains */
-			n = nm_ip_config_get_num_domains (ip_config);
-			for (i = 0; i < n; i++)
-				domains[n_domains++] = nm_ip_config_get_domain (ip_config, i);
+		if (n_searches > 0) {
+			for (i = 0; i < n_searches; i++)
+				domains[num_dom1++] = nm_ip_config_get_search (ip_config, i);
+		} else {
+			for (i = 0; i < n_domains; i++)
+				domains[num_dom1++] = nm_ip_config_get_domain (ip_config, i);
 		}
 
-		n = 0;
-		for (i = 0; i < n_domains; i++) {
+		nm_assert (num_dom1 < cap_dom);
+
+		num_dom2 = 0;
+		for (i = 0; i < num_dom1; i++) {
 			const char *domain_clean;
 			const char *parent;
+			int old_priority;
 			int parent_priority;
 
 			domain_clean = nm_utils_parse_dns_domain (domains[i], NULL);
 
 			/* Remove domains with lower priority */
-			old_priority = GPOINTER_TO_INT (g_hash_table_lookup (ht, domain_clean));
-			if (old_priority) {
+			old_priority = GPOINTER_TO_INT (nm_g_hash_table_lookup (ht, domain_clean));
+			if (old_priority != 0) {
 				if (old_priority < priority) {
 					_LOGT ("plugin: drop domain '%s' (i=%d, p=%d) because it already exists with p=%d",
 					       domains[i], ip_data->data->ifindex,
@@ -1345,10 +1371,13 @@ rebuild_domain_lists (NMDnsManager *self)
 			}
 
 			_LOGT ("plugin: add domain '%s' (i=%d, p=%d)", domains[i], ip_data->data->ifindex, priority);
+			if (!ht)
+				ht = g_hash_table_new (nm_str_hash, g_str_equal);
 			g_hash_table_insert (ht, (gpointer) domain_clean, GINT_TO_POINTER (priority));
-			domains[n++] = domains[i];
+			domains[num_dom2++] = domains[i];
 		}
-		domains[n] = NULL;
+		nm_assert (num_dom2 < cap_dom);
+		domains[num_dom2] = NULL;
 
 		g_strfreev (ip_data->domains.reverse);
 		ip_data->domains.reverse = get_ip_rdns_domains (ip_config);
@@ -1363,8 +1392,8 @@ clear_domain_lists (NMDnsManager *self)
 
 	head = _ip_config_lst_head (self);
 	c_list_for_each_entry (ip_data, head, ip_config_lst) {
-		g_clear_pointer (&ip_data->domains.search, g_free);
-		g_clear_pointer (&ip_data->domains.reverse, g_strfreev);
+		nm_clear_g_free (&ip_data->domains.search);
+		nm_clear_pointer (&ip_data->domains.reverse, g_strfreev);
 	}
 }
 
@@ -1373,21 +1402,24 @@ update_dns (NMDnsManager *self,
             gboolean no_caching,
             GError **error)
 {
-	NMDnsManagerPrivate *priv;
+	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 	const char *nis_domain = NULL;
 	gs_strfreev char **searches = NULL;
 	gs_strfreev char **options = NULL;
 	gs_strfreev char **nameservers = NULL;
 	gs_strfreev char **nis_servers = NULL;
-	gboolean caching = FALSE, update = TRUE;
+	gboolean caching = FALSE;
+	gboolean do_update = TRUE;
 	gboolean resolv_conf_updated = FALSE;
-	SpawnResult result = SR_ERROR;
+	SpawnResult result = SR_SUCCESS;
 	NMConfigData *data;
 	NMGlobalDnsConfig *global_config;
+	gs_free_error GError *local_error = NULL;
+	GError **const p_local_error =   error
+	                               ? &local_error
+	                               : NULL;
 
-	g_return_val_if_fail (!error || !*error, FALSE);
-
-	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
+	nm_assert (!error || !*error);
 
 	if (priv->is_stopped) {
 		_LOGD ("update-dns: not updating resolv.conf (is stopped)");
@@ -1398,7 +1430,7 @@ update_dns (NMDnsManager *self,
 
 	if (NM_IN_SET (priv->rc_manager, NM_DNS_MANAGER_RESOLV_CONF_MAN_UNMANAGED,
 	                                 NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE)) {
-		update = FALSE;
+		do_update = FALSE;
 		_LOGD ("update-dns: not updating resolv.conf");
 	} else {
 		priv->dns_touched = TRUE;
@@ -1436,7 +1468,7 @@ update_dns (NMDnsManager *self,
 			if (no_caching) {
 				_LOGD ("update-dns: plugin %s ignored (caching disabled)",
 				       plugin_name);
-				goto skip;
+				goto plugin_skip;
 			}
 			caching = TRUE;
 		}
@@ -1455,7 +1487,7 @@ update_dns (NMDnsManager *self,
 			caching = FALSE;
 		}
 
-	skip:
+plugin_skip:
 		;
 	}
 
@@ -1486,7 +1518,7 @@ update_dns (NMDnsManager *self,
 		nameservers[0] = g_strdup (lladdr);
 	}
 
-	if (update) {
+	if (do_update) {
 		switch (priv->rc_manager) {
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK:
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_FILE:
@@ -1494,7 +1526,7 @@ update_dns (NMDnsManager *self,
 			                             NM_CAST_STRV_CC (searches),
 			                             NM_CAST_STRV_CC (nameservers),
 			                             NM_CAST_STRV_CC (options),
-			                             error,
+			                             p_local_error,
 			                             priv->rc_manager);
 			resolv_conf_updated = TRUE;
 			/* If we have ended with no nameservers avoid updating again resolv.conf
@@ -1503,7 +1535,11 @@ update_dns (NMDnsManager *self,
 				priv->dns_touched = FALSE;
 			break;
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_RESOLVCONF:
-			result = dispatch_resolvconf (self, searches, nameservers, options, error);
+			result = dispatch_resolvconf (self,
+			                              searches,
+			                              nameservers,
+			                              options,
+			                              p_local_error);
 			break;
 		case NM_DNS_MANAGER_RESOLV_CONF_MAN_NETCONFIG:
 			result = dispatch_netconfig (self,
@@ -1511,20 +1547,20 @@ update_dns (NMDnsManager *self,
 			                             (const char *const*) nameservers,
 			                             nis_domain,
 			                             (const char *const*) nis_servers,
-			                             error);
+			                             p_local_error);
 			break;
 		default:
-			g_assert_not_reached ();
+			nm_assert_not_reached ();
 		}
 
 		if (result == SR_NOTFOUND) {
 			_LOGD ("update-dns: program not available, writing to resolv.conf");
-			g_clear_error (error);
+			g_clear_error (&local_error);
 			result = update_resolv_conf (self,
 			                             NM_CAST_STRV_CC (searches),
 			                             NM_CAST_STRV_CC (nameservers),
 			                             NM_CAST_STRV_CC (options),
-			                             error,
+			                             p_local_error,
 			                             NM_DNS_MANAGER_RESOLV_CONF_MAN_SYMLINK);
 			resolv_conf_updated = TRUE;
 		}
@@ -1542,13 +1578,21 @@ update_dns (NMDnsManager *self,
 	}
 
 	/* signal that resolv.conf was changed */
-	if (update && result == SR_SUCCESS)
+	if (   do_update
+	    && result == SR_SUCCESS)
 		g_signal_emit (self, signals[CONFIG_CHANGED], 0);
 
-	g_clear_pointer (&priv->config_variant, g_variant_unref);
+	nm_clear_pointer (&priv->config_variant, g_variant_unref);
 	_notify (self, PROP_CONFIGURATION);
 
-	return !update || result == SR_SUCCESS;
+	if (result != SR_SUCCESS) {
+		if (error)
+			g_propagate_error (error, g_steal_pointer (&local_error));
+		return FALSE;
+	}
+
+	nm_assert (!local_error);
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -1569,7 +1613,6 @@ nm_dns_manager_set_ip_config (NMDnsManager *self,
                               NMDnsIPConfigType ip_config_type)
 {
 	NMDnsManagerPrivate *priv;
-	GError *error = NULL;
 	NMDnsIPConfigData *ip_data;
 	NMDnsConfigData *data;
 	int ifindex;
@@ -1642,10 +1685,11 @@ nm_dns_manager_set_ip_config (NMDnsManager *self,
 	}
 
 changed:
-	if (   !priv->updates_queue
-	    && !update_dns (self, FALSE, &error)) {
-		_LOGW ("could not commit DNS changes: %s", error->message);
-		g_clear_error (&error);
+	if (!priv->updates_queue) {
+		gs_free_error GError *error = NULL;
+
+		if (!update_dns (self, FALSE, &error))
+			_LOGW ("could not commit DNS changes: %s", error->message);
 	}
 
 	return TRUE;
@@ -1667,7 +1711,6 @@ nm_dns_manager_set_hostname (NMDnsManager *self,
                              gboolean skip_update)
 {
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
-	GError *error = NULL;
 	const char *filtered = NULL;
 
 	/* Certain hostnames we don't want to include in resolv.conf 'searches' */
@@ -1687,9 +1730,12 @@ nm_dns_manager_set_hostname (NMDnsManager *self,
 
 	if (skip_update)
 		return;
-	if (!priv->updates_queue && !update_dns (self, FALSE, &error)) {
-		_LOGW ("could not commit DNS changes: %s", error->message);
-		g_clear_error (&error);
+
+	if (!priv->updates_queue) {
+		gs_free_error GError *error = NULL;
+
+		if (!update_dns (self, FALSE, &error))
+			_LOGW ("could not commit DNS changes: %s", error->message);
 	}
 }
 
@@ -1714,7 +1760,7 @@ void
 nm_dns_manager_end_updates (NMDnsManager *self, const char *func)
 {
 	NMDnsManagerPrivate *priv;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
 	gboolean changed;
 	guint8 new[HASH_LEN];
 
@@ -1735,10 +1781,8 @@ nm_dns_manager_end_updates (NMDnsManager *self, const char *func)
 
 	/* Commit all the outstanding changes */
 	_LOGD ("(%s): committing DNS changes (%d)", func, priv->updates_queue);
-	if (!update_dns (self, FALSE, &error)) {
+	if (!update_dns (self, FALSE, &error))
 		_LOGW ("could not commit DNS changes: %s", error->message);
-		g_clear_error (&error);
-	}
 
 	memset (priv->prev_hash, 0, sizeof (priv->prev_hash));
 }
@@ -1747,7 +1791,6 @@ void
 nm_dns_manager_stop (NMDnsManager *self)
 {
 	NMDnsManagerPrivate *priv;
-	GError *error = NULL;
 
 	priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 
@@ -1764,10 +1807,11 @@ nm_dns_manager_stop (NMDnsManager *self)
 	if (   priv->dns_touched
 	    && priv->plugin
 	    && NM_IS_DNS_DNSMASQ (priv->plugin)) {
-		if (!update_dns (self, TRUE, &error)) {
+		gs_free_error GError *error = NULL;
+
+		if (!update_dns (self, TRUE, &error))
 			_LOGW ("could not commit DNS changes on shutdown: %s", error->message);
-			g_clear_error (&error);
-		}
+
 		priv->dns_touched = FALSE;
 	}
 
@@ -2041,8 +2085,6 @@ config_changed_cb (NMConfig *config,
                    NMConfigData *old_data,
                    NMDnsManager *self)
 {
-	GError *error = NULL;
-
 	if (NM_FLAGS_ANY (changes, NM_CONFIG_CHANGE_DNS_MODE |
 	                           NM_CONFIG_CHANGE_RC_MANAGER |
 	                           NM_CONFIG_CHANGE_CAUSE_SIGHUP |
@@ -2063,10 +2105,10 @@ config_changed_cb (NMConfig *config,
 	                           NM_CONFIG_CHANGE_DNS_MODE |
 	                           NM_CONFIG_CHANGE_RC_MANAGER |
 	                           NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG)) {
-		if (!update_dns (self, FALSE, &error)) {
+		gs_free_error GError *error = NULL;
+
+		if (!update_dns (self, FALSE, &error))
 			_LOGW ("could not commit DNS changes: %s", error->message);
-			g_clear_error (&error);
-		}
 	}
 }
 
@@ -2296,7 +2338,7 @@ dispose (GObject *object)
 	c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_config_lst_head, ip_config_lst)
 		_ip_config_data_free (ip_data);
 
-	g_clear_pointer (&priv->configs, g_hash_table_destroy);
+	nm_clear_pointer (&priv->configs, g_hash_table_destroy);
 
 	nm_clear_g_source (&priv->plugin_ratelimit.timer);
 
@@ -2304,7 +2346,7 @@ dispose (GObject *object)
 
 	G_OBJECT_CLASS (nm_dns_manager_parent_class)->dispose (object);
 
-	g_clear_pointer (&priv->config_variant, g_variant_unref);
+	nm_clear_pointer (&priv->config_variant, g_variant_unref);
 }
 
 static void
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 735605ee..5581f3fe 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -140,7 +140,7 @@ create_dm_cmd_line (const char *iface,
 	 */
 	nm_strv_ptrarray_add_string_dup (cmd, "--strict-order");
 
-	nm_utils_inet4_ntop (listen_address->address, listen_address_s);
+	_nm_utils_inet4_ntop (listen_address->address, listen_address_s);
 
 	nm_strv_ptrarray_add_string_concat (cmd, "--listen-address=", listen_address_s);
 
@@ -169,7 +169,7 @@ create_dm_cmd_line (const char *iface,
 		g_string_append (s, "--dhcp-option=option:dns-server");
 		for (i = 0; i < n; i++) {
 			g_string_append_c (s, ',');
-			g_string_append (s, nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (ip4_config, i), tmpaddr));
+			g_string_append (s, _nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (ip4_config, i), tmpaddr));
 		}
 		nm_strv_ptrarray_take_gstring (cmd, &s);
 	}
@@ -339,7 +339,7 @@ nm_dnsmasq_manager_new (const char *iface)
 static void
 finalize (GObject *object)
 {
-	NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE ((NMDnsMasqManager *) object);
+	NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (object);
 
 	nm_dnsmasq_manager_stop (NM_DNSMASQ_MANAGER (object));
 
diff --git a/src/dnsmasq/nm-dnsmasq-utils.c b/src/dnsmasq/nm-dnsmasq-utils.c
index 01d59ecc..2373b422 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.c
+++ b/src/dnsmasq/nm-dnsmasq-utils.c
@@ -95,9 +95,8 @@ nm_dnsmasq_utils_get_range (const NMPlatformIP4Address *addr,
 	first = htonl (first);
 	last = htonl (last);
 
-	nm_utils_inet4_ntop (first, out_first);
-	nm_utils_inet4_ntop (last, out_last);
+	_nm_utils_inet4_ntop (first, out_first);
+	_nm_utils_inet4_ntop (last, out_last);
 
 	return TRUE;
 }
-
diff --git a/src/dnsmasq/tests/meson.build b/src/dnsmasq/tests/meson.build
index 6e429899..4584086e 100644
--- a/src/dnsmasq/tests/meson.build
+++ b/src/dnsmasq/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-dnsmasq-utils'
 
 exe = executable(
diff --git a/src/initrd/meson.build b/src/initrd/meson.build
index 09036f27..c056f3c2 100644
--- a/src/initrd/meson.build
+++ b/src/initrd/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 sources = files(
   'nmi-cmdline-reader.c',
   'nmi-dt-reader.c',
diff --git a/src/initrd/nm-initrd-generator.c b/src/initrd/nm-initrd-generator.c
index f3f53acf..dccb21f8 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/initrd/nm-initrd-generator.c
@@ -6,7 +6,7 @@
 #include "nm-default.h"
 #include "nm-core-utils.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 #include "nm-initrd-generator.h"
 #include "nm-glib-aux/nm-io-utils.h"
 
@@ -109,7 +109,7 @@ main (int argc, char *argv[])
 	if (!sysfs_dir)
 		sysfs_dir = g_strdup (DEFAULT_SYSFS_DIR);
 	if (dump_to_stdout)
-		g_clear_pointer (&connections_dir, g_free);
+		nm_clear_g_free (&connections_dir);
 
 	if (connections_dir && g_mkdir_with_parents (connections_dir, 0755) != 0) {
 		errsv = errno;
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index 2cb93a2d..2eb6d7e5 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -17,33 +17,67 @@
 
 /*****************************************************************************/
 
-static gboolean
-_connection_matches_type (gpointer key, gpointer value, gpointer user_data)
+typedef struct {
+	GHashTable *hash;
+	GPtrArray *array;
+	NMConnection *bootdev_connection;   /* connection for bootdev=$ifname */
+	NMConnection *default_connection;   /* connection not bound to any ifname */
+} Reader;
+
+static Reader *
+reader_new (void)
 {
-	NMConnection *connection = value;
-	const char *type_name = user_data;
-	NMSettingConnection *s_con;
+	Reader *reader;
 
-	s_con = nm_connection_get_setting_connection (connection);
-	if (type_name == NULL)
-		return nm_setting_connection_get_master (s_con) == NULL;
-	else
-		return strcmp (nm_setting_connection_get_connection_type (s_con), type_name) == 0;
+	reader = g_slice_new (Reader);
+	*reader = (Reader) {
+		.hash  = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref),
+		.array = g_ptr_array_new (),
+	};
+
+	return reader;
+}
+
+static GHashTable *
+reader_destroy (Reader *reader, gboolean free_hash)
+{
+	gs_unref_hashtable GHashTable *hash = NULL;
+
+	g_ptr_array_unref (reader->array);
+	hash = g_steal_pointer (&reader->hash);
+	nm_g_slice_free (reader);
+	if (!free_hash)
+		return g_steal_pointer (&hash);
+	return NULL;
+}
+
+static NMConnection *
+reader_add_connection (Reader *reader, const char *name, NMConnection *connection_take)
+{
+	char *name_dup;
+
+	name_dup = g_strdup (name);
+	if (g_hash_table_insert (reader->hash, name_dup, connection_take))
+		g_ptr_array_add (reader->array, name_dup);
+
+	return connection_take;
 }
 
+/* Returns a new connection owned by the reader */
 static NMConnection *
-add_conn (GHashTable *connections,
-          const char *basename,
-          const char *id,
-          const char *ifname,
-          const char *type_name,
-          NMConnectionMultiConnect multi_connect)
+reader_create_connection (Reader *reader,
+                          const char *basename,
+                          const char *id,
+                          const char *ifname,
+                          const char *type_name,
+                          NMConnectionMultiConnect multi_connect)
 {
 	NMConnection *connection;
 	NMSetting *setting;
 
-	connection = nm_simple_connection_new ();
-	g_hash_table_insert (connections, g_strdup (basename), connection);
+	connection = reader_add_connection (reader,
+	                                    basename,
+	                                    nm_simple_connection_new ());
 
 	/* Start off assuming dynamic IP configurations. */
 
@@ -76,44 +110,74 @@ add_conn (GHashTable *connections,
 }
 
 static NMConnection *
-get_conn (GHashTable *connections, const char *ifname, const char *type_name)
+reader_get_default_connection (Reader *reader)
 {
-	NMConnection *connection;
+	NMConnection *con;
+
+	if (!reader->default_connection) {
+		con = reader_create_connection (reader,
+		                                "default_connection",
+		                                "Wired Connection",
+		                                NULL,
+		                                NM_SETTING_WIRED_SETTING_NAME,
+		                                NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
+		reader->default_connection = con;
+	}
+	return reader->default_connection;
+}
+
+static NMConnection *
+reader_get_connection (Reader *reader,
+                       const char *ifname,
+                       const char *type_name,
+                       gboolean create_if_missing)
+{
+	NMConnection *connection = NULL;
 	NMSetting *setting;
-	const char *basename;
-	NMConnectionMultiConnect multi_connect;
 
-	if (ifname) {
-		basename = ifname;
-		multi_connect = NM_CONNECTION_MULTI_CONNECT_SINGLE;
-	} else {
-		/* This is essentially for the "ip=dhcp" scenario. */
-		basename = "default_connection";
-		multi_connect = NM_CONNECTION_MULTI_CONNECT_MULTIPLE;
-	}
+	if (!ifname) {
+		NMConnection *candidate;
+		NMSettingConnection *s_con;
+		guint i;
 
-	connection = g_hash_table_lookup (connections, (gpointer) basename);
-	if (!connection && !ifname) {
 		/*
 		 * If ifname was not given, we'll match the connection by type.
 		 * If the type was not given either, then we're happy with any connection but slaves.
 		 * This is so that things like "bond=bond0:eth1,eth2 nameserver=1.3.3.7 end up
 		 * slapping the nameserver to the most reasonable connection (bond0).
 		 */
-		connection = g_hash_table_find (connections,
-		                                _connection_matches_type,
-		                                (gpointer) type_name);
-	}
+		for (i = 0; i < reader->array->len; i++) {
+			candidate = g_hash_table_lookup (reader->hash, reader->array->pdata[i]);
+			s_con = nm_connection_get_setting_connection (candidate);
+
+			if (   type_name == NULL
+			    && nm_setting_connection_get_master (s_con) == NULL) {
+				connection = candidate;
+				break;
+			}
+
+			if (   type_name != NULL
+			    && nm_streq (nm_setting_connection_get_connection_type (s_con), type_name)) {
+				connection = candidate;
+				break;
+			}
+		}
+	} else
+		connection = g_hash_table_lookup (reader->hash, (gpointer) ifname);
 
 	if (!connection) {
+		if (!create_if_missing)
+			return NULL;
+
 		if (!type_name)
 			type_name = NM_SETTING_WIRED_SETTING_NAME;
 
-		connection = add_conn (connections, basename,
-		                       ifname ?: "Wired Connection",
-		                       ifname, type_name, multi_connect);
+		connection = reader_create_connection (reader, ifname,
+		                                       ifname ?: "Wired Connection",
+		                                       ifname, type_name,
+		                                       NM_CONNECTION_MULTI_CONNECT_SINGLE);
 	}
-	setting = (NMSetting *)nm_connection_get_setting_connection (connection);
+	setting = (NMSetting *) nm_connection_get_setting_connection (connection);
 
 	if (type_name) {
 		g_object_set (setting, NM_SETTING_CONNECTION_TYPE, type_name, NULL);
@@ -195,22 +259,27 @@ _base_setting_set (NMConnection *connection, const char *property, const char *v
 }
 
 static void
-read_all_connections_from_fw (GHashTable *connections, const char *sysfs_dir)
+reader_read_all_connections_from_fw (Reader *reader, const char *sysfs_dir)
 {
 	gs_unref_hashtable GHashTable *ibft = NULL;
-	NMConnection *connection;
-	GHashTableIter iter;
+	NMConnection *dt_connection;
 	const char *mac;
 	GHashTable *nic;
 	const char *index;
 	GError *error = NULL;
+	guint i, length;
+	gs_free const char **keys = NULL;
 
 	ibft = nmi_ibft_read (sysfs_dir);
+	keys = nm_utils_strdict_get_keys (ibft, TRUE, &length);
 
-	g_hash_table_iter_init (&iter, ibft);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &mac, (gpointer *) &nic)) {
-		connection = nm_simple_connection_new ();
+	for (i = 0; i < length; i++) {
+		gs_unref_object NMConnection *connection = NULL;
+		gs_free char *name = NULL;
 
+		mac = keys[i];
+		nic = g_hash_table_lookup (ibft, mac);
+		connection = nm_simple_connection_new ();
 		index = g_hash_table_lookup (nic, "index");
 		if (!index) {
 			_LOGW (LOGD_CORE, "Ignoring an iBFT entry without an index");
@@ -220,23 +289,20 @@ read_all_connections_from_fw (GHashTable *connections, const char *sysfs_dir)
 		if (!nmi_ibft_update_connection_from_nic (connection, nic, &error)) {
 			_LOGW (LOGD_CORE, "Unable to merge iBFT configuration: %s", error->message);
 			g_error_free (error);
+			continue;
 		}
 
-		g_hash_table_insert (connections,
-		                     g_strdup_printf ("ibft%s", index),
-		                     connection);
+		name = g_strdup_printf ("ibft%s", index);
+		reader_add_connection (reader, name, g_steal_pointer (&connection));
 	}
 
-	connection = nmi_dt_reader_parse (sysfs_dir);
-	if (connection) {
-		g_hash_table_insert (connections,
-		                     g_strdup ("ofw"),
-		                     connection);
-	}
+	dt_connection = nmi_dt_reader_parse (sysfs_dir);
+	if (dt_connection)
+		reader_add_connection (reader, "ofw", dt_connection);
 }
 
 static void
-parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
+reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 {
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4 = NULL, *s_ip6 = NULL;
@@ -299,12 +365,16 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 
 	if (ifname == NULL && (   g_strcmp0 (kind, "fw") == 0
 	                       || g_strcmp0 (kind, "ibft") == 0)) {
-		read_all_connections_from_fw (connections, sysfs_dir);
+		reader_read_all_connections_from_fw (reader, sysfs_dir);
 		return;
 	}
 
 	/* Parsing done, construct the NMConnection. */
-	connection = get_conn (connections, ifname, NULL);
+	if (ifname)
+		connection = reader_get_connection (reader, ifname, NULL, TRUE);
+	else
+		connection = reader_get_default_connection (reader);
+
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 
@@ -390,7 +460,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 		              NULL);
 		if (nm_setting_ip_config_get_num_addresses (s_ip6) == 0) {
 			g_object_set (s_ip6,
-			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
 			              NULL);
 		}
 	} else if (g_strcmp0 (kind, "dhcp6") == 0) {
@@ -445,7 +515,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 	if (gateway_ip && *gateway_ip) {
 		int addr_family = guess_ip_address_family (gateway_ip);
 
-		if (nm_utils_ipaddr_valid (addr_family, gateway_ip)) {
+		if (nm_utils_ipaddr_is_valid (addr_family, gateway_ip)) {
 			switch (addr_family) {
 			case AF_INET:
 				g_object_set (s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_ip, NULL);
@@ -470,7 +540,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 	for (i = 0; i < 2; i++) {
 		if (dns_addr_family[i] == AF_UNSPEC)
 			break;
-		if (nm_utils_ipaddr_valid (dns_addr_family[i], dns[i])) {
+		if (nm_utils_ipaddr_is_valid (dns_addr_family[i], dns[i])) {
 			switch (dns_addr_family[i]) {
 			case AF_INET:
 				nm_setting_ip_config_add_dns (s_ip4, dns[i]);
@@ -495,10 +565,10 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 }
 
 static void
-parse_master (GHashTable *connections,
-              char *argument,
-              const char *type_name,
-              const char *default_name)
+reader_parse_master (Reader *reader,
+                     char *argument,
+                     const char *type_name,
+                     const char *default_name)
 {
 	NMConnection *connection;
 	NMSettingConnection *s_con;
@@ -517,7 +587,7 @@ parse_master (GHashTable *connections,
 		master = master_to_free = g_strdup_printf ("%s0", default_name ?: type_name);
 	slaves = get_word (&argument, ':');
 
-	connection = get_conn (connections, master, type_name);
+	connection = reader_get_connection (reader, master, type_name, TRUE);
 	s_con = nm_connection_get_setting_connection (connection);
 	master = nm_setting_connection_get_uuid (s_con);
 
@@ -539,7 +609,7 @@ parse_master (GHashTable *connections,
 		if (slave == NULL)
 			slave = "eth0";
 
-		connection = get_conn (connections, slave, NULL);
+		connection = reader_get_connection (reader, slave, NULL, TRUE);
 		s_con = nm_connection_get_setting_connection (connection);
 		g_object_set (s_con,
 		              NM_SETTING_CONNECTION_SLAVE_TYPE, type_name,
@@ -554,69 +624,81 @@ parse_master (GHashTable *connections,
 }
 
 static void
-parse_rd_route (GHashTable *connections, char *argument)
+reader_add_routes (Reader *reader, GPtrArray *array)
 {
-	NMConnection *connection;
-	const char *net;
-	const char *gateway;
-	const char *interface;
-	int family = AF_UNSPEC;
-	NMIPAddr net_addr = { };
-	NMIPAddr gateway_addr = { };
-	int net_prefix = -1;
-	NMIPRoute *route;
-	NMSettingIPConfig *s_ip;
-	GError *error = NULL;
-
-	net = get_word (&argument, ':');
-	gateway = get_word (&argument, ':');
-	interface = get_word (&argument, ':');
-
-	connection = get_conn (connections, interface, NULL);
+	guint i;
+
+	for (i = 0; i < array->len; i++) {
+		NMConnection *connection = NULL;
+		const char *net;
+		const char *gateway;
+		const char *interface;
+		int family = AF_UNSPEC;
+		NMIPAddr net_addr = { };
+		NMIPAddr gateway_addr = { };
+		int net_prefix = -1;
+		NMIPRoute *route;
+		NMSettingIPConfig *s_ip;
+		char *argument;
+		gs_free_error GError *error = NULL;
+
+		argument = array->pdata[i];
+		net = get_word (&argument, ':');
+		gateway = get_word (&argument, ':');
+		interface = get_word (&argument, ':');
+
+		if (interface)
+			connection = reader_get_connection (reader, interface, NULL, TRUE);
+		if (!connection)
+			connection = reader->bootdev_connection;
+		if (!connection)
+			connection = reader_get_connection (reader, interface, NULL, FALSE);
+		if (!connection)
+			connection = reader_get_default_connection (reader);
+
+		if (net && *net) {
+			if (!nm_utils_parse_inaddr_prefix_bin (family, net, &family, &net_addr, &net_prefix)) {
+				_LOGW (LOGD_CORE, "Unrecognized address: %s", net);
+				continue;
+			}
+		}
 
-	if (net && *net) {
-		if (!nm_utils_parse_inaddr_prefix_bin (family, net, &family, &net_addr, &net_prefix)) {
-			_LOGW (LOGD_CORE, "Unrecognized address: %s", net);
-			return;
+		if (gateway && *gateway) {
+			if (!nm_utils_parse_inaddr_bin (family, gateway, &family, &gateway_addr)) {
+				_LOGW (LOGD_CORE, "Unrecognized address: %s", gateway);
+				continue;
+			}
 		}
-	}
 
-	if (gateway && *gateway) {
-		if (!nm_utils_parse_inaddr_bin (family, gateway, &family, &gateway_addr)) {
-			_LOGW (LOGD_CORE, "Unrecognized address: %s", gateway);
-			return;
+		switch (family) {
+		case AF_INET:
+			s_ip = nm_connection_get_setting_ip4_config (connection);
+			if (net_prefix == -1)
+				net_prefix = 32;
+			break;
+		case AF_INET6:
+			s_ip = nm_connection_get_setting_ip6_config (connection);
+			if (net_prefix == -1)
+				net_prefix = 128;
+			break;
+		default:
+			_LOGW (LOGD_CORE, "Unknown address family: %s", net);
+			continue;
 		}
-	}
 
-	switch (family) {
-	case AF_INET:
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		if (net_prefix == -1)
-			net_prefix = 32;
-		break;
-	case AF_INET6:
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		if (net_prefix == -1)
-			net_prefix = 128;
-		break;
-	default:
-		_LOGW (LOGD_CORE, "Unknown address family: %s", net);
-		return;
-	}
+		route = nm_ip_route_new_binary (family, &net_addr.addr_ptr, net_prefix, &gateway_addr.addr_ptr, -1, &error);
+		if (!route) {
+			g_warning ("Invalid route '%s via %s': %s\n", net, gateway, error->message);
+			continue;
+		}
 
-	route = nm_ip_route_new_binary (family, &net_addr.addr_ptr, net_prefix, &gateway_addr.addr_ptr, -1, &error);
-	if (!route) {
-		g_warning ("Invalid route '%s via %s': %s\n", net, gateway, error->message);
-		g_clear_error (&error);
-		return;
+		nm_setting_ip_config_add_route (s_ip, route);
+		nm_ip_route_unref (route);
 	}
-
-	nm_setting_ip_config_add_route (s_ip, route);
-	nm_ip_route_unref (route);
 }
 
 static void
-parse_vlan (GHashTable *connections, char *argument)
+reader_parse_vlan (Reader *reader, char *argument)
 {
 	NMConnection *connection;
 	NMSettingVlan *s_vlan;
@@ -632,90 +714,20 @@ parse_vlan (GHashTable *connections, char *argument)
 			break;
 	}
 
-	connection = get_conn (connections, vlan, NM_SETTING_VLAN_SETTING_NAME);
+	connection = reader_get_connection (reader, vlan, NM_SETTING_VLAN_SETTING_NAME, TRUE);
 
 	s_vlan = nm_connection_get_setting_vlan (connection);
 	g_object_set (s_vlan,
 	              NM_SETTING_VLAN_PARENT, phy,
-	              NM_SETTING_VLAN_ID, g_ascii_strtoull (vlanid, NULL, 10),
-	              NULL);
-
-	if (argument && *argument)
-		_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
-}
-
-static void
-parse_bootdev (GHashTable *connections, char *argument)
-{
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-
-	connection = get_conn (connections, NULL, NULL);
-
-	if (   nm_connection_get_interface_name (connection)
-	    && strcmp (nm_connection_get_interface_name (connection), argument) != 0) {
-		/* If the default connection already has an interface name,
-		 * we should not overwrite it. Create a new one instead. */
-		connection = get_conn (connections, argument, NULL);
-	}
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_INTERFACE_NAME, argument,
+	              NM_SETTING_VLAN_ID, (guint) _nm_utils_ascii_str_to_int64 (vlanid, 10, 0, G_MAXUINT, G_MAXUINT),
 	              NULL);
-}
-
-static void
-parse_nameserver (GHashTable *connections, char *argument)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
-	char *dns;
-
-	connection = get_conn (connections, NULL, NULL);
-
-	dns = get_word (&argument, '\0');
-
-	switch (guess_ip_address_family (dns)) {
-	case AF_INET:
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		break;
-	case AF_INET6:
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		break;
-	default:
-		_LOGW (LOGD_CORE, "Unknown address family: %s", dns);
-		break;
-	}
-
-	nm_setting_ip_config_add_dns (s_ip, dns);
 
 	if (argument && *argument)
 		_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
 }
 
 static void
-parse_rd_peerdns (GHashTable *connections, char *argument)
-{
-	gboolean auto_dns = !_nm_utils_ascii_str_to_bool (argument, TRUE);
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
-
-	connection = get_conn (connections, NULL, NULL);
-
-	s_ip = nm_connection_get_setting_ip4_config (connection);
-	g_object_set (s_ip,
-	              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, auto_dns,
-	              NULL);
-
-	s_ip = nm_connection_get_setting_ip6_config (connection);
-	g_object_set (s_ip,
-	              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, auto_dns,
-	              NULL);
-}
-
-static void
-parse_rd_znet (GHashTable *connections, char *argument, gboolean net_ifnames)
+reader_parse_rd_znet (Reader *reader, char *argument, gboolean net_ifnames)
 {
 	const char *nettype;
 	const char *subchannels[4] = { 0, 0, 0, 0 };
@@ -765,7 +777,7 @@ parse_rd_znet (GHashTable *connections, char *argument, gboolean net_ifnames)
 		ifname = g_strdup_printf ("%s%d", prefix, index);
 	}
 
-	connection = get_conn (connections, ifname, NM_SETTING_WIRED_SETTING_NAME);
+	connection = reader_get_connection (reader, ifname, NM_SETTING_WIRED_SETTING_NAME, TRUE);
 	s_wired = nm_connection_get_setting_wired (connection);
 	g_object_set (s_wired,
 	              NM_SETTING_WIRED_S390_NETTYPE, nettype,
@@ -795,18 +807,89 @@ _normalize_conn (gpointer key, gpointer value, gpointer user_data)
 	nm_connection_normalize (connection, NULL, NULL, NULL);
 }
 
+static void
+reader_set_ignore_auto_dns (Reader *reader)
+{
+	GHashTableIter iter;
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip = NULL;
+
+	g_hash_table_iter_init (&iter, reader->hash);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+		g_object_set (s_ip,
+		              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, TRUE,
+		              NULL);
+
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+		g_object_set (s_ip,
+		              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, TRUE,
+		              NULL);
+	}
+}
+
+static void
+reader_add_nameservers (Reader *reader, GPtrArray *nameservers)
+{
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	GHashTableIter iter;
+	int addr_family;
+	const char *ns;
+	guint i;
+
+	for (i = 0; i < nameservers->len; i++) {
+		ns = nameservers->pdata[i];
+		addr_family = guess_ip_address_family (ns);
+		if (addr_family == AF_UNSPEC) {
+			_LOGW (LOGD_CORE, "Unknown address family: %s", ns);
+			continue;
+		}
+
+		g_hash_table_iter_init (&iter, reader->hash);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
+			switch (addr_family) {
+			case AF_INET:
+				s_ip = nm_connection_get_setting_ip4_config (connection);
+				if (!NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip),
+				                   NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+				                   NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+					continue;
+				break;
+			case AF_INET6:
+				s_ip = nm_connection_get_setting_ip6_config (connection);
+				if (!NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip),
+				                   NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+				                   NM_SETTING_IP6_CONFIG_METHOD_DHCP,
+				                   NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+					continue;
+				break;
+			default:
+				nm_assert_not_reached ();
+				continue;
+			}
+
+			nm_setting_ip_config_add_dns (s_ip, ns);
+		}
+	}
+}
+
 GHashTable *
 nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 {
-	GHashTable *connections;
+	Reader *reader;
 	const char *tag;
 	gboolean ignore_bootif = FALSE;
 	gboolean neednet = FALSE;
 	gs_free char *bootif_val = NULL;
+	gs_free char *bootdev = NULL;
 	gboolean net_ifnames = TRUE;
+	gs_unref_ptrarray GPtrArray *nameservers = NULL;
+	gs_unref_ptrarray GPtrArray *routes = NULL;
+	gboolean ignore_auto_dns = FALSE;
 	int i;
 
-	connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+	reader = reader_new ();
 
 	for (i = 0; argv[i]; i++) {
 		if (strcmp (argv[i], "net.ifnames=0") == 0)
@@ -818,38 +901,49 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 	for (i = 0; argv[i]; i++) {
 		gs_free char *argument_clone = NULL;
 		char *argument;
+		char *word;
 
 		argument_clone = g_strdup (argv[i]);
 		argument = argument_clone;
 
 		tag = get_word (&argument, '=');
 		if (strcmp (tag, "ip") == 0)
-			parse_ip (connections, sysfs_dir, argument);
-		else if (strcmp (tag, "rd.route") == 0)
-			parse_rd_route (connections, argument);
-		else if (strcmp (tag, "bridge") == 0)
-			parse_master (connections, argument, NM_SETTING_BRIDGE_SETTING_NAME, "br");
+			reader_parse_ip (reader, sysfs_dir, argument);
+		else if (strcmp (tag, "rd.route") == 0) {
+			if (!routes)
+				routes = g_ptr_array_new_with_free_func (g_free);
+			g_ptr_array_add (routes, g_strdup (argument));
+		} else if (strcmp (tag, "bridge") == 0)
+			reader_parse_master (reader, argument, NM_SETTING_BRIDGE_SETTING_NAME, "br");
 		else if (strcmp (tag, "bond") == 0)
-			parse_master (connections, argument, NM_SETTING_BOND_SETTING_NAME, NULL);
+			reader_parse_master (reader, argument, NM_SETTING_BOND_SETTING_NAME, NULL);
 		else if (strcmp (tag, "team") == 0)
-			parse_master (connections, argument, NM_SETTING_TEAM_SETTING_NAME, NULL);
+			reader_parse_master (reader, argument, NM_SETTING_TEAM_SETTING_NAME, NULL);
 		else if (strcmp (tag, "vlan") == 0)
-			parse_vlan (connections, argument);
-		else if (strcmp (tag, "bootdev") == 0)
-			parse_bootdev (connections, argument);
-		else if (strcmp (tag, "nameserver") == 0)
-			parse_nameserver (connections, argument);
-		else if (strcmp (tag, "rd.peerdns") == 0)
-			parse_rd_peerdns (connections, argument);
+			reader_parse_vlan (reader, argument);
+		else if (strcmp (tag, "bootdev") == 0) {
+			g_free (bootdev);
+			bootdev = g_strdup (argument);
+		} else if (strcmp (tag, "nameserver") == 0) {
+			word = get_word (&argument, '\0');
+			if (word) {
+				if (!nameservers)
+					nameservers = g_ptr_array_new_with_free_func (g_free);
+				g_ptr_array_add (nameservers, g_strdup (word));
+			}
+			if (argument && *argument)
+				_LOGW (LOGD_CORE, "Ignoring extra: '%s'.", argument);
+		} else if (strcmp (tag, "rd.peerdns") == 0)
+			ignore_auto_dns = !_nm_utils_ascii_str_to_bool (argument, TRUE);
 		else if (strcmp (tag, "rd.iscsi.ibft") == 0 && _nm_utils_ascii_str_to_bool (argument, TRUE))
-			read_all_connections_from_fw (connections, sysfs_dir);
+			reader_read_all_connections_from_fw (reader, sysfs_dir);
 		else if (strcmp (tag, "rd.bootif") == 0)
 			ignore_bootif = !_nm_utils_ascii_str_to_bool (argument, TRUE);
 		else if (strcmp (tag, "rd.neednet") == 0)
 			neednet = _nm_utils_ascii_str_to_bool (argument, TRUE);
 		else if (strcmp (tag, "rd.znet") == 0)
-			parse_rd_znet (connections, argument, net_ifnames);
-		else if (strcasecmp (tag, "BOOTIF") == 0) {
+			reader_parse_rd_znet (reader, argument, net_ifnames);
+		else if (g_ascii_strcasecmp (tag, "BOOTIF") == 0) {
 			nm_clear_g_free (&bootif_val);
 			bootif_val = g_strdup (argument);
 		}
@@ -872,16 +966,22 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 			bootif += 3;
 		}
 
-		connection = get_conn (connections, NULL, NM_SETTING_WIRED_SETTING_NAME);
+		connection = reader_get_connection (reader, NULL, NM_SETTING_WIRED_SETTING_NAME, FALSE);
+		if (!connection)
+			connection = reader_get_default_connection (reader);
+
 		s_wired = nm_connection_get_setting_wired (connection);
 
 		if (   nm_connection_get_interface_name (connection)
 		    || (   nm_setting_wired_get_mac_address (s_wired)
 		        && !nm_utils_hwaddr_matches (nm_setting_wired_get_mac_address (s_wired), -1,
 		                                     bootif, -1))) {
-			connection = add_conn (connections, "bootif_connection", "BOOTIF Connection",
-			                       NULL, NM_SETTING_WIRED_SETTING_NAME,
-			                       NM_CONNECTION_MULTI_CONNECT_SINGLE);
+			connection = reader_create_connection (reader,
+			                                       "bootif_connection",
+			                                       "BOOTIF Connection",
+			                                       NULL,
+			                                       NM_SETTING_WIRED_SETTING_NAME,
+			                                       NM_CONNECTION_MULTI_CONNECT_SINGLE);
 			s_wired = (NMSettingWired *) nm_setting_wired_new ();
 			nm_connection_add_setting (connection, (NMSetting *) s_wired);
 		}
@@ -890,12 +990,28 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, const char *const*argv)
 		              NM_SETTING_WIRED_MAC_ADDRESS, bootif,
 		              NULL);
 	}
-	if (neednet && g_hash_table_size (connections) == 0) {
+
+	if (bootdev) {
+		NMConnection *connection;
+
+		connection = reader_get_connection (reader, bootdev, NULL, TRUE);
+		reader->bootdev_connection = connection;
+	}
+
+	if (neednet && g_hash_table_size (reader->hash) == 0) {
 		/* Make sure there's some connection. */
-		get_conn (connections, NULL, NM_SETTING_WIRED_SETTING_NAME);
+		reader_get_default_connection (reader);
 	}
 
-	g_hash_table_foreach (connections, _normalize_conn, NULL);
+	if (routes)
+		reader_add_routes (reader, routes);
+
+	if (nameservers)
+		reader_add_nameservers (reader, nameservers);
+
+	if (ignore_auto_dns)
+		reader_set_ignore_auto_dns (reader);
 
-	return connections;
+	g_hash_table_foreach (reader->hash, _normalize_conn, NULL);
+	return reader_destroy (reader, FALSE);
 }
diff --git a/src/initrd/nmi-dt-reader.c b/src/initrd/nmi-dt-reader.c
index a8677ed9..f118adc3 100644
--- a/src/initrd/nmi-dt-reader.c
+++ b/src/initrd/nmi-dt-reader.c
@@ -157,7 +157,7 @@ nmi_dt_reader_parse (const char *sysfs_dir)
 	local_hwaddr = dt_get_hwaddr_property (base, bootpath, "local-mac-address");
 	hwaddr = dt_get_hwaddr_property (base, bootpath, "mac-address");
 	if (g_strcmp0 (local_hwaddr, hwaddr) == 0)
-		g_clear_pointer (&local_hwaddr, g_free);
+		nm_clear_g_free (&local_hwaddr);
 
 	tokens = g_strsplit (path, ",", 0);
 
@@ -363,7 +363,7 @@ nmi_dt_reader_parse (const char *sysfs_dir)
 	if (!nm_connection_normalize (connection, NULL, NULL, &error)) {
 		_LOGW (LOGD_CORE, "Generated an invalid connection: %s",
 		       error->message);
-		g_clear_pointer (&connection, g_object_unref);
+		nm_clear_pointer (&connection, g_object_unref);
 	}
 
 	return g_steal_pointer (&connection);
diff --git a/src/initrd/nmi-ibft-reader.c b/src/initrd/nmi-ibft-reader.c
index 47b90ebf..477bc0bf 100644
--- a/src/initrd/nmi-ibft-reader.c
+++ b/src/initrd/nmi-ibft-reader.c
@@ -200,23 +200,23 @@ ip_setting_add_from_block (GHashTable *nic,
 		g_return_val_if_reached (FALSE);
 	}
 	g_object_set (s_ip,
-                      NM_SETTING_IP_CONFIG_METHOD, method,
-                      NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE,
+	              NM_SETTING_IP_CONFIG_METHOD, method,
+	              NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE,
 	              NULL);
 
-	if (s_gateway && !nm_utils_ipaddr_valid (family, s_gateway)) {
+	if (s_gateway && !nm_utils_ipaddr_is_valid (family, s_gateway)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "iBFT: invalid IP gateway '%s'.", s_gateway);
 		return FALSE;
 	}
 
-	if (s_dns1 && !nm_utils_ipaddr_valid (family, s_dns1)) {
+	if (s_dns1 && !nm_utils_ipaddr_is_valid (family, s_dns1)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "iBFT: invalid DNS1 address '%s'.", s_dns1);
 		return FALSE;
 	}
 
-	if (s_dns2 && !nm_utils_ipaddr_valid (family, s_dns2)) {
+	if (s_dns2 && !nm_utils_ipaddr_is_valid (family, s_dns2)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "iBFT: invalid DNS2 address '%s'.", s_dns2);
 		return FALSE;
diff --git a/src/initrd/tests/meson.build b/src/initrd/tests/meson.build
index 5dd2873c..1d0908b9 100644
--- a/src/initrd/tests/meson.build
+++ b/src/initrd/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 c_flags = test_c_flags + ['-DTEST_INITRD_DIR="@0@"'.format(meson.current_source_dir())]
 
 test_units = [
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index 8951e491..b2b06e27 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -264,7 +264,7 @@ test_if_ip6_manual (void)
 }
 
 static void
-test_multiple (void)
+test_multiple_merge (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	const char *const*ARGV = NM_MAKE_STRV ("ip=192.0.2.2:::::eth0",
@@ -307,6 +307,37 @@ test_multiple (void)
 }
 
 static void
+test_multiple_bootdev (void)
+{
+	gs_unref_hashtable GHashTable *connections = NULL;
+	const char *const*ARGV = NM_MAKE_STRV ("nameserver=1.2.3.4",
+	                                       "ip=eth3:auto6",
+	                                       "ip=eth4:dhcp",
+	                                       "bootdev=eth4");
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	g_assert (connections);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
+
+	connection = g_hash_table_lookup (connections, "eth3");
+	g_assert (connection);
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip6);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+
+	connection = g_hash_table_lookup (connections, "eth4");
+	g_assert (connection);
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	g_assert (s_ip4);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 1);
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 0), ==, "1.2.3.4");
+}
+
+static void
 test_bootdev (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
@@ -358,16 +389,16 @@ test_some_more (void)
 	g_assert (connections);
 	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
 
-	connection = g_hash_table_lookup (connections, "default_connection");
+	connection = g_hash_table_lookup (connections, "eth1");
 	g_assert (connection);
 	nmtst_assert_connection_verifies_without_normalization (connection);
 
 	s_con = nm_connection_get_setting_connection (connection);
 	g_assert (s_con);
 	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
-	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "Wired Connection");
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth1");
 	g_assert_cmpstr (nm_setting_connection_get_interface_name (s_con), ==, "eth1");
-	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
 
 	s_wired = nm_connection_get_setting_wired (connection);
 	g_assert (s_wired);
@@ -409,7 +440,8 @@ test_some_more (void)
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
 	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
-	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip6), ==, 0);
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip6), ==, 1);
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip6, 0), ==, "2001:db8:3::53");
 	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip6), ==, 1);
 	g_assert (!nm_setting_ip_config_get_gateway (s_ip6));
 	ip_route = nm_setting_ip_config_get_route (s_ip6, 0);
@@ -991,7 +1023,7 @@ test_bootif (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
 	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip6));
 }
 
@@ -1027,7 +1059,7 @@ test_bootif_hwtype (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
 	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip6));
 
 	connection = g_hash_table_lookup (connections, "bootif_connection");
@@ -1052,6 +1084,57 @@ test_bootif_hwtype (void)
 	g_assert (nm_setting_ip_config_get_may_fail (s_ip6));
 }
 
+/* Check that nameservers are assigned to all existing
+ * connections that support the specific IPv4/IPv6 address
+ * family.
+ */
+static void
+test_nameserver (void)
+{
+	gs_unref_hashtable GHashTable *connections = NULL;
+	const char *const*ARGV = NM_MAKE_STRV ("nameserver=1.1.1.1",
+	                                       "ip=eth0:dhcp",
+	                                       "ip=eth1:auto6",
+	                                       "ip=10.11.12.13::10.11.12.1:24:foo.example.com:eth2:none",
+	                                       "nameserver=1.0.0.1",
+	                                       "nameserver=[2606:4700:4700::1111]");
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV);
+	g_assert (connections);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 3);
+
+	connection = g_hash_table_lookup (connections, "eth0");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+
+	s_ip = nm_connection_get_setting_ip4_config (connection);
+	g_assert (s_ip);
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip), ==, 2);
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip, 0), ==, "1.1.1.1");
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip, 1), ==, "1.0.0.1");
+
+	connection = g_hash_table_lookup (connections, "eth1");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+
+	s_ip = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip);
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip), ==, 1);
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip, 0), ==, "2606:4700:4700::1111");
+
+	connection = g_hash_table_lookup (connections, "eth2");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+
+	s_ip = nm_connection_get_setting_ip4_config (connection);
+	g_assert (s_ip);
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip), ==, 2);
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip, 0), ==, "1.1.1.1");
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip, 1), ==, "1.0.0.1");
+}
+
 static void
 test_bootif_off (void)
 {
@@ -1075,7 +1158,9 @@ int main (int argc, char **argv)
 	g_test_add_func ("/initrd/cmdline/if_auto_with_mtu_and_mac", test_if_auto_with_mtu_and_mac);
 	g_test_add_func ("/initrd/cmdline/if_ip4_manual", test_if_ip4_manual);
 	g_test_add_func ("/initrd/cmdline/if_ip6_manual", test_if_ip6_manual);
-	g_test_add_func ("/initrd/cmdline/multiple", test_multiple);
+	g_test_add_func ("/initrd/cmdline/multiple/merge", test_multiple_merge);
+	g_test_add_func ("/initrd/cmdline/multiple/bootdev", test_multiple_bootdev);
+	g_test_add_func ("/initrd/cmdline/nameserver", test_nameserver);
 	g_test_add_func ("/initrd/cmdline/some_more", test_some_more);
 	g_test_add_func ("/initrd/cmdline/bootdev", test_bootdev);
 	g_test_add_func ("/initrd/cmdline/bond", test_bond);
diff --git a/src/main-utils.c b/src/main-utils.c
index 80222495..e17feb41 100644
--- a/src/main-utils.c
+++ b/src/main-utils.c
@@ -185,7 +185,7 @@ nm_main_utils_ensure_not_running_pidfile (const char *pidfile)
 	if (pid <= 0 || pid > 65536 || errno)
 		return;
 
-	g_clear_pointer (&contents, g_free);
+	nm_clear_g_free (&contents);
 	proc_cmdline = g_strdup_printf ("/proc/%ld/cmdline", pid);
 	if (!g_file_get_contents (proc_cmdline, &contents, &len, NULL))
 		return;
diff --git a/src/meson.build b/src/meson.build
index 748fa519..8ff9bd11 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 src_inc = include_directories('.')
 
 daemon_nm_default_dep = declare_dependency(
@@ -49,6 +51,7 @@ sources = files(
 deps = [
   daemon_nm_default_dep,
   libn_dhcp4_dep,
+  libnm_keyfile_dep,
   libnm_core_dep,
   libnm_systemd_shared_dep,
   libnm_udev_aux_dep,
@@ -86,6 +89,7 @@ sources = files(
   'devices/nm-device-tun.c',
   'devices/nm-device-veth.c',
   'devices/nm-device-vlan.c',
+  'devices/nm-device-vrf.c',
   'devices/nm-device-vxlan.c',
   'devices/nm-device-wireguard.c',
   'devices/nm-device-wpan.c',
@@ -125,7 +129,6 @@ sources = files(
   'nm-act-request.c',
   'nm-audit-manager.c',
   'nm-auth-manager.c',
-  'nm-auth-subject.c',
   'nm-auth-utils.c',
   'nm-dbus-manager.c',
   'nm-checkpoint.c',
@@ -134,8 +137,7 @@ sources = files(
   'nm-config-data.c',
   'nm-connectivity.c',
   'nm-dcb.c',
-  'nm-dhcp4-config.c',
-  'nm-dhcp6-config.c',
+  'nm-dhcp-config.c',
   'nm-dispatcher.c',
   'nm-firewall-manager.c',
   'nm-hostname-manager.c',
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c
index 020764d3..5f4594b9 100644
--- a/src/ndisc/nm-fake-ndisc.c
+++ b/src/ndisc/nm-fake-ndisc.c
@@ -62,7 +62,7 @@ struct _NMFakeRNDiscClass {
 
 G_DEFINE_TYPE (NMFakeNDisc, nm_fake_ndisc, NM_TYPE_NDISC)
 
-#define NM_FAKE_NDISC_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakeNDisc, NM_IS_FAKE_NDISC)
+#define NM_FAKE_NDISC_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakeNDisc, NM_IS_FAKE_NDISC, NMNDisc)
 
 /*****************************************************************************/
 
@@ -234,7 +234,7 @@ receive_ra (gpointer user_data)
 	NMNDiscDataInternal *rdata = ndisc->rdata;
 	FakeRa *ra = priv->ras->data;
 	NMNDiscConfigMap changed = 0;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	guint i;
 	NMNDiscDHCPLevel dhcp_level;
 
@@ -326,7 +326,7 @@ receive_ra (gpointer user_data)
 static void
 start (NMNDisc *ndisc)
 {
-	NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE ((NMFakeNDisc *) ndisc);
+	NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE (ndisc);
 	FakeRa *ra;
 
 	/* Queue up the first fake RA */
@@ -366,7 +366,7 @@ nm_fake_ndisc_new (int ifindex, const char *ifname)
 static void
 dispose (GObject *object)
 {
-	NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE ((NMFakeNDisc *) object);
+	NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE (object);
 
 	nm_clear_g_source (&priv->receive_ra_id);
 
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index 6f2815ef..b698489c 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -24,9 +24,7 @@
 
 typedef struct {
 	struct ndp *ndp;
-
-	GIOChannel *event_channel;
-	guint event_id;
+	GSource *event_source;
 } NMLndpNDiscPrivate;
 
 /*****************************************************************************/
@@ -44,14 +42,14 @@ struct _NMLndpNDiscClass {
 
 G_DEFINE_TYPE (NMLndpNDisc, nm_lndp_ndisc, NM_TYPE_NDISC)
 
-#define NM_LNDP_NDISC_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMLndpNDisc, NM_IS_LNDP_NDISC)
+#define NM_LNDP_NDISC_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMLndpNDisc, NM_IS_LNDP_NDISC, NMNDisc)
 
 /*****************************************************************************/
 
 static gboolean
 send_rs (NMNDisc *ndisc, GError **error)
 {
-	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
+	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc);
 	struct ndp_msg *msg;
 	int errsv;
 
@@ -99,10 +97,12 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	NMNDiscConfigMap changed = 0;
 	struct ndp_msgra *msgra = ndp_msgra (msg);
 	struct in6_addr gateway_addr;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	int offset;
 	int hop_limit;
 	guint32 val;
+	guint32 clamp_pltime;
+	guint32 clamp_vltime;
 
 	/* Router discovery is subject to the following RFC documents:
 	 *
@@ -167,7 +167,22 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			changed |= NM_NDISC_CONFIG_GATEWAYS;
 	}
 
-	/* Addresses & Routes */
+	/* Addresses & Routes
+	 *
+	 * The Preferred Lifetime and Valid Lifetime of PIOs are capped to Router Lifetime
+	 * and NM_NDISC_VLTIME_MULT * Preferred Lifetime, respectively.
+	 *
+	 * The Lifetime of RIOs is capped to the Router Lifetime (there is no point in
+	 * maintaining a route if it employs a dead router).
+	 *
+	 * See draft-gont-6man-slaac-renum
+	 */
+	#define NM_NDISC_VLTIME_MULT ((guint32) 48)
+	clamp_pltime = ndp_msgra_router_lifetime (msgra);
+	clamp_vltime =   (clamp_pltime < G_MAXUINT32 / NM_NDISC_VLTIME_MULT)
+	               ? clamp_pltime * NM_NDISC_VLTIME_MULT
+	               : G_MAXUINT32;
+
 	ndp_msg_opt_for_each_offset (offset, msg, NDP_MSG_OPT_PREFIX) {
 		guint8 r_plen;
 		struct in6_addr r_network;
@@ -188,7 +203,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 				.network = r_network,
 				.plen = r_plen,
 				.timestamp = now,
-				.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
+				.lifetime = NM_MIN (ndp_msg_opt_prefix_valid_time (msg, offset), clamp_vltime),
 			};
 
 			if (nm_ndisc_add_route (ndisc, &route))
@@ -201,8 +216,8 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			NMNDiscAddress address = {
 				.address = r_network,
 				.timestamp = now,
-				.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset),
-				.preferred = ndp_msg_opt_prefix_preferred_time (msg, offset),
+				.lifetime = NM_MIN (ndp_msg_opt_prefix_valid_time (msg, offset), clamp_vltime),
+				.preferred = NM_MIN (ndp_msg_opt_prefix_preferred_time (msg, offset), clamp_pltime),
 			};
 
 			if (address.preferred <= address.lifetime) {
@@ -216,7 +231,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			.gateway = gateway_addr,
 			.plen = ndp_msg_opt_route_prefix_len (msg, offset),
 			.timestamp = now,
-			.lifetime = ndp_msg_opt_route_lifetime (msg, offset),
+			.lifetime = NM_MIN (ndp_msg_opt_route_lifetime (msg, offset), clamp_pltime),
 			.preference = _route_preference_coerce (ndp_msg_opt_route_preference (msg, offset)),
 		};
 
@@ -231,7 +246,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 
 	/* DNS information */
 	ndp_msg_opt_for_each_offset(offset, msg, NDP_MSG_OPT_RDNSS) {
-		static struct in6_addr *addr;
+		struct in6_addr *addr;
 		int addr_index;
 
 		ndp_msg_opt_rdnss_for_each_addr (addr, addr_index, msg, offset) {
@@ -260,7 +275,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			NMNDiscDNSDomain dns_domain = {
 				.domain = domain,
 				.timestamp = now,
-				.lifetime = ndp_msg_opt_rdnss_lifetime (msg, offset),
+				.lifetime = ndp_msg_opt_dnssl_lifetime (msg, offset),
 			};
 
 			/* Pad the lifetime somewhat to give a bit of slack in cases
@@ -346,9 +361,9 @@ typedef struct {
 static gboolean
 send_ra (NMNDisc *ndisc, GError **error)
 {
-	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
+	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc);
 	NMNDiscDataInternal *rdata = ndisc->rdata;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	int errsv;
 	struct in6_addr *addr;
 	struct ndp_msg *msg;
@@ -484,17 +499,19 @@ receive_rs (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 }
 
 static gboolean
-event_ready (GIOChannel *source, GIOCondition condition, NMNDisc *ndisc)
+event_ready (int fd,
+             GIOCondition condition,
+             gpointer user_data)
 {
-	_nm_unused gs_unref_object NMNDisc *ndisc_keep_alive = g_object_ref (ndisc);
+	gs_unref_object NMNDisc *ndisc = g_object_ref (NM_NDISC (user_data));
 	nm_auto_pop_netns NMPNetns *netns = NULL;
-	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
+	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc);
 
 	_LOGD ("processing libndp events");
 
 	if (!nm_ndisc_netns_push (ndisc, &netns)) {
 		/* something is very wrong. Stop handling events. */
-		priv->event_id = 0;
+		nm_clear_g_source_inst (&priv->event_source);
 		return G_SOURCE_REMOVE;
 	}
 
@@ -505,17 +522,23 @@ event_ready (GIOChannel *source, GIOCondition condition, NMNDisc *ndisc)
 static void
 start (NMNDisc *ndisc)
 {
-	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
-	int fd = ndp_get_eventfd (priv->ndp);
+	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc);
+	int fd;
+
+	g_return_if_fail (!priv->event_source);
 
-	g_return_if_fail (!priv->event_channel);
-	g_return_if_fail (!priv->event_id);
+	fd = ndp_get_eventfd (priv->ndp);
 
-	priv->event_channel = g_io_channel_unix_new (fd);
-	priv->event_id = g_io_add_watch (priv->event_channel, G_IO_IN, (GIOFunc) event_ready, ndisc);
+	priv->event_source = nm_g_unix_fd_source_new (fd,
+	                                              G_IO_IN,
+	                                              G_PRIORITY_DEFAULT,
+	                                              event_ready,
+	                                              ndisc,
+	                                              NULL);
+	g_source_attach (priv->event_source, NULL);
 
 	/* Flush any pending messages to avoid using obsolete information */
-	event_ready (priv->event_channel, 0, ndisc);
+	event_ready (fd, 0, ndisc);
 
 	switch (nm_ndisc_get_node_type (ndisc)) {
 	case NM_NDISC_NODE_TYPE_HOST:
@@ -592,7 +615,7 @@ nm_lndp_ndisc_new (NMPlatform *platform,
 	                                                                              1, G_MAXINT32, NM_NDISC_ROUTER_SOLICITATION_INTERVAL_DEFAULT),
 	                      NULL);
 
-	priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
+	priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc);
 
 	errsv = ndp_open (&priv->ndp);
 
@@ -610,11 +633,10 @@ nm_lndp_ndisc_new (NMPlatform *platform,
 static void
 dispose (GObject *object)
 {
-	NMNDisc *ndisc = (NMNDisc *) object;
-	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
+	NMNDisc *ndisc = NM_NDISC (object);
+	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE (ndisc);
 
-	nm_clear_g_source (&priv->event_id);
-	g_clear_pointer (&priv->event_channel, g_io_channel_unref);
+	nm_clear_g_source_inst (&priv->event_source);
 
 	if (priv->ndp) {
 		switch (nm_ndisc_get_node_type (ndisc)) {
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index 0afca26b..bb3a92b0 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -173,7 +173,7 @@ _get_exp (char *buf, gsize buf_size, gint64 now_ns, gint64 expiry_time)
 		return "permanent";
 	l = g_snprintf (buf, buf_size,
 	                "%.4f",
-	                ((double) ((expiry_time * NM_UTILS_NS_PER_SECOND) - now_ns)) / ((double) NM_UTILS_NS_PER_SECOND));
+	                ((double) ((expiry_time * NM_UTILS_NSEC_PER_SEC) - now_ns)) / ((double) NM_UTILS_NSEC_PER_SEC));
 	nm_assert (l < buf_size);
 	return buf;
 }
@@ -442,8 +442,15 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 	}
 
 	if (existing) {
+		/* A Valid Lifetime of 0 eliminates the corresponding address(es). This deviates
+		 * from RFC4862 Section 5.5.3, item e), as recommended in IETF draft draft-gont-6man-slaac-renum.
+		 */
+		if (new->lifetime == 0) {
+			g_array_remove_index (rdata->addresses, i);
+			return TRUE;
+		}
+
 		if (from_ra) {
-			const gint32 NM_NDISC_PREFIX_LFT_MIN = 7200; /* seconds, RFC4862 5.5.3.e */
 			gint64 old_expiry_lifetime, old_expiry_preferred;
 
 			old_expiry_lifetime = get_expiry (existing);
@@ -452,25 +459,16 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 			if (new->lifetime == NM_NDISC_INFINITY)
 				existing->lifetime = NM_NDISC_INFINITY;
 			else {
-				gint64 new_lifetime, remaining_lifetime;
+				gint64 new_lifetime;
 
-				/* see RFC4862 5.5.3.e */
-				if (existing->lifetime == NM_NDISC_INFINITY)
-					remaining_lifetime = G_MAXINT64;
-				else
-					remaining_lifetime = ((gint64) existing->timestamp) + ((gint64) existing->lifetime) - ((gint64) now_s);
+				/* Honor small valid lifetimes, as discussed in
+				 * draft-gont-6man-slaac-renum, to allow for more timelier
+				 * reaction to renumbering events. This deviates from
+				 * RFC4862 Section 5.5.3, item e).
+				 */
 				new_lifetime = ((gint64) new->timestamp) + ((gint64) new->lifetime) - ((gint64) now_s);
-
-				if (   new_lifetime > (gint64) NM_NDISC_PREFIX_LFT_MIN
-				    || new_lifetime > remaining_lifetime) {
-					existing->timestamp = now_s;
-					existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1));
-				} else if (remaining_lifetime <= (gint64) NM_NDISC_PREFIX_LFT_MIN) {
-					/* keep the current lifetime. */
-				} else {
-					existing->timestamp = now_s;
-					existing->lifetime = NM_NDISC_PREFIX_LFT_MIN;
-				}
+				existing->timestamp = now_s;
+				existing->lifetime = CLAMP (new_lifetime, (gint64) 0, (gint64) (G_MAXUINT32 - 1));
 			}
 
 			if (new->preferred == NM_NDISC_INFINITY) {
@@ -487,11 +485,6 @@ nm_ndisc_add_address (NMNDisc *ndisc,
 			       || old_expiry_preferred != get_expiry_preferred (existing);
 		}
 
-		if (new->lifetime == 0) {
-			g_array_remove_index (rdata->addresses, i);
-			return TRUE;
-		}
-
 		if (   get_expiry (existing) == get_expiry (new)
 		    && get_expiry_preferred (existing) == get_expiry_preferred (new))
 			return FALSE;
@@ -676,7 +669,7 @@ nm_ndisc_add_dns_domain (NMNDisc *ndisc, const NMNDiscDNSDomain *new)
 		_different_message = g_strcmp0 (priv->last_error, error->message) != 0; \
 		_NMLOG (_different_message ? LOGL_WARN : LOGL_DEBUG, __VA_ARGS__); \
 		if (_different_message) { \
-			g_clear_pointer (&priv->last_error, g_free); \
+			nm_clear_g_free (&priv->last_error); \
 			priv->last_error = g_strdup (error->message); \
 		} \
 	} G_STMT_END
@@ -697,13 +690,13 @@ send_rs_timeout (NMNDisc *ndisc)
 	if (klass->send_rs (ndisc, &error)) {
 		_LOGD ("router solicitation sent");
 		priv->solicitations_left--;
-		g_clear_pointer (&priv->last_error, g_free);
+		nm_clear_g_free (&priv->last_error);
 	} else {
 		_MAYBE_WARN ("failure sending router solicitation: %s", error->message);
 		g_clear_error (&error);
 	}
 
-	priv->last_rs = nm_utils_get_monotonic_timestamp_s ();
+	priv->last_rs = nm_utils_get_monotonic_timestamp_sec ();
 	if (priv->solicitations_left > 0) {
 		_LOGD ("scheduling router solicitation retry in %d seconds.",
 		       (int) priv->router_solicitation_interval);
@@ -727,7 +720,7 @@ solicit_routers (NMNDisc *ndisc)
 	if (priv->send_rs_id)
 		return;
 
-	now = nm_utils_get_monotonic_timestamp_s ();
+	now = nm_utils_get_monotonic_timestamp_sec ();
 	priv->solicitations_left = priv->router_solicitations;
 
 	t = (((gint64) priv->last_rs) + priv->router_solicitation_interval) - now;
@@ -748,10 +741,10 @@ announce_router (NMNDisc *ndisc)
 	if (!nm_ndisc_netns_push (ndisc, &netns))
 		return G_SOURCE_REMOVE;
 
-	priv->last_ra = nm_utils_get_monotonic_timestamp_s ();
+	priv->last_ra = nm_utils_get_monotonic_timestamp_sec ();
 	if (klass->send_ra (ndisc, &error)) {
 		_LOGD ("router advertisement sent");
-		g_clear_pointer (&priv->last_error, g_free);
+		nm_clear_g_free (&priv->last_error);
 	} else {
 		_MAYBE_WARN ("failure sending router advertisement: %s", error->message);
 		g_clear_error (&error);
@@ -788,7 +781,7 @@ announce_router_initial (NMNDisc *ndisc)
 	priv->announcements_left = NM_NDISC_ROUTER_ADVERTISEMENTS_DEFAULT;
 
 	/* Unschedule an unsolicited resend if we are allowed to send now. */
-	if (G_LIKELY (nm_utils_get_monotonic_timestamp_s () - priv->last_ra > NM_NDISC_ROUTER_ADVERT_DELAY))
+	if (G_LIKELY (nm_utils_get_monotonic_timestamp_sec () - priv->last_ra > NM_NDISC_ROUTER_ADVERT_DELAY))
 		nm_clear_g_source (&priv->send_ra_id);
 
 	/* Schedule the initial send rather early. Clamp the delay by minimal
@@ -807,7 +800,7 @@ announce_router_solicited (NMNDisc *ndisc)
 	_LOGD ("will send an solicited router advertisement");
 
 	/* Unschedule an unsolicited resend if we are allowed to send now. */
-	if (nm_utils_get_monotonic_timestamp_s () - priv->last_ra > NM_NDISC_ROUTER_ADVERT_DELAY)
+	if (nm_utils_get_monotonic_timestamp_sec () - priv->last_ra > NM_NDISC_ROUTER_ADVERT_DELAY)
 		nm_clear_g_source (&priv->send_ra_id);
 
 	if (!priv->send_ra_id) {
@@ -965,7 +958,7 @@ nm_ndisc_dad_failed (NMNDisc *ndisc, const struct in6_addr *address, gboolean em
 		if (IN6_ARE_ADDR_EQUAL (&item->address, address)) {
 			char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
-			_LOGD ("DAD failed for discovered address %s", nm_utils_inet6_ntop (address, sbuf));
+			_LOGD ("DAD failed for discovered address %s", _nm_utils_inet6_ntop (address, sbuf));
 			changed = TRUE;
 			if (!complete_address (ndisc, item)) {
 				g_array_remove_index (rdata->addresses, i);
@@ -1031,7 +1024,7 @@ _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 	if (!_LOGD_ENABLED ())
 		return;
 
-	now_ns = nm_utils_get_monotonic_timestamp_ns ();
+	now_ns = nm_utils_get_monotonic_timestamp_nsec ();
 
 	priv = NM_NDISC_GET_PRIVATE (ndisc);
 	rdata = &priv->rdata;
@@ -1068,7 +1061,7 @@ _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 
 		inet_ntop (AF_INET6, &route->network, addrstr, sizeof (addrstr));
 		_LOGD ("  route %s/%u via %s pref %s exp %s", addrstr, (guint) route->plen,
-		       nm_utils_inet6_ntop (&route->gateway, sbuf),
+		       _nm_utils_inet6_ntop (&route->gateway, sbuf),
 		       nm_icmpv6_router_pref_to_string (route->preference, str_pref, sizeof (str_pref)),
 		       get_exp (str_exp, now_ns, route));
 	}
@@ -1245,7 +1238,7 @@ timeout_cb (gpointer user_data)
 	NMNDisc *self = user_data;
 
 	NM_NDISC_GET_PRIVATE (self)->timeout_id = 0;
-	check_timestamps (self, nm_utils_get_monotonic_timestamp_s (), 0);
+	check_timestamps (self, nm_utils_get_monotonic_timestamp_sec (), 0);
 	return G_SOURCE_REMOVE;
 }
 
@@ -1256,7 +1249,7 @@ nm_ndisc_ra_received (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
 
 	nm_clear_g_source (&priv->ra_timeout_id);
 	nm_clear_g_source (&priv->send_rs_id);
-	g_clear_pointer (&priv->last_error, g_free);
+	nm_clear_g_free (&priv->last_error);
 	check_timestamps (ndisc, now, changed);
 }
 
@@ -1265,7 +1258,7 @@ nm_ndisc_rs_received (NMNDisc *ndisc)
 {
 	NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE (ndisc);
 
-	g_clear_pointer (&priv->last_error, g_free);
+	nm_clear_g_free (&priv->last_error);
 	announce_router_solicited (ndisc);
 }
 
@@ -1370,7 +1363,7 @@ nm_ndisc_init (NMNDisc *ndisc)
 	priv->rdata.public.hop_limit = 64;
 
 	/* Start at very low number so that last_rs - router_solicitation_interval
-	 * is much lower than nm_utils_get_monotonic_timestamp_s() at startup.
+	 * is much lower than nm_utils_get_monotonic_timestamp_sec() at startup.
 	 */
 	priv->last_rs = G_MININT32;
 }
@@ -1384,7 +1377,7 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->ra_timeout_id);
 	nm_clear_g_source (&priv->send_rs_id);
 	nm_clear_g_source (&priv->send_ra_id);
-	g_clear_pointer (&priv->last_error, g_free);
+	nm_clear_g_free (&priv->last_error);
 
 	nm_clear_g_source (&priv->timeout_id);
 
diff --git a/src/ndisc/tests/meson.build b/src/ndisc/tests/meson.build
index c81e24ad..349eebba 100644
--- a/src/ndisc/tests/meson.build
+++ b/src/ndisc/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-ndisc-fake'
 
 exe = executable(
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index 8bdc053d..7a5eea01 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -157,7 +157,7 @@ static void
 test_simple (void)
 {
 	NMFakeNDisc *ndisc = ndisc_new ();
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	guint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now };
 	guint id;
 
@@ -217,9 +217,8 @@ test_everything_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 
 		g_assert_cmpint (rdata->gateways_n, ==, 1);
 		match_gateway (rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
-		g_assert_cmpint (rdata->addresses_n, ==, 2);
-		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 0);
-		match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1, 10, 10);
+		g_assert_cmpint (rdata->addresses_n, ==, 1);
+		match_address (rdata, 0, "2001:db8:a:b::1", data->timestamp1, 10, 10);
 		g_assert_cmpint (rdata->routes_n, ==, 1);
 		match_route (rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1, 10, 10);
 		g_assert_cmpint (rdata->dns_servers_n, ==, 1);
@@ -239,7 +238,7 @@ static void
 test_everything (void)
 {
 	NMFakeNDisc *ndisc = ndisc_new ();
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	guint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now };
 	guint id;
 
@@ -313,7 +312,7 @@ static void
 test_preference_order (void)
 {
 	NMFakeNDisc *ndisc = ndisc_new ();
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	guint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now };
 	guint id;
 
@@ -386,7 +385,7 @@ static void
 test_preference_changed (void)
 {
 	NMFakeNDisc *ndisc = ndisc_new ();
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	guint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now };
 	guint id;
 
@@ -440,7 +439,7 @@ success_timeout (TestData *data)
 static void
 test_dns_solicit_loop_rs_sent (NMFakeNDisc *ndisc, TestData *data)
 {
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	guint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	guint id;
 
 	if (data->rs_counter > 0 && data->rs_counter < 6) {
@@ -472,7 +471,7 @@ static void
 test_dns_solicit_loop (void)
 {
 	NMFakeNDisc *ndisc = ndisc_new ();
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	guint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now, 0 };
 	guint id;
 
diff --git a/src/nm-act-request.c b/src/nm-act-request.c
index ef36a40d..1c252052 100644
--- a/src/nm-act-request.c
+++ b/src/nm-act-request.c
@@ -19,7 +19,7 @@
 #include "devices/nm-device.h"
 #include "nm-active-connection.h"
 #include "settings/nm-settings-connection.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 
 typedef struct {
 	char *table;
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index bf50e55d..63dd8910 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -14,7 +14,7 @@
 #include "nm-simple-connection.h"
 #include "nm-auth-utils.h"
 #include "nm-auth-manager.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-keep-alive.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
@@ -138,7 +138,8 @@ static void auth_complete (NMActiveConnection *self, gboolean result, const char
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, NMActiveConnectionState,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_state_to_string, NMActiveConnectionState,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_UNKNOWN,      "unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVE_CONNECTION_STATE_ACTIVATING,   "activating"),
@@ -152,7 +153,8 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, NMActiveConnectionState,
 /* the maximum required buffer size for _state_flags_to_string(). */
 #define _NM_ACTIVATION_STATE_FLAG_TO_STRING_BUFSIZE (255)
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_state_flags_to_string, NMActivationStateFlags,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_state_flags_to_string, NMActivationStateFlags,
 	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_NONE,                                 "none"),
 	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_MASTER,                            "is-master"),
 	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_SLAVE,                             "is-slave"),
@@ -279,7 +281,7 @@ nm_active_connection_set_state (NMActiveConnection *self,
 		    priv->pending_activation_id)
 		{
 			nm_device_remove_pending_action (priv->device, priv->pending_activation_id, TRUE);
-			g_clear_pointer (&priv->pending_activation_id, g_free);
+			nm_clear_g_free (&priv->pending_activation_id);
 		}
 	}
 
@@ -598,7 +600,9 @@ nm_active_connection_get_user_requested (NMActiveConnection *self)
 {
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
 
-	return nm_auth_subject_is_unix_process (NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->subject);
+	return nm_auth_subject_get_subject_type (
+	           NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->subject
+	           ) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
 }
 
 NMDevice *
@@ -941,7 +945,7 @@ static void
 _settings_connection_flags_changed (NMSettingsConnection *settings_connection,
                                     NMActiveConnection *self)
 {
-	GError *error = NULL;
+	NMDevice *device;
 
 	nm_assert (NM_IS_ACTIVE_CONNECTION (self));
 	nm_assert (NM_IS_SETTINGS_CONNECTION (settings_connection));
@@ -953,12 +957,17 @@ _settings_connection_flags_changed (NMSettingsConnection *settings_connection,
 		return;
 
 	_set_activation_type_managed (self);
-	if (!nm_device_reapply (nm_active_connection_get_device (self),
-	                        nm_settings_connection_get_connection ((nm_active_connection_get_settings_connection (self))),
-	                        &error)) {
-		_LOGW ("failed to reapply new device settings on previously externally managed device: %s",
-		       error->message);
-		g_error_free (error);
+
+	device = nm_active_connection_get_device (self);
+	if (device) {
+		gs_free_error GError *error = NULL;
+
+		if (!nm_device_reapply (device,
+		                        nm_settings_connection_get_connection (nm_active_connection_get_settings_connection (self)),
+		                        &error)) {
+			_LOGW ("failed to reapply new device settings on previously externally managed device: %s",
+			       error->message);
+		}
 	}
 }
 
@@ -1230,7 +1239,7 @@ _device_cleanup (NMActiveConnection *self)
 
 	if (priv->pending_activation_id) {
 		nm_device_remove_pending_action (priv->device, priv->pending_activation_id, TRUE);
-		g_clear_pointer (&priv->pending_activation_id, g_free);
+		nm_clear_g_free (&priv->pending_activation_id);
 	}
 
 	g_clear_object (&priv->device);
@@ -1242,7 +1251,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE ((NMActiveConnection *) object);
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (object);
 	char **strv;
 	NMDevice *master_device = NULL;
 
@@ -1396,7 +1405,7 @@ set_property (GObject *object, guint prop_id,
 	case PROP_SPECIFIC_OBJECT:
 		/* construct-only */
 		tmp = g_value_get_string (value);
-		tmp = nm_utils_dbus_normalize_object_path (tmp);
+		tmp = nm_dbus_path_not_empty (tmp);
 		priv->specific_object = g_strdup (tmp);
 		break;
 	case PROP_DEFAULT:
diff --git a/src/nm-audit-manager.c b/src/nm-audit-manager.c
index b219b35a..26ba48f5 100644
--- a/src/nm-audit-manager.c
+++ b/src/nm-audit-manager.c
@@ -11,8 +11,9 @@
 #include <libaudit.h>
 #endif
 
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-config.h"
+#include "nm-dbus-manager.h"
 #include "settings/nm-settings-connection.h"
 
 /*****************************************************************************/
@@ -195,11 +196,12 @@ _audit_log_helper (NMAuditManager *self,
 		else if (G_IS_DBUS_METHOD_INVOCATION (subject_context)) {
 			GDBusMethodInvocation *context = subject_context;
 
-			subject = subject_free = nm_auth_subject_new_unix_process_from_context (context);
+			subject = subject_free = nm_dbus_manager_new_auth_subject_from_context (context);
 		} else
 			g_warn_if_reached ();
 	}
-	if (subject && nm_auth_subject_is_unix_process (subject)) {
+	if (subject &&
+	    nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
 		pid = nm_auth_subject_get_unix_process_pid (subject);
 		uid = nm_auth_subject_get_unix_process_uid (subject);
 		if (pid != G_MAXULONG) {
diff --git a/src/nm-auth-manager.c b/src/nm-auth-manager.c
index 3f248aee..88e6cb94 100644
--- a/src/nm-auth-manager.c
+++ b/src/nm-auth-manager.c
@@ -322,7 +322,7 @@ nm_auth_manager_check_authorization (NMAuthManager *self,
 	};
 	c_list_link_tail (&priv->calls_lst_head, &call_id->calls_lst);
 
-	if (nm_auth_subject_is_internal (subject)) {
+	if (nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL) {
 		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (succeeding for internal request)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
 		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
 	} else if (nm_auth_subject_get_unix_process_uid (subject) == 0) {
@@ -339,7 +339,7 @@ nm_auth_manager_check_authorization (NMAuthManager *self,
 		GVariant *subject_value;
 		GVariant *details_value;
 
-		subject_value = nm_auth_subject_unix_process_to_polkit_gvariant (subject);
+		subject_value = nm_auth_subject_unix_to_polkit_gvariant (subject);
 		nm_assert (g_variant_is_floating (subject_value));
 
 		/* ((PolkitDetails *)NULL) */
@@ -465,7 +465,7 @@ nm_auth_manager_force_shutdown (NMAuthManager *self)
 static void
 set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE ((NMAuthManager *) object);
+	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (object);
 	int v_int;
 
 	switch (prop_id) {
diff --git a/src/nm-auth-manager.h b/src/nm-auth-manager.h
index 33e3bb2c..0a17055e 100644
--- a/src/nm-auth-manager.h
+++ b/src/nm-auth-manager.h
@@ -6,18 +6,24 @@
 #ifndef NM_AUTH_MANAGER_H
 #define NM_AUTH_MANAGER_H
 
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-config-data.h"
 
 /*****************************************************************************/
 
 typedef enum {
-	NM_AUTH_CALL_RESULT_UNKNOWN,
-	NM_AUTH_CALL_RESULT_YES,
-	NM_AUTH_CALL_RESULT_AUTH,
-	NM_AUTH_CALL_RESULT_NO,
+	NM_AUTH_CALL_RESULT_UNKNOWN = NM_CLIENT_PERMISSION_RESULT_UNKNOWN,
+	NM_AUTH_CALL_RESULT_YES     = NM_CLIENT_PERMISSION_RESULT_YES,
+	NM_AUTH_CALL_RESULT_AUTH    = NM_CLIENT_PERMISSION_RESULT_AUTH,
+	NM_AUTH_CALL_RESULT_NO      = NM_CLIENT_PERMISSION_RESULT_NO,
 } NMAuthCallResult;
 
+static inline NMClientPermissionResult
+nm_auth_call_result_to_client (NMAuthCallResult result)
+{
+	return (NMClientPermissionResult) result;
+}
+
 static inline NMAuthCallResult
 nm_auth_call_result_eval (gboolean is_authorized,
                           gboolean is_challenge,
diff --git a/src/nm-auth-subject.c b/src/nm-auth-subject.c
deleted file mode 100644
index ecf6b013..00000000
--- a/src/nm-auth-subject.c
+++ /dev/null
@@ -1,432 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2013 - 2014 Red Hat, Inc.
- */
-
-/**
- * SECTION:nm-auth-subject
- * @short_description: Encapsulates authentication information about a requestor
- *
- * #NMAuthSubject encpasulates identifying information about an entity that
- * makes requests, like process identifier and user UID.
- */
-
-#include "nm-default.h"
-
-#include "nm-auth-subject.h"
-
-#include <stdlib.h>
-
-#include "nm-dbus-manager.h"
-
-enum {
-	PROP_0,
-	PROP_SUBJECT_TYPE,
-	PROP_UNIX_PROCESS_DBUS_SENDER,
-	PROP_UNIX_PROCESS_PID,
-	PROP_UNIX_PROCESS_UID,
-
-	PROP_LAST,
-};
-
-typedef struct {
-	NMAuthSubjectType subject_type;
-	struct {
-		gulong pid;
-		gulong uid;
-		guint64 start_time;
-		char *dbus_sender;
-	} unix_process;
-} NMAuthSubjectPrivate;
-
-struct _NMAuthSubject {
-	GObject parent;
-	NMAuthSubjectPrivate _priv;
-};
-
-struct _NMAuthSubjectClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMAuthSubject, nm_auth_subject, G_TYPE_OBJECT)
-
-#define NM_AUTH_SUBJECT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMAuthSubject, NM_IS_AUTH_SUBJECT)
-
-/*****************************************************************************/
-
-#define CHECK_SUBJECT(self, error_value) \
-	NMAuthSubjectPrivate *priv; \
-	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (self), error_value); \
-	priv = NM_AUTH_SUBJECT_GET_PRIVATE (self); \
-
-#define CHECK_SUBJECT_TYPED(self, expected_subject_type, error_value) \
-	CHECK_SUBJECT (self, error_value); \
-	g_return_val_if_fail (priv->subject_type == (expected_subject_type), error_value);
-
-const char *
-nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len)
-{
-	CHECK_SUBJECT (self, NULL);
-
-	switch (priv->subject_type) {
-	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
-		g_snprintf (buf, buf_len, "unix-process[pid=%lu, uid=%lu, start=%llu]",
-		            (unsigned long) priv->unix_process.pid,
-		            (unsigned long) priv->unix_process.uid,
-		            (unsigned long long) priv->unix_process.start_time);
-		break;
-	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
-		g_strlcpy (buf, "internal", buf_len);
-		break;
-	default:
-		g_strlcpy (buf, "invalid", buf_len);
-		break;
-	}
-	return buf;
-}
-
-/* returns a floating variant */
-GVariant *
-nm_auth_subject_unix_process_to_polkit_gvariant (NMAuthSubject *self)
-{
-	GVariantBuilder builder;
-	GVariant *dict;
-	GVariant *ret;
-	CHECK_SUBJECT_TYPED (self, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL);
-
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-	g_variant_builder_add (&builder, "{sv}", "pid",
-	                       g_variant_new_uint32 (priv->unix_process.pid));
-	g_variant_builder_add (&builder, "{sv}", "start-time",
-	                       g_variant_new_uint64 (priv->unix_process.start_time));
-	g_variant_builder_add (&builder, "{sv}", "uid",
-	                       g_variant_new_int32 (priv->unix_process.uid));
-	dict = g_variant_builder_end (&builder);
-	ret = g_variant_new ("(s@a{sv})", "unix-process", dict);
-	return ret;
-}
-
-NMAuthSubjectType
-nm_auth_subject_get_subject_type (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT (subject, NM_AUTH_SUBJECT_TYPE_INVALID);
-
-	return priv->subject_type;
-}
-
-gboolean
-nm_auth_subject_is_internal (NMAuthSubject *subject)
-{
-	return nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL;
-}
-
-gboolean
-nm_auth_subject_is_unix_process (NMAuthSubject *subject)
-{
-	return nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
-}
-
-gulong
-nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);
-
-	return priv->unix_process.pid;
-}
-
-gulong
-nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, G_MAXULONG);
-
-	return priv->unix_process.uid;
-}
-
-const char *
-nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject)
-{
-	CHECK_SUBJECT_TYPED (subject, NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL);
-
-	return priv->unix_process.dbus_sender;
-}
-
-/*****************************************************************************/
-
-static NMAuthSubject *
-_new_unix_process (GDBusMethodInvocation *context,
-                   GDBusConnection *connection,
-                   GDBusMessage *message)
-{
-	NMAuthSubject *self;
-	const char *dbus_sender = NULL;
-	gulong uid = 0;
-	gulong pid = 0;
-	gboolean success;
-
-	g_return_val_if_fail (context || (connection && message), NULL);
-
-	if (context) {
-		success = nm_dbus_manager_get_caller_info (nm_dbus_manager_get (),
-		                                           context,
-		                                           &dbus_sender,
-		                                           &uid,
-		                                           &pid);
-	} else {
-		nm_assert (message);
-		success = nm_dbus_manager_get_caller_info_from_message (nm_dbus_manager_get (),
-		                                                        connection,
-		                                                        message,
-		                                                        &dbus_sender,
-		                                                        &uid,
-		                                                        &pid);
-	}
-
-	if (!success)
-		return NULL;
-
-	g_return_val_if_fail (dbus_sender && *dbus_sender, NULL);
-	/* polkit glib library stores uid and pid as int. There might be some
-	 * pitfalls if the id ever happens to be larger then that. Just assert against
-	 * it here. */
-	g_return_val_if_fail (uid <= MIN (G_MAXINT, G_MAXINT32), NULL);
-	g_return_val_if_fail (pid > 0 && pid <= MIN (G_MAXINT, G_MAXINT32), NULL);
-
-	self = NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
-	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
-	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, dbus_sender,
-	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_PID, (gulong) pid,
-	                                      NM_AUTH_SUBJECT_UNIX_PROCESS_UID, (gulong) uid,
-	                                      NULL));
-
-	if (NM_AUTH_SUBJECT_GET_PRIVATE (self)->subject_type != NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
-		/* this most likely happened because the process is gone (start_time==0).
-		 * Either that is not assert-worthy, or constructed() already asserted.
-		 * Just return NULL. */
-		g_clear_object (&self);
-	}
-	return self;
-}
-
-NMAuthSubject *
-nm_auth_subject_new_unix_process_from_context (GDBusMethodInvocation *context)
-{
-	return _new_unix_process (context, NULL, NULL);
-}
-
-NMAuthSubject *
-nm_auth_subject_new_unix_process_from_message (GDBusConnection *connection,
-                                               GDBusMessage *message)
-{
-	return _new_unix_process (NULL, connection, message);
-}
-
-/**
- * nm_auth_subject_new_internal():
- *
- * Creates a new auth subject representing the NetworkManager process itself.
- *
- * Returns: the new #NMAuthSubject
- */
-NMAuthSubject *
-nm_auth_subject_new_internal (void)
-{
-	return NM_AUTH_SUBJECT (g_object_new (NM_TYPE_AUTH_SUBJECT,
-	                                      NM_AUTH_SUBJECT_SUBJECT_TYPE, (int) NM_AUTH_SUBJECT_TYPE_INTERNAL,
-	                                      NULL));
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
-{
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE ((NMAuthSubject *) object);
-
-	switch (prop_id) {
-	case PROP_SUBJECT_TYPE:
-		g_value_set_int (value, priv->subject_type);
-		break;
-	case PROP_UNIX_PROCESS_DBUS_SENDER:
-		g_value_set_string (value, priv->unix_process.dbus_sender);
-		break;
-	case PROP_UNIX_PROCESS_PID:
-		g_value_set_ulong (value, priv->unix_process.pid);
-		break;
-	case PROP_UNIX_PROCESS_UID:
-		g_value_set_ulong (value, priv->unix_process.uid);
-		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)
-{
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE ((NMAuthSubject *) object);
-	NMAuthSubjectType subject_type;
-	int i;
-	const char *str;
-	gulong id;
-
-	switch (prop_id) {
-	case PROP_SUBJECT_TYPE:
-		/* construct-only */
-		i = g_value_get_int (value);
-		g_return_if_fail (NM_IN_SET (i, (int) NM_AUTH_SUBJECT_TYPE_INTERNAL, (int) NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS));
-		subject_type = i;
-		priv->subject_type |= subject_type;
-		g_return_if_fail (priv->subject_type == subject_type);
-		break;
-	case PROP_UNIX_PROCESS_DBUS_SENDER:
-		/* construct-only */
-		if ((str = g_value_get_string (value))) {
-			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
-			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
-			priv->unix_process.dbus_sender = g_strdup (str);
-		}
-		break;
-	case PROP_UNIX_PROCESS_PID:
-		/* construct-only */
-		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
-			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
-			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
-			priv->unix_process.pid = id;
-		}
-		break;
-	case PROP_UNIX_PROCESS_UID:
-		/* construct-only */
-		if ((id = g_value_get_ulong (value)) != G_MAXULONG) {
-			priv->subject_type |= NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS;
-			g_return_if_fail (priv->subject_type == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
-			priv->unix_process.uid = id;
-		}
-		break;
-	default:
-		 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		 break;
-	}
-}
-
-static void
-_clear_private (NMAuthSubject *self)
-{
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);
-
-	priv->subject_type = NM_AUTH_SUBJECT_TYPE_INVALID;
-	priv->unix_process.pid = G_MAXULONG;
-	priv->unix_process.uid = G_MAXULONG;
-	g_clear_pointer (&priv->unix_process.dbus_sender, g_free);
-}
-
-static void
-nm_auth_subject_init (NMAuthSubject *self)
-{
-	_clear_private (self);
-}
-
-static void
-constructed (GObject *object)
-{
-	NMAuthSubject *self = NM_AUTH_SUBJECT (object);
-	NMAuthSubjectPrivate *priv = NM_AUTH_SUBJECT_GET_PRIVATE (self);
-
-	/* validate that the created instance. */
-
-	switch (priv->subject_type) {
-	case NM_AUTH_SUBJECT_TYPE_INTERNAL:
-		priv->unix_process.pid = G_MAXULONG;
-		priv->unix_process.uid = 0;  /* internal uses 'root' user */
-		return;
-	case NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS:
-		/* Ensure pid and uid to be representable as int32.
-		 * DBUS treats them as uint32, polkit library as int. */
-		if (priv->unix_process.pid > MIN (G_MAXINT, G_MAXINT32))
-			break;
-		if (priv->unix_process.uid > MIN (G_MAXINT, G_MAXINT32)) {
-			/* for uid==-1, libpolkit-gobject-1 detects the user based on the process id.
-			 * Don't bother and require the user id as parameter. */
-			break;
-		}
-		if (!priv->unix_process.dbus_sender || !*priv->unix_process.dbus_sender)
-			break;
-
-		priv->unix_process.start_time = nm_utils_get_start_time_for_pid (priv->unix_process.pid, NULL, NULL);
-
-		if (!priv->unix_process.start_time) {
-			/* Is the process already gone? Then fail creation of the auth subject
-			 * by clearing the type. */
-			if (kill (priv->unix_process.pid, 0) != 0)
-				_clear_private (self);
-
-			/* Otherwise, although we didn't detect a start_time, the process is still around.
-			 * That could be due to procfs mounted with hidepid. So just accept the request.
-			 *
-			 * Polkit on the other side, will accept 0 and try to lookup /proc/$PID/stat
-			 * itself (and if it fails to do so, assume a start-time of 0 and proceed).
-			 * The only combination that would fail here, is when NM is able to read the
-			 * start-time, but polkit is not. */
-		}
-		return;
-	default:
-		break;
-	}
-
-	_clear_private (self);
-	g_return_if_reached ();
-}
-
-static void
-finalize (GObject *object)
-{
-	_clear_private ((NMAuthSubject *) object);
-
-	G_OBJECT_CLASS (nm_auth_subject_parent_class)->finalize (object);
-}
-
-static void
-nm_auth_subject_class_init (NMAuthSubjectClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->set_property = set_property;
-	object_class->constructed = constructed;
-	object_class->finalize = finalize;
-
-	g_object_class_install_property
-	    (object_class, PROP_SUBJECT_TYPE,
-	     g_param_spec_int (NM_AUTH_SUBJECT_SUBJECT_TYPE, "", "",
-	                       NM_AUTH_SUBJECT_TYPE_INVALID,
-	                       NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS,
-	                       NM_AUTH_SUBJECT_TYPE_INVALID,
-	                       G_PARAM_READWRITE |
-	                       G_PARAM_CONSTRUCT_ONLY |
-	                       G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	    (object_class, PROP_UNIX_PROCESS_DBUS_SENDER,
-	     g_param_spec_string (NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	     (object_class, PROP_UNIX_PROCESS_PID,
-	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_PID, "", "",
-	                          0, G_MAXULONG, G_MAXULONG,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS));
-
-	g_object_class_install_property
-	     (object_class, PROP_UNIX_PROCESS_UID,
-	      g_param_spec_ulong (NM_AUTH_SUBJECT_UNIX_PROCESS_UID, "", "",
-	                          0, G_MAXULONG, G_MAXULONG,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS));
-
-}
diff --git a/src/nm-auth-subject.h b/src/nm-auth-subject.h
deleted file mode 100644
index 7a75aca9..00000000
--- a/src/nm-auth-subject.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2013 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_AUTH_SUBJECT_H__
-#define __NETWORKMANAGER_AUTH_SUBJECT_H__
-
-#define NM_TYPE_AUTH_SUBJECT            (nm_auth_subject_get_type ())
-#define NM_AUTH_SUBJECT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubject))
-#define NM_AUTH_SUBJECT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass))
-#define NM_IS_AUTH_SUBJECT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_AUTH_SUBJECT))
-#define NM_IS_AUTH_SUBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_AUTH_SUBJECT))
-#define NM_AUTH_SUBJECT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_AUTH_SUBJECT, NMAuthSubjectClass))
-
-typedef enum {
-	NM_AUTH_SUBJECT_TYPE_INVALID      = 0,
-	NM_AUTH_SUBJECT_TYPE_INTERNAL     = 1,
-	NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS = 2,
-} NMAuthSubjectType;
-
-#define NM_AUTH_SUBJECT_SUBJECT_TYPE               "subject-type"
-#define NM_AUTH_SUBJECT_UNIX_PROCESS_DBUS_SENDER   "unix-process-dbus-sender"
-#define NM_AUTH_SUBJECT_UNIX_PROCESS_PID           "unix-process-pid"
-#define NM_AUTH_SUBJECT_UNIX_PROCESS_UID           "unix-process-uid"
-
-typedef struct _NMAuthSubjectClass NMAuthSubjectClass;
-
-GType nm_auth_subject_get_type (void);
-
-NMAuthSubject *nm_auth_subject_new_internal (void);
-
-NMAuthSubject *nm_auth_subject_new_unix_process_from_context (GDBusMethodInvocation *context);
-
-NMAuthSubject *nm_auth_subject_new_unix_process_from_message (GDBusConnection *connection, GDBusMessage *message);
-
-NMAuthSubjectType nm_auth_subject_get_subject_type (NMAuthSubject *subject);
-
-gboolean nm_auth_subject_is_internal (NMAuthSubject *subject);
-
-gboolean nm_auth_subject_is_unix_process (NMAuthSubject *subject);
-
-gulong nm_auth_subject_get_unix_process_pid (NMAuthSubject *subject);
-
-const char *nm_auth_subject_get_unix_process_dbus_sender (NMAuthSubject *subject);
-
-gulong nm_auth_subject_get_unix_process_uid (NMAuthSubject *subject);
-
-const char *nm_auth_subject_to_string (NMAuthSubject *self, char *buf, gsize buf_len);
-
-GVariant *  nm_auth_subject_unix_process_to_polkit_gvariant (NMAuthSubject *self);
-
-#endif /* __NETWORKMANAGER_AUTH_SUBJECT_H__ */
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index a5b951ab..ca2870db 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -9,13 +9,14 @@
 
 #include "nm-glib-aux/nm-c-list.h"
 #include "nm-setting-connection.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-auth-manager.h"
 #include "nm-session-monitor.h"
+#include "nm-dbus-manager.h"
 
 /*****************************************************************************/
 
-struct NMAuthChain {
+struct _NMAuthChain {
 
 	CList parent_lst;
 
@@ -265,6 +266,14 @@ nm_auth_chain_get_subject (NMAuthChain *self)
 	return self->subject;
 }
 
+GDBusMethodInvocation *
+nm_auth_chain_get_context (NMAuthChain *self)
+{
+	g_return_val_if_fail (self, NULL);
+
+	return self->context;
+}
+
 /*****************************************************************************/
 
 static void
@@ -341,8 +350,10 @@ nm_auth_chain_add_call_unsafe (NMAuthChain *self,
 	g_return_if_fail (!self->is_finishing);
 	g_return_if_fail (!self->is_destroyed);
 	g_return_if_fail (permission && *permission);
-	nm_assert (   nm_auth_subject_is_unix_process (self->subject)
-	           || nm_auth_subject_is_internal (self->subject));
+	nm_assert (   nm_auth_subject_get_subject_type (self->subject)
+	           == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS
+	           || nm_auth_subject_get_subject_type (self->subject)
+	           == NM_AUTH_SUBJECT_TYPE_INTERNAL);
 
 	/* duplicate permissions are not supported, also because nm_auth_chain_get_result()
 	 * can only return one-permission. */
@@ -395,7 +406,7 @@ nm_auth_chain_new_context (GDBusMethodInvocation *context,
 	g_return_val_if_fail (context, NULL);
 	nm_assert (done_func);
 
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_dbus_manager_new_auth_subject_from_context (context);
 	if (!subject)
 		return NULL;
 
@@ -416,8 +427,10 @@ nm_auth_chain_new_subject (NMAuthSubject *subject,
 	NMAuthChain *self;
 
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
-	nm_assert (   nm_auth_subject_is_unix_process (subject)
-	           || nm_auth_subject_is_internal (subject));
+	nm_assert (   nm_auth_subject_get_subject_type (subject)
+	           == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS
+	           || nm_auth_subject_get_subject_type (subject)
+	           == NM_AUTH_SUBJECT_TYPE_INTERNAL);
 	nm_assert (done_func);
 
 	self = g_slice_new (NMAuthChain);
@@ -504,10 +517,12 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 
 	g_return_val_if_fail (connection, FALSE);
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), FALSE);
-	nm_assert (   nm_auth_subject_is_internal (subject)
-	           || nm_auth_subject_is_unix_process (subject));
+	nm_assert (   nm_auth_subject_get_subject_type (subject)
+	              == NM_AUTH_SUBJECT_TYPE_INTERNAL
+	           || nm_auth_subject_get_subject_type (subject)
+	              == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
 
-	if (nm_auth_subject_is_internal (subject))
+	if (nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL)
 		return TRUE;
 
 	uid = nm_auth_subject_get_unix_process_uid (subject);
diff --git a/src/nm-auth-utils.h b/src/nm-auth-utils.h
index 808d20fd..978d607a 100644
--- a/src/nm-auth-utils.h
+++ b/src/nm-auth-utils.h
@@ -12,7 +12,7 @@
 
 /*****************************************************************************/
 
-typedef struct NMAuthChain NMAuthChain;
+typedef struct _NMAuthChain NMAuthChain;
 
 typedef void (*NMAuthChainResultFunc) (NMAuthChain *chain,
                                        GDBusMethodInvocation *context,
@@ -53,6 +53,8 @@ void nm_auth_chain_destroy (NMAuthChain *chain);
 
 NMAuthSubject *nm_auth_chain_get_subject (NMAuthChain *self);
 
+GDBusMethodInvocation *nm_auth_chain_get_context (NMAuthChain *self);
+
 /*****************************************************************************/
 
 struct CList;
diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c
index 734a40e1..3a372667 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -9,7 +9,7 @@
 
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-core-utils.h"
 #include "nm-dbus-interface.h"
 #include "devices/nm-device.h"
@@ -585,7 +585,7 @@ nm_checkpoint_adjust_rollback_timeout (NMCheckpoint *self, guint32 add_timeout)
 	if (add_timeout == 0)
 		rollback_timeout_s = 0;
 	else {
-		now_ms = nm_utils_get_monotonic_timestamp_ms ();
+		now_ms = nm_utils_get_monotonic_timestamp_msec ();
 		add_timeout_ms = ((gint64) add_timeout) * 1000;
 		rollback_timeout_ms = (now_ms - priv->created_at_ms) + add_timeout_ms;
 
@@ -626,7 +626,7 @@ get_property (GObject *object, guint prop_id,
 	case PROP_CREATED:
 		g_value_set_int64 (value,
 		                   nm_utils_monotonic_timestamp_as_boottime (priv->created_at_ms,
-		                                                             NM_UTILS_NS_PER_MSEC));
+		                                                             NM_UTILS_NSEC_PER_MSEC));
 		break;
 	case PROP_ROLLBACK_TIMEOUT:
 		g_value_set_uint (value, priv->rollback_timeout_s);
@@ -679,7 +679,7 @@ nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_time
 	priv = NM_CHECKPOINT_GET_PRIVATE (self);
 	priv->manager = g_object_ref (manager);
 	priv->rollback_timeout_s = rollback_timeout_s;
-	priv->created_at_ms = nm_utils_get_monotonic_timestamp_ms ();
+	priv->created_at_ms = nm_utils_get_monotonic_timestamp_msec ();
 	priv->flags = flags;
 
 	if (rollback_timeout_s != 0) {
@@ -723,8 +723,8 @@ dispose (GObject *object)
 
 	nm_assert (c_list_is_empty (&self->checkpoints_lst));
 
-	g_clear_pointer (&priv->devices, g_hash_table_unref);
-	g_clear_pointer (&priv->connection_uuids, g_hash_table_unref);
+	nm_clear_pointer (&priv->devices, g_hash_table_unref);
+	nm_clear_pointer (&priv->connection_uuids, g_hash_table_unref);
 	nm_clear_pointer (&priv->removed_devices, g_ptr_array_unref);
 
 	nm_clear_g_signal_handler (priv->manager, &priv->dev_removed_id);
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index c787aa98..29dafa14 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -11,7 +11,7 @@
 #include "nm-config.h"
 #include "devices/nm-device.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 /*****************************************************************************/
 
@@ -1024,8 +1024,8 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 		if (strv) {
 			_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
 			for (i = 0, j = 0; strv[i]; i++) {
-				if (   nm_utils_ipaddr_valid (AF_INET, strv[i])
-				    || nm_utils_ipaddr_valid (AF_INET6, strv[i]))
+				if (   nm_utils_ipaddr_is_valid (AF_INET, strv[i])
+				    || nm_utils_ipaddr_is_valid (AF_INET6, strv[i]))
 					strv[j++] = strv[i];
 				else
 					g_free (strv[i]);
@@ -1147,8 +1147,8 @@ global_dns_domain_from_dbus (char *name, GVariant *variant)
 			strv = g_variant_dup_strv (val, NULL);
 			_nm_utils_strv_cleanup (strv, TRUE, TRUE, TRUE);
 			for (i = 0, j = 0; strv && strv[i]; i++) {
-				if (   nm_utils_ipaddr_valid (AF_INET, strv[i])
-				    || nm_utils_ipaddr_valid (AF_INET6, strv[i]))
+				if (   nm_utils_ipaddr_is_valid (AF_INET, strv[i])
+				    || nm_utils_ipaddr_is_valid (AF_INET6, strv[i]))
 					strv[j++] = strv[i];
 				else
 					g_free (strv[i]);
@@ -1875,7 +1875,7 @@ nm_config_data_new_update_no_auto_default (const NMConfigData *base,
 static void
 finalize (GObject *gobject)
 {
-	NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE ((NMConfigData *) gobject);
+	NMConfigDataPrivate *priv = NM_CONFIG_DATA_GET_PRIVATE (gobject);
 
 	g_free (priv->config_main_file);
 	g_free (priv->config_description);
diff --git a/src/nm-config.c b/src/nm-config.c
index 4ea97657..63347db7 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -14,7 +14,7 @@
 #include "devices/nm-device.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 #define DEFAULT_CONFIG_MAIN_FILE        NMCONFDIR "/NetworkManager.conf"
 #define DEFAULT_CONFIG_DIR              NMCONFDIR "/conf.d"
@@ -274,7 +274,7 @@ nm_config_get_warnings (NMConfig *config)
 void
 nm_config_clear_warnings (NMConfig *config)
 {
-	g_clear_pointer (&NM_CONFIG_GET_PRIVATE (config)->warnings, g_strfreev);
+	nm_clear_pointer (&NM_CONFIG_GET_PRIVATE (config)->warnings, g_strfreev);
 }
 
 NMConfigData *
@@ -481,17 +481,17 @@ nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device)
 static void
 _nm_config_cmd_line_options_clear (NMConfigCmdLineOptions *cli)
 {
-	g_clear_pointer (&cli->config_main_file, g_free);
-	g_clear_pointer (&cli->config_dir, g_free);
-	g_clear_pointer (&cli->system_config_dir, g_free);
-	g_clear_pointer (&cli->no_auto_default_file, g_free);
-	g_clear_pointer (&cli->intern_config_file, g_free);
-	g_clear_pointer (&cli->state_file, g_free);
-	g_clear_pointer (&cli->plugins, g_free);
+	nm_clear_g_free (&cli->config_main_file);
+	nm_clear_g_free (&cli->config_dir);
+	nm_clear_g_free (&cli->system_config_dir);
+	nm_clear_g_free (&cli->no_auto_default_file);
+	nm_clear_g_free (&cli->intern_config_file);
+	nm_clear_g_free (&cli->state_file);
+	nm_clear_g_free (&cli->plugins);
 	cli->configure_and_quit = NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED;
 	cli->is_debug = FALSE;
-	g_clear_pointer (&cli->connectivity_uri, g_free);
-	g_clear_pointer (&cli->connectivity_response, g_free);
+	nm_clear_g_free (&cli->connectivity_uri);
+	nm_clear_g_free (&cli->connectivity_response);
 	cli->connectivity_interval = -1;
 	cli->first_start = FALSE;
 }
@@ -2193,7 +2193,8 @@ _nm_config_state_set (NMConfig *self,
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROOT_PATH           "root-path"
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NEXT_SERVER         "next-server"
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN,   "unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED, "unmanaged"),
@@ -2898,7 +2899,7 @@ nm_config_new (const NMConfigCmdLineOptions *cli, char **atomic_section_prefixes
 static void
 finalize (GObject *gobject)
 {
-	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE ((NMConfig *) gobject);
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (gobject);
 
 	state_free (priv->state);
 
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index e941fe7e..2d80fd90 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -26,7 +26,8 @@
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, int /*NMConnectivityState*/,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_state_to_string, int /*NMConnectivityState*/,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_UNKNOWN,  "UNKNOWN"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_NONE,     "NONE"),
@@ -415,13 +416,13 @@ _con_curl_timeout_cb (gpointer user_data)
 }
 
 static int
-multi_timer_cb (CURLM *multi, long timeout_ms, void *userdata)
+multi_timer_cb (CURLM *multi, long timeout_msec, void *userdata)
 {
 	NMConnectivityCheckHandle *cb_data = userdata;
 
 	nm_clear_g_source (&cb_data->concheck.curl_timer);
-	if (timeout_ms != -1)
-		cb_data->concheck.curl_timer = g_timeout_add (timeout_ms, _con_curl_timeout_cb, cb_data);
+	if (timeout_msec != -1)
+		cb_data->concheck.curl_timer = g_timeout_add (timeout_msec, _con_curl_timeout_cb, cb_data);
 	return 0;
 }
 
@@ -515,8 +516,12 @@ multi_socket_cb (CURL *e_handle, curl_socket_t fd, int what, void *userdata, voi
 			condition = 0;
 
 		if (condition) {
-			fdp->source = g_unix_fd_source_new (fd, condition);
-			g_source_set_callback (fdp->source, G_SOURCE_FUNC (_con_curl_socketevent_cb), fdp, NULL);
+			fdp->source = nm_g_unix_fd_source_new (fd,
+			                                       condition,
+			                                       G_PRIORITY_DEFAULT,
+			                                       _con_curl_socketevent_cb,
+			                                       fdp,
+			                                       NULL);
 			g_source_attach (fdp->source, NULL);
 		}
 	}
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index e059994a..a9f50a0f 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -436,7 +436,7 @@ static const char *
 _kc_waited_to_string (char *buf, gint64 wait_start_us)
 #define _kc_waited_to_string(buf, wait_start_us) ( G_STATIC_ASSERT_EXPR(sizeof (buf) == KC_WAITED_TO_STRING && sizeof ((buf)[0]) == 1), _kc_waited_to_string (buf, wait_start_us) )
 {
-	g_snprintf (buf, KC_WAITED_TO_STRING, " (%ld usec elapsed)", (long) (nm_utils_get_monotonic_timestamp_us () - wait_start_us));
+	g_snprintf (buf, KC_WAITED_TO_STRING, " (%ld usec elapsed)", (long) (nm_utils_get_monotonic_timestamp_usec () - wait_start_us));
 	return buf;
 }
 
@@ -476,7 +476,7 @@ _kc_cb_timeout_grace_period (void *user_data)
 		}
 	} else {
 		nm_log_dbg (data->log_domain, "%s: process not terminated after %ld usec. Sending SIGKILL signal",
-		            data->log_name, (long) (nm_utils_get_monotonic_timestamp_us () - data->async.wait_start_us));
+		            data->log_name, (long) (nm_utils_get_monotonic_timestamp_usec () - data->async.wait_start_us));
 	}
 
 	return G_SOURCE_REMOVE;
@@ -591,7 +591,7 @@ nm_utils_kill_child_async (pid_t pid, int sig, NMLogDomain log_domain,
 	}
 
 	data = _kc_async_data_alloc (pid, log_domain, log_name, callback, user_data);
-	data->async.wait_start_us = nm_utils_get_monotonic_timestamp_us ();
+	data->async.wait_start_us = nm_utils_get_monotonic_timestamp_usec ();
 
 	if (sig != SIGKILL && wait_before_kill_msec > 0) {
 		data->async.source_timeout_kill_id = g_timeout_add (wait_before_kill_msec, _kc_cb_timeout_grace_period, data);
@@ -694,7 +694,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 		goto out;
 	}
 
-	wait_start_us = nm_utils_get_monotonic_timestamp_us ();
+	wait_start_us = nm_utils_get_monotonic_timestamp_usec ();
 
 	/* wait for the process to terminated... */
 	if (sig != SIGKILL) {
@@ -728,7 +728,7 @@ nm_utils_kill_child_sync (pid_t pid, int sig, NMLogDomain log_domain, const char
 			if (!wait_until)
 				break;
 
-			now = nm_utils_get_monotonic_timestamp_us ();
+			now = nm_utils_get_monotonic_timestamp_usec ();
 			if (now >= wait_until)
 				break;
 
@@ -871,7 +871,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 
 	/* wait for the process to terminate... */
 
-	wait_start_us = nm_utils_get_monotonic_timestamp_us ();
+	wait_start_us = nm_utils_get_monotonic_timestamp_usec ();
 
 	sleep_duration_usec = _sleep_duration_convert_ms_to_us (sleep_duration_msec);
 	if (sig != SIGKILL && wait_before_kill_msec)
@@ -922,7 +922,7 @@ nm_utils_kill_process_sync (pid_t pid, guint64 start_time, int sig, NMLogDomain
 		}
 
 		sleep_time = sleep_duration_usec;
-		now = nm_utils_get_monotonic_timestamp_us ();
+		now = nm_utils_get_monotonic_timestamp_usec ();
 
 		if (   max_wait_until != 0
 		    && now >= max_wait_until) {
@@ -2434,7 +2434,7 @@ _host_id_read_timestamp (gboolean use_secret_key_file,
 	    && stat (SECRET_KEY_FILE, &st) == 0) {
 		/* don't check for overflow or timestamps in the future. We get whatever
 		 * (bogus) date is on the file. */
-		*out_timestamp_ns = nm_utils_timespec_to_ns (&st.st_mtim);
+		*out_timestamp_ns = nm_utils_timespec_to_nsec (&st.st_mtim);
 		return TRUE;
 	}
 
@@ -2453,13 +2453,13 @@ _host_id_read_timestamp (gboolean use_secret_key_file,
 	 * is not stable across restarts, but apparently neither is the host-id
 	 * nor the secret_key itself. */
 
-#define EPOCH_TWO_YEARS  (G_GINT64_CONSTANT (2 * 365 * 24 * 3600) * NM_UTILS_NS_PER_SECOND)
+#define EPOCH_TWO_YEARS  (G_GINT64_CONSTANT (2 * 365 * 24 * 3600) * NM_UTILS_NSEC_PER_SEC)
 
 	v = nm_hash_siphash42 (1156657133u, host_id, host_id_len);
 
 	now = time (NULL);
 	*out_timestamp_ns = NM_MAX ((gint64) 1,
-	                            (now * NM_UTILS_NS_PER_SECOND) - ((gint64) (v % ((guint64) (EPOCH_TWO_YEARS)))));
+	                            (now * NM_UTILS_NSEC_PER_SEC) - ((gint64) (v % ((guint64) (EPOCH_TWO_YEARS)))));
 	return FALSE;
 }
 
@@ -2741,6 +2741,59 @@ nm_utils_boot_id_bin (void)
 
 /*****************************************************************************/
 
+const char *
+nm_utils_proc_cmdline (void)
+{
+	static const char *volatile proc_cmdline_cached = NULL;
+	const char *proc_cmdline;
+
+again:
+	proc_cmdline = g_atomic_pointer_get (&proc_cmdline_cached);
+	if (G_UNLIKELY (!proc_cmdline)) {
+		gs_free char *str = NULL;
+
+		g_file_get_contents ("/proc/cmdline", &str, NULL, NULL);
+		str = nm_str_realloc (str);
+
+		proc_cmdline = str ?: "";
+		if (!g_atomic_pointer_compare_and_exchange (&proc_cmdline_cached, NULL, proc_cmdline))
+			goto again;
+
+		g_steal_pointer (&str);
+	}
+
+	return proc_cmdline;
+}
+
+const char *const*
+nm_utils_proc_cmdline_split (void)
+{
+	static const char *const*volatile proc_cmdline_cached = NULL;
+	const char *const*proc_cmdline;
+
+again:
+	proc_cmdline = g_atomic_pointer_get (&proc_cmdline_cached);
+	if (G_UNLIKELY (!proc_cmdline)) {
+		gs_free const char **split = NULL;
+
+		/* TODO: support quotation, like systemd's proc_cmdline_extract_first().
+		 * For that, add a new NMUtilsStrsplitSetFlags flag. */
+		split = nm_utils_strsplit_set_full (nm_utils_proc_cmdline (),
+		                                    NM_ASCII_WHITESPACES,
+		                                    NM_UTILS_STRSPLIT_SET_FLAGS_NONE);
+		proc_cmdline =    split
+		               ?: NM_PTRARRAY_EMPTY (const char *);
+		if (!g_atomic_pointer_compare_and_exchange (&proc_cmdline_cached, NULL, proc_cmdline))
+			goto again;
+
+		g_steal_pointer (&split);
+	}
+
+	return proc_cmdline;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_utils_arp_type_detect_from_hwaddrlen:
  * @hwaddr_len: the length of the hardware address in bytes.
@@ -2999,12 +3052,14 @@ nm_utils_ipv6_interface_identifier_get_from_token (NMUtilsIPv6IfaceId *iid,
  * Returns: the input buffer filled with the id as string.
  */
 const char *
-nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid, char *buf)
+nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid,
+                                              char buf[static INET6_ADDRSTRLEN])
 {
 	struct in6_addr i6_token = { .s6_addr = { 0, } };
 
+	nm_assert (buf);
 	nm_utils_ipv6_addr_set_interface_identifier (&i6_token, iid);
-	return nm_utils_inet6_ntop (&i6_token, buf);
+	return _nm_utils_inet6_ntop (&i6_token, buf);
 }
 
 /*****************************************************************************/
@@ -3721,7 +3776,7 @@ nm_utils_lifetime_get (guint32 timestamp,
 	}
 
 	if (now <= 0)
-		now = nm_utils_get_monotonic_timestamp_s ();
+		now = nm_utils_get_monotonic_timestamp_sec ();
 
 	t_lifetime = nm_utils_lifetime_rebase_relative_time_on_now (timestamp, lifetime, now);
 	if (!t_lifetime) {
@@ -4053,24 +4108,386 @@ GVariant *
 nm_utils_strdict_to_variant (GHashTable *options)
 {
 	GVariantBuilder builder;
-	gs_free const char **keys = NULL;
+	gs_free NMUtilsNamedValue *values = NULL;
 	guint i;
-	guint nkeys;
+	guint n;
 
-	keys = nm_utils_strdict_get_keys (options, TRUE, &nkeys);
+	values = nm_utils_named_values_from_str_dict (options, &n);
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-	for (i = 0; i < nkeys; i++) {
+	for (i = 0; i < n; i++) {
 		g_variant_builder_add (&builder,
 		                       "{sv}",
-		                       keys[i],
-		                       g_variant_new_string (g_hash_table_lookup (options, keys[i])));
+		                       values[i].name,
+		                       g_variant_new_string (values[i].value_str));
 	}
 	return g_variant_builder_end (&builder);
 }
 
 /*****************************************************************************/
 
+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++) {
+		const unsigned mcs_octet = i / 8;
+		const unsigned 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
+
+void
+nm_wifi_utils_parse_ies (const guint8 *bytes,
+                         gsize len,
+                         guint32 *out_max_rate,
+                         gboolean *out_metered,
+                         gboolean *out_owe_transition_mode)
+{
+	guint8 id, elem_len;
+	guint32 m;
+
+	NM_SET_OUT (out_max_rate, 0);
+	NM_SET_OUT (out_metered, FALSE);
+	NM_SET_OUT (out_owe_transition_mode, 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 (out_max_rate) {
+				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 (out_max_rate) {
+				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 (out_metered) {
+				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 */
+			}
+			if (   out_owe_transition_mode
+			    && elem_len >= 10
+			    && bytes[0] == 0x50            /* OUI: WiFi Alliance */
+			    && bytes[1] == 0x6f
+			    && bytes[2] == 0x9a
+			    && bytes[3] == 0x1c)           /* OUI type: OWE Transition Mode */
+				*out_owe_transition_mode = TRUE;
+			break;
+		}
+
+		len -= elem_len;
+		bytes += elem_len;
+	}
+}
+
+/*****************************************************************************/
+
+guint8
+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);
+}
+
+/*****************************************************************************/
+
 NM_UTILS_ENUM2STR_DEFINE (nm_icmpv6_router_pref_to_string, NMIcmpv6RouterPref,
 	NM_UTILS_ENUM2STR (NM_ICMPV6_ROUTER_PREF_LOW,     "low"),
 	NM_UTILS_ENUM2STR (NM_ICMPV6_ROUTER_PREF_MEDIUM,  "medium"),
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 7d63d06a..e30d7b36 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -267,6 +267,8 @@ gboolean nm_utils_machine_id_is_fake (void);
 
 const char *nm_utils_boot_id_str (void);
 const struct _NMUuid *nm_utils_boot_id_bin (void);
+const char *nm_utils_proc_cmdline (void);
+const char *const*nm_utils_proc_cmdline_split (void);
 
 gboolean nm_utils_host_id_get (const guint8 **out_host_id,
                                gsize *out_host_id_len);
@@ -316,7 +318,7 @@ gboolean nm_utils_ipv6_interface_identifier_get_from_token (NMUtilsIPv6IfaceId *
                                                            const char *token);
 
 const char *nm_utils_inet6_interface_identifier_to_token (NMUtilsIPv6IfaceId iid,
-                                                         char *buf);
+                                                          char buf[static INET6_ADDRSTRLEN]);
 
 gboolean nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
                                                  const guint8 *hwaddr,
@@ -473,6 +475,16 @@ const char *nm_utils_parse_dns_domain (const char *domain, gboolean *is_routing)
 
 /*****************************************************************************/
 
+void nm_wifi_utils_parse_ies (const guint8 *bytes,
+                              gsize len,
+                              guint32 *out_max_rate,
+                              gboolean *out_metered,
+                              gboolean *out_owe_transition_mode);
+
+guint8 nm_wifi_utils_level_to_quality (int val);
+
+/*****************************************************************************/
+
 #define NM_VPN_ROUTE_METRIC_DEFAULT     50
 
 #define NM_UTILS_ERROR_MSG_REQ_AUTH_FAILED   "Unable to authenticate the request"
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index 3f6f8115..239e06e1 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -19,6 +19,7 @@
 #include "nm-std-aux/nm-dbus-compat.h"
 #include "nm-dbus-object.h"
 #include "NetworkManagerUtils.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 
 /* The base path for our GDBusObjectManagerServers.  They do not contain
  * "NetworkManager" because GDBusObjectManagerServer requires that all
@@ -523,7 +524,7 @@ _get_caller_info_ensure (NMDBusManager *self,
 	gint64 now_ns;
 	gsize num;
 
-#define CALLER_INFO_MAX_AGE   (NM_UTILS_NS_PER_SECOND * 1)
+#define CALLER_INFO_MAX_AGE   (NM_UTILS_NSEC_PER_SEC * 1)
 
 	/* Linear search the cache for the sender.
 	 *
@@ -564,7 +565,7 @@ _get_caller_info_ensure (NMDBusManager *self,
 		}
 	}
 
-	now_ns = nm_utils_get_monotonic_timestamp_ns ();
+	now_ns = nm_utils_get_monotonic_timestamp_nsec ();
 
 	if (   ensure_uid
 	    && (now_ns - caller_info->uid_checked_at) > CALLER_INFO_MAX_AGE) {
@@ -1630,7 +1631,7 @@ dispose (GObject *object)
 	nm_assert (!priv->objects_by_path || g_hash_table_size (priv->objects_by_path) == 0);
 	nm_assert (c_list_is_empty (&priv->objects_lst_head));
 
-	g_clear_pointer (&priv->objects_by_path, g_hash_table_destroy);
+	nm_clear_pointer (&priv->objects_by_path, g_hash_table_destroy);
 
 	c_list_for_each_entry_safe (s, s_safe, &priv->private_servers_lst_head, private_servers_lst)
 		private_server_free (s);
@@ -1669,3 +1670,66 @@ nm_dbus_manager_class_init (NMDBusManagerClass *klass)
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_POINTER);
 }
+
+static NMAuthSubject *
+_new_unix_process (GDBusMethodInvocation *context,
+                   GDBusConnection *connection,
+                   GDBusMessage *message)
+{
+	NMAuthSubject *self;
+	const char *dbus_sender = NULL;
+	gulong uid = 0;
+	gulong pid = 0;
+	gboolean success;
+
+	g_return_val_if_fail (context || (connection && message), NULL);
+
+	if (context) {
+		success = nm_dbus_manager_get_caller_info (nm_dbus_manager_get (),
+		                                           context,
+		                                           &dbus_sender,
+		                                           &uid,
+		                                           &pid);
+	} else {
+		nm_assert (message);
+		success = nm_dbus_manager_get_caller_info_from_message (nm_dbus_manager_get (),
+		                                                        connection,
+		                                                        message,
+		                                                        &dbus_sender,
+		                                                        &uid,
+		                                                        &pid);
+	}
+
+	if (!success)
+		return NULL;
+
+	g_return_val_if_fail (dbus_sender && *dbus_sender, NULL);
+	/* polkit glib library stores uid and pid as int. There might be some
+	 * pitfalls if the id ever happens to be larger then that. Just assert against
+	 * it here. */
+	g_return_val_if_fail (uid <= MIN (G_MAXINT, G_MAXINT32), NULL);
+	g_return_val_if_fail (pid > 0 && pid <= MIN (G_MAXINT, G_MAXINT32), NULL);
+
+	self = nm_auth_subject_new_unix_process (dbus_sender, pid, uid);
+
+	if (nm_auth_subject_get_subject_type (self) != NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
+		/* this most likely happened because the process is gone (start_time==0).
+		 * Either that is not assert-worthy, or constructed() already asserted.
+		 * Just return NULL. */
+		g_clear_object (&self);
+	}
+	return self;
+}
+
+NMAuthSubject *
+nm_dbus_manager_new_auth_subject_from_context (GDBusMethodInvocation *context)
+{
+	return _new_unix_process (context, NULL, NULL);
+}
+
+NMAuthSubject *
+nm_dbus_manager_new_auth_subject_from_message (GDBusConnection *connection,
+                                               GDBusMessage *message)
+{
+	return _new_unix_process (NULL, connection, message);
+}
diff --git a/src/nm-dbus-manager.h b/src/nm-dbus-manager.h
index a6094938..28785874 100644
--- a/src/nm-dbus-manager.h
+++ b/src/nm-dbus-manager.h
@@ -88,4 +88,9 @@ void nm_dbus_manager_private_server_register (NMDBusManager *self,
                                               const char *path,
                                               const char *tag);
 
+NMAuthSubject *nm_dbus_manager_new_auth_subject_from_context (GDBusMethodInvocation *context);
+
+NMAuthSubject *nm_dbus_manager_new_auth_subject_from_message (GDBusConnection *connection,
+                                                              GDBusMessage *message);
+
 #endif /* __NM_DBUS_MANAGER_H__ */
diff --git a/src/nm-dbus-object.c b/src/nm-dbus-object.c
index a829417c..a1332686 100644
--- a/src/nm-dbus-object.c
+++ b/src/nm-dbus-object.c
@@ -138,7 +138,7 @@ nm_dbus_object_unexport (NMDBusObject *self)
 
 	_nm_dbus_manager_obj_unexport (self);
 
-	g_clear_pointer (&self->internal.path, g_free);
+	nm_clear_g_free (&self->internal.path);
 	self->internal.export_version_id = 0;
 
 	self->internal.is_unexporting = FALSE;
diff --git a/src/nm-dhcp-config.c b/src/nm-dhcp-config.c
new file mode 100644
index 00000000..3b9211df
--- /dev/null
+++ b/src/nm-dhcp-config.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dhcp-config.h"
+
+#include "nm-dbus-interface.h"
+#include "nm-utils.h"
+#include "nm-dbus-object.h"
+#include "nm-core-utils.h"
+
+/*****************************************************************************/
+
+#define NM_TYPE_DHCP4_CONFIG            (nm_dhcp4_config_get_type ())
+#define NM_DHCP4_CONFIG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP4_CONFIG, NMDhcp4Config))
+#define NM_DHCP4_CONFIG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP4_CONFIG, NMDhcp4ConfigClass))
+#define NM_IS_DHCP4_CONFIG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP4_CONFIG))
+#define NM_IS_DHCP4_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP4_CONFIG))
+#define NM_DHCP4_CONFIG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP4_CONFIG, NMDhcp4ConfigClass))
+
+typedef struct _NMDhcp4Config NMDhcp4Config;
+typedef struct _NMDhcp4ConfigClass NMDhcp4ConfigClass;
+
+static GType nm_dhcp4_config_get_type (void);
+
+#define NM_TYPE_DHCP6_CONFIG            (nm_dhcp6_config_get_type ())
+#define NM_DHCP6_CONFIG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP6_CONFIG, NMDhcp6Config))
+#define NM_DHCP6_CONFIG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP6_CONFIG, NMDhcp6ConfigClass))
+#define NM_IS_DHCP6_CONFIG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP6_CONFIG))
+#define NM_IS_DHCP6_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP6_CONFIG))
+#define NM_DHCP6_CONFIG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP6_CONFIG, NMDhcp6ConfigClass))
+
+typedef struct _NMDhcp6Config NMDhcp6Config;
+typedef struct _NMDhcp6ConfigClass NMDhcp6ConfigClass;
+
+static GType nm_dhcp6_config_get_type (void);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpConfig,
+	PROP_OPTIONS,
+);
+
+typedef struct {
+	GVariant *options;
+} NMDhcpConfigPrivate;
+
+struct _NMDhcpConfig {
+	NMDBusObject parent;
+	NMDhcpConfigPrivate _priv;
+};
+
+struct _NMDhcpConfigClass {
+	NMDBusObjectClass parent;
+};
+
+G_DEFINE_ABSTRACT_TYPE (NMDhcpConfig, nm_dhcp_config, NM_TYPE_DBUS_OBJECT)
+
+#define NM_DHCP_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpConfig, NM_IS_DHCP_CONFIG)
+
+/*****************************************************************************/
+
+void
+nm_dhcp_config_set_options (NMDhcpConfig *self,
+                            GHashTable *options)
+{
+	NMDhcpConfigPrivate *priv;
+
+	g_return_if_fail (NM_IS_DHCP_CONFIG (self));
+	g_return_if_fail (options);
+
+	priv = NM_DHCP_CONFIG_GET_PRIVATE (self);
+
+	nm_g_variant_unref (priv->options);
+	priv->options = g_variant_ref_sink (nm_utils_strdict_to_variant (options));
+	_notify (self, PROP_OPTIONS);
+}
+
+const char *
+nm_dhcp_config_get_option (NMDhcpConfig *self, const char *key)
+{
+	NMDhcpConfigPrivate *priv;
+	const char *value;
+
+	g_return_val_if_fail (NM_IS_DHCP_CONFIG (self), NULL);
+	g_return_val_if_fail (key, NULL);
+
+	priv = NM_DHCP_CONFIG_GET_PRIVATE (self);
+
+	if (   priv->options
+	    && g_variant_lookup (priv->options, key, "&s", &value))
+		return value;
+	else
+		return NULL;
+}
+
+GVariant *
+nm_dhcp_config_get_options (NMDhcpConfig *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CONFIG (self), NULL);
+
+	return NM_DHCP_CONFIG_GET_PRIVATE (self)->options;
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDhcpConfigPrivate *priv = NM_DHCP_CONFIG_GET_PRIVATE (object);
+
+	switch (prop_id) {
+	case PROP_OPTIONS:
+		g_value_set_variant (value,    priv->options
+		                            ?: g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_config_init (NMDhcpConfig *self)
+{
+}
+
+NMDhcpConfig *
+nm_dhcp_config_new (int addr_family)
+{
+	nm_assert_addr_family (addr_family);
+
+	return g_object_new (  addr_family != AF_INET
+	                     ? NM_TYPE_DHCP6_CONFIG
+	                     : NM_TYPE_DHCP4_CONFIG,
+	                     NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDhcpConfigPrivate *priv = NM_DHCP_CONFIG_GET_PRIVATE (object);
+
+	nm_g_variant_unref (priv->options);
+
+	G_OBJECT_CLASS (nm_dhcp_config_parent_class)->finalize (object);
+}
+
+static void
+nm_dhcp_config_class_init (NMDhcpConfigClass *config_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
+
+	object_class->get_property = get_property;
+	object_class->finalize = finalize;
+
+	obj_properties[PROP_OPTIONS] =
+	     g_param_spec_variant (NM_DHCP_CONFIG_OPTIONS, "", "",
+	                           G_VARIANT_TYPE ("a{sv}"),
+	                           NULL,
+	                           G_PARAM_READABLE |
+	                           G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
+
+struct _NMDhcp4Config {
+	NMDhcpConfig parent;
+};
+
+struct _NMDhcp4ConfigClass {
+	NMDhcpConfigClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcp4Config, nm_dhcp4_config, NM_TYPE_DHCP_CONFIG)
+
+static void
+nm_dhcp4_config_init (NMDhcp4Config *self)
+{
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_dhcp4_config = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DHCP4_CONFIG,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Options", "a{sv}",  NM_DHCP_CONFIG_OPTIONS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
+static void
+nm_dhcp4_config_class_init (NMDhcp4ConfigClass *klass)
+{
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP4Config");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dhcp4_config);
+	dbus_object_class->export_on_construction = TRUE;
+}
+
+/*****************************************************************************/
+
+struct _NMDhcp6Config {
+	NMDhcpConfig parent;
+};
+
+struct _NMDhcp6ConfigClass {
+	NMDhcpConfigClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcp6Config, nm_dhcp6_config, NM_TYPE_DHCP_CONFIG)
+
+static void
+nm_dhcp6_config_init (NMDhcp6Config *self)
+{
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_dhcp6_config = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DHCP6_CONFIG,
+		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
+			&nm_signal_info_property_changed_legacy,
+		),
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Options", "a{sv}",  NM_DHCP_CONFIG_OPTIONS),
+		),
+	),
+	.legacy_property_changed = TRUE,
+};
+
+static void
+nm_dhcp6_config_class_init (NMDhcp6ConfigClass *klass)
+{
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+
+	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP6Config");
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dhcp6_config);
+	dbus_object_class->export_on_construction = TRUE;
+}
diff --git a/src/nm-dhcp-config.h b/src/nm-dhcp-config.h
new file mode 100644
index 00000000..da192e77
--- /dev/null
+++ b/src/nm-dhcp-config.h
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef __NM_DHCP_CONFIG_H__
+#define __NM_DHCP_CONFIG_H__
+
+/*****************************************************************************/
+
+#define NM_TYPE_DHCP_CONFIG            (nm_dhcp_config_get_type ())
+#define NM_DHCP_CONFIG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_CONFIG, NMDhcpConfig))
+#define NM_DHCP_CONFIG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_CONFIG, NMDhcpConfigClass))
+#define NM_IS_DHCP_CONFIG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_CONFIG))
+#define NM_IS_DHCP_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_CONFIG))
+#define NM_DHCP_CONFIG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CONFIG, NMDhcpConfigClass))
+
+#define NM_DHCP_CONFIG_OPTIONS "options"
+
+typedef struct _NMDhcpConfigClass NMDhcpConfigClass;
+
+GType nm_dhcp_config_get_type (void);
+
+NMDhcpConfig *nm_dhcp_config_new (int addr_family);
+
+int nm_dhcp_config_get_addr_family (NMDhcpConfig *self);
+
+void nm_dhcp_config_set_options (NMDhcpConfig *self,
+                                 GHashTable *options);
+
+const char *nm_dhcp_config_get_option (NMDhcpConfig *self, const char *option);
+
+GVariant *nm_dhcp_config_get_options (NMDhcpConfig *self);
+
+#endif /* __NM_DHCP_CONFIG_H__ */
diff --git a/src/nm-dhcp4-config.c b/src/nm-dhcp4-config.c
deleted file mode 100644
index cdbe2071..00000000
--- a/src/nm-dhcp4-config.c
+++ /dev/null
@@ -1,158 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-dhcp4-config.h"
-
-#include "nm-dbus-interface.h"
-#include "nm-utils.h"
-#include "nm-dbus-object.h"
-#include "nm-core-utils.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE (NMDhcp4Config,
-	PROP_OPTIONS,
-);
-
-typedef struct {
-	GVariant *options;
-} NMDhcp4ConfigPrivate;
-
-struct _NMDhcp4Config {
-	NMDBusObject parent;
-	NMDhcp4ConfigPrivate _priv;
-};
-
-struct _NMDhcp4ConfigClass {
-	NMDBusObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMDhcp4Config, nm_dhcp4_config, NM_TYPE_DBUS_OBJECT)
-
-#define NM_DHCP4_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcp4Config, NM_IS_DHCP4_CONFIG)
-
-/*****************************************************************************/
-
-void
-nm_dhcp4_config_set_options (NMDhcp4Config *self,
-                             GHashTable *options)
-{
-	NMDhcp4ConfigPrivate *priv = NM_DHCP4_CONFIG_GET_PRIVATE (self);
-	GVariant *val;
-
-	g_return_if_fail (NM_IS_DHCP4_CONFIG (self));
-	g_return_if_fail (options);
-
-	val = nm_utils_strdict_to_variant (options);
-	g_variant_unref (priv->options);
-	priv->options = g_variant_ref_sink (val);
-	_notify (self, PROP_OPTIONS);
-}
-
-const char *
-nm_dhcp4_config_get_option (NMDhcp4Config *self, const char *key)
-{
-	NMDhcp4ConfigPrivate *priv = NM_DHCP4_CONFIG_GET_PRIVATE (self);
-	const char *value;
-
-	g_return_val_if_fail (NM_IS_DHCP4_CONFIG (self), NULL);
-	g_return_val_if_fail (key != NULL, NULL);
-
-	if (g_variant_lookup (priv->options, key, "&s", &value))
-		return value;
-	else
-		return NULL;
-}
-
-GVariant *
-nm_dhcp4_config_get_options (NMDhcp4Config *self)
-{
-	g_return_val_if_fail (NM_IS_DHCP4_CONFIG (self), NULL);
-
-	return g_variant_ref (NM_DHCP4_CONFIG_GET_PRIVATE (self)->options);
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMDhcp4ConfigPrivate *priv = NM_DHCP4_CONFIG_GET_PRIVATE ((NMDhcp4Config *) object);
-
-	switch (prop_id) {
-	case PROP_OPTIONS:
-		g_value_set_variant (value, priv->options);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_dhcp4_config_init (NMDhcp4Config *self)
-{
-	NMDhcp4ConfigPrivate *priv = NM_DHCP4_CONFIG_GET_PRIVATE (self);
-
-	priv->options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
-	g_variant_ref_sink (priv->options);
-}
-
-NMDhcp4Config *
-nm_dhcp4_config_new (void)
-{
-	return NM_DHCP4_CONFIG (g_object_new (NM_TYPE_DHCP4_CONFIG, NULL));
-}
-
-static void
-finalize (GObject *object)
-{
-	NMDhcp4ConfigPrivate *priv = NM_DHCP4_CONFIG_GET_PRIVATE ((NMDhcp4Config *) object);
-
-	g_variant_unref (priv->options);
-
-	G_OBJECT_CLASS (nm_dhcp4_config_parent_class)->finalize (object);
-}
-
-static const NMDBusInterfaceInfoExtended interface_info_dhcp4_config = {
-	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
-		NM_DBUS_INTERFACE_DHCP4_CONFIG,
-		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
-			&nm_signal_info_property_changed_legacy,
-		),
-		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Options", "a{sv}",  NM_DHCP4_CONFIG_OPTIONS),
-		),
-	),
-	.legacy_property_changed = TRUE,
-};
-
-static void
-nm_dhcp4_config_class_init (NMDhcp4ConfigClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->finalize = finalize;
-
-	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP4Config");
-	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dhcp4_config);
-	dbus_object_class->export_on_construction = TRUE;
-
-	obj_properties[PROP_OPTIONS] =
-	     g_param_spec_variant (NM_DHCP4_CONFIG_OPTIONS, "", "",
-	                           G_VARIANT_TYPE ("a{sv}"),
-	                           NULL,
-	                           G_PARAM_READABLE |
-	                           G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-}
diff --git a/src/nm-dhcp4-config.h b/src/nm-dhcp4-config.h
deleted file mode 100644
index 3cad1e82..00000000
--- a/src/nm-dhcp4-config.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_DHCP4_CONFIG_H__
-#define __NETWORKMANAGER_DHCP4_CONFIG_H__
-
-#define NM_TYPE_DHCP4_CONFIG            (nm_dhcp4_config_get_type ())
-#define NM_DHCP4_CONFIG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP4_CONFIG, NMDhcp4Config))
-#define NM_DHCP4_CONFIG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP4_CONFIG, NMDhcp4ConfigClass))
-#define NM_IS_DHCP4_CONFIG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP4_CONFIG))
-#define NM_IS_DHCP4_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP4_CONFIG))
-#define NM_DHCP4_CONFIG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP4_CONFIG, NMDhcp4ConfigClass))
-
-#define NM_DHCP4_CONFIG_OPTIONS "options"
-
-typedef struct _NMDhcp4ConfigClass NMDhcp4ConfigClass;
-
-GType nm_dhcp4_config_get_type (void);
-
-NMDhcp4Config *nm_dhcp4_config_new (void);
-
-void nm_dhcp4_config_set_options (NMDhcp4Config *config,
-                                  GHashTable *options);
-
-const char *nm_dhcp4_config_get_option (NMDhcp4Config *config, const char *option);
-
-GVariant *nm_dhcp4_config_get_options (NMDhcp4Config *config);
-
-#endif /* __NETWORKMANAGER_DHCP4_CONFIG_H__ */
diff --git a/src/nm-dhcp6-config.c b/src/nm-dhcp6-config.c
deleted file mode 100644
index 676caa6d..00000000
--- a/src/nm-dhcp6-config.c
+++ /dev/null
@@ -1,156 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-dhcp6-config.h"
-
-#include "nm-dbus-interface.h"
-#include "nm-utils.h"
-#include "nm-dbus-object.h"
-#include "nm-core-utils.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE (NMDhcp6Config,
-	PROP_OPTIONS,
-);
-
-typedef struct {
-	GVariant *options;
-} NMDhcp6ConfigPrivate;
-
-struct _NMDhcp6Config {
-	NMDBusObject parent;
-	NMDhcp6ConfigPrivate _priv;
-};
-
-struct _NMDhcp6ConfigClass {
-	NMDBusObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMDhcp6Config, nm_dhcp6_config, NM_TYPE_DBUS_OBJECT)
-
-#define NM_DHCP6_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcp6Config, NM_IS_DHCP6_CONFIG)
-
-/*****************************************************************************/
-
-void
-nm_dhcp6_config_set_options (NMDhcp6Config *self,
-                             GHashTable *options)
-{
-	NMDhcp6ConfigPrivate *priv = NM_DHCP6_CONFIG_GET_PRIVATE (self);
-	GVariant *val;
-
-	g_return_if_fail (NM_IS_DHCP6_CONFIG (self));
-	g_return_if_fail (options);
-
-	val = nm_utils_strdict_to_variant (options);
-	g_variant_unref (priv->options);
-	priv->options = g_variant_ref_sink (val);
-	_notify (self, PROP_OPTIONS);
-}
-
-const char *
-nm_dhcp6_config_get_option (NMDhcp6Config *self, const char *key)
-{
-	NMDhcp6ConfigPrivate *priv = NM_DHCP6_CONFIG_GET_PRIVATE (self);
-	const char *value;
-
-	g_return_val_if_fail (NM_IS_DHCP6_CONFIG (self), NULL);
-	g_return_val_if_fail (key != NULL, NULL);
-
-	if (g_variant_lookup (priv->options, key, "&s", &value))
-		return value;
-	else
-		return NULL;
-}
-
-GVariant *
-nm_dhcp6_config_get_options (NMDhcp6Config *self)
-{
-	g_return_val_if_fail (NM_IS_DHCP6_CONFIG (self), NULL);
-
-	return g_variant_ref (NM_DHCP6_CONFIG_GET_PRIVATE (self)->options);
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMDhcp6ConfigPrivate *priv = NM_DHCP6_CONFIG_GET_PRIVATE ((NMDhcp6Config *) object);
-
-	switch (prop_id) {
-	case PROP_OPTIONS:
-		g_value_set_variant (value, priv->options);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-nm_dhcp6_config_init (NMDhcp6Config *self)
-{
-	NMDhcp6ConfigPrivate *priv = NM_DHCP6_CONFIG_GET_PRIVATE (self);
-
-	priv->options = g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0);
-	g_variant_ref_sink (priv->options);
-}
-
-NMDhcp6Config *
-nm_dhcp6_config_new (void)
-{
-	return NM_DHCP6_CONFIG (g_object_new (NM_TYPE_DHCP6_CONFIG, NULL));
-}
-
-static void
-finalize (GObject *object)
-{
-	NMDhcp6ConfigPrivate *priv = NM_DHCP6_CONFIG_GET_PRIVATE ((NMDhcp6Config *) object);
-
-	g_variant_unref (priv->options);
-
-	G_OBJECT_CLASS (nm_dhcp6_config_parent_class)->finalize (object);
-}
-
-static const NMDBusInterfaceInfoExtended interface_info_dhcp6_config = {
-	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
-		NM_DBUS_INTERFACE_DHCP6_CONFIG,
-		.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
-			&nm_signal_info_property_changed_legacy,
-		),
-		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Options", "a{sv}",  NM_DHCP6_CONFIG_OPTIONS),
-		),
-	),
-	.legacy_property_changed = TRUE,
-};
-
-static void
-nm_dhcp6_config_class_init (NMDhcp6ConfigClass *config_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
-	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (config_class);
-
-	object_class->get_property = get_property;
-	object_class->finalize = finalize;
-
-	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH"/DHCP6Config");
-	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_dhcp6_config);
-	dbus_object_class->export_on_construction = TRUE;
-
-	obj_properties[PROP_OPTIONS] =
-	     g_param_spec_variant (NM_DHCP6_CONFIG_OPTIONS, "", "",
-	                           G_VARIANT_TYPE ("a{sv}"),
-	                           NULL,
-	                           G_PARAM_READABLE |
-	                           G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-}
diff --git a/src/nm-dhcp6-config.h b/src/nm-dhcp6-config.h
deleted file mode 100644
index e5697e04..00000000
--- a/src/nm-dhcp6-config.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2008 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_DHCP6_CONFIG_H__
-#define __NETWORKMANAGER_DHCP6_CONFIG_H__
-
-#define NM_TYPE_DHCP6_CONFIG            (nm_dhcp6_config_get_type ())
-#define NM_DHCP6_CONFIG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP6_CONFIG, NMDhcp6Config))
-#define NM_DHCP6_CONFIG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP6_CONFIG, NMDhcp6ConfigClass))
-#define NM_IS_DHCP6_CONFIG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP6_CONFIG))
-#define NM_IS_DHCP6_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP6_CONFIG))
-#define NM_DHCP6_CONFIG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP6_CONFIG, NMDhcp6ConfigClass))
-
-#define NM_DHCP6_CONFIG_OPTIONS "options"
-
-typedef struct _NMDhcp6ConfigClass NMDhcp6ConfigClass;
-
-GType nm_dhcp6_config_get_type (void);
-
-NMDhcp6Config *nm_dhcp6_config_new (void);
-
-void nm_dhcp6_config_set_options (NMDhcp6Config *config,
-                                  GHashTable *options);
-
-const char *nm_dhcp6_config_get_option (NMDhcp6Config *config, const char *option);
-
-GVariant *nm_dhcp6_config_get_options (NMDhcp6Config *self);
-
-#endif /* __NETWORKMANAGER_DHCP6_CONFIG_H__ */
diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c
index 54b0567b..e5584cd6 100644
--- a/src/nm-dispatcher.c
+++ b/src/nm-dispatcher.c
@@ -14,8 +14,7 @@
 #include "nm-connectivity.h"
 #include "nm-act-request.h"
 #include "devices/nm-device.h"
-#include "nm-dhcp4-config.h"
-#include "nm-dhcp6-config.h"
+#include "nm-dhcp-config.h"
 #include "nm-proxy-config.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
@@ -331,8 +330,7 @@ fill_device_props (NMDevice *device,
 	NMProxyConfig *proxy_config;
 	NMIP4Config *ip4_config;
 	NMIP6Config *ip6_config;
-	NMDhcp4Config *dhcp4_config;
-	NMDhcp6Config *dhcp6_config;
+	NMDhcpConfig *dhcp_config;
 
 	/* If the action is for a VPN, send the VPN's IP interface instead of the device's */
 	g_variant_builder_add (dev_builder, "{sv}", NMD_DEVICE_PROPS_IP_INTERFACE,
@@ -360,13 +358,13 @@ fill_device_props (NMDevice *device,
 	if (ip6_config)
 		dump_ip6_to_props (ip6_config, ip6_builder);
 
-	dhcp4_config = nm_device_get_dhcp4_config (device);
-	if (dhcp4_config)
-		*dhcp4_props = nm_dhcp4_config_get_options (dhcp4_config);
+	dhcp_config = nm_device_get_dhcp_config (device, AF_INET);
+	if (dhcp_config)
+		*dhcp4_props = nm_g_variant_ref (nm_dhcp_config_get_options (dhcp_config));
 
-	dhcp6_config = nm_device_get_dhcp6_config (device);
-	if (dhcp6_config)
-		*dhcp6_props = nm_dhcp6_config_get_options (dhcp6_config);
+	dhcp_config = nm_device_get_dhcp_config (device, AF_INET6);
+	if (dhcp_config)
+		*dhcp6_props = nm_g_variant_ref (nm_dhcp_config_get_options (dhcp_config));
 }
 
 static void
diff --git a/src/nm-firewall-manager.c b/src/nm-firewall-manager.c
index cf6494bd..304b15f9 100644
--- a/src/nm-firewall-manager.c
+++ b/src/nm-firewall-manager.c
@@ -265,7 +265,7 @@ _handle_dbus_cb (GObject *source,
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	call_id = user_data;
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index df70c71b..fdc0d4a4 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -448,7 +448,7 @@ main (int argc, char *argv[])
 		fprintf (stderr,
 		         _("Ignoring unrecognized log domain(s) '%s' passed on command line.\n"),
 		         bad_domains);
-		g_clear_pointer (&bad_domains, g_free);
+		nm_clear_g_free (&bad_domains);
 	}
 
 	if (global_opt.become_daemon && !global_opt.debug) {
@@ -596,7 +596,7 @@ main (int argc, char *argv[])
 	_LOGI (LOGD_CORE, "exiting");
 
 	nm_clear_g_source (&sd_id);
-	g_clear_pointer (&gl.main_loop, g_main_loop_unref);
+	nm_clear_pointer (&gl.main_loop, g_main_loop_unref);
 	return 0;
 }
 
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index cd14fb8f..ae637aee 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -1097,8 +1097,8 @@ nm_ip4_config_create_setting (const NMIP4Config *self)
 	    && nm_setting_ip_config_get_num_addresses (s_ip4) > 0) {
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_GATEWAY,
-		              nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
-		                                   sbuf),
+		              _nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
+		                                    sbuf),
 		              NULL);
 	}
 
@@ -1133,7 +1133,7 @@ nm_ip4_config_create_setting (const NMIP4Config *self)
 	for (i = 0; i < nnameservers; i++) {
 		guint32 nameserver = nm_ip4_config_get_nameserver (self, i);
 
-		nm_setting_ip_config_add_dns (s_ip4, nm_utils_inet4_ntop (nameserver, sbuf));
+		nm_setting_ip_config_add_dns (s_ip4, _nm_utils_inet4_ntop (nameserver, sbuf));
 	}
 	for (i = 0; i < nsearches; i++) {
 		const char *search = nm_ip4_config_get_search (self, i);
@@ -3096,14 +3096,14 @@ get_property (GObject *object, guint prop_id,
 				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "address",
-				                       g_variant_new_string (nm_utils_inet4_ntop (address->address, addr_str)));
+				                       g_variant_new_string (_nm_utils_inet4_ntop (address->address, addr_str)));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "prefix",
 				                       g_variant_new_uint32 (address->plen));
 				if (address->peer_address != address->address) {
 					g_variant_builder_add (&addr_builder, "{sv}",
 					                       "peer",
-					                       g_variant_new_string (nm_utils_inet4_ntop (address->peer_address, addr_str)));
+					                       g_variant_new_string (_nm_utils_inet4_ntop (address->peer_address, addr_str)));
 				}
 
 				if (*address->label) {
@@ -3158,14 +3158,14 @@ out_addresses_cached:
 			g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "dest",
-			                       g_variant_new_string (nm_utils_inet4_ntop (route->network, addr_str)));
+			                       g_variant_new_string (_nm_utils_inet4_ntop (route->network, addr_str)));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "prefix",
 			                       g_variant_new_uint32 (route->plen));
 			if (route->gateway) {
 				g_variant_builder_add (&route_builder, "{sv}",
 				                       "next-hop",
-				                       g_variant_new_string (nm_utils_inet4_ntop (route->gateway, addr_str)));
+				                       g_variant_new_string (_nm_utils_inet4_ntop (route->gateway, addr_str)));
 			}
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "metric",
@@ -3218,8 +3218,8 @@ out_routes_cached:
 		for (i = 0; i < priv->nameservers->len; i++) {
 			GVariantBuilder nested_builder;
 
-			nm_utils_inet4_ntop (g_array_index (priv->nameservers, in_addr_t, i),
-			                     addr_str);
+			_nm_utils_inet4_ntop (g_array_index (priv->nameservers, in_addr_t, i),
+			                      addr_str);
 
 			g_variant_builder_init (&nested_builder, G_VARIANT_TYPE ("a{sv}"));
 			g_variant_builder_add (&nested_builder, "{sv}",
@@ -3255,8 +3255,8 @@ out_routes_cached:
 		for (i = 0; i < priv->wins->len; i++) {
 			g_variant_builder_add (&builder_data,
 			                       "s",
-			                       nm_utils_inet4_ntop (g_array_index (priv->wins, in_addr_t, i),
-			                                            addr_str));
+			                       _nm_utils_inet4_ntop (g_array_index (priv->wins, in_addr_t, i),
+			                                             addr_str));
 		}
 		g_value_take_variant (value,
 		                      g_variant_builder_end (&builder_data));
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 12553657..5acb3932 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -764,8 +764,8 @@ nm_ip6_config_create_setting (const NMIP6Config *self)
 	    && nm_setting_ip_config_get_num_addresses (s_ip6) > 0) {
 		g_object_set (s_ip6,
 		              NM_SETTING_IP_CONFIG_GATEWAY,
-		              nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway,
-		                                   sbuf),
+		              _nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway,
+		                                    sbuf),
 		              NULL);
 	}
 
@@ -807,7 +807,7 @@ nm_ip6_config_create_setting (const NMIP6Config *self)
 	for (i = 0; i < nnameservers; i++) {
 		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (self, i);
 
-		nm_setting_ip_config_add_dns (s_ip6, nm_utils_inet6_ntop (nameserver, sbuf));
+		nm_setting_ip_config_add_dns (s_ip6, _nm_utils_inet6_ntop (nameserver, sbuf));
 	}
 	for (i = 0; i < nsearches; i++) {
 		const char *search = nm_ip6_config_get_search (self, i);
@@ -2556,7 +2556,7 @@ get_property (GObject *object, guint prop_id,
 				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "address",
-				                       g_variant_new_string (nm_utils_inet6_ntop (&address->address, sbuf)));
+				                       g_variant_new_string (_nm_utils_inet6_ntop (&address->address, sbuf)));
 				g_variant_builder_add (&addr_builder, "{sv}",
 				                       "prefix",
 				                       g_variant_new_uint32 (address->plen));
@@ -2564,7 +2564,7 @@ get_property (GObject *object, guint prop_id,
 				    && !IN6_ARE_ADDR_EQUAL (&address->peer_address, &address->address)) {
 					g_variant_builder_add (&addr_builder, "{sv}",
 					                       "peer",
-					                       g_variant_new_string (nm_utils_inet6_ntop (&address->peer_address, sbuf)));
+					                       g_variant_new_string (_nm_utils_inet6_ntop (&address->peer_address, sbuf)));
 				}
 
 				g_variant_builder_add (&builder_data, "a{sv}", &addr_builder);
@@ -2609,14 +2609,14 @@ out_addresses_cached:
 			g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "dest",
-			                       g_variant_new_string (nm_utils_inet6_ntop (&route->network, sbuf)));
+			                       g_variant_new_string (_nm_utils_inet6_ntop (&route->network, sbuf)));
 			g_variant_builder_add (&route_builder, "{sv}",
 			                       "prefix",
 			                       g_variant_new_uint32 (route->plen));
 			if (!IN6_IS_ADDR_UNSPECIFIED (&route->gateway)) {
 				g_variant_builder_add (&route_builder, "{sv}",
 				                       "next-hop",
-				                       g_variant_new_string (nm_utils_inet6_ntop (&route->gateway, sbuf)));
+				                       g_variant_new_string (_nm_utils_inet6_ntop (&route->gateway, sbuf)));
 			}
 
 			g_variant_builder_add (&route_builder, "{sv}",
diff --git a/src/nm-logging.c b/src/nm-logging.c
index 9221a6c8..3368c5db 100644
--- a/src/nm-logging.c
+++ b/src/nm-logging.c
@@ -390,7 +390,7 @@ nm_logging_setup (const char  *level,
 		}
 	}
 
-	g_clear_pointer (&gl_main.logging_domains_to_string, g_free);
+	nm_clear_g_free (&gl_main.logging_domains_to_string);
 
 	had_platform_debug = _nm_logging_enabled_lockfree (LOGL_DEBUG, LOGD_PLATFORM);
 
@@ -735,7 +735,7 @@ _nm_log_impl (const char *file,
 			char *s_log_domains;
 			gsize l_log_domains;
 
-			now = nm_utils_get_monotonic_timestamp_ns ();
+			now = nm_utils_get_monotonic_timestamp_nsec ();
 			boottime = nm_utils_monotonic_timestamp_as_boottime (now, 1);
 
 			_iovec_set_format_a (iov++, 30, "PRIORITY=%d", level_desc[level].syslog_level);
@@ -766,8 +766,8 @@ _nm_log_impl (const char *file,
 				_iovec_set_format (iov++, iov_free++, "CODE_FUNC=%s", func);
 			_iovec_set_format (iov++, iov_free++, "CODE_FILE=%s", file ?: "");
 			_iovec_set_format_a (iov++, 20, "CODE_LINE=%u", line);
-			_iovec_set_format_a (iov++, 60, "TIMESTAMP_MONOTONIC=%lld.%06lld", (long long) (now / NM_UTILS_NS_PER_SECOND), (long long) ((now % NM_UTILS_NS_PER_SECOND) / 1000));
-			_iovec_set_format_a (iov++, 60, "TIMESTAMP_BOOTTIME=%lld.%06lld", (long long) (boottime / NM_UTILS_NS_PER_SECOND), (long long) ((boottime % NM_UTILS_NS_PER_SECOND) / 1000));
+			_iovec_set_format_a (iov++, 60, "TIMESTAMP_MONOTONIC=%lld.%06lld", (long long) (now / NM_UTILS_NSEC_PER_SEC), (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000));
+			_iovec_set_format_a (iov++, 60, "TIMESTAMP_BOOTTIME=%lld.%06lld", (long long) (boottime / NM_UTILS_NSEC_PER_SEC), (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000));
 			if (error != 0)
 				_iovec_set_format_a (iov++, 30, "ERRNO=%d", error);
 			if (ifname)
@@ -868,7 +868,7 @@ nm_log_handler (const char *log_domain,
 		{
 			gint64 now, boottime;
 
-			now = nm_utils_get_monotonic_timestamp_ns ();
+			now = nm_utils_get_monotonic_timestamp_nsec ();
 			boottime = nm_utils_monotonic_timestamp_as_boottime (now, 1);
 
 			sd_journal_send ("PRIORITY=%d", syslog_priority,
@@ -878,8 +878,8 @@ nm_log_handler (const char *log_domain,
 			                 "SYSLOG_FACILITY=3",
 			                 "GLIB_DOMAIN=%s", log_domain ?: "",
 			                 "GLIB_LEVEL=%d", (int) (level & G_LOG_LEVEL_MASK),
-			                 "TIMESTAMP_MONOTONIC=%lld.%06lld", (long long) (now / NM_UTILS_NS_PER_SECOND), (long long) ((now % NM_UTILS_NS_PER_SECOND) / 1000),
-			                 "TIMESTAMP_BOOTTIME=%lld.%06lld", (long long) (boottime / NM_UTILS_NS_PER_SECOND), (long long) ((boottime % NM_UTILS_NS_PER_SECOND) / 1000),
+			                 "TIMESTAMP_MONOTONIC=%lld.%06lld", (long long) (now / NM_UTILS_NSEC_PER_SEC), (long long) ((now % NM_UTILS_NSEC_PER_SEC) / 1000),
+			                 "TIMESTAMP_BOOTTIME=%lld.%06lld", (long long) (boottime / NM_UTILS_NSEC_PER_SEC), (long long) ((boottime % NM_UTILS_NSEC_PER_SEC) / 1000),
 			                 NULL);
 		}
 		break;
@@ -999,7 +999,7 @@ nm_logging_init (const char *logging_backend, gboolean debug)
 	if (fetch_monotonic_timestamp) {
 		/* ensure we read a monotonic timestamp. Reading the timestamp the first
 		 * time causes a logging message. We don't want to do that during _nm_log_impl. */
-		nm_utils_get_monotonic_timestamp_ns ();
+		nm_utils_get_monotonic_timestamp_nsec ();
 	}
 
 	if (obsolete_debug_backend)
diff --git a/src/nm-manager.c b/src/nm-manager.c
index d7b2211e..1f3ba1f6 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -383,7 +383,8 @@ static void _activation_auth_done (NMManager *self,
 
 /*****************************************************************************/
 
-static NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark)
+static
+NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark)
 
 /*****************************************************************************/
 
@@ -2426,7 +2427,7 @@ device_auth_request_cb (NMDevice *device,
 	char *permission_dup;
 
 	/* Validate the caller */
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_dbus_manager_new_auth_subject_from_context (context);
 	if (!subject) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -4503,7 +4504,7 @@ unmanaged_to_disconnected (NMDevice *device)
 static NMActivationStateFlags
 _activation_bind_lifetime_to_profile_visibility (NMAuthSubject *subject)
 {
-	if (   nm_auth_subject_is_internal (subject)
+	if (   nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL
 	    || nm_auth_subject_get_unix_process_uid (subject) == 0) {
 		/* internal requests and requests from root are always unbound. */
 		return NM_ACTIVATION_STATE_FLAG_NONE;
@@ -5087,8 +5088,10 @@ nm_manager_activate_connection (NMManager *self,
 		if (   sett_conn == nm_active_connection_get_settings_connection (active)
 		    && nm_streq0 (nm_active_connection_get_specific_object (active), specific_object)
 		    && (!device || nm_active_connection_get_device (active) == device)
-		    && nm_auth_subject_is_internal (nm_active_connection_get_subject (active))
-		    && nm_auth_subject_is_internal (subject)
+		    && nm_auth_subject_get_subject_type (nm_active_connection_get_subject (active))
+		    == NM_AUTH_SUBJECT_TYPE_INTERNAL
+		    && nm_auth_subject_get_subject_type (subject)
+		    == NM_AUTH_SUBJECT_TYPE_INTERNAL
 		    && nm_active_connection_get_activation_reason (active) == activation_reason)
 			return active;
 	}
@@ -5163,7 +5166,7 @@ validate_activation_request (NMManager *self,
 		connection = nm_settings_connection_get_connection (sett_conn);
 
 	/* Validate the caller */
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_dbus_manager_new_auth_subject_from_context (context);
 	if (!subject) {
 		g_set_error_literal (error,
 		                     NM_MANAGER_ERROR,
@@ -5306,9 +5309,9 @@ impl_manager_activate_connection (NMDBusObject *obj,
 
 	g_variant_get (parameters, "(&o&o&o)", &connection_path, &device_path, &specific_object_path);
 
-	connection_path = nm_utils_dbus_normalize_object_path (connection_path);
-	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
-	device_path = nm_utils_dbus_normalize_object_path (device_path);
+	connection_path = nm_dbus_path_not_empty (connection_path);
+	specific_object_path = nm_dbus_path_not_empty (specific_object_path);
+	device_path = nm_dbus_path_not_empty (device_path);
 
 	/* If the connection path is given and valid, that connection is activated.
 	 * Otherwise the "best" connection for the device is chosen and activated,
@@ -5596,8 +5599,8 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 		}
 	}
 
-	specific_object_path = nm_utils_dbus_normalize_object_path (specific_object_path);
-	device_path = nm_utils_dbus_normalize_object_path (device_path);
+	specific_object_path = nm_dbus_path_not_empty (specific_object_path);
+	device_path = nm_dbus_path_not_empty (device_path);
 
 	/* Try to create a new connection with the given settings.
 	 * We allow empty settings for AddAndActivateConnection(). In that case,
@@ -5813,7 +5816,7 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 	}
 
 	/* Validate the caller */
-	subject = nm_auth_subject_new_unix_process_from_context (invocation);
+	subject = nm_dbus_manager_new_auth_subject_from_context (invocation);
 	if (!subject) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -6097,7 +6100,7 @@ impl_manager_sleep (NMDBusObject *obj,
 
 	g_variant_get (parameters, "(b)", &do_sleep);
 
-	subject = nm_auth_subject_new_unix_process_from_context (invocation);
+	subject = nm_dbus_manager_new_auth_subject_from_context (invocation);
 
 	if (priv->sleeping == do_sleep) {
 		error = g_error_new (NM_MANAGER_ERROR,
@@ -6231,29 +6234,12 @@ done:
 /* Permissions */
 
 static void
-get_perm_add_result (NMManager *self, NMAuthChain *chain, GVariantBuilder *results, const char *permission)
-{
-	NMAuthCallResult result;
-
-	result = nm_auth_chain_get_result (chain, permission);
-	if (result == NM_AUTH_CALL_RESULT_YES)
-		g_variant_builder_add (results, "{ss}", permission, "yes");
-	else if (result == NM_AUTH_CALL_RESULT_NO)
-		g_variant_builder_add (results, "{ss}", permission, "no");
-	else if (result == NM_AUTH_CALL_RESULT_AUTH)
-		g_variant_builder_add (results, "{ss}", permission, "auth");
-	else {
-		_LOGD (LOGD_CORE, "unknown auth chain result %d", result);
-	}
-}
-
-static void
 get_permissions_done_cb (NMAuthChain *chain,
                          GDBusMethodInvocation *context,
                          gpointer user_data)
 {
-	NMManager *self = NM_MANAGER (user_data);
 	GVariantBuilder results;
+	int i;
 
 	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
@@ -6261,22 +6247,15 @@ get_permissions_done_cb (NMAuthChain *chain,
 
 	g_variant_builder_init (&results, G_VARIANT_TYPE ("a{ss}"));
 
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SLEEP_WAKE);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_NETWORK_CONTROL);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_RELOAD);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS);
-	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK);
+	for (i = 0; i < (int) G_N_ELEMENTS (nm_auth_permission_sorted); i++) {
+		const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1];
+		NMAuthCallResult result;
+		const char *result_str;
+
+		result = nm_auth_chain_get_result (chain, permission);
+		result_str = nm_client_permission_result_to_string (nm_auth_call_result_to_client (result));
+		g_variant_builder_add (&results, "{ss}", permission, result_str);
+	}
 
 	g_dbus_method_invocation_return_value (context,
 	                                       g_variant_new ("(a{ss})", &results));
@@ -6294,6 +6273,7 @@ impl_manager_get_permissions (NMDBusObject *obj,
 	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMAuthChain *chain;
+	int i;
 
 	chain = nm_auth_chain_new_context (invocation, get_permissions_done_cb, self);
 	if (!chain) {
@@ -6305,22 +6285,12 @@ impl_manager_get_permissions (NMDBusObject *obj,
 	}
 
 	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SLEEP_WAKE, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_RELOAD, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, FALSE);
-	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, FALSE);
+
+	for (i = 0; i < (int) G_N_ELEMENTS (nm_auth_permission_sorted); i++) {
+		const char *permission = nm_auth_permission_names_by_idx[nm_auth_permission_sorted[i] - 1];
+
+		nm_auth_chain_add_call_unsafe (chain, permission, FALSE);
+	}
 }
 
 static void
@@ -6544,7 +6514,7 @@ nm_manager_write_device_state (NMManager *self, NMDevice *device, int *out_ifind
 	guint32 route_metric_default_aspired;
 	guint32 route_metric_default_effective;
 	int nm_owned;
-	NMDhcp4Config *dhcp4_config;
+	NMDhcpConfig *dhcp_config;
 	const char *next_server = NULL;
 	const char *root_path = NULL;
 
@@ -6584,10 +6554,10 @@ nm_manager_write_device_state (NMManager *self, NMDevice *device, int *out_ifind
 	route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN,
 	                                                           TRUE, &route_metric_default_aspired);
 
-	dhcp4_config = nm_device_get_dhcp4_config (device);
-	if (dhcp4_config) {
-		root_path = nm_dhcp4_config_get_option (dhcp4_config, "root_path");
-		next_server = nm_dhcp4_config_get_option (dhcp4_config, "next_server");
+	dhcp_config = nm_device_get_dhcp_config (device, AF_INET);
+	if (dhcp_config) {
+		root_path = nm_dhcp_config_get_option (dhcp_config, "root_path");
+		next_server = nm_dhcp_config_get_option (dhcp_config, "next_server");
 	}
 
 	if (!nm_config_device_state_write (ifindex,
@@ -6965,7 +6935,7 @@ nm_manager_dbus_set_property_handle (NMDBusObject *obj,
 	gs_unref_object NMAuthSubject *subject = NULL;
 	DBusSetPropertyHandle *handle_data;
 
-	subject = nm_auth_subject_new_unix_process_from_context (invocation);
+	subject = nm_dbus_manager_new_auth_subject_from_context (invocation);
 	if (!subject) {
 		error_message = NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN;
 		goto err;
@@ -7761,7 +7731,7 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&priv->devices_inited_id);
 
-	g_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_free);
+	nm_clear_pointer (&priv->checkpoint_mgr, nm_checkpoint_manager_free);
 
 	if (priv->concheck_mgr) {
 		g_signal_handlers_disconnect_by_func (priv->concheck_mgr,
@@ -7816,7 +7786,7 @@ dispose (GObject *object)
 	g_clear_object (&priv->vpn_manager);
 
 	sleep_devices_clear (self);
-	g_clear_pointer (&priv->sleep_devices, g_hash_table_unref);
+	nm_clear_pointer (&priv->sleep_devices, g_hash_table_unref);
 
 	if (priv->sleep_monitor) {
 		g_signal_handlers_disconnect_by_func (priv->sleep_monitor, sleeping_cb, self);
@@ -7846,7 +7816,7 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&priv->timestamp_update_id);
 
-	g_clear_pointer (&priv->device_route_metrics, g_hash_table_destroy);
+	nm_clear_pointer (&priv->device_route_metrics, g_hash_table_destroy);
 
 	G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object);
 }
@@ -7854,7 +7824,7 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE ((NMManager *) object);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (object);
 
 	g_array_free (priv->capabilities, TRUE);
 
diff --git a/src/nm-pacrunner-manager.c b/src/nm-pacrunner-manager.c
index b5c7bc02..0324ccaa 100644
--- a/src/nm-pacrunner-manager.c
+++ b/src/nm-pacrunner-manager.c
@@ -145,7 +145,7 @@ get_ip_domains (GPtrArray *domains, NMIPConfig *ip_config)
 
 		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, (NMIP4Config *) ip_config, &address) {
 			cidr = g_strdup_printf ("%s/%u",
-			                        nm_utils_inet4_ntop (address->address, sbuf),
+			                        _nm_utils_inet4_ntop (address->address, sbuf),
 			                        address->plen);
 			g_ptr_array_add (domains, cidr);
 		}
@@ -154,7 +154,7 @@ get_ip_domains (GPtrArray *domains, NMIPConfig *ip_config)
 
 		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, (NMIP6Config *) ip_config, &address) {
 			cidr = g_strdup_printf ("%s/%u",
-			                        nm_utils_inet6_ntop (&address->address, sbuf),
+			                        _nm_utils_inet6_ntop (&address->address, sbuf),
 			                        address->plen);
 			g_ptr_array_add (domains, cidr);
 		}
@@ -167,7 +167,7 @@ get_ip_domains (GPtrArray *domains, NMIPConfig *ip_config)
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
 				continue;
 			cidr = g_strdup_printf ("%s/%u",
-			                        nm_utils_inet4_ntop (routes->network, sbuf),
+			                        _nm_utils_inet4_ntop (routes->network, sbuf),
 			                        routes->plen);
 			g_ptr_array_add (domains, cidr);
 		}
@@ -178,7 +178,7 @@ get_ip_domains (GPtrArray *domains, NMIPConfig *ip_config)
 			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
 				continue;
 			cidr = g_strdup_printf ("%s/%u",
-			                        nm_utils_inet6_ntop (&routes->network, sbuf),
+			                        _nm_utils_inet6_ntop (&routes->network, sbuf),
 			                        routes->plen);
 			g_ptr_array_add (domains, cidr);
 		}
@@ -610,7 +610,7 @@ nm_pacrunner_manager_init (NMPacrunnerManager *self)
 static void
 dispose (GObject *object)
 {
-	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE ((NMPacrunnerManager *) object);
+	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (object);
 
 	nm_assert (c_list_is_empty (&priv->conf_id_lst_head));
 
diff --git a/src/nm-policy.c b/src/nm-policy.c
index d322b2ef..2a9f6c05 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -29,8 +29,7 @@
 #include "settings/nm-settings.h"
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-agent-manager.h"
-#include "nm-dhcp4-config.h"
-#include "nm-dhcp6-config.h"
+#include "nm-dhcp-config.h"
 #include "nm-config.h"
 #include "nm-netns.h"
 #include "nm-hostname-manager.h"
@@ -184,7 +183,7 @@ clear_ip6_prefix_delegation (gpointer data)
 	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_LOGD (LOGD_IP6, "ipv6-pd: undelegating prefix %s/%d",
-	       nm_utils_inet6_ntop (&delegation->prefix.address, sbuf),
+	       _nm_utils_inet6_ntop (&delegation->prefix.address, sbuf),
 	       delegation->prefix.plen);
 
 	g_hash_table_foreach (delegation->subnets, _clear_ip6_subnet, NULL);
@@ -195,7 +194,7 @@ static void
 expire_ip6_delegations (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	guint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	IP6PrefixDelegation *delegation = NULL;
 	guint i;
 
@@ -225,7 +224,7 @@ ip6_subnet_from_delegation (IP6PrefixDelegation *delegation, NMDevice *device)
 		/* Check for out-of-prefixes condition. */
 		if (delegation->next_subnet >= (1 << (64 - delegation->prefix.plen))) {
 			_LOGD (LOGD_IP6, "ipv6-pd: no more prefixes in %s/%d",
-			       nm_utils_inet6_ntop (&delegation->prefix.address, sbuf),
+			       _nm_utils_inet6_ntop (&delegation->prefix.address, sbuf),
 			       delegation->prefix.plen);
 			return FALSE;
 		}
@@ -253,7 +252,7 @@ ip6_subnet_from_delegation (IP6PrefixDelegation *delegation, NMDevice *device)
 	subnet->preferred = delegation->prefix.preferred;
 
 	_LOGD (LOGD_IP6, "ipv6-pd: %s allocated from a /%d prefix on %s",
-	       nm_utils_inet6_ntop (&subnet->address, sbuf),
+	       _nm_utils_inet6_ntop (&subnet->address, sbuf),
 	       delegation->prefix.plen,
 	       nm_device_get_iface (device));
 
@@ -327,7 +326,7 @@ device_ip6_prefix_delegated (NMDevice *device,
 	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
 	_LOGI (LOGD_IP6, "ipv6-pd: received a prefix %s/%d from %s",
-	       nm_utils_inet6_ntop (&prefix->address, sbuf),
+	       _nm_utils_inet6_ntop (&prefix->address, sbuf),
 	       prefix->plen,
 	       nm_device_get_iface (device));
 
@@ -692,6 +691,7 @@ update_system_hostname (NMPolicy *self, const char *msg)
 	const NMPlatformIP4Address *addr4;
 	const NMPlatformIP6Address *addr6;
 	NMDevice *device;
+	NMDhcpConfig *dhcp_config;
 
 	g_return_if_fail (self != NULL);
 
@@ -745,12 +745,10 @@ update_system_hostname (NMPolicy *self, const char *msg)
 	}
 
 	if (priv->default_ac4) {
-		NMDhcp4Config *dhcp4_config;
-
 		/* Grab a hostname out of the device's DHCP4 config */
-		dhcp4_config = nm_device_get_dhcp4_config (get_default_device (self, AF_INET));
-		if (dhcp4_config) {
-			dhcp_hostname = nm_dhcp4_config_get_option (dhcp4_config, "host_name");
+		dhcp_config = nm_device_get_dhcp_config (get_default_device (self, AF_INET), AF_INET);
+		if (dhcp_config) {
+			dhcp_hostname = nm_dhcp_config_get_option (dhcp_config, "host_name");
 			if (dhcp_hostname && dhcp_hostname[0]) {
 				p = nm_str_skip_leading_spaces (dhcp_hostname);
 				if (p[0]) {
@@ -765,12 +763,10 @@ update_system_hostname (NMPolicy *self, const char *msg)
 	}
 
 	if (priv->default_ac6) {
-		NMDhcp6Config *dhcp6_config;
-
 		/* Grab a hostname out of the device's DHCP6 config */
-		dhcp6_config = nm_device_get_dhcp6_config (get_default_device (self, AF_INET6));
-		if (dhcp6_config) {
-			dhcp_hostname = nm_dhcp6_config_get_option (dhcp6_config, "host_name");
+		dhcp_config = nm_device_get_dhcp_config (get_default_device (self, AF_INET6), AF_INET6);
+		if (dhcp_config) {
+			dhcp_hostname = nm_dhcp_config_get_option (dhcp_config, "host_name");
 			if (dhcp_hostname && dhcp_hostname[0]) {
 				p = nm_str_skip_leading_spaces (dhcp_hostname);
 				if (p[0]) {
@@ -1007,21 +1003,6 @@ update_ip4_routing (NMPolicy *self, gboolean force_update)
 }
 
 static void
-update_ip6_dns_delegation (NMPolicy *self)
-{
-	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	NMDevice *device;
-	NMActiveConnection *ac;
-	const CList *tmp_list;
-
-	nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
-		device = nm_active_connection_get_device (ac);
-		if (device && nm_device_needs_ip6_subnet (device))
-			nm_device_copy_ip6_dns_config (device, get_default_device (self, AF_INET6));
-	}
-}
-
-static void
 update_ip6_prefix_delegation (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
@@ -1089,8 +1070,9 @@ update_ip6_routing (NMPolicy *self, gboolean force_update)
 }
 
 static void
-update_ip_dns (NMPolicy *self, int addr_family)
+update_ip_dns (NMPolicy *self, int addr_family, NMDevice *changed_device)
 {
+	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	gpointer ip_config;
 	const char *ip_iface = NULL;
 	NMVpnConnection *vpn = NULL;
@@ -1105,25 +1087,39 @@ update_ip_dns (NMPolicy *self, int addr_family)
 		 */
 		_dns_manager_set_ip_config (NM_POLICY_GET_PRIVATE (self)->dns_manager,
 		                            ip_config,
-		                            vpn
+		                              (   vpn
+		                               || (device && nm_device_is_vpn (device)))
 		                            ? NM_DNS_IP_CONFIG_TYPE_VPN
 		                            : NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE,
 		                            device);
 	}
 
-	if (addr_family == AF_INET6)
-		update_ip6_dns_delegation (self);
+	if (addr_family == AF_INET6) {
+		NMActiveConnection *ac;
+		const CList *tmp_list;
+
+		/* Tell devices needing a subnet about the new DNS configuration */
+		nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
+			device = nm_active_connection_get_device (ac);
+			if (   device
+			    && device != changed_device
+			    && nm_device_needs_ip6_subnet (device))
+				nm_device_copy_ip6_dns_config (device, get_default_device (self, AF_INET6));
+		}
+	}
 }
 
 static void
-update_routing_and_dns (NMPolicy *self, gboolean force_update)
+update_routing_and_dns (NMPolicy *self,
+                        gboolean force_update,
+                        NMDevice *changed_device)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 
 	nm_dns_manager_begin_updates (priv->dns_manager, __func__);
 
-	update_ip_dns (self, AF_INET);
-	update_ip_dns (self, AF_INET6);
+	update_ip_dns (self, AF_INET, changed_device);
+	update_ip_dns (self, AF_INET6, changed_device);
 
 	update_ip4_routing (self, force_update);
 	update_ip6_routing (self, force_update);
@@ -1549,7 +1545,7 @@ reset_connections_retries (gpointer user_data)
 	priv->reset_retries_id = 0;
 
 	min_stamp = 0;
-	now = nm_utils_get_monotonic_timestamp_s ();
+	now = nm_utils_get_monotonic_timestamp_sec ();
 	connections = nm_settings_get_connections (priv->settings, NULL);
 	for (i = 0; connections[i]; i++) {
 		NMSettingsConnection *connection = connections[i];
@@ -1594,7 +1590,7 @@ _connection_autoconnect_retries_set (NMPolicy *self,
 			gint32 retry_time = nm_settings_connection_autoconnect_retries_blocked_until (connection);
 
 			g_warn_if_fail (retry_time != 0);
-			priv->reset_retries_id = g_timeout_add_seconds (MAX (0, retry_time - nm_utils_get_monotonic_timestamp_s ()), reset_connections_retries, self);
+			priv->reset_retries_id = g_timeout_add_seconds (MAX (0, retry_time - nm_utils_get_monotonic_timestamp_sec ()), reset_connections_retries, self);
 		}
 	}
 }
@@ -1633,7 +1629,8 @@ activate_slave_connections (NMPolicy *self, NMDevice *device)
 		}
 
 		subject = nm_active_connection_get_subject (NM_ACTIVE_CONNECTION (req));
-		internal_activation = subject && nm_auth_subject_is_internal (subject);
+		internal_activation =    subject
+		                      && (nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL);
 	}
 
 	changed = FALSE;
@@ -1857,14 +1854,14 @@ device_state_changed (NMDevice *device,
 		if (ip6_config)
 			_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_DEFAULT, device);
 
-		update_routing_and_dns (self, FALSE);
+		update_routing_and_dns (self, FALSE, device);
 
 		nm_dns_manager_end_updates (priv->dns_manager, __func__);
 		break;
 	case NM_DEVICE_STATE_UNMANAGED:
 	case NM_DEVICE_STATE_UNAVAILABLE:
 		if (old_state > NM_DEVICE_STATE_DISCONNECTED)
-			update_routing_and_dns (self, FALSE);
+			update_routing_and_dns (self, FALSE, device);
 		break;
 	case NM_DEVICE_STATE_DEACTIVATING:
 		if (sett_conn) {
@@ -1899,7 +1896,7 @@ device_state_changed (NMDevice *device,
 			reset_autoconnect_all (self, device, FALSE);
 
 		if (old_state > NM_DEVICE_STATE_DISCONNECTED)
-			update_routing_and_dns (self, FALSE);
+			update_routing_and_dns (self, FALSE, device);
 
 		/* Device is now available for auto-activation */
 		schedule_activate_check (self, device);
@@ -1930,7 +1927,7 @@ device_state_changed (NMDevice *device,
 		if (   s_con
 		    && nm_setting_connection_get_num_secondaries (s_con) > 0) {
 			/* Make routes and DNS up-to-date before activating dependent connections */
-			update_routing_and_dns (self, FALSE);
+			update_routing_and_dns (self, FALSE, device);
 
 			/* Activate secondary (VPN) connections */
 			if (!activate_secondary_connections (self,
@@ -1986,7 +1983,7 @@ device_ip_config_changed (NMDevice *device,
 			if (old_config)
 				nm_dns_manager_set_ip_config (priv->dns_manager, old_config, NM_DNS_IP_CONFIG_TYPE_REMOVED);
 		}
-		update_ip_dns (self, addr_family);
+		update_ip_dns (self, addr_family, device);
 		if (addr_family == AF_INET)
 			update_ip4_routing (self, TRUE);
 		else
@@ -2108,7 +2105,7 @@ vpn_connection_activated (NMPolicy *self, NMVpnConnection *vpn)
 	if (ip6_config)
 		nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_VPN);
 
-	update_routing_and_dns (self, TRUE);
+	update_routing_and_dns (self, TRUE, NULL);
 
 	nm_dns_manager_end_updates (priv->dns_manager, __func__);
 }
@@ -2130,7 +2127,7 @@ vpn_connection_deactivated (NMPolicy *self, NMVpnConnection *vpn)
 	if (ip6_config)
 		nm_dns_manager_set_ip_config (priv->dns_manager, NM_IP_CONFIG_CAST (ip6_config), NM_DNS_IP_CONFIG_TYPE_REMOVED);
 
-	update_routing_and_dns (self, TRUE);
+	update_routing_and_dns (self, TRUE, NULL);
 
 	nm_dns_manager_end_updates (priv->dns_manager, __func__);
 }
@@ -2498,7 +2495,8 @@ nm_policy_get_activating_ip6_ac (NMPolicy *self)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_hostname_mode_to_string, NMPolicyHostnameMode,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_hostname_mode_to_string, NMPolicyHostnameMode,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_NONE,  "none"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_DHCP,  "dhcp"),
@@ -2670,7 +2668,7 @@ dispose (GObject *object)
 	nm_clear_g_object (&priv->default_ac6);
 	nm_clear_g_object (&priv->activating_ac4);
 	nm_clear_g_object (&priv->activating_ac6);
-	g_clear_pointer (&priv->pending_active_connections, g_hash_table_unref);
+	nm_clear_pointer (&priv->pending_active_connections, g_hash_table_unref);
 
 	c_list_for_each_entry_safe (data, data_safe, &priv->pending_activation_checks, pending_lst)
 		activate_data_free (data);
@@ -2708,9 +2706,9 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->reset_retries_id);
 	nm_clear_g_source (&priv->schedule_activate_all_id);
 
-	g_clear_pointer (&priv->orig_hostname, g_free);
-	g_clear_pointer (&priv->cur_hostname, g_free);
-	g_clear_pointer (&priv->last_hostname, g_free);
+	nm_clear_g_free (&priv->orig_hostname);
+	nm_clear_g_free (&priv->cur_hostname);
+	nm_clear_g_free (&priv->last_hostname);
 
 	if (priv->hostname_manager) {
 		g_signal_handlers_disconnect_by_data (priv->hostname_manager, priv);
diff --git a/src/nm-proxy-config.c b/src/nm-proxy-config.c
index feee4afe..06bb89ba 100644
--- a/src/nm-proxy-config.c
+++ b/src/nm-proxy-config.c
@@ -65,7 +65,7 @@ nm_proxy_config_merge_setting (NMProxyConfig *config, NMSettingProxy *setting)
 
 	priv = NM_PROXY_CONFIG_GET_PRIVATE (config);
 
-	g_clear_pointer (&priv->pac_script, g_free);
+	nm_clear_g_free (&priv->pac_script);
 
 	method = nm_setting_proxy_get_method (setting);
 	switch (method) {
diff --git a/src/nm-session-monitor.c b/src/nm-session-monitor.c
index 5d5fe4c9..6fdc57eb 100644
--- a/src/nm-session-monitor.c
+++ b/src/nm-session-monitor.c
@@ -48,7 +48,7 @@ struct _NMSessionMonitor {
 #if SESSION_TRACKING_XLOGIND
 	struct {
 		sd_login_monitor *monitor;
-		guint watch;
+		GSource *watch;
 	} sd;
 #endif
 
@@ -90,7 +90,9 @@ st_sd_session_exists (NMSessionMonitor *monitor, uid_t uid, gboolean active)
 }
 
 static gboolean
-st_sd_changed (GIOChannel *stream, GIOCondition condition, gpointer user_data)
+st_sd_changed (int fd,
+               GIOCondition condition,
+               gpointer user_data)
 {
 	NMSessionMonitor *monitor = user_data;
 
@@ -98,14 +100,13 @@ st_sd_changed (GIOChannel *stream, GIOCondition condition, gpointer user_data)
 
 	sd_login_monitor_flush (monitor->sd.monitor);
 
-	return TRUE;
+	return G_SOURCE_CONTINUE;
 }
 
 static void
 st_sd_init (NMSessionMonitor *monitor)
 {
 	int status;
-	GIOChannel *stream;
 
 	if (!g_file_test ("/run/systemd/seats/", G_FILE_TEST_EXISTS))
 		return;
@@ -115,10 +116,13 @@ st_sd_init (NMSessionMonitor *monitor)
 		return;
 	}
 
-	stream = g_io_channel_unix_new (sd_login_monitor_get_fd (monitor->sd.monitor));
-	monitor->sd.watch = g_io_add_watch (stream, G_IO_IN, st_sd_changed, monitor);
-
-	g_io_channel_unref (stream);
+	monitor->sd.watch = nm_g_unix_fd_source_new (sd_login_monitor_get_fd (monitor->sd.monitor),
+	                                             G_IO_IN,
+	                                             G_PRIORITY_DEFAULT,
+	                                             st_sd_changed,
+	                                             monitor,
+	                                             NULL);
+	g_source_attach (monitor->sd.watch, NULL);
 }
 
 static void
@@ -128,7 +132,7 @@ st_sd_finalize (NMSessionMonitor *monitor)
 		sd_login_monitor_unref (monitor->sd.monitor);
 		monitor->sd.monitor = NULL;
 	}
-	nm_clear_g_source (&monitor->sd.watch);
+	nm_clear_g_source_inst (&monitor->sd.watch);
 }
 #endif /* SESSION_TRACKING_XLOGIND */
 
@@ -181,8 +185,8 @@ out:
 	if (error)
 		_LOGE ("failed to load ConsoleKit database: %s", error->message);
 	g_clear_error (&error);
-	g_clear_pointer (&groups, g_strfreev);
-	g_clear_pointer (&keyfile, g_key_file_free);
+	nm_clear_pointer (&groups, g_strfreev);
+	nm_clear_pointer (&keyfile, g_key_file_free);
 
 	return finished;
 }
@@ -267,7 +271,7 @@ ck_init (NMSessionMonitor *monitor)
 static void
 ck_finalize (NMSessionMonitor *monitor)
 {
-	g_clear_pointer (&monitor->ck.cache, g_hash_table_unref);
+	nm_clear_pointer (&monitor->ck.cache, g_hash_table_unref);
 	g_clear_object (&monitor->ck.monitor);
 }
 #endif /* SESSION_TRACKING_CONSOLEKIT */
diff --git a/src/nm-test-utils-core.h b/src/nm-test-utils-core.h
index e1d250dd..d984c6c0 100644
--- a/src/nm-test-utils-core.h
+++ b/src/nm-test-utils-core.h
@@ -7,7 +7,7 @@
 #define __NM_TEST_UTILS_CORE_H__
 
 #include "NetworkManagerUtils.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 #define _NMTST_INSIDE_CORE 1
 
diff --git a/src/nm-types.h b/src/nm-types.h
index 2f1ac2dc..4db9a19b 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -24,8 +24,7 @@ typedef struct _NMConfig             NMConfig;
 typedef struct _NMConfigData         NMConfigData;
 typedef struct _NMConnectivity       NMConnectivity;
 typedef struct _NMDevice             NMDevice;
-typedef struct _NMDhcp4Config        NMDhcp4Config;
-typedef struct _NMDhcp6Config        NMDhcp6Config;
+typedef struct _NMDhcpConfig         NMDhcpConfig;
 typedef struct _NMProxyConfig        NMProxyConfig;
 typedef struct _NMIPConfig           NMIPConfig;
 typedef struct _NMIP4Config          NMIP4Config;
@@ -43,6 +42,8 @@ typedef struct _NMConfigDeviceStateData NMConfigDeviceStateData;
 
 struct _NMDedupMultiIndex;
 
+typedef struct _NMRefString NMRefString;
+
 /*****************************************************************************/
 
 typedef enum {
@@ -116,20 +117,18 @@ typedef struct _NMPNetns                 NMPNetns;
 typedef struct _NMPObject                NMPObject;
 
 typedef enum {
-	/* Please don't interpret type numbers outside nm-platform and use functions
-	 * like nm_platform_link_is_software() and nm_platform_supports_slaves().
-	 *
-	 * type & 0x10000 -> Software device type
-	 * type & 0x20000 -> Type supports slaves
-	 */
 
 	/* No type, used as error value */
 	NM_LINK_TYPE_NONE,
 
-	/* Unknown type  */
 	NM_LINK_TYPE_UNKNOWN,
 
+	NM_LINK_TYPE_ANY,
+
+#define _NM_LINK_TYPE_REAL_FIRST NM_LINK_TYPE_ETHERNET
+
 	/* Hardware types */
+#define _NM_LINK_TYPE_HW_FIRST NM_LINK_TYPE_ETHERNET
 	NM_LINK_TYPE_ETHERNET,
 	NM_LINK_TYPE_INFINIBAND,
 	NM_LINK_TYPE_OLPC_MESH,
@@ -139,9 +138,11 @@ typedef enum {
 	NM_LINK_TYPE_WPAN,
 	NM_LINK_TYPE_6LOWPAN,
 	NM_LINK_TYPE_WIFI_P2P,
+#define _NM_LINK_TYPE_HW_LAST  NM_LINK_TYPE_WIFI_P2P
 
 	/* Software types */
-	NM_LINK_TYPE_BNEP = 0x10000,   /* Bluetooth Ethernet emulation */
+#define _NM_LINK_TYPE_SW_FIRST NM_LINK_TYPE_BNEP
+	NM_LINK_TYPE_BNEP,   /* Bluetooth Ethernet emulation */
 	NM_LINK_TYPE_DUMMY,
 	NM_LINK_TYPE_GRE,
 	NM_LINK_TYPE_GRETAP,
@@ -160,17 +161,40 @@ typedef enum {
 	NM_LINK_TYPE_TUN,
 	NM_LINK_TYPE_VETH,
 	NM_LINK_TYPE_VLAN,
+	NM_LINK_TYPE_VRF,
 	NM_LINK_TYPE_VXLAN,
 	NM_LINK_TYPE_WIREGUARD,
+#define _NM_LINK_TYPE_SW_LAST  NM_LINK_TYPE_WIREGUARD
 
 	/* Software types with slaves */
-	NM_LINK_TYPE_BRIDGE = 0x10000 | 0x20000,
+#define _NM_LINK_TYPE_SW_MASTER_FIRST NM_LINK_TYPE_BRIDGE
+	NM_LINK_TYPE_BRIDGE,
 	NM_LINK_TYPE_BOND,
 	NM_LINK_TYPE_TEAM,
+#define _NM_LINK_TYPE_SW_MASTER_LAST NM_LINK_TYPE_TEAM
+
+#define _NM_LINK_TYPE_REAL_LAST NM_LINK_TYPE_TEAM
+
+#define _NM_LINK_TYPE_REAL_NUM ((int) (_NM_LINK_TYPE_REAL_LAST - _NM_LINK_TYPE_REAL_FIRST + 1))
 
-	NM_LINK_TYPE_ANY = G_MAXUINT32,
 } NMLinkType;
 
+static inline gboolean
+nm_link_type_is_software (NMLinkType link_type)
+{
+	G_STATIC_ASSERT (_NM_LINK_TYPE_SW_LAST + 1 == _NM_LINK_TYPE_SW_MASTER_FIRST);
+
+	return    link_type >= _NM_LINK_TYPE_SW_FIRST
+	       && link_type <= _NM_LINK_TYPE_SW_MASTER_LAST;
+}
+
+static inline gboolean
+nm_link_type_supports_slaves (NMLinkType link_type)
+{
+	return    link_type >= _NM_LINK_TYPE_SW_MASTER_FIRST
+	       && link_type <= _NM_LINK_TYPE_SW_MASTER_LAST;
+}
+
 typedef enum {
 	NMP_OBJECT_TYPE_UNKNOWN,
 	NMP_OBJECT_TYPE_LINK,
@@ -197,6 +221,7 @@ typedef enum {
 	NMP_OBJECT_TYPE_LNK_SIT,
 	NMP_OBJECT_TYPE_LNK_TUN,
 	NMP_OBJECT_TYPE_LNK_VLAN,
+	NMP_OBJECT_TYPE_LNK_VRF,
 	NMP_OBJECT_TYPE_LNK_VXLAN,
 	NMP_OBJECT_TYPE_LNK_WIREGUARD,
 
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 6aae808a..4a81dc49 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -46,7 +46,7 @@ struct _NMFakePlatformClass {
 
 G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)
 
-#define NM_FAKE_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakePlatform, NM_IS_FAKE_PLATFORM)
+#define NM_FAKE_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakePlatform, NM_IS_FAKE_PLATFORM, NMPlatform)
 
 /*****************************************************************************/
 
@@ -126,7 +126,7 @@ static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in
 static gboolean
 sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 
 	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
 
@@ -138,7 +138,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 static char *
 sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *path)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	const char *v;
 
 	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
@@ -155,7 +155,7 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 static NMFakePlatformLink *
 link_get (NMPlatform *platform, int ifindex)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMFakePlatformLink *device;
 	int idx;
 
@@ -223,7 +223,7 @@ link_add_pre (NMPlatform *platform,
               const void *address,
               size_t address_len)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (platform);
 	NMFakePlatformLink *device;
 	int ifindex;
 	NMPObject *o;
@@ -276,11 +276,12 @@ link_add_pre (NMPlatform *platform,
 
 static int
 link_add (NMPlatform *platform,
-          const char *name,
           NMLinkType type,
-          const char *veth_peer,
+          const char *name,
+          int parent,
           const void *address,
           size_t address_len,
+          gconstpointer extra_data,
           const NMPlatformLink **out_link)
 {
 	NMFakePlatformLink *device;
@@ -291,14 +292,52 @@ link_add (NMPlatform *platform,
 	nm_auto_nmpobj const NMPObject *obj_new_veth = NULL;
 	NMPCacheOpsType cache_op;
 	NMPCacheOpsType cache_op_veth = NMP_CACHE_OPS_UNCHANGED;
+	const char *veth_peer = NULL;
+	NMPObject *dev_obj;
+	NMPObject *dev_lnk = NULL;
 
 	device = link_add_pre (platform, name, type, address, address_len);
 
-	if (veth_peer) {
-		g_assert (type == NM_LINK_TYPE_VETH);
+	g_assert (device);
+
+	dev_obj = (NMPObject *) device->obj;
+
+	if (parent > 0)
+		dev_obj->link.parent = parent;
+	else
+		g_assert (parent == 0);
+
+	g_assert ((parent != 0) == NM_IN_SET (type, NM_LINK_TYPE_VLAN));
+
+	switch (type) {
+	case NM_LINK_TYPE_VETH:
+		veth_peer = extra_data;
+		g_assert (veth_peer);
 		device_veth = link_add_pre (platform, veth_peer, type, NULL, 0);
-	} else
-		g_assert (type != NM_LINK_TYPE_VETH);
+		break;
+	case NM_LINK_TYPE_VLAN: {
+		const NMPlatformLnkVlan *props = extra_data;
+
+		g_assert (props);
+
+		dev_lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, props);
+		break;
+	}
+	case NM_LINK_TYPE_VXLAN: {
+		const NMPlatformLnkVxlan *props = extra_data;
+
+		g_assert (props);
+
+		dev_lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, props);
+		break;
+	}
+	default:
+		g_assert (!extra_data);
+		break;
+	}
+
+	if (dev_lnk)
+		dev_obj->_link.netlink.lnk = dev_lnk;
 
 	link_add_prepare (platform, device, (NMPObject *) device->obj);
 	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
@@ -381,7 +420,7 @@ link_delete (NMPlatform *platform, int ifindex)
 		return FALSE;
 
 	obj_old = g_steal_pointer (&device->obj);
-	g_clear_pointer (&device->udi, g_free);
+	nm_clear_g_free (&device->udi);
 
 	cache_op = nmp_cache_remove (nm_platform_get_cache (platform),
 	                             obj_old,
@@ -710,45 +749,6 @@ link_release (NMPlatform *platform, int master_idx, int slave_idx)
 	return TRUE;
 }
 
-struct vlan_add_data {
-	guint32 vlan_flags;
-	int parent;
-	int vlan_id;
-};
-
-static void
-_vlan_add_prepare (NMPlatform *platform,
-                   NMFakePlatformLink *device,
-                   gconstpointer user_data)
-{
-	const struct vlan_add_data *d = user_data;
-	NMPObject *obj_tmp;
-	NMPObject *lnk;
-
-	obj_tmp = (NMPObject *) device->obj;
-
-	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
-	lnk->lnk_vlan.id = d->vlan_id;
-	lnk->lnk_vlan.flags = d->vlan_flags;
-
-	obj_tmp->link.parent = d->parent;
-	obj_tmp->_link.netlink.lnk = lnk;
-}
-
-static gboolean
-vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
-{
-	const struct vlan_add_data d = {
-		.parent = parent,
-		.vlan_id = vlan_id,
-		.vlan_flags = vlan_flags,
-	};
-
-	link_add_one (platform, name, NM_LINK_TYPE_VLAN,
-	              _vlan_add_prepare, &d, out_link);
-	return TRUE;
-}
-
 static gboolean
 link_vlan_change (NMPlatform *platform,
                   int ifindex,
@@ -764,35 +764,6 @@ link_vlan_change (NMPlatform *platform,
 	return FALSE;
 }
 
-static void
-_vxlan_add_prepare (NMPlatform *platform,
-                    NMFakePlatformLink *device,
-                    gconstpointer user_data)
-{
-	const NMPlatformLnkVxlan *props = user_data;
-	NMPObject *obj_tmp;
-	NMPObject *lnk;
-
-	obj_tmp = (NMPObject *) device->obj;
-
-	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
-	lnk->lnk_vxlan = *props;
-
-	obj_tmp->link.parent = props->parent_ifindex;
-	obj_tmp->_link.netlink.lnk = lnk;
-}
-
-static gboolean
-link_vxlan_add (NMPlatform *platform,
-                const char *name,
-                const NMPlatformLnkVxlan *props,
-                const NMPlatformLink **out_link)
-{
-	link_add_one (platform, name, NM_LINK_TYPE_VXLAN,
-	              _vxlan_add_prepare, props, out_link);
-	return TRUE;
-}
-
 struct infiniband_add_data {
 	int parent;
 	int p_key;
@@ -967,6 +938,7 @@ ip4_address_add (NMPlatform *platform,
                  in_addr_t addr,
                  guint8 plen,
                  in_addr_t peer_addr,
+                 in_addr_t broadcast_address,
                  guint32 lifetime,
                  guint32 preferred,
                  guint32 flags,
@@ -974,16 +946,19 @@ ip4_address_add (NMPlatform *platform,
 {
 	NMPlatformIP4Address address;
 
-	memset (&address, 0, sizeof (address));
-	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
-	address.ifindex = ifindex;
-	address.address = addr;
-	address.peer_address = peer_addr;
-	address.plen = plen;
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
-	address.lifetime = lifetime;
-	address.preferred = preferred;
-	address.n_ifa_flags = flags;
+	address = (NMPlatformIP4Address) {
+		.addr_source               = NM_IP_CONFIG_SOURCE_KERNEL,
+		.ifindex                   = ifindex,
+		.address                   = addr,
+		.plen                      = plen,
+		.peer_address              = peer_addr,
+		.broadcast_address         = broadcast_address,
+		.use_ip4_broadcast_address = TRUE,
+		.timestamp                 = nm_utils_get_monotonic_timestamp_sec (),
+		.lifetime                  = lifetime,
+		.preferred                 = preferred,
+		.n_ifa_flags               = flags,
+	};
 	if (label)
 		g_strlcpy (address.label, label, sizeof (address.label));
 
@@ -1008,7 +983,7 @@ ip6_address_add (NMPlatform *platform,
 	address.address = addr;
 	address.peer_address = (IN6_IS_ADDR_UNSPECIFIED (&peer_addr) || IN6_ARE_ADDR_EQUAL (&addr, &peer_addr)) ? in6addr_any : peer_addr;
 	address.plen = plen;
-	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+	address.timestamp = nm_utils_get_monotonic_timestamp_sec ();
 	address.lifetime = lifetime;
 	address.preferred = preferred;
 	address.n_ifa_flags = flags;
@@ -1262,10 +1237,10 @@ ip_route_add (NMPlatform *platform,
 
 			if (addr_family == AF_INET) {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet4_ntop (r4->network, sbuf), r->plen, r->metric);
+				             r->ifindex, _nm_utils_inet4_ntop (r4->network, sbuf), r->plen, r->metric);
 			} else {
 				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
-				             r->ifindex, nm_utils_inet6_ntop (&r6->network, sbuf), r->plen, r->metric);
+				             r->ifindex, _nm_utils_inet6_ntop (&r6->network, sbuf), r->plen, r->metric);
 			}
 			return -NME_UNSPEC;
 		}
@@ -1354,19 +1329,16 @@ nm_fake_platform_setup (void)
 
 	nm_platform_setup (platform);
 
-	/* add loopback interface */
-	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, NULL, 0, NULL);
-
-	/* add some ethernets */
-	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
-	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
-	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
+	link_add (platform, NM_LINK_TYPE_LOOPBACK, "lo",   0, NULL, 0, NULL, NULL);
+	link_add (platform, NM_LINK_TYPE_ETHERNET, "eth0", 0, NULL, 0, NULL, NULL);
+	link_add (platform, NM_LINK_TYPE_ETHERNET, "eth1", 0, NULL, 0, NULL, NULL);
+	link_add (platform, NM_LINK_TYPE_ETHERNET, "eth2", 0, NULL, 0, NULL, NULL);
 }
 
 static void
 finalize (GObject *object)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) object);
+	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (object);
 	int i;
 
 	g_hash_table_unref (priv->options);
@@ -1374,7 +1346,7 @@ finalize (GObject *object)
 		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
 
 		g_free (device->udi);
-		g_clear_pointer (&device->obj, nmp_object_unref);
+		nm_clear_pointer (&device->obj, nmp_object_unref);
 	}
 	g_array_unref (priv->links);
 
@@ -1418,9 +1390,7 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
 
-	platform_class->vlan_add = vlan_add;
 	platform_class->link_vlan_change = link_vlan_change;
-	platform_class->link_vxlan_add = link_vxlan_add;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
 	platform_class->infiniband_partition_delete = infiniband_partition_delete;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 7abe4dfc..f7249bb7 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -418,13 +418,14 @@ typedef struct {
 	struct nl_sock *genl;
 
 	struct nl_sock *nlh;
+
+	GSource *event_source;
+
 	guint32 nlh_seq_next;
 #if NM_MORE_LOGGING
 	guint32 nlh_seq_last_handled;
 #endif
 	guint32 nlh_seq_last_seen;
-	GIOChannel *event_channel;
-	guint event_id;
 
 	guint32 pruning[_REFRESH_ALL_TYPE_NUM];
 
@@ -603,7 +604,7 @@ _vlan_qos_mapping_cmp_from_ptr (gconstpointer a, gconstpointer b, gpointer user_
  ******************************************************************/
 
 typedef struct {
-	const NMLinkType nm_type;
+
 	const char *type_string;
 
 	/* IFLA_INFO_KIND / rtnl_link_get_type() where applicable; the rtnl type
@@ -623,68 +624,202 @@ typedef struct {
 	const char *devtype;
 } LinkDesc;
 
-static const LinkDesc linktypes[] = {
-	{ NM_LINK_TYPE_NONE,          "none",        NULL,          NULL },
-	{ NM_LINK_TYPE_UNKNOWN,       "unknown",     NULL,          NULL },
-
-	{ NM_LINK_TYPE_ETHERNET,      "ethernet",    NULL,          NULL },
-	{ NM_LINK_TYPE_INFINIBAND,    "infiniband",  NULL,          NULL },
-	{ NM_LINK_TYPE_OLPC_MESH,     "olpc-mesh",   NULL,          NULL },
-	{ NM_LINK_TYPE_WIFI,          "wifi",        NULL,          "wlan" },
-	{ NM_LINK_TYPE_WWAN_NET,      "wwan",        NULL,          "wwan" },
-	{ NM_LINK_TYPE_WIMAX,         "wimax",       "wimax",       "wimax" },
-	{ NM_LINK_TYPE_WPAN,          "wpan",        NULL,          NULL },
-	{ NM_LINK_TYPE_6LOWPAN,       "6lowpan",     NULL,          NULL },
-
-	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
-	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
-	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
-	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
-	{ NM_LINK_TYPE_IFB,           "ifb",         "ifb",         NULL },
-	{ NM_LINK_TYPE_IP6TNL,        "ip6tnl",      "ip6tnl",      NULL },
-	{ NM_LINK_TYPE_IP6GRE,        "ip6gre",      "ip6gre",      NULL },
-	{ NM_LINK_TYPE_IP6GRETAP,     "ip6gretap",   "ip6gretap",   NULL },
-	{ NM_LINK_TYPE_IPIP,          "ipip",        "ipip",        NULL },
-	{ NM_LINK_TYPE_LOOPBACK,      "loopback",    NULL,          NULL },
-	{ NM_LINK_TYPE_MACSEC,        "macsec",      "macsec",      NULL },
-	{ NM_LINK_TYPE_MACVLAN,       "macvlan",     "macvlan",     NULL },
-	{ NM_LINK_TYPE_MACVTAP,       "macvtap",     "macvtap",     NULL },
-	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
-	{ NM_LINK_TYPE_PPP,           "ppp",         NULL,          "ppp" },
-	{ NM_LINK_TYPE_SIT,           "sit",         "sit",         NULL },
-	{ NM_LINK_TYPE_TUN,           "tun",         "tun",         NULL },
-	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
-	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
-	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
-	{ NM_LINK_TYPE_WIREGUARD,     "wireguard",   "wireguard",   "wireguard" },
-
-	{ NM_LINK_TYPE_BRIDGE,        "bridge",      "bridge",      "bridge" },
-	{ NM_LINK_TYPE_BOND,          "bond",        "bond",        "bond" },
-	{ NM_LINK_TYPE_TEAM,          "team",        "team",        NULL },
+static const LinkDesc link_descs[] = {
+	[NM_LINK_TYPE_NONE] =        { "none",        NULL,          NULL        },
+	[NM_LINK_TYPE_UNKNOWN] =     { "unknown",     NULL,          NULL        },
+	[NM_LINK_TYPE_ANY] =         { "any",         NULL,          NULL        },
+
+	[NM_LINK_TYPE_ETHERNET] =    { "ethernet",    NULL,          NULL        },
+	[NM_LINK_TYPE_INFINIBAND] =  { "infiniband",  NULL,          NULL        },
+	[NM_LINK_TYPE_OLPC_MESH] =   { "olpc-mesh",   NULL,          NULL        },
+	[NM_LINK_TYPE_WIFI] =        { "wifi",        NULL,          "wlan"      },
+	[NM_LINK_TYPE_WWAN_NET] =    { "wwan",        NULL,          "wwan"      },
+	[NM_LINK_TYPE_WIMAX] =       { "wimax",       "wimax",       "wimax"     },
+	[NM_LINK_TYPE_WPAN] =        { "wpan",        NULL,          NULL        },
+	[NM_LINK_TYPE_6LOWPAN] =     { "6lowpan",     NULL,          NULL        },
+
+	[NM_LINK_TYPE_BNEP] =        { "bluetooth",   NULL,          "bluetooth" },
+	[NM_LINK_TYPE_DUMMY] =       { "dummy",       "dummy",       NULL        },
+	[NM_LINK_TYPE_GRE] =         { "gre",         "gre",         NULL        },
+	[NM_LINK_TYPE_GRETAP] =      { "gretap",      "gretap",      NULL        },
+	[NM_LINK_TYPE_IFB] =         { "ifb",         "ifb",         NULL        },
+	[NM_LINK_TYPE_IP6TNL] =      { "ip6tnl",      "ip6tnl",      NULL        },
+	[NM_LINK_TYPE_IP6GRE] =      { "ip6gre",      "ip6gre",      NULL        },
+	[NM_LINK_TYPE_IP6GRETAP] =   { "ip6gretap",   "ip6gretap",   NULL        },
+	[NM_LINK_TYPE_IPIP] =        { "ipip",        "ipip",        NULL        },
+	[NM_LINK_TYPE_LOOPBACK] =    { "loopback",    NULL,          NULL        },
+	[NM_LINK_TYPE_MACSEC] =      { "macsec",      "macsec",      NULL        },
+	[NM_LINK_TYPE_MACVLAN] =     { "macvlan",     "macvlan",     NULL        },
+	[NM_LINK_TYPE_MACVTAP] =     { "macvtap",     "macvtap",     NULL        },
+	[NM_LINK_TYPE_OPENVSWITCH] = { "openvswitch", "openvswitch", NULL        },
+	[NM_LINK_TYPE_PPP] =         { "ppp",         NULL,          "ppp"       },
+	[NM_LINK_TYPE_SIT] =         { "sit",         "sit",         NULL        },
+	[NM_LINK_TYPE_TUN] =         { "tun",         "tun",         NULL        },
+	[NM_LINK_TYPE_VETH] =        { "veth",        "veth",        NULL        },
+	[NM_LINK_TYPE_VLAN] =        { "vlan",        "vlan",        "vlan"      },
+	[NM_LINK_TYPE_VRF] =         { "vrf",         "vrf",         "vrf"       },
+	[NM_LINK_TYPE_VXLAN] =       { "vxlan",       "vxlan",       "vxlan"     },
+	[NM_LINK_TYPE_WIREGUARD] =   { "wireguard",   "wireguard",   "wireguard" },
+
+	[NM_LINK_TYPE_BRIDGE] =      { "bridge",      "bridge",      "bridge"    },
+	[NM_LINK_TYPE_BOND] =        { "bond",        "bond",        "bond"      },
+	[NM_LINK_TYPE_TEAM] =        { "team",        "team",        NULL        },
 };
 
-static const char *
-nm_link_type_to_rtnl_type_string (NMLinkType type)
+static const LinkDesc *
+_link_desc_from_link_type (NMLinkType link_type)
 {
-	int i;
+	nm_assert (_NM_INT_NOT_NEGATIVE (link_type));
+	nm_assert (link_type < G_N_ELEMENTS (link_descs));
+	nm_assert (link_descs[link_type].type_string);
 
-	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-		if (type == linktypes[i].nm_type)
-			return linktypes[i].rtnl_type;
+	return &link_descs[link_type];
+}
+
+static NMLinkType
+_link_type_from_rtnl_type (const char *name) \
+{
+	static const NMLinkType LIST[] = {
+		NM_LINK_TYPE_BOND,        /* "bond"        */
+		NM_LINK_TYPE_BRIDGE,      /* "bridge"      */
+		NM_LINK_TYPE_DUMMY,       /* "dummy"       */
+		NM_LINK_TYPE_GRE,         /* "gre"         */
+		NM_LINK_TYPE_GRETAP,      /* "gretap"      */
+		NM_LINK_TYPE_IFB,         /* "ifb"         */
+		NM_LINK_TYPE_IP6GRE,      /* "ip6gre"      */
+		NM_LINK_TYPE_IP6GRETAP,   /* "ip6gretap"   */
+		NM_LINK_TYPE_IP6TNL,      /* "ip6tnl"      */
+		NM_LINK_TYPE_IPIP,        /* "ipip"        */
+		NM_LINK_TYPE_MACSEC,      /* "macsec"      */
+		NM_LINK_TYPE_MACVLAN,     /* "macvlan"     */
+		NM_LINK_TYPE_MACVTAP,     /* "macvtap"     */
+		NM_LINK_TYPE_OPENVSWITCH, /* "openvswitch" */
+		NM_LINK_TYPE_SIT,         /* "sit"         */
+		NM_LINK_TYPE_TEAM,        /* "team"        */
+		NM_LINK_TYPE_TUN,         /* "tun"         */
+		NM_LINK_TYPE_VETH,        /* "veth"        */
+		NM_LINK_TYPE_VLAN,        /* "vlan"        */
+		NM_LINK_TYPE_VRF,         /* "vrf"         */
+		NM_LINK_TYPE_VXLAN,       /* "vxlan"       */
+		NM_LINK_TYPE_WIMAX,       /* "wimax"       */
+		NM_LINK_TYPE_WIREGUARD,   /* "wireguard"   */
+	};
+
+	nm_assert (name);
+
+	if (NM_MORE_ASSERT_ONCE (5)) {
+		int i, j, k;
+
+		for (i = 0; i < G_N_ELEMENTS (LIST); i++) {
+			nm_assert (_link_desc_from_link_type (LIST[i]) == &link_descs[LIST[i]]);
+			nm_assert (link_descs[LIST[i]].rtnl_type);
+			if (i > 0)
+				nm_assert (strcmp (link_descs[LIST[i - 1]].rtnl_type, link_descs[LIST[i]].rtnl_type) < 0);
+		}
+		for (i = 0; i < G_N_ELEMENTS (link_descs); i++) {
+			if (!link_descs[i].rtnl_type)
+				continue;
+			for (j = 0, k = 0; j < G_N_ELEMENTS (LIST); j++)
+				k += (LIST[j] == i);
+			nm_assert (k == 1);
+		}
+	}
+
+	{
+		unsigned imin = 0;
+		unsigned imax = (G_N_ELEMENTS (LIST) - 1);
+		unsigned imid = (G_N_ELEMENTS (LIST) - 1) / 2;
+
+		for (;;) {
+			const int cmp = strcmp (link_descs[LIST[imid]].rtnl_type, name);
+
+			if (G_UNLIKELY (cmp == 0))
+				return LIST[imid];
+
+			if (cmp < 0)
+				imin = imid + 1u;
+			else
+				imax = imid - 1u;
+
+			if (G_UNLIKELY (imin > imax))
+				return NM_LINK_TYPE_NONE;
+
+			imid = (imin + imax) / 2u;
+		}
 	}
-	g_return_val_if_reached (NULL);
 }
 
-const char *
-nm_link_type_to_string (NMLinkType type)
-{
-	int i;
+static NMLinkType
+_link_type_from_devtype (const char *name) \
+{
+	static const NMLinkType LIST[] = {
+		NM_LINK_TYPE_BNEP,      /* "bluetooth" */
+		NM_LINK_TYPE_BOND,      /* "bond"      */
+		NM_LINK_TYPE_BRIDGE,    /* "bridge"    */
+		NM_LINK_TYPE_PPP,       /* "ppp"       */
+		NM_LINK_TYPE_VLAN,      /* "vlan"      */
+		NM_LINK_TYPE_VRF,       /* "vrf"       */
+		NM_LINK_TYPE_VXLAN,     /* "vxlan"     */
+		NM_LINK_TYPE_WIMAX,     /* "wimax"     */
+		NM_LINK_TYPE_WIREGUARD, /* "wireguard" */
+		NM_LINK_TYPE_WIFI,      /* "wlan"      */
+		NM_LINK_TYPE_WWAN_NET,  /* "wwan"      */
+	};
+
+	nm_assert (name);
 
-	for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-		if (type == linktypes[i].nm_type)
-			return linktypes[i].type_string;
+	if (NM_MORE_ASSERT_ONCE (5)) {
+		int i, j, k;
+
+		for (i = 0; i < G_N_ELEMENTS (LIST); i++) {
+			nm_assert (_link_desc_from_link_type (LIST[i]) == &link_descs[LIST[i]]);
+			nm_assert (link_descs[LIST[i]].devtype);
+			if (i > 0)
+				nm_assert (strcmp (link_descs[LIST[i - 1]].devtype, link_descs[LIST[i]].devtype) < 0);
+		}
+		for (i = 0; i < G_N_ELEMENTS (link_descs); i++) {
+			if (!link_descs[i].devtype)
+				continue;
+			for (j = 0, k = 0; j < G_N_ELEMENTS (LIST); j++)
+				k += (LIST[j] == i);
+			nm_assert (k == 1);
+		}
 	}
-	g_return_val_if_reached (NULL);
+
+	{
+		unsigned imin = 0;
+		unsigned imax = (G_N_ELEMENTS (LIST) - 1);
+		unsigned imid = (G_N_ELEMENTS (LIST) - 1) / 2;
+
+		for (;;) {
+			const int cmp = strcmp (link_descs[LIST[imid]].devtype, name);
+
+			if (G_UNLIKELY (cmp == 0))
+				return LIST[imid];
+
+			if (cmp < 0)
+				imin = imid + 1u;
+			else
+				imax = imid - 1u;
+
+			if (G_UNLIKELY (imin > imax))
+				return NM_LINK_TYPE_NONE;
+
+			imid = (imin + imax) / 2u;
+		}
+	}
+}
+
+static const char *
+nm_link_type_to_rtnl_type_string (NMLinkType link_type)
+{
+	return _link_desc_from_link_type (link_type)->rtnl_type;
+}
+
+const char *
+nm_link_type_to_string (NMLinkType link_type)
+{
+	return _link_desc_from_link_type (link_type)->type_string;
 }
 
 /******************************************************************
@@ -738,8 +873,8 @@ _addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 
 	/* do all the calculations in milliseconds scale */
 
-	now_nm = nm_utils_get_monotonic_timestamp_ms ();
-	now_nl = nm_utils_clock_gettime_ms (CLOCK_MONOTONIC);
+	now_nm = nm_utils_get_monotonic_timestamp_msec ();
+	now_nl = nm_utils_clock_gettime_msec (CLOCK_MONOTONIC);
 
 	nm_assert (now_nm >= 1000);
 	nm_assert (now_nl >= 0);
@@ -748,13 +883,13 @@ _addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 
 	NM_SET_OUT (out_now_nm, now_nm / 1000);
 
-	/* converting the timestamp into nm_utils_get_monotonic_timestamp_ms() scale is
+	/* converting the timestamp into nm_utils_get_monotonic_timestamp_msec() scale is
 	 * a good guess but fails in the following situations:
 	 *
 	 * - If the address existed before start of the process, the timestamp in nm scale would
 	 *   be negative or zero. In this case we default to 1.
 	 * - during hibernation, the CLOCK_MONOTONIC/timestamp drifts from
-	 *   nm_utils_get_monotonic_timestamp_ms() scale.
+	 *   nm_utils_get_monotonic_timestamp_msec() scale.
 	 */
 	if (result <= 1000)
 		return 1;
@@ -809,7 +944,7 @@ _addrtime_get_lifetimes (guint32 timestamp,
 		if (now == 0) {
 			/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
 			nm_assert (timestamp == 1);
-			now = nm_utils_get_monotonic_timestamp_s ();
+			now = nm_utils_get_monotonic_timestamp_sec ();
 		}
 		if (timestamp < now) {
 			guint32 diff = now - timestamp;
@@ -894,10 +1029,12 @@ _linktype_get_type (NMPlatform *platform,
                     const NMPObject **link_cached,
                     const char **out_kind)
 {
-	guint i;
+	NMLinkType link_type;
 
 	NMTST_ASSERT_PLATFORM_NETNS_CURRENT (platform);
 	nm_assert (ifname);
+	nm_assert (_link_type_from_devtype ("wlan") == NM_LINK_TYPE_WIFI);
+	nm_assert (_link_type_from_rtnl_type ("bond") == NM_LINK_TYPE_BOND);
 
 	if (completed_from_cache) {
 		const NMPObject *obj;
@@ -905,7 +1042,7 @@ _linktype_get_type (NMPlatform *platform,
 		obj = _lookup_cached_link (cache, ifindex, completed_from_cache, link_cached);
 
 		/* If we detected the link type before, we stick to that
-		 * decision unless the "kind" no "name" changed. If "name" changed,
+		 * decision unless the "kind" or "name" changed. If "name" changed,
 		 * it means that their type may not have been determined correctly
 		 * due to race conditions while accessing sysfs.
 		 *
@@ -936,11 +1073,9 @@ _linktype_get_type (NMPlatform *platform,
 	*out_kind = g_intern_string (kind);
 
 	if (kind) {
-		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-			if (nm_streq0 (kind, linktypes[i].rtnl_type)) {
-				return linktypes[i].nm_type;
-			}
-		}
+		link_type = _link_type_from_rtnl_type (kind);
+		if (link_type != NM_LINK_TYPE_NONE)
+			return link_type;
 	}
 
 	if (arptype == ARPHRD_LOOPBACK)
@@ -987,16 +1122,16 @@ _linktype_get_type (NMPlatform *platform,
 				return NM_LINK_TYPE_OLPC_MESH;
 
 			devtype = _linktype_read_devtype (dirfd);
-			for (i = 0; devtype && i < G_N_ELEMENTS (linktypes); i++) {
-				if (g_strcmp0 (devtype, linktypes[i].devtype) == 0) {
-					if (linktypes[i].nm_type == NM_LINK_TYPE_BNEP) {
+			if (devtype) {
+				link_type = _link_type_from_devtype (devtype);
+				if (link_type != NM_LINK_TYPE_NONE) {
+					if (   link_type == NM_LINK_TYPE_BNEP
+					    && arptype != ARPHRD_ETHER) {
 						/* Both BNEP and 6lowpan use DEVTYPE=bluetooth, so we must
 						 * use arptype to distinguish between them.
 						 */
-						if (arptype != ARPHRD_ETHER)
-							continue;
-					}
-					return linktypes[i].nm_type;
+					} else
+						return link_type;
 				}
 			}
 
@@ -1022,13 +1157,14 @@ _linktype_get_type (NMPlatform *platform,
 			 */
 			if (!kind && !devtype)
 				return NM_LINK_TYPE_ETHERNET;
+
 			/* The USB gadget interfaces behave and look like ordinary ethernet devices
 			 * aside from the DEVTYPE. */
-			if (!g_strcmp0 (devtype, "gadget"))
+			if (nm_streq0 (devtype, "gadget"))
 				return NM_LINK_TYPE_ETHERNET;
 
 			/* Distributed Switch Architecture switch chips */
-			if (!g_strcmp0 (devtype, "dsa"))
+			if (nm_streq0 (devtype, "dsa"))
 				return NM_LINK_TYPE_ETHERNET;
 		}
 	}
@@ -1753,6 +1889,8 @@ _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 #undef IFLA_VXLAN_MAX
 #define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6
 
+#define IFLA_VRF_TABLE        1
+
 /* older kernel header might not contain 'struct ifla_vxlan_port_range'.
  * Redefine it. */
 struct nm_ifla_vxlan_port_range {
@@ -1847,6 +1985,33 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 	return obj;
 }
 
+static NMPObject *
+_parse_lnk_vrf (const char *kind, struct nlattr *info_data)
+{
+	static const struct nla_policy policy[] = {
+		[IFLA_VRF_TABLE]         = { .type = NLA_U32 },
+	};
+	NMPlatformLnkVrf *props;
+	struct nlattr *tb[G_N_ELEMENTS (policy)];
+	NMPObject *obj;
+
+	if (   !info_data
+	    || !nm_streq0 (kind, "vrf"))
+		return NULL;
+
+	if (nla_parse_nested_arr (tb, info_data, policy) < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VRF, NULL);
+
+	props = &obj->lnk_vrf;
+
+	if (tb[IFLA_VRF_TABLE])
+		props->table = nla_get_u32 (tb[IFLA_VRF_TABLE]);
+
+	return obj;
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -2797,6 +2962,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_VLAN:
 		lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data);
 		break;
+	case NM_LINK_TYPE_VRF:
+		lnk_data = _parse_lnk_vrf (nl_info_kind, nl_info_data);
+		break;
 	case NM_LINK_TYPE_VXLAN:
 		lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data);
 		break;
@@ -2978,6 +3146,12 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 			memcpy (&obj->ip4_address.address, nla_data (tb[IFA_LOCAL]), addr_len);
 		if (tb[IFA_ADDRESS])
 			memcpy (&obj->ip4_address.peer_address, nla_data (tb[IFA_ADDRESS]), addr_len);
+
+		_check_addr_or_return_null (tb, IFA_BROADCAST, addr_len);
+		obj->ip4_address.broadcast_address =   tb[IFA_BROADCAST]
+		                                     ? nla_get_u32 (tb[IFA_BROADCAST])
+		                                     : 0u;
+		obj->ip4_address.use_ip4_broadcast_address = TRUE;
 	} else {
 		/* For IPv6, IFA_ADDRESS is always present.
 		 *
@@ -3689,13 +3863,13 @@ nla_put_failure:
 static gboolean
 _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
                                NMLinkType link_type,
-                               const char *veth_peer)
+                               gconstpointer extra_data)
 {
 	struct nlattr *info;
+	struct nlattr *data = NULL;
 	const char *kind;
 
 	nm_assert (msg);
-	nm_assert (!!veth_peer == (link_type == NM_LINK_TYPE_VETH));
 
 	kind = nm_link_type_to_rtnl_type_string (link_type);
 	if (!kind)
@@ -3706,9 +3880,89 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 
 	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
 
-	if (veth_peer) {
+	switch (link_type) {
+	case NM_LINK_TYPE_VLAN: {
+		const NMPlatformLnkVlan *props = extra_data;
+
+		nm_assert (extra_data);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U16 (msg, IFLA_VLAN_ID, props->id);
+
+		{
+			struct ifla_vlan_flags flags = {
+				.flags = props->flags & NM_VLAN_FLAGS_ALL,
+				.mask  = NM_VLAN_FLAGS_ALL,
+			};
+
+			NLA_PUT (msg, IFLA_VLAN_FLAGS, sizeof (flags), &flags);
+		}
+		break;
+	}
+	case NM_LINK_TYPE_VRF: {
+		const NMPlatformLnkVrf *props = extra_data;
+
+		nm_assert (extra_data);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, IFLA_VRF_TABLE, props->table);
+		break;
+	}
+	case NM_LINK_TYPE_VXLAN: {
+		const NMPlatformLnkVxlan *props = extra_data;
+
+		nm_assert (extra_data);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, IFLA_VXLAN_ID, props->id);
+
+		if (props->group)
+			NLA_PUT (msg, IFLA_VXLAN_GROUP, sizeof (props->group), &props->group);
+		else if (!IN6_IS_ADDR_UNSPECIFIED (&props->group6))
+			NLA_PUT (msg, IFLA_VXLAN_GROUP6, sizeof (props->group6), &props->group6);
+
+		if (props->local)
+			NLA_PUT (msg, IFLA_VXLAN_LOCAL, sizeof (props->local), &props->local);
+		else if (!IN6_IS_ADDR_UNSPECIFIED (&props->local6))
+			NLA_PUT (msg, IFLA_VXLAN_LOCAL6, sizeof (props->local6), &props->local6);
+
+		if (props->parent_ifindex >= 0)
+			NLA_PUT_U32 (msg, IFLA_VXLAN_LINK, props->parent_ifindex);
+
+		if (   props->src_port_min
+		    || props->src_port_max) {
+			struct nm_ifla_vxlan_port_range port_range = {
+				.low  = htons (props->src_port_min),
+				.high = htons (props->src_port_max),
+			};
+
+			NLA_PUT (msg, IFLA_VXLAN_PORT_RANGE, sizeof (port_range), &port_range);
+		}
+
+		NLA_PUT_U16 (msg, IFLA_VXLAN_PORT, htons (props->dst_port));
+		NLA_PUT_U8 (msg, IFLA_VXLAN_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_TTL, props->ttl);
+		NLA_PUT_U32 (msg, IFLA_VXLAN_AGEING, props->ageing);
+		NLA_PUT_U32 (msg, IFLA_VXLAN_LIMIT, props->limit);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_LEARNING, !!props->learning);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_PROXY, !!props->proxy);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_RSC, !!props->rsc);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_L2MISS, !!props->l2miss);
+		NLA_PUT_U8 (msg, IFLA_VXLAN_L3MISS, !!props->l3miss);
+		break;
+	}
+	case NM_LINK_TYPE_VETH: {
+		const char *veth_peer = extra_data;
 		const struct ifinfomsg ifi = { };
-		struct nlattr *data, *info_peer;
+		struct nlattr *info_peer;
+
+		nm_assert (veth_peer);
 
 		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
 			goto nla_put_failure;
@@ -3718,8 +3972,174 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 			goto nla_put_failure;
 		NLA_PUT_STRING (msg, IFLA_IFNAME, veth_peer);
 		nla_nest_end (msg, info_peer);
-		nla_nest_end (msg, data);
+		break;
+	}
+	case NM_LINK_TYPE_GRE:
+	case NM_LINK_TYPE_GRETAP: {
+		const NMPlatformLnkGre *props = extra_data;
+
+		nm_assert (props);
+		nm_assert (props->is_tap == (link_type == NM_LINK_TYPE_GRETAP));
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_GRE_LINK, props->parent_ifindex);
+		NLA_PUT_U32 (msg, IFLA_GRE_LOCAL, props->local);
+		NLA_PUT_U32 (msg, IFLA_GRE_REMOTE, props->remote);
+		NLA_PUT_U8 (msg, IFLA_GRE_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_GRE_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery);
+		NLA_PUT_U32 (msg, IFLA_GRE_IKEY, htonl (props->input_key));
+		NLA_PUT_U32 (msg, IFLA_GRE_OKEY, htonl (props->output_key));
+		NLA_PUT_U16 (msg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+		NLA_PUT_U16 (msg, IFLA_GRE_OFLAGS, htons (props->output_flags));
+		break;
+	}
+	case NM_LINK_TYPE_SIT: {
+		const NMPlatformLnkSit *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_IPTUN_LINK, props->parent_ifindex);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_LOCAL, props->local);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_REMOTE, props->remote);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
+		break;
+	}
+	case NM_LINK_TYPE_IP6TNL: {
+		const NMPlatformLnkIp6Tnl *props = extra_data;
+		guint32 flowinfo;
+
+		nm_assert (props);
+		nm_assert (!props->is_gre);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_IPTUN_LINK, props->parent_ifindex);
+
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->local))
+			NLA_PUT (msg, IFLA_IPTUN_LOCAL, sizeof (props->local), &props->local);
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->remote))
+			NLA_PUT (msg, IFLA_IPTUN_REMOTE, sizeof (props->remote), &props->remote);
+
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit);
+
+		flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
+		flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
+		            & IP6_FLOWINFO_TCLASS_MASK;
+		NLA_PUT_U32 (msg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
+		NLA_PUT_U8 (msg, IFLA_IPTUN_PROTO, props->proto);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_FLAGS, props->flags);
+		break;
+	}
+	case NM_LINK_TYPE_IP6GRE:
+	case NM_LINK_TYPE_IP6GRETAP: {
+		const NMPlatformLnkIp6Tnl *props = extra_data;
+		guint32 flowinfo;
+
+		nm_assert (props);
+		nm_assert (props->is_gre);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_GRE_LINK, props->parent_ifindex);
+
+		NLA_PUT_U32 (msg, IFLA_GRE_IKEY, htonl (props->input_key));
+		NLA_PUT_U32 (msg, IFLA_GRE_OKEY, htonl (props->output_key));
+		NLA_PUT_U16 (msg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+		NLA_PUT_U16 (msg, IFLA_GRE_OFLAGS, htons (props->output_flags));
+
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->local))
+			NLA_PUT (msg, IFLA_GRE_LOCAL, sizeof (props->local), &props->local);
+		if (!IN6_IS_ADDR_UNSPECIFIED (&props->local))
+			NLA_PUT (msg, IFLA_GRE_REMOTE, sizeof (props->remote), &props->remote);
+
+		NLA_PUT_U8 (msg, IFLA_GRE_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_GRE_ENCAP_LIMIT, props->encap_limit);
+
+		flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
+		flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
+		            & IP6_FLOWINFO_TCLASS_MASK;
+		NLA_PUT_U32 (msg, IFLA_GRE_FLOWINFO, htonl (flowinfo));
+		NLA_PUT_U32 (msg, IFLA_GRE_FLAGS, props->flags);
+		break;
+	}
+	case NM_LINK_TYPE_IPIP: {
+		const NMPlatformLnkIpIp *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->parent_ifindex)
+			NLA_PUT_U32 (msg, IFLA_IPTUN_LINK, props->parent_ifindex);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_LOCAL, props->local);
+		NLA_PUT_U32 (msg, IFLA_IPTUN_REMOTE, props->remote);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TTL, props->ttl);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_TOS, props->tos);
+		NLA_PUT_U8 (msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
+		break;
+	}
+	case NM_LINK_TYPE_MACSEC: {
+		const NMPlatformLnkMacsec *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		if (props->icv_length)
+			NLA_PUT_U8 (msg, IFLA_MACSEC_ICV_LEN, 16);
+		if (props->cipher_suite)
+			NLA_PUT_U64 (msg, IFLA_MACSEC_CIPHER_SUITE, props->cipher_suite);
+		if (props->replay_protect)
+			NLA_PUT_U32 (msg, IFLA_MACSEC_WINDOW, props->window);
+
+		NLA_PUT_U64 (msg, IFLA_MACSEC_SCI, htobe64 (props->sci));
+		NLA_PUT_U8 (msg, IFLA_MACSEC_ENCODING_SA, props->encoding_sa);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_ENCRYPT, props->encrypt);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_PROTECT, props->protect);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_INC_SCI, props->include_sci);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_ES, props->es);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_SCB, props->scb);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_REPLAY_PROTECT, props->replay_protect);
+		NLA_PUT_U8 (msg, IFLA_MACSEC_VALIDATION, props->validation);
+		break;
+	};
+	case NM_LINK_TYPE_MACVTAP:
+	case NM_LINK_TYPE_MACVLAN: {
+		const NMPlatformLnkMacvlan *props = extra_data;
+
+		nm_assert (props);
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, IFLA_MACVLAN_MODE, props->mode);
+		NLA_PUT_U16 (msg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0);
+		break;
 	}
+	default:
+		nm_assert (!extra_data);
+		break;
+	}
+
+	if (data)
+		nla_nest_end (msg, data);
 
 	nla_nest_end (msg, info);
 
@@ -3743,6 +4163,11 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 	guint i;
 	gboolean has_any_vlan_properties = FALSE;
 
+	G_STATIC_ASSERT (NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
+
 #define VLAN_XGRESS_PRIO_VALID(from) (((from) & ~(guint32) 0x07) == 0)
 
 	nm_assert (msg);
@@ -3892,6 +4317,7 @@ _nl_msg_new_address (int nlmsg_type,
                      int scope,
                      guint32 lifetime,
                      guint32 preferred,
+                     in_addr_t ip4_broadcast_address,
                      const char *label)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -3935,16 +4361,8 @@ _nl_msg_new_address (int nlmsg_type,
 	if (label && label[0])
 		NLA_PUT_STRING (msg, IFA_LABEL, label);
 
-	if (   family == AF_INET
-	    && nlmsg_type != RTM_DELADDR
-	    && plen < 31 /* RFC 3021 */
-	    && address
-	    && *((in_addr_t *) address) != 0) {
-		in_addr_t broadcast;
-
-		broadcast = *((in_addr_t *) address) | ~_nm_utils_ip4_prefix_to_netmask (plen);
-		NLA_PUT (msg, IFA_BROADCAST, addr_len, &broadcast);
-	}
+	if (ip4_broadcast_address != 0)
+		NLA_PUT (msg, IFA_BROADCAST, sizeof (in_addr_t), &ip4_broadcast_address);
 
 	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
 	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
@@ -4236,7 +4654,7 @@ _nl_msg_new_qdisc (int nlmsg_type,
 	if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
 		goto nla_put_failure;
 
-	if (strcmp (qdisc->kind, "fq_codel") == 0) {
+	if (nm_streq (qdisc->kind, "fq_codel")) {
 		if (qdisc->fq_codel.limit)
 			NLA_PUT_U32 (msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit);
 		if (qdisc->fq_codel.flows)
@@ -4909,7 +5327,8 @@ refresh_all_type_get_info (RefreshAllType refresh_all_type)
 	return &infos[refresh_all_type];
 }
 
-_NM_UTILS_LOOKUP_DEFINE (static, delayed_action_type_to_refresh_all_type, DelayedActionType, RefreshAllType,
+static
+NM_UTILS_LOOKUP_DEFINE (delayed_action_type_to_refresh_all_type, DelayedActionType, RefreshAllType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (0),
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS,             REFRESH_ALL_TYPE_LINKS),
 	NM_UTILS_LOOKUP_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES,     REFRESH_ALL_TYPE_IP4_ADDRESSES),
@@ -4992,7 +5411,8 @@ delayed_action_refresh_from_needle_object (const NMPObject *obj_needle)
 	return delayed_action_type_from_refresh_all_type (refresh_all_type_from_needle_object (obj_needle));
 }
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (delayed_action_to_string, DelayedActionType,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (delayed_action_to_string, DelayedActionType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS,             "refresh-all-links"),
 	NM_UTILS_LOOKUP_STR_ITEM (DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES,     "refresh-all-ip4-addresses"),
@@ -5031,14 +5451,14 @@ delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data
 		data = user_data;
 
 		if (data) {
-			gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_ns ();
+			gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_nsec ();
 			char b[255];
 
 			nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT", response-type %d%s%s)",
 			                        data->seq_number,
 			                        timeout < 0 ? "-" : "",
-			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NS_PER_SECOND,
-			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
+			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NSEC_PER_SEC,
+			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NSEC_PER_SEC,
 			                        (int) data->response_type,
 			                        data->seq_result ? ", " : "",
 			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, NULL, b, sizeof (b)) : "");
@@ -5140,7 +5560,7 @@ delayed_action_wait_for_nl_response_complete_check (NMPlatform *platform,
 		if (data->seq_result)
 			delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
 		else if (   p_now_ns
-		         && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) >= data->timeout_abs_ns)) {
+		         && ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_nsec ())) >= data->timeout_abs_ns)) {
 			/* the caller can optionally check for timeout by providing a p_now_ns argument. */
 			delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
 		} else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
@@ -5360,7 +5780,7 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
 {
 	DelayedActionWaitForNlResponseData data = {
 		.seq_number = seq_number,
-		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)),
+		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_nsec () + (200 * (NM_UTILS_NSEC_PER_SEC / 1000)),
 		.out_seq_result = out_seq_result,
 		.out_errmsg = out_errmsg,
 		.response_type = response_type,
@@ -6491,11 +6911,12 @@ out:
 
 static int
 link_add (NMPlatform *platform,
-          const char *name,
           NMLinkType type,
-          const char *veth_peer,
+          const char *name,
+          int parent,
           const void *address,
           size_t address_len,
+          gconstpointer extra_data,
           const NMPlatformLink **out_link)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -6519,10 +6940,13 @@ link_add (NMPlatform *platform,
 	if (!nlmsg)
 		return -NME_UNSPEC;
 
+	if (parent > 0)
+		NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
+
 	if (address && address_len)
 		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
-	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, veth_peer))
+	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, extra_data))
 		return -NME_UNSPEC;
 
 	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
@@ -7158,385 +7582,6 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 }
 
 static gboolean
-vlan_add (NMPlatform *platform,
-          const char *name,
-          int parent,
-          int vlan_id,
-          guint32 vlan_flags,
-          const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-
-	G_STATIC_ASSERT (NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR);
-	G_STATIC_ASSERT (NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP);
-	G_STATIC_ASSERT (NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING);
-	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
-
-	vlan_flags &= (guint32) NM_VLAN_FLAGS_ALL;
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!_nl_msg_new_link_set_linkinfo_vlan (nlmsg,
-	                                         vlan_id,
-	                                         NM_VLAN_FLAGS_ALL,
-	                                         vlan_flags,
-	                                         NULL,
-	                                         0,
-	                                         NULL,
-	                                         0))
-		return FALSE;
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_gre_add (NMPlatform *platform,
-              const char *name,
-              const NMPlatformLnkGre *props,
-              const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->is_tap ? "gretap" : "gre");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex);
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_LOCAL, props->local);
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_REMOTE, props->remote);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery);
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_ip6tnl_add (NMPlatform *platform,
-                 const char *name,
-                 const NMPlatformLnkIp6Tnl *props,
-                 const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-	guint32 flowinfo;
-
-	g_return_val_if_fail (!props->is_gre, FALSE);
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ip6tnl");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
-
-	if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_IPTUN_LOCAL, sizeof (props->local), &props->local);
-	if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_IPTUN_REMOTE, sizeof (props->remote), &props->remote);
-
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit);
-
-	flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
-	flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
-	            & IP6_FLOWINFO_TCLASS_MASK;
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLAGS, props->flags);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_ip6gre_add (NMPlatform *platform,
-                 const char *name,
-                 const NMPlatformLnkIp6Tnl *props,
-                 const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-	guint32 flowinfo;
-
-	g_return_val_if_fail (props->is_gre, FALSE);
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->is_tap ? "ip6gretap" : "ip6gre");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex);
-
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
-	NLA_PUT_U16 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
-
-	if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_GRE_LOCAL, sizeof (props->local), &props->local);
-	if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_GRE_REMOTE, sizeof (props->remote), &props->remote);
-
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_GRE_ENCAP_LIMIT, props->encap_limit);
-
-	flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
-	flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
-	            & IP6_FLOWINFO_TCLASS_MASK;
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_FLOWINFO, htonl (flowinfo));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_FLAGS, props->flags);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_ipip_add (NMPlatform *platform,
-               const char *name,
-               const NMPlatformLnkIpIp *props,
-               const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ipip");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_macsec_add (NMPlatform *platform,
-                 const char *name,
-                 int parent,
-                 const NMPlatformLnkMacsec *props,
-                 const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "macsec");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->icv_length)
-		NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ICV_LEN, 16);
-	if (props->cipher_suite)
-		NLA_PUT_U64 (nlmsg, IFLA_MACSEC_CIPHER_SUITE, props->cipher_suite);
-	if (props->replay_protect)
-		NLA_PUT_U32 (nlmsg, IFLA_MACSEC_WINDOW, props->window);
-
-	NLA_PUT_U64 (nlmsg, IFLA_MACSEC_SCI, htobe64 (props->sci));
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ENCODING_SA, props->encoding_sa);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ENCRYPT, props->encrypt);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_PROTECT, props->protect);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_INC_SCI, props->include_sci);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_ES, props->es);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_SCB, props->scb);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_REPLAY_PROTECT, props->replay_protect);
-	NLA_PUT_U8 (nlmsg, IFLA_MACSEC_VALIDATION, props->validation);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 NM_LINK_TYPE_MACSEC,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_macvlan_add (NMPlatform *platform,
-                  const char *name,
-                  int parent,
-                  const NMPlatformLnkMacvlan *props,
-                  const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->tap ? "macvtap" : "macvlan");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	NLA_PUT_U32 (nlmsg, IFLA_MACVLAN_MODE, props->mode);
-	NLA_PUT_U16 (nlmsg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_sit_add (NMPlatform *platform,
-              const char *name,
-              const NMPlatformLnkSit *props,
-              const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "sit");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	if (props->parent_ifindex)
-		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
-	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
 link_tun_add (NMPlatform *platform,
               const char *name,
               const NMPlatformLnkTun *props,
@@ -7592,107 +7637,6 @@ link_tun_add (NMPlatform *platform,
 	return TRUE;
 }
 
-static gboolean
-link_vxlan_add (NMPlatform *platform,
-                const char *name,
-                const NMPlatformLnkVxlan *props,
-                const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-	struct nlattr *data;
-	struct nm_ifla_vxlan_port_range port_range;
-
-	g_return_val_if_fail (props, FALSE);
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "vxlan");
-
-	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
-		goto nla_put_failure;
-
-	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_ID, props->id);
-
-	if (props->group)
-		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP, sizeof (props->group), &props->group);
-	else if (memcmp (&props->group6, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP6, sizeof (props->group6), &props->group6);
-
-	if (props->local)
-		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL, sizeof (props->local), &props->local);
-	else if (memcmp (&props->local6, &in6addr_any, sizeof (in6addr_any)))
-		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL6, sizeof (props->local6), &props->local6);
-
-	if (props->parent_ifindex >= 0)
-		NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LINK, props->parent_ifindex);
-
-	if (props->src_port_min || props->src_port_max) {
-		port_range.low = htons (props->src_port_min);
-		port_range.high = htons (props->src_port_max);
-		NLA_PUT (nlmsg, IFLA_VXLAN_PORT_RANGE, sizeof (port_range), &port_range);
-	}
-
-	NLA_PUT_U16 (nlmsg, IFLA_VXLAN_PORT, htons (props->dst_port));
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TOS, props->tos);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TTL, props->ttl);
-	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_AGEING, props->ageing);
-	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LIMIT, props->limit);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_LEARNING, !!props->learning);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_PROXY, !!props->proxy);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_RSC, !!props->rsc);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L2MISS, !!props->l2miss);
-	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L3MISS, !!props->l3miss);
-
-	nla_nest_end (nlmsg, data);
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-static gboolean
-link_6lowpan_add (NMPlatform *platform,
-                  const char *name,
-                  int parent,
-                  const NMPlatformLink **out_link)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct nlattr *info;
-
-	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
-	                          NLM_F_CREATE | NLM_F_EXCL,
-	                          0,
-	                          name);
-	if (!nlmsg)
-		return FALSE;
-
-	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
-
-	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
-		goto nla_put_failure;
-
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "lowpan");
-
-	nla_nest_end (nlmsg, info);
-
-	return (do_add_link_with_lookup (platform,
-	                                 NM_LINK_TYPE_6LOWPAN,
-	                                 name, nlmsg, out_link) >= 0);
-nla_put_failure:
-	g_return_val_if_reached (FALSE);
-}
-
-
 static void
 _vlan_change_vlan_qos_mapping_create (gboolean is_ingress_map,
                                       gboolean reset_all,
@@ -8196,6 +8140,7 @@ ip4_address_add (NMPlatform *platform,
                  in_addr_t addr,
                  guint8 plen,
                  in_addr_t peer_addr,
+                 in_addr_t broadcast_address,
                  guint32 lifetime,
                  guint32 preferred,
                  guint32 flags,
@@ -8215,6 +8160,7 @@ ip4_address_add (NMPlatform *platform,
 	                             nm_utils_ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
 	                             lifetime,
 	                             preferred,
+	                             broadcast_address,
 	                             label);
 
 	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
@@ -8245,6 +8191,7 @@ ip6_address_add (NMPlatform *platform,
 	                             RT_SCOPE_UNIVERSE,
 	                             lifetime,
 	                             preferred,
+	                             0,
 	                             NULL);
 
 	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
@@ -8268,6 +8215,7 @@ ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 pl
 	                             RT_SCOPE_NOWHERE,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             0,
 	                             NULL);
 	if (!nlmsg)
 		g_return_val_if_reached (FALSE);
@@ -8293,6 +8241,7 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, gui
 	                             RT_SCOPE_NOWHERE,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
 	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             0,
 	                             NULL);
 	if (!nlmsg)
 		g_return_val_if_reached (FALSE);
@@ -8402,7 +8351,7 @@ ip_route_get (NMPlatform *platform,
 			.r.rtm_flags = 0x1000 /* RTM_F_LOOKUP_TABLE */,
 		};
 
-		g_clear_pointer (&route, nmp_object_unref);
+		nm_clear_pointer (&route, nmp_object_unref);
 
 		if (!_nl_addattr_l (&req.n, sizeof (req), RTA_DST, address, addr_len))
 			nm_assert_not_reached ();
@@ -8576,12 +8525,8 @@ tfilter_add (NMPlatform *platform,
 
 /*****************************************************************************/
 
-#define EVENT_CONDITIONS      ((GIOCondition) (G_IO_IN | G_IO_PRI))
-#define ERROR_CONDITIONS      ((GIOCondition) (G_IO_ERR | G_IO_NVAL))
-#define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP))
-
 static gboolean
-event_handler (GIOChannel *channel,
+event_handler (int fd,
                GIOCondition io_condition,
                gpointer user_data)
 {
@@ -8609,7 +8554,7 @@ event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 	nm_auto_free unsigned char *buf = NULL;
 
 continue_reading:
-	g_clear_pointer (&buf, free);
+	nm_clear_pointer (&buf, free);
 	n = nl_recv (sk, &nla, &buf, &creds, &creds_has);
 
 	if (n <= 0) {
@@ -8800,7 +8745,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 	int r;
 	struct pollfd pfd;
 	gboolean any = FALSE;
-	int timeout_ms;
+	int timeout_msec;
 	struct {
 		guint32 seq_number;
 		gint64 timeout_abs_ns;
@@ -8881,12 +8826,12 @@ after_read:
 
 		_LOGT ("netlink: read: wait for ACK for sequence number %u...", next.seq_number);
 
-		timeout_ms = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
+		timeout_msec = (next.timeout_abs_ns - next.now_ns) / (NM_UTILS_NSEC_PER_SEC / 1000);
 
 		memset (&pfd, 0, sizeof (pfd));
 		pfd.fd = nl_socket_get_fd (priv->nlh);
 		pfd.events = POLLIN;
-		r = poll (&pfd, 1, MAX (1, timeout_ms));
+		r = poll (&pfd, 1, MAX (1, timeout_msec));
 
 		if (r == 0) {
 			/* timeout and there is nothing to read. */
@@ -9052,14 +8997,13 @@ constructed (GObject *_object)
 {
 	NMPlatform *platform = NM_PLATFORM (_object);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int channel_flags;
-	gboolean status;
 	int nle;
+	int fd;
 
 	nm_assert (!platform->_netns || platform->_netns == nmp_netns_get_current ());
 
 	if (nm_platform_get_use_udev (platform)) {
-		priv->udev_client = nm_udev_client_new ((const char *[]) { "net", NULL },
+		priv->udev_client = nm_udev_client_new (NM_MAKE_STRV ("net"),
 		                                        handle_udev_event, platform);
 	}
 
@@ -9123,18 +9067,18 @@ constructed (GObject *_object)
 	                                 RTNLGRP_TC,
 	                                 0);
 	g_assert (!nle);
-	_LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
-	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh));
-	g_io_channel_set_encoding (priv->event_channel, NULL, NULL);
+	fd = nl_socket_get_fd (priv->nlh);
+
+	_LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), fd);
 
-	channel_flags = g_io_channel_get_flags (priv->event_channel);
-	status = g_io_channel_set_flags (priv->event_channel,
-	                                 channel_flags | G_IO_FLAG_NONBLOCK, NULL);
-	g_assert (status);
-	priv->event_id = g_io_add_watch (priv->event_channel,
-	                                (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS),
-	                                 event_handler, platform);
+	priv->event_source = nm_g_unix_fd_source_new (fd,
+	                                              G_IO_IN | G_IO_NVAL | G_IO_PRI | G_IO_ERR | G_IO_HUP,
+	                                              G_PRIORITY_DEFAULT,
+	                                              event_handler,
+	                                              platform,
+	                                              NULL);
+	g_source_attach (priv->event_source, NULL);
 
 	/* complete construction of the GObject instance before populating the cache. */
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->constructed (_object);
@@ -9227,8 +9171,8 @@ finalize (GObject *object)
 
 	nl_socket_free (priv->genl);
 
-	g_source_remove (priv->event_id);
-	g_io_channel_unref (priv->event_channel);
+	nm_clear_g_source_inst (&priv->event_source);
+
 	nl_socket_free (priv->nlh);
 
 	if (priv->sysctl_get_prev_values) {
@@ -9294,10 +9238,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 
 	platform_class->link_can_assume = link_can_assume;
 
-	platform_class->vlan_add = vlan_add;
 	platform_class->link_vlan_change = link_vlan_change;
 	platform_class->link_wireguard_change = link_wireguard_change;
-	platform_class->link_vxlan_add = link_vxlan_add;
 
 	platform_class->infiniband_partition_add = infiniband_partition_add;
 	platform_class->infiniband_partition_delete = infiniband_partition_delete;
@@ -9325,15 +9267,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->wpan_set_short_addr = wpan_set_short_addr;
 	platform_class->wpan_set_channel = wpan_set_channel;
 
-	platform_class->link_gre_add = link_gre_add;
-	platform_class->link_ip6tnl_add = link_ip6tnl_add;
-	platform_class->link_ip6gre_add = link_ip6gre_add;
-	platform_class->link_macsec_add = link_macsec_add;
-	platform_class->link_macvlan_add = link_macvlan_add;
-	platform_class->link_ipip_add = link_ipip_add;
-	platform_class->link_sit_add = link_sit_add;
 	platform_class->link_tun_add = link_tun_add;
-	platform_class->link_6lowpan_add = link_6lowpan_add;
 
 	platform_class->object_delete = object_delete;
 	platform_class->ip4_address_add = ip4_address_add;
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index b55fbd66..98884cd7 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -262,7 +262,8 @@ out:
  * ethtool
  *****************************************************************************/
 
-NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
+static
+NM_UTILS_ENUM2STR_DEFINE (_ethtool_cmd_to_string, guint32,
 	NM_UTILS_ENUM2STR (ETHTOOL_GDRVINFO,   "ETHTOOL_GDRVINFO"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GFEATURES,  "ETHTOOL_GFEATURES"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GLINK,      "ETHTOOL_GLINK"),
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 6795dde7..9bbc3745 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -298,7 +298,8 @@ nm_platform_get_multi_idx (NMPlatform *self)
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_ADD,     "add"),
 	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_CHANGE,  "change"),
@@ -1150,11 +1151,12 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
 /**
  * nm_platform_link_add:
  * @self: platform instance
- * @name: Interface name
  * @type: Interface type
- * @veth_peer: For veths, the peer name
+ * @name: Interface name
+ * @parent: the IFLA_LINK parameter or 0.
  * @address: (allow-none): set the mac address of the link
  * @address_len: the length of the @address
+ * @extra_data: depending on @type, additional data.
  * @out_link: on success, the link object
  *
  * Add a software interface.  If the interface already exists and is of type
@@ -1167,66 +1169,104 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
  *
  * Returns: the negative nm-error on failure.
  */
-static int
+int
 nm_platform_link_add (NMPlatform *self,
-                      const char *name,
                       NMLinkType type,
-                      const char *veth_peer,
+                      const char *name,
+                      int parent,
                       const void *address,
                       size_t address_len,
+                      gconstpointer extra_data,
                       const NMPlatformLink **out_link)
 {
 	int r;
 	char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3];
+	char parent_buf[64];
+	char buf[512];
 
 	_CHECK_SELF (self, klass, -NME_BUG);
 
 	g_return_val_if_fail (name, -NME_BUG);
 	g_return_val_if_fail ((address != NULL) ^ (address_len == 0) , -NME_BUG);
 	g_return_val_if_fail (address_len <= NM_UTILS_HWADDR_LEN_MAX, -NME_BUG);
-	g_return_val_if_fail ((!!veth_peer) == (type == NM_LINK_TYPE_VETH), -NME_BUG);
+	g_return_val_if_fail (parent >= 0, -NME_BUG);
 
 	r = _link_add_check_existing (self, name, type, out_link);
 	if (r < 0)
 		return r;
 
-	_LOG2D ("link: adding link: %s (%d)"
-	        "%s%s" /* address */
-	        "%s%s" /* veth peer */
+	_LOG2D ("link: adding link: "
+	        "%s "    /* type */
+	        "\"%s\"" /* name */
+	        "%s%s"   /* parent */
+	        "%s%s"   /* address */
+	        "%s"     /* extra_data */
 	        "",
 	        nm_link_type_to_string (type),
-	        (int) type,
+	        name,
+	        parent > 0 ? ", parent " : "",
+	        parent > 0 ? nm_sprintf_buf (parent_buf, "%d", parent) : "",
 	        address ? ", address: " : "",
 	        address ? nm_utils_hwaddr_ntoa_buf (address, address_len, FALSE, addr_buf, sizeof (addr_buf)) : "",
-	        veth_peer ? ", veth-peer: " : "",
-	        veth_peer ?: "");
-
-	return klass->link_add (self, name, type, veth_peer, address, address_len, out_link);
-}
-
-int
-nm_platform_link_veth_add (NMPlatform *self,
-                            const char *name,
-                            const char *peer,
-                            const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_VETH, peer, NULL, 0, out_link);
-}
-
-/**
- * nm_platform_link_dummy_add:
- * @self: platform instance
- * @name: New interface name
- * @out_link: on success, the link object
- *
- * Create a software ethernet-like interface
- */
-int
-nm_platform_link_dummy_add (NMPlatform *self,
-                            const char *name,
-                            const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, NULL, 0, out_link);
+	        ({
+	            char *buf_p = buf;
+	            gsize buf_len = sizeof (buf);
+
+	            buf[0] = '\0';
+
+	            switch (type) {
+	            case NM_LINK_TYPE_VLAN:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_vlan_to_string ((const NMPlatformLnkVlan *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_VRF:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_vrf_to_string ((const NMPlatformLnkVrf *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_VXLAN:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_vxlan_to_string ((const NMPlatformLnkVxlan *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_VETH:
+	                nm_sprintf_buf (buf, ", veth-peer \"%s\"", (const char *) extra_data);
+	                break;
+	            case NM_LINK_TYPE_GRE:
+	            case NM_LINK_TYPE_GRETAP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_gre_to_string ((const NMPlatformLnkGre *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_SIT:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_sit_to_string ((const NMPlatformLnkSit *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_IP6TNL:
+	            case NM_LINK_TYPE_IP6GRE:
+	            case NM_LINK_TYPE_IP6GRETAP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_ip6tnl_to_string ((const NMPlatformLnkIp6Tnl *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_IPIP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_ipip_to_string ((const NMPlatformLnkIpIp *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_MACSEC:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_macsec_to_string ((const NMPlatformLnkMacsec *) extra_data, buf_p, buf_len);
+	                break;
+	            case NM_LINK_TYPE_MACVLAN:
+	            case NM_LINK_TYPE_MACVTAP:
+	                nm_utils_strbuf_append_str (&buf_p, &buf_len, ", ");
+	                nm_platform_lnk_macvlan_to_string ((const NMPlatformLnkMacvlan *) extra_data, buf_p, buf_len);
+	                break;
+	            default:
+	                nm_assert (!extra_data);
+	                break;
+	            }
+
+	            buf;
+	        }));
+
+	return klass->link_add (self, type, name, parent, address, address_len, extra_data, out_link);
 }
 
 /**
@@ -1283,7 +1323,7 @@ nm_platform_link_get_ifindex (NMPlatform *self, const char *name)
 }
 
 const char *
-nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */)
+nm_platform_if_indextoname (NMPlatform *self, int ifindex, char out_ifname[static 16 /* IFNAMSIZ */])
 {
 	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
 
@@ -1408,7 +1448,7 @@ nm_platform_link_get_unmanaged (NMPlatform *self, int ifindex, gboolean *unmanag
 gboolean
 nm_platform_link_is_software (NMPlatform *self, int ifindex)
 {
-	return (nm_platform_link_get_type (self, ifindex) & 0x10000);
+	return nm_link_type_is_software (nm_platform_link_get_type (self, ifindex));
 }
 
 /**
@@ -1422,7 +1462,7 @@ nm_platform_link_is_software (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_supports_slaves (NMPlatform *self, int ifindex)
 {
-	return (nm_platform_link_get_type (self, ifindex) & 0x20000);
+	return nm_link_type_supports_slaves (nm_platform_link_get_type (self, ifindex));
 }
 
 /**
@@ -2193,7 +2233,7 @@ nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatfor
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVLAN, out_link);
 }
 
-const NMPlatformLnkMacvtap *
+const NMPlatformLnkMacvlan *
 nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_MACVTAP, out_link);
@@ -2217,6 +2257,12 @@ nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLi
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VLAN, out_link);
 }
 
+const NMPlatformLnkVrf *
+nm_platform_link_get_lnk_vrf (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VRF, out_link);
+}
+
 const NMPlatformLnkVxlan *
 nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -2231,7 +2277,8 @@ nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatf
 
 /*****************************************************************************/
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_flags_to_string, NMPlatformWireGuardChangeFlags,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_wireguard_change_flags_to_string, NMPlatformWireGuardChangeFlags,
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE,            "none"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS,   "replace-peers"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY, "has-private-key"),
@@ -2239,7 +2286,8 @@ NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_flags_to_string, NMPlatformW
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK,      "has-fwmark"),
 );
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_peer_flags_to_string, NMPlatformWireGuardChangePeerFlags,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_wireguard_change_peer_flags_to_string, NMPlatformWireGuardChangePeerFlags,
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_NONE,                   "none"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_REMOVE_ME,              "remove"),
 	NM_UTILS_FLAGS2STR (NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_HAS_PRESHARED_KEY,      "psk"),
@@ -2250,14 +2298,6 @@ NM_UTILS_FLAGS2STR_DEFINE_STATIC (_wireguard_change_peer_flags_to_string, NMPlat
 );
 
 int
-nm_platform_link_wireguard_add (NMPlatform *self,
-                                const char *name,
-                                const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_WIREGUARD, NULL, NULL, 0, out_link);
-}
-
-int
 nm_platform_link_wireguard_change (NMPlatform *self,
                                    int ifindex,
                                    const NMPlatformLnkWireGuard *lnk_wireguard,
@@ -2316,128 +2356,6 @@ nm_platform_link_wireguard_change (NMPlatform *self,
 /*****************************************************************************/
 
 /**
- * nm_platform_link_bridge_add:
- * @self: platform instance
- * @name: New interface name
- * @address: (allow-none): set the mac address of the new bridge
- * @address_len: the length of the @address
- * @out_link: on success, the link object
- *
- * Create a software bridge.
- */
-int
-nm_platform_link_bridge_add (NMPlatform *self,
-                             const char *name,
-                             const void *address,
-                             size_t address_len,
-                             const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_BRIDGE, NULL, address, address_len, out_link);
-}
-
-/**
- * nm_platform_link_bond_add:
- * @self: platform instance
- * @name: New interface name
- * @out_link: on success, the link object
- *
- * Create a software bonding device.
- */
-int
-nm_platform_link_bond_add (NMPlatform *self,
-                           const char *name,
-                           const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_BOND, NULL, NULL, 0, out_link);
-}
-
-/**
- * nm_platform_link_team_add:
- * @self: platform instance
- * @name: New interface name
- * @out_link: on success, the link object
- *
- * Create a software teaming device.
- */
-int
-nm_platform_link_team_add (NMPlatform *self,
-                           const char *name,
-                           const NMPlatformLink **out_link)
-{
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_TEAM, NULL, NULL, 0, out_link);
-}
-
-/**
- * nm_platform_link_vlan_add:
- * @self: platform instance
- * @name: New interface name
- * @vlanid: VLAN identifier
- * @vlanflags: VLAN flags from libnm
- * @out_link: on success, the link object
- *
- * Create a software VLAN device.
- */
-int
-nm_platform_link_vlan_add (NMPlatform *self,
-                           const char *name,
-                           int parent,
-                           int vlanid,
-                           guint32 vlanflags,
-                           const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (parent >= 0, -NME_BUG);
-	g_return_val_if_fail (vlanid >= 0, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VLAN, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("link: adding link vlan parent %d vlanid %d vlanflags %x",
-	        parent, vlanid, vlanflags);
-
-	if (!klass->vlan_add (self, name, parent, vlanid, vlanflags, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_link_vxlan_add:
- * @self: platform instance
- * @name: New interface name
- * @props: properties of the new link
- * @out_link: on success, the link object
- *
- * Create a VXLAN device.
- */
-int
-nm_platform_link_vxlan_add (NMPlatform *self,
-                            const char *name,
-                            const NMPlatformLnkVxlan *props,
-                            const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_VXLAN, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("link: adding link %s", nm_platform_lnk_vxlan_to_string (props, NULL, 0));
-
-	if (!klass->link_vxlan_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
  * nm_platform_link_tun_add:
  * @self: platform instance
  * @name: new interface name
@@ -2489,38 +2407,6 @@ nm_platform_link_tun_add (NMPlatform *self,
 	return 0;
 }
 
-/**
- * nm_platform_6lowpan_add:
- * @self: platform instance
- * @parent: parent link
- * @name: name of the new interface
- * @out_link: on success, the link object
- *
- * Create a 6LoWPAN interface.
- */
-int
-nm_platform_link_6lowpan_add (NMPlatform *self,
-                              const char *name,
-                              int parent,
-                              const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link 6lowpan parent %u", parent);
-
-	if (!klass->link_6lowpan_add (self, name, parent, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
 gboolean
 nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out_parent)
 {
@@ -2761,39 +2647,6 @@ nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, i
 	return nm_platform_link_vlan_change (self, ifindex, 0, 0, FALSE, NULL, 0, FALSE, &map, 1);
 }
 
-/**
- * nm_platform_link_gre_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a software GRE device.
- */
-int
-nm_platform_link_gre_add (NMPlatform *self,
-                          const char *name,
-                          const NMPlatformLnkGre *props,
-                          const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_gre_to_string (props, NULL, 0));
-
-	if (!klass->link_gre_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
 static int
 _infiniband_add_add_or_delete (NMPlatform *self,
                                int ifindex,
@@ -2918,217 +2771,6 @@ nm_platform_link_infiniband_get_properties (NMPlatform *self,
 	return TRUE;
 }
 
-/**
- * nm_platform_ip6tnl_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create an IPv6 tunnel.
- */
-int
-nm_platform_link_ip6tnl_add (NMPlatform *self,
-                             const char *name,
-                             const NMPlatformLnkIp6Tnl *props,
-                             const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-	g_return_val_if_fail (!props->is_gre, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
-
-	if (!klass->link_ip6tnl_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_ip6gre_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create an IPv6 GRE/GRETAP tunnel.
- */
-int
-nm_platform_link_ip6gre_add (NMPlatform *self,
-                             const char *name,
-                             const NMPlatformLnkIp6Tnl *props,
-                             const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-	g_return_val_if_fail (props->is_gre, -NME_BUG);
-
-	r = _link_add_check_existing (self,
-	                              name,
-	                              props->is_tap
-	                                  ? NM_LINK_TYPE_IP6GRETAP
-	                                  : NM_LINK_TYPE_IP6GRE,
-	                              out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
-
-	if (!klass->link_ip6gre_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_ipip_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create an IPIP tunnel.
- */
-int
-nm_platform_link_ipip_add (NMPlatform *self,
-                           const char *name,
-                           const NMPlatformLnkIpIp *props,
-                           const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_IPIP, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_ipip_to_string (props, NULL, 0));
-
-	if (!klass->link_ipip_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_macsec_add:
- * @self: platform instance
- * @name: name of the new interface
- * @parent: parent link
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a MACsec interface.
- */
-int
-nm_platform_link_macsec_add (NMPlatform *self,
-                             const char *name,
-                             int parent,
-                             const NMPlatformLnkMacsec *props,
-                             const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_MACSEC, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_macsec_to_string (props, NULL, 0));
-
-	if (!klass->link_macsec_add (self, name, parent, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_macvlan_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a MACVLAN or MACVTAP device.
- */
-int
-nm_platform_link_macvlan_add (NMPlatform *self,
-                              const char *name,
-                              int parent,
-                              const NMPlatformLnkMacvlan *props,
-                              const NMPlatformLink **out_link)
-{
-	int r;
-	NMLinkType type;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	type = props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN;
-
-	r = _link_add_check_existing (self, name, type, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_macvlan_to_string (props, NULL, 0));
-
-	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
-/**
- * nm_platform_sit_add:
- * @self: platform instance
- * @name: name of the new interface
- * @props: interface properties
- * @out_link: on success, the link object
- *
- * Create a software SIT device.
- */
-int
-nm_platform_link_sit_add (NMPlatform *self,
-                          const char *name,
-                          const NMPlatformLnkSit *props,
-                          const NMPlatformLink **out_link)
-{
-	int r;
-
-	_CHECK_SELF (self, klass, -NME_BUG);
-
-	g_return_val_if_fail (props, -NME_BUG);
-	g_return_val_if_fail (name, -NME_BUG);
-
-	r = _link_add_check_existing (self, name, NM_LINK_TYPE_SIT, out_link);
-	if (r < 0)
-		return r;
-
-	_LOG2D ("adding link %s", nm_platform_lnk_sit_to_string (props, NULL, 0));
-
-	if (!klass->link_sit_add (self, name, props, out_link))
-		return -NME_UNSPEC;
-	return 0;
-}
-
 gboolean
 nm_platform_link_veth_get_properties (NMPlatform *self, int ifindex, int *out_peer_ifindex)
 {
@@ -3675,6 +3317,7 @@ nm_platform_ip4_address_add (NMPlatform *self,
                              in_addr_t address,
                              guint8 plen,
                              in_addr_t peer_address,
+                             in_addr_t broadcast_address,
                              guint32 lifetime,
                              guint32 preferred,
                              guint32 flags,
@@ -3689,22 +3332,26 @@ nm_platform_ip4_address_add (NMPlatform *self,
 	g_return_val_if_fail (!label || strlen (label) < sizeof (((NMPlatformIP4Address *) NULL)->label), FALSE);
 
 	if (_LOGD_ENABLED ()) {
-		NMPlatformIP4Address addr = { 0 };
-
-		addr.ifindex = ifindex;
-		addr.address = address;
-		addr.peer_address = peer_address;
-		addr.plen = plen;
-		addr.timestamp = 0; /* set it at zero, which to_string will treat as *now* */
-		addr.lifetime = lifetime;
-		addr.preferred = preferred;
-		addr.n_ifa_flags = flags;
+		NMPlatformIP4Address addr;
+
+		addr = (NMPlatformIP4Address) {
+			.ifindex                   = ifindex,
+			.address                   = address,
+			.peer_address              = peer_address,
+			.plen                      = plen,
+			.timestamp                 = 0, /* set it at zero, which to_string will treat as *now* */
+			.lifetime                  = lifetime,
+			.preferred                 = preferred,
+			.n_ifa_flags               = flags,
+			.broadcast_address         = broadcast_address,
+			.use_ip4_broadcast_address = TRUE,
+		};
 		if (label)
 			g_strlcpy (addr.label, label, sizeof (addr.label));
 
 		_LOG3D ("address: adding or updating IPv4 address: %s", nm_platform_ip4_address_to_string (&addr, NULL, 0));
 	}
-	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, lifetime, preferred, flags, label);
+	return klass->ip4_address_add (self, ifindex, address, plen, peer_address, broadcast_address, lifetime, preferred, flags, label);
 }
 
 gboolean
@@ -3755,12 +3402,12 @@ nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address
 	g_return_val_if_fail (plen <= 32, FALSE);
 
 	_LOG3D ("address: deleting IPv4 address %s/%d, %s%s",
-	        nm_utils_inet4_ntop (address, b1),
+	        _nm_utils_inet4_ntop (address, b1),
 	        plen,
 	        peer_address != address
 	            ? nm_sprintf_buf (str_peer,
 	                              "peer %s, ",
-	                              nm_utils_inet4_ntop (peer_address, b2))
+	                              _nm_utils_inet4_ntop (peer_address, b2))
 	            : "",
 	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip4_address_delete (self, ifindex, address, plen, peer_address);
@@ -3778,7 +3425,7 @@ nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr a
 	g_return_val_if_fail (plen <= 128, FALSE);
 
 	_LOG3D ("address: deleting IPv6 address %s/%d, %s",
-	        nm_utils_inet6_ntop (&address, sbuf), plen,
+	        _nm_utils_inet6_ntop (&address, sbuf), plen,
 	        _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
 	return klass->ip6_address_delete (self, ifindex, address, plen);
 }
@@ -4048,7 +3695,7 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 {
 	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
 	const NMPlatformIP4Address *known_address;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	GHashTable *plat_subnets = NULL;
 	GHashTable *known_subnets = NULL;
 	gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
@@ -4163,8 +3810,14 @@ nm_platform_ip4_address_sync (NMPlatform *self,
 		if (!lifetime)
 			goto delete_and_next2;
 
-		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
-		                                  known_address->peer_address, lifetime, preferred,
+		if (!nm_platform_ip4_address_add (self,
+		                                  ifindex,
+		                                  known_address->address,
+		                                  known_address->plen,
+		                                  known_address->peer_address,
+		                                  nm_platform_ip4_broadcast_address_from_addr (known_address),
+		                                  lifetime,
+		                                  preferred,
 		                                  ifa_flags,
 		                                  known_address->label))
 			goto delete_and_next2;
@@ -4178,23 +3831,39 @@ delete_and_next2:
 	return TRUE;
 }
 
-static guint
-ip6_address_scope_priority (const struct in6_addr *addr)
+typedef enum {
+	IP6_ADDR_SCOPE_LOOPBACK,
+	IP6_ADDR_SCOPE_LINKLOCAL,
+	IP6_ADDR_SCOPE_SITELOCAL,
+	IP6_ADDR_SCOPE_OTHER,
+} IP6AddrScope;
+
+static IP6AddrScope
+ip6_address_scope (const NMPlatformIP6Address *a)
 {
-	if (IN6_IS_ADDR_LINKLOCAL (addr))
-		return 1;
-	if (IN6_IS_ADDR_SITELOCAL (addr))
-		return 2;
-	return 3;
+	if (IN6_IS_ADDR_LOOPBACK (&a->address))
+		return IP6_ADDR_SCOPE_LOOPBACK;
+	if (IN6_IS_ADDR_LINKLOCAL (&a->address))
+		return IP6_ADDR_SCOPE_LINKLOCAL;
+	if (IN6_IS_ADDR_SITELOCAL (&a->address))
+		return IP6_ADDR_SCOPE_SITELOCAL;
+	return IP6_ADDR_SCOPE_OTHER;
 }
 
 static int
-ip6_address_scope_cmp (gconstpointer a, gconstpointer b)
+ip6_address_scope_cmp (gconstpointer p_a, gconstpointer p_b, gpointer increasing)
 {
-	const NMPlatformIP6Address *x = NMP_OBJECT_CAST_IP6_ADDRESS (*(const void **) a);
-	const NMPlatformIP6Address *y = NMP_OBJECT_CAST_IP6_ADDRESS (*(const void **) b);
+	const NMPlatformIP6Address *a;
+	const NMPlatformIP6Address *b;
 
-	return ip6_address_scope_priority (&x->address) - ip6_address_scope_priority (&y->address);
+	if (!increasing)
+		NM_SWAP (p_a, p_b);
+
+	a = NMP_OBJECT_CAST_IP6_ADDRESS (*(const NMPObject *const*) p_a);
+	b = NMP_OBJECT_CAST_IP6_ADDRESS (*(const NMPObject *const*) p_b);
+
+	NM_CMP_DIRECT (ip6_address_scope (a), ip6_address_scope (b));
+	return 0;
 }
 
 /**
@@ -4224,7 +3893,7 @@ nm_platform_ip6_address_sync (NMPlatform *self,
                               gboolean full_sync)
 {
 	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 	guint i_plat, i_know;
 	gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
 	NMPLookup lookup;
@@ -4235,7 +3904,7 @@ nm_platform_ip6_address_sync (NMPlatform *self,
 	 * apply the same sorting to known addresses, so that we don't try to
 	 * unnecessary change the order of addresses with different scopes. */
 	if (known_addresses)
-		g_ptr_array_sort (known_addresses, ip6_address_scope_cmp);
+		g_ptr_array_sort_with_data (known_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (TRUE));
 
 	if (!_addr_array_clean_expired (AF_INET6, ifindex, known_addresses, now, &known_addresses_idx))
 		known_addresses = NULL;
@@ -4250,6 +3919,10 @@ nm_platform_ip6_address_sync (NMPlatform *self,
 
 	if (plat_addresses) {
 		guint known_addresses_len;
+		IP6AddrScope cur_scope;
+		gboolean delete_remaining_addrs;
+
+		g_ptr_array_sort_with_data (plat_addresses, ip6_address_scope_cmp, GINT_TO_POINTER (FALSE));
 
 		known_addresses_len = known_addresses ? known_addresses->len : 0;
 
@@ -4301,38 +3974,54 @@ clear_and_next:
 		 * selection will choose addresses in the order as they are reported by kernel.
 		 * Note that the order in @plat_addresses of the remaining matches is highest
 		 * priority first.
-		 * We need to compare this to the order in @known_addresses (which has lowest
-		 * priority first).
+		 * We need to compare this to the order of addresses with same scope in
+		 * @known_addresses (which has lowest priority first).
 		 *
 		 * If we find a first discrepancy, we need to delete all remaining addresses
-		 * from that point on, because below we must re-add all the addresses in the
-		 * right order to get their priority right. */
+		 * with same scope from that point on, because below we must re-add all the
+		 * addresses in the right order to get their priority right. */
+		cur_scope = IP6_ADDR_SCOPE_LOOPBACK;
+		delete_remaining_addrs = FALSE;
 		i_plat = plat_addresses->len;
 		i_know = 0;
 		while (i_plat > 0) {
 			const NMPlatformIP6Address *plat_addr = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[--i_plat]);
+			IP6AddrScope plat_scope;
 
 			if (!plat_addr)
 				continue;
 
-			for (; i_know < known_addresses_len; i_know++) {
-				const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
+			plat_scope = ip6_address_scope (plat_addr);
+			if (cur_scope != plat_scope) {
+				nm_assert (cur_scope < plat_scope);
+				delete_remaining_addrs = FALSE;
+				cur_scope = plat_scope;
+			}
 
-				if (!know_addr)
-					continue;
+			if (!delete_remaining_addrs) {
+				delete_remaining_addrs = TRUE;
+				for (; i_know < known_addresses_len; i_know++) {
+					const NMPlatformIP6Address *know_addr = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i_know]);
+					IP6AddrScope know_scope;
 
-				if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) {
-					/* we have a match. Mark address as handled. */
-					i_know++;
-					goto next_plat;
-				}
+					if (!know_addr)
+						continue;
 
-				/* all remainging addresses need to be removed as well, so that we can
-				 * re-add them in the correct order. Signal that, by setting @i_know
-				 * so that the next @i_plat iteration, we won't enter the loop and
-				 * delete the address right away */
-				i_know = known_addresses_len;
-				break;
+					know_scope = ip6_address_scope (know_addr);
+					if (know_scope < plat_scope)
+						continue;
+
+					if (IN6_ARE_ADDR_EQUAL (&plat_addr->address, &know_addr->address)) {
+						/* we have a match. Mark address as handled. */
+						i_know++;
+						delete_remaining_addrs = FALSE;
+						goto next_plat;
+					}
+
+					/* plat_address has no match. Now delete_remaining_addrs is TRUE and we will
+					 * delete all the remaining addresses with cur_scope. */
+					break;
+				}
 			}
 
 			nm_platform_ip6_address_delete (self, ifindex, plat_addr->address, plat_addr->plen);
@@ -4965,15 +4654,15 @@ nm_platform_ip_route_get (NMPlatform *self,
 #define IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S ((int) (((IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS + 999) * 3) / 1000))
 
 static gint64
-_ip4_dev_route_blacklist_timeout_ms_get (gint64 timeout_ms)
+_ip4_dev_route_blacklist_timeout_ms_get (gint64 timeout_msec)
 {
-	return timeout_ms >> 1;
+	return timeout_msec >> 1;
 }
 
 static gint64
-_ip4_dev_route_blacklist_timeout_ms_marked (gint64 timeout_ms)
+_ip4_dev_route_blacklist_timeout_ms_marked (gint64 timeout_msec)
 {
-	return !!(timeout_ms & ((gint64) 1));
+	return !!(timeout_msec & ((gint64) 1));
 }
 
 static gboolean
@@ -4992,7 +4681,7 @@ again:
 	if (!priv->ip4_dev_route_blacklist_hash)
 		goto out;
 
-	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	now_ms = nm_utils_get_monotonic_timestamp_msec ();
 
 	g_hash_table_iter_init (&iter, priv->ip4_dev_route_blacklist_hash);
 	while (g_hash_table_iter_next (&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) {
@@ -5055,7 +4744,7 @@ _ip4_dev_route_blacklist_notify_route (NMPlatform *self,
 	                                   (gpointer *) &p_timeout_ms))
 		return;
 
-	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	now_ms = nm_utils_get_monotonic_timestamp_msec ();
 	if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get (*p_timeout_ms)) {
 		/* already expired. Wait for gc. */
 		return;
@@ -5086,7 +4775,7 @@ _ip4_dev_route_blacklist_gc_timeout_handle (gpointer user_data)
 
 	nm_assert (priv->ip4_dev_route_blacklist_gc_timeout_id);
 
-	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	now_ms = nm_utils_get_monotonic_timestamp_msec ();
 
 	g_hash_table_iter_init (&iter, priv->ip4_dev_route_blacklist_hash);
 	while (g_hash_table_iter_next (&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) {
@@ -5108,7 +4797,7 @@ _ip4_dev_route_blacklist_schedule (NMPlatform *self)
 
 	if (   !priv->ip4_dev_route_blacklist_hash
 	    || g_hash_table_size (priv->ip4_dev_route_blacklist_hash) == 0) {
-		g_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
+		nm_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
 		nm_clear_g_source (&priv->ip4_dev_route_blacklist_gc_timeout_id);
 	} else {
 		if (!priv->ip4_dev_route_blacklist_gc_timeout_id) {
@@ -5155,8 +4844,8 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 	GHashTableIter iter;
 	const NMPObject *p_obj;
 	guint i;
-	gint64 timeout_ms;
-	gint64 timeout_ms_val;
+	gint64 timeout_msec;
+	gint64 timeout_msec_val;
 	gint64 *p_timeout_ms;
 	gboolean needs_check = FALSE;
 
@@ -5191,8 +4880,8 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 			                                                            nm_g_slice_free_fcn_gint64);
 		}
 
-		timeout_ms = nm_utils_get_monotonic_timestamp_ms () + IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS;
-		timeout_ms_val = (timeout_ms << 1) | ((gint64) 1);
+		timeout_msec = nm_utils_get_monotonic_timestamp_msec () + IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS;
+		timeout_msec_val = (timeout_msec << 1) | ((gint64) 1);
 		for (i = 0; i < ip4_dev_route_blacklist->len; i++) {
 			const NMPObject *o;
 
@@ -5206,7 +4895,7 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 					/* un-expire and reuse the entry. */
 					_LOGT ("ip4-dev-route: register %s (update)",
 					       nmp_object_to_string (p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-					*p_timeout_ms = timeout_ms_val;
+					*p_timeout_ms = timeout_msec_val;
 					continue;
 				}
 			}
@@ -5214,7 +4903,7 @@ nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
 			_LOGT ("ip4-dev-route: register %s",
 			       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
 			p_timeout_ms = g_slice_new (gint64);
-			*p_timeout_ms = timeout_ms_val;
+			*p_timeout_ms = timeout_msec_val;
 			g_hash_table_replace (priv->ip4_dev_route_blacklist_hash,
 			                      (gpointer) nmp_object_ref (o),
 			                      p_timeout_ms);
@@ -5605,16 +5294,16 @@ nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len
 	            "%s" /* okey */
 	            "",
 	            lnk->is_tap ? "tap" : "",
-	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
-	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", _nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", _nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
 	            lnk->path_mtu_discovery ? "" : " nopmtudisc",
 	            lnk->input_flags ? nm_sprintf_buf (str_input_flags, " iflags 0x%x", lnk->input_flags) : "",
 	            lnk->output_flags ? nm_sprintf_buf (str_output_flags, " oflags 0x%x", lnk->output_flags) : "",
-	            NM_FLAGS_HAS (lnk->input_flags, GRE_KEY) || lnk->input_key ? nm_sprintf_buf (str_input_key, " ikey %s", nm_utils_inet4_ntop (lnk->input_key, str_input_key1)) : "",
-	            NM_FLAGS_HAS (lnk->output_flags, GRE_KEY) || lnk->output_key ? nm_sprintf_buf (str_output_key, " okey %s", nm_utils_inet4_ntop (lnk->output_key, str_output_key1)) : "");
+	            NM_FLAGS_HAS (lnk->input_flags, GRE_KEY) || lnk->input_key ? nm_sprintf_buf (str_input_key, " ikey %s", _nm_utils_inet4_ntop (lnk->input_key, str_input_key1)) : "",
+	            NM_FLAGS_HAS (lnk->output_flags, GRE_KEY) || lnk->output_key ? nm_sprintf_buf (str_output_key, " okey %s", _nm_utils_inet4_ntop (lnk->output_key, str_output_key1)) : "");
 	return buf;
 }
 
@@ -5673,8 +5362,8 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            " flags 0x%x"
 	            "",
 	            str_type,
-	            nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
-	            nm_sprintf_buf (str_local, " local %s", nm_utils_inet6_ntop (&lnk->local, str_local1)),
+	            nm_sprintf_buf (str_remote, " remote %s", _nm_utils_inet6_ntop (&lnk->remote, str_remote1)),
+	            nm_sprintf_buf (str_local, " local %s", _nm_utils_inet6_ntop (&lnk->local, str_local1)),
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tclass == 1 ? " tclass inherit" : nm_sprintf_buf (str_tclass, " tclass 0x%x", lnk->tclass),
@@ -5708,8 +5397,8 @@ nm_platform_lnk_ipip_to_string (const NMPlatformLnkIpIp *lnk, char *buf, gsize l
 	            "%s" /* tos */
 	            "%s" /* path_mtu_discovery */
 	            "",
-	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
-	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", _nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", _nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
@@ -5791,8 +5480,8 @@ nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len
 	            "%s" /* flags */
 	            "%s" /* proto */
 	            "",
-	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
-	            lnk->local ? nm_sprintf_buf (str_local, " local %s", nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
+	            lnk->remote ? nm_sprintf_buf (str_remote, " remote %s", _nm_utils_inet4_ntop (lnk->remote, str_remote1)) : "",
+	            lnk->local ? nm_sprintf_buf (str_local, " local %s", _nm_utils_inet4_ntop (lnk->local, str_local1)) : "",
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
 	            lnk->ttl ? nm_sprintf_buf (str_ttl, " ttl %u", lnk->ttl) : " ttl inherit",
 	            lnk->tos ? (lnk->tos == 1 ? " tos inherit" : nm_sprintf_buf (str_tos, " tos 0x%x", lnk->tos)) : "",
@@ -5856,6 +5545,20 @@ nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize l
 }
 
 const char *
+nm_platform_lnk_vrf_to_string (const NMPlatformLnkVrf *lnk, char *buf, gsize len)
+{
+	char *b;
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	b = buf;
+
+	nm_utils_strbuf_append (&b, &len, "table %u", lnk->table);
+	return buf;
+}
+
+const char *
 nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len)
 {
 	char str_group[100];
@@ -5879,7 +5582,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_group, sizeof (str_group),
 		            " %s %s",
 		            IN_MULTICAST (ntohl (lnk->group)) ? "group" : "remote",
-		            nm_utils_inet4_ntop (lnk->group, sbuf));
+		            _nm_utils_inet4_ntop (lnk->group, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->group6))
 		str_group6[0] = '\0';
@@ -5888,7 +5591,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		            " %s%s %s",
 		            IN6_IS_ADDR_MULTICAST (&lnk->group6) ? "group" : "remote",
 		            str_group[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->group6, sbuf));
+		            _nm_utils_inet6_ntop (&lnk->group6, sbuf));
 	}
 
 	if (lnk->local == 0)
@@ -5896,7 +5599,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	else {
 		g_snprintf (str_local, sizeof (str_local),
 		            " local %s",
-		            nm_utils_inet4_ntop (lnk->local, sbuf));
+		            _nm_utils_inet4_ntop (lnk->local, sbuf));
 	}
 	if (IN6_IS_ADDR_UNSPECIFIED (&lnk->local6))
 		str_local6[0] = '\0';
@@ -5904,7 +5607,7 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 		g_snprintf (str_local6, sizeof (str_local6),
 		            " local%s %s",
 		            str_local[0] ? "6" : "", /* usually, a vxlan has either v4 or v6 only. */
-		            nm_utils_inet6_ntop (&lnk->local6, sbuf));
+		            _nm_utils_inet6_ntop (&lnk->local6, sbuf));
 	}
 
 	g_snprintf (buf, len,
@@ -6054,7 +5757,9 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *bu
 	char str_lft[30], str_pref[30], str_time[50], s_source[50];
 	char *str_peer = NULL;
 	const char *str_lft_p, *str_pref_p, *str_time_p;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
+	in_addr_t broadcast_address;
+	char str_broadcast[INET_ADDRSTRLEN];
 
 	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
 		return buf;
@@ -6083,9 +5788,27 @@ nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *bu
 	                                      now, str_pref, sizeof (str_pref)) );
 	str_time_p = _lifetime_summary_to_string (now, address->timestamp, address->preferred, address->lifetime, str_time, sizeof (str_time));
 
+	broadcast_address = nm_platform_ip4_broadcast_address_from_addr (address);
+
 	g_snprintf (buf, len,
-	            "%s/%d lft %s pref %s%s%s%s%s%s src %s%s",
-	            s_address, address->plen, str_lft_p, str_pref_p, str_time_p,
+	            "%s/%d"
+	            "%s%s" /* broadcast */
+	            " lft %s"
+	            " pref %s"
+	            "%s" /* time */
+	            "%s" /* peer  */
+	            "%s" /* dev */
+	            "%s" /* flags */
+	            "%s" /* label */
+	            " src %s"
+	            "%s" /* external */
+	            "",
+	            s_address, address->plen,
+	            broadcast_address ? " brd " : "",
+	            broadcast_address ? nm_utils_inet4_ntop (broadcast_address, str_broadcast) : "",
+	            str_lft_p,
+	            str_pref_p,
+	            str_time_p,
 	            str_peer ?: "",
 	            str_dev,
 	            _to_string_ifa_flags (address->n_ifa_flags, s_flags, sizeof (s_flags)),
@@ -6167,7 +5890,7 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
 	char *str_peer = NULL;
 	const char *str_lft_p, *str_pref_p, *str_time_p;
-	gint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_sec ();
 
 	if (!nm_utils_to_string_buffer_init_null (address, &buf, &len))
 		return buf;
@@ -6203,7 +5926,8 @@ nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *bu
 	return buf;
 }
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_rtm_flags_to_string, unsigned,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_rtm_flags_to_string, unsigned,
 	NM_UTILS_FLAGS2STR (RTNH_F_DEAD,                   "dead"),
 	NM_UTILS_FLAGS2STR (RTNH_F_PERVASIVE,              "pervasive"),
 	NM_UTILS_FLAGS2STR (RTNH_F_ONLINK,                 "onlink"),
@@ -6380,7 +6104,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            route->mss,
 	            nmp_utils_ip_config_source_to_string (route->rt_source, s_source, sizeof (s_source)),
 	            route->src_plen || !IN6_IS_ADDR_UNSPECIFIED (&route->src)
-	              ? nm_sprintf_buf (s_src_all, " src %s/%u", nm_utils_inet6_ntop (&route->src, s_src), (unsigned) route->src_plen)
+	              ? nm_sprintf_buf (s_src_all, " src %s/%u", _nm_utils_inet6_ntop (&route->src, s_src), (unsigned) route->src_plen)
 	              : "",
 	            _rtm_flags_to_string_full (str_rtm_flags, sizeof (str_rtm_flags), route->r_rtm_flags),
 	            s_pref_src[0] ? " pref-src " : "",
@@ -7222,6 +6946,21 @@ nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b
 }
 
 void
+nm_platform_lnk_vrf_hash_update (const NMPlatformLnkVrf *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->table);
+}
+
+int
+nm_platform_lnk_vrf_cmp (const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b)
+{
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, table);
+	return 0;
+}
+
+void
 nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h)
 {
 	nm_hash_update_vals (h,
@@ -7298,6 +7037,7 @@ nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashStat
 	nm_hash_update_vals (h,
 	                     obj->ifindex,
 	                     obj->addr_source,
+	                     nm_platform_ip4_broadcast_address_from_addr (obj),
 	                     obj->timestamp,
 	                     obj->lifetime,
 	                     obj->preferred,
@@ -7317,6 +7057,7 @@ nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4A
 	NM_CMP_FIELD (a, b, address);
 	NM_CMP_FIELD (a, b, plen);
 	NM_CMP_FIELD (a, b, peer_address);
+	NM_CMP_DIRECT (nm_platform_ip4_broadcast_address_from_addr (a), nm_platform_ip4_broadcast_address_from_addr (b));
 	NM_CMP_FIELD (a, b, addr_source);
 	NM_CMP_FIELD (a, b, timestamp);
 	NM_CMP_FIELD (a, b, lifetime);
@@ -8238,7 +7979,7 @@ finalize (GObject *object)
 
 	nm_clear_g_source (&priv->ip4_dev_route_blacklist_check_id);
 	nm_clear_g_source (&priv->ip4_dev_route_blacklist_gc_timeout_id);
-	g_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
+	nm_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
 	g_clear_object (&self->_netns);
 	nm_dedup_multi_index_unref (priv->multi_idx);
 	nmp_cache_free (priv->cache);
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 4bd8e34d..7edaaf58 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -287,7 +287,7 @@ typedef enum {
 	 * 2 @lifetime==@preferred==NM_PLATFORM_LIFETIME_PERMANENT: @timestamp is irrelevant (but mostly
 	 *   set to 0). Such addresses are permanent.
 	 * 3 Non permanent addresses should (almost) always have @timestamp > 0. 0 is not a valid timestamp
-	 *   and never returned by nm_utils_get_monotonic_timestamp_s(). In this case @valid/@preferred
+	 *   and never returned by nm_utils_get_monotonic_timestamp_sec(). In this case @valid/@preferred
 	 *   is anchored at @timestamp.
 	 * 4 Non permanent addresses with @timestamp == 0 are implicitly anchored at *now*, thus the time
 	 *   moves as time goes by. This is usually not useful, except e.g. nm_platform_ip[46]_address_add().
@@ -308,6 +308,8 @@ typedef enum {
 	guint8 plen; \
 	\
 	bool external:1; \
+	\
+	bool use_ip4_broadcast_address:1; \
 	;
 
 /**
@@ -325,7 +327,7 @@ typedef struct {
 
 /**
  * NMPlatformIP4Address:
- * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_s()
+ * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec()
  **/
 struct _NMPlatformIP4Address {
 	__NMPlatformIPAddress_COMMON;
@@ -344,12 +346,18 @@ struct _NMPlatformIP4Address {
 	 * */
 	in_addr_t peer_address;  /* PTP peer address */
 
+	/* IFA_BROADCAST.
+	 *
+	 * This parameter is ignored unless use_ip4_broadcast_address is TRUE.
+	 * See nm_platform_ip4_broadcast_address_from_addr(). */
+	in_addr_t broadcast_address;
+
 	char label[NMP_IFNAMSIZ];
 };
 
 /**
  * NMPlatformIP6Address:
- * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_s()
+ * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec()
  **/
 struct _NMPlatformIP6Address {
 	__NMPlatformIPAddress_COMMON;
@@ -795,8 +803,6 @@ typedef struct {
 	bool tap:1;
 } NMPlatformLnkMacvlan;
 
-typedef NMPlatformLnkMacvlan NMPlatformLnkMacvtap;
-
 typedef struct {
 	in_addr_t local;
 	in_addr_t remote;
@@ -830,6 +836,10 @@ typedef struct {
 } NMPlatformLnkVlan;
 
 typedef struct {
+	guint32 table;
+} NMPlatformLnkVrf;
+
+typedef struct {
 	struct in6_addr group6;
 	struct in6_addr local6;
 	in_addr_t group;
@@ -965,11 +975,12 @@ typedef struct {
 	void (*process_events) (NMPlatform *self);
 
 	int (*link_add) (NMPlatform *self,
-	                 const char *name,
 	                 NMLinkType type,
-	                 const char *veth_peer,
+	                 const char *name,
+	                 int parent,
 	                 const void *address,
 	                 size_t address_len,
+	                 gconstpointer extra_data,
 	                 const NMPlatformLink **out_link);
 	gboolean (*link_delete) (NMPlatform *self, int ifindex);
 	gboolean (*link_refresh) (NMPlatform *self, int ifindex);
@@ -1028,7 +1039,6 @@ typedef struct {
 	                              guint peers_len,
 	                              NMPlatformWireGuardChangeFlags change_flags);
 
-	gboolean (*vlan_add) (NMPlatform *self, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
 	gboolean (*link_vlan_change) (NMPlatform *self,
 	                              int ifindex,
 	                              NMVlanFlags flags_mask,
@@ -1039,49 +1049,11 @@ typedef struct {
 	                              gboolean egress_reset_all,
 	                              const NMVlanQosMapping *egress_map,
 	                              gsize n_egress_map);
-	gboolean (*link_vxlan_add) (NMPlatform *self,
-	                            const char *name,
-	                            const NMPlatformLnkVxlan *props,
-	                            const NMPlatformLink **out_link);
-	gboolean (*link_gre_add) (NMPlatform *self,
-	                          const char *name,
-	                          const NMPlatformLnkGre *props,
-	                          const NMPlatformLink **out_link);
-	gboolean (*link_ip6tnl_add) (NMPlatform *self,
-	                             const char *name,
-	                             const NMPlatformLnkIp6Tnl *props,
-	                             const NMPlatformLink **out_link);
-	gboolean (*link_ip6gre_add) (NMPlatform *self,
-	                             const char *name,
-	                             const NMPlatformLnkIp6Tnl *props,
-	                             const NMPlatformLink **out_link);
-	gboolean (*link_ipip_add) (NMPlatform *self,
-	                           const char *name,
-	                           const NMPlatformLnkIpIp *props,
-	                           const NMPlatformLink **out_link);
-	gboolean (*link_macsec_add) (NMPlatform *self,
-	                             const char *name,
-	                             int parent,
-	                             const NMPlatformLnkMacsec *props,
-	                             const NMPlatformLink **out_link);
-	gboolean (*link_macvlan_add) (NMPlatform *self,
-	                              const char *name,
-	                              int parent,
-	                              const NMPlatformLnkMacvlan *props,
-	                              const NMPlatformLink **out_link);
-	gboolean (*link_sit_add) (NMPlatform *self,
-	                          const char *name,
-	                          const NMPlatformLnkSit *props,
-	                          const NMPlatformLink **out_link);
 	gboolean (*link_tun_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkTun *props,
 	                          const NMPlatformLink **out_link,
 	                          int *out_fd);
-	gboolean (*link_6lowpan_add) (NMPlatform *self,
-	                              const char *name,
-	                              int parent,
-	                              const NMPlatformLink **out_link);
 
 	gboolean (*infiniband_partition_add) (NMPlatform *self, int parent, int p_key, const NMPlatformLink **out_link);
 	gboolean (*infiniband_partition_delete) (NMPlatform *self, int parent, int p_key);
@@ -1116,6 +1088,7 @@ typedef struct {
 	                             in_addr_t address,
 	                             guint8 plen,
 	                             in_addr_t peer_address,
+	                             in_addr_t broadcast_address,
 	                             guint32 lifetime,
 	                             guint32 preferred_lft,
 	                             guint32 flags,
@@ -1187,6 +1160,31 @@ NMPlatform *nm_platform_get (void);
 
 /*****************************************************************************/
 
+static inline in_addr_t
+nm_platform_ip4_broadcast_address_create (in_addr_t address,
+                                          guint8 plen)
+{
+	return address | ~_nm_utils_ip4_prefix_to_netmask (plen);
+}
+
+static inline in_addr_t
+nm_platform_ip4_broadcast_address_from_addr (const NMPlatformIP4Address *addr)
+{
+	nm_assert (addr);
+
+	if (addr->use_ip4_broadcast_address)
+		return addr->broadcast_address;
+
+	/* the set broadcast-address gets ignored, and we determine a default brd base
+	 * on the peer IFA_ADDRESS. */
+	if (   addr->peer_address != 0u
+	    && addr->plen < 31 /* RFC3021 */)
+		return nm_platform_ip4_broadcast_address_create (addr->peer_address, addr->plen);
+	return 0u;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_platform_route_table_coerce:
  * @table: the route table, in its original value as received
@@ -1352,7 +1350,8 @@ int nm_platform_sysctl_ip_conf_get_rp_filter_ipv4 (NMPlatform *platform,
                                                    gboolean consider_all,
                                                    gboolean *out_due_to_all);
 
-const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */);
+const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex,
+                                        char out_ifname[static 16 /* IFNAMSIZ */]);
 int nm_platform_if_nametoindex (NMPlatform *self, const char *ifname);
 
 const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
@@ -1363,11 +1362,200 @@ const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const ch
 const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, NMLinkType link_type, gconstpointer address, size_t length);
 
 GPtrArray *nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name);
-int nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-int nm_platform_link_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, const NMPlatformLink **out_link);
-int nm_platform_link_bond_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-int nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
-int nm_platform_link_veth_add (NMPlatform *self, const char *name, const char *peer, const NMPlatformLink **out_link);
+
+int nm_platform_link_add (NMPlatform *self,
+                          NMLinkType type,
+                          const char *name,
+                          int parent,
+                          const void *address,
+                          size_t address_len,
+                          gconstpointer extra_data,
+                          const NMPlatformLink **out_link);
+
+static inline int
+nm_platform_link_veth_add (NMPlatform *self,
+                            const char *name,
+                            const char *peer,
+                            const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_VETH, name, 0, NULL, 0, peer, out_link);
+}
+
+static inline int
+nm_platform_link_dummy_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_DUMMY, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_bridge_add (NMPlatform *self,
+                             const char *name,
+                             const void *address,
+                             size_t address_len,
+                             const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_BRIDGE, name, 0, address, address_len, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_bond_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_BOND, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_team_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_TEAM, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_wireguard_add (NMPlatform *self,
+                                const char *name,
+                                const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_WIREGUARD, name, 0, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_gre_add (NMPlatform *self,
+                          const char *name,
+                          const void *address,
+                          size_t address_len,
+                          const NMPlatformLnkGre *props,
+                          const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+
+	return nm_platform_link_add (self, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, name, 0, address, address_len, props, out_link);
+}
+
+static inline int
+nm_platform_link_sit_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkSit *props,
+                          const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_SIT, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_vlan_add (NMPlatform *self,
+                           const char *name,
+                           int parent,
+                           int vlanid,
+                           guint32 vlanflags,
+                           const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (parent >= 0, -NME_BUG);
+	g_return_val_if_fail (vlanid >= 0, -NME_BUG);
+
+	return nm_platform_link_add (self,
+	                             NM_LINK_TYPE_VLAN,
+	                             name,
+	                             parent,
+	                             NULL,
+	                             0,
+	                             &((NMPlatformLnkVlan) {
+	                                .id    = vlanid,
+	                                .flags = vlanflags,
+	                             }),
+	                             out_link);
+}
+
+static inline int
+nm_platform_link_vrf_add (NMPlatform *self,
+                          const char *name,
+                          const NMPlatformLnkVrf *props,
+                          const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_VRF, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_vxlan_add (NMPlatform *self,
+                            const char *name,
+                            const NMPlatformLnkVxlan *props,
+                            const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_VXLAN, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_6lowpan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, NM_LINK_TYPE_6LOWPAN, name, parent, NULL, 0, NULL, out_link);
+}
+
+static inline int
+nm_platform_link_ip6tnl_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *props,
+                             const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (!props->is_gre, -NME_BUG);
+
+	return nm_platform_link_add (self, NM_LINK_TYPE_IP6TNL, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_ip6gre_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *props,
+                             const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (props->is_gre, -NME_BUG);
+
+	return nm_platform_link_add (self, props->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_ipip_add (NMPlatform *self,
+                           const char *name,
+                           const NMPlatformLnkIpIp *props,
+                           const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+
+	return nm_platform_link_add (self, NM_LINK_TYPE_IPIP, name, 0, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_macsec_add (NMPlatform *self,
+                             const char *name,
+                             int parent,
+                             const NMPlatformLnkMacsec *props,
+                             const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (parent > 0, -NME_BUG);
+
+	return nm_platform_link_add (self, NM_LINK_TYPE_MACSEC, name, parent, NULL, 0, props, out_link);
+}
+
+static inline int
+nm_platform_link_macvlan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLnkMacvlan *props,
+                              const NMPlatformLink **out_link)
+{
+	g_return_val_if_fail (props, -NME_BUG);
+	g_return_val_if_fail (parent > 0, -NME_BUG);
+
+	return nm_platform_link_add (self, props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN, name, parent, NULL, 0, props, out_link);
+}
 
 gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
 
@@ -1477,19 +1665,14 @@ const NMPlatformLnkInfiniband *nm_platform_link_get_lnk_infiniband (NMPlatform *
 const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacsec *nm_platform_link_get_lnk_macsec (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
-const NMPlatformLnkMacvtap *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkMacvlan *nm_platform_link_get_lnk_macvtap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkTun *nm_platform_link_get_lnk_tun (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVlan *nm_platform_link_get_lnk_vlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkVrf *nm_platform_link_get_lnk_vrf (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkVxlan *nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkWireGuard *nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 
-int nm_platform_link_vlan_add (NMPlatform *self,
-                               const char *name,
-                               int parent,
-                               int vlanid,
-                               guint32 vlanflags,
-                               const NMPlatformLink **out_link);
 gboolean nm_platform_link_vlan_set_ingress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_set_egress_map (NMPlatform *self, int ifindex, int from, int to);
 gboolean nm_platform_link_vlan_change (NMPlatform *self,
@@ -1503,11 +1686,6 @@ gboolean nm_platform_link_vlan_change (NMPlatform *self,
                                        const NMVlanQosMapping *egress_map,
                                        gsize n_egress_map);
 
-int nm_platform_link_vxlan_add (NMPlatform *self,
-                                const char *name,
-                                const NMPlatformLnkVxlan *props,
-                                const NMPlatformLink **out_link);
-
 int nm_platform_link_infiniband_add (NMPlatform *self,
                                      int parent,
                                      int p_key,
@@ -1550,32 +1728,6 @@ const struct in6_addr *nm_platform_ip6_address_get_peer (const NMPlatformIP6Addr
 
 const NMPlatformIP4Address *nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 
-int nm_platform_link_gre_add (NMPlatform *self,
-                              const char *name,
-                              const NMPlatformLnkGre *props,
-                              const NMPlatformLink **out_link);
-int nm_platform_link_ip6tnl_add (NMPlatform *self,
-                                 const char *name,
-                                 const NMPlatformLnkIp6Tnl *props,
-                                 const NMPlatformLink **out_link);
-int nm_platform_link_ip6gre_add (NMPlatform *self,
-                                 const char *name,
-                                 const NMPlatformLnkIp6Tnl *props,
-                                 const NMPlatformLink **out_link);
-int nm_platform_link_ipip_add (NMPlatform *self,
-                               const char *name,
-                               const NMPlatformLnkIpIp *props,
-                               const NMPlatformLink **out_link);
-int nm_platform_link_macsec_add (NMPlatform *self,
-                                 const char *name,
-                                 int parent,
-                                 const NMPlatformLnkMacsec *props,
-                                 const NMPlatformLink **out_link);
-int nm_platform_link_macvlan_add (NMPlatform *self,
-                                  const char *name,
-                                  int parent,
-                                  const NMPlatformLnkMacvlan *props,
-                                  const NMPlatformLink **out_link);
 int nm_platform_link_sit_add (NMPlatform *self,
                               const char *name,
                               const NMPlatformLnkSit *props,
@@ -1585,10 +1737,6 @@ int nm_platform_link_tun_add (NMPlatform *self,
                               const NMPlatformLnkTun *props,
                               const NMPlatformLink **out_link,
                               int *out_fd);
-int nm_platform_link_6lowpan_add (NMPlatform *self,
-                                  const char *name,
-                                  int parent,
-                                  const NMPlatformLink **out_link);
 gboolean nm_platform_link_6lowpan_get_properties (NMPlatform *self,
                                                   int ifindex,
                                                   int *out_parent);
@@ -1614,6 +1762,7 @@ gboolean nm_platform_ip4_address_add (NMPlatform *self,
                                       in_addr_t address,
                                       guint8 plen,
                                       in_addr_t peer_address,
+                                      in_addr_t broadcast_address,
                                       guint32 lifetime,
                                       guint32 preferred_lft,
                                       guint32 flags,
@@ -1693,6 +1842,7 @@ const char *nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk,
 const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_tun_to_string (const NMPlatformLnkTun *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vlan_to_string (const NMPlatformLnkVlan *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_vrf_to_string (const NMPlatformLnkVrf *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *buf, gsize len);
 const char *nm_platform_ip4_address_to_string (const NMPlatformIP4Address *address, char *buf, gsize len);
@@ -1725,6 +1875,7 @@ int nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatform
 int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b);
 int nm_platform_lnk_tun_cmp (const NMPlatformLnkTun *a, const NMPlatformLnkTun *b);
 int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b);
+int nm_platform_lnk_vrf_cmp (const NMPlatformLnkVrf *a, const NMPlatformLnkVrf *b);
 int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b);
 int nm_platform_lnk_wireguard_cmp (const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b);
 int nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b);
@@ -1771,6 +1922,7 @@ void nm_platform_lnk_macvlan_hash_update (const NMPlatformLnkMacvlan *obj, NMHas
 void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *h);
 void nm_platform_lnk_tun_hash_update (const NMPlatformLnkTun *obj, NMHashState *h);
 void nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h);
+void nm_platform_lnk_vrf_hash_update (const NMPlatformLnkVrf *obj, NMHashState *h);
 void nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
 void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, NMHashState *h);
 
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index 78f76cc7..bc91d7a8 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -31,7 +31,8 @@
 #define _CLONE_NS_ALL    ((int) (CLONE_NEWNS | CLONE_NEWNET))
 #define _CLONE_NS_ALL_V          CLONE_NEWNS , CLONE_NEWNET
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_clone_ns_to_str, int,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_clone_ns_to_str, int,
 	NM_UTILS_FLAGS2STR (CLONE_NEWNS,  "mnt"),
 	NM_UTILS_FLAGS2STR (CLONE_NEWNET, "net"),
 );
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 97aa4f28..541211cd 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -233,13 +233,13 @@ nm_sock_addr_union_to_string (const NMSockAddrUnion *sa,
 	case AF_INET:
 		g_snprintf (buf, len,
 		            "%s:%u",
-		            nm_utils_inet4_ntop (sa->in.sin_addr.s_addr, s_addr),
+		            _nm_utils_inet4_ntop (sa->in.sin_addr.s_addr, s_addr),
 		            (guint) htons (sa->in.sin_port));
 		break;
 	case AF_INET6:
 		g_snprintf (buf, len,
 		            "[%s%s]:%u",
-		            nm_utils_inet6_ntop (&sa->in6.sin6_addr, s_addr),
+		            _nm_utils_inet6_ntop (&sa->in6.sin6_addr, s_addr),
 		            (  sa->in6.sin6_scope_id != 0
 		             ? nm_sprintf_buf (s_scope_id, "%u", sa->in6.sin6_scope_id)
 		             : ""),
@@ -525,11 +525,11 @@ _vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
                                const NMVlanQosMapping *src_map)
 {
 	if (src_n_map == 0) {
-		g_clear_pointer (dst_map, g_free);
+		nm_clear_g_free (dst_map);
 		*dst_n_map = 0;
 	} else if (   src_n_map != *dst_n_map
 	           || _vlan_xgress_qos_mappings_cmp (src_n_map, *dst_map, src_map) != 0) {
-		g_clear_pointer (dst_map, g_free);
+		nm_clear_g_free (dst_map);
 		*dst_n_map = src_n_map;
 		if (src_n_map > 0)
 			*dst_map = nm_memdup (src_map, sizeof (*src_map) * src_n_map);
@@ -1082,10 +1082,10 @@ _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsiz
 	return buf; \
 }
 _vt_cmd_plobj_to_string_id (link,        NMPlatformLink,       "%d",            obj->ifindex);
-_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s", obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen,
+_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s", obj->ifindex, _nm_utils_inet4_ntop ( obj->address, buf1), obj->plen,
                                                                obj->peer_address != obj->address ? "," : "",
-                                                               obj->peer_address != obj->address ? nm_utils_inet4_ntop (nm_utils_ip4_address_clear_host_address (obj->peer_address, obj->plen), buf2) : "");
-_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1));
+                                                               obj->peer_address != obj->address ? _nm_utils_inet4_ntop (nm_utils_ip4_address_clear_host_address (obj->peer_address, obj->plen), buf2) : "");
+_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, _nm_utils_inet6_ntop (&obj->address, buf1));
 _vt_cmd_plobj_to_string_id (qdisc,       NMPlatformQdisc,      "%d: %d",        obj->ifindex, obj->parent);
 _vt_cmd_plobj_to_string_id (tfilter,     NMPlatformTfilter,    "%d: %d",        obj->ifindex, obj->parent);
 
@@ -3291,7 +3291,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVTAP,
 		.sizeof_data                        = sizeof (NMPObjectLnkMacvtap),
-		.sizeof_public                      = sizeof (NMPlatformLnkMacvtap),
+		.sizeof_public                      = sizeof (NMPlatformLnkMacvlan),
 		.obj_type_name                      = "macvtap",
 		.lnk_link_type                      = NM_LINK_TYPE_MACVTAP,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
@@ -3336,6 +3336,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vlan_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_VRF - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_VRF,
+		.sizeof_data                        = sizeof (NMPObjectLnkVrf),
+		.sizeof_public                      = sizeof (NMPlatformLnkVrf),
+		.obj_type_name                      = "vrf",
+		.lnk_link_type                      = NM_LINK_TYPE_VRF,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vrf_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vrf_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vrf_cmp,
+	},
+
 	[NMP_OBJECT_TYPE_LNK_VXLAN - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_VXLAN,
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 0c0796c3..44525ed0 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -287,6 +287,10 @@ typedef struct {
 } NMPObjectLnkVlan;
 
 typedef struct {
+	NMPlatformLnkVrf _public;
+} NMPObjectLnkVrf;
+
+typedef struct {
 	NMPlatformLnkVxlan _public;
 } NMPObjectLnkVxlan;
 
@@ -366,6 +370,9 @@ struct _NMPObject {
 		NMPlatformLnkVlan       lnk_vlan;
 		NMPObjectLnkVlan        _lnk_vlan;
 
+		NMPlatformLnkVrf        lnk_vrf;
+		NMPObjectLnkVrf         _lnk_vrf;
+
 		NMPlatformLnkVxlan      lnk_vxlan;
 		NMPObjectLnkVxlan       _lnk_vxlan;
 
@@ -493,6 +500,7 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX (NMPObjectType obj_type)
 	case NMP_OBJECT_TYPE_LNK_SIT:
 	case NMP_OBJECT_TYPE_LNK_TUN:
 	case NMP_OBJECT_TYPE_LNK_VLAN:
+	case NMP_OBJECT_TYPE_LNK_VRF:
 	case NMP_OBJECT_TYPE_LNK_VXLAN:
 	case NMP_OBJECT_TYPE_LNK_WIREGUARD:
 		return TRUE;
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index 6f7173b6..4a50bca9 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_fake_c_flags = test_c_flags + ['-DSETUP=nm_fake_platform_setup']
 test_linux_c_flags = test_c_flags + ['-DSETUP=nm_linux_platform_setup']
 
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 34175c01..d19ff1f0 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -61,8 +61,7 @@ test_cleanup_internal (void)
 			break;
 	});
 
-	/* Add routes and addresses */
-	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, lifetime, preferred, 0, NULL));
+	g_assert (nm_platform_ip4_address_add (NM_PLATFORM_GET, ifindex, addr4, plen4, addr4, nm_platform_ip4_broadcast_address_create (addr4, plen4), lifetime, preferred, 0, NULL));
 	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, addr6, plen6, in6addr_any, lifetime, preferred, flags));
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway4, 32, INADDR_ANY, 0, metric, mss);
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network4, plen4, gateway4, 0, metric, mss);
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index f831fe94..f393ae93 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -190,7 +190,7 @@ _accept_or_wait_signal (const char *file, int line, const char *func, SignalData
 	if (data->received_count == 0) {
 		data->loop = g_main_loop_new (NULL, FALSE);
 		g_main_loop_run (data->loop);
-		g_clear_pointer (&data->loop, g_main_loop_unref);
+		nm_clear_pointer (&data->loop, g_main_loop_unref);
 	}
 
 	_accept_signal (file, line, func, data);
@@ -205,7 +205,7 @@ _wait_signal (const char *file, int line, const char *func, SignalData *data)
 
 	data->loop = g_main_loop_new (NULL, FALSE);
 	g_main_loop_run (data->loop);
-	g_clear_pointer (&data->loop, g_main_loop_unref);
+	nm_clear_pointer (&data->loop, g_main_loop_unref);
 
 	_accept_signal (file, line, func, data);
 }
@@ -356,7 +356,7 @@ _nmtstp_assert_ip4_route_exists (const char *file,
 
 		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet4_ntop (network, sbuf),
+		         _nm_utils_inet4_ntop (network, sbuf),
 		         plen,
 		         metric,
 		         tos,
@@ -467,10 +467,10 @@ _nmtstp_assert_ip6_route_exists (const char *file,
 
 		g_error ("[%s:%u] %s(): The ip6 route %s/%d metric %u src %s/%d shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet6_ntop (network, s_network),
+		         _nm_utils_inet6_ntop (network, s_network),
 		         plen,
 		         metric,
-		         nm_utils_inet6_ntop (src, s_src),
+		         _nm_utils_inet6_ntop (src, s_src),
 		         src_plen,
 		         c_exists,
 		         c);
@@ -553,7 +553,7 @@ _wait_for_signal_timeout (gpointer user_data)
 }
 
 guint
-nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
+nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_msec)
 {
 	WaitForSignalData data = { 0 };
 	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
@@ -568,7 +568,7 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 	id_ip4_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 	id_ip6_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 
-	/* if timeout_ms is negative, it means the wait-time already expired.
+	/* if timeout_msec is negative, it means the wait-time already expired.
 	 * Maybe, we should do nothing and return right away, without even
 	 * processing events from platform. However, that inconsistency (of not
 	 * processing events from mainloop) is inconvenient.
@@ -578,7 +578,7 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 	 * a zero timeout: we check whether there are any events pending in platform,
 	 * and quite the mainloop immediately afterwards. But we always check. */
 
-	data.id = g_timeout_add (CLAMP (timeout_ms, 0, G_MAXUINT32),
+	data.id = g_timeout_add (CLAMP (timeout_msec, 0, G_MAXUINT32),
 	                         _wait_for_signal_timeout, &data);
 
 	g_main_loop_run (data.loop);
@@ -590,7 +590,7 @@ nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 	g_assert (nm_clear_g_signal_handler (platform, &id_ip4_route));
 	g_assert (nm_clear_g_signal_handler (platform, &id_ip6_route));
 
-	g_clear_pointer (&data.loop, g_main_loop_unref);
+	nm_clear_pointer (&data.loop, g_main_loop_unref);
 
 	/* return the number of signals, or 0 if timeout was reached .*/
 	return data.signal_counts;
@@ -603,7 +603,7 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 	guint signal_counts;
 
 	while (TRUE) {
-		now = nm_utils_get_monotonic_timestamp_ms ();
+		now = nm_utils_get_monotonic_timestamp_msec ();
 
 		if (until_ms < now)
 			return 0;
@@ -615,11 +615,11 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 }
 
 const NMPlatformLink *
-nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms)
+nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_msec)
 {
 	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type,
-	                                   timeout_ms
-	                                     ? nm_utils_get_monotonic_timestamp_ms () + timeout_ms
+	                                   timeout_msec
+	                                     ? nm_utils_get_monotonic_timestamp_msec () + timeout_msec
 	                                     : 0);
 }
 
@@ -633,7 +633,7 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 	_init_platform (&platform, FALSE);
 
 	while (TRUE) {
-		now = nm_utils_get_monotonic_timestamp_ms ();
+		now = nm_utils_get_monotonic_timestamp_msec ();
 
 		plink = nm_platform_link_get_by_ifname (platform, ifname);
 		if (   plink
@@ -704,7 +704,7 @@ nmtstp_ip_address_check_lifetime (const NMPlatformIPAddress *addr,
 	g_assert (addr);
 
 	if (now == -1)
-		now = nm_utils_get_monotonic_timestamp_s ();
+		now = nm_utils_get_monotonic_timestamp_sec ();
 	g_assert (now > 0);
 
 	g_assert (expected_preferred <= expected_lifetime);
@@ -750,7 +750,7 @@ nmtstp_ip_address_assert_lifetime (const NMPlatformIPAddress *addr,
 	g_assert (addr);
 
 	if (now == -1)
-		now = nm_utils_get_monotonic_timestamp_s ();
+		now = nm_utils_get_monotonic_timestamp_sec ();
 	g_assert (now > 0);
 
 	g_assert (expected_preferred <= expected_lifetime);
@@ -831,12 +831,12 @@ _ip_address_add (NMPlatform *platform,
 			if (   peer_address->addr4 != address->addr4
 			    || nmtst_get_rand_uint32 () % 2) {
 				/* If the peer is the same as the local address, we can omit it. The result should be identical */
-				nm_sprintf_buf (s_peer, " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
+				nm_sprintf_buf (s_peer, " peer %s", _nm_utils_inet4_ntop (peer_address->addr4, b2));
 			} else
 				s_peer[0] = '\0';
 
 			nmtstp_run_command_check ("ip address change %s%s/%d dev %s%s%s%s",
-			                          nm_utils_inet4_ntop (address->addr4, b1),
+			                          _nm_utils_inet4_ntop (address->addr4, b1),
 			                          s_peer,
 			                          plen,
 			                          ifname,
@@ -849,9 +849,9 @@ _ip_address_add (NMPlatform *platform,
 			/* flags not implemented (yet) */
 			g_assert (flags == 0);
 			nmtstp_run_command_check ("ip address change %s%s%s/%d dev %s%s%s%s",
-			                          nm_utils_inet6_ntop (&address->addr6, b1),
+			                          _nm_utils_inet6_ntop (&address->addr6, b1),
 			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? " peer " : "",
-			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? nm_utils_inet6_ntop (&peer_address->addr6, b2) : "",
+			                          !IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) ? _nm_utils_inet6_ntop (&peer_address->addr6, b2) : "",
 			                          plen,
 			                          ifname,
 			                          s_valid ?: "",
@@ -867,6 +867,7 @@ _ip_address_add (NMPlatform *platform,
 			                                       address->addr4,
 			                                       plen,
 			                                       peer_address->addr4,
+			                                       0u,
 			                                       lifetime,
 			                                       preferred,
 			                                       flags,
@@ -886,7 +887,7 @@ _ip_address_add (NMPlatform *platform,
 	}
 
 	/* Let's wait until we see the address. */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 500;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 500;
 	do {
 
 		if (external_command)
@@ -1058,15 +1059,15 @@ _ip_address_del (NMPlatform *platform,
 
 		if (is_v4) {
 			success = nmtstp_run_command ("ip address delete %s%s%s/%d dev %s",
-			                              nm_utils_inet4_ntop (address->addr4, b1),
+			                              _nm_utils_inet4_ntop (address->addr4, b1),
 			                              peer_address->addr4 != address->addr4 ? " peer " : "",
-			                              peer_address->addr4 != address->addr4 ? nm_utils_inet4_ntop (peer_address->addr4, b2) : "",
+			                              peer_address->addr4 != address->addr4 ? _nm_utils_inet4_ntop (peer_address->addr4, b2) : "",
 			                              plen,
 			                              ifname);
 		} else {
 			g_assert (!peer_address);
 			success = nmtstp_run_command ("ip address delete %s/%d dev %s",
-			                              nm_utils_inet6_ntop (&address->addr6, b1),
+			                              _nm_utils_inet6_ntop (&address->addr6, b1),
 			                              plen,
 			                              ifname);
 		}
@@ -1091,7 +1092,7 @@ _ip_address_del (NMPlatform *platform,
 	}
 
 	/* Let's wait until we get the result */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 250;
 	do {
 		if (external_command)
 			nm_platform_process_events (platform);
@@ -1257,15 +1258,15 @@ nmtstp_link_gre_add (NMPlatform *platform,
 		                                name,
 		                                type,
 		                                dev ?: "",
-		                                nm_utils_inet4_ntop (lnk->local, b1),
-		                                nm_utils_inet4_ntop (lnk->remote, b2),
+		                                _nm_utils_inet4_ntop (lnk->local, b1),
+		                                _nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
 			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
-		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_gre_add (platform, name, lnk, &pllink));
+		success = NMTST_NM_ERR_SUCCESS (nm_platform_link_gre_add (platform, name, NULL, 0, lnk, &pllink));
 
 	_assert_pllink (platform, success, pllink, name, link_type);
 
@@ -1319,8 +1320,8 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 		                                name,
 		                                mode,
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, b1),
-		                                nm_utils_inet6_ntop (&lnk->remote, b2),
+		                                _nm_utils_inet6_ntop (&lnk->local, b1),
+		                                _nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                encap_ignore ? "none" : nm_sprintf_buf (encap, "%u", lnk->encap_limit),
@@ -1367,8 +1368,8 @@ nmtstp_link_ip6gre_add (NMPlatform *platform,
 		                                name,
 		                                lnk->is_tap ? "ip6gretap" : "ip6gre",
 		                                dev,
-		                                nm_utils_inet6_ntop (&lnk->local, b1),
-		                                nm_utils_inet6_ntop (&lnk->remote, b2),
+		                                _nm_utils_inet6_ntop (&lnk->local, b1),
+		                                _nm_utils_inet6_ntop (&lnk->remote, b2),
 		                                lnk->ttl,
 		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
 		                                lnk->flow_label);
@@ -1412,8 +1413,8 @@ nmtstp_link_ipip_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode ipip %s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, b1),
-		                                nm_utils_inet4_ntop (lnk->remote, b2),
+		                                _nm_utils_inet4_ntop (lnk->local, b1),
+		                                _nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
@@ -1506,8 +1507,8 @@ nmtstp_link_sit_add (NMPlatform *platform,
 		success = !nmtstp_run_command ("ip tunnel add %s mode sit%s local %s remote %s ttl %u tos %02x %s",
 		                                name,
 		                                dev,
-		                                nm_utils_inet4_ntop (lnk->local, b1),
-		                                nm_utils_inet4_ntop (lnk->remote, b2),
+		                                _nm_utils_inet4_ntop (lnk->local, b1),
+		                                _nm_utils_inet4_ntop (lnk->remote, b2),
 		                                lnk->ttl,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
@@ -1586,6 +1587,45 @@ nmtstp_link_tun_add (NMPlatform *platform,
 }
 
 const NMPlatformLink *
+nmtstp_link_vrf_add (NMPlatform *platform,
+                     gboolean external_command,
+                     const char *name,
+                     const NMPlatformLnkVrf *lnk,
+                     gboolean *out_not_supported)
+{
+	const NMPlatformLink *pllink = NULL;
+	int r = 0;
+
+	g_assert (nm_utils_ifname_valid_kernel (name, NULL));
+
+	NM_SET_OUT (out_not_supported, FALSE);
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	_init_platform (&platform, external_command);
+
+	if (external_command) {
+		r = nmtstp_run_command ("ip link add %s type vrf table %u",
+		                        name,
+		                        lnk->table);
+
+		if (r == 0)
+			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_VRF, 100);
+		else
+			_LOGI ("Adding vrf device via iproute2 failed. Assume iproute2 is not up to the task.");
+	}
+
+	if (!pllink) {
+		r = nm_platform_link_vrf_add (platform, name, lnk, &pllink);
+		if (r == -EOPNOTSUPP)
+			NM_SET_OUT (out_not_supported, TRUE);
+	}
+
+	_assert_pllink (platform, r == 0, pllink, name, NM_LINK_TYPE_VRF);
+
+	return pllink;
+}
+
+const NMPlatformLink *
 nmtstp_link_vxlan_add (NMPlatform *platform,
                        gboolean external_command,
                        const char *name,
@@ -1610,16 +1650,16 @@ nmtstp_link_vxlan_add (NMPlatform *platform,
 			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
 
 		if (lnk->local)
-			nm_utils_inet4_ntop (lnk->local, local);
+			_nm_utils_inet4_ntop (lnk->local, local);
 		else if (memcmp (&lnk->local6, &in6addr_any, sizeof (in6addr_any)))
-			nm_utils_inet6_ntop (&lnk->local6, local);
+			_nm_utils_inet6_ntop (&lnk->local6, local);
 		else
 			local[0] = '\0';
 
 		if (lnk->group)
-			nm_utils_inet4_ntop (lnk->group, group);
+			_nm_utils_inet4_ntop (lnk->group, group);
 		else if (memcmp (&lnk->group6, &in6addr_any, sizeof (in6addr_any)))
-			nm_utils_inet6_ntop (&lnk->group6, group);
+			_nm_utils_inet6_ntop (&lnk->group6, group);
 		else
 			group[0] = '\0';
 
@@ -1736,7 +1776,7 @@ nmtstp_link_delete (NMPlatform *platform,
 	}
 
 	/* Let's wait until we get the result */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 250;
 	do {
 		if (external_command)
 			nm_platform_process_events (platform);
@@ -1785,7 +1825,7 @@ nmtstp_link_set_updown (NMPlatform *platform,
 	}
 
 	/* Let's wait until we get the result */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_msec () + 250;
 	do {
 		if (external_command)
 			nm_platform_process_events (platform);
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index c571df6a..522c3756 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -32,13 +32,13 @@
         const NMLogDomain __domain = (domain); \
         \
         if (nm_logging_enabled (__level, __domain)) { \
-            gint64 _ts = nm_utils_get_monotonic_timestamp_ns (); \
+            gint64 _ts = nm_utils_get_monotonic_timestamp_nsec (); \
             \
             _nm_log (__level, __domain, 0, NULL, NULL, \
                      "%s[%ld.%09ld]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                      _NMLOG_PREFIX_NAME, \
-                     (long) (_ts / NM_UTILS_NS_PER_SECOND), \
-                     (long) (_ts % NM_UTILS_NS_PER_SECOND) \
+                     (long) (_ts / NM_UTILS_NSEC_PER_SEC), \
+                     (long) (_ts % NM_UTILS_NSEC_PER_SEC) \
                      _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
@@ -101,14 +101,14 @@ int nmtstp_run_command (const char *format, ...) _nm_printf (1, 2);
 
 /*****************************************************************************/
 
-guint nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms);
+guint nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_msec);
 guint nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms);
-const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms);
+const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_msec);
 const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
 
-#define nmtstp_assert_wait_for_signal(platform, timeout_ms) \
+#define nmtstp_assert_wait_for_signal(platform, timeout_msec) \
 	G_STMT_START { \
-		if (nmtstp_wait_for_signal (platform, timeout_ms) == 0) \
+		if (nmtstp_wait_for_signal (platform, timeout_msec) == 0) \
 			g_assert_not_reached (); \
 	} G_STMT_END
 
@@ -118,8 +118,8 @@ const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const ch
 			g_assert_not_reached (); \
 	} G_STMT_END
 
-#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_ms) \
-	nmtst_assert_nonnull (nmtstp_wait_for_link (platform, ifname, expected_link_type, timeout_ms))
+#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_msec) \
+	nmtst_assert_nonnull (nmtstp_wait_for_link (platform, ifname, expected_link_type, timeout_msec))
 
 #define nmtstp_assert_wait_for_link_until(platform, ifname, expected_link_type, until_ms) \
 	nmtst_assert_nonnull (nmtstp_wait_for_link_until (platform, ifname, expected_link_type, until_ms))
@@ -360,6 +360,11 @@ const NMPlatformLink *nmtstp_link_tun_add (NMPlatform *platform,
                                            const char *name,
                                            const NMPlatformLnkTun *lnk,
                                            int *out_fd);
+const NMPlatformLink *nmtstp_link_vrf_add (NMPlatform *platform,
+                                           gboolean external_command,
+                                           const char *name,
+                                           const NMPlatformLnkVrf *lnk,
+                                           gboolean *out_not_supported);
 const NMPlatformLink *nmtstp_link_vxlan_add (NMPlatform *platform,
                                              gboolean external_command,
                                              const char *name,
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 37e1cde5..2661914b 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1144,7 +1144,7 @@ test_software_detect (gconstpointer user_data)
 		break;
 	}
 	case NM_LINK_TYPE_MACVTAP: {
-		NMPlatformLnkMacvtap lnk_macvtap = { };
+		NMPlatformLnkMacvlan lnk_macvtap = { };
 
 		lnk_macvtap.mode = MACVLAN_MODE_PRIVATE;
 		lnk_macvtap.no_promisc = FALSE;
@@ -1182,6 +1182,21 @@ test_software_detect (gconstpointer user_data)
 	case NM_LINK_TYPE_VLAN:
 		nmtstp_run_command_check ("ip link add name %s link %s type vlan id 1242", DEVICE_NAME, PARENT_NAME);
 		break;
+	case NM_LINK_TYPE_VRF: {
+		NMPlatformLnkVrf lnk_vrf = { };
+		gboolean not_supported;
+
+		lnk_vrf.table = 9876;
+
+		if (!nmtstp_link_vrf_add (NULL, ext, DEVICE_NAME, &lnk_vrf, &not_supported)) {
+			if (not_supported) {
+				g_test_skip ("Cannot create VRF interface because of missing kernel support");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding VRF interface");
+		}
+		break;
+	}
 	case NM_LINK_TYPE_VXLAN: {
 		NMPlatformLnkVxlan lnk_vxlan = { };
 
@@ -1389,7 +1404,7 @@ test_software_detect (gconstpointer user_data)
 			break;
 		}
 		case NM_LINK_TYPE_MACVTAP: {
-			const NMPlatformLnkMacvtap *plnk = &lnk->lnk_macvlan;
+			const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan;
 
 			g_assert (plnk == nm_platform_link_get_lnk_macvtap (NM_PLATFORM_GET, ifindex, NULL));
 			g_assert_cmpint (plnk->no_promisc, ==, FALSE);
@@ -1444,6 +1459,13 @@ test_software_detect (gconstpointer user_data)
 			g_assert_cmpint (plnk->id, ==, 1242);
 			break;
 		}
+		case NM_LINK_TYPE_VRF: {
+			const NMPlatformLnkVrf *plnk = &lnk->lnk_vrf;
+
+			g_assert (plnk == nm_platform_link_get_lnk_vrf (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->table, ==, 9876);
+			break;
+		}
 		case NM_LINK_TYPE_VXLAN: {
 			const NMPlatformLnkVxlan *plnk = &lnk->lnk_vxlan;
 
@@ -2095,7 +2117,7 @@ test_vlan_set_xgress (void)
 static void
 test_create_many_links_do (guint n_devices)
 {
-	gint64 time, start_time = nm_utils_get_monotonic_timestamp_ns ();
+	gint64 time, start_time = nm_utils_get_monotonic_timestamp_nsec ();
 	guint i;
 	char name[64];
 	const NMPlatformLink *pllink;
@@ -2149,8 +2171,8 @@ test_create_many_links_do (guint n_devices)
 	_LOGI (">>> process events after deleting devices...");
 	nm_platform_process_events (NM_PLATFORM_GET);
 
-	time = nm_utils_get_monotonic_timestamp_ns () - start_time;
-	_LOGI (">>> finished in %ld.%09ld seconds", (long) (time / NM_UTILS_NS_PER_SECOND), (long) (time % NM_UTILS_NS_PER_SECOND));
+	time = nm_utils_get_monotonic_timestamp_nsec () - start_time;
+	_LOGI (">>> finished in %ld.%09ld seconds", (long) (time / NM_UTILS_NSEC_PER_SEC), (long) (time % NM_UTILS_NSEC_PER_SEC));
 }
 
 static void
@@ -3313,6 +3335,7 @@ _nmtstp_setup_tests (void)
 		test_software_detect_add ("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0);
 		test_software_detect_add ("/link/software/detect/tun", NM_LINK_TYPE_TUN, 0);
 		test_software_detect_add ("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0);
+		test_software_detect_add ("/link/software/detect/vrf", NM_LINK_TYPE_VRF, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0);
 		test_software_detect_add ("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1);
 		test_software_detect_add ("/link/software/detect/wireguard/0", NM_LINK_TYPE_WIREGUARD, 0);
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index f8b1bb9a..f074ca69 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -19,7 +19,7 @@
 
 static void
 _wait_for_ipv4_addr_device_route (NMPlatform *platform,
-                                  gint64 timeout_ms,
+                                  gint64 timeout_msec,
                                   int ifindex,
                                   in_addr_t addr,
                                   guint8 plen)
@@ -55,7 +55,7 @@ _wait_for_ipv4_addr_device_route (NMPlatform *platform,
 
 static void
 _wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
-                                   gint64 timeout_ms,
+                                   gint64 timeout_msec,
                                    int ifindex,
                                    guint addr_n,
                                    const struct in6_addr *addrs)
@@ -67,7 +67,7 @@ _wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
 	 * small amount of time, which prevents the immediate addition of the route
 	 * with RTA_PREFSRC */
 
-	NMTST_WAIT_ASSERT (timeout_ms, {
+	NMTST_WAIT_ASSERT (timeout_msec, {
 		gboolean should_wait = FALSE;
 		const NMPlatformIP6Address *plt_addr;
 
@@ -544,6 +544,7 @@ test_ip4_route_options (gconstpointer test_data)
 		                                       a->address,
 		                                       a->plen,
 		                                       a->peer_address,
+		                                       nm_platform_ip4_broadcast_address_create (a->address, a->plen),
 		                                       a->lifetime,
 		                                       a->preferred,
 		                                       a->n_ifa_flags,
@@ -1238,7 +1239,7 @@ again_uid_range:
 	rr->uid_range.end   = nmtst_rand_select (0u, uids.uid, uids.euid);
 	if (rr->uid_range_has) {
 		if (rr->uid_range.end < rr->uid_range.start)
-			NMTST_SWAP (rr->uid_range.start, rr->uid_range.end);
+			NM_SWAP (rr->uid_range.start, rr->uid_range.end);
 		if (   rr->uid_range.start == ((guint32) -1)
 		    || rr->uid_range.end   == ((guint32) -1))
 			goto again_uid_range;
@@ -1258,7 +1259,7 @@ again_uid_range:
 				range->start = nmtst_rand_select (1u, 0xFFFEu, ((p      ) % 0xFFFEu) + 1);
 				range->end   = nmtst_rand_select (1u, 0xFFFEu, ((p >> 16) % 0xFFFEu) + 1, range->start);
 				if (range->end < range->start)
-					NMTST_SWAP (range->start, range->end);
+					NM_SWAP (range->start, range->end);
 			}
 		}
 	}
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index afeb3b02..a090ef9e 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -152,7 +152,7 @@ dispose (GObject *object)
 {
 	NMWifiUtilsNl80211 *self = NM_WIFI_UTILS_NL80211 (object);
 
-	g_clear_pointer (&self->freqs, g_free);
+	nm_clear_g_free (&self->freqs);
 }
 
 struct nl80211_iface_info {
diff --git a/src/ppp/meson.build b/src/ppp/meson.build
index 0c458768..181a6bef 100644
--- a/src/ppp/meson.build
+++ b/src/ppp/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 name = 'nm-pppd-plugin'
 
 c_flags = [
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index f649e0cb..751ec28b 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -41,7 +41,8 @@
 
 #define NM_PPPD_PLUGIN PPPD_PLUGIN_DIR "/nm-pppd-plugin.so"
 
-static NM_CACHED_QUARK_FCN ("ppp-manager-secret-tries", ppp_manager_secret_tries_quark)
+static
+NM_CACHED_QUARK_FCN ("ppp-manager-secret-tries", ppp_manager_secret_tries_quark)
 
 /*****************************************************************************/
 
@@ -664,7 +665,8 @@ out:
 
 /*****************************************************************************/
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (pppd_exit_code_to_str, int,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (pppd_exit_code_to_str, int,
 	NM_UTILS_LOOKUP_DEFAULT ("Unknown error"),
 	NM_UTILS_LOOKUP_STR_ITEM ( 1, "Fatal pppd error");
 	NM_UTILS_LOOKUP_STR_ITEM ( 2, "pppd options error"),
@@ -1259,7 +1261,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE ((NMPPPManager *) object);
+	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_PARENT_IFACE:
@@ -1275,7 +1277,7 @@ static void
 set_property (GObject *object, guint prop_id,
               const GValue *value, GParamSpec *pspec)
 {
-	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE ((NMPPPManager *) object);
+	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_PARENT_IFACE:
@@ -1333,7 +1335,7 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE ((NMPPPManager *) object);
+	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (object);
 
 	g_free (priv->parent_iface);
 
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index ecca0eb3..363b1175 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -35,15 +35,9 @@ typedef struct {
 	NMAuthManager *auth_mgr;
 	NMSessionMonitor *session_monitor;
 
-	/* Auth chains for checking agent permissions */
-	GSList *chains;
+	CList agent_lst_head;
 
-	/* Hashed by owner name, not identifier, since two agents in different
-	 * sessions can use the same identifier.
-	 */
-	GHashTable *agents;
-
-	CList requests;
+	CList request_lst_head;
 
 	guint64 agent_version_id;
 } NMAgentManagerPrivate;
@@ -119,6 +113,8 @@ static void _con_del_request_start (Request *req);
 
 static gboolean _con_get_try_complete_early (Request *req);
 
+static void agent_disconnected_cb (NMSecretAgent *agent, gpointer user_data);
+
 /*****************************************************************************/
 
 guint64
@@ -152,7 +148,7 @@ _request_type_to_string (RequestType request_type, gboolean verbose)
 /*****************************************************************************/
 
 struct _NMAgentManagerCallId {
-	CList lst_request;
+	CList request_lst;
 
 	NMAgentManager *self;
 
@@ -201,40 +197,73 @@ struct _NMAgentManagerCallId {
 
 /*****************************************************************************/
 
-static gboolean
-remove_agent (NMAgentManager *self, const char *owner)
+static NMSecretAgent *
+_agent_find_by_owner (NMAgentManagerPrivate *priv,
+                      const char *owner)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMSecretAgent *agent;
-	CList *iter, *safe;
 
-	g_return_val_if_fail (owner != NULL, FALSE);
+	c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+		if (nm_streq (nm_secret_agent_get_dbus_owner (agent), owner))
+			return agent;
+	}
+	return NULL;
+}
 
-	/* Make sure this agent has already registered */
-	agent = g_hash_table_lookup (priv->agents, owner);
-	if (!agent)
-		return FALSE;
+static NMSecretAgent *
+_agent_find_by_identifier_and_uid (NMAgentManagerPrivate *priv,
+                                   const char *identifier,
+                                   gulong sender_uid)
+{
+	NMSecretAgent *agent;
+
+	c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+		if (    nm_streq0 (nm_secret_agent_get_identifier (agent), identifier)
+		    && sender_uid == nm_secret_agent_get_owner_uid (agent))
+			return agent;
+	}
+	return NULL;
+}
+
+/*****************************************************************************/
+
+static void
+_agent_remove (NMAgentManager *self, NMSecretAgent *agent)
+{
+	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
+	CList *iter, *safe;
+
+	nm_assert (NM_IS_SECRET_AGENT (agent));
+	nm_assert (c_list_contains (&priv->agent_lst_head, &agent->agent_lst));
 
 	_LOGD (agent, "agent unregistered or disappeared");
 
+	nm_clear_pointer (&agent->auth_chain, nm_auth_chain_destroy);
+
+	c_list_unlink (&agent->agent_lst);
+
+	g_signal_handlers_disconnect_by_func (agent, G_CALLBACK (agent_disconnected_cb), self);
+
 	/* Remove this agent from any in-progress secrets requests */
-	c_list_for_each_safe (iter, safe, &priv->requests)
-		request_remove_agent (c_list_entry (iter, Request, lst_request), agent);
+	c_list_for_each_safe (iter, safe, &priv->request_lst_head)
+		request_remove_agent (c_list_entry (iter, Request, request_lst), agent);
 
-	/* And dispose of the agent */
-	g_hash_table_remove (priv->agents, owner);
-	return TRUE;
+	g_object_unref (agent);
 }
 
 /* Call this *after* calling request_next_agent() */
 static void
-maybe_remove_agent_on_error (NMSecretAgent *agent,
+maybe_remove_agent_on_error (NMAgentManager *self,
+                             NMSecretAgent *agent,
                              GError *error)
 {
-	if (   g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)
-	    || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED)
-	    || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
-		remove_agent (nm_agent_manager_get (), nm_secret_agent_get_dbus_owner (agent));
+	if (   !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED)
+	    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED)
+	    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
+		return;
+
+	if (!c_list_is_empty (&agent->agent_lst))
+		_agent_remove (self, agent);
 }
 
 /*****************************************************************************/
@@ -295,70 +324,92 @@ validate_identifier (const char *identifier, GError **error)
 }
 
 static void
-agent_register_permissions_done (NMAuthChain *chain,
-                                 GDBusMethodInvocation *context,
-                                 gpointer user_data)
+_agent_permissions_check_done (NMAuthChain *chain,
+                               GDBusMethodInvocation *context,
+                               gpointer user_data)
 {
 	NMAgentManager *self = NM_AGENT_MANAGER (user_data);
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMSecretAgent *agent;
-	const char *sender;
-	NMAuthCallResult result;
-	CList *iter;
+	Request *request;
 
-	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
-
-	priv->chains = g_slist_remove (priv->chains, chain);
+	nm_assert (!context || G_IS_DBUS_METHOD_INVOCATION (context));
 
 	agent = nm_auth_chain_steal_data (chain, "agent");
-	nm_assert (agent);
 
-	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
-	if (result == NM_AUTH_CALL_RESULT_YES)
-		nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, TRUE);
+	nm_assert (NM_IS_SECRET_AGENT (agent));
+	nm_assert (agent->auth_chain == chain);
+	nm_assert (agent->fully_registered == (!context));
+	nm_assert (c_list_contains (&priv->agent_lst_head, &agent->agent_lst));
 
-	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
-	if (result == NM_AUTH_CALL_RESULT_YES)
-		nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, TRUE);
+	agent->auth_chain = NULL;
+
+	nm_secret_agent_add_permission (agent,
+	                                NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED,
+	                                (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) == NM_AUTH_CALL_RESULT_YES));
+	nm_secret_agent_add_permission (agent,
+	                                NM_AUTH_PERMISSION_WIFI_SHARE_OPEN,
+	                                (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN) == NM_AUTH_CALL_RESULT_YES));
+
+	if (agent->fully_registered) {
+		_LOGD (agent, "updated agent permissions");
+		return;
+	}
 
-	priv->agent_version_id += 1;
-	sender = nm_secret_agent_get_dbus_owner (agent);
-	g_hash_table_insert (priv->agents, g_strdup (sender), agent);
 	_LOGI (agent, "agent registered");
+
+	agent->fully_registered = TRUE;
+
+	priv->agent_version_id += 1;
+
 	g_dbus_method_invocation_return_value (context, NULL);
 
-	/* Signal an agent was registered */
-	g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
+	c_list_for_each_entry (request, &priv->request_lst_head, request_lst)
+		request_add_agent (request, agent);
 
-	/* Add this agent to any in-progress secrets requests */
-	c_list_for_each (iter, &priv->requests)
-		request_add_agent (c_list_entry (iter, Request, lst_request), agent);
+	g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
 }
 
-static NMSecretAgent *
-find_agent_by_identifier_and_uid (NMAgentManager *self,
-                                  const char *identifier,
-                                  gulong sender_uid)
+static NMAuthChain *
+_agent_create_auth_chain (NMAgentManager *self,
+                          NMSecretAgent *agent,
+                          GDBusMethodInvocation *context)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	GHashTableIter iter;
-	NMSecretAgent *agent;
+	NMAuthChain *chain;
 
-	g_hash_table_iter_init (&iter, priv->agents);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
-		if (   g_strcmp0 (nm_secret_agent_get_identifier (agent), identifier) == 0
-		    && nm_secret_agent_get_owner_uid (agent) == sender_uid)
-			return agent;
+	_LOGD (agent, "requesting permissions");
+
+	nm_assert (   !agent->auth_chain
+	           || (agent->fully_registered == (!nm_auth_chain_get_context (agent->auth_chain))));
+
+	if (   agent->auth_chain
+	    && !context
+	    && !agent->fully_registered) {
+		/* we restart the authorization check (without a @context), but the currently
+		 * pending auth-chain carries a context. We need to pass it on as we replace
+		 * the auth-chain. */
+		context = nm_auth_chain_get_context (agent->auth_chain);
+		nm_assert (context);
 	}
-	return NULL;
+
+	chain = nm_auth_chain_new_subject (nm_secret_agent_get_subject (agent),
+	                                   context,
+	                                   _agent_permissions_check_done,
+	                                   self);
+
+	nm_auth_chain_set_data (chain, "agent", agent, NULL);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
+
+	nm_clear_pointer (&agent->auth_chain, nm_auth_chain_destroy);
+	agent->auth_chain = chain;
+	return chain;
 }
 
 static void
 agent_disconnected_cb (NMSecretAgent *agent, gpointer user_data)
 {
-	/* The agent quit, so remove it and let interested clients know */
-	remove_agent (NM_AGENT_MANAGER (user_data),
-	              nm_secret_agent_get_dbus_owner (agent));
+	_agent_remove (NM_AGENT_MANAGER (user_data), agent);
 }
 
 static void
@@ -368,65 +419,44 @@ agent_manager_register_with_capabilities (NMAgentManager *self,
                                           guint32 capabilities)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	NMAuthSubject *subject;
+	gs_unref_object NMAuthSubject *subject = NULL;
 	gulong sender_uid = G_MAXULONG;
 	GError *error = NULL;
 	NMSecretAgent *agent;
-	NMAuthChain *chain;
 
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_dbus_manager_new_auth_subject_from_context (context);
 	if (!subject) {
 		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
 		                             NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
 		                             NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
-		goto done;
+		g_dbus_method_invocation_take_error (context, error);
+		return;
 	}
 	sender_uid = nm_auth_subject_get_unix_process_uid (subject);
 
 	/* Validate the identifier */
-	if (!validate_identifier (identifier, &error))
-		goto done;
+	if (!validate_identifier (identifier, &error)) {
+		g_dbus_method_invocation_take_error (context, error);
+		return;
+	}
 
 	/* Only one agent for each identifier is allowed per user */
-	if (find_agent_by_identifier_and_uid (self, identifier, sender_uid)) {
+	if (_agent_find_by_identifier_and_uid (priv, identifier, sender_uid)) {
 		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
 		                             NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
 		                             "An agent with this ID is already registered for this user.");
-		goto done;
+		g_dbus_method_invocation_take_error (context, error);
+		return;
 	}
 
-	/* Success, add the new agent */
 	agent = nm_secret_agent_new (context, subject, identifier, capabilities);
-	if (!agent) {
-		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
-		                             NM_AGENT_MANAGER_ERROR_FAILED,
-		                             "Failed to initialize the agent");
-		goto done;
-	}
+
 	g_signal_connect (agent, NM_SECRET_AGENT_DISCONNECTED,
 	                  G_CALLBACK (agent_disconnected_cb), self);
 
-	_LOGD (agent, "requesting permissions");
-
-	/* Kick off permissions requests for this agent */
-	chain = nm_auth_chain_new_subject (subject, context, agent_register_permissions_done, self);
-	if (chain) {
-		nm_auth_chain_set_data (chain, "agent", agent, g_object_unref);
-		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
-		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
+	c_list_link_tail (&priv->agent_lst_head, &agent->agent_lst);
 
-		priv->chains = g_slist_append (priv->chains, chain);
-	} else {
-		g_object_unref (agent);
-		error = g_error_new_literal (NM_AGENT_MANAGER_ERROR,
-		                             NM_AGENT_MANAGER_ERROR_FAILED,
-		                             "Unable to start agent authentication.");
-	}
-
-done:
-	if (error)
-		g_dbus_method_invocation_take_error (context, error);
-	g_clear_object (&subject);
+	_agent_create_auth_chain (self, agent, context);
 }
 
 static void
@@ -470,8 +500,11 @@ impl_agent_manager_unregister (NMDBusObject *obj,
                                GVariant *parameters)
 {
 	NMAgentManager *self = NM_AGENT_MANAGER (obj);
+	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
+	NMSecretAgent *agent;
 
-	if (!remove_agent (self, sender)) {
+	agent = _agent_find_by_owner (priv, sender);
+	if (!agent) {
 		g_dbus_method_invocation_return_error_literal (invocation,
 		                                               NM_AGENT_MANAGER_ERROR,
 		                                               NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
@@ -479,6 +512,8 @@ impl_agent_manager_unregister (NMDBusObject *obj,
 		return;
 	}
 
+	_agent_remove (self, agent);
+
 	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
@@ -497,7 +532,7 @@ request_new (NMAgentManager *self,
 	req->request_type = request_type;
 	req->detail = g_strdup (detail);
 	req->subject = g_object_ref (subject);
-	c_list_link_tail (&NM_AGENT_MANAGER_GET_PRIVATE (self)->requests, &req->lst_request);
+	c_list_link_tail (&NM_AGENT_MANAGER_GET_PRIVATE (self)->request_lst_head, &req->request_lst);
 	return req;
 }
 
@@ -583,7 +618,7 @@ req_complete_cancel (Request *req, gboolean is_disposing)
 	gs_free_error GError *error = NULL;
 
 	nm_assert (req && req->self);
-	nm_assert (!c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (req->self)->requests, &req->lst_request));
+	nm_assert (!c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (req->self)->request_lst_head, &req->request_lst));
 
 	nm_utils_error_set_cancelled (&error, is_disposing, "NMAgentManager");
 	req_complete_release (req, NULL, NULL, NULL, error);
@@ -598,9 +633,9 @@ req_complete (Request *req,
 {
 	NMAgentManager *self = req->self;
 
-	nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->requests, &req->lst_request));
+	nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->request_lst_head, &req->request_lst));
 
-	c_list_unlink (&req->lst_request);
+	c_list_unlink (&req->request_lst);
 
 	req_complete_release (req, secrets, agent_dbus_owner, agent_username, error);
 }
@@ -626,7 +661,7 @@ agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 	b_pid = nm_secret_agent_get_pid (b);
 
 	/* Prefer agents in the process the request came from */
-	if (nm_auth_subject_is_unix_process (req->subject)) {
+	if (nm_auth_subject_get_subject_type (req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
 		requester = nm_auth_subject_get_unix_process_pid (req->subject);
 
 		if (a_pid != b_pid) {
@@ -682,7 +717,7 @@ request_add_agent (Request *req, NMSecretAgent *agent)
 	}
 
 	/* If the request should filter agents by UID, do that now */
-	if (nm_auth_subject_is_unix_process (req->subject)) {
+	if (nm_auth_subject_get_subject_type (req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
 		uid_t agent_uid, subject_uid;
 
 		agent_uid = nm_secret_agent_get_owner_uid (agent);
@@ -710,12 +745,12 @@ static void
 request_add_agents (NMAgentManager *self, Request *req)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	GHashTableIter iter;
-	gpointer data;
+	NMSecretAgent *agent;
 
-	g_hash_table_iter_init (&iter, priv->agents);
-	while (g_hash_table_iter_next (&iter, NULL, &data))
-		request_add_agent (req, NM_SECRET_AGENT (data));
+	c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+		if (agent->fully_registered)
+			request_add_agent (req, agent);
+	}
 }
 
 static void
@@ -868,7 +903,7 @@ _con_get_request_done (NMSecretAgent *agent,
 
 			/* Try the next agent */
 			request_next_agent (req);
-			maybe_remove_agent_on_error (agent, error);
+			maybe_remove_agent_on_error (self, agent, error);
 		}
 		return;
 	}
@@ -1037,7 +1072,7 @@ _con_get_request_start (Request *req)
 		                                            NULL,
 		                                            _con_get_request_start_validated,
 		                                            req);
-		g_assert (req->con.chain);
+		nm_assert (req->con.chain);
 
 		/* If the caller is the only user in the connection's permissions, then
 		 * we use the 'modify.own' permission instead of 'modify.system'.  If the
@@ -1188,7 +1223,6 @@ nm_agent_manager_get_secrets (NMAgentManager *self,
 	req->con.get.callback = callback;
 	req->con.get.callback_data = callback_data;
 
-	/* Kick off the request */
 	if (!(req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM))
 		request_add_agents (self, req);
 	req->idle_id = g_idle_add (request_start, req);
@@ -1203,9 +1237,9 @@ nm_agent_manager_cancel_secrets (NMAgentManager *self,
 	g_return_if_fail (request_id);
 	g_return_if_fail (request_id->request_type == REQUEST_TYPE_CON_GET);
 
-	nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->requests, &request_id->lst_request));
+	nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->request_lst_head, &request_id->request_lst));
 
-	c_list_unlink (&request_id->lst_request);
+	c_list_unlink (&request_id->request_lst);
 
 	req_complete_cancel (request_id, FALSE);
 }
@@ -1242,7 +1276,7 @@ _con_save_request_done (NMSecretAgent *agent,
 		       LOG_REQ_ARG (req), error->message);
 		/* Try the next agent */
 		request_next_agent (req);
-		maybe_remove_agent_on_error (agent, error);
+		maybe_remove_agent_on_error (self, agent, error);
 		return;
 	}
 
@@ -1291,7 +1325,6 @@ nm_agent_manager_save_secrets (NMAgentManager *self,
 	req->con.path = g_strdup (path);
 	req->con.connection = g_object_ref (connection);
 
-	/* Kick off the request */
 	request_add_agents (self, req);
 	req->idle_id = g_idle_add (request_start, req);
 }
@@ -1333,7 +1366,7 @@ _con_del_request_done (NMSecretAgent *agent,
 	/* Tell the next agent to delete secrets */
 	request_next_agent (req);
 	if (error)
-		maybe_remove_agent_on_error (agent, error);
+		maybe_remove_agent_on_error (self, agent, error);
 }
 
 static void
@@ -1376,27 +1409,36 @@ nm_agent_manager_delete_secrets (NMAgentManager *self,
 	req->con.connection = g_object_ref (connection);
 	g_object_unref (subject);
 
-	/* Kick off the request */
 	request_add_agents (self, req);
 	req->idle_id = g_idle_add (request_start, req);
 }
 
 /*****************************************************************************/
 
-NMSecretAgent *
-nm_agent_manager_get_agent_by_user (NMAgentManager *self, const char *username)
+gboolean
+nm_agent_manager_has_agent_with_permission (NMAgentManager *self,
+                                            const char *username,
+                                            const char *permission)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	GHashTableIter iter;
+	NMAgentManagerPrivate *priv;
 	NMSecretAgent *agent;
 
-	g_hash_table_iter_init (&iter, priv->agents);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
-		if (g_strcmp0 (nm_secret_agent_get_owner_username (agent), username) == 0)
-			return agent;
+	g_return_val_if_fail (NM_IS_AGENT_MANAGER (self), FALSE);
+	g_return_val_if_fail (username, FALSE);
+	g_return_val_if_fail (permission, FALSE);
+
+	priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
+
+	c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+		if (!agent->fully_registered)
+			continue;
+		if (!nm_streq0 (nm_secret_agent_get_owner_username (agent), username))
+			continue;
+		if (nm_secret_agent_has_permission (agent, permission))
+			return TRUE;
 	}
 
-	return NULL;
+	return FALSE;
 }
 
 /*****************************************************************************/
@@ -1407,17 +1449,16 @@ nm_agent_manager_all_agents_have_capability (NMAgentManager *manager,
                                              NMSecretAgentCapabilities capability)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (manager);
-	GHashTableIter iter;
 	NMSecretAgent *agent;
-	gboolean subject_is_unix_process = nm_auth_subject_is_unix_process (subject);
-	gulong subject_uid = subject_is_unix_process ? nm_auth_subject_get_unix_process_uid (subject) : 0;
+	gboolean subject_is_unix_process = (nm_auth_subject_get_subject_type (subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
+	gulong subject_uid = subject_is_unix_process ? nm_auth_subject_get_unix_process_uid (subject) : 0u;
 
-	g_hash_table_iter_init (&iter, priv->agents);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
+	c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
+		if (!agent->fully_registered)
+			continue;
 		if (   subject_is_unix_process
 		    && nm_secret_agent_get_owner_uid (agent) != subject_uid)
 			continue;
-
 		if (!(nm_secret_agent_get_capabilities (agent) & capability))
 			return FALSE;
 	}
@@ -1428,58 +1469,13 @@ nm_agent_manager_all_agents_have_capability (NMAgentManager *manager,
 /*****************************************************************************/
 
 static void
-agent_permissions_changed_done (NMAuthChain *chain,
-                                GDBusMethodInvocation *context,
-                                gpointer user_data)
-{
-	NMAgentManager *self = NM_AGENT_MANAGER (user_data);
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	NMSecretAgent *agent;
-	gboolean share_protected = FALSE, share_open = FALSE;
-
-	priv->chains = g_slist_remove (priv->chains, chain);
-
-	agent = nm_auth_chain_get_data (chain, "agent");
-	g_assert (agent);
-
-	_LOGD (agent, "updated agent permissions");
-
-	if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) == NM_AUTH_CALL_RESULT_YES)
-		share_protected = TRUE;
-	if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN) == NM_AUTH_CALL_RESULT_YES)
-		share_open = TRUE;
-
-	nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, share_protected);
-	nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, share_open);
-}
-
-static void
 authority_changed_cb (NMAuthManager *auth_manager, NMAgentManager *self)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
-	GHashTableIter iter;
 	NMSecretAgent *agent;
 
-	/* Recheck the permissions of all secret agents */
-	g_hash_table_iter_init (&iter, priv->agents);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &agent)) {
-		NMAuthChain *chain;
-
-		/* Kick off permissions requests for this agent */
-		chain = nm_auth_chain_new_subject (nm_secret_agent_get_subject (agent),
-		                                   NULL,
-		                                   agent_permissions_changed_done,
-		                                   self);
-		g_assert (chain);
-		priv->chains = g_slist_append (priv->chains, chain);
-
-		/* Make sure if the agent quits while the permissions call is in progress
-		 * that the object sticks around until our callback.
-		 */
-		nm_auth_chain_set_data (chain, "agent", g_object_ref (agent), g_object_unref);
-		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
-		nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
-	}
+	c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst)
+		_agent_create_auth_chain (self, agent, NULL);
 }
 
 /*****************************************************************************/
@@ -1490,14 +1486,14 @@ nm_agent_manager_init (NMAgentManager *self)
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 
 	priv->agent_version_id = 1;
-	c_list_init (&priv->requests);
-	priv->agents = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+	c_list_init (&priv->agent_lst_head);
+	c_list_init (&priv->request_lst_head);
 }
 
 static void
 constructed (GObject *object)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE ((NMAgentManager *) object);
+	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (object);
 
 	G_OBJECT_CLASS (nm_agent_manager_parent_class)->constructed (object);
 
@@ -1515,24 +1511,19 @@ constructed (GObject *object)
 static void
 dispose (GObject *object)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE ((NMAgentManager *) object);
-	CList *iter;
-
-cancel_more:
-	c_list_for_each (iter, &priv->requests) {
-		c_list_unlink (iter);
-		req_complete_cancel (c_list_entry (iter, Request, lst_request), TRUE);
-		goto cancel_more;
-	}
-
-	g_slist_free_full (priv->chains, (GDestroyNotify) nm_auth_chain_destroy);
-	priv->chains = NULL;
+	NMAgentManager *self = NM_AGENT_MANAGER (object);
+	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
+	Request *request;
+	NMSecretAgent *agent;
 
-	if (priv->agents) {
-		g_hash_table_destroy (priv->agents);
-		priv->agents = NULL;
+	while ((request = c_list_first_entry (&priv->request_lst_head, Request, request_lst))) {
+		c_list_unlink (&request->request_lst);
+		req_complete_cancel (request, TRUE);
 	}
 
+	while ((agent = c_list_first_entry (&priv->agent_lst_head, NMSecretAgent, agent_lst)))
+		_agent_remove (self, agent);
+
 	if (priv->auth_mgr) {
 		g_signal_handlers_disconnect_by_func (priv->auth_mgr,
 		                                      G_CALLBACK (authority_changed_cb),
diff --git a/src/settings/nm-agent-manager.h b/src/settings/nm-agent-manager.h
index 5200d241..bf2dcb6c 100644
--- a/src/settings/nm-agent-manager.h
+++ b/src/settings/nm-agent-manager.h
@@ -65,8 +65,9 @@ void nm_agent_manager_delete_secrets (NMAgentManager *manager,
                                       const char *path,
                                       NMConnection *connection);
 
-NMSecretAgent *nm_agent_manager_get_agent_by_user (NMAgentManager *manager,
-                                                   const char *username);
+gboolean nm_agent_manager_has_agent_with_permission (NMAgentManager *self,
+                                                     const char *username,
+                                                     const char *permission);
 
 gboolean nm_agent_manager_all_agents_have_capability (NMAgentManager *manager,
                                                       NMAuthSubject *subject,
diff --git a/src/settings/nm-secret-agent.c b/src/settings/nm-secret-agent.c
index 74bd9b2f..d7172c63 100644
--- a/src/settings/nm-secret-agent.c
+++ b/src/settings/nm-secret-agent.c
@@ -14,7 +14,7 @@
 #include "nm-glib-aux/nm-dbus-aux.h"
 #include "nm-dbus-interface.h"
 #include "nm-core-internal.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-simple-connection.h"
 #include "NetworkManagerUtils.h"
 #include "c-list/src/c-list.h"
@@ -34,33 +34,28 @@ enum {
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-typedef struct {
+typedef struct _NMSecretAgentPrivate {
 	CList permissions;
+	CList requests;
+	GDBusConnection *dbus_connection;
 	char *description;
 	NMAuthSubject *subject;
 	char *identifier;
 	char *owner_username;
 	char *dbus_owner;
-	GDBusConnection *dbus_connection;
 	GCancellable *name_owner_cancellable;
-	CList requests;
-	NMSecretAgentCapabilities capabilities;
 	guint name_owner_changed_id;
+	NMSecretAgentCapabilities capabilities;
 	bool shutdown_wait_obj_registered:1;
 } NMSecretAgentPrivate;
 
-struct _NMSecretAgent {
-	GObject parent;
-	NMSecretAgentPrivate _priv;
-};
-
 struct _NMSecretAgentClass {
 	GObjectClass parent;
 };
 
 G_DEFINE_TYPE (NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT)
 
-#define NM_SECRET_AGENT_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSecretAgent, NM_IS_SECRET_AGENT)
+#define NM_SECRET_AGENT_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMSecretAgent, NM_IS_SECRET_AGENT)
 
 /*****************************************************************************/
 
@@ -112,7 +107,8 @@ G_DEFINE_TYPE (NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_capabilities_to_string, NMSecretAgentCapabilities,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_capabilities_to_string, NMSecretAgentCapabilities,
 	NM_UTILS_FLAGS2STR (NM_SECRET_AGENT_CAPABILITY_NONE,      "none"),
 	NM_UTILS_FLAGS2STR (NM_SECRET_AGENT_CAPABILITY_VPN_HINTS, "vpn-hints"),
 );
@@ -375,7 +371,7 @@ _dbus_call_cb (GObject *source,
 	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 
 	if (   !ret
-	    && nm_utils_error_is_cancelled (error, FALSE))
+	    && nm_utils_error_is_cancelled (error))
 		return;
 
 	call_id = user_data;
@@ -709,7 +705,8 @@ nm_secret_agent_new (GDBusMethodInvocation *context,
 
 	g_return_val_if_fail (context != NULL, NULL);
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
-	g_return_val_if_fail (nm_auth_subject_is_unix_process (subject), NULL);
+	g_return_val_if_fail (nm_auth_subject_get_subject_type (subject)
+	                      == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS, NULL);
 	g_return_val_if_fail (identifier != NULL, NULL);
 
 	dbus_connection = g_dbus_method_invocation_get_connection (context);
@@ -764,8 +761,13 @@ nm_secret_agent_new (GDBusMethodInvocation *context,
 static void
 nm_secret_agent_init (NMSecretAgent *self)
 {
-	NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
+	NMSecretAgentPrivate *priv;
 
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_SECRET_AGENT, NMSecretAgentPrivate);
+
+	self->_priv = priv;
+
+	c_list_init (&self->agent_lst);
 	c_list_init (&priv->permissions);
 	c_list_init (&priv->requests);
 }
@@ -776,6 +778,8 @@ dispose (GObject *object)
 	NMSecretAgent *self = NM_SECRET_AGENT (object);
 	NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
 
+	nm_assert (c_list_is_empty (&self->agent_lst));
+	nm_assert (!self->auth_chain);
 	nm_assert (c_list_is_empty (&priv->requests));
 
 	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
@@ -813,6 +817,8 @@ nm_secret_agent_class_init (NMSecretAgentClass *config_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (config_class);
 
+	g_type_class_add_private (object_class, sizeof (NMSecretAgentPrivate));
+
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 
diff --git a/src/settings/nm-secret-agent.h b/src/settings/nm-secret-agent.h
index ea86e432..37a93d17 100644
--- a/src/settings/nm-secret-agent.h
+++ b/src/settings/nm-secret-agent.h
@@ -8,6 +8,8 @@
 
 #include "nm-connection.h"
 
+#include "c-list/src/c-list.h"
+
 #define NM_TYPE_SECRET_AGENT            (nm_secret_agent_get_type ())
 #define NM_SECRET_AGENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SECRET_AGENT, NMSecretAgent))
 #define NM_SECRET_AGENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SECRET_AGENT, NMSecretAgentClass))
@@ -20,6 +22,17 @@
 typedef struct _NMSecretAgentClass NMSecretAgentClass;
 typedef struct _NMSecretAgentCallId NMSecretAgentCallId;
 
+struct _NMAuthChain;
+struct _NMSecretAgentPrivate;
+
+struct _NMSecretAgent {
+	GObject parent;
+	CList agent_lst;
+	struct _NMAuthChain *auth_chain;
+	struct _NMSecretAgentPrivate *_priv;
+	bool fully_registered:1;
+};
+
 GType nm_secret_agent_get_type (void);
 
 NMSecretAgent *nm_secret_agent_new (GDBusMethodInvocation *context,
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index ccbab807..48e3dce2 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -18,12 +18,12 @@
 #include "nm-session-monitor.h"
 #include "nm-auth-manager.h"
 #include "nm-auth-utils.h"
-#include "nm-auth-subject.h"
 #include "nm-agent-manager.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 #include "nm-audit-manager.h"
 #include "nm-settings.h"
+#include "nm-dbus-manager.h"
 #include "settings/plugins/keyfile/nms-keyfile-storage.h"
 
 #define AUTOCONNECT_RETRIES_UNSET        -2
@@ -409,10 +409,7 @@ nm_settings_connection_check_permission (NMSettingsConnection *self,
 		 * either.
 		 */
 		if (nm_setting_connection_get_permission (s_con, i, NULL, &puser, NULL)) {
-			NMSecretAgent *agent = nm_agent_manager_get_agent_by_user (priv->agent_mgr, puser);
-
-			if (   agent
-			    && nm_secret_agent_has_permission (agent, permission))
+			if (nm_agent_manager_has_agent_with_permission (priv->agent_mgr, puser, permission))
 				return TRUE;
 		}
 	}
@@ -1249,7 +1246,7 @@ _new_auth_subject (GDBusMethodInvocation *context, GError **error)
 {
 	NMAuthSubject *subject;
 
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_dbus_manager_new_auth_subject_from_context (context);
 	if (!subject) {
 		g_set_error_literal (error,
 		                     NM_SETTINGS_ERROR,
@@ -1971,7 +1968,8 @@ _nm_settings_connection_emit_signal_updated_internal (NMSettingsConnection *self
 
 /*****************************************************************************/
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_settings_connection_flags_to_string, NMSettingsConnectionIntFlags,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_settings_connection_flags_to_string, NMSettingsConnectionIntFlags,
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,          "none"),
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED,       "unsaved"),
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,  "nm-generated"),
@@ -2365,7 +2363,7 @@ _autoconnect_retries_set (NMSettingsConnection *self,
 		 * the tracking of resetting the retry count in NMPolicy needs adjustment
 		 * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
 		 * the next-timeout every time a connection gets blocked). */
-		priv->autoconnect_retries_blocked_until = nm_utils_get_monotonic_timestamp_s () + AUTOCONNECT_RESET_RETRIES_TIMER;
+		priv->autoconnect_retries_blocked_until = nm_utils_get_monotonic_timestamp_sec () + AUTOCONNECT_RESET_RETRIES_TIMER;
 	}
 }
 
@@ -2416,7 +2414,8 @@ nm_settings_connection_autoconnect_retries_blocked_until (NMSettingsConnection *
 	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_retries_blocked_until;
 }
 
-NM_UTILS_FLAGS2STR_DEFINE_STATIC (_autoconnect_blocked_reason_to_string, NMSettingsAutoconnectBlockedReason,
+static
+NM_UTILS_FLAGS2STR_DEFINE (_autoconnect_blocked_reason_to_string, NMSettingsAutoconnectBlockedReason,
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE, "none"),
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, "user-request"),
 	NM_UTILS_FLAGS2STR (NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, "failed"),
@@ -2612,14 +2611,14 @@ dispose (GObject *object)
 	nm_clear_pointer (&priv->system_secrets, g_variant_unref);
 	nm_clear_pointer (&priv->agent_secrets, g_variant_unref);
 
-	g_clear_pointer (&priv->seen_bssids, g_hash_table_destroy);
+	nm_clear_pointer (&priv->seen_bssids, g_hash_table_destroy);
 
 	g_clear_object (&priv->agent_mgr);
 
 	g_clear_object (&priv->connection);
 
-	g_clear_pointer (&priv->kf_db_timestamps, nm_key_file_db_unref);
-	g_clear_pointer (&priv->kf_db_seen_bssids, nm_key_file_db_unref);
+	nm_clear_pointer (&priv->kf_db_timestamps, nm_key_file_db_unref);
+	nm_clear_pointer (&priv->kf_db_seen_bssids, nm_key_file_db_unref);
 
 	G_OBJECT_CLASS (nm_settings_connection_parent_class)->dispose (object);
 
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index f964fb16..874efbfc 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -22,7 +22,7 @@
 
 #include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-glib-aux/nm-keyfile-aux.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-setting-8021x.h"
@@ -54,7 +54,7 @@
 #include "nm-settings-plugin.h"
 #include "nm-dbus-manager.h"
 #include "nm-auth-utils.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-session-monitor.h"
 #include "plugins/keyfile/nms-keyfile-plugin.h"
 #include "plugins/keyfile/nms-keyfile-storage.h"
@@ -67,7 +67,11 @@
 
 /*****************************************************************************/
 
-static NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
+static
+NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
+
+static
+NM_CACHED_QUARK_FCN ("default-wired-connection-blocked", _default_wired_connection_blocked_quark)
 
 /*****************************************************************************/
 
@@ -548,7 +552,7 @@ _startup_complete_check (NMSettings *self,
 		goto ready;
 
 	if (!now_us)
-		now_us = nm_utils_get_monotonic_timestamp_us ();
+		now_us = nm_utils_get_monotonic_timestamp_usec ();
 
 	next_expiry = 0;
 
@@ -654,7 +658,7 @@ _startup_complete_notify_connection (NMSettings *self,
 		} else
 			scd = g_hash_table_lookup (priv->startup_complete_idx, &sett_conn);
 		if (!scd) {
-			now_us = nm_utils_get_monotonic_timestamp_us ();
+			now_us = nm_utils_get_monotonic_timestamp_usec ();
 			scd = g_slice_new (StartupCompleteData);
 			*scd = (StartupCompleteData) {
 				.sett_conn = g_object_ref (sett_conn),
@@ -2574,7 +2578,7 @@ settings_add_connection_helper (NMSettings *self,
 		return;
 	}
 
-	subject = nm_auth_subject_new_unix_process_from_context (context);
+	subject = nm_dbus_manager_new_auth_subject_from_context (context);
 	if (!subject) {
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_SETTINGS_ERROR,
@@ -2918,7 +2922,7 @@ impl_settings_get_connection_by_uuid (NMDBusObject *obj,
 		goto error;
 	}
 
-	subject = nm_auth_subject_new_unix_process_from_context (invocation);
+	subject = nm_dbus_manager_new_auth_subject_from_context (invocation);
 	if (!subject) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
@@ -3431,9 +3435,13 @@ device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 	 */
 	if (   !NM_DEVICE_GET_CLASS (device)->new_default_connection
 	    || !nm_device_get_managed (device, FALSE)
-	    || g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()))
+	    || g_object_get_qdata (G_OBJECT (device), _default_wired_connection_blocked_quark ()))
 		return;
 
+	/* we only check once whether to create the auto-default connection. If we reach this point,
+	 * we mark the creation of the default-wired-connection as blocked. */
+	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_blocked_quark (), device);
+
 	if (nm_config_get_no_auto_default_for_device (priv->config, device)) {
 		_LOGT ("auto-default: cannot create auto-default connection for device %s: disabled by \"no-auto-default\"",
 		       nm_device_get_iface (device));
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
index d9dd3edb..e193ff96 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 install_data(
   'nm-ifcfg-rh.conf',
   install_dir: dbus_conf_dir,
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index f57ca1a7..f5c9bb5e 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -20,6 +20,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-config.h"
+#include "nm-dbus-manager.h"
 #include "settings/nm-settings-plugin.h"
 #include "settings/nm-settings-utils.h"
 #include "NetworkManagerUtils.h"
@@ -205,9 +206,9 @@ _load_file (NMSIfcfgRHPlugin *self,
 		const char *unmanaged_spec;
 		const char *unrecognized_spec;
 
-		if (!nms_ifcfg_rh_util_parse_unhandled_spec (unhandled_spec,
-		                                             &unmanaged_spec,
-		                                             &unrecognized_spec)) {
+		if (!nms_ifcfg_rh_utils_parse_unhandled_spec (unhandled_spec,
+		                                              &unmanaged_spec,
+		                                              &unrecognized_spec)) {
 			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
 			                    "invalid unhandled spec \"%s\"",
 			                    unhandled_spec);
@@ -1132,6 +1133,13 @@ _dbus_setup (NMSIfcfgRHPlugin *self)
 
 	_dbus_clear (self);
 
+	if (!NM_MAIN_DBUS_CONNECTION_GET) {
+		_LOGW ("dbus: don't use D-Bus for %s service", IFCFGRH1_BUS_NAME);
+		return;
+	}
+
+	/* We use a separate D-Bus connection so that org.freedesktop.NetworkManager and com.redhat.ifcfgrh1
+	 * are exported by different connections. */
 	address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
 	if (address == NULL) {
 		_LOGW ("dbus: failed getting address for system bus: %s", error->message);
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index e01f7344..8e153412 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -399,7 +399,9 @@ make_connection_setting (const char *file,
 	if (v) {
 		GError *error = NULL;
 
-		if (nm_utils_ifname_valid_kernel (v, &error)) {
+		/* Only validate for NMU_IFACE_KERNEL, because ifcfg plugin anyway
+		 * doesn't support OVS types. */
+		if (nm_utils_ifname_valid (v, NMU_IFACE_KERNEL, &error)) {
 			g_object_set (s_con,
 			              NM_SETTING_CONNECTION_INTERFACE_NAME, v,
 			              NULL);
@@ -502,6 +504,24 @@ make_connection_setting (const char *file,
 	}
 
 	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "VRF_UUID", &value);
+	if (!v)
+		v = svGetValueStr (ifcfg, "VRF", &value);
+	if (v) {
+		const char *old_value;
+
+		if ((old_value = nm_setting_connection_get_master (s_con))) {
+			PARSE_WARNING ("Already configured as slave of %s. Ignoring VRF{_UUID}=\"%s\"",
+			               old_value, v);
+		} else {
+			g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
+			g_object_set (s_con, NM_SETTING_CONNECTION_SLAVE_TYPE,
+			              NM_SETTING_VRF_SETTING_NAME, NULL);
+		}
+	}
+
+
+	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "GATEWAY_PING_TIMEOUT", &value);
 	if (v) {
 		gint64 tmp;
@@ -530,16 +550,18 @@ make_connection_setting (const char *file,
 	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (int) vint64, NULL);
 
 	nm_clear_g_free (&value);
-	v = svGetValueStr (ifcfg, "DEVTIMEOUT", &value);
+	v = svGetValue (ifcfg, "DEVTIMEOUT", &value);
 	if (v) {
+		v = nm_str_skip_leading_spaces (v);
 		vint64 = _nm_utils_ascii_str_to_int64 (v, 10, 0, ((gint64) G_MAXINT32) / 1000, -1);
 		if (vint64 != -1)
 			vint64 *= 1000;
-		else {
+		else if (v[0] != '\0') {
 			char *endptr;
 			double d;
 
-			d = g_ascii_strtod (v, &endptr);
+			d = nm_g_ascii_strtod (v, &endptr);
+			endptr = nm_str_skip_leading_spaces (endptr);
 			if (   errno == 0
 			    && endptr[0] == '\0'
 			    && d >= 0.0) {
@@ -709,7 +731,7 @@ read_full_ip4_address (shvarFile *ifcfg,
 			else {
 				/* Try to autodetermine the prefix for the address' class */
 				prefix = _nm_utils_ip4_get_default_prefix (ipaddr);
-				PARSE_WARNING ("missing %s, assuming %s/%d", prefix_tag, nm_utils_inet4_ntop (ipaddr, inet_buf), prefix);
+				PARSE_WARNING ("missing %s, assuming %s/%d", prefix_tag, _nm_utils_inet4_ntop (ipaddr, inet_buf), prefix);
 			}
 		}
 	}
@@ -1287,7 +1309,7 @@ read_one_ip4_route (shvarFile *ifcfg,
 		prefix = nm_utils_ip4_netmask_to_prefix (netmask);
 		if (netmask != _nm_utils_ip4_prefix_to_netmask (prefix)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid IP4 netmask '%s' \"%s\"", netmask_tag, nm_utils_inet4_ntop (netmask, inet_buf));
+			             "Invalid IP4 netmask '%s' \"%s\"", netmask_tag, _nm_utils_inet4_ntop (netmask, inet_buf));
 			return FALSE;
 		}
 	} else {
@@ -1318,7 +1340,7 @@ read_one_ip4_route (shvarFile *ifcfg,
 	v = svGetValueStr (ifcfg, numbered_tag (tag, "OPTIONS", which), &value);
 	if (v) {
 		if (parse_route_line (v, AF_INET, *out_route, NULL, error) < 0) {
-			g_clear_pointer (out_route, nm_ip_route_unref);
+			nm_clear_pointer (out_route, nm_ip_route_unref);
 			return FALSE;
 		}
 	}
@@ -1327,39 +1349,40 @@ read_one_ip4_route (shvarFile *ifcfg,
 }
 
 static gboolean
-read_route_file (int addr_family,
-                 const char *filename,
-                 NMSettingIPConfig *s_ip,
-                 GError **error)
+read_route_file_parse (int addr_family,
+                       const char *filename,
+                       const char *contents,
+                       gsize len,
+                       NMSettingIPConfig *s_ip,
+                       GError **error)
 {
-	gs_free char *contents = NULL;
-	char *contents_rest = NULL;
-	const char *line;
-	gsize len = 0;
 	gsize line_num;
 
-	g_return_val_if_fail (filename, FALSE);
-	g_return_val_if_fail (   (addr_family == AF_INET  && NM_IS_SETTING_IP4_CONFIG (s_ip))
-	                      || (addr_family == AF_INET6 && NM_IS_SETTING_IP6_CONFIG (s_ip)), FALSE);
-	g_return_val_if_fail (!error || !*error, FALSE);
+	nm_assert (filename);
+	nm_assert (addr_family == nm_setting_ip_config_get_addr_family (s_ip));
+	nm_assert (!error || !*error);
 
-	if (   !g_file_get_contents (filename, &contents, &len, NULL)
-	    || !len) {
+	if (len <= 0)
 		return TRUE;  /* missing/empty = success */
-	}
 
 	line_num = 0;
-	for (line = strtok_r (contents, "\n", &contents_rest);
-	     line;
-	     line = strtok_r (NULL, "\n", &contents_rest)) {
+	while (TRUE) {
 		nm_auto_unref_ip_route NMIPRoute *route = NULL;
 		gs_free_error GError *local = NULL;
+		const char *line = contents;
+		char *eol;
 		int e;
 
+		eol = strchr (contents, '\n');
+		if (eol) {
+			eol[0] = '\0';
+			contents = &eol[1];
+		}
+
 		line_num++;
 
 		if (parse_route_line_is_comment (line))
-			continue;
+			goto next;
 
 		e = parse_route_line (line, addr_family, NULL, &route, &local);
 
@@ -1371,14 +1394,38 @@ read_route_file (int addr_family,
 				 * entire connection. */
 				PARSE_WARNING ("ignoring invalid route at \"%s\" (%s:%lu): %s", line, filename, (long unsigned) line_num, local->message);
 			}
-			continue;
+			goto next;
 		}
 
 		if (!nm_setting_ip_config_add_route (s_ip, route))
 			PARSE_WARNING ("duplicate IPv%c route", addr_family == AF_INET ? '4' : '6');
+
+next:
+		if (!eol)
+			return TRUE;
+
+		/* restore original content. */
+		eol[0] = '\n';
 	}
+}
 
-	return TRUE;
+static gboolean
+read_route_file (int addr_family,
+                 const char *filename,
+                 NMSettingIPConfig *s_ip,
+                 GError **error)
+{
+	gs_free char *contents = NULL;
+	gsize len;
+
+	nm_assert (filename);
+	nm_assert (addr_family == nm_setting_ip_config_get_addr_family (s_ip));
+	nm_assert (!error || !*error);
+
+	if (!g_file_get_contents (filename, &contents, &len, NULL))
+		return TRUE;  /* missing/empty = success */
+
+	return read_route_file_parse (addr_family, filename, contents, len, s_ip, error);
 }
 
 static void
@@ -1577,7 +1624,6 @@ make_ip4_setting (shvarFile *ifcfg,
 	int i;
 	guint32 a;
 	gboolean has_key;
-	shvarFile *route_ifcfg;
 	gboolean never_default;
 	gint64 i64;
 	int priority;
@@ -1771,10 +1817,10 @@ make_ip4_setting (shvarFile *ifcfg,
 			nm_clear_g_free (&value);
 			v = svGetValueStr (ifcfg, tag, &value);
 			if (v) {
-				if (nm_utils_ipaddr_valid (AF_INET, v)) {
+				if (nm_utils_ipaddr_is_valid (AF_INET, v)) {
 					if (!nm_setting_ip_config_add_dns (s_ip4, v))
 						PARSE_WARNING ("duplicate DNS server %s", tag);
-				} else if (nm_utils_ipaddr_valid (AF_INET6, v)) {
+				} else if (nm_utils_ipaddr_is_valid (AF_INET6, v)) {
 					/* Ignore IPv6 addresses */
 				} else {
 					PARSE_WARNING ("invalid DNS server address %s", v);
@@ -1814,32 +1860,34 @@ make_ip4_setting (shvarFile *ifcfg,
 	/* Static routes  - route-<name> file */
 	route_path = utils_get_route_path (svFileGetName (ifcfg));
 
-	if (!routes_read) {
-		/* NOP */
-	} else if (utils_has_route_file_new_syntax (route_path)) {
-		/* Parse route file in new syntax */
-		route_ifcfg = utils_get_route_ifcfg (svFileGetName (ifcfg), FALSE);
-		if (route_ifcfg) {
+	if (routes_read) {
+		gs_free char *contents = NULL;
+		gsize len;
+
+		if (!g_file_get_contents (route_path, &contents, &len, NULL))
+			len = 0;
+
+		if (utils_has_route_file_new_syntax_content (contents, len)) {
+			nm_auto_shvar_file_close shvarFile *route_ifcfg = NULL;
+
+			/* Parse route file in new syntax */
+			route_ifcfg = svFile_new (route_path, -1, contents);
 			for (i = 0;; i++) {
-				NMIPRoute *route = NULL;
+				nm_auto_unref_ip_route NMIPRoute *route = NULL;
 
-				if (!read_one_ip4_route (route_ifcfg, i, &route, error)) {
-					svCloseFile (route_ifcfg);
+				if (!read_one_ip4_route (route_ifcfg, i, &route, error))
 					return NULL;
-				}
 
 				if (!route)
 					break;
 
 				if (!nm_setting_ip_config_add_route (s_ip4, route))
 					PARSE_WARNING ("duplicate IP4 route");
-				nm_ip_route_unref (route);
 			}
-			svCloseFile (route_ifcfg);
+		} else {
+			if (!read_route_file_parse (AF_INET, route_path, contents, len, s_ip4, error))
+				return NULL;
 		}
-	} else {
-		if (!read_route_file (AF_INET, route_path, s_ip4, error))
-			return NULL;
 	}
 
 	/* Legacy value NM used for a while but is incorrect (rh #459370) */
@@ -2214,7 +2262,7 @@ make_ip6_setting (shvarFile *ifcfg,
 			char *ptr;
 			if ((ptr = strchr (v, '%')) != NULL)
 				*ptr = '\0';  /* remove %interface prefix if present */
-			if (!nm_utils_ipaddr_valid (AF_INET6, v)) {
+			if (!nm_utils_ipaddr_is_valid (AF_INET6, v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				             "Invalid IP6 address '%s'", v);
 				return NULL;
@@ -2253,10 +2301,10 @@ make_ip6_setting (shvarFile *ifcfg,
 			break;
 		}
 
-		if (nm_utils_ipaddr_valid (AF_INET6, v)) {
+		if (nm_utils_ipaddr_is_valid (AF_INET6, v)) {
 			if (!nm_setting_ip_config_add_dns (s_ip6, v))
 				PARSE_WARNING ("duplicate DNS server %s", tag);
-		} else if (nm_utils_ipaddr_valid (AF_INET, v)) {
+		} else if (nm_utils_ipaddr_is_valid (AF_INET, v)) {
 			/* Ignore IPv4 addresses */
 		} else {
 			PARSE_WARNING ("invalid DNS server address %s", v);
@@ -3623,6 +3671,14 @@ next:
 	v = svGetValueStr (ifcfg, "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH", &value);
 	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH, v, NULL);
 
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_DOMAIN_MATCH", &value);
+	g_object_set (s_8021x, NM_SETTING_802_1X_DOMAIN_MATCH, v, NULL);
+
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_PHASE2_DOMAIN_MATCH", &value);
+	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH, v, NULL);
+
 	timeout = svGetValueInt64 (ifcfg, "IEEE_8021X_AUTH_TIMEOUT", 10, 0, G_MAXINT32, 0);
 	g_object_set (s_8021x, NM_SETTING_802_1X_AUTH_TIMEOUT, (int) timeout, NULL);
 
@@ -3645,7 +3701,7 @@ make_wpa_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingWirelessSecurity *wsec = NULL;
 	gs_free char *value = NULL;
 	const char *v;
-	gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
+	gboolean wpa_psk = FALSE, wpa_sae = FALSE, wpa_owe = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
 	int i_val;
 	GError *local = NULL;
 
@@ -3654,10 +3710,12 @@ make_wpa_setting (shvarFile *ifcfg,
 	v = svGetValueStr (ifcfg, "KEY_MGMT", &value);
 	wpa_psk = nm_streq0 (v, "WPA-PSK");
 	wpa_sae = nm_streq0 (v, "SAE");
+	wpa_owe = nm_streq0 (v, "OWE");
 	wpa_eap = nm_streq0 (v, "WPA-EAP");
 	ieee8021x = nm_streq0 (v, "IEEE8021X");
 	if (   !wpa_psk
 	    && !wpa_sae
+	    && !wpa_owe
 	    && !wpa_eap
 	    && !ieee8021x)
 		return NULL; /* Not WPA or Dynamic WEP */
@@ -3673,7 +3731,7 @@ make_wpa_setting (shvarFile *ifcfg,
 	              NULL);
 
 	/* Pairwise and Group ciphers (only relevant for WPA/RSN) */
-	if (wpa_psk || wpa_sae || wpa_eap) {
+	if (wpa_psk || wpa_sae || wpa_owe || wpa_eap) {
 		fill_wpa_ciphers (ifcfg, wsec, FALSE, adhoc);
 		fill_wpa_ciphers (ifcfg, wsec, TRUE, adhoc);
 	}
@@ -3722,7 +3780,7 @@ make_wpa_setting (shvarFile *ifcfg,
 			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
 		}
 	} else {
-		nm_assert (wpa_eap || ieee8021x);
+		nm_assert (wpa_eap || ieee8021x || wpa_owe);
 
 		/* Adhoc mode is mutually exclusive with any 802.1x-based authentication */
 		if (adhoc) {
@@ -3731,14 +3789,17 @@ make_wpa_setting (shvarFile *ifcfg,
 			return NULL;
 		}
 
-		*s_8021x = fill_8021x (ifcfg, file, v, TRUE, error);
-		if (!*s_8021x)
-			return NULL;
-
-		{
-			gs_free char *lower = g_ascii_strdown (v, -1);
+		if (wpa_owe) {
+			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL);
+		} else {
+			*s_8021x = fill_8021x (ifcfg, file, v, TRUE, error);
+			if (!*s_8021x)
+				return NULL;
 
-			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, lower, NULL);
+			{
+				gs_free char *lower = g_ascii_strdown (v, -1);
+				g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, lower, NULL);
+			}
 		}
 	}
 
@@ -3782,7 +3843,7 @@ make_leap_setting (shvarFile *ifcfg,
 	nm_clear_g_free (&value);
 
 	value = svGetValueStr_cp (ifcfg, "SECURITYMODE");
-	if (!value || strcasecmp (value, "leap"))
+	if (!value || g_ascii_strcasecmp (value, "leap"))
 		return NULL; /* Not LEAP */
 	nm_clear_g_free (&value);
 
@@ -4339,7 +4400,7 @@ parse_ethtool_option (const char *value,
 			i++;
 
 			if (nm_utils_hwaddr_valid (opt_val, ETH_ALEN)) {
-				g_clear_pointer (out_password, g_free);
+				nm_clear_g_free (out_password);
 				*out_password = g_strdup (opt_val);
 			} else
 				PARSE_WARNING ("Wake-on-LAN password '%s' is invalid", opt_val);
@@ -5091,18 +5152,24 @@ handle_bridge_option (NMSetting *setting,
 		gboolean only_with_stp;
 		gboolean extended_bool;
 	} m/*etadata*/[] = {
-		{ "DELAY",              NM_SETTING_BRIDGE_FORWARD_DELAY,      BRIDGE_OPT_TYPE_MAIN,   .only_with_stp = TRUE },
-		{ "priority",           NM_SETTING_BRIDGE_PRIORITY,           BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE },
-		{ "hello_time",         NM_SETTING_BRIDGE_HELLO_TIME,         BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE },
-		{ "max_age",            NM_SETTING_BRIDGE_MAX_AGE,            BRIDGE_OPT_TYPE_OPTION, .only_with_stp = TRUE },
-		{ "ageing_time",        NM_SETTING_BRIDGE_AGEING_TIME,        BRIDGE_OPT_TYPE_OPTION },
-		{ "multicast_snooping", NM_SETTING_BRIDGE_MULTICAST_SNOOPING, BRIDGE_OPT_TYPE_OPTION },
-		{ "vlan_filtering",     NM_SETTING_BRIDGE_VLAN_FILTERING,     BRIDGE_OPT_TYPE_OPTION },
-		{ "default_pvid",       NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID,  BRIDGE_OPT_TYPE_OPTION },
-		{ "group_fwd_mask",     NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, BRIDGE_OPT_TYPE_OPTION },
-		{ "priority",           NM_SETTING_BRIDGE_PORT_PRIORITY,      BRIDGE_OPT_TYPE_PORT_OPTION },
-		{ "path_cost",          NM_SETTING_BRIDGE_PORT_PATH_COST,     BRIDGE_OPT_TYPE_PORT_OPTION },
-		{ "hairpin_mode",       NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,  BRIDGE_OPT_TYPE_PORT_OPTION, .extended_bool = TRUE, },
+		{ "DELAY",                      NM_SETTING_BRIDGE_FORWARD_DELAY,              BRIDGE_OPT_TYPE_MAIN,        .only_with_stp = TRUE },
+		{ "priority",                   NM_SETTING_BRIDGE_PRIORITY,                   BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
+		{ "hello_time",                 NM_SETTING_BRIDGE_HELLO_TIME,                 BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
+		{ "max_age",                    NM_SETTING_BRIDGE_MAX_AGE,                    BRIDGE_OPT_TYPE_OPTION,      .only_with_stp = TRUE },
+		{ "ageing_time",                NM_SETTING_BRIDGE_AGEING_TIME,                BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_querier",          NM_SETTING_BRIDGE_MULTICAST_QUERIER,          BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_query_use_ifaddr", NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR, BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_snooping",         NM_SETTING_BRIDGE_MULTICAST_SNOOPING,         BRIDGE_OPT_TYPE_OPTION },
+		{ "multicast_router",           NM_SETTING_BRIDGE_MULTICAST_ROUTER,           BRIDGE_OPT_TYPE_OPTION },
+		{ "vlan_filtering",             NM_SETTING_BRIDGE_VLAN_FILTERING,             BRIDGE_OPT_TYPE_OPTION },
+		{ "default_pvid",               NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID,          BRIDGE_OPT_TYPE_OPTION },
+		{ "group_address",              NM_SETTING_BRIDGE_GROUP_ADDRESS,              BRIDGE_OPT_TYPE_OPTION },
+		{ "group_fwd_mask",             NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,         BRIDGE_OPT_TYPE_OPTION },
+		{ "vlan_protocol",              NM_SETTING_BRIDGE_VLAN_PROTOCOL,              BRIDGE_OPT_TYPE_OPTION },
+		{ "vlan_stats_enabled",         NM_SETTING_BRIDGE_VLAN_STATS_ENABLED,         BRIDGE_OPT_TYPE_OPTION },
+		{ "priority",                   NM_SETTING_BRIDGE_PORT_PRIORITY,              BRIDGE_OPT_TYPE_PORT_OPTION },
+		{ "path_cost",                  NM_SETTING_BRIDGE_PORT_PATH_COST,             BRIDGE_OPT_TYPE_PORT_OPTION },
+		{ "hairpin_mode",               NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,          BRIDGE_OPT_TYPE_PORT_OPTION, .extended_bool = TRUE, },
 	};
 	const char *error_message = NULL;
 	int i;
@@ -5124,9 +5191,9 @@ handle_bridge_option (NMSetting *setting,
 		switch (param_spec->value_type) {
 		case G_TYPE_BOOLEAN:
 			if (m[i].extended_bool) {
-				if (!strcasecmp (value, "on") || !strcasecmp (value, "yes") || !strcmp (value, "1"))
+				if (!g_ascii_strcasecmp (value, "on") || !g_ascii_strcasecmp (value, "yes") || !strcmp (value, "1"))
 					v = TRUE;
-				else if (!strcasecmp (value, "off") || !strcasecmp (value, "no"))
+				else if (!g_ascii_strcasecmp (value, "off") || !g_ascii_strcasecmp (value, "no"))
 					v = FALSE;
 				else {
 					error_message = "is not a boolean";
@@ -5155,6 +5222,9 @@ handle_bridge_option (NMSetting *setting,
 				goto warn;
 			}
 			return;
+		case G_TYPE_STRING:
+			nm_g_object_set_property_string (G_OBJECT (setting), m[i].property_name, value, NULL);
+			return;
 		default:
 			nm_assert_not_reached ();
 			continue;
@@ -5259,11 +5329,11 @@ make_bridge_setting (shvarFile *ifcfg,
 
 	value = svGetValueStr (ifcfg, "STP", &value_to_free);
 	if (value) {
-		if (!strcasecmp (value, "on") || !strcasecmp (value, "yes")) {
+		if (!g_ascii_strcasecmp (value, "on") || !g_ascii_strcasecmp (value, "yes")) {
 			g_object_set (s_bridge, NM_SETTING_BRIDGE_STP, TRUE, NULL);
 			stp = TRUE;
 			stp_set = TRUE;
-		} else if (!strcasecmp (value, "off") || !strcasecmp (value, "no")) {
+		} else if (!g_ascii_strcasecmp (value, "off") || !g_ascii_strcasecmp (value, "no")) {
 			g_object_set (s_bridge, NM_SETTING_BRIDGE_STP, FALSE, NULL);
 			stp_set = TRUE;
 		} else
@@ -5774,9 +5844,9 @@ connection_from_file_full (const char *filename,
 
 	devtype = svGetValueStr_cp (main_ifcfg, "DEVICETYPE");
 	if (devtype) {
-		if (!strcasecmp (devtype, TYPE_TEAM))
+		if (!g_ascii_strcasecmp (devtype, TYPE_TEAM))
 			type = g_strdup (TYPE_TEAM);
-		else if (!strcasecmp (devtype, TYPE_TEAM_PORT)) {
+		else if (!g_ascii_strcasecmp (devtype, TYPE_TEAM_PORT)) {
 			gs_free char *device = NULL;
 
 			type = svGetValueStr_cp (main_ifcfg, "TYPE");
@@ -5910,26 +5980,26 @@ connection_from_file_full (const char *filename,
 	}
 
 	if (svGetValueBoolean (main_ifcfg, "BONDING_MASTER", FALSE) &&
-	    strcasecmp (type, TYPE_BOND)) {
+	    g_ascii_strcasecmp (type, TYPE_BOND)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "BONDING_MASTER=yes key only allowed in TYPE=bond connections");
 		return NULL;
 	}
 
 	/* Construct the connection */
-	if (!strcasecmp (type, TYPE_ETHERNET))
+	if (!g_ascii_strcasecmp (type, TYPE_ETHERNET))
 		connection = wired_connection_from_ifcfg (filename, main_ifcfg, error);
-	else if (!strcasecmp (type, TYPE_WIRELESS))
+	else if (!g_ascii_strcasecmp (type, TYPE_WIRELESS))
 		connection = wireless_connection_from_ifcfg (filename, main_ifcfg, error);
-	else if (!strcasecmp (type, TYPE_INFINIBAND))
+	else if (!g_ascii_strcasecmp (type, TYPE_INFINIBAND))
 		connection = infiniband_connection_from_ifcfg (filename, main_ifcfg, error);
-	else if (!strcasecmp (type, TYPE_BOND))
+	else if (!g_ascii_strcasecmp (type, TYPE_BOND))
 		connection = bond_connection_from_ifcfg (filename, main_ifcfg, error);
-	else if (!strcasecmp (type, TYPE_TEAM))
+	else if (!g_ascii_strcasecmp (type, TYPE_TEAM))
 		connection = team_connection_from_ifcfg (filename, main_ifcfg, error);
-	else if (!strcasecmp (type, TYPE_VLAN))
+	else if (!g_ascii_strcasecmp (type, TYPE_VLAN))
 		connection = vlan_connection_from_ifcfg (filename, main_ifcfg, error);
-	else if (!strcasecmp (type, TYPE_BRIDGE))
+	else if (!g_ascii_strcasecmp (type, TYPE_BRIDGE))
 		connection = bridge_connection_from_ifcfg (filename, main_ifcfg, error);
 	else {
 		connection = create_unhandled_connection (filename, main_ifcfg, "unrecognized", out_unhandled);
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index ee389bd9..28f57f50 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -17,9 +17,9 @@
 /*****************************************************************************/
 
 gboolean
-nms_ifcfg_rh_util_parse_unhandled_spec (const char *unhandled_spec,
-                                        const char **out_unmanaged_spec,
-                                        const char **out_unrecognized_spec)
+nms_ifcfg_rh_utils_parse_unhandled_spec (const char *unhandled_spec,
+                                         const char **out_unmanaged_spec,
+                                         const char **out_unrecognized_spec)
 {
 	if (unhandled_spec) {
 		if (NM_STR_HAS_PREFIX (unhandled_spec, "unmanaged:")) {
@@ -72,7 +72,7 @@ check_suffix (const char *base, const char *tag)
 
 	len = strlen (base);
 	tag_len = strlen (tag);
-	if ((len > tag_len) && !strcasecmp (base + len - tag_len, tag))
+	if ((len > tag_len) && !g_ascii_strcasecmp (base + len - tag_len, tag))
 		return TRUE;
 	return FALSE;
 }
@@ -260,27 +260,60 @@ utils_get_route_ifcfg (const char *parent, gboolean should_create)
 gboolean
 utils_has_route_file_new_syntax (const char *filename)
 {
-	char *contents = NULL;
-	gsize len = 0;
-	gboolean ret = FALSE;
-	const char *pattern = "^[[:space:]]*ADDRESS[0-9]+=";
+	gs_free char *contents_data = NULL;
+	gsize len;
 
 	g_return_val_if_fail (filename != NULL, TRUE);
 
-	if (!g_file_get_contents (filename, &contents, &len, NULL))
+	if (!g_file_get_contents (filename, &contents_data, &len, NULL))
 		return TRUE;
 
-	if (len <= 0) {
-		ret = TRUE;
-		goto gone;
-	}
+	return utils_has_route_file_new_syntax_content (contents_data, len);
+}
+
+gboolean
+utils_has_route_file_new_syntax_content (const char *contents,
+                                         gsize len)
+{
+	if (len <= 0)
+		return TRUE;
+
+	while (TRUE) {
+		const char *line = contents;
+		char *eol;
+		gboolean found = FALSE;
+
+		/* matches regex "^[[:space:]]*ADDRESS[0-9]+=" */
+
+		eol = (char *) strchr (contents, '\n');
+		if (eol) {
+			eol[0] = '\0';
+			contents = &eol[1];
+		}
 
-	if (g_regex_match_simple (pattern, contents, G_REGEX_MULTILINE, 0))
-		ret = TRUE;
+		line = nm_str_skip_leading_spaces (line);
+		if (NM_STR_HAS_PREFIX (line, "ADDRESS")) {
+			line += NM_STRLEN ("ADDRESS");
+			if (g_ascii_isdigit (line[0])) {
+				while (g_ascii_isdigit ((++line)[0])) {
+					/* pass */
+				}
+				if (line[0] == '=')
+					found = TRUE;
+			}
+		}
+
+		if (eol) {
+			/* restore the line ending. We don't want to mangle the content from
+			 * POV of the caller. */
+			eol[0] = '\n';
+		}
 
-gone:
-	g_free (contents);
-	return ret;
+		if (found)
+			return TRUE;
+		if (!eol)
+			return FALSE;
+	}
 }
 
 gboolean
@@ -546,31 +579,15 @@ const char *const _nm_ethtool_ifcfg_names[] = {
 	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,       "tx-vlan-stag-hw-insert"),
 };
 
-const NMEthtoolData *
-nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name)
-{
-	static const struct {
-		NMEthtoolID ethtool_id;
-		const char *kernel_name;
-	} kernel_names[] = {
-		{ NM_ETHTOOL_ID_FEATURE_GRO,    "rx-gro" },
-		{ NM_ETHTOOL_ID_FEATURE_GSO,    "tx-generic-segmentation" },
-		{ NM_ETHTOOL_ID_FEATURE_LRO,    "rx-lro" },
-		{ NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter" },
-		{ NM_ETHTOOL_ID_FEATURE_RX,     "rx-checksum" },
-		{ NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing" },
-		{ NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse" },
-		{ NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert" },
-	};
-	guint i;
-
-	for (i = 0; i < G_N_ELEMENTS (_nm_ethtool_ifcfg_names); i++) {
-		if (nm_streq (name, _nm_ethtool_ifcfg_names[i]))
-			return nm_ethtool_data[i];
-	}
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	_get_ethtoolid_by_name,
+	NMEthtoolID,
+	{ nm_assert (name); },
+	{ return NM_ETHTOOL_ID_UNKNOWN; },
 
-	/* Option not found. Note that ethtool utility has built-in features and
-	 * NetworkManager's API follows the naming of these built-in features, whenever
+	/* Map the names from kernel/ethtool/ifcfg to NMEthtoolID. Note that ethtool utility has built-in
+	 * features and NetworkManager's API follows the naming of these built-in features, whenever
 	 * they exist.
 	 * For example, NM's "ethtool.feature-ntuple" corresponds to ethtool utility's "ntuple"
 	 * feature. However the underlying kernel feature is called "rx-ntuple-filter" (as reported
@@ -578,10 +595,430 @@ nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name)
 	 *
 	 * With ethtool utility, whose command line we attempt to parse here, the user can also
 	 * specify the name of the underlying kernel feature directly. So, check whether that is
-	 * the case and if yes, map them to the corresponding NetworkManager's features. */
-	for (i = 0; i < G_N_ELEMENTS (kernel_names); i++) {
-		if (nm_streq (name, kernel_names[i].kernel_name))
-			return nm_ethtool_data[kernel_names[i].ethtool_id];
+	 * the case and if yes, map them to the corresponding NetworkManager's features.
+	 *
+	 * That is why there are duplicate IDs in this list. */
+	{ "esp-hw-offload",               NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD               },
+	{ "esp-tx-csum-hw-offload",       NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD       },
+	{ "fcoe-mtu",                     NM_ETHTOOL_ID_FEATURE_FCOE_MTU                     },
+	{ "gro",                          NM_ETHTOOL_ID_FEATURE_GRO                          },
+	{ "gso",                          NM_ETHTOOL_ID_FEATURE_GSO                          },
+	{ "highdma",                      NM_ETHTOOL_ID_FEATURE_HIGHDMA                      },
+	{ "hw-tc-offload",                NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD                },
+	{ "l2-fwd-offload",               NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD               },
+	{ "loopback",                     NM_ETHTOOL_ID_FEATURE_LOOPBACK                     },
+	{ "lro",                          NM_ETHTOOL_ID_FEATURE_LRO                          },
+	{ "ntuple",                       NM_ETHTOOL_ID_FEATURE_NTUPLE                       },
+	{ "rx",                           NM_ETHTOOL_ID_FEATURE_RX                           },
+	{ "rx-all",                       NM_ETHTOOL_ID_FEATURE_RX_ALL                       },
+	{ "rx-checksum",                  NM_ETHTOOL_ID_FEATURE_RX                           }, // kernel-only name
+	{ "rx-fcs",                       NM_ETHTOOL_ID_FEATURE_RX_FCS                       },
+	{ "rx-gro",                       NM_ETHTOOL_ID_FEATURE_GRO                          }, // kernel-only name
+	{ "rx-gro-hw",                    NM_ETHTOOL_ID_FEATURE_RX_GRO_HW                    },
+	{ "rx-hashing",                   NM_ETHTOOL_ID_FEATURE_RXHASH                       }, // kernel-only name
+	{ "rx-lro",                       NM_ETHTOOL_ID_FEATURE_LRO                          }, // kernel-only name
+	{ "rx-ntuple-filter",             NM_ETHTOOL_ID_FEATURE_NTUPLE                       }, // kernel-only name
+	{ "rx-udp_tunnel-port-offload",   NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD   },
+	{ "rx-vlan-filter",               NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER               },
+	{ "rx-vlan-hw-parse",             NM_ETHTOOL_ID_FEATURE_RXVLAN                       }, // kernel-only name
+	{ "rx-vlan-stag-filter",          NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER          },
+	{ "rx-vlan-stag-hw-parse",        NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE        },
+	{ "rxhash",                       NM_ETHTOOL_ID_FEATURE_RXHASH                       },
+	{ "rxvlan",                       NM_ETHTOOL_ID_FEATURE_RXVLAN                       },
+	{ "sg",                           NM_ETHTOOL_ID_FEATURE_SG                           },
+	{ "tls-hw-record",                NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD                },
+	{ "tls-hw-tx-offload",            NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD            },
+	{ "tso",                          NM_ETHTOOL_ID_FEATURE_TSO                          },
+	{ "tx",                           NM_ETHTOOL_ID_FEATURE_TX                           },
+	{ "tx-checksum-fcoe-crc",         NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC         },
+	{ "tx-checksum-ip-generic",       NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC       },
+	{ "tx-checksum-ipv4",             NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4             },
+	{ "tx-checksum-ipv6",             NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6             },
+	{ "tx-checksum-sctp",             NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP             },
+	{ "tx-esp-segmentation",          NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION          },
+	{ "tx-fcoe-segmentation",         NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION         },
+	{ "tx-generic-segmentation",      NM_ETHTOOL_ID_FEATURE_GSO                          }, // kernel-only name
+	{ "tx-gre-csum-segmentation",     NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION     },
+	{ "tx-gre-segmentation",          NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION          },
+	{ "tx-gso-partial",               NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL               },
+	{ "tx-gso-robust",                NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST                },
+	{ "tx-ipxip4-segmentation",       NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION       },
+	{ "tx-ipxip6-segmentation",       NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION       },
+	{ "tx-nocache-copy",              NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY              },
+	{ "tx-scatter-gather",            NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER            },
+	{ "tx-scatter-gather-fraglist",   NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST   },
+	{ "tx-sctp-segmentation",         NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION         },
+	{ "tx-tcp-ecn-segmentation",      NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION      },
+	{ "tx-tcp-mangleid-segmentation", NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION },
+	{ "tx-tcp-segmentation",          NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION          },
+	{ "tx-tcp6-segmentation",         NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION         },
+	{ "tx-udp-segmentation",          NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION          },
+	{ "tx-udp_tnl-csum-segmentation", NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION },
+	{ "tx-udp_tnl-segmentation",      NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION      },
+	{ "tx-vlan-hw-insert",            NM_ETHTOOL_ID_FEATURE_TXVLAN                       }, // kernel-only name
+	{ "tx-vlan-stag-hw-insert",       NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT       },
+	{ "txvlan",                       NM_ETHTOOL_ID_FEATURE_TXVLAN                       },
+);
+
+const NMEthtoolData *
+nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name)
+{
+	NMEthtoolID id;
+
+	id = _get_ethtoolid_by_name (name);
+	if (id == NM_ETHTOOL_ID_UNKNOWN)
+		return NULL;
+
+	nm_assert (_NM_INT_NOT_NEGATIVE (id));
+	nm_assert (id < G_N_ELEMENTS (nm_ethtool_data));
+	nm_assert (nm_ethtool_data[id]);
+	nm_assert (nm_ethtool_data[id]->id == id);
+	return nm_ethtool_data[id];
+}
+
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_utils_is_numbered_tag_impl (const char *key,
+                                         const char *tag,
+                                         gsize tag_len,
+                                         gint64 *out_idx)
+{
+	gint64 idx;
+
+	nm_assert (key);
+	nm_assert (tag);
+	nm_assert (tag_len == strlen (tag));
+	nm_assert (tag_len > 0);
+
+	if (strncmp (key, tag, tag_len) != 0)
+		return FALSE;
+
+	key += tag_len;
+
+	if (key[0] == '\0') {
+		/* The key has no number suffix. We treat this also as a numbered
+		 * tag, and it is for certain tags like "IPADDR", but not so much
+		 * for others like "ROUTING_RULE_". The caller may want to handle
+		 * this case specially. */
+		NM_SET_OUT (out_idx, -1);
+		return TRUE;
+	}
+
+	if (!NM_STRCHAR_ALL (key, ch, g_ascii_isdigit (ch)))
+		return FALSE;
+
+	idx = _nm_utils_ascii_str_to_int64 (key, 10, 0, G_MAXINT64, -1);
+	if (idx == -1)
+		return FALSE;
+
+	NM_SET_OUT (out_idx, idx);
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+#define _KEY_TYPE(key, flags) { .key_name = ""key"", .key_flags = ((NMS_IFCFG_KEY_TYPE_WELL_KNOWN) | (flags)), }
+
+const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
+	_KEY_TYPE ("ACD_TIMEOUT",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("ADDRESS",                                     NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("ARPING_WAIT",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("AUTH_RETRIES",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("AUTOCONNECT_PRIORITY",                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("AUTOCONNECT_RETRIES",                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("AUTOCONNECT_SLAVES",                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BAND",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BONDING_MASTER",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BONDING_OPTS",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BOOTPROTO",                                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BRIDGE",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BRIDGE_MACADDR",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BRIDGE_PORT_VLANS",                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BRIDGE_UUID",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BRIDGE_VLANS",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BRIDGING_OPTS",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BROWSER_ONLY",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("BSSID",                                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("CHANNEL",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("CIPHER_GROUP",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("CIPHER_PAIRWISE",                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("CONNECTED_MODE",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("CONNECTION_METERED",                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("CTCPROT",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DCB",                                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_FCOE_ADVERTISE,                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_FCOE_ENABLE,                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_FCOE_MODE,                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DCB_APP_FCOE_PRIORITY",                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_FCOE_WILLING,                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_FIP_ADVERTISE,                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_FIP_ENABLE,                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DCB_APP_FIP_PRIORITY",                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_FIP_WILLING,                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_ISCSI_ADVERTISE,                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_ISCSI_ENABLE,                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DCB_APP_ISCSI_PRIORITY",                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_APP_ISCSI_WILLING,                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PFC_ADVERTISE,                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PFC_ENABLE,                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PFC_UP,                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PFC_WILLING,                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_ADVERTISE,                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_ENABLE,                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_ID,                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_PCT,                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_STRICT,                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_UP2TC,                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_UPPCT,                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE (KEY_DCB_PG_WILLING,                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DEFAULTKEY",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DEFROUTE",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DELAY",                                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DEVICE",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DEVICETYPE",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DEVTIMEOUT",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPV6C",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPV6_DUID",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPV6_HOSTNAME",                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPV6_HOSTNAME_FLAGS",                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPV6_IAID",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPV6_SEND_HOSTNAME",                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCP_CLIENT_ID",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCP_FQDN",                                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCP_HOSTNAME",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCP_HOSTNAME_FLAGS",                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCP_IAID",                                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCP_SEND_HOSTNAME",                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPv6_DUID",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DHCPv6_IAID",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("DNS",                                         NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("DOMAIN",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("ESSID",                                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("ETHTOOL_OPTS",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("ETHTOOL_WAKE_ON_LAN",                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("FILS",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("FILTER",                                      NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("GATEWAY",                                     NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("GATEWAYDEV",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("GATEWAY_PING_TIMEOUT",                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("GENERATE_MAC_ADDRESS_MASK",                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("GVRP",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("HWADDR",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("HWADDR_BLACKLIST",                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_ALTSUBJECT_MATCHES",               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_ANON_IDENTITY",                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_AUTH_TIMEOUT",                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_CA_CERT",                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_CA_CERT_PASSWORD",                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_CA_CERT_PASSWORD_FLAGS",           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_CLIENT_CERT",                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_CLIENT_CERT_PASSWORD",             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_CLIENT_CERT_PASSWORD_FLAGS",       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_DOMAIN_MATCH",                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_DOMAIN_SUFFIX_MATCH",              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_EAP_METHODS",                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_FAST_PROVISIONING",                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_IDENTITY",                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_AUTH_METHODS",               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_CA_CERT",                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_CA_CERT_PASSWORD",           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_CA_CERT_PASSWORD_FLAGS",     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_CLIENT_CERT",                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_CLIENT_CERT_PASSWORD",       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_CLIENT_CERT_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_PRIVATE_KEY",                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD",       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_OPTIONAL",                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PAC_FILE",                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PASSWORD",                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PASSWORD_FLAGS",                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PASSWORD_RAW",                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PASSWORD_RAW_FLAGS",               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PEAP_FORCE_NEW_LABEL",             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PEAP_VERSION",                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PHASE1_AUTH_FLAGS",                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES",        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PHASE2_DOMAIN_MATCH",              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH",       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PHASE2_SUBJECT_MATCH",             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PRIVATE_KEY",                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PRIVATE_KEY_PASSWORD",             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS",       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_SUBJECT_MATCH",                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IEEE_8021X_SYSTEM_CA_CERTS",                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPADDR",                                      NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("IPV4_DHCP_TIMEOUT",                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV4_DNS_PRIORITY",                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV4_FAILURE_FATAL",                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV4_ROUTE_METRIC",                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV4_ROUTE_TABLE",                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6ADDR",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6ADDR_SECONDARIES",                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6FORWARDING",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6INIT",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6TUNNELIPV4",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_ADDR_GEN_MODE",                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_AUTOCONF",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_DEFAULTDEV",                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_DEFAULTGW",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_DEFROUTE",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_DHCP_TIMEOUT",                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_DISABLED",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_DNS_PRIORITY",                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_DOMAIN",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_FAILURE_FATAL",                          NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_PEERDNS",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_PEERROUTES",                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_PRIVACY",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_PRIVACY_PREFER_PUBLIC_IP",               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_RA_TIMEOUT",                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_RES_OPTIONS",                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_ROUTE_METRIC",                           NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_ROUTE_TABLE",                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("IPV6_TOKEN",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("KEY",                                         NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("KEY_MGMT",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("KEY_PASSPHRASE",                              NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("KEY_TYPE",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("LLDP",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("LLMNR",                                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MACADDR",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MAC_ADDRESS_RANDOMIZATION",                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MASTER",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MASTER_UUID",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MATCH_INTERFACE_NAME",                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MDNS",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("METRIC",                                      NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("MODE",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MTU",                                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MULTI_CONNECT",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("MVRP",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("NAME",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("NETMASK",                                     NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("NETTYPE",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("NM_CONTROLLED",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN | NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY ),
+	_KEY_TYPE ("NM_USER_",                                    NMS_IFCFG_KEY_TYPE_IS_PREFIX ),
+	_KEY_TYPE ("ONBOOT",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("OPTIONS",                                     NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("OVS_PORT",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("OVS_PORT_UUID",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PAC_SCRIPT",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PAC_URL",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PEERDNS",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PEERROUTES",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PHYSDEV",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PKEY",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PMF",                                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PORTNAME",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("POWERSAVE",                                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("PREFIX",                                      NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("PROXY_METHOD",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("QDISC",                                       NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("REORDER_HDR",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("RES_OPTIONS",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("ROUTING_RULE6_",                              NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("ROUTING_RULE_",                               NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("SEARCH",                                      NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("SECONDARY_UUIDS",                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("SECURITYMODE",                                NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("SLAVE",                                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("SRIOV_AUTOPROBE_DRIVERS",                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("SRIOV_TOTAL_VFS",                             NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("SRIOV_VF",                                    NMS_IFCFG_KEY_TYPE_IS_NUMBERED ),
+	_KEY_TYPE ("SSID_HIDDEN",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("STABLE_ID",                                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("STP",                                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("SUBCHANNELS",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("TEAM_CONFIG",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("TEAM_MASTER",                                 NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("TEAM_MASTER_UUID",                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("TEAM_PORT_CONFIG",                            NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("TYPE",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("USERS",                                       NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("UUID",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("VLAN",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("VLAN_EGRESS_PRIORITY_MAP",                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("VLAN_FLAGS",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("VLAN_ID",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("VLAN_INGRESS_PRIORITY_MAP",                   NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("VRF",                                         NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("VRF_UUID",                                    NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("WEP_KEY_FLAGS",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("WPA_ALLOW_WPA",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("WPA_ALLOW_WPA2",                              NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("WPA_PSK",                                     NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("WPA_PSK_FLAGS",                               NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("WPS_METHOD",                                  NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+	_KEY_TYPE ("ZONE",                                        NMS_IFCFG_KEY_TYPE_IS_PLAIN ),
+};
+
+const NMSIfcfgKeyTypeInfo *
+nms_ifcfg_well_known_key_find_info (const char *key, gssize *out_idx)
+{
+	gssize idx;
+
+	G_STATIC_ASSERT (G_STRUCT_OFFSET (NMSIfcfgKeyTypeInfo, key_name) == 0);
+
+	idx = nm_utils_array_find_binary_search (nms_ifcfg_well_known_keys,
+	                                         sizeof (nms_ifcfg_well_known_keys[0]),
+	                                         G_N_ELEMENTS (nms_ifcfg_well_known_keys),
+	                                         &key,
+	                                         nm_strcmp_p_with_data,
+	                                         NULL);
+	NM_SET_OUT (out_idx, idx);
+	if (idx < 0)
+		return NULL;
+	return &nms_ifcfg_well_known_keys[idx];
+}
+
+const NMSIfcfgKeyTypeInfo *
+nms_ifcfg_rh_utils_is_well_known_key (const char *key)
+{
+	const NMSIfcfgKeyTypeInfo *ti;
+	gssize idx;
+
+	nm_assert (key);
+
+	ti = nms_ifcfg_well_known_key_find_info (key, &idx);
+
+	if (ti) {
+		if (NM_FLAGS_ANY (ti->key_flags,   NMS_IFCFG_KEY_TYPE_IS_PLAIN
+		                                 | NMS_IFCFG_KEY_TYPE_IS_NUMBERED)) {
+			/* These tags are valid on full match.
+			 *
+			 * Note that numbered tags we also treat as valid if they have no
+			 * suffix. That is correct for "IPADDR", but less so for "ROUTING_RULE_". */
+			return ti;
+		}
+		nm_assert (NM_FLAGS_HAS (ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_PREFIX));
+		/* a prefix tag needs some extra suffix afterwards to be valid. */
+		return NULL;
+	}
+
+	/* Not found. Maybe it's a numbered/prefixed key? With idx we got the index where
+	 * we should insert the key. Since the numbered/prefixed keys share a prefix, we can
+	 * find the possible prefix at the index before the insert position. */
+	idx = ~idx;
+	if (idx == 0)
+		return NULL;
+
+	ti = &nms_ifcfg_well_known_keys[idx - 1];
+
+	if (NM_FLAGS_HAS (ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_NUMBERED)) {
+		if (nms_ifcfg_rh_utils_is_numbered_tag (key, ti->key_name, NULL))
+			return ti;
+		return NULL;
+	}
+
+	if (NM_FLAGS_HAS (ti->key_flags, NMS_IFCFG_KEY_TYPE_IS_PREFIX)) {
+		gsize l = strlen (ti->key_name);
+
+		if (   strncmp (key, ti->key_name, l) == 0
+		    && key[l] != '\0')
+			return ti;
+		return NULL;
 	}
 
 	return NULL;
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 035146ff..8941fc19 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -11,9 +11,48 @@
 
 #include "shvar.h"
 
-gboolean nms_ifcfg_rh_util_parse_unhandled_spec (const char *unhandled_spec,
-                                                 const char **out_unmanaged_spec,
-                                                 const char **out_unrecognized_spec);
+/*****************************************************************************/
+
+typedef enum {
+	NMS_IFCFG_KEY_TYPE_UNKNOWN         = 0,
+	NMS_IFCFG_KEY_TYPE_WELL_KNOWN      = (1u << 0),
+
+	NMS_IFCFG_KEY_TYPE_IS_PLAIN        = (1u << 1),
+	NMS_IFCFG_KEY_TYPE_IS_NUMBERED     = (1u << 2),
+	NMS_IFCFG_KEY_TYPE_IS_PREFIX       = (1u << 3),
+
+	/* by default, well knowns keys that are not explicitly set
+	 * by the writer (the unvisited, dirty ones) are removed.
+	 * With this flag, such keys are kept if they are present. */
+	NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY = (1u << 4),
+
+} NMSIfcfgKeyTypeFlags;
+
+typedef struct {
+	const char *key_name;
+	NMSIfcfgKeyTypeFlags key_flags;
+} NMSIfcfgKeyTypeInfo;
+
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[231];
+
+const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info (const char *key, gssize *out_idx);
+
+static inline NMSIfcfgKeyTypeFlags
+nms_ifcfg_well_known_key_find_info_flags (const char *key)
+{
+	const NMSIfcfgKeyTypeInfo *ti;
+
+	ti = nms_ifcfg_well_known_key_find_info (key, NULL);
+	if (!ti)
+		return NMS_IFCFG_KEY_TYPE_UNKNOWN;
+	return ti->key_flags;
+}
+
+/*****************************************************************************/
+
+gboolean nms_ifcfg_rh_utils_parse_unhandled_spec (const char *unhandled_spec,
+                                                  const char **out_unmanaged_spec,
+                                                  const char **out_unrecognized_spec);
 
 #define NM_IFCFG_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
 #define NM_IFCFG_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
@@ -37,6 +76,8 @@ shvarFile *utils_get_keys_ifcfg (const char *parent, gboolean should_create);
 shvarFile *utils_get_route_ifcfg (const char *parent, gboolean should_create);
 
 gboolean utils_has_route_file_new_syntax (const char *filename);
+gboolean utils_has_route_file_new_syntax_content (const char *contents,
+                                                  gsize len);
 gboolean utils_has_complex_routes (const char *filename, int addr_family);
 
 gboolean utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg);
@@ -51,6 +92,12 @@ _nms_ifcfg_rh_utils_numbered_tag (char *buf, gsize buf_len, const char *tag_name
 {
 	gsize l;
 
+#if NM_MORE_ASSERTS > 5
+	nm_assert (NM_FLAGS_ALL (nms_ifcfg_well_known_key_find_info_flags (tag_name),
+	                           NMS_IFCFG_KEY_TYPE_WELL_KNOWN
+	                         | NMS_IFCFG_KEY_TYPE_IS_NUMBERED));
+#endif
+
 	l = g_strlcpy (buf, tag_name, buf_len);
 	nm_assert (l < buf_len);
 	if (which != -1) {
@@ -70,6 +117,28 @@ _nms_ifcfg_rh_utils_numbered_tag (char *buf, gsize buf_len, const char *tag_name
 		_nms_ifcfg_rh_utils_numbered_tag (buf, sizeof (buf), ""tag_name"", (which)); \
 	})
 
+gboolean nms_ifcfg_rh_utils_is_numbered_tag_impl (const char *key,
+                                                  const char *tag,
+                                                  gsize tag_len,
+                                                  gint64 *out_idx);
+
+static inline gboolean
+nms_ifcfg_rh_utils_is_numbered_tag (const char *key,
+                                    const char *tag,
+                                    gint64 *out_idx)
+{
+	nm_assert (tag);
+
+	return nms_ifcfg_rh_utils_is_numbered_tag_impl (key, tag, strlen (tag), out_idx);
+}
+
+#define NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG(key, tag, out_idx) \
+	nms_ifcfg_rh_utils_is_numbered_tag_impl (key, tag, NM_STRLEN (tag), out_idx)
+
+/*****************************************************************************/
+
+const NMSIfcfgKeyTypeInfo *nms_ifcfg_rh_utils_is_well_known_key (const char *key);
+
 /*****************************************************************************/
 
 extern const char *const _nm_ethtool_ifcfg_names[_NM_ETHTOOL_ID_FEATURE_NUM];
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 90a1a2b8..a0797fdc 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -66,10 +66,8 @@ save_secret_flags (shvarFile *ifcfg,
 	g_return_if_fail (ifcfg != NULL);
 	g_return_if_fail (key != NULL);
 
-	if (flags == NM_SETTING_SECRET_FLAG_NONE) {
-		svUnsetValue (ifcfg, key);
+	if (flags == NM_SETTING_SECRET_FLAG_NONE)
 		return;
-	}
 
 	/* Convert flags bitfield into string representation */
 	str = g_string_sized_new (20);
@@ -100,9 +98,6 @@ set_secret (shvarFile *ifcfg,
             const char *flags_key,
             NMSettingSecretFlags flags)
 {
-	/* Clear the secret from the ifcfg and the associated "keys" file */
-	svUnsetValue (ifcfg, key);
-
 	/* Save secret flags */
 	save_secret_flags (ifcfg, flags_key, flags);
 
@@ -131,9 +126,6 @@ write_secrets (shvarFile *ifcfg,
 		return FALSE;
 	}
 
-	/* we purge all existing secrets. */
-	svUnsetAll (keyfile, SV_KEY_TYPE_ANY);
-
 	secrets_keys = nm_utils_strdict_get_keys (secrets, TRUE, &secrets_keys_n);
 	for (i = 0; i < secrets_keys_n; i++) {
 		const char *k = secrets_keys[i];
@@ -147,7 +139,7 @@ write_secrets (shvarFile *ifcfg,
 
 	if (!any_secrets)
 		(void) unlink (svFileGetName (keyfile));
-	else if (!svWriteFile (keyfile, 0600, &local)) {
+	else if (!svWriteFileWithoutDirtyWellknown (keyfile, 0600, &local)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "Failure to write secrets to '%s': %s", svFileGetName (keyfile), local->message);
 		return FALSE;
@@ -372,12 +364,8 @@ write_8021x_setting (NMConnection *connection,
 	int vint;
 
 	s_8021x = nm_connection_get_setting_802_1x (connection);
-	if (!s_8021x) {
-		/* If wired, clear KEY_MGMT */
-		if (wired)
-			svUnsetValue (ifcfg, "KEY_MGMT");
+	if (!s_8021x)
 		return TRUE;
-	}
 
 	/* If wired, write KEY_MGMT */
 	if (wired)
@@ -424,10 +412,7 @@ write_8021x_setting (NMConnection *connection,
 	                             nm_setting_802_1x_get_system_ca_certs (s_8021x));
 
 	value = nm_setting_802_1x_get_phase1_peapver (s_8021x);
-	if (NM_IN_STRSET (value, "0", "1"))
-		svSetValueStr (ifcfg, "IEEE_8021X_PEAP_VERSION", value);
-	else
-		svUnsetValue (ifcfg, "IEEE_8021X_PEAP_VERSION");
+	svSetValueStr (ifcfg, "IEEE_8021X_PEAP_VERSION", value);
 
 	svSetValueBoolean_cond_true (ifcfg,
 	                             "IEEE_8021X_PEAP_FORCE_NEW_LABEL",
@@ -452,7 +437,6 @@ write_8021x_setting (NMConnection *connection,
 	svSetValueStr (ifcfg, "IEEE_8021X_FAST_PROVISIONING", value);
 
 	/* Phase2 auth methods */
-	svUnsetValue (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS");
 	phase2_auth = g_string_new (NULL);
 
 	value = nm_setting_802_1x_get_phase2_auth (s_8021x);
@@ -473,9 +457,7 @@ write_8021x_setting (NMConnection *connection,
 	}
 
 	auth_flags = nm_setting_802_1x_get_phase1_auth_flags (s_8021x);
-	if (auth_flags == NM_SETTING_802_1X_AUTH_FLAGS_NONE) {
-		svUnsetValue (ifcfg, "IEEE_8021X_PHASE1_AUTH_FLAGS");
-	} else {
+	if (auth_flags != NM_SETTING_802_1X_AUTH_FLAGS_NONE) {
 		svSetValueEnum (ifcfg, "IEEE_8021X_PHASE1_AUTH_FLAGS",
 		                nm_setting_802_1x_auth_flags_get_type(),
 		                auth_flags);
@@ -492,7 +474,6 @@ write_8021x_setting (NMConnection *connection,
 	svSetValueStr (ifcfg, "IEEE_8021X_PHASE2_SUBJECT_MATCH",
 	               nm_setting_802_1x_get_phase2_subject_match (s_8021x));
 
-	svUnsetValue (ifcfg, "IEEE_8021X_ALTSUBJECT_MATCHES");
 	str = g_string_new (NULL);
 	num = nm_setting_802_1x_get_num_altsubject_matches (s_8021x);
 	for (i = 0; i < num; i++) {
@@ -505,7 +486,6 @@ write_8021x_setting (NMConnection *connection,
 		svSetValueStr (ifcfg, "IEEE_8021X_ALTSUBJECT_MATCHES", str->str);
 	g_string_free (str, TRUE);
 
-	svUnsetValue (ifcfg, "IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES");
 	str = g_string_new (NULL);
 	num = nm_setting_802_1x_get_num_phase2_altsubject_matches (s_8021x);
 	for (i = 0; i < num; i++) {
@@ -523,6 +503,11 @@ write_8021x_setting (NMConnection *connection,
 	svSetValueStr (ifcfg, "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH",
 	               nm_setting_802_1x_get_phase2_domain_suffix_match (s_8021x));
 
+	svSetValueStr (ifcfg, "IEEE_8021X_DOMAIN_MATCH",
+	               nm_setting_802_1x_get_domain_match (s_8021x));
+	svSetValueStr (ifcfg, "IEEE_8021X_PHASE2_DOMAIN_MATCH",
+	               nm_setting_802_1x_get_phase2_domain_match (s_8021x));
+
 	vint = nm_setting_802_1x_get_auth_timeout (s_8021x);
 	svSetValueInt64_cond (ifcfg, "IEEE_8021X_AUTH_TIMEOUT", vint > 0, vint);
 
@@ -565,14 +550,11 @@ write_wireless_security_setting (NMConnection *connection,
 	}
 
 	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
-	g_assert (key_mgmt);
+	nm_assert (key_mgmt);
 
 	auth_alg = nm_setting_wireless_security_get_auth_alg (s_wsec);
 
-	svUnsetValue (ifcfg, "DEFAULTKEY");
-
 	if (!strcmp (key_mgmt, "none")) {
-		svUnsetValue (ifcfg, "KEY_MGMT");
 		wep = TRUE;
 		*no_8021x = TRUE;
 	} else if (!strcmp (key_mgmt, "wpa-psk")) {
@@ -583,6 +565,10 @@ write_wireless_security_setting (NMConnection *connection,
 		svSetValueStr (ifcfg, "KEY_MGMT", "SAE");
 		wpa = TRUE;
 		*no_8021x = TRUE;
+	} else if (!strcmp (key_mgmt, "owe")) {
+		svSetValueStr (ifcfg, "KEY_MGMT", "OWE");
+		wpa = FALSE;
+		*no_8021x = TRUE;
 	} else if (!strcmp (key_mgmt, "ieee8021x")) {
 		svSetValueStr (ifcfg, "KEY_MGMT", "IEEE8021X");
 		dynamic_wep = TRUE;
@@ -591,7 +577,6 @@ write_wireless_security_setting (NMConnection *connection,
 		wpa = TRUE;
 	}
 
-	svUnsetValue (ifcfg, "SECURITYMODE");
 	if (auth_alg) {
 		if (!strcmp (auth_alg, "shared"))
 			svSetValueStr (ifcfg, "SECURITYMODE", "restricted");
@@ -611,11 +596,8 @@ write_wireless_security_setting (NMConnection *connection,
 		}
 	}
 
-	/* WPS */
 	wps_method = nm_setting_wireless_security_get_wps_method (s_wsec);
-	if (wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
-		svUnsetValue (ifcfg, "WPS_METHOD");
-	else
+	if (wps_method != NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
 		svSetValueEnum (ifcfg, "WPS_METHOD", nm_setting_wireless_security_wps_method_get_type (), wps_method);
 
 	/* WEP keys */
@@ -701,8 +683,6 @@ write_wireless_security_setting (NMConnection *connection,
 	}
 
 	/* WPA protos */
-	svUnsetValue (ifcfg, "WPA_ALLOW_WPA");
-	svUnsetValue (ifcfg, "WPA_ALLOW_WPA2");
 	num = nm_setting_wireless_security_get_num_protos (s_wsec);
 	for (i = 0; i < num; i++) {
 		proto = nm_setting_wireless_security_get_proto (s_wsec, i);
@@ -713,7 +693,6 @@ write_wireless_security_setting (NMConnection *connection,
 	}
 
 	/* WPA Pairwise ciphers */
-	svUnsetValue (ifcfg, "CIPHER_PAIRWISE");
 	str = g_string_new (NULL);
 	num = nm_setting_wireless_security_get_num_pairwise (s_wsec);
 	for (i = 0; i < num; i++) {
@@ -735,7 +714,6 @@ write_wireless_security_setting (NMConnection *connection,
 	g_string_free (str, TRUE);
 
 	/* WPA Group ciphers */
-	svUnsetValue (ifcfg, "CIPHER_GROUP");
 	str = g_string_new (NULL);
 	num = nm_setting_wireless_security_get_num_groups (s_wsec);
 	for (i = 0; i < num; i++) {
@@ -760,16 +738,12 @@ write_wireless_security_setting (NMConnection *connection,
 	            "WPA_PSK_FLAGS",
 	            wpa ? nm_setting_wireless_security_get_psk_flags (s_wsec) : NM_SETTING_SECRET_FLAG_NONE);
 
-	if (nm_setting_wireless_security_get_pmf (s_wsec) == NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT)
-		svUnsetValue (ifcfg, "PMF");
-	else {
+	if (nm_setting_wireless_security_get_pmf (s_wsec) != NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) {
 		svSetValueEnum (ifcfg, "PMF", nm_setting_wireless_security_pmf_get_type (),
 		                nm_setting_wireless_security_get_pmf (s_wsec));
 	}
 
-	if (nm_setting_wireless_security_get_fils (s_wsec) == NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT)
-		svUnsetValue (ifcfg, "FILS");
-	else {
+	if (nm_setting_wireless_security_get_fils (s_wsec) != NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) {
 		svSetValueEnum (ifcfg, "FILS", nm_setting_wireless_security_fils_get_type (),
 		                nm_setting_wireless_security_get_fils (s_wsec));
 	}
@@ -810,14 +784,12 @@ write_wireless_setting (NMConnection *connection,
 	svSetValueStr (ifcfg, "GENERATE_MAC_ADDRESS_MASK",
 	               nm_setting_wireless_get_generate_mac_address_mask (s_wireless));
 
-	svUnsetValue (ifcfg, "HWADDR_BLACKLIST");
 	macaddr_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless);
 	if (macaddr_blacklist[0]) {
-		char *blacklist_str;
+		gs_free char *blacklist_str = NULL;
 
 		blacklist_str = g_strjoinv (" ", (char **) macaddr_blacklist);
 		svSetValueStr (ifcfg, "HWADDR_BLACKLIST", blacklist_str);
-		g_free (blacklist_str);
 	}
 
 	mtu = nm_setting_wireless_get_mtu (s_wireless);
@@ -879,9 +851,9 @@ write_wireless_setting (NMConnection *connection,
 	}
 
 	mode = nm_setting_wireless_get_mode (s_wireless);
-	if (!mode)
-		svUnsetValue(ifcfg, "MODE");
-	else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_INFRA))
+	if (!mode) {
+		/* pass */
+	} else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_INFRA))
 		svSetValueStr (ifcfg, "MODE", "Managed");
 	else if (nm_streq (mode, NM_SETTING_WIRELESS_MODE_ADHOC)) {
 		svSetValueStr (ifcfg, "MODE", "Ad-Hoc");
@@ -895,8 +867,6 @@ write_wireless_setting (NMConnection *connection,
 		return FALSE;
 	}
 
-	svUnsetValue (ifcfg, "CHANNEL");
-	svUnsetValue (ifcfg, "BAND");
 	chan = nm_setting_wireless_get_channel (s_wireless);
 	if (chan) {
 		svSetValueInt64 (ifcfg, "CHANNEL", chan);
@@ -912,18 +882,13 @@ write_wireless_setting (NMConnection *connection,
 	 * otherwise there's no way to detect WEP vs. open when WEP keys aren't
 	 * saved.
 	 */
-	svUnsetValue (ifcfg, "DEFAULTKEY");
-	svUnsetValue (ifcfg, "SECURITYMODE");
 
 	if (nm_connection_get_setting_wireless_security (connection)) {
 		if (!write_wireless_security_setting (connection, ifcfg, secrets, adhoc, no_8021x, error))
 			return FALSE;
 	} else {
 		/* Clear out wifi security keys */
-		svUnsetValue (ifcfg, "KEY_MGMT");
-		svUnsetValue (ifcfg, "IEEE_8021X_IDENTITY");
 		set_secret (ifcfg, secrets, "IEEE_8021X_PASSWORD", NULL, "IEEE_8021X_PASSWORD_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
-		svUnsetValue (ifcfg, "SECURITYMODE");
 
 		/* Clear existing keys */
 		set_secret (ifcfg, secrets, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
@@ -937,11 +902,6 @@ write_wireless_setting (NMConnection *connection,
 			set_secret (ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 		}
 
-		svUnsetValue (ifcfg, "DEFAULTKEY");
-		svUnsetValue (ifcfg, "WPA_ALLOW_WPA");
-		svUnsetValue (ifcfg, "WPA_ALLOW_WPA2");
-		svUnsetValue (ifcfg, "CIPHER_PAIRWISE");
-		svUnsetValue (ifcfg, "CIPHER_GROUP");
 		set_secret (ifcfg, secrets, "WPA_PSK", NULL, "WPA_PSK_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 	}
 
@@ -959,7 +919,6 @@ write_wireless_setting (NMConnection *connection,
 		break;
 	default:
 	case NM_SETTING_WIRELESS_POWERSAVE_DEFAULT:
-		svUnsetValue (ifcfg, "POWERSAVE");
 		break;
 	}
 
@@ -1050,8 +1009,7 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 
 		blacklist_str = g_strjoinv (" ", (char **) macaddr_blacklist);
 		svSetValueStr (ifcfg, "HWADDR_BLACKLIST", blacklist_str);
-	} else
-		svUnsetValue (ifcfg, "HWADDR_BLACKLIST");
+	}
 
 	mtu = nm_setting_wired_get_mtu (s_wired);
 	svSetValueInt64_cond (ifcfg, "MTU", mtu != 0, mtu);
@@ -1085,7 +1043,6 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	svSetValueStr (ifcfg, "CTCPROT",
 	               nm_setting_wired_get_s390_option_by_key (s_wired, "ctcprot"));
 
-	svUnsetValue (ifcfg, "OPTIONS");
 	num_opts = nm_setting_wired_get_num_s390_options (s_wired);
 	if (s390_subchannels && num_opts) {
 		nm_auto_free_gstring GString *tmp = NULL;
@@ -1137,11 +1094,9 @@ write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **erro
 	s_wired = nm_connection_get_setting_wired (connection);
 	s_ethtool = NM_SETTING_ETHTOOL (nm_connection_get_setting (connection, NM_TYPE_SETTING_ETHTOOL));
 
-	if (!s_wired && !s_ethtool) {
-		svUnsetValue (ifcfg, "ETHTOOL_WAKE_ON_LAN");
-		svUnsetValue (ifcfg, "ETHTOOL_OPTS");
+	if (   !s_wired
+	    && !s_ethtool)
 		return TRUE;
-	}
 
 	if (s_wired) {
 		auto_negotiate = nm_setting_wired_get_auto_negotiate (s_wired);
@@ -1200,8 +1155,7 @@ write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **erro
 			if (wol_password && NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
 				g_string_append_printf (str, "s sopass %s", wol_password);
 		}
-	} else
-		svUnsetValue (ifcfg, "ETHTOOL_WAKE_ON_LAN");
+	}
 
 	if (s_ethtool) {
 		NMEthtoolID ethtool_id;
@@ -1246,8 +1200,7 @@ write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **erro
 	if (str) {
 		svSetValueStr (ifcfg, "ETHTOOL_OPTS", str->str);
 		g_string_free (str, TRUE);
-	} else
-		svUnsetValue (ifcfg, "ETHTOOL_OPTS");
+	}
 
 	return TRUE;
 }
@@ -1346,10 +1299,6 @@ write_vlan_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
 	svSetValueStr (ifcfg, "VLAN_EGRESS_PRIORITY_MAP", tmp);
 	g_free (tmp);
 
-	svUnsetValue (ifcfg, "HWADDR");
-	svUnsetValue (ifcfg, "MACADDR");
-	svUnsetValue (ifcfg, "MTU");
-
 	*wired = write_wired_for_virtual (connection, ifcfg);
 
 	return TRUE;
@@ -1368,8 +1317,6 @@ write_bond_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
 		return FALSE;
 	}
 
-	svUnsetValue (ifcfg, "BONDING_OPTS");
-
 	num_opts = nm_setting_bond_get_num_options (s_bond);
 	if (num_opts) {
 		nm_auto_free_gstring GString *str = NULL;
@@ -1463,10 +1410,9 @@ write_bridge_vlans (NMSetting *setting,
 
 	g_object_get (setting, property_name, &vlans, NULL);
 
-	if (!vlans || !vlans->len) {
-		svUnsetValue (ifcfg, key);
+	if (   !vlans
+	    || !vlans->len)
 		return TRUE;
-	}
 
 	string = g_string_new ("");
 	for (i = 0; i < vlans->len; i++) {
@@ -1492,8 +1438,8 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 	NMSettingBridge *s_bridge;
 	guint32 i;
 	gboolean b;
+	const char *s;
 	GString *opts;
-	const char *mac;
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
 	if (!s_bridge) {
@@ -1502,12 +1448,10 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		return FALSE;
 	}
 
-	svUnsetValue (ifcfg, "BRIDGING_OPTS");
 	svSetValueBoolean (ifcfg, "STP", FALSE);
-	svUnsetValue (ifcfg, "DELAY");
 
-	mac = nm_setting_bridge_get_mac_address (s_bridge);
-	svSetValueStr (ifcfg, "BRIDGE_MACADDR", mac);
+	s = nm_setting_bridge_get_mac_address (s_bridge);
+	svSetValueStr (ifcfg, "BRIDGE_MACADDR", s);
 
 	/* Bridge options */
 	opts = g_string_sized_new (32);
@@ -1543,6 +1487,13 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "ageing_time=%u", i);
 	}
 
+	s = nm_setting_bridge_get_group_address (s_bridge);
+	if (s) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "group_address=%s", s);
+	}
+
 	i = nm_setting_bridge_get_group_forward_mask (s_bridge);
 	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) {
 		if (opts->len)
@@ -1550,6 +1501,20 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "group_fwd_mask=%u", i);
 	}
 
+	b = nm_setting_bridge_get_multicast_querier (s_bridge);
+	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_QUERIER)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_querier=%u", (guint) b);
+	}
+
+	b = nm_setting_bridge_get_multicast_query_use_ifaddr (s_bridge);
+	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_query_use_ifaddr=%u", (guint) b);
+	}
+
 	b = nm_setting_bridge_get_multicast_snooping (s_bridge);
 	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) {
 		if (opts->len)
@@ -1557,6 +1522,13 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "multicast_snooping=%u", (guint32) b);
 	}
 
+	s = nm_setting_bridge_get_multicast_router (s_bridge);
+	if (s) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "multicast_router=%s", s);
+	}
+
 	b = nm_setting_bridge_get_vlan_filtering (s_bridge);
 	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_FILTERING)) {
 		if (opts->len)
@@ -1571,6 +1543,20 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wire
 		g_string_append_printf (opts, "default_pvid=%u", i);
 	}
 
+	s = nm_setting_bridge_get_vlan_protocol (s_bridge);
+	if (s) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "vlan_protocol=%s", s);
+	}
+
+	b = nm_setting_bridge_get_vlan_stats_enabled (s_bridge);
+	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_VLAN_STATS_ENABLED)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "vlan_stats_enabled=%u", (guint) b);
+	}
+
 	if (opts->len)
 		svSetValueStr (ifcfg, "BRIDGING_OPTS", opts->str);
 	g_string_free (opts, TRUE);
@@ -1600,8 +1586,6 @@ write_bridge_port_setting (NMConnection *connection, shvarFile *ifcfg, GError **
 	if (!s_port)
 		return TRUE;
 
-	svUnsetValue (ifcfg, "BRIDGING_OPTS");
-
 	/* Bridge options */
 	string = g_string_sized_new (32);
 
@@ -1677,11 +1661,11 @@ write_dcb_app (shvarFile *ifcfg,
 
 	write_dcb_flags (ifcfg, tag, flags);
 
-	nm_sprintf_buf (prop, "DCB_%s_PRIORITY", tag);
-	if ((flags & NM_SETTING_DCB_FLAG_ENABLE) && (priority >= 0))
+	if (   (flags & NM_SETTING_DCB_FLAG_ENABLE)
+	    && (priority >= 0)) {
+		nm_sprintf_buf (prop, "DCB_%s_PRIORITY", tag);
 		svSetValueInt64 (ifcfg, prop, priority);
-	else
-		svUnsetValue (ifcfg, prop);
+	}
 }
 
 typedef gboolean (*DcbGetBoolFunc) (NMSettingDcb *, guint);
@@ -1696,10 +1680,8 @@ write_dcb_bool_array (shvarFile *ifcfg,
 	char str[9];
 	guint i;
 
-	if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) {
-		svUnsetValue (ifcfg, key);
+	if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
 		return;
-	}
 
 	str[8] = 0;
 	for (i = 0; i < 8; i++)
@@ -1719,10 +1701,8 @@ write_dcb_uint_array (shvarFile *ifcfg,
 	char str[9];
 	guint i, num;
 
-	if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) {
-		svUnsetValue (ifcfg, key);
+	if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
 		return;
-	}
 
 	str[8] = 0;
 	for (i = 0; i < 8; i++) {
@@ -1747,10 +1727,8 @@ write_dcb_percent_array (shvarFile *ifcfg,
 	GString *str;
 	guint i;
 
-	if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) {
-		svUnsetValue (ifcfg, key);
+	if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
 		return;
-	}
 
 	str = g_string_sized_new (30);
 	for (i = 0; i < 8; i++) {
@@ -1769,38 +1747,8 @@ write_dcb_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	NMSettingDcbFlags flags;
 
 	s_dcb = nm_connection_get_setting_dcb (connection);
-	if (!s_dcb) {
-		static const char *clear_keys[] = {
-		    "DCB",
-		    KEY_DCB_APP_FCOE_ENABLE,
-		    KEY_DCB_APP_FCOE_ADVERTISE,
-		    KEY_DCB_APP_FCOE_WILLING,
-		    KEY_DCB_APP_FCOE_MODE,
-		    KEY_DCB_APP_ISCSI_ENABLE,
-		    KEY_DCB_APP_ISCSI_ADVERTISE,
-		    KEY_DCB_APP_ISCSI_WILLING,
-		    KEY_DCB_APP_FIP_ENABLE,
-		    KEY_DCB_APP_FIP_ADVERTISE,
-		    KEY_DCB_APP_FIP_WILLING,
-		    KEY_DCB_PFC_ENABLE,
-		    KEY_DCB_PFC_ADVERTISE,
-		    KEY_DCB_PFC_WILLING,
-		    KEY_DCB_PFC_UP,
-		    KEY_DCB_PG_ENABLE,
-		    KEY_DCB_PG_ADVERTISE,
-		    KEY_DCB_PG_WILLING,
-		    KEY_DCB_PG_ID,
-		    KEY_DCB_PG_PCT,
-		    KEY_DCB_PG_UPPCT,
-		    KEY_DCB_PG_STRICT,
-		    KEY_DCB_PG_UP2TC,
-		    NULL };
-		const char **iter;
-
-		for (iter = clear_keys; *iter; iter++)
-			svUnsetValue (ifcfg, *iter);
+	if (!s_dcb)
 		return TRUE;
-	}
 
 	svSetValueStr (ifcfg, "DCB", "yes");
 
@@ -1809,8 +1757,6 @@ write_dcb_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	               nm_setting_dcb_get_app_fcoe_priority (s_dcb));
 	if (nm_setting_dcb_get_app_fcoe_flags (s_dcb) & NM_SETTING_DCB_FLAG_ENABLE)
 		svSetValueStr (ifcfg, KEY_DCB_APP_FCOE_MODE, nm_setting_dcb_get_app_fcoe_mode (s_dcb));
-	else
-		svUnsetValue (ifcfg, KEY_DCB_APP_FCOE_MODE);
 
 	write_dcb_app (ifcfg, "APP_ISCSI",
 	               nm_setting_dcb_get_app_iscsi_flags (s_dcb),
@@ -1877,9 +1823,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 		svSetValueStr (ifcfg, "AUTOCONNECT_SLAVES",
 		               autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES ? "yes" :
 		               autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO ? "no" : NULL);
-	} else
-		svUnsetValue (ifcfg, "AUTOCONNECT_SLAVES");
-
+	}
 	switch (nm_setting_connection_get_lldp (s_con)) {
 	case NM_SETTING_CONNECTION_LLDP_ENABLE_RX:
 		tmp = "rx";
@@ -1893,7 +1837,6 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	svSetValueStr (ifcfg, "LLDP", tmp);
 
 	/* Permissions */
-	svUnsetValue (ifcfg, "USERS");
 	n = nm_setting_connection_get_num_permissions (s_con);
 	if (n > 0) {
 		str = g_string_sized_new (n * 20);
@@ -1923,8 +1866,6 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	svSetValueStr (ifcfg, "BRIDGE", NULL);
 	svSetValueStr (ifcfg, "TEAM_MASTER_UUID", NULL);
 	svSetValueStr (ifcfg, "TEAM_MASTER", NULL);
-	svSetValueStr (ifcfg, "OVS_PORT_UUID", NULL);
-	svSetValueStr (ifcfg, "OVS_PORT", NULL);
 
 	master = nm_setting_connection_get_master (s_con);
 	if (master) {
@@ -1957,6 +1898,9 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 		} else if (nm_setting_connection_is_slave_type (s_con, NM_SETTING_OVS_PORT_SETTING_NAME)) {
 			svSetValueStr (ifcfg, "OVS_PORT_UUID", master);
 			svSetValueStr (ifcfg, "OVS_PORT", master_iface);
+		} else if (nm_setting_connection_is_slave_type (s_con, NM_SETTING_VRF_SETTING_NAME)) {
+			svSetValueStr (ifcfg, "VRF_UUID", master);
+			svSetValueStr (ifcfg, "VRF", master_iface);
 		} else {
 			_LOGW ("don't know how to set master for a %s slave",
 			       nm_setting_connection_get_slave_type (s_con));
@@ -1967,11 +1911,8 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 		svSetValueStr (ifcfg, "DEVICETYPE", TYPE_TEAM);
 	else if (master_iface && nm_setting_connection_is_slave_type (s_con, NM_SETTING_TEAM_SETTING_NAME))
 		svSetValueStr (ifcfg, "DEVICETYPE", TYPE_TEAM_PORT);
-	else
-		svUnsetValue (ifcfg, "DEVICETYPE");
 
 	/* secondary connection UUIDs */
-	svUnsetValue (ifcfg, "SECONDARY_UUIDS");
 	n = nm_setting_connection_get_num_secondaries (s_con);
 	if (n > 0) {
 		str = g_string_sized_new (n * 37);
@@ -2004,17 +1945,19 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	case NM_METERED_NO:
 		svSetValueStr (ifcfg, "CONNECTION_METERED", "no");
 		break;
-	default:
-		svUnsetValue (ifcfg, "CONNECTION_METERED");
+	case NM_METERED_UNKNOWN:
+	case NM_METERED_GUESS_YES:
+	case NM_METERED_GUESS_NO:
+		break;
 	}
 
 	vint = nm_setting_connection_get_auth_retries (s_con);
 	svSetValueInt64_cond (ifcfg, "AUTH_RETRIES", vint >= 0, vint);
 
 	vint32 = nm_setting_connection_get_wait_device_timeout (s_con);
-	if (vint32 == -1)
-		svUnsetValue (ifcfg, "DEVTIMEOUT");
-	else if ((vint32 % 1000) == 0)
+	if (vint32 == -1) {
+		/* pass */
+	} else if ((vint32 % 1000) == 0)
 		svSetValueInt64 (ifcfg, "DEVTIMEOUT", vint32 / 1000);
 	else {
 		char b[100];
@@ -2028,15 +1971,13 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
 		svSetValueEnum (ifcfg, "MDNS", nm_setting_connection_mdns_get_type (),
 		                mdns);
-	} else
-		svUnsetValue (ifcfg, "MDNS");
+	}
 
 	llmnr = nm_setting_connection_get_llmnr (s_con);
 	if (llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT) {
 		svSetValueEnum (ifcfg, "LLMNR", nm_setting_connection_llmnr_get_type (),
 		                llmnr);
-	} else
-		svUnsetValue (ifcfg, "LLMNR");
+	}
 }
 
 static char *
@@ -2119,8 +2060,6 @@ write_route_file_svformat (const char *filename, NMSettingIPConfig *s_ip4)
 
 	routefile = utils_get_route_ifcfg (filename, TRUE);
 
-	svUnsetAll (routefile, SV_KEY_TYPE_ROUTE_SVFORMAT);
-
 	num = nm_setting_ip_config_get_num_routes (s_ip4);
 	for (i = 0; i < num; i++) {
 		char buf[INET_ADDRSTRLEN];
@@ -2144,7 +2083,7 @@ write_route_file_svformat (const char *filename, NMSettingIPConfig *s_ip4)
 
 		netmask = _nm_utils_ip4_prefix_to_netmask (nm_ip_route_get_prefix (route));
 		svSetValueStr (routefile, netmask_key,
-		               nm_utils_inet4_ntop (netmask, buf));
+		               _nm_utils_inet4_ntop (netmask, buf));
 
 		svSetValueStr (routefile, gw_key, nm_ip_route_get_next_hop (route));
 
@@ -2221,10 +2160,6 @@ write_proxy_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	if (!s_proxy)
 		return TRUE;
 
-	svUnsetValue (ifcfg, "BROWSER_ONLY");
-	svUnsetValue (ifcfg, "PAC_URL");
-	svUnsetValue (ifcfg, "PAC_SCRIPT");
-
 	method = nm_setting_proxy_get_method (s_proxy);
 	switch (method) {
 	case NM_SETTING_PROXY_METHOD_AUTO:
@@ -2258,8 +2193,6 @@ write_user_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 
 	s_user = NM_SETTING_USER (nm_connection_get_setting (connection, NM_TYPE_SETTING_USER));
 
-	svUnsetAll (ifcfg, SV_KEY_TYPE_USER);
-
 	if (!s_user)
 		return TRUE;
 
@@ -2292,13 +2225,9 @@ write_sriov_setting (NMConnection *connection, shvarFile *ifcfg)
 	char key[32];
 	char *str;
 
-	svUnsetAll (ifcfg, SV_KEY_TYPE_SRIOV_VF);
-
 	s_sriov = NM_SETTING_SRIOV (nm_connection_get_setting (connection,
 	                                                       NM_TYPE_SETTING_SRIOV));
 	if (!s_sriov) {
-		svUnsetValue (ifcfg, "SRIOV_TOTAL_VFS");
-		svUnsetValue (ifcfg, "SRIOV_AUTOPROBE_DRIVERS");
 		return;
 	}
 
@@ -2307,8 +2236,6 @@ write_sriov_setting (NMConnection *connection, shvarFile *ifcfg)
 	b = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
 	if (b != NM_TERNARY_DEFAULT)
 		svSetValueInt64 (ifcfg, "SRIOV_AUTOPROBE_DRIVERS", b);
-	else
-		svUnsetValue (ifcfg, "SRIOV_AUTOPROBE_DRIVERS");
 
 	num = nm_setting_sriov_get_num_vfs (s_sriov);
 	for (i = 0; i < num; i++) {
@@ -2327,8 +2254,6 @@ write_tc_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	guint i, num, n;
 	char tag[64];
 
-	svUnsetAll (ifcfg, SV_KEY_TYPE_TC);
-
 	s_tc = nm_connection_get_setting_tc_config (connection);
 	if (!s_tc)
 		return TRUE;
@@ -2371,8 +2296,6 @@ write_match_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	nm_auto_free_gstring GString *str = NULL;
 	guint i, num;
 
-	svUnsetValue (ifcfg, "MATCH_INTERFACE_NAME");
-
 	s_match = (NMSettingMatch *) nm_connection_get_setting (connection, NM_TYPE_SETTING_MATCH);
 	if (!s_match)
 		return TRUE;
@@ -2404,10 +2327,8 @@ write_res_options (shvarFile *ifcfg, NMSettingIPConfig *s_ip, const char *var)
 	nm_auto_free_gstring GString *value = NULL;
 	guint i, num_options;
 
-	if (!nm_setting_ip_config_has_dns_options (s_ip)) {
-		svUnsetValue (ifcfg, var);
+	if (!nm_setting_ip_config_has_dns_options (s_ip))
 		return;
-	}
 
 	value = g_string_new (NULL);
 	num_options = nm_setting_ip_config_get_num_dns_options (s_ip);
@@ -2420,6 +2341,47 @@ write_res_options (shvarFile *ifcfg, NMSettingIPConfig *s_ip, const char *var)
 	svSetValue (ifcfg, var, value->str);
 }
 
+static void
+write_dns_setting (shvarFile *ifcfg,
+                   NMConnection *connection,
+                   int addr_family)
+{
+	NMSettingIPConfig *s_ip;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6 = NULL;
+	guint num4;
+	guint num6 = 0;
+	guint num;
+	guint i;
+	guint offset;
+
+	if (addr_family == AF_INET6) {
+		s_ip6 = nm_connection_get_setting_ip6_config (connection);
+		num6 = s_ip6 ? nm_setting_ip_config_get_num_dns (s_ip6) : 0u;
+	}
+
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	num4 = s_ip4 ? nm_setting_ip_config_get_num_dns (s_ip4) : 0u;
+
+	if (addr_family == AF_INET6) {
+		num = num6;
+		offset = num4;
+		s_ip = s_ip6;
+	} else {
+		num = num4;
+		offset = 0;
+		s_ip = s_ip4;
+	}
+
+	for (i = 0; i < num; i++) {
+		char tag[64];
+
+		svSetValueStr (ifcfg,
+		               numbered_tag (tag, "DNS", offset + i + 1u),
+		               nm_setting_ip_config_get_dns (s_ip, i));
+	}
+}
+
 static gboolean
 write_ip4_setting (NMConnection *connection,
                    shvarFile *ifcfg,
@@ -2446,16 +2408,8 @@ write_ip4_setting (NMConnection *connection,
 	NM_SET_OUT (out_route_content, NULL);
 
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	if (!s_ip4) {
-		/* slave-type: clear IPv4 settings.
-		 *
-		 * Some IPv4 setting related options are not cleared,
-		 * for no strong reason. */
-		svUnsetValue (ifcfg, "BOOTPROTO");
-		svUnsetValue (ifcfg, "RES_OPTIONS");
-		svUnsetAll (ifcfg, SV_KEY_TYPE_IP4_ADDRESS);
+	if (!s_ip4)
 		return TRUE;
-	}
 
 	method = nm_setting_ip_config_get_method (s_ip4);
 
@@ -2463,17 +2417,8 @@ write_ip4_setting (NMConnection *connection,
 	if (!method)
 		method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
 
-	if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
-		/* IPv4 disabled, clear IPv4 related parameters */
-		svUnsetValue (ifcfg, "BOOTPROTO");
-		for (j = -1; j < 256; j++) {
-			svUnsetValue (ifcfg, numbered_tag (tag, "IPADDR", j));
-			svUnsetValue (ifcfg, numbered_tag (tag, "PREFIX", j));
-			svUnsetValue (ifcfg, numbered_tag (tag, "NETMASK", j));
-			svUnsetValue (ifcfg, numbered_tag (tag, "GATEWAY", j));
-		}
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 		return TRUE;
-	}
 
 	num = nm_setting_ip_config_get_num_addresses (s_ip4);
 
@@ -2490,7 +2435,7 @@ write_ip4_setting (NMConnection *connection,
 	else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
 		svSetValueStr (ifcfg, "BOOTPROTO", "shared");
 
-	has_netmask = !!svFindFirstKeyWithPrefix (ifcfg, "NETMASK");
+	has_netmask = !!svFindFirstNumberedKey (ifcfg, "NETMASK");
 
 	/* Write out IPADDR<n>, PREFIX<n>, GATEWAY<n> for current IP addresses
 	 * without labels. Unset obsolete NETMASK<n>.
@@ -2532,45 +2477,15 @@ write_ip4_setting (NMConnection *connection,
 			char buf[INET_ADDRSTRLEN];
 
 			svSetValueStr (ifcfg, tag,
-			               nm_utils_inet4_ntop (_nm_utils_ip4_prefix_to_netmask (prefix), buf));
-		} else
-			svUnsetValue (ifcfg, tag);
+			               _nm_utils_inet4_ntop (_nm_utils_ip4_prefix_to_netmask (prefix), buf));
+		}
 
 		n++;
 	}
 
-	svUnsetValue (ifcfg, numbered_tag (tag, "IPADDR", 0));
-	svUnsetValue (ifcfg, numbered_tag (tag, "PREFIX", 0));
-	svUnsetValue (ifcfg, numbered_tag (tag, "NETMASK", 0));
-	if (n == 0) {
-		svUnsetValue (ifcfg, "IPADDR");
-		svUnsetValue (ifcfg, "PREFIX");
-		svUnsetValue (ifcfg, "NETMASK");
-	}
-	for (j = n; j < 256; j++) {
-		svUnsetValue (ifcfg, numbered_tag (tag, "IPADDR", j));
-		svUnsetValue (ifcfg, numbered_tag (tag, "PREFIX", j));
-		svUnsetValue (ifcfg, numbered_tag (tag, "NETMASK", j));
-	}
-
-	for (j = -1; j < 256; j++) {
-		if (j != 0)
-			svUnsetValue (ifcfg, numbered_tag (tag, "GATEWAY", j));
-	}
 	svSetValueStr (ifcfg, "GATEWAY", nm_setting_ip_config_get_gateway (s_ip4));
 
-	num = nm_setting_ip_config_get_num_dns (s_ip4);
-	for (i = 0; i < 254; i++) {
-		const char *dns;
-
-		numbered_tag (tag, "DNS", i + 1);
-		if (i >= num)
-			svUnsetValue (ifcfg, tag);
-		else {
-			dns = nm_setting_ip_config_get_dns (s_ip4, i);
-			svSetValueStr (ifcfg, tag, dns);
-		}
-	}
+	write_dns_setting (ifcfg, connection, AF_INET);
 
 	num = nm_setting_ip_config_get_num_dns_searches (s_ip4);
 	if (num > 0) {
@@ -2582,8 +2497,7 @@ write_ip4_setting (NMConnection *connection,
 		}
 		svSetValueStr (ifcfg, "DOMAIN", searches->str);
 		g_string_free (searches, TRUE);
-	} else
-		svUnsetValue (ifcfg, "DOMAIN");
+	}
 
 	/* DEFROUTE; remember that it has the opposite meaning from never-default */
 	svSetValueBoolean (ifcfg, "DEFROUTE", !nm_setting_ip_config_get_never_default (s_ip4));
@@ -2644,8 +2558,7 @@ write_ip4_setting (NMConnection *connection,
 
 	timeout = nm_setting_ip_config_get_dad_timeout (s_ip4);
 	if (timeout < 0) {
-		svUnsetValue (ifcfg, "ACD_TIMEOUT");
-		svUnsetValue (ifcfg, "ARPING_WAIT");
+		/* pass */
 	} else if (timeout == 0) {
 		svSetValueStr (ifcfg, "ACD_TIMEOUT", "0");
 		svSetValueStr (ifcfg, "ARPING_WAIT", "0");
@@ -2658,8 +2571,6 @@ write_ip4_setting (NMConnection *connection,
 	priority = nm_setting_ip_config_get_dns_priority (s_ip4);
 	if (priority)
 		svSetValueInt64 (ifcfg, "IPV4_DNS_PRIORITY", priority);
-	else
-		svUnsetValue (ifcfg, "IPV4_DNS_PRIORITY");
 
 	write_res_options (ifcfg, s_ip4, "RES_OPTIONS");
 
@@ -2745,7 +2656,7 @@ write_ip4_aliases (NMConnection *connection, const char *base_ifcfg_path)
 
 		svSetValueInt64 (ifcfg, "PREFIX", nm_ip_address_get_prefix(addr));
 
-		svWriteFile (ifcfg, 0644, NULL);
+		svWriteFileWithoutDirtyWellknown (ifcfg, 0644, NULL);
 		svCloseFile (ifcfg);
 	}
 }
@@ -2757,12 +2668,10 @@ write_ip6_setting (NMConnection *connection,
                    GError **error)
 {
 	NMSettingIPConfig *s_ip6;
-	NMSettingIPConfig *s_ip4;
 	const char *value;
-	guint i, num, num4;
+	guint i, num;
 	int priority;
 	NMIPAddress *addr;
-	const char *dns;
 	gint64 route_metric;
 	NMIPRouteTableSyncMode route_table;
 	GString *ip_str1, *ip_str2, *ip_ptr;
@@ -2774,45 +2683,21 @@ write_ip6_setting (NMConnection *connection,
 	NM_SET_OUT (out_route6_content, NULL);
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
-	if (!s_ip6) {
-		/* slave-type: clear IPv6 settings
-		 *
-		 * Some IPv6 setting related options are not cleared,
-		 * for no strong reason. */
-		svUnsetValue (ifcfg, "IPV6INIT");
-		svUnsetValue (ifcfg, "IPV6_AUTOCONF");
-		svUnsetValue (ifcfg, "DHCPV6C");
-		svUnsetValue (ifcfg, "DHCPv6_DUID");
-		svUnsetValue (ifcfg, "DHCPv6_IAID");
-		svUnsetValue (ifcfg, "DHCPV6_HOSTNAME");
-		svUnsetValue (ifcfg, "DHCPV6_SEND_HOSTNAME");
-		svUnsetValue (ifcfg, "IPV6_DEFROUTE");
-		svUnsetValue (ifcfg, "IPV6_PEERDNS");
-		svUnsetValue (ifcfg, "IPV6_PEERROUTES");
-		svUnsetValue (ifcfg, "IPV6_FAILURE_FATAL");
-		svUnsetValue (ifcfg, "IPV6_ROUTE_METRIC");
-		svUnsetValue (ifcfg, "IPV6_ADDR_GEN_MODE");
-		svUnsetValue (ifcfg, "IPV6_RES_OPTIONS");
+	if (!s_ip6)
 		return TRUE;
-	}
 
 	value = nm_setting_ip_config_get_method (s_ip6);
 	g_assert (value);
-	svUnsetValue (ifcfg, "IPV6_DISABLED");
 	if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "no");
-		svUnsetValue (ifcfg, "DHCPV6C");
 		return TRUE;
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
 		svSetValueStr (ifcfg, "IPV6_DISABLED", "yes");
 		svSetValueStr (ifcfg, "IPV6INIT", "no");
-		svUnsetValue (ifcfg, "DHCPV6C");
-		svUnsetValue (ifcfg, "IPV6_AUTOCONF");
 		return TRUE;
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "yes");
 		svSetValueStr (ifcfg, "IPV6_AUTOCONF", "yes");
-		svUnsetValue (ifcfg, "DHCPV6C");
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "yes");
 		svSetValueStr (ifcfg, "IPV6_AUTOCONF", "no");
@@ -2820,15 +2705,12 @@ write_ip6_setting (NMConnection *connection,
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "yes");
 		svSetValueStr (ifcfg, "IPV6_AUTOCONF", "no");
-		svUnsetValue (ifcfg, "DHCPV6C");
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "yes");
 		svSetValueStr (ifcfg, "IPV6_AUTOCONF", "no");
-		svUnsetValue (ifcfg, "DHCPV6C");
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "yes");
 		svSetValueStr (ifcfg, "IPV6_AUTOCONF", "shared");
-		svUnsetValue (ifcfg, "DHCPV6C");
 	}
 
 	svSetValueStr (ifcfg, "DHCPV6_DUID",
@@ -2842,9 +2724,7 @@ write_ip6_setting (NMConnection *connection,
 	/* Missing DHCPV6_SEND_HOSTNAME means TRUE, and we prefer not write it
 	 * explicitly in that case, because it is NM-specific variable
 	 */
-	if (nm_setting_ip_config_get_dhcp_send_hostname (s_ip6))
-		svUnsetValue (ifcfg, "DHCPV6_SEND_HOSTNAME");
-	else
+	if (!nm_setting_ip_config_get_dhcp_send_hostname (s_ip6))
 		svSetValueStr (ifcfg, "DHCPV6_SEND_HOSTNAME", "no");
 
 	timeout = nm_setting_ip6_config_get_ra_timeout (NM_SETTING_IP6_CONFIG (s_ip6));
@@ -2889,22 +2769,7 @@ write_ip6_setting (NMConnection *connection,
 	g_string_free (ip_str1, TRUE);
 	g_string_free (ip_str2, TRUE);
 
-	/* Write out DNS - 'DNS' key is used both for IPv4 and IPv6 */
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	num4 = s_ip4 ? nm_setting_ip_config_get_num_dns (s_ip4) : 0; /* from where to start with IPv6 entries */
-	num = nm_setting_ip_config_get_num_dns (s_ip6);
-	for (i = 0; i < 254; i++) {
-		char tag[64];
-
-		numbered_tag (tag, "DNS", i + num4 + 1);
-
-		if (i >= num)
-			svUnsetValue (ifcfg, tag);
-		else {
-			dns = nm_setting_ip_config_get_dns (s_ip6, i);
-			svSetValueStr (ifcfg, tag, dns);
-		}
-	}
+	write_dns_setting (ifcfg, connection, AF_INET6);
 
 	/* Write out DNS domains */
 	num = nm_setting_ip_config_get_num_dns_searches (s_ip6);
@@ -2918,15 +2783,11 @@ write_ip6_setting (NMConnection *connection,
 			g_string_append (searches, nm_setting_ip_config_get_dns_search (s_ip6, i));
 		}
 		svSetValueStr (ifcfg, "IPV6_DOMAIN", searches->str);
-	} else
-		svUnsetValue (ifcfg, "IPV6_DOMAIN");
+	}
 
 	/* handle IPV6_DEFROUTE */
 	/* IPV6_DEFROUTE has the opposite meaning from 'never-default' */
-	if (nm_setting_ip_config_get_never_default (s_ip6))
-		svSetValueStr (ifcfg, "IPV6_DEFROUTE", "no");
-	else
-		svSetValueStr (ifcfg, "IPV6_DEFROUTE", "yes");
+	svSetValueBoolean (ifcfg, "IPV6_DEFROUTE", !nm_setting_ip_config_get_never_default (s_ip6));
 
 	svSetValueStr (ifcfg, "IPV6_PEERDNS",
 	               nm_setting_ip_config_get_ignore_auto_dns (s_ip6) ? "no" : NULL);
@@ -2950,8 +2811,6 @@ write_ip6_setting (NMConnection *connection,
 	                      route_table);
 
 	/* IPv6 Privacy Extensions */
-	svUnsetValue (ifcfg, "IPV6_PRIVACY");
-	svUnsetValue (ifcfg, "IPV6_PRIVACY_PREFER_PUBLIC_IP");
 	switch (nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6))) {
 	case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
 		svSetValueStr (ifcfg, "IPV6_PRIVACY", "no");
@@ -2972,8 +2831,6 @@ write_ip6_setting (NMConnection *connection,
 	if (addr_gen_mode != NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) {
 		svSetValueEnum (ifcfg, "IPV6_ADDR_GEN_MODE", nm_setting_ip6_config_addr_gen_mode_get_type (),
 		                addr_gen_mode);
-	} else {
-		svUnsetValue (ifcfg, "IPV6_ADDR_GEN_MODE");
 	}
 
 	/* IPv6 tokenized interface identifier */
@@ -2983,8 +2840,6 @@ write_ip6_setting (NMConnection *connection,
 	priority = nm_setting_ip_config_get_dns_priority (s_ip6);
 	if (priority)
 		svSetValueInt64 (ifcfg, "IPV6_DNS_PRIORITY", priority);
-	else
-		svUnsetValue (ifcfg, "IPV6_DNS_PRIORITY");
 
 	write_res_options (ifcfg, s_ip6, "IPV6_RES_OPTIONS");
 
@@ -3001,8 +2856,6 @@ write_ip_routing_rules (NMConnection *connection,
 	gsize idx;
 	int is_ipv4;
 
-	svUnsetAll (ifcfg, SV_KEY_TYPE_ROUTING_RULE4 | SV_KEY_TYPE_ROUTING_RULE6);
-
 	if (route_ignore)
 		return;
 
@@ -3233,8 +3086,6 @@ do_write_construct (NMConnection *connection,
 	if (!write_tc_setting (connection, ifcfg, error))
 		return FALSE;
 
-	svUnsetValue (ifcfg, "DHCP_HOSTNAME");
-	svUnsetValue (ifcfg, "DHCP_FQDN");
 
 	route_path_is_svformat = utils_has_route_file_new_syntax (route_path);
 
@@ -3321,7 +3172,7 @@ do_write_to_disk (NMConnection *connection,
 	 * only. But we loaded the ifcfg files from disk, and managled our
 	 * new settings (in-memory). */
 
-	if (!svWriteFile (ifcfg, 0644, error))
+	if (!svWriteFileWithoutDirtyWellknown (ifcfg, 0644, error))
 		return FALSE;
 
 	write_ip4_aliases (connection, svFileGetName (ifcfg));
@@ -3340,7 +3191,7 @@ do_write_to_disk (NMConnection *connection,
 		else {
 			nm_assert (route_content_svformat || route_content);
 			if (route_content_svformat) {
-				if (!svWriteFile (route_content_svformat, 0644, error))
+				if (!svWriteFileWithoutDirtyWellknown (route_content_svformat, 0644, error))
 					return FALSE;
 			} else {
 				if (!g_file_set_contents (route_path, route_content->str, route_content->len, NULL)) {
@@ -3450,6 +3301,11 @@ nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
 			_LOGW ("write: failure to re-read connection \"%s\": %s",
 			       svFileGetName (ifcfg), "connection is unhandled");
 		} else {
+			/* ifcfg-rh doesn't support the 'timestamp' property, let's add it here */
+			g_object_set (nm_connection_get_setting_connection (reread),
+			              NM_SETTING_CONNECTION_TIMESTAMP,
+			              nm_setting_connection_get_timestamp (nm_connection_get_setting_connection (connection)),
+			              NULL);
 			if (out_reread_same) {
 				reread_same = nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
 				if (!reread_same) {
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 16b2dd37..f2a1dd78 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -19,13 +19,23 @@
 #include "nm-glib-aux/nm-enum-utils.h"
 #include "nm-glib-aux/nm-io-utils.h"
 #include "c-list/src/c-list.h"
+#include "nms-ifcfg-rh-utils.h"
 
 /*****************************************************************************/
 
 struct _shvarLine {
 
+	const char *key;
+
 	CList lst;
 
+	/* We index variables by their key in shvarFile.lst_idx. One shell variable might
+	 * occur multiple times in a file (in which case the last occurrence wins).
+	 * Hence, we need to keep a list of all the same keys.
+	 *
+	 * This is a pointer to the next shadowed line. */
+	struct _shvarLine *prev_shadowed;
+
 	/* There are three cases:
 	 *
 	 * 1) the line is not a valid variable assignment (that is, it doesn't
@@ -43,21 +53,41 @@ struct _shvarLine {
 	 *   @key/@key_with_prefix.
 	 * */
 	char *line;
-	const char *key;
 	char *key_with_prefix;
+
+	/* svSetValue() will clear the dirty flag. */
+	bool dirty:1;
 };
 
 typedef struct _shvarLine shvarLine;
 
 struct _shvarFile {
-	char      *fileName;
-	int        fd;
-	CList      lst_head;
-	gboolean   modified;
+	char *fileName;
+	CList lst_head;
+	GHashTable *lst_idx;
+	int fd;
+	bool modified:1;
 };
 
 /*****************************************************************************/
 
+static void _line_link_parse (shvarFile *s, const char *value, gsize len);
+
+/*****************************************************************************/
+
+#define ASSERT_key_is_well_known(key) \
+	nm_assert ( ({ \
+		const char *_key = (key); \
+		gboolean _is_wellknown = TRUE; \
+		\
+		if (!nms_ifcfg_rh_utils_is_well_known_key (_key)) { \
+			_is_wellknown = FALSE; \
+			g_critical ("ifcfg-rh key \"%s\" is not well-known", _key); \
+		} \
+		\
+		_is_wellknown; \
+	}) )
+
 /**
  * svParseBoolean:
  * @value: the input string
@@ -603,15 +633,33 @@ out_error:
 
 /*****************************************************************************/
 
-static shvarFile *
-svFile_new (const char *name)
+shvarFile *
+svFile_new (const char *name,
+            int fd,
+            const char *content)
 {
 	shvarFile *s;
+	const char *p;
+	const char *q;
+
+	nm_assert (name);
+	nm_assert (fd >= -1);
+
+	s = g_slice_new (shvarFile);
+	*s = (shvarFile) {
+		.fileName = g_strdup (name),
+		.fd       = fd,
+		.lst_head = C_LIST_INIT (s->lst_head),
+		.lst_idx  = g_hash_table_new (nm_pstr_hash, nm_pstr_equal),
+	};
+
+	if (content) {
+		for (p = content; (q = strchr (p, '\n')) != NULL; p = q + 1)
+			_line_link_parse (s, p, q - p);
+		if (p[0])
+			_line_link_parse (s, p, strlen (p));
+	}
 
-	s = g_slice_new0 (shvarFile);
-	s->fd = -1;
-	s->fileName = g_strdup (name);
-	c_list_init (&s->lst_head);
 	return s;
 }
 
@@ -671,8 +719,11 @@ line_new_parse (const char *value, gsize len)
 
 	nm_assert (value);
 
-	line = g_slice_new0 (shvarLine);
-	c_list_init (&line->lst);
+	line = g_slice_new (shvarLine);
+	*line = (shvarLine) {
+		.lst      = C_LIST_INIT (line->lst),
+		.dirty    = TRUE,
+	};
 
 	for (k = 0; k < len; k++) {
 		if (g_ascii_isspace (value[k]))
@@ -706,14 +757,19 @@ line_new_build (const char *key, const char *value)
 {
 	char *value_escaped = NULL;
 	shvarLine *line;
+	char *new_key;
 
 	value = svEscape (value, &value_escaped);
 
 	line = g_slice_new (shvarLine);
-	c_list_init (&line->lst);
-	line->line = value_escaped ?: g_strdup (value);
-	line->key_with_prefix = g_strdup (key);
-	line->key = line->key_with_prefix;
+	new_key = g_strdup (key),
+	*line = (shvarLine) {
+		.lst             = C_LIST_INIT (line->lst),
+		.line            = value_escaped ?: g_strdup (value),
+		.key_with_prefix = new_key,
+		.key             = new_key,
+		.dirty           = FALSE,
+	};
 	ASSERT_shvarLine (line);
 	return line;
 }
@@ -727,6 +783,8 @@ line_set (shvarLine *line, const char *value)
 	ASSERT_shvarLine (line);
 	nm_assert (line->key);
 
+	line->dirty = FALSE;
+
 	if (line->key != line->key_with_prefix) {
 		memmove (line->key_with_prefix, line->key, strlen (line->key) + 1);
 		line->key = line->key_with_prefix;
@@ -753,14 +811,48 @@ static void
 line_free (shvarLine *line)
 {
 	ASSERT_shvarLine (line);
+	c_list_unlink_stale (&line->lst);
 	g_free (line->line);
 	g_free (line->key_with_prefix);
-	c_list_unlink_stale (&line->lst);
 	g_slice_free (shvarLine, line);
 }
 
 /*****************************************************************************/
 
+static void
+_line_link_parse (shvarFile *s, const char *value, gsize len)
+{
+	shvarLine *line;
+
+	line = line_new_parse (value, len);
+	if (!line->key)
+		goto do_link;
+
+	if (G_UNLIKELY (!g_hash_table_insert (s->lst_idx, line, line))) {
+		shvarLine *existing_key;
+		shvarLine *existing_val;
+
+		/* Slow-path: we have duplicate keys. Fix the mess we created.
+		 * Unfortunately, g_hash_table_insert() now had to allocate an extra
+		 * array to track the keys/values differently. I wish there was an
+		 * GHashTable API to add a key only if it does not exist yet. */
+
+		if (!g_hash_table_lookup_extended (s->lst_idx, line, (gpointer *) &existing_key, (gpointer *) &existing_val))
+			nm_assert_not_reached ();
+
+		nm_assert (existing_val == line);
+		nm_assert (existing_key != line);
+		line->prev_shadowed = existing_key;
+		g_hash_table_replace (s->lst_idx, line, line);
+	}
+
+do_link:
+	c_list_link_tail (&s->lst_head, &line->lst);
+}
+
+/*****************************************************************************/
+
+
 /* Open the file <name>, returning a shvarFile on success and NULL on failure.
  * Add a wrinkle to let the caller specify whether or not to create the file
  * (actually, return a structure anyway) if it doesn't exist.
@@ -768,11 +860,9 @@ line_free (shvarLine *line)
 static shvarFile *
 svOpenFileInternal (const char *name, gboolean create, GError **error)
 {
-	shvarFile *s;
 	gboolean closefd = FALSE;
 	int errsv = 0;
-	gs_free char *arena = NULL;
-	const char *p, *q;
+	gs_free char *content = NULL;
 	gs_free_error GError *local = NULL;
 	nm_auto_close int fd = -1;
 
@@ -789,7 +879,7 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 
 	if (fd < 0) {
 		if (create)
-			return svFile_new (name);
+			return svFile_new (name, -1, NULL);
 
 		g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errsv),
 		             "Could not read file '%s': %s",
@@ -801,12 +891,12 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 	                               closefd,
 	                               10 * 1024 * 1024,
 	                               NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
-	                               &arena,
+	                               &content,
 	                               NULL,
 	                               NULL,
 	                               &local)) {
 		if (create)
-			return svFile_new (name);
+			return svFile_new (name, -1, NULL);
 
 		g_set_error (error, G_FILE_ERROR,
 		             local->domain == G_FILE_ERROR ? local->code : G_FILE_ERROR_FAILED,
@@ -815,21 +905,14 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 		return NULL;
 	}
 
-	s = svFile_new (name);
-
-	for (p = arena; (q = strchr (p, '\n')) != NULL; p = q + 1)
-		c_list_link_tail (&s->lst_head, &line_new_parse (p, q - p)->lst);
-	if (p[0])
-		c_list_link_tail (&s->lst_head, &line_new_parse (p, strlen (p))->lst);
-
 	/* closefd is set if we opened the file read-only, so go ahead and
 	 * close it, because we can't write to it anyway */
-	if (!closefd) {
-		nm_assert (fd > 0);
-		s->fd = nm_steal_fd (&fd);
-	}
-
-	return s;
+	nm_assert (closefd || fd >= 0);
+	return svFile_new (name,
+	                     !closefd
+	                   ? nm_steal_fd (&fd)
+	                   : -1,
+	                   content);
 }
 
 /* Open the file <name>, return shvarFile on success, NULL on failure */
@@ -851,54 +934,32 @@ svCreateFile (const char *name)
 /*****************************************************************************/
 
 static gboolean
-_is_all_digits (const char *str)
+_svKeyMatchesType (const char *key, SvKeyType match_key_type)
 {
-	return    str[0]
-	       && NM_STRCHAR_ALL (str, ch, g_ascii_isdigit (ch));
-}
-
-#define IS_NUMBERED_TAG(key, tab_name) \
-	({ \
-		const char *_key2 = (key); \
-		\
-		(   (strncmp (_key2, tab_name, NM_STRLEN (tab_name)) == 0) \
-		 && _is_all_digits (&_key2[NM_STRLEN (tab_name)])); \
-	})
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ANY))
+		return TRUE;
 
-#define IS_NUMBERED_TAG_PARSE(key, tab_name, out_idx) \
+#define _IS_NUMBERED(key, tag) \
 	({ \
-		const char *_key = (key); \
 		gint64 _idx; \
-		gboolean _good = FALSE; \
-		gint64 *_out_idx = (out_idx); \
 		\
-		if (    IS_NUMBERED_TAG (_key, ""tab_name"") \
-		    && (_idx = _nm_utils_ascii_str_to_int64 (&_key[NM_STRLEN (tab_name)], 10, 0, G_MAXINT64, -1)) != -1) { \
-			NM_SET_OUT (_out_idx, _idx); \
-			_good = TRUE; \
-		} \
-		_good; \
+		   NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG (key, tag, &_idx) \
+		&& _idx >= 0; \
 	})
 
-static gboolean
-_svKeyMatchesType (const char *key, SvKeyType match_key_type)
-{
-	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ANY))
-		return TRUE;
-
 	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTE_SVFORMAT)) {
-		if (   IS_NUMBERED_TAG (key, "ADDRESS")
-		    || IS_NUMBERED_TAG (key, "NETMASK")
-		    || IS_NUMBERED_TAG (key, "GATEWAY")
-		    || IS_NUMBERED_TAG (key, "METRIC")
-		    || IS_NUMBERED_TAG (key, "OPTIONS"))
+		if (   _IS_NUMBERED (key, "ADDRESS")
+		    || _IS_NUMBERED (key, "NETMASK")
+		    || _IS_NUMBERED (key, "GATEWAY")
+		    || _IS_NUMBERED (key, "METRIC")
+		    || _IS_NUMBERED (key, "OPTIONS"))
 			return TRUE;
 	}
 	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_IP4_ADDRESS)) {
-		if (   IS_NUMBERED_TAG (key, "IPADDR")
-		    || IS_NUMBERED_TAG (key, "PREFIX")
-		    || IS_NUMBERED_TAG (key, "NETMASK")
-		    || IS_NUMBERED_TAG (key, "GATEWAY"))
+		if (   _IS_NUMBERED (key, "IPADDR")
+		    || _IS_NUMBERED (key, "PREFIX")
+		    || _IS_NUMBERED (key, "NETMASK")
+		    || _IS_NUMBERED (key, "GATEWAY"))
 			return TRUE;
 	}
 	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_USER)) {
@@ -906,20 +967,20 @@ _svKeyMatchesType (const char *key, SvKeyType match_key_type)
 			return TRUE;
 	}
 	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_TC)) {
-		if (   IS_NUMBERED_TAG (key, "QDISC")
-		    || IS_NUMBERED_TAG (key, "FILTER"))
+		if (   _IS_NUMBERED (key, "QDISC")
+		    || _IS_NUMBERED (key, "FILTER"))
 			return TRUE;
 	}
 	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_SRIOV_VF)) {
-		if (IS_NUMBERED_TAG (key, "SRIOV_VF"))
+		if (_IS_NUMBERED (key, "SRIOV_VF"))
 			return TRUE;
 	}
 	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE4)) {
-		if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", NULL))
+		if (_IS_NUMBERED (key, "ROUTING_RULE_"))
 			return TRUE;
 	}
 	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTING_RULE6)) {
-		if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", NULL))
+		if (_IS_NUMBERED (key, "ROUTING_RULE6_"))
 			return TRUE;
 	}
 
@@ -931,9 +992,8 @@ svNumberedParseKey (const char *key)
 {
 	gint64 idx;
 
-	if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE_", &idx))
-		return idx;
-	if (IS_NUMBERED_TAG_PARSE (key, "ROUTING_RULE6_", &idx))
+	if (   NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG (key, "ROUTING_RULE_", &idx)
+	    || NMS_IFCFG_RH_UTIL_IS_NUMBERED_TAG (key, "ROUTING_RULE6_", &idx))
 		return idx;
 	return -1;
 }
@@ -1001,20 +1061,18 @@ svGetKeysSorted (shvarFile *s,
 /*****************************************************************************/
 
 const char *
-svFindFirstKeyWithPrefix (shvarFile *s, const char *key_prefix)
+svFindFirstNumberedKey (shvarFile *s, const char *key_prefix)
 {
-	CList *current;
-	const shvarLine *l;
+	const shvarLine *line;
 
 	g_return_val_if_fail (s, NULL);
 	g_return_val_if_fail (key_prefix, NULL);
 
-	c_list_for_each (current, &s->lst_head) {
-		l = c_list_entry (current, shvarLine, lst);
-		if (   l->key
-		    && l->line
-		    && g_str_has_prefix (l->key, key_prefix))
-			return l->key;
+	c_list_for_each_entry (line, &s->lst_head, lst) {
+		if (   line->key
+		    && line->line
+		    && nms_ifcfg_rh_utils_is_numbered_tag (line->key, key_prefix, NULL))
+			return line->key;
 	}
 
 	return NULL;
@@ -1025,20 +1083,16 @@ svFindFirstKeyWithPrefix (shvarFile *s, const char *key_prefix)
 static const char *
 _svGetValue (shvarFile *s, const char *key, char **to_free)
 {
-	CList *current;
-	const shvarLine *line, *l;
+	const shvarLine *line;
 	const char *v;
 
 	nm_assert (s);
 	nm_assert (_shell_is_name (key, -1));
 	nm_assert (to_free);
 
-	line = NULL;
-	c_list_for_each (current, &s->lst_head) {
-		l = c_list_entry (current, shvarLine, lst);
-		if (l->key && nm_streq (l->key, key))
-			line = l;
-	}
+	ASSERT_key_is_well_known (key);
+
+	line = g_hash_table_lookup (s->lst_idx, &key);
 
 	if (line && line->line) {
 		v = svUnescape (line->line, to_free);
@@ -1229,24 +1283,53 @@ svGetValueEnum (shvarFile *s, const char *key,
 gboolean
 svUnsetAll (shvarFile *s, SvKeyType match_key_type)
 {
-	CList *current;
 	shvarLine *line;
 	gboolean changed = FALSE;
 
 	g_return_val_if_fail (s, FALSE);
 
-	c_list_for_each (current, &s->lst_head) {
-		line = c_list_entry (current, shvarLine, lst);
+	c_list_for_each_entry (line, &s->lst_head, lst) {
 		ASSERT_shvarLine (line);
-		if (!line->key)
-			continue;
+		if (   line->key
+		    && _svKeyMatchesType (line->key, match_key_type)) {
+			if (nm_clear_g_free (&line->line)) {
+				ASSERT_shvarLine (line);
+				changed = TRUE;
+			}
+		}
+	}
 
-		if (_svKeyMatchesType (line->key, match_key_type)) {
+	if (changed)
+		s->modified = TRUE;
+	return changed;
+}
+
+gboolean
+svUnsetDirtyWellknown (shvarFile *s, NMTernary new_dirty_value)
+{
+	shvarLine *line;
+	gboolean changed = FALSE;
+
+	g_return_val_if_fail (s, FALSE);
+
+	c_list_for_each_entry (line, &s->lst_head, lst) {
+		const NMSIfcfgKeyTypeInfo *ti;
+
+		ASSERT_shvarLine (line);
+
+		if (   line->dirty
+		    && line->key
+		    && line->line
+		    && (ti = nms_ifcfg_rh_utils_is_well_known_key (line->key))
+		    && !NM_FLAGS_HAS (ti->key_flags, NMS_IFCFG_KEY_TYPE_KEEP_WHEN_DIRTY)) {
 			if (nm_clear_g_free (&line->line)) {
 				ASSERT_shvarLine (line);
 				changed = TRUE;
 			}
 		}
+
+		if (new_dirty_value != NM_TERNARY_DEFAULT)
+			line->dirty = (new_dirty_value != NM_TERNARY_FALSE);
 	}
 
 	if (changed)
@@ -1259,8 +1342,8 @@ svUnsetAll (shvarFile *s, SvKeyType match_key_type)
 gboolean
 svSetValue (shvarFile *s, const char *key, const char *value)
 {
-	CList *current;
-	shvarLine *line, *l;
+	shvarLine *line;
+	shvarLine *l_shadowed;
 	gboolean changed = FALSE;
 
 	g_return_val_if_fail (s, FALSE);
@@ -1268,29 +1351,37 @@ svSetValue (shvarFile *s, const char *key, const char *value)
 
 	nm_assert (_shell_is_name (key, -1));
 
-	line = NULL;
-	c_list_for_each (current, &s->lst_head) {
-		l = c_list_entry (current, shvarLine, lst);
-		if (l->key && nm_streq (l->key, key)) {
-			if (line) {
-				/* if we find multiple entries for the same key, we can
-				 * delete all but the last. */
-				line_free (line);
-				changed = TRUE;
-			}
-			line = l;
-		}
+	ASSERT_key_is_well_known (key);
+
+	line = g_hash_table_lookup (s->lst_idx, &key);
+	if (   line
+	    && (l_shadowed = line->prev_shadowed)) {
+		/* if we find multiple entries for the same key, we can
+		 * delete the shadowed ones. */
+		line->prev_shadowed = NULL;
+		changed = TRUE;
+		do {
+			shvarLine *l = l_shadowed;
+
+			l_shadowed = l_shadowed->prev_shadowed;
+			line_free (l);
+		} while (l_shadowed);
 	}
 
 	if (!value) {
 		if (line) {
+			/* We only clear the value, but leave the line entry. This way, if we
+			 * happen to re-add the value, we write it to the same line again. */
 			if (nm_clear_g_free (&line->line)) {
 				changed = TRUE;
 			}
 		}
 	} else {
 		if (!line) {
-			c_list_link_tail (&s->lst_head, &line_new_build (key, value)->lst);
+			line = line_new_build (key, value);
+			if (!g_hash_table_add (s->lst_idx, line))
+				nm_assert_not_reached ();
+			c_list_link_tail (&s->lst_head, &line->lst);
 			changed = TRUE;
 		} else {
 			if (line_set (line, value))
@@ -1442,14 +1533,15 @@ svWriteFile (shvarFile *s, int mode, GError **error)
 void
 svCloseFile (shvarFile *s)
 {
-	CList *current, *safe;
+	shvarLine *line;
 
 	g_return_if_fail (s != NULL);
 
 	if (s->fd >= 0)
 		nm_close (s->fd);
 	g_free (s->fileName);
-	c_list_for_each_safe (current, safe, &s->lst_head)
-		line_free (c_list_entry (current, shvarLine, lst));
+	g_hash_table_destroy (s->lst_idx);
+	while ((line = c_list_first_entry (&s->lst_head, shvarLine, lst)))
+		line_free (line);
 	g_slice_free (shvarFile, s);
 }
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index c3bbabab..410284f8 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -24,13 +24,21 @@ const char *svFileGetName (const shvarFile *s);
 void _nmtst_svFileSetName (shvarFile *s, const char *fileName);
 void _nmtst_svFileSetModified (shvarFile *s);
 
+/*****************************************************************************/
+
+shvarFile *svFile_new (const char *name,
+                       int fd,
+                       const char *content);
+
 /* Create the file <name>, return a shvarFile (never fails) */
 shvarFile *svCreateFile (const char *name);
 
 /* Open the file <name>, return shvarFile on success, NULL on failure */
 shvarFile *svOpenFile (const char *name, GError **error);
 
-const char *svFindFirstKeyWithPrefix (shvarFile *s, const char *key_prefix);
+/*****************************************************************************/
+
+const char *svFindFirstNumberedKey (shvarFile *s, const char *key_prefix);
 
 /* Get the value associated with the key, and leave the current pointer
  * pointing at the line containing the value.  The char* returned MUST
@@ -79,6 +87,7 @@ gboolean svSetValueEnum (shvarFile *s, const char *key, GType gtype, int value);
 
 gboolean svUnsetValue (shvarFile *s, const char *key);
 gboolean svUnsetAll (shvarFile *s, SvKeyType match_key_type);
+gboolean svUnsetDirtyWellknown (shvarFile *s, NMTernary new_dirty_value);
 
 /* Write the current contents iff modified.  Returns FALSE on error
  * and TRUE on success.  Do not write if no values have been modified.
@@ -88,6 +97,13 @@ gboolean svUnsetAll (shvarFile *s, SvKeyType match_key_type);
  */
 gboolean svWriteFile (shvarFile *s, int mode, GError **error);
 
+static inline gboolean
+svWriteFileWithoutDirtyWellknown (shvarFile *s, int mode, GError **error)
+{
+	svUnsetDirtyWellknown (s, NM_TERNARY_FALSE);
+	return svWriteFile (s, mode, error);
+}
+
 /* Close the file descriptor (if open) and free the shvarFile. */
 void svCloseFile (shvarFile *s);
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/meson.build b/src/settings/plugins/ifcfg-rh/tests/meson.build
index 21699045..1ce61c90 100644
--- a/src/settings/plugins/ifcfg-rh/tests/meson.build
+++ b/src/settings/plugins/ifcfg-rh/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-ifcfg-rh'
 
 exe = executable(
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
index d4785ff5..2e69c5e7 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-bridge-component-b.cexpected
@@ -1,6 +1,5 @@
 HWADDR=00:22:15:59:62:97
 TYPE=Ethernet
-BRIDGING_OPTS="priority=28 hairpin_mode=1"
 NAME="System test-bridge-component"
 UUID=${UUID}
 DEVICE=eth0
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
index 36df7712..b288c04a 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
@@ -1,4 +1,4 @@
-BONDING_OPTS="downdelay=5 miimon=100 mode=balance-rr updelay=10"
+BONDING_OPTS="mode=balance-rr downdelay=5 miimon=100 updelay=10"
 TYPE=Bond
 BONDING_MASTER=yes
 HWADDR=
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
index 5dfdce4d..87493ac2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-netmask-1.cexpected
@@ -2,7 +2,6 @@ DNS1=192.0.2.1
 IPADDR=102.0.2.2
 GATEWAY=192.0.2.1
 NETMASK=255.254.0.0
-BOOTPROTO="static"
 DEVICE=eth1
 ONBOOT=yes
 IPV6INIT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe
new file mode 100644
index 00000000..354046ad
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-owe
@@ -0,0 +1,5 @@
+TYPE=Wireless
+DEVICE=wlan1
+ESSID=blahblah_owe
+MODE=Managed
+KEY_MGMT=OWE
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
index a78051aa..4ab45ae3 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wifi-wpa-psk
@@ -16,4 +16,4 @@ CIPHER_GROUP="TKIP CCMP WEP40 WEP104"
 KEY_MGMT=WPA-PSK
 WPA_ALLOW_WPA=yes
 WPA_ALLOW_WPA2=yes
-LAST_ENTRY=no-newline
\ No newline at end of file
+CTCPROT=no-newline
\ No newline at end of file
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
index dc47126c..e683db3c 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-autoip
@@ -3,3 +3,4 @@ DEVICE=eth0
 BOOTPROTO=autoip
 IPV4_FAILURE_FATAL=yes
 PEERDNS=no
+DEVTIMEOUT=2.6
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
index 2c1b7fb4..f834464e 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4
@@ -5,32 +5,32 @@
 # don't consider only line-by-line, thus this is
 # expected.
 #
-# Also note that setting NAME will replace the last
+# Also note that setting IPADDR will replace the last
 # occurrence, and delete all previous once.
 
 #L1
-NAME=l2
+IPADDR=l2
 
 #L2
-NAME=l3
+IPADDR=l3
 
-some_key1=''
-some_key2=$'\U0x'
-some_key3=$'x\U0'
+METRIC1=''
+METRIC2=$'\U0x'
+METRIC3=$'x\U0'
 
 #L4
-NAME='
-NAME=l4x
+IPADDR='
+IPADDR=l4x
 '
 
 #Lx-1
-NAME2=not-visible
+IPADDR2=not-visible
 #Lx-2
-NAME2='invalid
+IPADDR2='invalid
 #Lx-3
 
 #Ly-1
-NAME3='invalid
+IPADDR3='invalid
 #Ly-2
-NAME3=name3-value
+IPADDR3=name3-value
 #Ly-3
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
index cf3f45bd..040ddc9d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-write-unknown-4.expected
@@ -5,27 +5,27 @@
 # don't consider only line-by-line, thus this is
 # expected.
 #
-# Also note that setting NAME will replace the last
+# Also note that setting IPADDR will replace the last
 # occurrence, and delete all previous once.
 
 #L1
 
 #L2
 
-some_key1=''
-some_key2=$'\U0x'
-some_key3=$'x\U0'
+METRIC1=''
+METRIC2=$'\U0x'
+METRIC3=$'x\U0'
 
 #L4
-NAME=set-by-test1
+IPADDR=set-by-test1
 #NM: '
 
 #Lx-1
 #Lx-2
-NAME2=set-by-test2
+IPADDR2=set-by-test2
 #Lx-3
 
 #Ly-1
 #Ly-2
-NAME3=set-by-test3
+IPADDR3=set-by-test3
 #Ly-3
diff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index 675421d3..efa9ea9b 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -1738,6 +1738,7 @@ static void
 test_read_wired_autoip (void)
 {
 	gs_unref_object NMConnection *connection = NULL;
+	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip4;
 	char *unmanaged = NULL;
 
@@ -1751,6 +1752,9 @@ test_read_wired_autoip (void)
 	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
 	g_assert (!nm_setting_ip_config_get_may_fail (s_ip4));
 	g_assert (nm_setting_ip_config_get_ignore_auto_dns (s_ip4));
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert_cmpint (nm_setting_connection_get_wait_device_timeout (s_con), ==, 2600);
 }
 
 static void
@@ -2094,7 +2098,7 @@ test_read_wired_aliases_good (gconstpointer test_data)
 		g_assert (ip4_addr != NULL);
 
 		addr = nm_ip_address_get_address (ip4_addr);
-		g_assert (nm_utils_ipaddr_valid (AF_INET, addr));
+		g_assert (nm_utils_ipaddr_is_valid (AF_INET, addr));
 
 		for (j = 0; j < expected_num_addresses; j++) {
 			if (!g_strcmp0 (addr, expected_address[j]))
@@ -2868,7 +2872,7 @@ test_ifcfg_no_trailing_newline (void)
 	shvarFile *sv;
 
 	sv = _svOpenFile (TEST_IFCFG_DIR"/ifcfg-test-wifi-wpa-psk");
-	_svGetValue_check (sv, "LAST_ENTRY", "no-newline");
+	_svGetValue_check (sv, "CTCPROT", "no-newline");
 	svCloseFile (sv);
 }
 
@@ -3035,6 +3039,45 @@ test_read_wifi_sae (void)
 }
 
 static void
+test_read_wifi_owe (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWireless *s_wireless;
+	NMSettingWirelessSecurity *s_wsec;
+	GBytes *ssid;
+	const char *expected_ssid = "blahblah_owe";
+
+	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-wifi-owe",
+	                                    NULL, TYPE_WIRELESS, NULL);
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "System blahblah_owe (test-wifi-owe)");
+
+	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
+	g_assert (nm_setting_connection_get_autoconnect (s_con));
+
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_assert (s_wireless);
+
+	g_assert_cmpint (nm_setting_wireless_get_mtu (s_wireless), ==, 0);
+
+	ssid = nm_setting_wireless_get_ssid (s_wireless);
+	g_assert (ssid);
+	g_assert_cmpmem (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid), expected_ssid, strlen (expected_ssid));
+
+	g_assert (!nm_setting_wireless_get_bssid (s_wireless));
+	g_assert_cmpstr (nm_setting_wireless_get_mode (s_wireless), ==, "infrastructure");
+
+	s_wsec = nm_connection_get_setting_wireless_security (connection);
+	g_assert (s_wsec);
+	g_assert_cmpstr (nm_setting_wireless_security_get_key_mgmt (s_wsec), ==, "owe");
+	g_assert (!nm_setting_wireless_security_get_psk (s_wsec));
+	g_assert (!nm_setting_wireless_security_get_auth_alg (s_wsec));
+}
+
+static void
 test_read_wifi_wpa_psk_2 (void)
 {
 	NMConnection *connection;
@@ -9742,23 +9785,23 @@ test_write_unknown (gconstpointer test_data)
 	_nmtst_svFileSetModified (sv);
 
 	if (g_str_has_suffix (testfile, "ifcfg-test-write-unknown-4")) {
-		_svGetValue_check (sv, "NAME", "l4x");
-		_svGetValue_check (sv, "NAME2", "");
-		_svGetValue_check (sv, "NAME3", "name3-value");
-
-		svSetValue (sv, "NAME", "set-by-test1");
-		svSetValue (sv, "NAME2", NULL);
-		svSetValue (sv, "NAME2", "set-by-test2");
-		svSetValue (sv, "NAME3", "set-by-test3");
-
-		_svGetValue_check (sv, "some_key", NULL);
-		_svGetValue_check (sv, "some_key1", "");
-		_svGetValue_check (sv, "some_key2", "");
-		_svGetValue_check (sv, "some_key3", "x");
-
-		_svGetValue_check (sv, "NAME", "set-by-test1");
-		_svGetValue_check (sv, "NAME2", "set-by-test2");
-		_svGetValue_check (sv, "NAME3", "set-by-test3");
+		_svGetValue_check (sv, "IPADDR", "l4x");
+		_svGetValue_check (sv, "IPADDR2", "");
+		_svGetValue_check (sv, "IPADDR3", "name3-value");
+
+		svSetValue (sv, "IPADDR", "set-by-test1");
+		svSetValue (sv, "IPADDR2", NULL);
+		svSetValue (sv, "IPADDR2", "set-by-test2");
+		svSetValue (sv, "IPADDR3", "set-by-test3");
+
+		_svGetValue_check (sv, "METRIC", NULL);
+		_svGetValue_check (sv, "METRIC1", "");
+		_svGetValue_check (sv, "METRIC2", "");
+		_svGetValue_check (sv, "METRIC3", "x");
+
+		_svGetValue_check (sv, "IPADDR", "set-by-test1");
+		_svGetValue_check (sv, "IPADDR2", "set-by-test2");
+		_svGetValue_check (sv, "IPADDR3", "set-by-test3");
 	}
 
 	success = svWriteFile (sv, 0644, &error);
@@ -10203,6 +10246,160 @@ test_tc_write (void)
 
 /*****************************************************************************/
 
+static void
+test_well_known_keys (void)
+{
+	gsize i;
+
+	for (i = 0; i < G_N_ELEMENTS (nms_ifcfg_well_known_keys); i++) {
+		const NMSIfcfgKeyTypeInfo *ti = &nms_ifcfg_well_known_keys[i];
+
+		g_assert (ti->key_name);
+		g_assert (ti->key_name[0]);
+		g_assert (NM_FLAGS_HAS (ti->key_flags, NMS_IFCFG_KEY_TYPE_WELL_KNOWN));
+		g_assert (nm_utils_is_power_of_two (ti->key_flags & (  NMS_IFCFG_KEY_TYPE_IS_PLAIN
+		                                                     | NMS_IFCFG_KEY_TYPE_IS_NUMBERED
+		                                                     | NMS_IFCFG_KEY_TYPE_IS_PREFIX)));
+	}
+
+	for (i = 1; i < G_N_ELEMENTS (nms_ifcfg_well_known_keys); i++) {
+		const NMSIfcfgKeyTypeInfo *ti_prev = &nms_ifcfg_well_known_keys[i - 1];
+		const NMSIfcfgKeyTypeInfo *ti = &nms_ifcfg_well_known_keys[i];
+
+		g_assert_cmpstr (ti_prev->key_name, <, ti->key_name);
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (nms_ifcfg_well_known_keys); i++) {
+		const NMSIfcfgKeyTypeInfo *ti = &nms_ifcfg_well_known_keys[i];
+		gs_free char *key_name = NULL;
+		gssize idx;
+
+		g_assert (ti == nms_ifcfg_well_known_key_find_info (ti->key_name, &idx));
+		g_assert_cmpint (i, ==, idx);
+
+		key_name = g_strdup (ti->key_name);
+		g_assert (ti == nms_ifcfg_well_known_key_find_info (key_name, &idx));
+		g_assert_cmpint (i, ==, idx);
+	}
+
+#define _test_well_known(key, expected) \
+	G_STMT_START { \
+		const NMSIfcfgKeyTypeInfo *_ti; \
+		const char *_expected = (expected); \
+		\
+		_ti = nms_ifcfg_rh_utils_is_well_known_key (""key""); \
+		if (!_expected) { \
+			g_assert (!_ti); \
+		} else { \
+			g_assert (_ti); \
+			g_assert_cmpstr (_ti->key_name, ==, _expected); \
+		} \
+	} G_STMT_END
+
+#define _test_well_known_plain(key) \
+	_test_well_known (""key"", ""key"")
+
+	_test_well_known_plain ("ONBOOT");
+	_test_well_known ("NM_USER_",  NULL);
+	_test_well_known ("NM_USER_x", "NM_USER_");
+	_test_well_known ("IPADDR",    "IPADDR");
+	_test_well_known ("IPADDR1",   "IPADDR");
+	_test_well_known ("IPADDRx",   NULL);
+}
+
+/*****************************************************************************/
+
+static void
+_do_utils_has_route_file_new_syntax_size (gboolean has_new_syntax,
+                                          const char *content,
+                                          gssize content_len)
+{
+	nmtst_auto_unlinkfile char *testfile = g_strdup (TEST_SCRATCH_DIR"/utils-has-route-file-new-syntax-test.txt");
+	gboolean val;
+
+	nmtst_file_set_contents_size (testfile, content, content_len);
+
+	val = utils_has_route_file_new_syntax (testfile);
+
+	g_assert_cmpint (val, ==, has_new_syntax);
+}
+#define _do_utils_has_route_file_new_syntax(has_new_syntax, content) \
+	_do_utils_has_route_file_new_syntax_size (has_new_syntax, (content), NM_STRLEN (content))
+
+static void
+test_utils_has_route_file_new_syntax (void)
+{
+	_do_utils_has_route_file_new_syntax (TRUE,  "");
+	_do_utils_has_route_file_new_syntax (FALSE, "\0");
+	_do_utils_has_route_file_new_syntax (FALSE, "\n");
+	_do_utils_has_route_file_new_syntax (FALSE, "ADDRESS=bogus");
+	_do_utils_has_route_file_new_syntax (FALSE, "ADDRESS=bogus\0");
+	_do_utils_has_route_file_new_syntax (TRUE,  "ADDRESS1=b\0ogus\0");
+	_do_utils_has_route_file_new_syntax (TRUE,  "ADDRESS1=bogus\0");
+	_do_utils_has_route_file_new_syntax (TRUE,  "\n\n\tADDRESS1=bogus\0");
+	_do_utils_has_route_file_new_syntax (FALSE, "\n\n\tADDRESS=bogus\n");
+	_do_utils_has_route_file_new_syntax (TRUE,  "\n\n\tADDRESS=bogus\n  ADDRESS000=\n");
+	_do_utils_has_route_file_new_syntax (FALSE, "\n\n\tROUTE1=bogus\n  ADDRES=\n");
+	_do_utils_has_route_file_new_syntax (FALSE, "\n\n\tADDRESS=bogus\n  ADDRESS\000000=\n");
+}
+
+/*****************************************************************************/
+
+static void
+test_ethtool_names (void)
+{
+	static const struct {
+		NMEthtoolID ethtool_id;
+		const char *kernel_name;
+	} kernel_names[] = {
+		{ NM_ETHTOOL_ID_FEATURE_GRO,    "rx-gro" },
+		{ NM_ETHTOOL_ID_FEATURE_GSO,    "tx-generic-segmentation" },
+		{ NM_ETHTOOL_ID_FEATURE_LRO,    "rx-lro" },
+		{ NM_ETHTOOL_ID_FEATURE_NTUPLE, "rx-ntuple-filter" },
+		{ NM_ETHTOOL_ID_FEATURE_RX,     "rx-checksum" },
+		{ NM_ETHTOOL_ID_FEATURE_RXHASH, "rx-hashing" },
+		{ NM_ETHTOOL_ID_FEATURE_RXVLAN, "rx-vlan-hw-parse" },
+		{ NM_ETHTOOL_ID_FEATURE_TXVLAN, "tx-vlan-hw-insert" },
+	};
+	const NMEthtoolData *data;
+	NMEthtoolID id;
+	int i;
+
+	for (id = _NM_ETHTOOL_ID_FEATURE_FIRST; id <= _NM_ETHTOOL_ID_FEATURE_LAST; id++) {
+		const char *ifcfg_rh_name;
+		int idx;
+
+		idx = id - _NM_ETHTOOL_ID_FEATURE_FIRST;
+		g_assert (idx >= 0);
+		g_assert (idx < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
+		ifcfg_rh_name = _nm_ethtool_ifcfg_names[idx];
+		g_assert (ifcfg_rh_name && ifcfg_rh_name[0]);
+
+		for (i = 0; i < G_N_ELEMENTS (_nm_ethtool_ifcfg_names); i++) {
+			if (i != idx)
+				g_assert_cmpstr (ifcfg_rh_name, !=, _nm_ethtool_ifcfg_names[i]);
+		}
+
+		g_assert_cmpstr (nms_ifcfg_rh_utils_get_ethtool_name (id), ==, ifcfg_rh_name);
+
+		data = nms_ifcfg_rh_utils_get_ethtool_by_name (ifcfg_rh_name);
+		g_assert (data);
+		g_assert (data->id == id);
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (kernel_names); i++) {
+		const char *name = kernel_names[i].kernel_name;
+
+		id = kernel_names[i].ethtool_id;
+		data = nms_ifcfg_rh_utils_get_ethtool_by_name (name);
+		g_assert (data);
+		g_assert (data->id == id);
+		g_assert_cmpstr (nms_ifcfg_rh_utils_get_ethtool_name (id), !=, name);
+	}
+}
+
+/*****************************************************************************/
+
 #define TPATH "/settings/plugins/ifcfg-rh/"
 
 #define TEST_IFCFG_WIFI_OPEN_SSID_LONG_QUOTED TEST_IFCFG_DIR"/ifcfg-test-wifi-open-ssid-long-quoted"
@@ -10322,6 +10519,7 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "wifi/read/wpa-psk/adhoc", test_read_wifi_wpa_psk_adhoc);
 	g_test_add_func (TPATH "wifi/read/wpa-psk/hex", test_read_wifi_wpa_psk_hex);
 	g_test_add_func (TPATH "wifi/read/sae", test_read_wifi_sae);
+	g_test_add_func (TPATH "wifi/read/owe", test_read_wifi_owe);
 	g_test_add_func (TPATH "wifi/read/dynamic-wep/leap", test_read_wifi_dynamic_wep_leap);
 	g_test_add_func (TPATH "wifi/read/wpa/eap/tls", test_read_wifi_wpa_eap_tls);
 	g_test_add_func (TPATH "wifi/read/wpa/eap/ttls/tls", test_read_wifi_wpa_eap_ttls_tls);
@@ -10498,6 +10696,10 @@ int main (int argc, char **argv)
 
 	g_test_add_func (TPATH "tc/read", test_tc_read);
 	g_test_add_func (TPATH "tc/write", test_tc_write);
+	g_test_add_func (TPATH "utils/test_well_known_keys", test_well_known_keys);
+	g_test_add_func (TPATH "utils/test_utils_has_route_file_new_syntax", test_utils_has_route_file_new_syntax);
+
+	g_test_add_func (TPATH "utils/test_ethtool_names", test_ethtool_names);
 
 	return g_test_run ();
 }
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/settings/plugins/ifupdown/meson.build
index 0cabe771..7e2b44fb 100644
--- a/src/settings/plugins/ifupdown/meson.build
+++ b/src/settings/plugins/ifupdown/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 sources = files(
   'nms-ifupdown-interface-parser.c',
   'nms-ifupdown-parser.c',
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index 1db9ef15..ac3ed1ce 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -484,7 +484,7 @@ update_ip4_setting_from_if_block (NMConnection *connection,
 		/* gateway */
 		gateway_v = ifparser_getkey (block, "gateway");
 		if (gateway_v) {
-			if (!nm_utils_ipaddr_valid (AF_INET, gateway_v)) {
+			if (!nm_utils_ipaddr_is_valid (AF_INET, gateway_v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				             "Invalid IPv4 gateway '%s'", gateway_v);
 				return FALSE;
@@ -565,9 +565,8 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 		const char *nameserver_v;
 		const char *nameservers_v;
 		const char *search_v;
-		int prefix_int = 128;
+		guint prefix_int;
 
-		/* Address */
 		address_v = ifparser_getkey (block, "address");
 		if (!address_v) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -575,12 +574,12 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 			return FALSE;
 		}
 
-		/* Prefix */
 		prefix_v = ifparser_getkey (block, "netmask");
 		if (prefix_v)
-			prefix_int = g_ascii_strtoll (prefix_v, NULL, 10);
+			prefix_int = _nm_utils_ascii_str_to_int64 (prefix_v, 10, 0, 128, G_MAXINT);
+		else
+			prefix_int = 128;
 
-		/* Add the new address to the setting */
 		addr = nm_ip_address_new (AF_INET6, address_v, prefix_int, error);
 		if (!addr)
 			return FALSE;
@@ -593,10 +592,9 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 		}
 		nm_ip_address_unref (addr);
 
-		/* gateway */
 		gateway_v = ifparser_getkey (block, "gateway");
 		if (gateway_v) {
-			if (!nm_utils_ipaddr_valid (AF_INET6, gateway_v)) {
+			if (!nm_utils_ipaddr_is_valid (AF_INET6, gateway_v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				             "Invalid IPv6 gateway '%s'", gateway_v);
 				return FALSE;
@@ -614,7 +612,6 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 		if (!nm_setting_ip_config_get_num_dns (s_ip6))
 			_LOGI ("No dns-nameserver configured in /etc/network/interfaces");
 
-		/* DNS searches */
 		search_v = ifparser_getkey (block, "dns-search");
 		if (search_v) {
 			gs_free const char **list = NULL;
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index d19db0fd..808df49f 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -376,7 +376,7 @@ dispose (GObject *object)
 	NMSIfupdownPlugin *plugin = NMS_IFUPDOWN_PLUGIN (object);
 	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (plugin);
 
-	g_clear_pointer (&priv->eni_ifaces, g_hash_table_destroy);
+	nm_clear_pointer (&priv->eni_ifaces, g_hash_table_destroy);
 
 	G_OBJECT_CLASS (nms_ifupdown_plugin_parent_class)->dispose (object);
 }
diff --git a/src/settings/plugins/ifupdown/tests/meson.build b/src/settings/plugins/ifupdown/tests/meson.build
index 1ca094b5..a39e4e21 100644
--- a/src/settings/plugins/ifupdown/tests/meson.build
+++ b/src/settings/plugins/ifupdown/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-ifupdown'
 
 exe = executable(
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index fdb88d2a..2906b399 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -23,7 +23,7 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-core-internal.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 #include "systemd/nm-sd-utils-shared.h"
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index af9e6726..e8c9f9fe 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -9,7 +9,7 @@
 
 #include <sys/stat.h>
 
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 #include "NetworkManagerUtils.h"
 #include "nms-keyfile-utils.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c
index f03c601a..bc8dfd12 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -11,7 +11,7 @@
 #include <sys/stat.h>
 
 #include "nm-glib-aux/nm-io-utils.h"
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 #include "nm-utils.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index fa95198c..7554becc 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -12,7 +12,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
-#include "nm-keyfile-internal.h"
+#include "nm-keyfile/nm-keyfile-internal.h"
 
 #include "nms-keyfile-utils.h"
 #include "nms-keyfile-reader.h"
@@ -60,7 +60,7 @@ cert_writer (NMConnection *connection,
 					 * that would be interpreted as legacy binary format by reader. */
 					tmp = nm_keyfile_detect_unqualified_path_scheme (info->keyfile_dir, p, -1, FALSE, NULL);
 					if (tmp) {
-						g_clear_pointer (&tmp, g_free);
+						nm_clear_g_free (&tmp);
 						accepted_path = p;
 					}
 				}
@@ -71,7 +71,7 @@ cert_writer (NMConnection *connection,
 			 * Otherwise, add a file:// prefix */
 			tmp = nm_keyfile_detect_unqualified_path_scheme (info->keyfile_dir, path, -1, FALSE, NULL);
 			if (tmp) {
-				g_clear_pointer (&tmp, g_free);
+				nm_clear_g_free (&tmp);
 				accepted_path = path;
 			}
 		}
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 7bf9fda0..cd1dc3aa 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-keyfile-settings'
 
 exe = executable(
diff --git a/src/settings/plugins/meson.build b/src/settings/plugins/meson.build
index 83981aab..cde0844b 100644
--- a/src/settings/plugins/meson.build
+++ b/src/settings/plugins/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 if enable_ifcfg_rh
   subdir('ifcfg-rh')
 endif
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index dec4556d..53b6d360 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -14,7 +14,7 @@
 
 #include "nm-supplicant-settings-verify.h"
 #include "nm-setting.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
 #include "nm-setting-ip4-config.h"
@@ -30,13 +30,10 @@ typedef struct {
 typedef struct {
 	GHashTable *config;
 	GHashTable *blobs;
-	guint32    ap_scan;
-	gboolean   fast_required;
-	gboolean   dispose_has_run;
-	gboolean   support_pmf;
-	gboolean   support_fils;
-	gboolean   support_ft;
-	gboolean   support_sha384;
+	NMSupplCapMask capabilities;
+	guint32 ap_scan;
+	bool fast_required:1;
+	bool dispose_has_run:1;
 } NMSupplicantConfigPrivate;
 
 struct _NMSupplicantConfig {
@@ -54,9 +51,15 @@ G_DEFINE_TYPE (NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+static gboolean
+_get_capability (NMSupplicantConfigPrivate *priv,
+                 NMSupplCapType type)
+{
+	return NM_SUPPL_CAP_MASK_GET (priv->capabilities, type) == NM_TERNARY_TRUE;
+}
+
 NMSupplicantConfig *
-nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
-                          gboolean support_ft, gboolean support_sha384)
+nm_supplicant_config_new (NMSupplCapMask capabilities)
 {
 	NMSupplicantConfigPrivate *priv;
 	NMSupplicantConfig *self;
@@ -64,10 +67,7 @@ nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
 	self = g_object_new (NM_TYPE_SUPPLICANT_CONFIG, NULL);
 	priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 
-	priv->support_pmf = support_pmf;
-	priv->support_fils = support_fils;
-	priv->support_ft = support_ft;
-	priv->support_sha384 = support_sha384;
+	priv->capabilities = capabilities;
 
 	return self;
 }
@@ -88,10 +88,6 @@ nm_supplicant_config_init (NMSupplicantConfig * self)
 	                                      g_free,
 	                                      (GDestroyNotify) config_option_free);
 
-	priv->blobs = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                     g_free,
-	                                     (GDestroyNotify) g_bytes_unref);
-
 	priv->ap_scan = 1;
 	priv->dispose_has_run = FALSE;
 }
@@ -224,6 +220,11 @@ nm_supplicant_config_add_blob (NMSupplicantConfig *self,
 	nm_log_info (LOGD_SUPPLICANT, "Config: added '%s' value '%s'", key, opt->value);
 
 	g_hash_table_insert (priv->config, g_strdup (key), opt);
+	if (!priv->blobs) {
+		priv->blobs = g_hash_table_new_full (nm_str_hash, g_str_equal,
+		                                     g_free,
+		                                     (GDestroyNotify) g_bytes_unref);
+	}
 	g_hash_table_insert (priv->blobs,
 	                     g_strdup (blobid),
 	                     g_bytes_ref (value));
@@ -256,10 +257,10 @@ nm_supplicant_config_add_blob_for_connection (NMSupplicantConfig *self,
 static void
 nm_supplicant_config_finalize (GObject *object)
 {
-	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE ((NMSupplicantConfig *) object);
+	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (object);
 
 	g_hash_table_destroy (priv->config);
-	g_hash_table_destroy (priv->blobs);
+	nm_clear_pointer (&priv->blobs, g_hash_table_destroy);
 
 	G_OBJECT_CLASS (nm_supplicant_config_parent_class)->finalize (object);
 }
@@ -768,7 +769,7 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	g_return_val_if_fail (!error || !*error, FALSE);
 
 	/* Check if we actually support FILS */
-	if (!priv->support_fils) {
+	if (!_get_capability (priv, NM_SUPPL_CAP_TYPE_FILS)) {
 		if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED) {
 			g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			                     "Supplicant does not support FILS");
@@ -780,36 +781,40 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting);
 	key_mgmt_conf = g_string_new (key_mgmt);
 	if (nm_streq (key_mgmt, "wpa-psk")) {
-		if (priv->support_pmf)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 			g_string_append (key_mgmt_conf, " wpa-psk-sha256");
-		if (priv->support_ft)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 			g_string_append (key_mgmt_conf, " ft-psk");
 	} else if (nm_streq (key_mgmt, "wpa-eap")) {
-		if (priv->support_pmf)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 			g_string_append (key_mgmt_conf, " wpa-eap-sha256");
-		if (priv->support_ft)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 			g_string_append (key_mgmt_conf, " ft-eap");
-		if (priv->support_ft && priv->support_sha384)
+		if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_FT)
+		    && _get_capability (priv, NM_SUPPL_CAP_TYPE_SHA384))
 			g_string_append (key_mgmt_conf, " ft-eap-sha384");
 		switch (fils) {
 		case NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED:
 			g_string_truncate (key_mgmt_conf, 0);
-			if (!priv->support_pmf)
+			if (!_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 				g_string_assign (key_mgmt_conf, "fils-sha256 fils-sha384");
 			/* fall-through */
 		case NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL:
-			if (priv->support_pmf)
+			if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF))
 				g_string_append (key_mgmt_conf, " fils-sha256 fils-sha384");
-			if (priv->support_pmf && priv->support_ft)
+			if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_PMF)
+			    && _get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 				g_string_append (key_mgmt_conf, " ft-fils-sha256");
-			if (priv->support_pmf && priv->support_ft & priv->support_sha384)
+			if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_PMF)
+			    && _get_capability (priv, NM_SUPPL_CAP_TYPE_FT)
+			    && _get_capability (priv, NM_SUPPL_CAP_TYPE_SHA384))
 				g_string_append (key_mgmt_conf, " ft-fils-sha384");
 			break;
 		default:
 			break;
 		}
 	} else if (nm_streq (key_mgmt, "sae")) {
-		if (priv->support_ft)
+		if (_get_capability (priv, NM_SUPPL_CAP_TYPE_FT))
 			g_string_append (key_mgmt_conf, " ft-sae");
 	}
 
@@ -867,13 +872,13 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		}
 	}
 
-	/* Don't try to enable PMF on non-WPA/SAE networks */
-	if (!NM_IN_STRSET (key_mgmt, "wpa-eap", "wpa-psk", "sae"))
+	/* Don't try to enable PMF on non-WPA/SAE/OWE networks */
+	if (!NM_IN_STRSET (key_mgmt, "wpa-eap", "wpa-psk", "sae", "owe"))
 		pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
 
 	/* Check if we actually support PMF */
 	set_pmf = TRUE;
-	if (!priv->support_pmf) {
+	if (!_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF)) {
 		if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
 			g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
 			                     "Supplicant does not support PMF");
@@ -885,7 +890,8 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	/* Only WPA-specific things when using WPA */
 	if (   !strcmp (key_mgmt, "wpa-psk")
 	    || !strcmp (key_mgmt, "wpa-eap")
-	    || !strcmp (key_mgmt, "sae")) {
+	    || !strcmp (key_mgmt, "sae")
+	    || !strcmp (key_mgmt, "owe")) {
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, proto, protos, "proto", ' ', TRUE, NULL, error))
 			return FALSE;
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, pairwise, pairwise, "pairwise", ' ', TRUE, NULL, error))
@@ -1323,6 +1329,14 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
 	if (!add_string_val (self, value, "domain_suffix_match2", FALSE, NULL, error))
 		return FALSE;
 
+	/* domain match */
+	value = nm_setting_802_1x_get_domain_match (setting);
+	if (!add_string_val (self, value, "domain_match", FALSE, NULL, error))
+		return FALSE;
+	value = nm_setting_802_1x_get_phase2_domain_match (setting);
+	if (!add_string_val (self, value, "domain_match2", FALSE, NULL, error))
+		return FALSE;
+
 	/* Private key */
 	added = FALSE;
 	switch (nm_setting_802_1x_get_private_key_scheme (setting)) {
diff --git a/src/supplicant/nm-supplicant-config.h b/src/supplicant/nm-supplicant-config.h
index 361f9ac6..e1da9a1e 100644
--- a/src/supplicant/nm-supplicant-config.h
+++ b/src/supplicant/nm-supplicant-config.h
@@ -25,8 +25,7 @@ typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
 
 GType nm_supplicant_config_get_type (void);
 
-NMSupplicantConfig *nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
-                                              gboolean support_ft, gboolean support_sha384);
+NMSupplicantConfig *nm_supplicant_config_new (NMSupplCapMask capabilities);
 
 guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self);
 
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index c19da11c..cc3d109e 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -7,49 +7,35 @@
 #include "nm-default.h"
 
 #include "nm-supplicant-interface.h"
-#include "nm-supplicant-manager.h"
 
 #include <stdio.h>
 
 #include "NetworkManagerUtils.h"
-#include "nm-supplicant-config.h"
 #include "nm-core-internal.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-ref-string.h"
 #include "nm-std-aux/nm-dbus-compat.h"
+#include "nm-supplicant-config.h"
+#include "nm-supplicant-manager.h"
+#include "shared/nm-glib-aux/nm-dbus-aux.h"
 
-#define WPAS_DBUS_IFACE_INTERFACE             WPAS_DBUS_INTERFACE ".Interface"
-#define WPAS_DBUS_IFACE_INTERFACE_WPS         WPAS_DBUS_INTERFACE ".Interface.WPS"
-#define WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE  WPAS_DBUS_INTERFACE ".Interface.P2PDevice"
-#define WPAS_DBUS_IFACE_BSS                   WPAS_DBUS_INTERFACE ".BSS"
-#define WPAS_DBUS_IFACE_PEER                  WPAS_DBUS_INTERFACE ".Peer"
-#define WPAS_DBUS_IFACE_GROUP                 WPAS_DBUS_INTERFACE ".Group"
-#define WPAS_DBUS_IFACE_NETWORK               WPAS_DBUS_INTERFACE ".Network"
-#define WPAS_ERROR_INVALID_IFACE              WPAS_DBUS_INTERFACE ".InvalidInterface"
-#define WPAS_ERROR_EXISTS_ERROR               WPAS_DBUS_INTERFACE ".InterfaceExists"
+#define DBUS_TIMEOUT_MSEC 20000
 
 /*****************************************************************************/
 
 typedef struct {
-	GDBusProxy *proxy;
-	gulong change_id;
-} BssData;
-
-typedef struct {
-	GDBusProxy *proxy;
-	gulong change_id;
-} PeerData;
-
-struct _AddNetworkData;
-
-typedef struct {
 	NMSupplicantInterface *self;
 	char *type;
 	char *bssid;
 	char *pin;
-	GDBusProxy *proxy;
+	guint signal_id;
 	GCancellable *cancellable;
-	bool is_cancelling;
+	bool needs_cancelling:1;
+	bool is_cancelling:1;
 } WpsData;
 
+struct _AddNetworkData;
+
 typedef struct {
 	NMSupplicantInterface *self;
 	NMSupplicantConfig *cfg;
@@ -64,34 +50,27 @@ typedef struct {
 typedef struct _AddNetworkData {
 	/* the assoc_data at the time when doing the call. */
 	AssocData *assoc_data;
+	NMRefString *name_owner;
+	NMRefString *object_path;
+	GObject *shutdown_wait_obj;
 } AddNetworkData;
 
-typedef struct {
-	NMSupplicantInterface *self;
-	NMSupplicantInterfaceDisconnectCb callback;
-	gpointer user_data;
-} DisconnectData;
-
 enum {
 	STATE,                   /* change in the interface's state */
-	REMOVED,                 /* interface was removed by the supplicant */
-	BSS_UPDATED,             /* a new BSS appeared or an existing had properties changed */
-	BSS_REMOVED,             /* supplicant removed BSS from its scan list */
-	PEER_UPDATED,            /* a new Peer appeared or an existing had properties changed */
-	PEER_REMOVED,            /* supplicant removed Peer from its scan list */
-	SCAN_DONE,               /* wifi scan is complete */
-	CREDENTIALS_REQUEST,     /* 802.1x identity or password requested */
+	BSS_CHANGED,             /* a new BSS appeared, was updated, or was removed. */
+	PEER_CHANGED,            /* a new Peer appeared, was updated, or was removed */
 	WPS_CREDENTIALS,         /* WPS credentials received */
 	GROUP_STARTED,           /* a new Group (interface) was created */
 	GROUP_FINISHED,          /* a Group (interface) has been finished */
-	GROUP_FORMATION_FAILURE, /* P2P Group formation failed */
 	LAST_SIGNAL
 };
+
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
-	PROP_IFACE,
-	PROP_OBJECT_PATH,
+	PROP_SUPPLICANT_MANAGER,
+	PROP_DBUS_OBJECT_PATH,
+	PROP_IFINDEX,
 	PROP_P2P_GROUP_JOINED,
 	PROP_P2P_GROUP_PATH,
 	PROP_P2P_GROUP_OWNER,
@@ -99,76 +78,83 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_CURRENT_BSS,
 	PROP_DRIVER,
 	PROP_P2P_AVAILABLE,
-	PROP_FAST_SUPPORT,
-	PROP_AP_SUPPORT,
-	PROP_PMF_SUPPORT,
-	PROP_FILS_SUPPORT,
-	PROP_P2P_SUPPORT,
-	PROP_MESH_SUPPORT,
-	PROP_WFD_SUPPORT,
-	PROP_FT_SUPPORT,
-	PROP_SHA384_SUPPORT,
 	PROP_AUTH_STATE,
 );
 
-typedef struct {
-	char *         dev;
-	NMSupplicantDriver driver;
-	gboolean       has_credreq;  /* Whether querying 802.1x credentials is supported */
-	NMSupplicantFeature fast_support;
-	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
-	NMSupplicantFeature pmf_support;
-	NMSupplicantFeature fils_support;
-	NMSupplicantFeature p2p_support;
-	NMSupplicantFeature mesh_support;
-	NMSupplicantFeature wfd_support;
-	NMSupplicantFeature ft_support_global;
-	NMSupplicantFeature ft_support_per_iface;
-	NMSupplicantFeature sha384_support;
-	guint32        max_scan_ssids;
-	guint32        ready_count;
+typedef struct _NMSupplicantInterfacePrivate {
 
-	char *         object_path;
-	NMSupplicantInterfaceState state;
-	int            disconnect_reason;
+	NMSupplicantManager *supplicant_manager;
 
-	bool           scanning:1;
+	GDBusConnection *dbus_connection;
+	NMRefString *name_owner;
+	NMRefString *object_path;
 
-	bool           scan_done_pending:1;
-	bool           scan_done_success:1;
+	char          *ifname;
 
-	GDBusProxy *   wpas_proxy;
-	GCancellable * init_cancellable;
-	GDBusProxy *   iface_proxy;
-	GCancellable * other_cancellable;
-	GDBusProxy *   p2p_proxy;
-	GDBusProxy *   group_proxy;
+	GCancellable  *main_cancellable;
 
-	gboolean       p2p_proxy_acquired;
-	gboolean       group_proxy_acquired;
-	gboolean       p2p_capable;
+	NMRefString   *p2p_group_path;
 
-	gboolean       p2p_group_owner;
+	GCancellable  *p2p_group_properties_cancellable;
 
-	WpsData *wps_data;
+	WpsData       *wps_data;
+
+	AssocData     *assoc_data;
+
+	char          *net_path;
 
-	AssocData *    assoc_data;
+	char          *driver;
 
-	char *         net_path;
-	GHashTable *   bss_proxies;
-	char *         current_bss;
+	GHashTable    *bss_idx;
+	CList          bss_lst_head;
+	CList          bss_initializing_lst_head;
 
-	GHashTable *   peer_proxies;
+	NMRefString   *current_bss;
 
-	gint64         last_scan; /* timestamp as returned by nm_utils_get_monotonic_timestamp_ms() */
+	GHashTable    *peer_idx;
+	CList          peer_lst_head;
+	CList          peer_initializing_lst_head;
+
+	gint64         last_scan_msec;
 
 	NMSupplicantAuthState auth_state;
-} NMSupplicantInterfacePrivate;
 
-struct _NMSupplicantInterface {
-	GObject parent;
-	NMSupplicantInterfacePrivate _priv;
-};
+	NMSupplicantDriver requested_driver;
+	NMSupplCapMask global_capabilities;
+	NMSupplCapMask iface_capabilities;
+
+	guint          properties_changed_id;
+	guint          signal_id;
+	guint          bss_properties_changed_id;
+	guint          peer_properties_changed_id;
+	guint          p2p_group_properties_changed_id;
+
+	int            ifindex;
+
+	int            starting_pending_count;
+
+	guint32        max_scan_ssids;
+
+	gint32         disconnect_reason;
+
+	NMSupplicantInterfaceState state;
+	NMSupplicantInterfaceState supp_state;
+
+	bool           scanning_property:1;
+	bool           scanning_cached:1;
+
+	bool           p2p_capable_property:1;
+	bool           p2p_capable_cached:1;
+
+	bool           p2p_group_owner_property:1;
+	bool           p2p_group_owner_cached:1;
+
+	bool           p2p_group_joined_cached:1;
+
+	bool           is_ready_main:1;
+	bool           is_ready_p2p_device:1;
+
+} NMSupplicantInterfacePrivate;
 
 struct _NMSupplicantInterfaceClass {
 	GObjectClass parent;
@@ -176,420 +162,1003 @@ struct _NMSupplicantInterfaceClass {
 
 G_DEFINE_TYPE (NMSupplicantInterface, nm_supplicant_interface, G_TYPE_OBJECT)
 
-#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE)
+#define NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMSupplicantInterface, NM_IS_SUPPLICANT_INTERFACE)
 
 /*****************************************************************************/
 
+static const char *
+_log_pretty_object_path (NMSupplicantInterfacePrivate *priv)
+{
+	const char *s;
+
+	nm_assert (priv);
+	nm_assert (NM_IS_REF_STRING (priv->object_path));
+
+	s = priv->object_path->str;
+	if (NM_STR_HAS_PREFIX (s, "/fi/w1/wpa_supplicant1/Interfaces/")) {
+		s += NM_STRLEN ("/fi/w1/wpa_supplicant1/Interfaces/");
+		if (   s[0]
+		    && s[0] != '/')
+			return s;
+	}
+	return priv->object_path->str;
+}
+
 #define _NMLOG_DOMAIN           LOGD_SUPPLICANT
 #define _NMLOG_PREFIX_NAME      "sup-iface"
 #define _NMLOG(level, ...) \
     G_STMT_START { \
-         char _sbuf[64]; \
-         const char *__ifname = self ?  NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev : NULL; \
+         NMSupplicantInterface *_self = (self); \
+         NMSupplicantInterfacePrivate *_priv = _self ? NM_SUPPLICANT_INTERFACE_GET_PRIVATE (_self) : NULL; \
+         char _sbuf[255]; \
+         const char *_ifname = _priv ? _priv->ifname : NULL; \
          \
-         nm_log ((level), _NMLOG_DOMAIN, __ifname, NULL, \
+         nm_log ((level), _NMLOG_DOMAIN, _ifname, NULL, \
                  "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
                  _NMLOG_PREFIX_NAME, \
-                 ((self) ? nm_sprintf_buf (_sbuf, "[%p,%s]", (self), __ifname) : "") \
+                 (  _self \
+                  ? nm_sprintf_buf (_sbuf, \
+                                    "["NM_HASH_OBFUSCATE_PTR_FMT",%s,%s]", \
+                                    NM_HASH_OBFUSCATE_PTR (_self), \
+                                    _log_pretty_object_path (_priv), \
+                                    _ifname ?: "???") \
+                  : "") \
                  _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
     } G_STMT_END
 
 /*****************************************************************************/
 
-static void scan_done_emit_signal (NMSupplicantInterface *self);
+static void _starting_check_ready (NMSupplicantInterface *self);
+
+static void assoc_return (NMSupplicantInterface *self,
+                          GError *error,
+                          const char *message);
 
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE (nm_supplicant_interface_state_to_string, NMSupplicantInterfaceState,
-	NM_UTILS_LOOKUP_DEFAULT_WARN ("unknown"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INVALID,         "invalid"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INIT,            "init"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_STARTING,        "starting"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_READY,           "ready"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DISABLED,        "disabled"),
+	NM_UTILS_LOOKUP_DEFAULT_WARN ("internal-unknown"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INVALID,         "internal-invalid"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_STARTING,        "internal-starting"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,  "4way_handshake"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,      "associated"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,     "associating"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING,  "authenticating"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_COMPLETED,       "completed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED,    "disconnected"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, "group_handshake"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_INACTIVE,        "inactive"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DISABLED,        "interface_disabled"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_SCANNING,        "scanning"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING,  "authenticating"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING,     "associating"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED,      "associated"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,  "4-way handshake"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE, "group handshake"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_COMPLETED,       "completed"),
-	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DOWN,            "down"),
+
+	NM_UTILS_LOOKUP_STR_ITEM (NM_SUPPLICANT_INTERFACE_STATE_DOWN,            "internal-down"),
+);
+
+static
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE (
+	wpas_state_string_to_enum,
+	NMSupplicantInterfaceState,
+	{ nm_assert (name); },
+	{ return NM_SUPPLICANT_INTERFACE_STATE_INVALID; },
+	{ "4way_handshake",     NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE  },
+	{ "associated",         NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED      },
+	{ "associating",        NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING     },
+	{ "authenticating",     NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING  },
+	{ "completed",          NM_SUPPLICANT_INTERFACE_STATE_COMPLETED       },
+	{ "disconnected",       NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED    },
+	{ "group_handshake",    NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE },
+	{ "inactive",           NM_SUPPLICANT_INTERFACE_STATE_INACTIVE        },
+	{ "interface_disabled", NM_SUPPLICANT_INTERFACE_STATE_DISABLED        },
+	{ "scanning",           NM_SUPPLICANT_INTERFACE_STATE_SCANNING        },
 );
 
 /*****************************************************************************/
 
+static NM80211ApSecurityFlags
+security_from_vardict (GVariant *security)
+{
+	NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE;
+	const char **array;
+	const char *tmp;
+
+	nm_assert (g_variant_is_of_type (security, G_VARIANT_TYPE_VARDICT));
+
+	if (g_variant_lookup (security, "KeyMgmt", "^a&s", &array)) {
+		if (g_strv_contains (array, "wpa-psk") ||
+		    g_strv_contains (array, "wpa-ft-psk"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK;
+		if (g_strv_contains (array, "wpa-eap") ||
+		    g_strv_contains (array, "wpa-ft-eap") ||
+		    g_strv_contains (array, "wpa-fils-sha256") ||
+		    g_strv_contains (array, "wpa-fils-sha384"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
+		if (g_strv_contains (array, "sae"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_SAE;
+		if (g_strv_contains (array, "owe"))
+			flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE;
+		g_free (array);
+	}
+
+	if (g_variant_lookup (security, "Pairwise", "^a&s", &array)) {
+		if (g_strv_contains (array, "tkip"))
+			flags |= NM_802_11_AP_SEC_PAIR_TKIP;
+		if (g_strv_contains (array, "ccmp"))
+			flags |= NM_802_11_AP_SEC_PAIR_CCMP;
+		g_free (array);
+	}
+
+	if (g_variant_lookup (security, "Group", "&s", &tmp)) {
+		if (nm_streq (tmp, "wep40"))
+			flags |= NM_802_11_AP_SEC_GROUP_WEP40;
+		else if (nm_streq (tmp, "wep104"))
+			flags |= NM_802_11_AP_SEC_GROUP_WEP104;
+		else if (nm_streq (tmp, "tkip"))
+			flags |= NM_802_11_AP_SEC_GROUP_TKIP;
+		else if (nm_streq (tmp, "ccmp"))
+			flags |= NM_802_11_AP_SEC_GROUP_CCMP;
+	}
+
+	return flags;
+}
+
+/*****************************************************************************/
+
+/* Various conditions prevent _starting_check_ready() from completing. For example,
+ * bss_initializing_lst_head, peer_initializing_lst_head and p2p_group_properties_cancellable.
+ * At some places, these conditions might toggle, and it would seems we would have
+ * to call _starting_check_ready() at that point, to ensure we don't miss a state
+ * change that we are ready. However, these places are deep in the call stack and
+ * not suitable to perform this state change. Instead, the callers *MUST* have
+ * added their own starting_pending_count to delay _starting_check_ready().
+ *
+ * Assert that is the case. */
+#define nm_assert_starting_has_pending_count(v) nm_assert ((v) > 0)
+
+/*****************************************************************************/
+
 static void
-bss_data_destroy (gpointer user_data)
+_dbus_connection_call (NMSupplicantInterface *self,
+                       const char *interface_name,
+                       const char *method_name,
+                       GVariant *parameters,
+                       const GVariantType *reply_type,
+                       GDBusCallFlags flags,
+                       int timeout_msec,
+                       GCancellable *cancellable,
+                       GAsyncReadyCallback callback,
+                       gpointer user_data)
 {
-	BssData *bss_data = user_data;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	nm_clear_g_signal_handler (bss_data->proxy, &bss_data->change_id);
-	g_object_unref (bss_data->proxy);
-	g_slice_free (BssData, bss_data);
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner->str,
+	                        priv->object_path->str,
+	                        interface_name,
+	                        method_name,
+	                        parameters,
+	                        reply_type,
+	                        flags,
+	                        timeout_msec,
+	                        cancellable,
+	                        callback,
+	                        user_data);
 }
 
 static void
-bss_proxy_properties_changed_cb (GDBusProxy *proxy,
-                                 GVariant *changed_properties,
-                                 char **invalidated_properties,
+_dbus_connection_call_simple_cb (GObject *source,
+                                 GAsyncResult *result,
                                  gpointer user_data)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+	const char *log_reason;
+	gs_free char *remote_error = NULL;
+
+	nm_utils_user_data_unpack (user_data, &self, &log_reason);
+
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (res) {
+		_LOGT ("call-%s: success", log_reason);
+		return;
+	}
 
-	if (priv->scanning)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+	remote_error = g_dbus_error_get_remote_error (error);
+	if (!nm_streq0 (remote_error, "fi.w1.wpa_supplicant1.NotConnected")) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("call-%s: failed with %s", log_reason, error->message);
+		return;
+	}
 
-	g_signal_emit (self, signals[BSS_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               changed_properties);
+	_LOGT ("call-%s: failed with %s", log_reason, error->message);
 }
 
-static GVariant *
-bss_proxy_get_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
+static void
+_dbus_connection_call_simple (NMSupplicantInterface *self,
+                              const char *interface_name,
+                              const char *method_name,
+                              GVariant *parameters,
+                              const GVariantType *reply_type,
+                              const char *log_reason)
 {
-	gs_strfreev char **properties = NULL;
-	GVariantBuilder builder;
-	char **iter;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
+	_dbus_connection_call (self,
+	                       interface_name,
+	                       method_name,
+	                       parameters,
+	                       reply_type,
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       priv->main_cancellable,
+	                       _dbus_connection_call_simple_cb,
+	                       nm_utils_user_data_pack (self, log_reason));
+}
 
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-	if (iter) {
-		while (*iter) {
-			GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);
+/*****************************************************************************/
 
-			g_variant_builder_add (&builder, "{sv}", *iter++, copy);
-			g_variant_unref (copy);
-		}
-	}
-	return g_variant_builder_end (&builder);
+static void
+_emit_signal_state (NMSupplicantInterface *self,
+                    NMSupplicantInterfaceState new_state,
+                    NMSupplicantInterfaceState old_state,
+                    gint32 disconnect_reason)
+{
+	g_signal_emit (self,
+	               signals[STATE],
+	               0,
+	               (int) new_state,
+	               (int) old_state,
+	               (int) disconnect_reason);
 }
 
+/*****************************************************************************/
+
 static void
-bss_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+_remove_network (NMSupplicantInterface *self)
 {
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	GVariant *props = NULL;
-	const char *object_path;
-	BssData *bss_data;
-	gboolean success;
-
-	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
-	if (   !success
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gs_free char *net_path = NULL;
+
+	if (!priv->net_path)
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	net_path = g_steal_pointer (&priv->net_path);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE,
+	                              "RemoveNetwork",
+	                              g_variant_new ("(o)", net_path),
+	                              G_VARIANT_TYPE ("()"),
+	                              "remove-network");
+}
 
-	if (!success) {
-		_LOGD ("failed to acquire BSS proxy: (%s)", error->message);
-		g_hash_table_remove (priv->bss_proxies,
-		                     g_dbus_proxy_get_object_path (proxy));
-		return;
-	}
+/*****************************************************************************/
+
+static void
+_notify_maybe_scanning (NMSupplicantInterface *self)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gboolean scanning;
+
+	scanning =   nm_supplicant_interface_state_is_operational (priv->state)
+	          && (   priv->scanning_property
+	              || priv->supp_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING);
 
-	object_path = g_dbus_proxy_get_object_path (proxy);
-	bss_data = g_hash_table_lookup (priv->bss_proxies, object_path);
-	if (!bss_data)
+	if (priv->scanning_cached == scanning)
 		return;
 
-	bss_data->change_id = g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (bss_proxy_properties_changed_cb), self);
+	if (   !scanning
+	    && !c_list_is_empty (&priv->bss_initializing_lst_head)) {
+		/* we would change state to indicate we no longer scan. However,
+		 * we still have BSS instances to be initialized. Delay the
+		 * state change further. */
+		return;
+	}
 
-	props = bss_proxy_get_properties (self, proxy);
-	g_signal_emit (self, signals[BSS_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               g_variant_ref_sink (props));
-	g_variant_unref (props);
+	_LOGT ("scanning: %s", scanning ? "yes" : "no");
 
-	if (priv->scan_done_pending)
-		scan_done_emit_signal (self);
+	if (!scanning)
+		priv->last_scan_msec = nm_utils_get_monotonic_timestamp_msec ();
+	else {
+		/* while we are scanning, we set the timestamp to -1. */
+		priv->last_scan_msec = -1;
+	}
+	priv->scanning_cached = scanning;
+	_notify (self, PROP_SCANNING);
 }
 
 static void
-bss_add_new (NMSupplicantInterface *self, const char *object_path)
+_notify_maybe_p2p_available (NMSupplicantInterface *self)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	GDBusProxy *bss_proxy;
-	BssData *bss_data;
+	gboolean value;
 
-	g_return_if_fail (object_path != NULL);
+	value =    priv->is_ready_p2p_device
+	        && priv->p2p_capable_property;
 
-	if (g_hash_table_lookup (priv->bss_proxies, object_path))
+	if (priv->p2p_capable_cached == value)
 		return;
 
-	bss_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-	                          "g-bus-type", G_BUS_TYPE_SYSTEM,
-	                          "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-	                          "g-name", WPAS_DBUS_SERVICE,
-	                          "g-object-path", object_path,
-	                          "g-interface-name", WPAS_DBUS_IFACE_BSS,
-	                          NULL);
-	bss_data = g_slice_new0 (BssData);
-	bss_data->proxy = bss_proxy;
-	g_hash_table_insert (priv->bss_proxies,
-	                     (char *) g_dbus_proxy_get_object_path (bss_proxy),
-	                     bss_data);
-	g_async_initable_init_async (G_ASYNC_INITABLE (bss_proxy),
-	                             G_PRIORITY_DEFAULT,
-	                             priv->other_cancellable,
-	                             (GAsyncReadyCallback) bss_proxy_acquired_cb,
-	                             self);
+	priv->p2p_capable_cached = value;
+	_notify (self, PROP_P2P_AVAILABLE);
 }
 
 static void
-peer_data_destroy (gpointer user_data)
+_notify_maybe_p2p_group (NMSupplicantInterface *self)
 {
-	PeerData *peer_data = user_data;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gboolean value_joined;
+	gboolean value_owner;
+	gboolean joined_changed;
+	gboolean owner_changed;
+
+	value_joined =    priv->p2p_group_path
+	               && !priv->p2p_group_properties_cancellable;
+	value_owner =    value_joined
+	              && priv->p2p_group_owner_property;
+
+	if ((joined_changed = (priv->p2p_group_joined_cached != value_joined)))
+		priv->p2p_group_joined_cached = value_joined;
 
-	nm_clear_g_signal_handler (peer_data->proxy, &peer_data->change_id);
-	g_object_unref (peer_data->proxy);
-	g_slice_free (PeerData, peer_data);
+	if ((owner_changed = (priv->p2p_group_owner_cached != value_owner)))
+		priv->p2p_group_owner_cached = value_owner;
+
+	if (joined_changed)
+		_notify (self, PROP_P2P_GROUP_JOINED);
+	if (owner_changed)
+		_notify (self, PROP_P2P_GROUP_OWNER);
 }
 
+/*****************************************************************************/
+
 static void
-peer_proxy_properties_changed_cb (GDBusProxy *proxy,
-                                  GVariant *changed_properties,
-                                  char **invalidated_properties,
-                                  gpointer user_data)
+_bss_info_destroy (NMSupplicantBssInfo *bss_info)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	g_signal_emit (self, signals[PEER_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               changed_properties);
+	c_list_unlink_stale (&bss_info->_bss_lst);
+	nm_clear_g_cancellable (&bss_info->_init_cancellable);
+	g_bytes_unref (bss_info->ssid);
+	nm_ref_string_unref (bss_info->bss_path);
+	nm_g_slice_free (bss_info);
 }
 
-static GVariant *
-peer_proxy_get_properties (NMSupplicantInterface *self, GDBusProxy *proxy)
+static void
+_bss_info_changed_emit (NMSupplicantInterface *self,
+                        NMSupplicantBssInfo *bss_info,
+                        gboolean is_present)
 {
-	gs_strfreev char **properties = NULL;
-	GVariantBuilder builder;
-	char **iter;
+	_LOGT ("BSS %s %s",
+	       bss_info->bss_path->str,
+	       is_present ? "updated" : "deleted");
+	g_signal_emit (self,
+	               signals[BSS_CHANGED],
+	               0,
+	               bss_info,
+	               is_present);
+}
 
-	iter = properties = g_dbus_proxy_get_cached_property_names (proxy);
+static void
+_bss_info_properties_changed (NMSupplicantInterface *self,
+                              NMSupplicantBssInfo *bss_info,
+                              GVariant *properties,
+                              gboolean initial)
+{
+	gboolean v_b;
+	GVariant *v_v;
+	const char *v_s;
+	gint16 v_i16;
+	guint16 v_u16;
+	guint32 v_u32;
+	NM80211ApFlags p_ap_flags;
+	NM80211Mode p_mode;
+	guint8 p_signal_percent;
+	const guint8 *arr_data;
+	gsize arr_len;
+	guint32 p_max_rate;
+	gboolean p_max_rate_has;
+	gint64 now_msec = 0;
+
+	if (nm_g_variant_lookup (properties, "Age", "u", &v_u32)) {
+		bss_info->last_seen_msec =   nm_utils_get_monotonic_timestamp_msec_cached (&now_msec)
+		                           - (((gint64) v_u32) * 1000);
+	} else if (initial) {
+		/* Unknown Age. Assume we just received it. */
+		bss_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
+	}
+
+	p_ap_flags = bss_info->ap_flags;
+	if (nm_g_variant_lookup (properties, "Privacy", "b", &v_b))
+		p_ap_flags = NM_FLAGS_ASSIGN (p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY, v_b);
+	else {
+		nm_assert (  !initial
+		           || !NM_FLAGS_HAS (p_ap_flags, NM_802_11_AP_FLAGS_PRIVACY));
+	}
+	v_v = nm_g_variant_lookup_value (properties, "WPS", G_VARIANT_TYPE_VARDICT);
+	if (   v_v
+	    || initial) {
+		NM80211ApFlags f = NM_802_11_AP_FLAGS_NONE;
+
+		if (v_v) {
+			if (g_variant_lookup (v_v, "Type", "&s", &v_s)) {
+				p_ap_flags = NM_802_11_AP_FLAGS_WPS;
+				if (nm_streq (v_s, "pcb"))
+					f |= NM_802_11_AP_FLAGS_WPS_PBC;
+				else if (nm_streq (v_s, "pin"))
+					f |= NM_802_11_AP_FLAGS_WPS_PIN;
+			}
+			g_variant_unref (v_v);
+		}
+		p_ap_flags = NM_FLAGS_ASSIGN_MASK (p_ap_flags,
+		                                     NM_802_11_AP_FLAGS_WPS
+		                                   | NM_802_11_AP_FLAGS_WPS_PBC
+		                                   | NM_802_11_AP_FLAGS_WPS_PIN,
+		                                   f);
+	}
+	if (bss_info->ap_flags != p_ap_flags) {
+		bss_info->ap_flags = p_ap_flags;
+		nm_assert (bss_info->ap_flags == p_ap_flags);
+	}
+
+	if (nm_g_variant_lookup (properties, "Mode", "&s", &v_s)) {
+		if (nm_streq (v_s, "infrastructure"))
+			p_mode = NM_802_11_MODE_INFRA;
+		else if (nm_streq (v_s, "ad-hoc"))
+			p_mode = NM_802_11_MODE_ADHOC;
+		else if (nm_streq (v_s, "mesh"))
+			p_mode = NM_802_11_MODE_MESH;
+		else
+			p_mode = NM_802_11_MODE_UNKNOWN;
+	} else if (initial)
+		p_mode = NM_802_11_MODE_UNKNOWN;
+	else
+		p_mode = bss_info->mode;
+	if (bss_info->mode != p_mode) {
+		bss_info->mode = p_mode;
+		nm_assert (bss_info->mode == p_mode);
+	}
+
+	if (nm_g_variant_lookup (properties, "Signal", "n", &v_i16))
+		p_signal_percent = nm_wifi_utils_level_to_quality (v_i16);
+	else if (initial)
+		p_signal_percent = 0;
+	else
+		p_signal_percent = bss_info->signal_percent;
+	bss_info->signal_percent = p_signal_percent;
+
+	if (nm_g_variant_lookup (properties, "Frequency", "q", &v_u16))
+		bss_info->frequency = v_u16;
+
+	v_v = nm_g_variant_lookup_value (properties, "SSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		arr_len = MIN (32, arr_len);
+
+		/* Stupid ieee80211 layer uses <hidden> */
+		if (   arr_data
+		    && arr_len
+		    && !(   NM_IN_SET (arr_len, 8, 9)
+		         && memcmp (arr_data, "<hidden>", arr_len) == 0)
+		    && !nm_utils_is_empty_ssid (arr_data, arr_len)) {
+			/* good */
+		} else
+			arr_len = 0;
 
-	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-	if (iter) {
-		while (*iter) {
-			GVariant *copy = g_dbus_proxy_get_cached_property (proxy, *iter);
+		if (!nm_utils_gbytes_equal_mem (bss_info->ssid, arr_data, arr_len)) {
+			_nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer (&bss_info->ssid);
 
-			g_variant_builder_add (&builder, "{sv}", *iter++, copy);
-			g_variant_unref (copy);
+			bss_info->ssid =   (arr_len == 0)
+			                 ? NULL
+			                 : g_bytes_new (arr_data, arr_len);
 		}
+
+		g_variant_unref (v_v);
+	} else {
+		nm_assert (   !initial
+		           || !bss_info->ssid);
+	}
+
+	v_v = nm_g_variant_lookup_value (properties, "BSSID", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		if (   arr_len == ETH_ALEN
+		    && memcmp (arr_data, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (arr_data, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0) {
+			/* pass */
+		} else
+			arr_len = 0;
+
+		if (arr_len != 0) {
+			nm_assert (arr_len == sizeof (bss_info->bssid));
+			bss_info->bssid_valid = TRUE;
+			memcpy (bss_info->bssid, arr_data, sizeof (bss_info->bssid));
+		} else if (bss_info->bssid_valid) {
+			bss_info->bssid_valid = FALSE;
+			memset (bss_info->bssid, 0, sizeof (bss_info->bssid));
+		}
+		g_variant_unref (v_v);
+	} else {
+		nm_assert (   !initial
+		           || !bss_info->bssid_valid);
+	}
+	nm_assert (   (   bss_info->bssid_valid
+	               && !nm_utils_memeqzero (bss_info->bssid, sizeof (bss_info->bssid)))
+	           || (  !bss_info->bssid_valid
+	               && nm_utils_memeqzero (bss_info->bssid, sizeof (bss_info->bssid))));
+
+	p_max_rate_has = FALSE;
+	p_max_rate = 0;
+	v_v = nm_g_variant_lookup_value (properties, "Rates", G_VARIANT_TYPE ("au"));
+	if (v_v) {
+		const guint32 *rates = g_variant_get_fixed_array (v_v, &arr_len, sizeof (guint32));
+		gsize i;
+
+		for (i = 0; i < arr_len; i++)
+			p_max_rate = NM_MAX (p_max_rate, rates[i]);
+		p_max_rate_has = TRUE;
+		g_variant_unref (v_v);
+	}
+	v_v = nm_g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		gboolean p_owe_transition_mode;
+		gboolean p_metered;
+		guint32 rate;
+
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		nm_wifi_utils_parse_ies (arr_data, arr_len, &rate, &p_metered, &p_owe_transition_mode);
+		p_max_rate = NM_MAX (p_max_rate, rate);
+		p_max_rate_has = TRUE;
+		g_variant_unref (v_v);
+
+		if (p_owe_transition_mode)
+			bss_info->rsn_flags |= NM_802_11_AP_SEC_KEY_MGMT_OWE;
+		else
+			bss_info->rsn_flags &= ~NM_802_11_AP_SEC_KEY_MGMT_OWE;
+
+		bss_info->metered = p_metered;
 	}
-	return g_variant_builder_end (&builder);
+	if (p_max_rate_has)
+		bss_info->max_rate = p_max_rate / 1000u;
+
+	v_v = nm_g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		bss_info->wpa_flags = security_from_vardict (v_v);
+		g_variant_unref (v_v);
+	}
+
+	v_v = nm_g_variant_lookup_value (properties, "RSN", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		bss_info->rsn_flags = security_from_vardict (v_v);
+		g_variant_unref (v_v);
+	}
+
+	_bss_info_changed_emit (self, bss_info, TRUE);
 }
 
 static void
-peer_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+_bss_info_get_all_cb (GVariant *result,
+                      GError *error,
+                      gpointer user_data)
 {
+	NMSupplicantBssInfo *bss_info;
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	GVariant *props = NULL;
-	const char *object_path;
-	PeerData *peer_data;
-	gboolean success;
-
-	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
-	if (   !success
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
+	bss_info = user_data;
+	self = bss_info->_self;
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (!success) {
-		_LOGD ("failed to acquire Peer proxy: (%s)", error->message);
-		g_hash_table_remove (priv->peer_proxies,
-		                     g_dbus_proxy_get_object_path (proxy));
-		return;
-	}
+	g_clear_object (&bss_info->_init_cancellable);
+	nm_c_list_move_tail (&priv->bss_lst_head, &bss_info->_bss_lst);
 
-	object_path = g_dbus_proxy_get_object_path (proxy);
-	peer_data = g_hash_table_lookup (priv->peer_proxies, object_path);
-	if (!peer_data)
-		return;
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
 
-	peer_data->change_id = g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (peer_proxy_properties_changed_cb), self);
+	_bss_info_properties_changed (self, bss_info, properties, TRUE);
 
-	props = peer_proxy_get_properties (self, proxy);
+	_starting_check_ready (self);
 
-	g_signal_emit (self, signals[PEER_UPDATED], 0,
-	               g_dbus_proxy_get_object_path (proxy),
-	               g_variant_ref_sink (props));
-	g_variant_unref (props);
+	_notify_maybe_scanning (self);
 }
 
 static void
-peer_add_new (NMSupplicantInterface *self, const char *object_path)
+_bss_info_add (NMSupplicantInterface *self, const char *object_path)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	GDBusProxy *peer_proxy;
-	PeerData *peer_data;
+	nm_auto_ref_string NMRefString *bss_path = NULL;
+	NMSupplicantBssInfo *bss_info;
 
-	g_return_if_fail (object_path != NULL);
+	bss_path = nm_ref_string_new (nm_dbus_path_not_empty (object_path));
+	if (!bss_path)
+		return;
 
-	if (g_hash_table_lookup (priv->peer_proxies, object_path))
+	bss_info = g_hash_table_lookup (priv->bss_idx, &bss_path);
+	if (bss_info) {
+		bss_info->_bss_dirty = FALSE;
 		return;
+	}
 
-	peer_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-	                           "g-bus-type", G_BUS_TYPE_SYSTEM,
-	                           "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-	                           "g-name", WPAS_DBUS_SERVICE,
-	                           "g-object-path", object_path,
-	                           "g-interface-name", WPAS_DBUS_IFACE_PEER,
-	                           NULL);
-	peer_data = g_slice_new0 (PeerData);
-	peer_data->proxy = peer_proxy;
-	g_hash_table_insert (priv->peer_proxies,
-	                     (char *) g_dbus_proxy_get_object_path (peer_proxy),
-	                     peer_data);
-	g_async_initable_init_async (G_ASYNC_INITABLE (peer_proxy),
-	                             G_PRIORITY_DEFAULT,
-	                             priv->other_cancellable,
-	                             (GAsyncReadyCallback) peer_proxy_acquired_cb,
-	                             self);
+	bss_info = g_slice_new (NMSupplicantBssInfo);
+	*bss_info = (NMSupplicantBssInfo) {
+		._self             = self,
+		.bss_path          = g_steal_pointer (&bss_path),
+		._init_cancellable = g_cancellable_new (),
+	};
+	c_list_link_tail (&priv->bss_initializing_lst_head, &bss_info->_bss_lst);
+	g_hash_table_add (priv->bss_idx, bss_info);
+
+	nm_dbus_connection_call_get_all (priv->dbus_connection,
+	                                 priv->name_owner->str,
+	                                 bss_info->bss_path->str,
+	                                 NM_WPAS_DBUS_IFACE_BSS,
+	                                 5000,
+	                                 bss_info->_init_cancellable,
+	                                 _bss_info_get_all_cb,
+	                                 bss_info);
+}
+
+static gboolean
+_bss_info_remove (NMSupplicantInterface *self,
+                  NMRefString **p_bss_path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantBssInfo *bss_info;
+	gpointer unused_but_required;
+
+	if (!g_hash_table_steal_extended (priv->bss_idx,
+	                                  p_bss_path,
+	                                  (gpointer *) &bss_info,
+	                                  &unused_but_required))
+		return FALSE;
+
+	c_list_unlink (&bss_info->_bss_lst);
+	if (!bss_info->_init_cancellable)
+		_bss_info_changed_emit (self, bss_info, FALSE);
+	_bss_info_destroy (bss_info);
+
+	nm_assert_starting_has_pending_count (priv->starting_pending_count);
+
+	return TRUE;
 }
 
 /*****************************************************************************/
 
 static void
-set_state (NMSupplicantInterface *self, NMSupplicantInterfaceState new_state)
+_peer_info_destroy (NMSupplicantPeerInfo *peer_info)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	NMSupplicantInterfaceState old_state = priv->state;
+	c_list_unlink (&peer_info->_peer_lst);
+	nm_clear_g_cancellable (&peer_info->_init_cancellable);
 
-	if (new_state == priv->state)
-		return;
+	g_free (peer_info->device_name);
+	g_free (peer_info->manufacturer);
+	g_free (peer_info->model);
+	g_free (peer_info->model_number);
+	g_free (peer_info->serial);
+	g_bytes_unref (peer_info->ies);
+
+	nm_g_slice_free (peer_info);
+}
+
+static void
+_peer_info_changed_emit (NMSupplicantInterface *self,
+                         NMSupplicantPeerInfo *peer_info,
+                         gboolean is_present)
+{
+	g_signal_emit (self,
+	               signals[PEER_CHANGED],
+	               0,
+	               peer_info,
+	               is_present);
+}
+
+static void
+_peer_info_properties_changed (NMSupplicantInterface *self,
+                               NMSupplicantPeerInfo *peer_info,
+                               GVariant *properties,
+                               gboolean initial)
+{
+	GVariant *v_v;
+	const char *v_s;
+	gint32 v_i32;
+	const guint8 *arr_data;
+	gsize arr_len;
+
+	peer_info->last_seen_msec = nm_utils_get_monotonic_timestamp_msec ();
 
-	/* DOWN is a terminal state */
-	g_return_if_fail (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	if (nm_g_variant_lookup (properties, "level", "i", &v_i32))
+		peer_info->signal_percent = nm_wifi_utils_level_to_quality (v_i32);
 
-	/* Cannot regress to READY, STARTING, or INIT from higher states */
-	if (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY)
-		g_return_if_fail (new_state > NM_SUPPLICANT_INTERFACE_STATE_READY);
+	if (nm_g_variant_lookup (properties, "DeviceName", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->device_name, v_s);
 
-	if (new_state == NM_SUPPLICANT_INTERFACE_STATE_READY) {
-		nm_clear_g_cancellable (&priv->other_cancellable);
-		priv->other_cancellable = g_cancellable_new ();
-	} else if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
-		nm_clear_g_cancellable (&priv->init_cancellable);
-		nm_clear_g_cancellable (&priv->other_cancellable);
+	if (nm_g_variant_lookup (properties, "Manufacturer", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->manufacturer, v_s);
 
-		if (priv->iface_proxy)
-			g_signal_handlers_disconnect_by_data (priv->iface_proxy, self);
+	if (nm_g_variant_lookup (properties, "Model", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->model, v_s);
+
+	if (nm_g_variant_lookup (properties, "ModelNumber", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->model_number, v_s);
+
+	if (nm_g_variant_lookup (properties, "Serial", "&s", &v_s))
+		nm_utils_strdup_reset (&peer_info->serial, v_s);
+
+	v_v = nm_g_variant_lookup_value (properties, "DeviceAddress", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		if (   arr_len == ETH_ALEN
+		    && memcmp (arr_data, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
+		    && memcmp (arr_data, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0) {
+			/* pass */
+		} else
+			arr_len = 0;
+
+		if (arr_len != 0) {
+			nm_assert (arr_len == sizeof (peer_info->address));
+			peer_info->address_valid = TRUE;
+			memcpy (peer_info->address, arr_data, sizeof (peer_info->address));
+		} else if (peer_info->address_valid) {
+			peer_info->address_valid = FALSE;
+			memset (peer_info->address, 0, sizeof (peer_info->address));
+		}
+		g_variant_unref (v_v);
+	} else {
+		nm_assert (   !initial
+		           || !peer_info->address_valid);
 	}
+	nm_assert (   (   peer_info->address_valid
+	               && !nm_utils_memeqzero (peer_info->address, sizeof (peer_info->address)))
+	           || (  !peer_info->address_valid
+	               && nm_utils_memeqzero (peer_info->address, sizeof (peer_info->address))));
 
-	priv->state = new_state;
+	/* The IEs property contains the WFD R1 subelements */
+	v_v = nm_g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v_v) {
+		arr_data = g_variant_get_fixed_array (v_v, &arr_len, 1);
+		if (!nm_utils_gbytes_equal_mem (peer_info->ies, arr_data, arr_len)) {
+			_nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer (&peer_info->ies);
 
-	if (   priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING
-	    || old_state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+			peer_info->ies = g_bytes_new (arr_data, arr_len);
+		} else if (   arr_len == 0
+		           && !peer_info->ies)
+			peer_info->ies = g_bytes_new (NULL, 0);
+		g_variant_unref (v_v);
+	}
 
-	/* Disconnect reason is no longer relevant when not in the DISCONNECTED state */
-	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED)
-		priv->disconnect_reason = 0;
+	_peer_info_changed_emit (self, peer_info, TRUE);
+}
 
-	g_signal_emit (self, signals[STATE], 0,
-	               (int) priv->state,
-	               (int) old_state,
-	               (int) priv->disconnect_reason);
-}
-
-static NMSupplicantInterfaceState
-wpas_state_string_to_enum (const char *str_state)
-{
-	if (!strcmp (str_state, "interface_disabled"))
-		return NM_SUPPLICANT_INTERFACE_STATE_DISABLED;
-	else if (!strcmp (str_state, "disconnected"))
-		return NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED;
-	else if (!strcmp (str_state, "inactive"))
-		return NM_SUPPLICANT_INTERFACE_STATE_INACTIVE;
-	else if (!strcmp (str_state, "scanning"))
-		return NM_SUPPLICANT_INTERFACE_STATE_SCANNING;
-	else if (!strcmp (str_state, "authenticating"))
-		return NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING;
-	else if (!strcmp (str_state, "associating"))
-		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING;
-	else if (!strcmp (str_state, "associated"))
-		return NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED;
-	else if (!strcmp (str_state, "4way_handshake"))
-		return NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE;
-	else if (!strcmp (str_state, "group_handshake"))
-		return NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE;
-	else if (!strcmp (str_state, "completed"))
-		return NM_SUPPLICANT_INTERFACE_STATE_COMPLETED;
-
-	return NM_SUPPLICANT_INTERFACE_STATE_INVALID;
+static void
+_peer_info_get_all_cb (GVariant *result,
+                       GError *error,
+                       gpointer user_data)
+{
+	NMSupplicantPeerInfo *peer_info;
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	peer_info = user_data;
+	self = peer_info->_self;
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	g_clear_object (&peer_info->_init_cancellable);
+	nm_c_list_move_tail (&priv->peer_lst_head, &peer_info->_peer_lst);
+
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
+
+	_peer_info_properties_changed (self, peer_info, properties, TRUE);
+
+	_starting_check_ready (self);
 }
 
 static void
-set_state_from_string (NMSupplicantInterface *self, const char *new_state)
+_peer_info_add (NMSupplicantInterface *self, const char *object_path)
 {
-	NMSupplicantInterfaceState state;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *peer_path = NULL;
+	NMSupplicantPeerInfo *peer_info;
 
-	state = wpas_state_string_to_enum (new_state);
-	if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID) {
-		_LOGW ("unknown supplicant state '%s'", new_state);
+	peer_path = nm_ref_string_new (nm_dbus_path_not_empty (object_path));
+	if (!peer_path)
+		return;
+
+	peer_info = g_hash_table_lookup (priv->peer_idx, &peer_path);
+
+	if (peer_info) {
+		peer_info->_peer_dirty = FALSE;
 		return;
 	}
-	set_state (self, state);
+
+	peer_info = g_slice_new (NMSupplicantPeerInfo);
+	*peer_info = (NMSupplicantPeerInfo) {
+		._self             = self,
+		.peer_path         = g_steal_pointer (&peer_path),
+		._init_cancellable = g_cancellable_new (),
+	};
+	c_list_link_tail (&priv->peer_initializing_lst_head, &peer_info->_peer_lst);
+	g_hash_table_add (priv->peer_idx, peer_info);
+
+	nm_dbus_connection_call_get_all (priv->dbus_connection,
+	                                 priv->name_owner->str,
+	                                 peer_info->peer_path->str,
+	                                 NM_WPAS_DBUS_IFACE_PEER,
+	                                 5000,
+	                                 peer_info->_init_cancellable,
+	                                 _peer_info_get_all_cb,
+	                                 peer_info);
 }
 
+static gboolean
+_peer_info_remove (NMSupplicantInterface *self,
+                   NMRefString **p_peer_path)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantPeerInfo *peer_info;
+	gpointer unused_but_required;
+
+	if (!g_hash_table_steal_extended (priv->peer_idx,
+	                                  p_peer_path,
+	                                  (gpointer *) &peer_info,
+	                                  &unused_but_required))
+		return FALSE;
+
+	c_list_unlink (&peer_info->_peer_lst);
+	if (!peer_info->_init_cancellable)
+		_peer_info_changed_emit (self, peer_info, FALSE);
+	_peer_info_destroy (peer_info);
+
+	nm_assert_starting_has_pending_count (priv->starting_pending_count);
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 static void
-set_scanning (NMSupplicantInterface *self, gboolean new_scanning)
+set_state_down (NMSupplicantInterface *self,
+                gboolean force_remove_from_supplicant,
+                const char *reason)
 {
+	_nm_unused gs_unref_object NMSupplicantInterface *self_keep_alive = g_object_ref (self);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantBssInfo *bss_info;
+	NMSupplicantPeerInfo *peer_info;
+	NMSupplicantInterfaceState old_state;
+
+	nm_assert (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	nm_assert (!c_list_is_empty (&self->supp_lst));
+
+	_LOGD ("remove interface \"%s\" on %s (%s)%s",
+	       priv->object_path->str,
+	       priv->name_owner->str,
+	       reason,
+	       force_remove_from_supplicant ? " (remove in wpa_supplicant)" : "");
+
+	old_state = priv->state;
+
+	priv->state = NM_SUPPLICANT_INTERFACE_STATE_DOWN;
+
+	_nm_supplicant_manager_unregister_interface (priv->supplicant_manager, self);
+
+	nm_assert (c_list_is_empty (&self->supp_lst));
+
+	if (force_remove_from_supplicant) {
+		_nm_supplicant_manager_dbus_call_remove_interface (priv->supplicant_manager,
+		                                                   priv->name_owner->str,
+		                                                   priv->object_path->str);
+	}
+
+	_emit_signal_state (self, priv->state, old_state, 0);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->properties_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->signal_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->bss_properties_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->peer_properties_changed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection, &priv->p2p_group_properties_changed_id);
+
+	nm_supplicant_interface_cancel_wps (self);
 
-	if (priv->scanning != new_scanning) {
-		priv->scanning = new_scanning;
+	if (priv->assoc_data) {
+		gs_free_error GError *error = NULL;
+
+		nm_utils_error_set_cancelled (&error, TRUE, "NMSupplicantInterface");
+		assoc_return (self, error, "cancelled because supplicant interface is going down");
+	}
 
-		/* Cache time of last scan completion */
-		if (priv->scanning == FALSE)
-			priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
+	while ((bss_info = c_list_first_entry (&priv->bss_initializing_lst_head, NMSupplicantBssInfo, _bss_lst))) {
+		g_hash_table_remove (priv->bss_idx, bss_info);
+		_bss_info_destroy (bss_info);
+	}
+	while ((bss_info = c_list_first_entry (&priv->bss_lst_head, NMSupplicantBssInfo, _bss_lst))) {
+		g_hash_table_remove (priv->bss_idx, bss_info);
+		_bss_info_destroy (bss_info);
+	}
+	nm_assert (g_hash_table_size (priv->bss_idx) == 0);
 
-		_notify (self, PROP_SCANNING);
+	while ((peer_info = c_list_first_entry (&priv->peer_initializing_lst_head, NMSupplicantPeerInfo, _peer_lst))) {
+		g_hash_table_remove (priv->peer_idx, peer_info);
+		_peer_info_destroy (peer_info);
 	}
+	while ((peer_info = c_list_first_entry (&priv->peer_lst_head, NMSupplicantPeerInfo, _peer_lst))) {
+		g_hash_table_remove (priv->peer_idx, peer_info);
+		_peer_info_destroy (peer_info);
+	}
+	nm_assert (g_hash_table_size (priv->peer_idx) == 0);
+
+	nm_clear_g_cancellable (&priv->main_cancellable);
+	nm_clear_g_cancellable (&priv->p2p_group_properties_cancellable);
+
+	nm_clear_pointer (&priv->p2p_group_path, nm_ref_string_unref);
+
+	_remove_network (self);
+
+	nm_clear_pointer (&priv->current_bss, nm_ref_string_unref);
+
+	_notify_maybe_scanning (self);
 }
 
-gboolean
-nm_supplicant_interface_get_scanning (NMSupplicantInterface *self)
+static void
+set_state (NMSupplicantInterface *self, NMSupplicantInterfaceState new_state)
 {
-	NMSupplicantInterfacePrivate *priv;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterfaceState old_state = priv->state;
 
-	g_return_val_if_fail (self, FALSE);
+	nm_assert (new_state > NM_SUPPLICANT_INTERFACE_STATE_STARTING);
+	nm_assert (new_state < NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+	nm_assert (nm_supplicant_interface_state_is_operational (new_state));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	if (priv->scanning)
-		return TRUE;
-	if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
-		return TRUE;
-	return FALSE;
+	nm_assert (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_STARTING);
+	nm_assert (priv->state < NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+
+	if (new_state == priv->state)
+		return;
+
+	_LOGT ("set state \"%s\" (was \"%s\")",
+	       nm_supplicant_interface_state_to_string (new_state),
+	       nm_supplicant_interface_state_to_string (priv->state));
+
+	priv->state = new_state;
+
+	_emit_signal_state (self,
+	                    priv->state,
+	                    old_state,
+	                      priv->state != NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED
+	                    ? 0u
+	                    : priv->disconnect_reason);
 }
 
-const char *
+NMRefString *
 nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self)
 {
-	NMSupplicantInterfacePrivate *priv;
-
 	g_return_val_if_fail (self != NULL, FALSE);
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	return priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY ? priv->current_bss : NULL;
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->current_bss;
+}
+
+gboolean
+nm_supplicant_interface_get_scanning (NMSupplicantInterface *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->scanning_cached;
 }
 
 gint64
 nm_supplicant_interface_get_last_scan (NMSupplicantInterface *self)
 {
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan;
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->last_scan_msec;
 }
 
 #define MATCH_PROPERTY(p, n, v, t) (!strcmp (p, n) && g_variant_is_of_type (v, t))
@@ -600,33 +1169,31 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 	gboolean have_active = FALSE;
 	gboolean have_ssid = FALSE;
-	gboolean have_p2p = FALSE;
 	gboolean have_ft = FALSE;
 	gint32 max_scan_ssids = -1;
 	const char **array;
 
-	g_return_if_fail (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
+	nm_assert (capabilities && g_variant_is_of_type (capabilities, G_VARIANT_TYPE_VARDICT));
 
 	if (g_variant_lookup (capabilities, "KeyMgmt", "^a&s", &array)) {
 		have_ft = g_strv_contains (array, "wpa-ft-psk");
 		g_free (array);
 	}
 
-	priv->ft_support_per_iface =   have_ft
-	                             ? NM_SUPPLICANT_FEATURE_YES
-	                             : NM_SUPPLICANT_FEATURE_NO;
+	priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET (priv->iface_capabilities,
+	                                                  NM_SUPPL_CAP_TYPE_FT,
+	                                                    have_ft
+	                                                  ? NM_TERNARY_TRUE
+	                                                  : NM_TERNARY_FALSE);
 
 	if (g_variant_lookup (capabilities, "Modes", "^a&s", &array)) {
-		if (g_strv_contains (array, "p2p"))
-			have_p2p = TRUE;
+		/* Setting p2p_capable might toggle _prop_p2p_available_get(). However,
+		 * we don't need to check for a property changed notification, because
+		 * the caller did g_object_freeze_notify() and will perform the check. */
+		priv->p2p_capable_property = g_strv_contains (array, "p2p");
 		g_free (array);
 	}
 
-	if (priv->p2p_capable != have_p2p) {
-		priv->p2p_capable = have_p2p;
-		_notify (self, PROP_P2P_AVAILABLE);
-	}
-
 	if (g_variant_lookup (capabilities, "Scan", "^a&s", &array)) {
 		if (g_strv_contains (array, "active"))
 			have_active = TRUE;
@@ -638,187 +1205,116 @@ parse_capabilities (NMSupplicantInterface *self, GVariant *capabilities)
 	if (g_variant_lookup (capabilities, "MaxScanSSID", "i", &max_scan_ssids)) {
 		/* We need active scan and SSID probe capabilities to care about MaxScanSSIDs */
 		if (max_scan_ssids > 0 && have_active && have_ssid) {
-			/* wpa_supplicant's WPAS_MAX_SCAN_SSIDS value is 16, but for speed
+			/* wpa_supplicant's NM_WPAS_MAX_SCAN_SSIDS value is 16, but for speed
 			 * and to ensure we don't disclose too many SSIDs from the hidden
 			 * list, we'll limit to 5.
 			 */
 			priv->max_scan_ssids = CLAMP (max_scan_ssids, 0, 5);
-			_LOGI ("supports %d scan SSIDs", priv->max_scan_ssids);
+			_LOGD ("supports %d scan SSIDs", priv->max_scan_ssids);
 		}
 	}
 }
 
 static void
-iface_check_ready (NMSupplicantInterface *self)
+_starting_check_ready (NMSupplicantInterface *self)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (priv->ready_count && priv->state < NM_SUPPLICANT_INTERFACE_STATE_READY) {
-		priv->ready_count--;
-		if (priv->ready_count == 0)
-			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_READY);
-	}
-}
-
-gboolean
-nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
-                                           const char *field,
-                                           const char *value,
-                                           GError **error)
-{
-	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
-
-	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), FALSE);
-	g_return_val_if_fail (field != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	g_return_val_if_fail (priv->has_credreq == TRUE, FALSE);
-
-	/* Need a network block object path */
-	g_return_val_if_fail (priv->net_path, FALSE);
-	reply = g_dbus_proxy_call_sync (priv->iface_proxy,
-	                                "NetworkReply",
-	                                g_variant_new ("(oss)",
-	                                               priv->net_path,
-	                                               field,
-	                                               value),
-	                                G_DBUS_CALL_FLAGS_NONE,
-	                                5000,
-	                                NULL,
-	                                error);
-	if (error && *error)
-		g_dbus_error_strip_remote_error (*error);
-
-	return !!reply;
-}
-
-static void
-iface_check_netreply_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *variant = NULL;
-	gs_free_error GError *error = NULL;
-
-	/* We know NetworkReply is supported if the NetworkReply method returned
-	 * successfully (which is unexpected since we sent a bogus network
-	 * object path) or if we got an "InvalidArgs" (which indicates NetworkReply
-	 * is supported).  We know it's not supported if we get an
-	 * "UnknownMethod" error.
-	 */
-
-	variant = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_STARTING)
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (variant || _nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.InvalidArgs"))
-		priv->has_credreq = TRUE;
-
-	_LOGD ("supplicant %s network credentials requests",
-	       priv->has_credreq ? "supports" : "does not support");
-
-	iface_check_ready (self);
-}
+	if (priv->starting_pending_count > 0)
+		return;
 
-static void
-iface_set_pmf_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *variant = NULL;
-	gs_free_error GError *error = NULL;
+	if (!c_list_is_empty (&priv->bss_initializing_lst_head))
+		return;
 
-	variant = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (!c_list_is_empty (&priv->peer_initializing_lst_head))
 		return;
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
+	if (priv->p2p_group_properties_cancellable)
+		return;
 
-	if (error)
-		_LOGW ("failed to set Pmf=1: %s", error->message);
+	nm_assert (priv->state == NM_SUPPLICANT_INTERFACE_STATE_STARTING);
 
-	iface_check_ready (self);
-}
+	if (!nm_supplicant_interface_state_is_operational (priv->supp_state)) {
+		_LOGW ("Supplicant state is unknown during initialization. Destroy the interface");
+		set_state_down (self, TRUE, "failure to get valid interface state");
+		return;
+	}
 
-gboolean
-nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->group_proxy_acquired;
+	set_state (self, priv->supp_state);
 }
 
-const char*
-nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self)
+static NMTernary
+_get_capability (NMSupplicantInterfacePrivate *priv,
+                 NMSupplCapType type)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMTernary value;
+	NMTernary iface_value;
 
-	if (priv->group_proxy_acquired)
-		return g_dbus_proxy_get_object_path (priv->group_proxy);
-	else
-		return NULL;
+	switch (type) {
+	case NM_SUPPL_CAP_TYPE_AP:
+		iface_value = NM_SUPPL_CAP_MASK_GET (priv->iface_capabilities, type);
+		value = NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, type);
+		value = MAX (iface_value, value);
+		break;
+	case NM_SUPPL_CAP_TYPE_FT:
+		value = NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, type);
+		if (value != NM_TERNARY_FALSE) {
+			iface_value = NM_SUPPL_CAP_MASK_GET (priv->iface_capabilities, type);
+			if (iface_value != NM_TERNARY_DEFAULT)
+				value = iface_value;
+		}
+		break;
+	default:
+		nm_assert (NM_SUPPL_CAP_MASK_GET (priv->iface_capabilities, type) == NM_TERNARY_DEFAULT);
+		value = NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, type);
+		break;
+	}
+	return value;
 }
 
-gboolean
-nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self)
+NMTernary
+nm_supplicant_interface_get_capability (NMSupplicantInterface *self,
+                                        NMSupplCapType type)
 {
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_owner;
+	return _get_capability (NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self), type);
 }
 
-NMSupplicantFeature
-nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
+NMSupplCapMask
+nm_supplicant_interface_get_capabilities (NMSupplicantInterface *self)
 {
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ap_support;
-}
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplCapMask caps;
 
-NMSupplicantFeature
-nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->pmf_support;
-}
+	caps = priv->global_capabilities;
+	caps = NM_SUPPL_CAP_MASK_SET (caps, NM_SUPPL_CAP_TYPE_AP, _get_capability (priv, NM_SUPPL_CAP_TYPE_AP));
+	caps = NM_SUPPL_CAP_MASK_SET (caps, NM_SUPPL_CAP_TYPE_FT, _get_capability (priv, NM_SUPPL_CAP_TYPE_FT));
 
-NMSupplicantFeature
-nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->fils_support;
-}
+	nm_assert (!NM_FLAGS_ANY (priv->iface_capabilities,
+	                          ~(  NM_SUPPL_CAP_MASK_T_AP_MASK
+	                            | NM_SUPPL_CAP_MASK_T_FT_MASK)));
 
-NMSupplicantFeature
-nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_support;
-}
+#if NM_MORE_ASSERTS > 10
+	{
+		NMSupplCapType type;
 
-NMSupplicantFeature
-nm_supplicant_interface_get_mesh_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->mesh_support;
-}
+		for (type = 0; type < _NM_SUPPL_CAP_TYPE_NUM; type++)
+			nm_assert (NM_SUPPL_CAP_MASK_GET (caps, type) == _get_capability (priv, type));
+	}
+#endif
 
-NMSupplicantFeature
-nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->wfd_support;
+	return caps;
 }
 
-NMSupplicantFeature
-nm_supplicant_interface_get_ft_support (NMSupplicantInterface *self)
+void
+nm_supplicant_interface_set_global_capabilities (NMSupplicantInterface *self,
+                                                 NMSupplCapMask value)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (priv->ft_support_global == NM_SUPPLICANT_FEATURE_NO)
-		return NM_SUPPLICANT_FEATURE_NO;
-	if (priv->ft_support_per_iface != NM_SUPPLICANT_FEATURE_UNKNOWN)
-		return priv->ft_support_per_iface;
-	return priv->ft_support_global;
-}
-
-NMSupplicantFeature
-nm_supplicant_interface_get_sha384_support (NMSupplicantInterface *self)
-{
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->sha384_support;
+	priv->global_capabilities = value;
 }
 
 NMSupplicantAuthState
@@ -827,270 +1323,296 @@ nm_supplicant_interface_get_auth_state (NMSupplicantInterface *self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->auth_state;
 }
 
-void
-nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
-                                        NMSupplicantFeature ap_support)
+/*****************************************************************************/
+
+static void
+_p2p_group_properties_changed (NMSupplicantInterface *self,
+                               GVariant *properties)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char *s;
 
-	/* Use the best indicator of support between the supplicant global
-	 * Capabilities property and the interface's introspection data.
-	 */
-	if (ap_support > priv->ap_support)
-		priv->ap_support = ap_support;
+	if (!properties)
+		priv->p2p_group_owner_property = FALSE;
+	else if (g_variant_lookup (properties, "Role", "&s", &s))
+		priv->p2p_group_owner_property = nm_streq (s, "GO");
+
+	_notify_maybe_p2p_group (self);
 }
 
-void
-nm_supplicant_interface_set_fast_support (NMSupplicantInterface *self,
-                                          NMSupplicantFeature fast_support)
+static void
+_p2p_group_properties_changed_cb (GDBusConnection *connection,
+                                  const char *sender_name,
+                                  const char *object_path,
+                                  const char *signal_interface_name,
+                                  const char *signal_name,
+                                  GVariant *parameters,
+                                  gpointer user_data)
 {
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gs_unref_variant GVariant *changed_properties = NULL;
 
-	priv->fast_support = fast_support;
-}
+	if (priv->p2p_group_properties_cancellable)
+		return;
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
 
-void
-nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
-                                         NMSupplicantFeature pmf_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               NULL,
+	               &changed_properties,
+	               NULL);
 
-	priv->pmf_support = pmf_support;
+	_p2p_group_properties_changed (self, changed_properties);
 }
 
-void
-nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
-                                          NMSupplicantFeature fils_support)
+static void
+_p2p_group_properties_get_all_cb (GVariant *result,
+                                  GError *error,
+                                  gpointer user_data)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	gs_unref_variant GVariant *properties = NULL;
 
-	priv->fils_support = fils_support;
-}
+	if (nm_utils_error_is_cancelled (error))
+		return;
 
-void
-nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
-                                         NMSupplicantFeature p2p_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	self = NM_SUPPLICANT_INTERFACE (user_data);
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	priv->p2p_support = p2p_support;
-}
+	g_object_freeze_notify (G_OBJECT (self));
 
-void
-nm_supplicant_interface_set_mesh_support (NMSupplicantInterface *self,
-                                          NMSupplicantFeature mesh_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_clear_g_cancellable (&priv->p2p_group_properties_cancellable);
 
-	priv->mesh_support = mesh_support;
-}
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
 
-void
-nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
-                                         NMSupplicantFeature wfd_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	_p2p_group_properties_changed (self, properties);
 
-	priv->wfd_support = wfd_support;
-}
+	_starting_check_ready (self);
 
-void
-nm_supplicant_interface_set_ft_support (NMSupplicantInterface *self,
-                                        NMSupplicantFeature ft_support)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	_notify_maybe_p2p_group (self);
 
-	priv->ft_support_global = ft_support;
+	g_object_thaw_notify (G_OBJECT (self));
 }
 
-void
-nm_supplicant_interface_set_sha384_support (NMSupplicantInterface *self,
-                                            NMSupplicantFeature sha384_support)
+static void
+_p2p_group_set_path (NMSupplicantInterface *self,
+                     const char *path)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *group_path = NULL;
+
+	group_path = nm_ref_string_new (nm_dbus_path_not_empty (path));
 
-	priv->sha384_support = sha384_support;
+	if (priv->p2p_group_path == group_path)
+		return;
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->p2p_group_properties_changed_id);
+	nm_clear_g_cancellable (&priv->p2p_group_properties_cancellable);
+
+	nm_ref_string_unref (priv->p2p_group_path);
+	priv->p2p_group_path = g_steal_pointer (&group_path);
+
+	if (priv->p2p_group_path) {
+		priv->p2p_group_properties_cancellable = g_cancellable_new ();
+		priv->p2p_group_properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+		                                                                                                priv->name_owner->str,
+		                                                                                                priv->p2p_group_path->str,
+		                                                                                                NM_WPAS_DBUS_IFACE_GROUP,
+		                                                                                                _p2p_group_properties_changed_cb,
+		                                                                                                self,
+		                                                                                                NULL);
+		nm_dbus_connection_call_get_all (priv->dbus_connection,
+		                                 priv->name_owner->str,
+		                                 priv->p2p_group_path->str,
+		                                 NM_WPAS_DBUS_IFACE_GROUP,
+		                                 5000,
+		                                 priv->p2p_group_properties_cancellable,
+		                                 _p2p_group_properties_get_all_cb,
+		                                 self);
+	}
+
+	_notify (self, PROP_P2P_GROUP_PATH);
+	_notify_maybe_p2p_group (self);
+
+	nm_assert_starting_has_pending_count (priv->starting_pending_count);
 }
 
 /*****************************************************************************/
 
 static void
-_wps_data_free (WpsData *data)
+_wps_data_free (WpsData *wps_data,
+                GDBusConnection *dbus_connection)
 {
-	g_free (data->type);
-	g_free (data->pin);
-	g_free (data->bssid);
-	g_clear_object (&data->cancellable);
-	if (data->proxy && data->self)
-		g_signal_handlers_disconnect_by_data (data->proxy, data->self);
-	g_clear_object (&data->proxy);
-	g_slice_free (WpsData, data);
+	nm_clear_g_dbus_connection_signal (dbus_connection,
+	                                   &wps_data->signal_id);
+	nm_clear_g_cancellable (&wps_data->cancellable);
+	g_free (wps_data->type);
+	g_free (wps_data->pin);
+	g_free (wps_data->bssid);
+	nm_g_slice_free (wps_data);
 }
 
 static void
-_wps_credentials_changed_cb (GDBusProxy *proxy,
-                             GVariant *props,
+_wps_credentials_changed_cb (GDBusConnection *connection,
+                             const char *sender_name,
+                             const char *object_path,
+                             const char *signal_interface_name,
+                             const char *signal_name,
+                             GVariant *parameters,
                              gpointer user_data)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterface *self = user_data;
+	gs_unref_variant GVariant *props = NULL;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
+		return;
+
+	g_variant_get (parameters, "(@a{sv})", &props);
 
 	_LOGT ("wps: new credentials");
 	g_signal_emit (self, signals[WPS_CREDENTIALS], 0, props);
 }
 
 static void
-_wps_handle_start_cb (GObject *source_object,
-                      GAsyncResult *res,
+_wps_handle_start_cb (GObject *source,
+                      GAsyncResult *result,
                       gpointer user_data)
 {
 	NMSupplicantInterface *self;
-	WpsData *data;
-	gs_unref_variant GVariant *result = NULL;
+	WpsData *wps_data;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
-	if (   !result
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
-	data = user_data;
-	self = data->self;
+	wps_data = user_data;
+	self = wps_data->self;
 
-	if (result)
+	if (res)
 		_LOGT ("wps: started with success");
 	else
 		_LOGW ("wps: start failed with %s", error->message);
 
-	g_clear_object (&data->cancellable);
-	nm_clear_g_free (&data->type);
-	nm_clear_g_free (&data->pin);
-	nm_clear_g_free (&data->bssid);
+	g_clear_object (&wps_data->cancellable);
+	nm_clear_g_free (&wps_data->type);
+	nm_clear_g_free (&wps_data->pin);
+	nm_clear_g_free (&wps_data->bssid);
 }
 
 static void
-_wps_handle_set_pc_cb (GObject *source_object,
-                       GAsyncResult *res,
+_wps_handle_set_pc_cb (GVariant *res,
+                       GError *error,
                        gpointer user_data)
 {
-	WpsData *data;
 	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *result = NULL;
-	gs_free_error GError *error = NULL;
+	NMSupplicantInterfacePrivate *priv;
+	WpsData *wps_data;
 	GVariantBuilder start_args;
 	guint8 bssid_buf[ETH_ALEN];
 
-	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
-	if (   !result
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
-	data = user_data;
-	self = data->self;
+	wps_data = user_data;
+	self = wps_data->self;
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (result)
+	if (res)
 		_LOGT ("wps: ProcessCredentials successfully set, starting...");
 	else
 		_LOGW ("wps: ProcessCredentials failed to set (%s), starting...", error->message);
 
-	_nm_dbus_signal_connect (data->proxy, "Credentials", G_VARIANT_TYPE ("(a{sv})"),
-	                         G_CALLBACK (_wps_credentials_changed_cb), self);
+	wps_data->signal_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+	                                                          priv->name_owner->str,
+	                                                          NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+	                                                          "Credentials",
+	                                                          priv->object_path->str,
+	                                                          NULL,
+	                                                          G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                          _wps_credentials_changed_cb,
+	                                                          self,
+	                                                          NULL);
 
 	g_variant_builder_init (&start_args, G_VARIANT_TYPE_VARDICT);
 	g_variant_builder_add (&start_args, "{sv}", "Role", g_variant_new_string ("enrollee"));
-	g_variant_builder_add (&start_args, "{sv}", "Type", g_variant_new_string (data->type));
-	if (data->pin)
-		g_variant_builder_add (&start_args, "{sv}", "Pin", g_variant_new_string (data->pin));
-
-	if (data->bssid) {
+	g_variant_builder_add (&start_args, "{sv}", "Type", g_variant_new_string (wps_data->type));
+	if (wps_data->pin)
+		g_variant_builder_add (&start_args, "{sv}", "Pin", g_variant_new_string (wps_data->pin));
+	if (wps_data->bssid) {
 		/* The BSSID is in fact not mandatory. If it is not set the supplicant would
 		 * enroll with any BSS in range. */
-		if (!nm_utils_hwaddr_aton (data->bssid, bssid_buf, sizeof (bssid_buf)))
+		if (!nm_utils_hwaddr_aton (wps_data->bssid, bssid_buf, sizeof (bssid_buf)))
 			nm_assert_not_reached ();
 		g_variant_builder_add (&start_args, "{sv}", "Bssid",
 		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, bssid_buf,
 		                                                  ETH_ALEN, sizeof (guint8)));
 	}
 
-	g_dbus_proxy_call (data->proxy,
-	                   "Start",
-	                   g_variant_new ("(a{sv})", &start_args),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   data->cancellable,
-	                   _wps_handle_start_cb,
-	                   data);
-}
+	wps_data->needs_cancelling = TRUE;
+	if (!wps_data->cancellable)
+		wps_data->cancellable = g_cancellable_new ();
 
-static void
-_wps_call_set_pc (WpsData *data)
-{
-	g_dbus_proxy_call (data->proxy,
-	                   "org.freedesktop.DBus.Properties.Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE_WPS,
-	                                  "ProcessCredentials",
-	                                  g_variant_new_boolean (TRUE)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   data->cancellable,
-	                   _wps_handle_set_pc_cb,
-	                   data);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+	                       "Start",
+	                       g_variant_new ("(a{sv})", &start_args),
+	                       G_VARIANT_TYPE ("(a{sv})"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       5000,
+	                       wps_data->cancellable,
+	                       _wps_handle_start_cb,
+	                       wps_data);
 }
 
 static void
-_wps_handle_proxy_cb (GObject *source_object,
-                      GAsyncResult *res,
-                      gpointer user_data)
+_wps_call_set_pc (NMSupplicantInterface *self,
+                  WpsData *wps_data)
 {
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	WpsData *data;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *proxy;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
-	if (   !proxy
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
+	if (!wps_data->cancellable)
+		wps_data->cancellable = g_cancellable_new ();
 
-	data = user_data;
-	self = data->self;
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (!proxy) {
-		_LOGW ("wps: failure to create D-Bus proxy: %s", error->message);
-		_wps_data_free (data);
-		priv->wps_data = NULL;
-		return;
-	}
-
-	data->proxy = proxy;
-	_LOGT ("wps: D-Bus proxy created. set ProcessCredentials...");
-	_wps_call_set_pc (data);
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+	                             "ProcessCredentials",
+	                             g_variant_new_boolean (TRUE),
+	                             5000,
+	                             wps_data->cancellable,
+	                             _wps_handle_set_pc_cb,
+	                             wps_data);
 }
 
 static void
-_wps_handle_cancel_cb (GObject *source_object,
-                       GAsyncResult *res,
+_wps_handle_cancel_cb (GObject *source,
+                       GAsyncResult *result,
                        gpointer user_data)
 {
+	GDBusConnection *dbus_connection = G_DBUS_CONNECTION (source);
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	WpsData *data;
-	gs_unref_variant GVariant *result = NULL;
+	WpsData *wps_data;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
-	if (   !result
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
+	res = g_dbus_connection_call_finish (dbus_connection, result, &error);
+	nm_assert (!nm_utils_error_is_cancelled (error));
 
-	data = user_data;
-	self = data->self;
+	wps_data = user_data;
+	self = wps_data->self;
 
 	if (!self) {
-		_wps_data_free (data);
-		if (result)
+		_wps_data_free (wps_data, dbus_connection);
+		if (res)
 			_LOGT ("wps: cancel completed successfully, after supplicant interface is gone");
 		else
 			_LOGW ("wps: cancel failed (%s), after supplicant interface is gone", error->message);
@@ -1099,23 +1621,24 @@ _wps_handle_cancel_cb (GObject *source_object,
 
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	data->is_cancelling = FALSE;
+	wps_data->is_cancelling = FALSE;
 
-	if (!data->type) {
+	if (!wps_data->type) {
 		priv->wps_data = NULL;
-		_wps_data_free (data);
-		if (result)
+		_wps_data_free (wps_data, dbus_connection);
+		if (res)
 			_LOGT ("wps: cancel completed successfully");
 		else
 			_LOGW ("wps: cancel failed (%s)", error->message);
 		return;
 	}
 
-	if (result)
+	if (res)
 		_LOGT ("wps: cancel completed successfully, setting ProcessCredentials now...");
 	else
 		_LOGW ("wps: cancel failed (%s), setting ProcessCredentials now...", error->message);
-	_wps_call_set_pc (data);
+
+	_wps_call_set_pc (self, wps_data);
 }
 
 static void
@@ -1125,74 +1648,71 @@ _wps_start (NMSupplicantInterface *self,
             const char *pin)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	WpsData *data = priv->wps_data;
+	WpsData *wps_data;
 
 	if (type)
 		_LOGI ("wps: type %s start...", type);
 
-	if (!data) {
+	wps_data = priv->wps_data;
+
+	if (!wps_data) {
 		if (!type)
 			return;
 
-		data = g_slice_new0 (WpsData);
-		data->self = self;
-		data->type = g_strdup (type);
-		data->bssid = g_strdup (bssid);
-		data->pin = g_strdup (pin);
-		data->cancellable = g_cancellable_new ();
-
-		priv->wps_data = data;
-
-		_LOGT ("wps: create D-Bus proxy...");
-
-		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-		                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-		                          NULL,
-		                          WPAS_DBUS_SERVICE,
-		                          priv->object_path,
-		                          WPAS_DBUS_IFACE_INTERFACE_WPS,
-		                          data->cancellable,
-		                          _wps_handle_proxy_cb,
-		                          data);
-		return;
-	}
-
-	g_free (data->type);
-	g_free (data->bssid);
-	g_free (data->pin);
-	data->type = g_strdup (type);
-	data->bssid = g_strdup (bssid);
-	data->pin = g_strdup (pin);
+		if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
+			_LOGD ("wps: interface is down. Cannot start with WPS");
+			return;
+		}
 
-	if (!data->proxy) {
-		if (!type) {
-			nm_clear_g_cancellable (&data->cancellable);
-			priv->wps_data = NULL;
-			_wps_data_free (data);
+		wps_data = g_slice_new (WpsData);
+		*wps_data = (WpsData) {
+			.self        = self,
+			.type        = g_strdup (type),
+			.bssid       = g_strdup (bssid),
+			.pin         = g_strdup (pin),
+		};
+		priv->wps_data = wps_data;
+	} else {
+		g_free (wps_data->type);
+		g_free (wps_data->bssid);
+		g_free (wps_data->pin);
+		wps_data->type = g_strdup (type);
+		wps_data->bssid = g_strdup (bssid);
+		wps_data->pin = g_strdup (pin);
+	}
 
-			_LOGT ("wps: abort creation of D-Bus proxy");
-		} else
-			_LOGT ("wps: new enrollment. Wait for D-Bus proxy...");
+	if (wps_data->is_cancelling) {
+		/* we wait for cancellation to complete. */
 		return;
 	}
 
-	if (data->is_cancelling)
+	if (   !type
+	    || wps_data->needs_cancelling) {
+
+		_LOGT ("wps: cancel %senrollment...",
+		       wps_data->needs_cancelling ? "previous " : "");
+
+		wps_data->is_cancelling = TRUE;
+		wps_data->needs_cancelling = FALSE;
+		nm_clear_g_cancellable (&wps_data->cancellable);
+		nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+		                                   &wps_data->signal_id);
+
+		_dbus_connection_call (self,
+		                       NM_WPAS_DBUS_IFACE_INTERFACE_WPS,
+		                       "Cancel",
+		                       NULL,
+		                       G_VARIANT_TYPE ("()"),
+		                       G_DBUS_CALL_FLAGS_NONE,
+		                       5000,
+		                       NULL,
+		                       _wps_handle_cancel_cb,
+		                       wps_data);
 		return;
+	}
 
-	_LOGT ("wps: cancel previous enrollment...");
-
-	data->is_cancelling = TRUE;
-	nm_clear_g_cancellable (&data->cancellable);
-	data->cancellable = g_cancellable_new ();
-	g_signal_handlers_disconnect_by_data (data->proxy, self);
-	g_dbus_proxy_call (data->proxy,
-	                   "Cancel",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   data->cancellable,
-	                   _wps_handle_cancel_cb,
-	                   data);
+	_LOGT ("wps: setting ProcessCredentials...");
+	_wps_call_set_pc (self, wps_data);
 }
 
 void
@@ -1213,926 +1733,199 @@ nm_supplicant_interface_cancel_wps (NMSupplicantInterface *self)
 /*****************************************************************************/
 
 static void
-iface_introspect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+iface_introspect_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *variant = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 	const char *data;
+	NMTernary value;
 
-	variant = _nm_dbus_proxy_call_finish (proxy, result,
-	                                      G_VARIANT_TYPE ("(s)"),
-	                                      &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	if (variant) {
-		g_variant_get (variant, "(&s)", &data);
-
-		/* The ProbeRequest method only exists if AP mode has been enabled */
-		if (strstr (data, "ProbeRequest"))
-			priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
-	}
-
-	iface_check_ready (self);
-}
-
-static void
-scan_done_emit_signal (NMSupplicantInterface *self)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char *object_path;
-	BssData *bss_data;
-	gboolean success;
-	GHashTableIter iter;
-
-	g_hash_table_iter_init (&iter, priv->bss_proxies);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &bss_data)) {
-		/* we have some BSS' that need to be initialized first. Delay
-		 * emitting signal. */
-		if (!bss_data->change_id) {
-			priv->scan_done_pending = TRUE;
-			return;
-		}
-	}
+	nm_assert (NM_SUPPL_CAP_MASK_GET (priv->global_capabilities, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_DEFAULT);
 
-	/* Emit BSS_UPDATED so that wifi device has the APs (in case it removed them) */
-	g_hash_table_iter_init (&iter, priv->bss_proxies);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &object_path, (gpointer *) &bss_data)) {
-		gs_unref_variant GVariant *props = NULL;
+	value = NM_TERNARY_DEFAULT;
+	if (res) {
+		g_variant_get (res, "(&s)", &data);
 
-		props = bss_proxy_get_properties (self, bss_data->proxy);
-		g_signal_emit (self, signals[BSS_UPDATED], 0,
-		               object_path,
-		               g_variant_ref_sink (props));
+		/* The ProbeRequest method only exists if AP mode has been enabled */
+		value =   strstr (data, "ProbeRequest")
+		        ? NM_TERNARY_TRUE
+		        : NM_TERNARY_FALSE;
 	}
 
-	success = priv->scan_done_success;
-	priv->scan_done_success = FALSE;
-	priv->scan_done_pending = FALSE;
-	g_signal_emit (self, signals[SCAN_DONE], 0, success);
-}
-
-static void
-wpas_iface_scan_done (GDBusProxy *proxy,
-                      gboolean success,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	/* Cache last scan completed time */
-	priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
-	priv->scan_done_success |= success;
-	scan_done_emit_signal (self);
-}
-
-static void
-wpas_iface_bss_added (GDBusProxy *proxy,
-                      const char *path,
-                      GVariant *props,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (priv->scanning)
-		priv->last_scan = nm_utils_get_monotonic_timestamp_ms ();
-
-	bss_add_new (self, path);
-}
-
-static void
-wpas_iface_bss_removed (GDBusProxy *proxy,
-                        const char *path,
-                        gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	BssData *bss_data;
-
-	bss_data = g_hash_table_lookup (priv->bss_proxies, path);
-	if (!bss_data)
-		return;
-	g_hash_table_steal (priv->bss_proxies, path);
-	g_signal_emit (self, signals[BSS_REMOVED], 0, path);
-	bss_data_destroy (bss_data);
-}
-
-static void
-wpas_iface_network_request (GDBusProxy *proxy,
-                            const char *path,
-                            const char *field,
-                            const char *message,
-                            gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (priv->has_credreq && priv->net_path && !g_strcmp0 (path, priv->net_path))
-		g_signal_emit (self, signals[CREDENTIALS_REQUEST], 0, field, message);
-}
-
-static void
-eap_changed (GDBusProxy *proxy,
-             const char *status,
-             const char *parameter,
-             gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	NMSupplicantAuthState auth_state = NM_SUPPLICANT_AUTH_STATE_UNKNOWN;
-
-	if (nm_streq0 (status, "started"))
-		auth_state = NM_SUPPLICANT_AUTH_STATE_STARTED;
-	else if (nm_streq0 (status, "completion")) {
-		if (nm_streq0 (parameter, "success"))
-			auth_state = NM_SUPPLICANT_AUTH_STATE_SUCCESS;
-		else if (nm_streq0 (parameter, "failure"))
-			auth_state = NM_SUPPLICANT_AUTH_STATE_FAILURE;
-	}
+	priv->iface_capabilities = NM_SUPPL_CAP_MASK_SET (priv->iface_capabilities, NM_SUPPL_CAP_TYPE_AP, value);
 
-	/* the state eventually reaches one of started, success or failure
-	 * so ignore any other intermediate (unknown) state change. */
-	if (   auth_state != NM_SUPPLICANT_AUTH_STATE_UNKNOWN
-	    && auth_state != priv->auth_state) {
-		priv->auth_state = auth_state;
-		_notify (self, PROP_AUTH_STATE);
-	}
+	priv->starting_pending_count--;
+	_starting_check_ready (self);
 }
 
 static void
-props_changed_cb (GDBusProxy *proxy,
-                  GVariant *changed_properties,
-                  GStrv invalidated_properties,
-                  gpointer user_data)
+_properties_changed_main (NMSupplicantInterface *self,
+                          GVariant *properties)
 {
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char *s, **array, **iter;
-	gboolean b = FALSE;
-	gint32 i32;
-	GVariant *v;
-
-	g_object_freeze_notify (G_OBJECT (self));
+	const char **v_strv;
+	const char *v_s;
+	gboolean v_b;
+	gint32 v_i32;
+	GVariant *v_v;
+	gboolean do_log_driver_info = FALSE;
+	gboolean do_set_state = FALSE;
+	gboolean do_notify_current_bss = FALSE;
 
-	if (g_variant_lookup (changed_properties, "Scanning", "b", &b))
-		set_scanning (self, b);
+	nm_assert (properties || g_variant_is_of_type (properties, G_VARIANT_TYPE ("a{sv}")));
 
-	if (   g_variant_lookup (changed_properties, "State", "&s", &s)
-	    && priv->state >= NM_SUPPLICANT_INTERFACE_STATE_READY) {
-		/* Only transition to actual wpa_supplicant interface states (ie,
-		 * anything > READY) after the NMSupplicantInterface has had a
-		 * chance to initialize, which is signalled by entering the READY
-		 * state.
-		 */
-		set_state_from_string (self, s);
+	v_v = g_variant_lookup_value (properties, "Capabilities", G_VARIANT_TYPE_VARDICT);
+	if (v_v) {
+		parse_capabilities (self, v_v);
+		g_variant_unref (v_v);
 	}
 
-	if (g_variant_lookup (changed_properties, "BSSs", "^a&o", &array)) {
-		iter = array;
-		while (*iter)
-			bss_add_new (self, *iter++);
-		g_free (array);
-	}
+	if (nm_g_variant_lookup (properties, "Scanning", "b", &v_b))
+		priv->scanning_property = v_b;
 
-	if (g_variant_lookup (changed_properties, "CurrentBSS", "&o", &s)) {
-		s = nm_utils_dbus_normalize_object_path (s);
-		if (!nm_streq0 (s, priv->current_bss)) {
-			g_free (priv->current_bss);
-			priv->current_bss = g_strdup (s);
-			_notify (self, PROP_CURRENT_BSS);
-		}
+	if (nm_g_variant_lookup (properties, "Ifname", "&s", &v_s)) {
+		if (nm_utils_strdup_reset (&priv->ifname, v_s))
+			do_log_driver_info = TRUE;
 	}
-
-	v = g_variant_lookup_value (changed_properties, "Capabilities", G_VARIANT_TYPE_VARDICT);
-	if (v) {
-		parse_capabilities (self, v);
-		g_variant_unref (v);
+	if (nm_g_variant_lookup (properties, "Driver", "&s", &v_s)) {
+		if (nm_utils_strdup_reset (&priv->driver, v_s))
+			do_log_driver_info = TRUE;
 	}
 
-	if (g_variant_lookup (changed_properties, "DisconnectReason", "i", &i32)) {
+	if (nm_g_variant_lookup (properties, "DisconnectReason", "i", &v_i32)) {
 		/* Disconnect reason is currently only given for deauthentication events,
 		 * not disassociation; currently they are IEEE 802.11 "reason codes",
 		 * defined by (IEEE 802.11-2007, 7.3.1.7, Table 7-22).  Any locally caused
 		 * deauthentication will be negative, while authentications caused by the
 		 * AP will be positive.
 		 */
-		priv->disconnect_reason = i32;
-		if (priv->disconnect_reason != 0)
-			_LOGW ("connection disconnected (reason %d)", priv->disconnect_reason);
-	}
-
-	/* We may not have priv->dev set yet if this interface was created from a
-	 * known wpa_supplicant interface without knowing the device name.
-	 */
-	if (priv->dev == NULL && g_variant_lookup (changed_properties, "Ifname", "&s", &s)) {
-		priv->dev = g_strdup (s);
-		_notify (self, PROP_IFACE);
-	}
-
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-group_props_changed_cb (GDBusProxy *proxy,
-                        GVariant *changed_properties,
-                        char **invalidated_properties,
-                        gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	char *s;
-
-	g_object_freeze_notify (G_OBJECT (self));
-
-#if 0
-	v = g_variant_lookup_value (properties, "BSSID", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		if (   len == ETH_ALEN
-		    && memcmp (bytes, nm_ip_addr_zero.addr_eth, ETH_ALEN) != 0
-		    && memcmp (bytes, (char[ETH_ALEN]) { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, ETH_ALEN) != 0)
-			nm_wifi_p2p_group_set_bssid_bin (group, bytes);
-		g_variant_unref (v);
-	}
-
-	v = g_variant_lookup_value (properties, "SSID", G_VARIANT_TYPE_BYTESTRING);
-	if (v) {
-		bytes = g_variant_get_fixed_array (v, &len, 1);
-		len = MIN (32, len);
-
-		/* Stupid ieee80211 layer uses <hidden> */
-		if (   bytes && len
-		    && !(((len == 8) || (len == 9)) && !memcmp (bytes, "<hidden>", 8))
-		    && !nm_utils_is_empty_ssid (bytes, len))
-			nm_wifi_p2p_group_set_ssid (group, bytes, len);
-
-		g_variant_unref (v);
-	}
-#endif
-
-	if (g_variant_lookup (changed_properties, "Role", "s", &s)) {
-		priv->p2p_group_owner = g_strcmp0 (s, "GO") == 0;
-		_notify (self, PROP_P2P_GROUP_OWNER);
-		g_free (s);
-	}
-
-	/* NOTE: We do not seem to get any property change notifications for the Members
-	 *       property. However, we can keep track of these indirectly either by querying
-	 *       the groups that each peer is in or listening to the Join/Disconnect
-	 *       notifications.
-	 */
-
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-group_proxy_acquired_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	gboolean success;
-
-	success = g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error);
-	if (   !success
-	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (!success) {
-		_LOGD ("failed to acquire Group proxy: (%s)", error->message);
-		g_clear_object (&priv->group_proxy);
-		return;
-	}
-
-	priv->group_proxy_acquired = TRUE;
-	_notify (self, PROP_P2P_GROUP_JOINED);
-	_notify (self, PROP_P2P_GROUP_PATH);
-
-	iface_check_ready (self);
-}
-
-static void
-p2p_props_changed_cb (GDBusProxy *proxy,
-                      GVariant *changed_properties,
-                      GStrv invalidated_properties,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char **array, **iter;
-	const char *path = NULL;
-
-	g_object_freeze_notify (G_OBJECT (self));
-
-	if (g_variant_lookup (changed_properties, "Peers", "^a&o", &array)) {
-		iter = array;
-		while (*iter)
-			peer_add_new (self, *iter++);
-		g_free (array);
-	}
-
-	if (g_variant_lookup (changed_properties, "Group", "&o", &path)) {
-		if (priv->group_proxy && g_strcmp0 (path, g_dbus_proxy_get_object_path (priv->group_proxy)) == 0) {
-			/* We already have the proxy, nothing to do. */
-		} else if (nm_dbus_path_not_empty (path)) {
-			if (priv->group_proxy != NULL) {
-				_LOGW ("P2P: Unexpected update of the group object path");
-				priv->group_proxy_acquired = FALSE;
-				_notify (self, PROP_P2P_GROUP_JOINED);
-				_notify (self, PROP_P2P_GROUP_PATH);
-				g_clear_object (&priv->group_proxy);
+		priv->disconnect_reason = v_i32;
+	}
+
+	if (nm_g_variant_lookup (properties, "State", "&s", &v_s)) {
+		NMSupplicantInterfaceState state;
+
+		state = wpas_state_string_to_enum (v_s);
+		if (state == NM_SUPPLICANT_INTERFACE_STATE_INVALID)
+			_LOGT ("ignore unknown supplicant state '%s'", v_s);
+		else if (priv->supp_state != state) {
+			priv->supp_state = state;
+			if (priv->state > NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
+				/* Only transition to actual wpa_supplicant interface states (ie,
+				 * anything > STARTING) after the NMSupplicantInterface has had a
+				 * chance to initialize, which is signalled by entering the STARTING
+				 * state.
+				 */
+				do_set_state = TRUE;
 			}
-
-			/* Delay ready state if we have not reached it yet. */
-			if (priv->ready_count)
-				priv->ready_count++;
-
-			priv->group_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-			                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
-			                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-			                                  "g-name", WPAS_DBUS_SERVICE,
-			                                  "g-object-path", path,
-			                                  "g-interface-name", WPAS_DBUS_IFACE_GROUP,
-			                                  NULL);
-			g_signal_connect (priv->group_proxy, "g-properties-changed", G_CALLBACK (group_props_changed_cb), self);
-			g_async_initable_init_async (G_ASYNC_INITABLE (priv->group_proxy),
-			                             G_PRIORITY_DEFAULT,
-			                             priv->other_cancellable,
-			                             (GAsyncReadyCallback) group_proxy_acquired_cb,
-			                             self);
-		} else {
-			priv->group_proxy_acquired = FALSE;
-			_notify (self, PROP_P2P_GROUP_JOINED);
-			_notify (self, PROP_P2P_GROUP_PATH);
-			g_clear_object (&priv->group_proxy);
 		}
 	}
 
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
-p2p_device_found (GDBusProxy *proxy,
-                  const char *path,
-                  gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	peer_add_new (self, path);
-}
-
-static void
-p2p_device_lost (GDBusProxy *proxy,
-                 const char *path,
-                 gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	PeerData *peer_data;
-
-	peer_data = g_hash_table_lookup (priv->peer_proxies, path);
-	if (!peer_data)
-		return;
-	g_hash_table_steal (priv->peer_proxies, path);
-	g_signal_emit (self, signals[PEER_REMOVED], 0, path);
-	peer_data_destroy (peer_data);
-}
-
-static void
-p2p_group_started (GDBusProxy *proxy,
-                   GVariant *params,
-                   gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	NMSupplicantInterface *iface = NULL;
-	char *group_path = NULL;
-	char *iface_path = NULL;
-
-	/* There is one more parameter: the role, but we don't really care about that here. */
-	if (!g_variant_lookup (params, "group_object", "&o", &group_path)) {
-		_LOGW ("P2P: GroupStarted signal is missing the \"group_object\" parameter");
-		return;
-	}
-
-	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
-		_LOGW ("P2P: GroupStarted signal is missing the \"interface\" parameter");
-		return;
-	}
-
-	if (g_strcmp0 (iface_path, priv->object_path) == 0) {
-		_LOGW ("P2P: GroupStarted on existing interface");
-		iface = g_object_ref (self);
-	} else {
-		iface = nm_supplicant_manager_create_interface_from_path (nm_supplicant_manager_get (),
-		                                                          iface_path);
-		if (iface == NULL) {
-			_LOGW ("P2P: Group interface already exists in GroupStarted handler, aborting further processing.");
-			return;
+	if (nm_g_variant_lookup (properties, "CurrentBSS", "&o", &v_s)) {
+		v_s = nm_dbus_path_not_empty (v_s);
+		if (!nm_ref_string_equals_str (priv->current_bss, v_s)) {
+			nm_ref_string_unref (priv->current_bss);
+			priv->current_bss = nm_ref_string_new (v_s);
+			do_notify_current_bss = TRUE;
 		}
 	}
 
-	/* Signal existence of the (new) interface. */
-	g_signal_emit (self, signals[GROUP_STARTED], 0, iface);
-	g_object_unref (iface);
-}
-
-static void
-p2p_group_formation_failure (GDBusProxy *proxy,
-                             const char *group,
-                             gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	g_signal_emit (self, signals[GROUP_FORMATION_FAILURE], 0, group);
-}
-
-static void
-p2p_group_finished (GDBusProxy *proxy,
-                    GVariant *params,
-                    gpointer user_data)
-{
-	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	const char *iface_path = NULL;
-	/* TODO: Group finished is called on the management interface!
-	 *       This means the signal consumer will currently need to assume which
-	 *       interface is finishing or it needs to match the object paths.
-	 */
-
-	if (!g_variant_lookup (params, "interface_object", "&o", &iface_path)) {
-		_LOGW ("P2P: GroupFinished signal is missing the \"interface\" parameter");
-		return;
+	if (do_log_driver_info) {
+		_LOGD ("supplicant interface for ifindex=%d, ifname=%s%s%s, driver=%s%s%s (requested %s)",
+		       priv->ifindex,
+		       NM_PRINT_FMT_QUOTE_STRING (priv->ifname),
+		       NM_PRINT_FMT_QUOTE_STRING (priv->driver),
+		       nm_supplicant_driver_to_string (priv->requested_driver));
 	}
 
-	_LOGD ("P2P: GroupFinished signal on interface %s for interface %s", priv->object_path, iface_path);
-
-	/* Signal group finish interface (on management interface). */
-	g_signal_emit (self, signals[GROUP_FINISHED], 0, iface_path);
-}
+	if (nm_g_variant_lookup (properties, "BSSs", "^a&o", &v_strv)) {
+		NMSupplicantBssInfo *bss_info;
+		NMSupplicantBssInfo *bss_info_safe;
+		const char **iter;
 
-static void
-on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
+		c_list_for_each_entry (bss_info, &priv->bss_lst_head, _bss_lst)
+			bss_info->_bss_dirty = TRUE;
+		c_list_for_each_entry (bss_info, &priv->bss_initializing_lst_head, _bss_lst)
+			bss_info->_bss_dirty = TRUE;
 
-	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-			self = NM_SUPPLICANT_INTERFACE (user_data);
-			_LOGW ("failed to acquire wpa_supplicant interface proxy: (%s)", error->message);
-			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-		}
-		return;
-	}
+		for (iter = v_strv; *iter; iter++)
+			_bss_info_add (self, *iter);
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+		g_free (v_strv);
 
-	_nm_dbus_signal_connect (priv->iface_proxy, "ScanDone", G_VARIANT_TYPE ("(b)"),
-	                         G_CALLBACK (wpas_iface_scan_done), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "BSSAdded", G_VARIANT_TYPE ("(oa{sv})"),
-	                         G_CALLBACK (wpas_iface_bss_added), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "BSSRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (wpas_iface_bss_removed), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "NetworkRequest", G_VARIANT_TYPE ("(oss)"),
-	                         G_CALLBACK (wpas_iface_network_request), self);
-	_nm_dbus_signal_connect (priv->iface_proxy, "EAP", G_VARIANT_TYPE ("(ss)"),
-	                         G_CALLBACK (eap_changed), self);
-
-	/* Scan result aging parameters */
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "BSSExpireAge",
-	                                  g_variant_new_uint32 (250)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   NULL,
-	                   NULL);
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "BSSExpireCount",
-	                                  g_variant_new_uint32 (2)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   NULL,
-	                   NULL);
-
-	/* Check whether NetworkReply and AP mode are supported.
-	 * ready_count was initialized to 1 in interface_add_done().
-	 */
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "NetworkReply",
-	                   g_variant_new ("(oss)",
-	                                  "/fff",
-	                                  "foobar",
-	                                  "foobar"),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->init_cancellable,
-	                   (GAsyncReadyCallback) iface_check_netreply_cb,
-	                   self);
-
-	if (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) {
-		/* Initialize global PMF setting to 'optional' */
-		priv->ready_count++;
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   DBUS_INTERFACE_PROPERTIES ".Set",
-		                   g_variant_new ("(ssv)",
-		                                  WPAS_DBUS_IFACE_INTERFACE,
-		                                  "Pmf",
-		                                  g_variant_new_string ("1")),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) iface_set_pmf_cb,
-		                   self);
-	}
-
-	if (priv->ap_support == NM_SUPPLICANT_FEATURE_UNKNOWN) {
-		/* If the global supplicant capabilities property is not present, we can
-		 * fall back to checking whether the ProbeRequest method is supported.  If
-		 * neither of these works we have no way of determining if AP mode is
-		 * supported or not.  hostap 1.0 and earlier don't support either of these.
-		 */
-		priv->ready_count++;
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   DBUS_INTERFACE_INTROSPECTABLE ".Introspect",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) iface_introspect_cb,
-		                   self);
-	}
-}
-
-static void
-on_p2p_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-
-	if (!g_async_initable_init_finish (G_ASYNC_INITABLE (proxy), result, &error)) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-			self = NM_SUPPLICANT_INTERFACE (user_data);
-			priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-			_LOGW ("failed to acquire wpa_supplicant p2p proxy: (%s)", error->message);
-
-			g_clear_object (&priv->p2p_proxy);
-
-			iface_check_ready (self);
+		c_list_for_each_entry_safe (bss_info, bss_info_safe, &priv->bss_initializing_lst_head, _bss_lst) {
+			if (bss_info->_bss_dirty)
+				_bss_info_remove (self, &bss_info->bss_path);
+		}
+		c_list_for_each_entry_safe (bss_info, bss_info_safe, &priv->bss_lst_head, _bss_lst) {
+			if (bss_info->_bss_dirty)
+				_bss_info_remove (self, &bss_info->bss_path);
 		}
-		return;
 	}
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	if (do_notify_current_bss)
+		_notify (self, PROP_CURRENT_BSS);
 
-	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceFound", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (p2p_device_found), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "DeviceLost", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (p2p_device_lost), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupStarted", G_VARIANT_TYPE ("(a{sv})"),
-	                         G_CALLBACK (p2p_group_started), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFormationFailure", G_VARIANT_TYPE ("(s)"),
-	                         G_CALLBACK (p2p_group_formation_failure), self);
-	_nm_dbus_signal_connect (priv->p2p_proxy, "GroupFinished", G_VARIANT_TYPE ("(a{sv})"),
-	                         G_CALLBACK (p2p_group_finished), self);
-	/* TODO:
-	 *  * WpsFailed
-	 *  * FindStopped
-	 *  * GONegotationFailure
-	 *  * InvitationReceived
-	 */
-
-	priv->p2p_proxy_acquired = TRUE;
-	_notify (self, PROP_P2P_AVAILABLE);
+	if (do_set_state)
+		set_state (self, priv->supp_state);
 
-	iface_check_ready (self);
+	_notify_maybe_scanning (self);
 }
 
 static void
-interface_add_done (NMSupplicantInterface *self, const char *path)
+_properties_changed_p2p_device (NMSupplicantInterface *self,
+                                GVariant *properties)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char **v_strv;
+	const char *v_s;
 
-	_LOGD ("interface added to supplicant");
-
-	/* Iface ready check happens in iface_check_netreply_cb */
-	priv->ready_count = 1;
-
-	priv->object_path = g_strdup (path);
-	_notify (self, PROP_OBJECT_PATH);
-	priv->iface_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-	                                  "g-bus-type", G_BUS_TYPE_SYSTEM,
-	                                  "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-	                                  "g-name", WPAS_DBUS_SERVICE,
-	                                  "g-object-path", priv->object_path,
-	                                  "g-interface-name", WPAS_DBUS_IFACE_INTERFACE,
-	                                  NULL);
-	g_signal_connect (priv->iface_proxy, "g-properties-changed", G_CALLBACK (props_changed_cb), self);
-	g_async_initable_init_async (G_ASYNC_INITABLE (priv->iface_proxy),
-	                             G_PRIORITY_DEFAULT,
-	                             priv->init_cancellable,
-	                             (GAsyncReadyCallback) on_iface_proxy_acquired,
-	                             self);
-
-	if (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) {
-		priv->ready_count++;
-		priv->p2p_proxy = g_object_new (G_TYPE_DBUS_PROXY,
-		                                "g-bus-type", G_BUS_TYPE_SYSTEM,
-		                                "g-flags", G_DBUS_PROXY_FLAGS_NONE,
-		                                "g-name", WPAS_DBUS_SERVICE,
-		                                "g-object-path", priv->object_path,
-		                                "g-interface-name", WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
-		                                NULL);
-		g_signal_connect (priv->p2p_proxy, "g-properties-changed", G_CALLBACK (p2p_props_changed_cb), self);
-		g_async_initable_init_async (G_ASYNC_INITABLE (priv->p2p_proxy),
-		                             G_PRIORITY_DEFAULT,
-		                             priv->init_cancellable,
-		                             (GAsyncReadyCallback) on_p2p_proxy_acquired,
-		                             self);
-	}
-}
-
-static void
-interface_get_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *variant = NULL;
-	gs_free_error GError *error = NULL;
-	const char *path;
-
-	variant = _nm_dbus_proxy_call_finish (proxy, result,
-	                                      G_VARIANT_TYPE ("(o)"),
-	                                      &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-
-	if (variant) {
-		g_variant_get (variant, "(&o)", &path);
-		interface_add_done (self, path);
-	} else {
-		g_dbus_error_strip_remote_error (error);
-		_LOGE ("error getting interface: %s", error->message);
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-	}
-}
-
-static void
-interface_add_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	gs_unref_variant GVariant *variant = NULL;
-	const char *path;
-
-	variant = _nm_dbus_proxy_call_finish (proxy, result,
-	                                      G_VARIANT_TYPE ("(o)"),
-	                                      &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (variant) {
-		g_variant_get (variant, "(&o)", &path);
-		interface_add_done (self, path);
-	} else if (_nm_dbus_error_has_name (error, WPAS_ERROR_EXISTS_ERROR)) {
-		/* Interface already added, just get its object path */
-		g_dbus_proxy_call (priv->wpas_proxy,
-		                   "GetInterface",
-		                   g_variant_new ("(s)", priv->dev),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) interface_get_cb,
-		                   self);
-	} else if (   g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMED_OUT)
-	           || g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
-		/* Supplicant wasn't running and could not be launched via service
-		 * activation.  Wait for it to start by moving back to the INIT
-		 * state.
-		 */
-		g_dbus_error_strip_remote_error (error);
-		_LOGD ("failed to activate supplicant: %s", error->message);
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_INIT);
-	} else {
-		g_dbus_error_strip_remote_error (error);
-		_LOGE ("error adding interface: %s", error->message);
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-	}
-}
-
-static void
-interface_removed_cb (GDBusProxy *proxy,
-                      const char *path,
-                      gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
+	nm_assert (!properties || g_variant_is_of_type (properties, G_VARIANT_TYPE ("a{sv}")));
 
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (g_strcmp0 (priv->object_path, path) != 0)
-		return;
+	if (nm_g_variant_lookup (properties, "Peers", "^a&o", &v_strv)) {
+		NMSupplicantPeerInfo *peer_info;
+		NMSupplicantPeerInfo *peer_info_safe;
+		const char *const*iter;
 
-	_LOGD ("Received interface removed signal");
+		c_list_for_each_entry (peer_info, &priv->peer_lst_head, _peer_lst)
+			peer_info->_peer_dirty = TRUE;
+		c_list_for_each_entry (peer_info, &priv->peer_initializing_lst_head, _peer_lst)
+			peer_info->_peer_dirty = TRUE;
 
-	/* The interface may lose its last reference during signal handling otherwise. */
-	g_object_ref (self);
+		for (iter = v_strv; *iter; iter++)
+			_peer_info_add (self, *iter);
 
-	/* Invalidate the object path to prevent the manager from trying to remove
-	 * a non-existing interface. */
-	g_clear_pointer (&priv->object_path, g_free);
-	_notify (self, PROP_OBJECT_PATH);
+		g_free (v_strv);
 
-	/* No need to clean up everything now, that will happen at dispose time. */
-
-	/* Interface is down and has been removed. */
-	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
-	g_signal_emit (self, signals[REMOVED], 0);
-
-	g_object_unref (self);
-}
-
-#if HAVE_WEXT
-#define DEFAULT_WIFI_DRIVER "nl80211,wext"
-#else
-#define DEFAULT_WIFI_DRIVER "nl80211"
-#endif
-
-static void
-on_wpas_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	NMSupplicantInterface *self;
-	NMSupplicantInterfacePrivate *priv;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *wpas_proxy;
-	GVariantBuilder props;
-
-	wpas_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (!wpas_proxy) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-			self = NM_SUPPLICANT_INTERFACE (user_data);
-			_LOGW ("failed to acquire wpa_supplicant proxy: (%s)", error->message);
-			set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
+		c_list_for_each_entry_safe (peer_info, peer_info_safe, &priv->peer_initializing_lst_head, _peer_lst) {
+			if (peer_info->_peer_dirty)
+				_peer_info_remove (self, &peer_info->peer_path);
 		}
-		return;
-	}
-
-	self = NM_SUPPLICANT_INTERFACE (user_data);
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	priv->wpas_proxy = wpas_proxy;
-
-	/* Watch for interface removal. */
-	_nm_dbus_signal_connect (priv->wpas_proxy, "InterfaceRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (interface_removed_cb), self);
-
-	/* Try to add the interface to the supplicant.  If the supplicant isn't
-	 * running, this will start it via D-Bus activation and return the response
-	 * when the supplicant has started.
-	 */
-
-	if (priv->dev != NULL) {
-		const char *driver_name = NULL;
-
-		switch (priv->driver) {
-		case NM_SUPPLICANT_DRIVER_WIRELESS:
-			driver_name = DEFAULT_WIFI_DRIVER;
-			break;
-		case NM_SUPPLICANT_DRIVER_WIRED:
-			driver_name = "wired";
-			break;
-		case NM_SUPPLICANT_DRIVER_MACSEC:
-			driver_name = "macsec_linux";
-			break;
+		c_list_for_each_entry_safe (peer_info, peer_info_safe, &priv->peer_lst_head, _peer_lst) {
+			if (peer_info->_peer_dirty)
+				_peer_info_remove (self, &peer_info->peer_path);
 		}
-
-		g_return_if_fail (driver_name);
-
-		g_variant_builder_init (&props, G_VARIANT_TYPE_VARDICT);
-		g_variant_builder_add (&props, "{sv}",
-		                       "Driver",
-		                       g_variant_new_string (driver_name));
-		g_variant_builder_add (&props, "{sv}",
-		                       "Ifname",
-		                       g_variant_new_string (priv->dev));
-
-		g_dbus_proxy_call (priv->wpas_proxy,
-		                   "CreateInterface",
-		                   g_variant_new ("(a{sv})", &props),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->init_cancellable,
-		                   (GAsyncReadyCallback) interface_add_cb,
-		                   self);
-	} else if (priv->object_path) {
-		interface_add_done (self, priv->object_path);
-	} else {
-		g_assert_not_reached ();
-	}
-}
-
-static void
-interface_add (NMSupplicantInterface *self)
-{
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	/* Can only start the interface from INIT state */
-	g_return_if_fail (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT);
-
-	_LOGD ("adding interface to supplicant");
-
-	/* Move to starting to prevent double-calls of interface_add() */
-	set_state (self, NM_SUPPLICANT_INTERFACE_STATE_STARTING);
-
-	nm_clear_g_cancellable (&priv->init_cancellable);
-	priv->init_cancellable = g_cancellable_new ();
-
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                          NULL,
-	                          WPAS_DBUS_SERVICE,
-	                          WPAS_DBUS_PATH,
-	                          WPAS_DBUS_INTERFACE,
-	                          priv->init_cancellable,
-	                          (GAsyncReadyCallback) on_wpas_proxy_acquired,
-	                          self);
-}
-
-void
-nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
-                                                  gboolean available)
-{
-	NMSupplicantInterfacePrivate *priv;
-
-	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
-
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	if (available) {
-		/* This can happen if the supplicant couldn't be activated but
-		 * for some reason was started after the activation failure.
-		 */
-		if (priv->state == NM_SUPPLICANT_INTERFACE_STATE_INIT)
-			interface_add (self);
-	} else {
-		/* The supplicant stopped; so we must tear down the interface */
-		set_state (self, NM_SUPPLICANT_INTERFACE_STATE_DOWN);
 	}
-}
-
-static void
-log_result_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
-{
-	gs_unref_variant GVariant *reply = NULL;
-	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (   !reply
-	    && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
-	    && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) {
-		g_dbus_error_strip_remote_error (error);
-		nm_log_warn (_NMLOG_DOMAIN, "%s: failed to %s: %s",
-		             _NMLOG_PREFIX_NAME, (const char *) user_data, error->message);
-	}
+	if (nm_g_variant_lookup (properties, "Group", "&o", &v_s))
+		_p2p_group_set_path (self, v_s);
 }
 
 /*****************************************************************************/
 
 static void
-assoc_return (NMSupplicantInterface *self, GError *error, const char *message)
+assoc_return (NMSupplicantInterface *self,
+              GError *error,
+              const char *message)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 	AssocData *assoc_data;
@@ -2143,9 +1936,14 @@ assoc_return (NMSupplicantInterface *self, GError *error, const char *message)
 
 	if (error) {
 		g_dbus_error_strip_remote_error (error);
-		_LOGW ("assoc[%p]: %s: %s", assoc_data, message, error->message);
-	} else
-		_LOGD ("assoc[%p]: association request successful", assoc_data);
+		_LOGW ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: %s: %s",
+		       NM_HASH_OBFUSCATE_PTR (assoc_data),
+		       message,
+		       error->message);
+	} else {
+		_LOGD ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: association request successful",
+		       NM_HASH_OBFUSCATE_PTR (assoc_data));
+	}
 
 	if (assoc_data->add_network_data) {
 		/* signal that this request already completed */
@@ -2171,112 +1969,83 @@ nm_supplicant_interface_disconnect (NMSupplicantInterface * self)
 
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	/* Cancel all pending calls related to a prior connection attempt */
-	if (priv->assoc_data) {
-		gs_free_error GError *error = NULL;
-
-		nm_utils_error_set_cancelled (&error, FALSE, "NMSupplicantInterface");
-		assoc_return (self, error, "abort due to disconnect");
-	}
-
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->iface_proxy)
-		return;
-
 	/* Disconnect from the current AP */
 	if (   (priv->state >= NM_SUPPLICANT_INTERFACE_STATE_SCANNING)
 	    && (priv->state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)) {
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   "Disconnect",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   NULL,
-		                   (GAsyncReadyCallback) log_result_cb,
-		                   "disconnect");
-	}
-
-	/* Remove any network that was added by NetworkManager */
-	if (priv->net_path) {
-		g_dbus_proxy_call (priv->iface_proxy,
-		                   "RemoveNetwork",
-		                   g_variant_new ("(o)", priv->net_path),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   -1,
-		                   priv->other_cancellable,
-		                   (GAsyncReadyCallback) log_result_cb,
-		                   "remove network");
-		g_free (priv->net_path);
-		priv->net_path = NULL;
+		_dbus_connection_call_simple (self,
+		                              NM_WPAS_DBUS_IFACE_INTERFACE,
+		                              "Disconnect",
+		                              NULL,
+		                              G_VARIANT_TYPE ("()"),
+		                              "disconnect");
 	}
 
+	_remove_network (self);
+
 	/* Cancel any WPS enrollment, if any */
 	nm_supplicant_interface_cancel_wps (self);
+
+	/* Cancel all pending calls related to a prior connection attempt */
+	if (priv->assoc_data) {
+		gs_free_error GError *error = NULL;
+
+		nm_utils_error_set_cancelled (&error, FALSE, "NMSupplicantInterface");
+		assoc_return (self, error, "abort due to disconnect");
+	}
 }
 
 static void
-disconnect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+disconnect_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
-	DisconnectData *disconnect_data = user_data;
-	gs_unref_object NMSupplicantInterface *self = disconnect_data->self;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_object NMSupplicantInterface *self = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
+	NMSupplicantInterfaceDisconnectCb callback;
+	gpointer callback_user_data;
+
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 
-	/* an already disconnected interface is not an error*/
-	if (   !reply
+	if (   !res
 	    && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) {
+		/* an already disconnected interface is not an error*/
 		g_clear_error(&error);
 	}
 
-	disconnect_data->callback(self, error, disconnect_data->user_data);
-	g_slice_free (DisconnectData, disconnect_data);
+	callback (self, error, callback_user_data);
 }
 
 void
-nm_supplicant_interface_disconnect_async ( NMSupplicantInterface * self,
-                                           GCancellable * cancellable,
-                                           NMSupplicantInterfaceDisconnectCb callback,
-                                           gpointer user_data)
+nm_supplicant_interface_disconnect_async (NMSupplicantInterface *self,
+                                          GCancellable *cancellable,
+                                          NMSupplicantInterfaceDisconnectCb callback,
+                                          gpointer user_data)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-	DisconnectData *disconnect_data;
-
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->iface_proxy)
-		return;
-
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
-	g_return_if_fail (NULL != callback);
-
-	disconnect_data = g_slice_new0(DisconnectData);
+	g_return_if_fail (callback);
 
-	/* Keep interface alive until disconnect finishes */
-	disconnect_data->self = g_object_ref (self);
-	disconnect_data->callback = callback;
-	disconnect_data->user_data = user_data;
-
-	/* Disconnect the interface */
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "Disconnect",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   cancellable,
-	                   (GAsyncReadyCallback) disconnect_cb,
-	                   disconnect_data);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "Disconnect",
+	                       NULL,
+	                       G_VARIANT_TYPE ("()"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       cancellable,
+	                       disconnect_cb,
+	                       nm_utils_user_data_pack (g_object_ref (self), callback, user_data));
 }
 
 static void
-assoc_select_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_select_network_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
@@ -2291,26 +2060,28 @@ assoc_call_select_network (NMSupplicantInterface *self)
 {
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "SelectNetwork",
-	                   g_variant_new ("(o)", priv->net_path),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->assoc_data->cancellable,
-	                   (GAsyncReadyCallback) assoc_select_network_cb,
-	                   self);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "SelectNetwork",
+	                       g_variant_new ("(o)", priv->net_path),
+	                       G_VARIANT_TYPE ("()"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       priv->assoc_data->cancellable,
+	                       assoc_select_network_cb,
+	                       self);
 }
 
 static void
-assoc_add_blob_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_add_blob_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
@@ -2322,52 +2093,62 @@ assoc_add_blob_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 	}
 
 	priv->assoc_data->blobs_left--;
-	_LOGT ("assoc[%p]: blob added (%u left)", priv->assoc_data, priv->assoc_data->blobs_left);
+	_LOGT ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: blob added (%u left)",
+	       NM_HASH_OBFUSCATE_PTR (priv->assoc_data),
+	       priv->assoc_data->blobs_left);
 	if (priv->assoc_data->blobs_left == 0)
 		assoc_call_select_network (self);
 }
 
 static void
-assoc_add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_add_network_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	AddNetworkData *add_network_data = user_data;
 	AssocData *assoc_data;
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 	GHashTable *blobs;
 	GHashTableIter iter;
 	const char *blob_name;
 	GBytes *blob_data;
+	nm_auto_ref_string NMRefString *name_owner = NULL;
+	nm_auto_ref_string NMRefString *object_path = NULL;
+
+	g_clear_object (&add_network_data->shutdown_wait_obj);
 
 	assoc_data = add_network_data->assoc_data;
 	if (assoc_data)
 		assoc_data->add_network_data = NULL;
-	g_slice_free (AddNetworkData, add_network_data);
+	name_owner = g_steal_pointer (&add_network_data->name_owner);
+	object_path = g_steal_pointer (&add_network_data->object_path);
+	nm_g_slice_free (add_network_data);
 
-	reply = _nm_dbus_proxy_call_finish (proxy, result,
-	                                    G_VARIANT_TYPE ("(o)"),
-	                                    &error);
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
 
 	if (!assoc_data) {
 		if (!error) {
-			gs_free char *net_path = NULL;
+			const char *net_path;
 
 			/* the assoc-request was already cancelled, but the AddNetwork request succeeded.
 			 * Cleanup the created network.
 			 *
 			 * This cleanup action does not work when NetworkManager is about to exit
 			 * and leaves the mainloop. During program shutdown, we may orphan networks. */
-			g_variant_get (reply, "(o)", &net_path);
-			g_dbus_proxy_call (proxy,
-			                   "RemoveNetwork",
-			                   g_variant_new ("(o)", net_path),
-			                   G_DBUS_CALL_FLAGS_NONE,
-			                   -1,
-			                   NULL,
-			                   NULL,
-			                   NULL);
+			g_variant_get (res, "(&o)", &net_path);
+			g_dbus_connection_call (G_DBUS_CONNECTION (source),
+			                        name_owner->str,
+			                        object_path->str,
+			                        NM_WPAS_DBUS_IFACE_INTERFACE,
+			                        "RemoveNetwork",
+			                        g_variant_new ("(o)", net_path),
+			                        G_VARIANT_TYPE ("()"),
+			                        G_DBUS_CALL_FLAGS_NONE,
+			                        DBUS_TIMEOUT_MSEC,
+			                        NULL,
+			                        NULL,
+			                        NULL);
 		}
 		return;
 	}
@@ -2380,44 +2161,50 @@ assoc_add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 		return;
 	}
 
-	g_variant_get (reply, "(o)", &priv->net_path);
+	nm_assert (!priv->net_path);
+	g_variant_get (res, "(o)", &priv->net_path);
 
 	/* Send blobs first; otherwise jump to selecting the network */
 	blobs = nm_supplicant_config_get_blobs (priv->assoc_data->cfg);
-	priv->assoc_data->blobs_left = g_hash_table_size (blobs);
+	priv->assoc_data->blobs_left =   blobs
+	                               ? g_hash_table_size (blobs)
+	                               : 0u;
 
-	_LOGT ("assoc[%p]: network added (%s) (%u blobs left)", priv->assoc_data, priv->net_path, priv->assoc_data->blobs_left);
+	_LOGT ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: network added (%s) (%u blobs left)",
+	       NM_HASH_OBFUSCATE_PTR (priv->assoc_data),
+	       priv->net_path,
+	       priv->assoc_data->blobs_left);
 
-	if (priv->assoc_data->blobs_left == 0)
+	if (priv->assoc_data->blobs_left == 0) {
 		assoc_call_select_network (self);
-	else {
-		g_hash_table_iter_init (&iter, blobs);
-		while (g_hash_table_iter_next (&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
-			g_dbus_proxy_call (priv->iface_proxy,
-			                   "AddBlob",
-			                   g_variant_new ("(s@ay)",
-			                                  blob_name,
-			                                  nm_utils_gbytes_to_variant_ay (blob_data)),
-			                   G_DBUS_CALL_FLAGS_NONE,
-			                   -1,
-			                   priv->assoc_data->cancellable,
-			                   (GAsyncReadyCallback) assoc_add_blob_cb,
-			                   self);
-		}
+		return;
+	}
+
+	g_hash_table_iter_init (&iter, blobs);
+	while (g_hash_table_iter_next (&iter, (gpointer) &blob_name, (gpointer) &blob_data)) {
+		_dbus_connection_call (self,
+		                       NM_WPAS_DBUS_IFACE_INTERFACE,
+		                       "AddBlob",
+		                       g_variant_new ("(s@ay)",
+		                                      blob_name,
+		                                      nm_utils_gbytes_to_variant_ay (blob_data)),
+		                       G_VARIANT_TYPE ("()"),
+		                       G_DBUS_CALL_FLAGS_NONE,
+		                       DBUS_TIMEOUT_MSEC,
+		                       priv->assoc_data->cancellable,
+		                       assoc_add_blob_cb,
+		                       self);
 	}
 }
 
 static void
-assoc_set_ap_scan_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+assoc_set_ap_scan_cb (GVariant *ret, GError *error, gpointer user_data)
 {
 	NMSupplicantInterface *self;
 	NMSupplicantInterfacePrivate *priv;
-	gs_unref_variant GVariant *reply = NULL;
-	gs_free_error GError *error = NULL;
 	AddNetworkData *add_network_data;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
@@ -2428,23 +2215,36 @@ assoc_set_ap_scan_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 		return;
 	}
 
-	_LOGT ("assoc[%p]: set interface ap_scan to %d",
-	       priv->assoc_data,
+	_LOGT ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: interface ap_scan set to %d",
+	       NM_HASH_OBFUSCATE_PTR (priv->assoc_data),
 	       nm_supplicant_config_get_ap_scan (priv->assoc_data->cfg));
 
-	add_network_data = g_slice_new0 (AddNetworkData);
+	/* the association does not keep @self alive. We want to be able to remove
+	 * the network again, even if @self is already gone. Hence, track the data
+	 * separately.
+	 *
+	 * For that we also have a shutdown_wait_obj so that on exit we still wait
+	 * to handle the response. */
+	add_network_data = g_slice_new (AddNetworkData);
+	*add_network_data = (AddNetworkData) {
+		.assoc_data        = priv->assoc_data,
+		.name_owner        = nm_ref_string_ref (priv->name_owner),
+		.object_path       = nm_ref_string_ref (priv->object_path),
+		.shutdown_wait_obj = g_object_new (G_TYPE_OBJECT, NULL),
+	};
+	nm_shutdown_wait_obj_register_object (add_network_data->shutdown_wait_obj, "supplicant-add-network");
 	priv->assoc_data->add_network_data = add_network_data;
 
-	add_network_data->assoc_data = priv->assoc_data;
-
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "AddNetwork",
-	                   g_variant_new ("(@a{sv})", nm_supplicant_config_to_variant (priv->assoc_data->cfg)),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   NULL,
-	                   (GAsyncReadyCallback) assoc_add_network_cb,
-	                   add_network_data);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "AddNetwork",
+	                       g_variant_new ("(@a{sv})", nm_supplicant_config_to_variant (priv->assoc_data->cfg)),
+	                       G_VARIANT_TYPE ("(o)"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       NULL,
+	                       assoc_add_network_cb,
+	                       add_network_data);
 }
 
 static gboolean
@@ -2491,61 +2291,64 @@ nm_supplicant_interface_assoc (NMSupplicantInterface *self,
 
 	nm_supplicant_interface_disconnect (self);
 
-	assoc_data = g_slice_new0 (AssocData);
-	priv->assoc_data = assoc_data;
+	assoc_data = g_slice_new (AssocData);
+	*assoc_data = (AssocData) {
+		.self      = self,
+		.cfg       = g_object_ref (cfg),
+		.callback  = callback,
+		.user_data = user_data,
+	};
 
-	assoc_data->self = self;
-	assoc_data->cfg = g_object_ref (cfg);
-	assoc_data->callback = callback;
-	assoc_data->user_data = user_data;
+	priv->assoc_data = assoc_data;
 
-	_LOGD ("assoc[%p]: starting association...", assoc_data);
+	_LOGD ("assoc["NM_HASH_OBFUSCATE_PTR_FMT"]: starting association...",
+	       NM_HASH_OBFUSCATE_PTR (assoc_data));
 
-	/* Make sure the supplicant supports EAP-FAST before trying to send
-	 * it an EAP-FAST configuration.
-	 */
-	if (   priv->fast_support == NM_SUPPLICANT_FEATURE_NO
+	if (   _get_capability (priv, NM_SUPPL_CAP_TYPE_FAST) == NM_TERNARY_FALSE
 	    && nm_supplicant_config_fast_required (cfg)) {
+		/* Make sure the supplicant supports EAP-FAST before trying to send
+		 * it an EAP-FAST configuration.
+		 */
 		assoc_data->fail_on_idle_id = g_idle_add (assoc_fail_on_idle_cb, self);
 		return;
 	}
 
 	assoc_data->cancellable = g_cancellable_new();
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   DBUS_INTERFACE_PROPERTIES ".Set",
-	                   g_variant_new ("(ssv)",
-	                                  WPAS_DBUS_IFACE_INTERFACE,
-	                                  "ApScan",
-	                                  g_variant_new_uint32 (nm_supplicant_config_get_ap_scan (priv->assoc_data->cfg))),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->assoc_data->cancellable,
-	                   (GAsyncReadyCallback) assoc_set_ap_scan_cb,
-	                   self);
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE,
+	                             "ApScan",
+	                             g_variant_new_uint32 (nm_supplicant_config_get_ap_scan (priv->assoc_data->cfg)),
+	                             DBUS_TIMEOUT_MSEC,
+	                             assoc_data->cancellable,
+	                             assoc_set_ap_scan_cb,
+	                             self);
 }
 
 /*****************************************************************************/
 
 static void
-scan_request_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+scan_request_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
-	gs_unref_variant GVariant *reply = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	reply = g_dbus_proxy_call_finish (proxy, result, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
 	self = NM_SUPPLICANT_INTERFACE (user_data);
 	if (error) {
 		if (_nm_dbus_error_has_name (error, "fi.w1.wpa_supplicant1.Interface.ScanError"))
-			_LOGD ("could not get scan request result: %s", error->message);
+			_LOGD ("request-scan: could not get scan request result: %s", error->message);
 		else {
 			g_dbus_error_strip_remote_error (error);
-			_LOGW ("could not get scan request result: %s", error->message);
+			_LOGW ("request-scan: could not get scan request result: %s", error->message);
 		}
-	}
+	} else
+		_LOGT ("request-scan: request scanning success");
 }
 
 void
@@ -2561,7 +2364,8 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
 
 	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	/* Scan parameters */
+	_LOGT ("request-scan: request scanning (%u ssids)...", ssids_len);
+
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 	g_variant_builder_add (&builder, "{sv}", "Type", g_variant_new_string ("active"));
 	g_variant_builder_add (&builder, "{sv}", "AllowRoam", g_variant_new_boolean (FALSE));
@@ -2577,14 +2381,16 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
 		g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder));
 	}
 
-	g_dbus_proxy_call (priv->iface_proxy,
-	                   "Scan",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) scan_request_cb,
-	                   self);
+	_dbus_connection_call (self,
+	                       NM_WPAS_DBUS_IFACE_INTERFACE,
+	                       "Scan",
+	                       g_variant_new ("(a{sv})", &builder),
+	                       G_VARIANT_TYPE ("()"),
+	                       G_DBUS_CALL_FLAGS_NONE,
+	                       DBUS_TIMEOUT_MSEC,
+	                       priv->main_cancellable,
+	                       scan_request_cb,
+	                       self);
 }
 
 /*****************************************************************************/
@@ -2597,7 +2403,23 @@ nm_supplicant_interface_get_state (NMSupplicantInterface * self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->state;
 }
 
-const char *
+void
+_nm_supplicant_interface_set_state_down (NMSupplicantInterface * self,
+                                         gboolean force_remove_from_supplicant,
+                                         const char *reason)
+{
+	set_state_down (self, force_remove_from_supplicant, reason);
+}
+
+NMRefString *
+nm_supplicant_interface_get_name_owner (NMSupplicantInterface *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
+
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->name_owner;
+}
+
+NMRefString *
 nm_supplicant_interface_get_object_path (NMSupplicantInterface *self)
 {
 	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
@@ -2610,7 +2432,7 @@ nm_supplicant_interface_get_ifname (NMSupplicantInterface *self)
 {
 	g_return_val_if_fail (NM_IS_SUPPLICANT_INTERFACE (self), NULL);
 
-	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->dev;
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ifname;
 }
 
 guint
@@ -2627,133 +2449,461 @@ void
 nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
                                         guint timeout)
 {
-	NMSupplicantInterfacePrivate *priv;
 	GVariantBuilder builder;
 
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 	g_return_if_fail (timeout > 0 && timeout <= 600);
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 	g_variant_builder_add (&builder, "{sv}", "Timeout", g_variant_new_int32 (timeout));
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Find",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   self);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Find",
+	                              g_variant_new ("(a{sv})", &builder),
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-find");
 }
 
 void
 nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self)
 {
-	NMSupplicantInterfacePrivate *priv;
-
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "StopFind",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) scan_request_cb,
-	                   self);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "StopFind",
+	                              NULL,
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-stop-find");
 }
 
 /*****************************************************************************/
 
 void
-nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
-                                     const char * peer,
-                                     const char * wps_method,
-                                     const char * wps_pin)
+nm_supplicant_interface_p2p_connect (NMSupplicantInterface *self,
+                                     const char *peer,
+                                     const char *wps_method,
+                                     const char *wps_pin)
 {
-	NMSupplicantInterfacePrivate *priv;
 	GVariantBuilder builder;
 
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
-
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->p2p_proxy || !priv->object_path)
-		return;
-
-	/* Connect parameters */
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 
 	g_variant_builder_add (&builder, "{sv}", "wps_method", g_variant_new_string (wps_method));
-
 	if (wps_pin)
 		g_variant_builder_add (&builder, "{sv}", "pin", g_variant_new_string (wps_pin));
-
 	g_variant_builder_add (&builder, "{sv}", "peer", g_variant_new_object_path (peer));
-
 	g_variant_builder_add (&builder, "{sv}", "join", g_variant_new_boolean (FALSE));
 	g_variant_builder_add (&builder, "{sv}", "persistent", g_variant_new_boolean (FALSE));
 	g_variant_builder_add (&builder, "{sv}", "go_intent", g_variant_new_int32 (7));
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Connect",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   "p2p connect");
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Connect",
+	                              g_variant_new ("(a{sv})", &builder),
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-connect");
 }
 
 void
 nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self)
 {
-	NMSupplicantInterfacePrivate *priv;
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Cancel",
+	                              NULL,
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-cancel");
+}
 
+void
+nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self)
+{
 	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	_dbus_connection_call_simple (self,
+	                              NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                              "Disconnect",
+	                              NULL,
+	                              G_VARIANT_TYPE ("()"),
+	                              "p2p-disconnect");
+}
+
+/*****************************************************************************/
+
+static void
+_properties_changed (NMSupplicantInterface *self,
+                     const char *interface_name,
+                     GVariant *properties,
+                     gboolean initial)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	gboolean is_main;
+
+	nm_assert (!properties || g_variant_is_of_type (properties, G_VARIANT_TYPE ("a{sv}")));
+
+	if (initial)
+		priv->starting_pending_count--;
+
+	if (   (initial || priv->is_ready_main)
+	    && nm_streq (interface_name, NM_WPAS_DBUS_IFACE_INTERFACE))
+		is_main = TRUE;
+	else if (   (initial || priv->is_ready_p2p_device)
+	         && nm_streq (interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) {
+		nm_assert (_get_capability (priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE);
+		is_main = FALSE;
+	} else
+		return;
+
+	g_object_freeze_notify (G_OBJECT (self));
+
+	priv->starting_pending_count++;
 
-	/* Don't do anything if there is no connection to the supplicant yet. */
-	if (!priv->p2p_proxy || !priv->object_path)
+	if (is_main) {
+		priv->is_ready_main = TRUE;
+		_properties_changed_main (self, properties);
+	} else {
+		priv->is_ready_p2p_device = TRUE;
+		_properties_changed_p2p_device (self, properties);
+	}
+
+	priv->starting_pending_count--;
+	_starting_check_ready (self);
+
+	_notify_maybe_scanning (self);
+	_notify_maybe_p2p_available (self);
+
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+_properties_changed_cb (GDBusConnection *connection,
+                        const char *sender_name,
+                        const char *object_path,
+                        const char *signal_interface_name,
+                        const char *signal_name,
+                        GVariant *parameters,
+                        gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	const char *interface_name;
+	gs_unref_variant GVariant *changed_properties = NULL;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
 		return;
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Cancel",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   "cancel p2p connect");
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               &interface_name,
+	               &changed_properties,
+	               NULL);
+	_properties_changed (self,
+	                     interface_name,
+	                     changed_properties,
+	                     FALSE);
 }
 
-void
-nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self)
+static void
+_bss_properties_changed_cb (GDBusConnection *connection,
+                            const char *sender_name,
+                            const char *object_path,
+                            const char *signal_interface_name,
+                            const char *signal_name,
+                            GVariant *parameters,
+                            gpointer user_data)
 {
-	NMSupplicantInterfacePrivate *priv;
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *bss_path = NULL;
+	gs_unref_variant GVariant *changed_properties = NULL;
+	NMSupplicantBssInfo *bss_info;
 
-	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
 
-	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	bss_path = nm_ref_string_new (object_path);
 
-	/* Don't do anything if there is no connection to the supplicant. */
-	if (!priv->p2p_proxy || !priv->object_path)
+	bss_info = g_hash_table_lookup (priv->bss_idx, &bss_path);
+	if (!bss_info)
+		return;
+	if (bss_info->_init_cancellable)
 		return;
 
-	g_dbus_proxy_call (priv->p2p_proxy,
-	                   "Disconnect",
-	                   NULL,
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   priv->other_cancellable,
-	                   (GAsyncReadyCallback) log_result_cb,
-	                   "p2p disconnect");
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               NULL,
+	               &changed_properties,
+	               NULL);
+	_bss_info_properties_changed (self, bss_info, changed_properties, FALSE);
+}
+
+static void
+_peer_properties_changed_cb (GDBusConnection *connection,
+                             const char *sender_name,
+                             const char *object_path,
+                             const char *signal_interface_name,
+                             const char *signal_name,
+                             GVariant *parameters,
+                             gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *peer_path = NULL;
+	gs_unref_variant GVariant *changed_properties = NULL;
+	NMSupplicantPeerInfo *peer_info;
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sa{sv}as)")))
+		return;
+
+	peer_path = nm_ref_string_new (object_path);
+
+	peer_info = g_hash_table_lookup (priv->peer_idx, &peer_path);
+	if (!peer_info)
+		return;
+	if (peer_info->_init_cancellable)
+		return;
+
+	g_variant_get (parameters,
+	               "(&s@a{sv}^a&s)",
+	               NULL,
+	               &changed_properties,
+	               NULL);
+	_peer_info_properties_changed (self, peer_info, changed_properties, FALSE);
+}
+
+static void
+_get_all_main_cb (GVariant *result,
+                  GError *error,
+                  gpointer user_data)
+{
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
+	_properties_changed (user_data,
+	                     NM_WPAS_DBUS_IFACE_INTERFACE,
+	                     properties,
+	                     TRUE);
+}
+
+static void
+_get_all_p2p_device_cb (GVariant *result,
+                        GError *error,
+                        gpointer user_data)
+{
+	gs_unref_variant GVariant *properties = NULL;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (result)
+		g_variant_get (result, "(@a{sv})", &properties);
+	_properties_changed (user_data,
+	                     NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+	                     properties,
+	                     TRUE);
+}
+
+static void
+_signal_handle (NMSupplicantInterface *self,
+                const char *signal_interface_name,
+                const char *signal_name,
+                GVariant *parameters)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	const char *path;
+
+	if (nm_streq (signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE)) {
+
+		if (!priv->is_ready_main)
+			return;
+
+		if (nm_streq (signal_name, "BSSAdded")) {
+			if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sv})")))
+				return;
+
+			g_variant_get (parameters, "(&oa{sv})", &path, NULL);
+			_bss_info_add (self, path);
+			return;
+		}
+
+		if (nm_streq (signal_name, "BSSRemoved")) {
+			nm_auto_ref_string NMRefString *bss_path = NULL;
+
+			if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+				return;
+
+			g_variant_get (parameters, "(&o)", &path);
+			bss_path = nm_ref_string_new (path);
+			_bss_info_remove (self, &bss_path);
+			return;
+		}
+
+		if (nm_streq (signal_name, "EAP")) {
+			NMSupplicantAuthState auth_state = NM_SUPPLICANT_AUTH_STATE_UNKNOWN;
+			const char *status;
+			const char *parameter;
+
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ss)")))
+				return;
+
+			g_variant_get (parameters, "(&s&s)", &status, &parameter);
+
+			if (nm_streq (status, "started"))
+				auth_state = NM_SUPPLICANT_AUTH_STATE_STARTED;
+			else if (nm_streq (status, "completion")) {
+				if (nm_streq (parameter, "success"))
+					auth_state = NM_SUPPLICANT_AUTH_STATE_SUCCESS;
+				else if (nm_streq (parameter, "failure"))
+					auth_state = NM_SUPPLICANT_AUTH_STATE_FAILURE;
+			}
+
+			/* the state eventually reaches one of started, success or failure
+			 * so ignore any other intermediate (unknown) state change. */
+			if (   auth_state != NM_SUPPLICANT_AUTH_STATE_UNKNOWN
+			    && auth_state != priv->auth_state) {
+				priv->auth_state = auth_state;
+				_notify (self, PROP_AUTH_STATE);
+			}
+			return;
+		}
+
+		return;
+	}
+
+	if (nm_streq (signal_interface_name, NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE)) {
+
+		if (!priv->is_ready_p2p_device)
+			return;
+
+		if (nm_streq (signal_name, "DeviceFound")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) {
+				g_variant_get (parameters, "(&o)", &path);
+				_peer_info_add (self, path);
+			}
+			return;
+		}
+
+		if (nm_streq (signal_name, "DeviceLost")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) {
+				nm_auto_ref_string NMRefString *peer_path = NULL;
+
+				g_variant_get (parameters, "(&o)", &path);
+				peer_path = nm_ref_string_new (path);
+				_peer_info_remove (self, &peer_path);
+			}
+			return;
+		}
+
+		if (nm_streq (signal_name, "GroupStarted")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})"))) {
+				gs_unref_variant GVariant *args = NULL;
+				gs_unref_object NMSupplicantInterface *iface = NULL;
+				const char *group_path;
+				const char *iface_path;
+
+				g_variant_get (parameters, "(@a{sv})", &args);
+				if (!g_variant_lookup (args, "group_object", "&o", &group_path))
+					return;
+				if (!g_variant_lookup (args, "interface_object", "&o", &iface_path))
+					return;
+
+				if (nm_streq (iface_path, priv->object_path->str)) {
+					_LOGW ("P2P: GroupStarted on existing interface");
+					iface = g_object_ref (self);
+				} else {
+					iface = nm_supplicant_manager_create_interface_from_path (priv->supplicant_manager,
+					                                                          iface_path);
+					if (iface == NULL) {
+						_LOGW ("P2P: Group interface already exists in GroupStarted handler, aborting further processing.");
+						return;
+					}
+				}
+
+				/* Signal existence of the (new) interface. */
+				g_signal_emit (self, signals[GROUP_STARTED], 0, iface);
+			}
+			return;
+		}
+
+		if (nm_streq (signal_name, "GroupFinished")) {
+			if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})"))) {
+				gs_unref_variant GVariant *args = NULL;
+				const char *iface_path;
+
+				g_variant_get (parameters, "(@a{sv})", &args);
+
+				/* TODO: Group finished is called on the management interface!
+				 *       This means the signal consumer will currently need to assume which
+				 *       interface is finishing or it needs to match the object paths.
+				 */
+				if (!g_variant_lookup (args, "interface_object", "&o", &iface_path))
+					return;
+
+				_LOGD ("P2P: GroupFinished signal on interface %s for interface %s", priv->object_path->str, iface_path);
+
+				/* Signal group finish interface (on management interface). */
+				g_signal_emit (self, signals[GROUP_FINISHED], 0, iface_path);
+			}
+			return;
+		}
+
+		return;
+	}
+}
+
+static void
+_signal_cb (GDBusConnection *connection,
+            const char *sender_name,
+            const char *object_path,
+            const char *signal_interface_name,
+            const char *signal_name,
+            GVariant *parameters,
+            gpointer user_data)
+{
+	NMSupplicantInterface *self = user_data;
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->starting_pending_count++;
+	_signal_handle (self, signal_interface_name, signal_name, parameters);
+	priv->starting_pending_count--;
+	_starting_check_ready (self);
+
+	_notify_maybe_scanning (self);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_supplicant_interface_get_p2p_available (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_capable_cached;
+}
+
+gboolean
+nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_joined_cached;
+}
+
+const char*
+nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *self)
+{
+	return nm_ref_string_get_str (NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_path);
+}
+
+gboolean
+nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->p2p_group_owner_cached;
 }
 
 /*****************************************************************************/
@@ -2764,26 +2914,27 @@ get_property (GObject *object,
               GValue *value,
               GParamSpec *pspec)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object);
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (object);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	switch (prop_id) {
 	case PROP_SCANNING:
-		g_value_set_boolean (value, priv->scanning);
+		g_value_set_boolean (value, nm_supplicant_interface_get_scanning (self));
 		break;
 	case PROP_CURRENT_BSS:
-		g_value_set_string (value, priv->current_bss);
+		g_value_set_string (value, nm_ref_string_get_str (nm_supplicant_interface_get_current_bss (self)));
 		break;
 	case PROP_P2P_GROUP_JOINED:
-		g_value_set_boolean (value, priv->p2p_capable && priv->group_proxy_acquired);
+		g_value_set_boolean (value, nm_supplicant_interface_get_p2p_group_joined (self));
 		break;
 	case PROP_P2P_GROUP_PATH:
-		g_value_set_string (value, nm_supplicant_interface_get_p2p_group_path (NM_SUPPLICANT_INTERFACE (object)));
+		g_value_set_string (value, nm_supplicant_interface_get_p2p_group_path (self));
 		break;
 	case PROP_P2P_GROUP_OWNER:
-		g_value_set_boolean (value, priv->p2p_group_owner);
+		g_value_set_boolean (value, nm_supplicant_interface_get_p2p_group_owner (self));
 		break;
 	case PROP_P2P_AVAILABLE:
-		g_value_set_boolean (value, priv->p2p_capable && priv->p2p_proxy_acquired);
+		g_value_set_boolean (value, nm_supplicant_interface_get_p2p_available (self));
 		break;
 	case PROP_AUTH_STATE:
 		g_value_set_uint (value, priv->auth_state);
@@ -2800,56 +2951,34 @@ set_property (GObject *object,
               const GValue *value,
               GParamSpec *pspec)
 {
-	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE ((NMSupplicantInterface *) object);
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (object);
 
 	switch (prop_id) {
-	case PROP_IFACE:
+	case PROP_SUPPLICANT_MANAGER:
 		/* construct-only */
-		priv->dev = g_value_dup_string (value);
-		break;
-	case PROP_OBJECT_PATH:
-		/* construct-only */
-		priv->object_path = g_value_dup_string (value);
-		break;
-	case PROP_DRIVER:
-		/* construct-only */
-		priv->driver = g_value_get_uint (value);
-		break;
-	case PROP_FAST_SUPPORT:
-		/* construct-only */
-		priv->fast_support = g_value_get_int (value);
-		break;
-	case PROP_AP_SUPPORT:
-		/* construct-only */
-		priv->ap_support = g_value_get_int (value);
-		break;
-	case PROP_PMF_SUPPORT:
-		/* construct-only */
-		priv->pmf_support = g_value_get_int (value);
-		break;
-	case PROP_FILS_SUPPORT:
-		/* construct-only */
-		priv->fils_support = g_value_get_int (value);
-		break;
-	case PROP_P2P_SUPPORT:
-		/* construct-only */
-		priv->p2p_support = g_value_get_int (value);
-		break;
-	case PROP_MESH_SUPPORT:
-		/* construct-only */
-		priv->mesh_support = g_value_get_int (value);
+		priv->supplicant_manager = g_object_ref (g_value_get_pointer (value));
+		nm_assert (NM_IS_SUPPLICANT_MANAGER (priv->supplicant_manager));
+
+		priv->dbus_connection = g_object_ref (nm_supplicant_manager_get_dbus_connection (priv->supplicant_manager));
+		nm_assert (G_IS_DBUS_CONNECTION (priv->dbus_connection));
+
+		priv->name_owner = nm_ref_string_ref (nm_supplicant_manager_get_dbus_name_owner (priv->supplicant_manager));
+		nm_assert (NM_IS_REF_STRING (priv->name_owner));
+
+		priv->global_capabilities = nm_supplicant_manager_get_global_capabilities (priv->supplicant_manager);
 		break;
-	case PROP_WFD_SUPPORT:
+	case PROP_DBUS_OBJECT_PATH:
 		/* construct-only */
-		priv->wfd_support = g_value_get_int (value);
+		priv->object_path = nm_ref_string_ref (g_value_get_pointer (value));
+		nm_assert (NM_IS_REF_STRING (priv->object_path));
 		break;
-	case PROP_FT_SUPPORT:
+	case PROP_IFINDEX:
 		/* construct-only */
-		priv->ft_support_global = g_value_get_int (value);
+		priv->ifindex = g_value_get_int (value);
 		break;
-	case PROP_SHA384_SUPPORT:
+	case PROP_DRIVER:
 		/* construct-only */
-		priv->sha384_support = g_value_get_int (value);
+		priv->requested_driver = g_value_get_uint (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2857,47 +2986,179 @@ set_property (GObject *object,
 	}
 }
 
+/*****************************************************************************/
+
 static void
 nm_supplicant_interface_init (NMSupplicantInterface * self)
 {
+	NMSupplicantInterfacePrivate *priv;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfacePrivate);
+
+	self->_priv = priv;
+
+	nm_assert (priv->global_capabilities == NM_SUPPL_CAP_MASK_NONE);
+	nm_assert (priv->iface_capabilities == NM_SUPPL_CAP_MASK_NONE);
+
+	priv->state = NM_SUPPLICANT_INTERFACE_STATE_STARTING;
+	priv->supp_state = NM_SUPPLICANT_INTERFACE_STATE_INVALID;
+	priv->last_scan_msec = -1;
+
+	c_list_init (&self->supp_lst);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSupplicantBssInfo, bss_path) == 0);
+	priv->bss_idx = g_hash_table_new (nm_pdirect_hash, nm_pdirect_equal);
+
+	c_list_init (&priv->bss_lst_head);
+	c_list_init (&priv->bss_initializing_lst_head);
+
+	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSupplicantPeerInfo, peer_path) == 0);
+	priv->peer_idx = g_hash_table_new (nm_pdirect_hash, nm_pdirect_equal);
+
+	c_list_init (&priv->peer_lst_head);
+	c_list_init (&priv->peer_initializing_lst_head);
+
+	priv->main_cancellable = g_cancellable_new ();
+}
+
+static void
+constructed (GObject *object)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (object);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT;
-	priv->bss_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, bss_data_destroy);
-	priv->peer_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, peer_data_destroy);
+	G_OBJECT_CLASS (nm_supplicant_interface_parent_class)->constructed (object);
+
+	_LOGD ("new supplicant interface %s on %s",
+	       priv->object_path->str,
+	       priv->name_owner->str);
+
+	priv->properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+	                                                                                      priv->name_owner->str,
+	                                                                                      priv->object_path->str,
+	                                                                                      NULL,
+	                                                                                      _properties_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+
+	priv->bss_properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+	                                                                                          priv->name_owner->str,
+	                                                                                          NULL,
+	                                                                                          NM_WPAS_DBUS_IFACE_BSS,
+	                                                                                          _bss_properties_changed_cb,
+	                                                                                          self,
+	                                                                                          NULL);
+
+	priv->signal_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+	                                                      priv->name_owner->str,
+	                                                      NULL,
+	                                                      NULL,
+	                                                      priv->object_path->str,
+	                                                      NULL,
+	                                                      G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                      _signal_cb,
+	                                                      self,
+	                                                      NULL);
+
+	/* Scan result aging parameters */
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE,
+	                             "BSSExpireAge",
+	                             g_variant_new_uint32 (250),
+	                             DBUS_TIMEOUT_MSEC,
+	                             NULL,
+	                             NULL,
+	                             NULL);
+	nm_dbus_connection_call_set (priv->dbus_connection,
+	                             priv->name_owner->str,
+	                             priv->object_path->str,
+	                             NM_WPAS_DBUS_IFACE_INTERFACE,
+	                             "BSSExpireCount",
+	                             g_variant_new_uint32 (2),
+	                             DBUS_TIMEOUT_MSEC,
+	                             NULL,
+	                             NULL,
+	                             NULL);
+
+	if (_get_capability (priv, NM_SUPPL_CAP_TYPE_PMF) == NM_TERNARY_TRUE) {
+		/* Initialize global PMF setting to 'optional' */
+		nm_dbus_connection_call_set (priv->dbus_connection,
+		                             priv->name_owner->str,
+		                             priv->object_path->str,
+		                             NM_WPAS_DBUS_IFACE_INTERFACE,
+		                             "Pmf",
+		                             g_variant_new_string ("1"),
+		                             DBUS_TIMEOUT_MSEC,
+		                             NULL,
+		                             NULL,
+		                             NULL);
+	}
+
+	if (_get_capability (priv, NM_SUPPL_CAP_TYPE_AP) == NM_TERNARY_DEFAULT) {
+		/* If the global supplicant capabilities property is not present, we can
+		 * fall back to checking whether the ProbeRequest method is supported.  If
+		 * neither of these works we have no way of determining if AP mode is
+		 * supported or not.  hostap 1.0 and earlier don't support either of these.
+		 */
+		priv->starting_pending_count++;
+		_dbus_connection_call (self,
+		                       DBUS_INTERFACE_INTROSPECTABLE,
+		                       "Introspect",
+		                       NULL,
+		                       G_VARIANT_TYPE ("(s)"),
+		                       G_DBUS_CALL_FLAGS_NONE,
+		                       5000,
+		                       priv->main_cancellable,
+		                       iface_introspect_cb,
+		                       self);
+	}
+
+	priv->starting_pending_count++;
+	nm_dbus_connection_call_get_all (priv->dbus_connection,
+	                                 priv->name_owner->str,
+	                                 priv->object_path->str,
+	                                 NM_WPAS_DBUS_IFACE_INTERFACE,
+	                                 5000,
+	                                 priv->main_cancellable,
+	                                 _get_all_main_cb,
+	                                 self);
+
+	if (_get_capability (priv, NM_SUPPL_CAP_TYPE_P2P) == NM_TERNARY_TRUE) {
+		priv->peer_properties_changed_id = nm_dbus_connection_signal_subscribe_properties_changed (priv->dbus_connection,
+		                                                                                           priv->name_owner->str,
+		                                                                                           NULL,
+		                                                                                           NM_WPAS_DBUS_IFACE_PEER,
+		                                                                                           _peer_properties_changed_cb,
+		                                                                                           self,
+		                                                                                           NULL);
+
+		priv->starting_pending_count++;
+		nm_dbus_connection_call_get_all (priv->dbus_connection,
+		                                 priv->name_owner->str,
+		                                 priv->object_path->str,
+		                                 NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE,
+		                                 5000,
+		                                 priv->main_cancellable,
+		                                 _get_all_p2p_device_cb,
+		                                 self);
+	}
 }
 
 NMSupplicantInterface *
-nm_supplicant_interface_new (const char *ifname,
-                             const char *object_path,
-                             NMSupplicantDriver driver,
-                             NMSupplicantFeature fast_support,
-                             NMSupplicantFeature ap_support,
-                             NMSupplicantFeature pmf_support,
-                             NMSupplicantFeature fils_support,
-                             NMSupplicantFeature p2p_support,
-                             NMSupplicantFeature mesh_support,
-                             NMSupplicantFeature wfd_support,
-                             NMSupplicantFeature ft_support,
-                             NMSupplicantFeature sha384_support)
-{
-	/* One of ifname or path need to be set */
-	g_return_val_if_fail (ifname != NULL || object_path != NULL, NULL);
-	g_return_val_if_fail (ifname == NULL || object_path == NULL, NULL);
+nm_supplicant_interface_new (NMSupplicantManager *supplicant_manager,
+                             NMRefString *object_path,
+                             int ifindex,
+                             NMSupplicantDriver driver)
+{
+	nm_assert (NM_IS_SUPPLICANT_MANAGER (supplicant_manager));
 
 	return g_object_new (NM_TYPE_SUPPLICANT_INTERFACE,
-	                     NM_SUPPLICANT_INTERFACE_IFACE, ifname,
-	                     NM_SUPPLICANT_INTERFACE_OBJECT_PATH, object_path,
+	                     NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, supplicant_manager,
+	                     NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, object_path,
+	                     NM_SUPPLICANT_INTERFACE_IFINDEX, ifindex,
 	                     NM_SUPPLICANT_INTERFACE_DRIVER, (guint) driver,
-	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, (int) fast_support,
-	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
-	                     NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, (int) pmf_support,
-	                     NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, (int) fils_support,
-	                     NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, (int) p2p_support,
-	                     NM_SUPPLICANT_INTERFACE_MESH_SUPPORT, (int) mesh_support,
-	                     NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, (int) wfd_support,
-	                     NM_SUPPLICANT_INTERFACE_FT_SUPPORT, (int) ft_support,
-	                     NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT, (int) sha384_support,
 	                     NULL);
 }
 
@@ -2907,7 +3168,11 @@ dispose (GObject *object)
 	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (object);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
-	nm_supplicant_interface_cancel_wps (self);
+	if (priv->state != NM_SUPPLICANT_INTERFACE_STATE_DOWN)
+		set_state_down (self, TRUE, "NMSupplicantInterface is disposing");
+
+	nm_assert (c_list_is_empty (&self->supp_lst));
+
 	if (priv->wps_data) {
 		/* we shut down, but an asynchronous Cancel request is pending.
 		 * We don't want to cancel it, so mark wps-data that @self is gone.
@@ -2917,38 +3182,21 @@ dispose (GObject *object)
 		priv->wps_data = NULL;
 	}
 
-	if (priv->assoc_data) {
-		gs_free_error GError *error = NULL;
-
-		nm_utils_error_set_cancelled (&error, TRUE, "NMSupplicantInterface");
-		assoc_return (self, error, "cancelled due to dispose of supplicant interface");
-	}
-
-	if (priv->iface_proxy)
-		g_signal_handlers_disconnect_by_data (priv->iface_proxy, object);
-	g_clear_object (&priv->iface_proxy);
-	if (priv->p2p_proxy)
-		g_signal_handlers_disconnect_by_data (priv->p2p_proxy, object);
-	g_clear_object (&priv->p2p_proxy);
-	if (priv->group_proxy)
-		g_signal_handlers_disconnect_by_data (priv->group_proxy, object);
-	g_clear_object (&priv->group_proxy);
+	nm_assert (!priv->assoc_data);
 
-	nm_clear_g_cancellable (&priv->init_cancellable);
-	nm_clear_g_cancellable (&priv->other_cancellable);
+	nm_clear_pointer (&priv->bss_idx, g_hash_table_destroy);
+	nm_clear_pointer (&priv->peer_idx, g_hash_table_destroy);
 
-	if (priv->wpas_proxy)
-		g_signal_handlers_disconnect_by_data (priv->wpas_proxy, object);
-	g_clear_object (&priv->wpas_proxy);
-	g_clear_pointer (&priv->bss_proxies, g_hash_table_destroy);
-	g_clear_pointer (&priv->peer_proxies, g_hash_table_destroy);
-
-	g_clear_pointer (&priv->net_path, g_free);
-	g_clear_pointer (&priv->dev, g_free);
-	g_clear_pointer (&priv->object_path, g_free);
-	g_clear_pointer (&priv->current_bss, g_free);
+	nm_clear_pointer (&priv->current_bss, nm_ref_string_unref);
 
 	G_OBJECT_CLASS (nm_supplicant_interface_parent_class)->dispose (object);
+
+	nm_clear_pointer (&priv->object_path, nm_ref_string_unref);
+	nm_clear_pointer (&priv->name_owner, nm_ref_string_unref);
+	g_clear_object (&priv->supplicant_manager);
+	g_clear_object (&priv->dbus_connection);
+	nm_clear_g_free (&priv->ifname);
+	nm_assert (!priv->net_path);
 }
 
 static void
@@ -2956,10 +3204,36 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	object_class->dispose = dispose;
+	g_type_class_add_private (object_class, sizeof (NMSupplicantInterfacePrivate));
+
+	object_class->constructed  = constructed;
+	object_class->dispose      = dispose;
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 
+	obj_properties[PROP_SUPPLICANT_MANAGER] =
+	    g_param_spec_pointer (NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER, "", "",
+	                          G_PARAM_WRITABLE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_DBUS_OBJECT_PATH] =
+	    g_param_spec_pointer (NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH, "", "",
+	                          G_PARAM_WRITABLE |
+	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_IFINDEX] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_IFINDEX, "", "",
+	                      0, G_MAXINT, 0,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_DRIVER] =
+	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "",
+	                       0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS,
+	                       G_PARAM_WRITABLE |
+	                       G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_SCANNING] =
 	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_SCANNING, "", "",
 	                          FALSE,
@@ -2970,18 +3244,6 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                         NULL,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_IFACE] =
-	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_IFACE, "", "",
-	                         NULL,
-	                         G_PARAM_WRITABLE |
-	                         G_PARAM_CONSTRUCT_ONLY |
-	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_OBJECT_PATH] =
-	    g_param_spec_string (NM_SUPPLICANT_INTERFACE_OBJECT_PATH, "", "",
-	                         NULL,
-	                         G_PARAM_WRITABLE |
-	                         G_PARAM_CONSTRUCT_ONLY |
-	                         G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_P2P_GROUP_JOINED] =
 	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED, "", "",
 	                          FALSE,
@@ -2997,89 +3259,11 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                          FALSE,
 	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_DRIVER] =
-	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_DRIVER, "", "",
-	                       0, G_MAXUINT, NM_SUPPLICANT_DRIVER_WIRELESS,
-	                       G_PARAM_WRITABLE |
-	                       G_PARAM_CONSTRUCT_ONLY |
-	                       G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_P2P_AVAILABLE] =
 	    g_param_spec_boolean (NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE, "", "",
 	                          FALSE,
 	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_FAST_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_AP_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_AP_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_PMF_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_FILS_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_P2P_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_MESH_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_MESH_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_WFD_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_FT_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FT_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_SHA384_SUPPORT] =
-	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT, "", "",
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      NM_SUPPLICANT_FEATURE_YES,
-	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
-	                      G_PARAM_WRITABLE |
-	                      G_PARAM_CONSTRUCT_ONLY |
-	                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_AUTH_STATE] =
 	    g_param_spec_uint (NM_SUPPLICANT_INTERFACE_AUTH_STATE, "", "",
 	                       NM_SUPPLICANT_AUTH_STATE_UNKNOWN,
@@ -3098,61 +3282,21 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
 
-	signals[REMOVED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
-
-	signals[BSS_UPDATED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_UPDATED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
-
-	signals[BSS_REMOVED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
-	signals[PEER_UPDATED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_UPDATED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_VARIANT);
-
-	signals[PEER_REMOVED] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_REMOVED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
-	signals[SCAN_DONE] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_SCAN_DONE,
+	signals[BSS_CHANGED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_BSS_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_LAST,
 	                  0,
 	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+	                  G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
 
-	signals[CREDENTIALS_REQUEST] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST,
+	signals[PEER_CHANGED] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_PEER_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_LAST,
 	                  0,
 	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
+	                  G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
 
 	signals[WPS_CREDENTIALS] =
 	    g_signal_new (NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
@@ -3177,12 +3321,4 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 1, G_TYPE_STRING);
-
-	signals[GROUP_FORMATION_FAILURE] =
-	    g_signal_new (NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 1, G_TYPE_STRING);
 }
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index 3580f754..eb414f26 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -9,15 +9,17 @@
 
 #include "nm-supplicant-types.h"
 
+#include "c-list/src/c-list.h"
+
 /*
  * Supplicant interface states
  *   A mix of wpa_supplicant interface states and internal states.
  */
 typedef enum {
-	NM_SUPPLICANT_INTERFACE_STATE_INVALID = -1,
-	NM_SUPPLICANT_INTERFACE_STATE_INIT = 0,
-	NM_SUPPLICANT_INTERFACE_STATE_STARTING,
-	NM_SUPPLICANT_INTERFACE_STATE_READY,
+	NM_SUPPLICANT_INTERFACE_STATE_INVALID = 0,
+
+	NM_SUPPLICANT_INTERFACE_STATE_STARTING = 1,
+
 	NM_SUPPLICANT_INTERFACE_STATE_DISABLED,
 	NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED,
 	NM_SUPPLICANT_INTERFACE_STATE_INACTIVE,
@@ -28,9 +30,24 @@ typedef enum {
 	NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE,
 	NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE,
 	NM_SUPPLICANT_INTERFACE_STATE_COMPLETED,
+
 	NM_SUPPLICANT_INTERFACE_STATE_DOWN,
 } NMSupplicantInterfaceState;
 
+static inline gboolean
+nm_supplicant_interface_state_is_operational (NMSupplicantInterfaceState state)
+{
+	return    state > NM_SUPPLICANT_INTERFACE_STATE_STARTING
+	       && state < NM_SUPPLICANT_INTERFACE_STATE_DOWN;
+}
+
+static inline gboolean
+nm_supplicant_interface_state_is_associated (NMSupplicantInterfaceState state)
+{
+	return    state >= NM_SUPPLICANT_INTERFACE_STATE_AUTHENTICATING
+	       && state <= NM_SUPPLICANT_INTERFACE_STATE_COMPLETED;
+}
+
 typedef enum {
 	NM_SUPPLICANT_AUTH_STATE_UNKNOWN,
 	NM_SUPPLICANT_AUTH_STATE_STARTED,
@@ -46,60 +63,48 @@ typedef enum {
 #define NM_IS_SUPPLICANT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_SUPPLICANT_INTERFACE))
 #define NM_SUPPLICANT_INTERFACE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_SUPPLICANT_INTERFACE, NMSupplicantInterfaceClass))
 
-/* Properties */
-#define NM_SUPPLICANT_INTERFACE_IFACE            "iface"
-#define NM_SUPPLICANT_INTERFACE_OBJECT_PATH      "object-path"
-#define NM_SUPPLICANT_INTERFACE_SCANNING         "scanning"
-#define NM_SUPPLICANT_INTERFACE_CURRENT_BSS      "current-bss"
-#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED "p2p-group-joined"
-#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH   "p2p-group-path"
-#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER  "p2p-group-owner"
-#define NM_SUPPLICANT_INTERFACE_DRIVER           "driver"
-#define NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE    "p2p-available"
-#define NM_SUPPLICANT_INTERFACE_FAST_SUPPORT     "fast-support"
-#define NM_SUPPLICANT_INTERFACE_AP_SUPPORT       "ap-support"
-#define NM_SUPPLICANT_INTERFACE_PMF_SUPPORT      "pmf-support"
-#define NM_SUPPLICANT_INTERFACE_FILS_SUPPORT     "fils-support"
-#define NM_SUPPLICANT_INTERFACE_P2P_SUPPORT      "p2p-support"
-#define NM_SUPPLICANT_INTERFACE_MESH_SUPPORT     "mesh-support"
-#define NM_SUPPLICANT_INTERFACE_WFD_SUPPORT      "wfd-support"
-#define NM_SUPPLICANT_INTERFACE_FT_SUPPORT       "ft-support"
-#define NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT   "sha384-support"
-#define NM_SUPPLICANT_INTERFACE_AUTH_STATE       "auth-state"
-
-/* Signals */
-#define NM_SUPPLICANT_INTERFACE_STATE            "state"
-#define NM_SUPPLICANT_INTERFACE_REMOVED          "removed"
-#define NM_SUPPLICANT_INTERFACE_BSS_UPDATED      "bss-updated"
-#define NM_SUPPLICANT_INTERFACE_BSS_REMOVED      "bss-removed"
-#define NM_SUPPLICANT_INTERFACE_PEER_UPDATED     "peer-updated"
-#define NM_SUPPLICANT_INTERFACE_PEER_REMOVED     "peer-removed"
-#define NM_SUPPLICANT_INTERFACE_SCAN_DONE        "scan-done"
-#define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request"
-#define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS  "wps-credentials"
+#define NM_SUPPLICANT_INTERFACE_SUPPLICANT_MANAGER  "supplicant-manager"
+#define NM_SUPPLICANT_INTERFACE_DBUS_OBJECT_PATH    "dbus-object-path"
+#define NM_SUPPLICANT_INTERFACE_IFINDEX             "ifindex"
+#define NM_SUPPLICANT_INTERFACE_SCANNING            "scanning"
+#define NM_SUPPLICANT_INTERFACE_CURRENT_BSS         "current-bss"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_JOINED    "p2p-group-joined"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_PATH      "p2p-group-path"
+#define NM_SUPPLICANT_INTERFACE_P2P_GROUP_OWNER     "p2p-group-owner"
+#define NM_SUPPLICANT_INTERFACE_DRIVER              "driver"
+#define NM_SUPPLICANT_INTERFACE_P2P_AVAILABLE       "p2p-available"
+#define NM_SUPPLICANT_INTERFACE_AUTH_STATE          "auth-state"
+
+#define NM_SUPPLICANT_INTERFACE_STATE                   "state"
+#define NM_SUPPLICANT_INTERFACE_BSS_CHANGED             "bss-changed"
+#define NM_SUPPLICANT_INTERFACE_PEER_CHANGED            "peer-changed"
+#define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS         "wps-credentials"
 #define NM_SUPPLICANT_INTERFACE_GROUP_STARTED           "group-started"
 #define NM_SUPPLICANT_INTERFACE_GROUP_FINISHED          "group-finished"
-#define NM_SUPPLICANT_INTERFACE_GROUP_FORMATION_FAILURE "group-formation-failure"
 
 typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass;
 
+struct _NMSupplicantInterfacePrivate;
+
+struct _NMSupplicantInterface {
+	GObject parent;
+	CList supp_lst;
+	struct _NMSupplicantInterfacePrivate *_priv;
+};
+
 GType nm_supplicant_interface_get_type (void);
 
-NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
-                                                     const char *object_path,
-                                                     NMSupplicantDriver driver,
-                                                     NMSupplicantFeature fast_support,
-                                                     NMSupplicantFeature ap_support,
-                                                     NMSupplicantFeature pmf_support,
-                                                     NMSupplicantFeature fils_support,
-                                                     NMSupplicantFeature p2p_support,
-                                                     NMSupplicantFeature mesh_support,
-                                                     NMSupplicantFeature wfd_support,
-                                                     NMSupplicantFeature ft_support,
-                                                     NMSupplicantFeature sha384_support);
-
-void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
-                                                       gboolean available);
+NMSupplicantInterface *nm_supplicant_interface_new (NMSupplicantManager *supplicant_manager,
+                                                    NMRefString *object_path,
+                                                    int ifindex,
+                                                    NMSupplicantDriver driver);
+
+NMRefString *nm_supplicant_interface_get_name_owner (NMSupplicantInterface *self);
+NMRefString *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface);
+
+void _nm_supplicant_interface_set_state_down (NMSupplicantInterface * self,
+                                              gboolean force_remove_from_supplicant,
+                                              const char *reason);
 
 typedef void (*NMSupplicantInterfaceAssocCb) (NMSupplicantInterface *iface,
                                               GError *error,
@@ -123,8 +128,6 @@ nm_supplicant_interface_disconnect_async (NMSupplicantInterface * self,
                                           NMSupplicantInterfaceDisconnectCb callback,
                                           gpointer user_data);
 
-const char *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface);
-
 void nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
                                            GBytes *const*ssids,
                                            guint ssids_len);
@@ -135,7 +138,7 @@ const char *nm_supplicant_interface_state_to_string (NMSupplicantInterfaceState
 
 gboolean nm_supplicant_interface_get_scanning (NMSupplicantInterface *self);
 
-const char *nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self);
+NMRefString *nm_supplicant_interface_get_current_bss (NMSupplicantInterface *self);
 
 gint64 nm_supplicant_interface_get_last_scan (NMSupplicantInterface *self);
 
@@ -143,7 +146,7 @@ const char *nm_supplicant_interface_get_ifname (NMSupplicantInterface *self);
 
 guint nm_supplicant_interface_get_max_scan_ssids (NMSupplicantInterface *self);
 
-gboolean nm_supplicant_interface_get_has_credentials_request (NMSupplicantInterface *self);
+gboolean nm_supplicant_interface_get_p2p_available (NMSupplicantInterface *self);
 
 gboolean nm_supplicant_interface_get_p2p_group_joined (NMSupplicantInterface *self);
 
@@ -151,11 +154,6 @@ const char* nm_supplicant_interface_get_p2p_group_path (NMSupplicantInterface *s
 
 gboolean nm_supplicant_interface_get_p2p_group_owner (NMSupplicantInterface *self);
 
-gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
-                                                    const char *field,
-                                                    const char *value,
-                                                    GError **error);
-
 void nm_supplicant_interface_p2p_start_find (NMSupplicantInterface *self,
                                              guint timeout);
 void nm_supplicant_interface_p2p_stop_find (NMSupplicantInterface *self);
@@ -167,41 +165,13 @@ void nm_supplicant_interface_p2p_connect (NMSupplicantInterface * self,
 void nm_supplicant_interface_p2p_cancel_connect (NMSupplicantInterface * self);
 void nm_supplicant_interface_p2p_disconnect (NMSupplicantInterface * self);
 
-NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_mesh_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_ft_support (NMSupplicantInterface *self);
-NMSupplicantFeature nm_supplicant_interface_get_sha384_support (NMSupplicantInterface *self);
-
-void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
-                                             NMSupplicantFeature apmode);
-
-void nm_supplicant_interface_set_fast_support (NMSupplicantInterface *self,
-                                               NMSupplicantFeature fast_support);
-
-void nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
-                                              NMSupplicantFeature pmf_support);
-
-void nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
-                                               NMSupplicantFeature fils_support);
-
-void nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
-                                              NMSupplicantFeature p2p_support);
-
-void nm_supplicant_interface_set_mesh_support (NMSupplicantInterface *self,
-                                               NMSupplicantFeature mesh_support);
-
-void nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
-                                              NMSupplicantFeature wfd_support);
+void nm_supplicant_interface_set_global_capabilities (NMSupplicantInterface *self,
+                                                      NMSupplCapMask value);
 
-void nm_supplicant_interface_set_ft_support (NMSupplicantInterface *self,
-                                             NMSupplicantFeature ft_support);
+NMTernary nm_supplicant_interface_get_capability (NMSupplicantInterface *self,
+                                                  NMSupplCapType type);
 
-void nm_supplicant_interface_set_sha384_support (NMSupplicantInterface *self,
-                                                 NMSupplicantFeature sha384_support);
+NMSupplCapMask nm_supplicant_interface_get_capabilities (NMSupplicantInterface *self);
 
 void nm_supplicant_interface_enroll_wps (NMSupplicantInterface *self,
                                          const char *const type,
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
index 49581f6e..cf5cf111 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -8,29 +8,64 @@
 
 #include "nm-supplicant-manager.h"
 
+#include "nm-core-internal.h"
+#include "nm-dbus-manager.h"
+#include "nm-glib-aux/nm-dbus-aux.h"
+#include "nm-glib-aux/nm-ref-string.h"
 #include "nm-supplicant-interface.h"
 #include "nm-supplicant-types.h"
-#include "nm-core-internal.h"
+#include "platform/nm-platform.h"
 
 /*****************************************************************************/
 
+#define CREATE_IFACE_TRY_COUNT_MAX 7u
+
+struct _NMSupplMgrCreateIfaceHandle {
+	NMSupplicantManager *self;
+	CList create_iface_lst;
+	GCancellable *cancellable;
+	NMSupplicantManagerCreateInterfaceCb callback;
+	gpointer callback_user_data;
+	NMShutdownWaitObjHandle *shutdown_handle;
+	NMRefString *name_owner;
+	GError *fail_on_idle_error;
+	NMSupplicantDriver driver;
+	int ifindex;
+	guint fail_on_idle_id;
+	guint create_iface_try_count:5;
+};
+
+enum {
+	AVAILABLE_CHANGED,
+	LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
 typedef struct {
-	GDBusProxy *     proxy;
-	GCancellable *   cancellable;
-	gboolean         running;
-
-	GSList          *ifaces;
-	NMSupplicantFeature fast_support;
-	NMSupplicantFeature ap_support;
-	NMSupplicantFeature pmf_support;
-	NMSupplicantFeature fils_support;
-	NMSupplicantFeature p2p_support;
-	NMSupplicantFeature mesh_support;
-	NMSupplicantFeature wfd_support;
-	NMSupplicantFeature ft_support;
-	NMSupplicantFeature sha384_support;
-	guint             die_count_reset_id;
-	guint             die_count;
+	GDBusConnection *dbus_connection;
+
+	NMRefString *name_owner;
+
+	GCancellable *get_name_owner_cancellable;
+	GCancellable *get_capabilities_cancellable;
+	GCancellable *poke_name_owner_cancellable;
+
+	GHashTable *supp_ifaces;
+	CList supp_lst_head;
+
+	CList create_iface_lst_head;
+
+	NMSupplCapMask capabilities;
+
+	guint name_owner_changed_id;
+	guint interface_removed_id;
+	guint poke_name_owner_timeout_id;
+	guint available_reset_id;
+
+	/* see nm_supplicant_manager_get_available(). */
+	NMTernary available:2;
+
 } NMSupplicantManagerPrivate;
 
 struct _NMSupplicantManager {
@@ -46,6 +81,8 @@ G_DEFINE_TYPE (NMSupplicantManager, nm_supplicant_manager, G_TYPE_OBJECT)
 
 #define NM_SUPPLICANT_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSupplicantManager, NM_IS_SUPPLICANT_MANAGER)
 
+NM_DEFINE_SINGLETON_GETTER (NMSupplicantManager, nm_supplicant_manager_get, NM_TYPE_SUPPLICANT_MANAGER);
+
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN      LOGD_SUPPLICANT
@@ -57,72 +94,140 @@ NM_CACHED_QUARK_FCN ("nm-supplicant-error-quark", nm_supplicant_error_quark)
 
 /*****************************************************************************/
 
-static gboolean
-die_count_exceeded (guint32 count)
+static void _create_iface_proceed_all (NMSupplicantManager *self,
+                                       GError *error);
+static void _supp_iface_add (NMSupplicantManager *self,
+                             NMRefString *iface_path,
+                             NMSupplicantInterface *supp_iface);
+static void _supp_iface_remove_one (NMSupplicantManager *self,
+                                    NMSupplicantInterface *supp_iface,
+                                    gboolean force_remove_from_supplicant,
+                                    const char *reason);
+static void _create_iface_dbus_call_get_interface (NMSupplicantManager *self,
+                                                   NMSupplMgrCreateIfaceHandle *handle,
+                                                   const char *ifname);
+static void _create_iface_dbus_call_create_interface (NMSupplicantManager *self,
+                                                      NMSupplMgrCreateIfaceHandle *handle,
+                                                      const char *ifname);
+static gboolean _create_iface_fail_on_idle_cb (gpointer user_data);
+
+static gboolean _available_reset_cb (gpointer user_data);
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE (nm_supplicant_driver_to_string, NMSupplicantDriver,
+	NM_UTILS_LOOKUP_DEFAULT_WARN (NULL),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_UNKNOWN,  "???"),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_WIRELESS, NM_WPAS_DEFAULT_WIFI_DRIVER),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_WIRED,    "wired"),
+	NM_UTILS_LOOKUP_ITEM    (NM_SUPPLICANT_DRIVER_MACSEC,   "macsec_linux"),
+);
+
+/*****************************************************************************/
+
+NMTernary
+nm_supplicant_manager_is_available (NMSupplicantManager *self)
 {
-	return count > 2;
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NM_TERNARY_FALSE);
+
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->available;
 }
 
-static gboolean
-is_available (NMSupplicantManager *self)
+NMRefString *
+nm_supplicant_manager_get_dbus_name_owner (NMSupplicantManager *self)
 {
-	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
 
-	return    priv->running
-	       && !die_count_exceeded (priv->die_count);
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->name_owner;
+}
+
+GDBusConnection *nm_supplicant_manager_get_dbus_connection (NMSupplicantManager *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
+
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->dbus_connection;
+}
+
+NMSupplCapMask
+nm_supplicant_manager_get_global_capabilities (NMSupplicantManager *self)
+{
+	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NM_SUPPL_CAP_MASK_NONE);
+
+	return NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->capabilities;
 }
 
 /*****************************************************************************/
 
 static void
-_sup_iface_last_ref (gpointer data,
-                     GObject *object,
-                     gboolean is_last_ref)
+_caps_set (NMSupplicantManagerPrivate *priv,
+           NMSupplCapType type,
+           NMTernary value)
 {
-	NMSupplicantManager *self = data;
-	NMSupplicantManagerPrivate *priv;
-	NMSupplicantInterface *sup_iface = (NMSupplicantInterface *) object;
-	const char *op;
+	priv->capabilities = NM_SUPPL_CAP_MASK_SET (priv->capabilities, type, value);
+}
 
-	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
-	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (sup_iface));
-	g_return_if_fail (is_last_ref);
+static char
+_caps_to_char (NMSupplicantManagerPrivate *priv,
+               NMSupplCapType type)
+{
+	NMTernary val;
+
+	val = NM_SUPPL_CAP_MASK_GET (priv->capabilities, type);
+	if (val == NM_TERNARY_TRUE)
+		return '+';
+	if (val == NM_TERNARY_FALSE)
+		return '-';
+	return '?';
+}
 
-	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+/*****************************************************************************/
 
-	if (!g_slist_find (priv->ifaces, sup_iface))
-		g_return_if_reached ();
-
-	/* Ask wpa_supplicant to remove this interface */
-	if (   priv->running
-	    && priv->proxy
-	    && (op = nm_supplicant_interface_get_object_path (sup_iface))) {
-		g_dbus_proxy_call (priv->proxy,
-		                   "RemoveInterface",
-		                   g_variant_new ("(o)", op),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   3000,
-		                   NULL,
-		                   NULL,
-		                   NULL);
-	}
+static void
+_dbus_call_remove_interface (GDBusConnection *dbus_connection,
+                             const char *name_owner,
+                             const char *iface_path)
+{
+	nm_assert (G_IS_DBUS_CONNECTION (dbus_connection));
+	nm_assert (name_owner);
+	nm_assert (iface_path);
+
+	g_dbus_connection_call (dbus_connection,
+	                        name_owner,
+	                        NM_WPAS_DBUS_PATH,
+	                        NM_WPAS_DBUS_INTERFACE,
+	                        "RemoveInterface",
+	                        g_variant_new ("(o)", iface_path),
+	                        G_VARIANT_TYPE ("()"),
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        10000,
+	                        NULL,
+	                        NULL,
+	                        NULL);
+}
 
-	priv->ifaces = g_slist_remove (priv->ifaces, sup_iface);
-	g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self);
+void
+_nm_supplicant_manager_dbus_call_remove_interface (NMSupplicantManager *self,
+                                                   const char *name_owner,
+                                                   const char *iface_path)
+{
+	_dbus_call_remove_interface (NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->dbus_connection,
+	                             name_owner,
+	                             iface_path);
 }
 
+/*****************************************************************************/
+
 static void
 on_supplicant_wfd_ies_set (GObject *source_object,
-                           GAsyncResult *res,
+                           GAsyncResult *result,
                            gpointer user_data)
 {
-	gs_unref_variant GVariant *result = NULL;
+	gs_unref_variant GVariant *res = NULL;
 	gs_free_error GError *error = NULL;
 
-	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), res, &error);
-
-	if (!result)
-		_LOGW ("failed to set WFD IEs on wpa_supplicant: %s", error->message);
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), result, &error);
+	if (!res)
+		_LOGD ("failed to set WFD IEs on wpa_supplicant: %s", error->message);
 }
 
 /**
@@ -140,172 +245,677 @@ nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
 {
 	NMSupplicantManagerPrivate *priv;
 	GVariantBuilder params;
-	GVariant *val;
 
 	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self));
 
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	_LOGD ("setting WFD IEs for P2P operation");
+	if (!priv->name_owner)
+		return;
 
-	if (wfd_ies)
-		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                 g_bytes_get_data (wfd_ies, NULL),
-		                                 g_bytes_get_size (wfd_ies),
-		                                 sizeof (guint8));
-	else
-		val = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                 NULL, 0, sizeof (guint8));
+	_LOGD ("setting WFD IEs for P2P operation on %s", priv->name_owner->str);
 
 	g_variant_builder_init (&params, G_VARIANT_TYPE ("(ssv)"));
 
-	g_variant_builder_add (&params, "s", g_dbus_proxy_get_interface_name (priv->proxy));
+	g_variant_builder_add (&params, "s", NM_WPAS_DBUS_INTERFACE);
 	g_variant_builder_add (&params, "s", "WFDIEs");
-	g_variant_builder_add_value (&params, g_variant_new_variant (val));
+	g_variant_builder_add_value (&params,
+	                             g_variant_new_variant (nm_utils_gbytes_to_variant_ay (wfd_ies)));
 
-	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
-	                        g_dbus_proxy_get_name (priv->proxy),
-	                        g_dbus_proxy_get_object_path (priv->proxy),
-	                        "org.freedesktop.DBus.Properties",
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner->str,
+	                        NM_WPAS_DBUS_PATH,
+	                        DBUS_INTERFACE_PROPERTIES,
 	                        "Set",
 	                        g_variant_builder_end (&params),
-	                        G_VARIANT_TYPE_UNIT,
+	                        G_VARIANT_TYPE ("()"),
 	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
-	                        1000,
+	                        3000,
 	                        NULL,
 	                        on_supplicant_wfd_ies_set,
 	                        NULL);
 }
 
-/**
- * nm_supplicant_manager_create_interface:
- * @self: the #NMSupplicantManager
- * @ifname: the interface for which to obtain the supplicant interface
- * @is_wireless: whether the interface is supposed to be wireless.
- *
- * Note: the manager owns a reference to the instance and the only way to
- *   get the manager to release it, is by dropping all other references
- *   to the supplicant-interface (or destroying the manager).
- *
- * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL.
- *   Must be unrefed at the end.
- * */
-NMSupplicantInterface *
+/*****************************************************************************/
+
+static gboolean
+_poke_name_owner_timeout_cb (gpointer user_data)
+{
+	NMSupplicantManager *self = user_data;
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	gs_free_error GError *error = NULL;
+	gboolean available_changed = FALSE;
+
+	nm_assert (!priv->name_owner);
+
+	priv->poke_name_owner_timeout_id = 0;
+	nm_clear_g_cancellable (&priv->poke_name_owner_cancellable);
+
+	_LOGT ("poke service \"%s\" failed for good with timeout%s",
+	       NM_WPAS_DBUS_SERVICE,
+	         (priv->available == NM_TERNARY_DEFAULT)
+	       ? " (set as not available)"
+	       : "");
+
+	if (priv->available == NM_TERNARY_DEFAULT) {
+		/* the available flag usually only changes together with the name-owner.
+		 * However, if we tries to poke the service but failed to start it (with
+		 * timeout), was also set it as (hard) not available. */
+		priv->available = NM_TERNARY_FALSE;
+		nm_clear_g_source (&priv->available_reset_id);
+		priv->available_reset_id = g_timeout_add_seconds (60,
+		                                                  _available_reset_cb,
+		                                                  self);
+		available_changed = TRUE;
+	}
+
+	nm_utils_error_set (&error,
+	                    NM_UTILS_ERROR_UNKNOWN,
+	                    "Failed to D-Bus activate wpa_supplicant service");
+
+	_create_iface_proceed_all (self, error);
+
+	if (available_changed) {
+		/* We delay the emitting of the notification after aborting all
+		 * create-iface handles. */
+		g_signal_emit (self, signals[AVAILABLE_CHANGED], 0);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_poke_name_owner_cb (GObject *source,
+                     GAsyncResult *result,
+                     gpointer user_data)
+{
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+
+	res = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (!res)
+		_LOGT ("poke service \"%s\" failed: %s", NM_WPAS_DBUS_SERVICE, error->message);
+	else
+		_LOGT ("poke service \"%s\" succeeded", NM_WPAS_DBUS_SERVICE);
+
+	/* in both cases, we react the same: we wait for the name owner to appear
+	 * or hit the timeout. */
+}
+
+static void
+_poke_name_owner (NMSupplicantManager *self)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	if (priv->poke_name_owner_cancellable)
+		return;
+
+	_LOGT ("poke service \"%s\"...", NM_WPAS_DBUS_SERVICE);
+
+	priv->poke_name_owner_cancellable = g_cancellable_new ();
+	priv->poke_name_owner_timeout_id = g_timeout_add (3000,
+	                                                  _poke_name_owner_timeout_cb,
+	                                                  self);
+	nm_dbus_connection_call_start_service_by_name (priv->dbus_connection,
+	                                               NM_WPAS_DBUS_SERVICE,
+	                                               5000,
+	                                               priv->poke_name_owner_cancellable,
+	                                               _poke_name_owner_cb,
+	                                               self);
+}
+
+/*****************************************************************************/
+
+static void
+_create_iface_complete (NMSupplMgrCreateIfaceHandle *handle,
+                        NMSupplicantInterface *supp_iface,
+                        GError *error)
+{
+	nm_assert (!supp_iface || NM_IS_SUPPLICANT_INTERFACE (supp_iface));
+	nm_assert ((!!supp_iface) != (!!error));
+
+	c_list_unlink (&handle->create_iface_lst);
+
+	nm_clear_g_source (&handle->fail_on_idle_id);
+
+	if (handle->callback) {
+		NMSupplicantManagerCreateInterfaceCb callback;
+
+		nm_assert (NM_IS_SUPPLICANT_MANAGER (handle->self));
+
+		callback = handle->callback;
+		handle->callback = NULL;
+		callback (handle->self,
+		          handle,
+		          supp_iface,
+		          error,
+		          handle->callback_user_data);
+	}
+
+	g_clear_error (&handle->fail_on_idle_error);
+
+	g_clear_object (&handle->self);
+
+	if (handle->shutdown_handle) {
+		/* we have a pending CreateInterface request. We keep the handle
+		 * instance alive. This is to remove the device again, once the
+		 * request completes. */
+		return;
+	}
+
+	nm_clear_g_cancellable (&handle->cancellable);
+	nm_ref_string_unref (handle->name_owner);
+
+	nm_g_slice_free_fcn (handle);
+}
+
+static void
+_create_iface_add (NMSupplicantManager *self,
+                   NMSupplMgrCreateIfaceHandle *handle,
+                   const char *iface_path_str,
+                   gboolean created_by_us)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	nm_auto_ref_string NMRefString *iface_path = NULL;
+	gs_unref_object NMSupplicantInterface *supp_iface = NULL;
+
+	iface_path = nm_ref_string_new (iface_path_str);
+
+	supp_iface = g_hash_table_lookup (priv->supp_ifaces, iface_path);
+	if (supp_iface) {
+		/* Now this is odd... Reuse the same interface. */
+		g_object_ref (supp_iface);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: interface %s on %s created (already existing)",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       iface_path_str,
+		       priv->name_owner->str);
+		_create_iface_complete (handle, supp_iface, NULL);
+		return;
+	}
+
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: interface %s on %s created%s",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       iface_path_str,
+	       priv->name_owner->str,
+	       created_by_us ? " (created by us)" : "");
+
+	supp_iface = nm_supplicant_interface_new (self,
+	                                          iface_path,
+	                                          handle->ifindex,
+	                                          handle->driver);
+
+	_supp_iface_add (self, iface_path, supp_iface);
+
+	_create_iface_complete (handle, supp_iface, NULL);
+}
+
+static void
+_create_iface_dbus_call_get_interface_cb (GObject *source,
+                                          GAsyncResult *result,
+                                          gpointer user_data)
+{
+	GDBusConnection *dbus_connection = G_DBUS_CONNECTION (source);
+	NMSupplMgrCreateIfaceHandle *handle;
+	NMSupplicantManager *self;
+	NMSupplicantManagerPrivate *priv;
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+	const char *iface_path_str;
+
+	res = g_dbus_connection_call_finish (dbus_connection, result, &error);
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	handle = user_data;
+	nm_assert (handle->callback);
+
+	self = handle->self;
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (handle->name_owner == priv->name_owner);
+
+	if (!res) {
+		char ifname[NMP_IFNAMSIZ];
+
+		if (   handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX
+		    && _nm_dbus_error_has_name (error, NM_WPAS_ERROR_UNKNOWN_IFACE)
+		    && nm_platform_if_indextoname (NM_PLATFORM_GET, handle->ifindex, ifname)) {
+			/* Before, supplicant told us the interface existed. Was there a race?
+			 * Try again. */
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call failed to get interface. Try to create it again (ifname \"%s\")",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       ifname);
+			_create_iface_dbus_call_create_interface (self, handle, ifname);
+			return;
+		}
+
+		g_clear_object (&handle->cancellable);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call to get interface failed: %s",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       error->message);
+		_create_iface_complete (handle, NULL, error);
+		return;
+	}
+
+	g_clear_object (&handle->cancellable);
+
+	g_variant_get (res, "(&o)", &iface_path_str);
+
+	_create_iface_add (self, handle, iface_path_str, FALSE);
+}
+
+static void
+_create_iface_dbus_call_create_interface_cb (GObject *source,
+                                             GAsyncResult *result,
+                                             gpointer user_data)
+{
+	GDBusConnection *dbus_connection = G_DBUS_CONNECTION (source);
+	NMSupplMgrCreateIfaceHandle *handle = user_data;
+	NMSupplicantManager *self;
+	NMSupplicantManagerPrivate *priv;
+	gs_unref_variant GVariant *res = NULL;
+	gs_free_error GError *error = NULL;
+	const char *iface_path_str;
+	char ifname[NMP_IFNAMSIZ];
+
+	res = g_dbus_connection_call_finish (dbus_connection, result, &error);
+
+	nm_shutdown_wait_obj_unregister (g_steal_pointer (&handle->shutdown_handle));
+
+	if (!res) {
+		if (   handle->callback
+		    && ({ nm_assert (handle->self); TRUE; })
+		    && _nm_dbus_error_has_name (error, NM_WPAS_ERROR_EXISTS_ERROR)
+		    && nm_platform_if_indextoname (NM_PLATFORM_GET, handle->ifindex, ifname)) {
+			self = handle->self;
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call failed to create interface. Try to get existing interface (ifname \"%s\")",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       ifname);
+			_create_iface_dbus_call_get_interface (self, handle, ifname);
+			return;
+		}
+		g_clear_object (&handle->cancellable);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: D-Bus call failed: %s",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       error->message);
+		_create_iface_complete (handle, NULL, error);
+		return;
+	}
+
+	g_clear_object (&handle->cancellable);
+
+	self = handle->self;
+	priv =   self
+	       ? NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)
+	       : NULL;
+
+	g_variant_get (res, "(&o)", &iface_path_str);
+
+	if (   !handle->callback
+	    || priv->name_owner != handle->name_owner) {
+		if (!handle->callback) {
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: request already cancelled but still remove interface %s in %s",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       iface_path_str,
+			       handle->name_owner->str);
+		} else {
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: name owner changed, still remove interface %s in %s",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       iface_path_str,
+			       handle->name_owner->str);
+			nm_utils_error_set (&error,
+			                    NM_UTILS_ERROR_UNKNOWN,
+			                    "The name owner changed since creating the interface");
+		}
+		_dbus_call_remove_interface (dbus_connection,
+		                             handle->name_owner->str,
+		                             iface_path_str);
+		_create_iface_complete (handle, NULL, error);
+		return;
+	}
+
+	_create_iface_add (self, handle, iface_path_str, TRUE);
+}
+
+static void
+_create_iface_dbus_call_get_interface (NMSupplicantManager *self,
+                                       NMSupplMgrCreateIfaceHandle *handle,
+                                       const char *ifname)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (handle->cancellable);
+	nm_assert (!handle->shutdown_handle);
+
+	g_dbus_connection_call (priv->dbus_connection,
+	                        priv->name_owner->str,
+	                        NM_WPAS_DBUS_PATH,
+	                        NM_WPAS_DBUS_INTERFACE,
+	                        "GetInterface",
+	                        g_variant_new ("(s)", ifname),
+	                        G_VARIANT_TYPE ("(o)"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        5000,
+	                        handle->cancellable,
+	                        _create_iface_dbus_call_get_interface_cb,
+	                        handle);
+}
+
+static void
+_create_iface_dbus_call_create_interface (NMSupplicantManager *self,
+                                          NMSupplMgrCreateIfaceHandle *handle,
+                                          const char *ifname)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	GVariantBuilder builder;
+
+	nm_assert (priv->name_owner == handle->name_owner);
+	nm_assert (handle->cancellable);
+	nm_assert (!handle->shutdown_handle);
+	nm_assert (handle->create_iface_try_count <= CREATE_IFACE_TRY_COUNT_MAX);
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&builder,
+	                       "{sv}",
+	                       "Driver",
+	                       g_variant_new_string (nm_supplicant_driver_to_string (handle->driver)));
+	g_variant_builder_add (&builder,
+	                       "{sv}",
+	                       "Ifname",
+	                       g_variant_new_string (ifname));
+
+	handle->shutdown_handle = nm_shutdown_wait_obj_register_cancellable_full (handle->cancellable,
+	                                                                          g_strdup_printf ("wpas-create-" NM_HASH_OBFUSCATE_PTR_FMT,
+	                                                                                           NM_HASH_OBFUSCATE_PTR (handle)),
+	                                                                          TRUE);
+	handle->create_iface_try_count++;
+	g_dbus_connection_call (priv->dbus_connection,
+	                        handle->name_owner->str,
+	                        NM_WPAS_DBUS_PATH,
+	                        NM_WPAS_DBUS_INTERFACE,
+	                        "CreateInterface",
+	                        g_variant_new ("(a{sv})", &builder),
+	                        G_VARIANT_TYPE ("(o)"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        5000,
+	                        handle->cancellable,
+	                        _create_iface_dbus_call_create_interface_cb,
+	                        handle);
+}
+
+static void
+_create_iface_dbus_start (NMSupplicantManager *self,
+                          NMSupplMgrCreateIfaceHandle *handle)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	char ifname[NMP_IFNAMSIZ];
+
+	nm_assert (priv->name_owner);
+	nm_assert (!handle->cancellable);
+
+	if (!nm_platform_if_indextoname (NM_PLATFORM_GET, handle->ifindex, ifname)) {
+		nm_utils_error_set (&handle->fail_on_idle_error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "Cannot find interface %d",
+		                    handle->ifindex);
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: creating interface fails to find interface name for ifindex %d",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       handle->ifindex);
+		handle->fail_on_idle_id = g_idle_add (_create_iface_fail_on_idle_cb, handle);
+		return;
+	}
+
+	/* Our handle keeps @self alive. That means, when NetworkManager shall shut
+	 * down, it's the responsibility of the callers to cancel the handles,
+	 * to initiate coordinated shutdown.
+	 *
+	 * However, we now issue a CreateInterface call. Even if the handle gets cancelled
+	 * (because of shutdown, or because the caller is no longer interested in the
+	 * result), we don't want to cancel this request. Instead, we want to get
+	 * the interface path and remove it right away.
+	 *
+	 * That means, the D-Bus call cannot be cancelled (because we always care about
+	 * the result). Only the @handle can be cancelled, but parts of the handle will
+	 * stick around to complete the task.
+	 *
+	 * See also handle->shutdown_handle.
+	 */
+	handle->name_owner = nm_ref_string_ref (priv->name_owner);
+	handle->cancellable = g_cancellable_new ();
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: creating interface (ifname \"%s\")...",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       ifname);
+	_create_iface_dbus_call_create_interface (self, handle, ifname);
+}
+
+static gboolean
+_create_iface_fail_on_idle_cb (gpointer user_data)
+{
+	NMSupplMgrCreateIfaceHandle *handle = user_data;
+
+	handle->fail_on_idle_id = 0;
+
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: fail with internal error: %s",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       handle->fail_on_idle_error->message);
+
+	_create_iface_complete (handle, NULL, handle->fail_on_idle_error);
+	return G_SOURCE_REMOVE;
+}
+
+NMSupplMgrCreateIfaceHandle *
 nm_supplicant_manager_create_interface (NMSupplicantManager *self,
-                                        const char *ifname,
-                                        NMSupplicantDriver driver)
+                                        int ifindex,
+                                        NMSupplicantDriver driver,
+                                        NMSupplicantManagerCreateInterfaceCb callback,
+                                        gpointer user_data)
 {
 	NMSupplicantManagerPrivate *priv;
-	NMSupplicantInterface *iface;
-	GSList *ifaces;
+	NMSupplMgrCreateIfaceHandle *handle;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
-	g_return_val_if_fail (ifname != NULL, NULL);
+	g_return_val_if_fail (ifindex > 0, NULL);
+	g_return_val_if_fail (callback, NULL);
+	nm_assert (nm_supplicant_driver_to_string (driver));
 
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	_LOGD ("(%s): creating new supplicant interface", ifname);
+	handle = g_slice_new (NMSupplMgrCreateIfaceHandle);
+	*handle = (NMSupplMgrCreateIfaceHandle) {
+		.self               = g_object_ref (self),
+		.callback           = callback,
+		.callback_user_data = user_data,
+		.driver             = driver,
+		.ifindex            = ifindex,
+	};
+	c_list_link_tail (&priv->create_iface_lst_head, &handle->create_iface_lst);
+
+	if (!priv->dbus_connection) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). Fail bacause no D-Bus connection to talk to wpa_supplicant...",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       ifindex,
+		       nm_supplicant_driver_to_string (driver));
+		nm_utils_error_set (&handle->fail_on_idle_error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "No D-Bus connection to talk to wpa_supplicant");
+		handle->fail_on_idle_id = g_idle_add (_create_iface_fail_on_idle_cb, handle);
+		return handle;
+	}
 
-	/* assert against not requesting duplicate interfaces. */
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
-		if (g_strcmp0 (nm_supplicant_interface_get_ifname (ifaces->data), ifname) == 0)
-			g_return_val_if_reached (NULL);
+	if (!priv->name_owner) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). %s",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       ifindex,
+		       nm_supplicant_driver_to_string (driver),
+		         priv->poke_name_owner_cancellable
+		       ? "Waiting for supplicant..."
+		       : "Poke supplicant...");
+		_poke_name_owner (self);
+		return handle;
 	}
 
-	iface = nm_supplicant_interface_new (ifname,
-	                                     NULL,
-	                                     driver,
-	                                     priv->fast_support,
-	                                     priv->ap_support,
-	                                     priv->pmf_support,
-	                                     priv->fils_support,
-	                                     priv->p2p_support,
-	                                     priv->mesh_support,
-	                                     priv->wfd_support,
-	                                     priv->ft_support,
-	                                     priv->sha384_support);
-
-	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
-	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
-
-	/* If we're making the supplicant take a time out for a bit, don't
-	 * let the supplicant interface start immediately, just let it hang
-	 * around in INIT state until we're ready to talk to the supplicant
-	 * again.
-	 */
-	if (is_available (self))
-		nm_supplicant_interface_set_supplicant_available (iface, TRUE);
+	if (priv->get_capabilities_cancellable) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). Waiting to fetch capabilities for %s...",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       ifindex,
+		       nm_supplicant_driver_to_string (driver),
+		       priv->name_owner->str);
+		return handle;
+	}
 
-	return iface;
+	_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: new request interface %d (driver %s). create interface on %s...",
+	       NM_HASH_OBFUSCATE_PTR (handle),
+	       ifindex,
+	       nm_supplicant_driver_to_string (driver),
+	       priv->name_owner->str);
+
+	_create_iface_dbus_start (self, handle);
+	return handle;
+}
+
+static void
+_create_iface_proceed_all (NMSupplicantManager *self,
+                           GError *error)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	NMSupplMgrCreateIfaceHandle *handle;
+
+	nm_assert (error || priv->name_owner);
+	nm_assert (error || !priv->get_capabilities_cancellable);
+
+	if (c_list_is_empty (&priv->create_iface_lst_head))
+		return;
+
+	if (error) {
+		CList alt_list;
+
+		/* we move the handles we want to proceed to a alternative list.
+		 * That is, because we invoke callbacks to the caller, who might
+		 * create another request right away. We don't want to proceed
+		 * that one. */
+		c_list_init (&alt_list);
+		c_list_splice (&alt_list, &priv->create_iface_lst_head);
+
+		while ((handle = c_list_last_entry (&alt_list, NMSupplMgrCreateIfaceHandle, create_iface_lst))) {
+			/* We don't need to keep @self alive. Every handle holds a reference already. */
+			_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: create interface failed: %s",
+			       NM_HASH_OBFUSCATE_PTR (handle),
+			       error->message);
+			_create_iface_complete (handle, NULL, error);
+		}
+		return;
+	}
+
+	/* start all the handles. This does not invoke callbacks, so the list of handles
+	 * cannot be modified while we iterate it. */
+	c_list_for_each_entry (handle, &priv->create_iface_lst_head, create_iface_lst) {
+		_LOGT ("create-iface["NM_HASH_OBFUSCATE_PTR_FMT"]: create interface on %s...",
+		       NM_HASH_OBFUSCATE_PTR (handle),
+		       priv->name_owner->str);
+		_create_iface_dbus_start (self, handle);
+	}
+}
+
+void
+nm_supplicant_manager_create_interface_cancel (NMSupplMgrCreateIfaceHandle *handle)
+{
+	gs_free_error GError *error = NULL;
+
+	if (!handle)
+		return;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (handle->self));
+	g_return_if_fail (handle->callback);
+	nm_assert (!c_list_is_empty (&handle->create_iface_lst));
+
+	nm_utils_error_set_cancelled (&error, FALSE, NULL);
+	_create_iface_complete (handle, NULL, error);
 }
 
-/**
- * nm_supplicant_manager_create_interface_from_path:
- * @self: the #NMSupplicantManager
- * @object_path: the DBus object path for which to obtain the supplicant interface
- *
- * Note: the manager owns a reference to the instance and the only way to
- *   get the manager to release it, is by dropping all other references
- *   to the supplicant-interface (or destroying the manager).
- *
- * Returns: (transfer full): returns a #NMSupplicantInterface or %NULL.
- *   Must be unrefed at the end.
- * */
 NMSupplicantInterface *
 nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
                                                   const char *object_path)
 {
 	NMSupplicantManagerPrivate *priv;
-	NMSupplicantInterface *iface;
-	GSList *ifaces;
+	NMSupplicantInterface *supp_iface;
+	nm_auto_ref_string NMRefString *iface_path = NULL;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL);
-	g_return_val_if_fail (object_path != NULL, NULL);
+	g_return_val_if_fail (object_path, NULL);
 
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	_LOGD ("creating new supplicant interface for dbus path %s", object_path);
+	iface_path = nm_ref_string_new (object_path);
 
-	/* assert against not requesting duplicate interfaces. */
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
-		if (g_strcmp0 (nm_supplicant_interface_get_object_path (ifaces->data), object_path) == 0)
-			g_return_val_if_reached (NULL);
-	}
+	supp_iface = g_hash_table_lookup (priv->supp_ifaces, iface_path);
 
-	iface = nm_supplicant_interface_new (NULL,
-	                                     object_path,
-	                                     NM_SUPPLICANT_DRIVER_WIRELESS,
-	                                     priv->fast_support,
-	                                     priv->ap_support,
-	                                     priv->pmf_support,
-	                                     priv->fils_support,
-	                                     priv->p2p_support,
-	                                     priv->mesh_support,
-	                                     priv->wfd_support,
-	                                     priv->ft_support,
-	                                     priv->sha384_support);
-
-	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
-	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
-
-	/* If we're making the supplicant take a time out for a bit, don't
-	 * let the supplicant interface start immediately, just let it hang
-	 * around in INIT state until we're ready to talk to the supplicant
-	 * again.
-	 */
-	if (is_available (self))
-		nm_supplicant_interface_set_supplicant_available (iface, TRUE);
+	if (supp_iface)
+		return g_object_ref (supp_iface);
+
+	supp_iface = nm_supplicant_interface_new (self,
+	                                          iface_path,
+	                                          0,
+	                                          NM_SUPPLICANT_DRIVER_UNKNOWN);
+
+	_supp_iface_add (self, iface_path, supp_iface);
 
-	return iface;
+	return supp_iface;
 }
 
+/*****************************************************************************/
+
 static void
-update_capabilities (NMSupplicantManager *self)
+_dbus_interface_removed_cb (GDBusConnection *connection,
+                            const char *sender_name,
+                            const char *object_path,
+                            const char *signal_interface_name,
+                            const char *signal_name,
+                            GVariant *parameters,
+                            gpointer user_data)
 {
+	NMSupplicantManager *self = user_data;
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	GSList *ifaces;
-	const char **array;
-	GVariant *value;
+	NMSupplicantInterface *supp_iface;
+	const char *iface_path_str;
+	nm_auto_ref_string NMRefString *iface_path = NULL;
+
+	nm_assert (nm_streq (sender_name, priv->name_owner->str));
+
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+		return;
+
+	g_variant_get (parameters, "(&o)", &iface_path_str);
+
+	iface_path = nm_ref_string_new (iface_path_str);
+
+	supp_iface = g_hash_table_lookup (priv->supp_ifaces, iface_path);
+	if (!supp_iface)
+		return;
+
+	_supp_iface_remove_one (self, supp_iface, FALSE, "InterfaceRemoved signal from wpa_supplicant");
+}
+
+/*****************************************************************************/
+
+static void
+_dbus_get_capabilities_cb (GVariant *res,
+                           GError *error,
+                           gpointer user_data)
+{
+	NMSupplicantManager *self;
+	NMSupplicantManagerPrivate *priv;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	self = user_data;
+	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->get_capabilities_cancellable);
 
 	/* The supplicant only advertises global capabilities if the following
 	 * commit has been applied:
@@ -316,269 +926,398 @@ update_capabilities (NMSupplicantManager *self)
 	 *
 	 * dbus: Add global capabilities property
 	 */
-	priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-	priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-	priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_AP,     NM_TERNARY_DEFAULT);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_PMF,    NM_TERNARY_DEFAULT);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_FILS,   NM_TERNARY_DEFAULT);
+
 	/* Support for the following is newer than the capabilities property */
-	priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
-	priv->ft_support = NM_SUPPLICANT_FEATURE_NO;
-	priv->sha384_support = NM_SUPPLICANT_FEATURE_NO;
-	priv->mesh_support = NM_SUPPLICANT_FEATURE_NO;
-
-	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
-	if (value) {
-		if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
-			array = g_variant_get_strv (value, NULL);
-			priv->ap_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->pmf_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->fils_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->ft_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->sha384_support = NM_SUPPLICANT_FEATURE_NO;
-			priv->mesh_support = NM_SUPPLICANT_FEATURE_NO;
-			if (array) {
-				if (g_strv_contains (array, "ap"))
-					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "pmf"))
-					priv->pmf_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "fils"))
-					priv->fils_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "p2p"))
-					priv->p2p_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "ft"))
-					priv->ft_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "sha384"))
-					priv->sha384_support = NM_SUPPLICANT_FEATURE_YES;
-				if (g_strv_contains (array, "mesh"))
-					priv->mesh_support = NM_SUPPLICANT_FEATURE_YES;
-				g_free (array);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_P2P,    NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_FT,     NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_MESH,   NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_FAST,   NM_TERNARY_FALSE);
+	_caps_set (priv, NM_SUPPL_CAP_TYPE_WFD,    NM_TERNARY_FALSE);
+
+	if (res) {
+		nm_auto_free_variant_iter GVariantIter *res_iter = NULL;
+		const char *res_key;
+		GVariant *res_val;
+
+		g_variant_get (res, "(a{sv})", &res_iter);
+		while (g_variant_iter_loop (res_iter, "{&sv}", &res_key, &res_val)) {
+			if (nm_streq (res_key, "Capabilities")) {
+				if (g_variant_is_of_type (res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
+					gs_free const char **array = NULL;
+					const char **a;
+
+					array = g_variant_get_strv (res_val, NULL);
+					_caps_set (priv, NM_SUPPL_CAP_TYPE_AP,   NM_TERNARY_FALSE);
+					_caps_set (priv, NM_SUPPL_CAP_TYPE_PMF,  NM_TERNARY_FALSE);
+					_caps_set (priv, NM_SUPPL_CAP_TYPE_FILS, NM_TERNARY_FALSE);
+					if (array) {
+						for (a = array; *a; a++) {
+							if (nm_streq (*a, "ap"))     { _caps_set (priv, NM_SUPPL_CAP_TYPE_AP,     NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "pmf"))    { _caps_set (priv, NM_SUPPL_CAP_TYPE_PMF,    NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "fils"))   { _caps_set (priv, NM_SUPPL_CAP_TYPE_FILS,   NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "p2p"))    { _caps_set (priv, NM_SUPPL_CAP_TYPE_P2P,    NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "ft"))     { _caps_set (priv, NM_SUPPL_CAP_TYPE_FT,     NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "sha384")) { _caps_set (priv, NM_SUPPL_CAP_TYPE_SHA384, NM_TERNARY_TRUE); continue; }
+							if (nm_streq (*a, "mesh"))   { _caps_set (priv, NM_SUPPL_CAP_TYPE_MESH,   NM_TERNARY_TRUE); continue; }
+						}
+					}
+				}
+				continue;
 			}
-		}
-		g_variant_unref (value);
-	}
-
-	/* Tell all interfaces about results of the AP/PMF/FILS/P2P/FT/SHA384 check */
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
-		nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support);
-		nm_supplicant_interface_set_pmf_support (ifaces->data, priv->pmf_support);
-		nm_supplicant_interface_set_fils_support (ifaces->data, priv->fils_support);
-		nm_supplicant_interface_set_p2p_support (ifaces->data, priv->p2p_support);
-		nm_supplicant_interface_set_ft_support (ifaces->data, priv->ft_support);
-		nm_supplicant_interface_set_sha384_support (ifaces->data, priv->sha384_support);
-		nm_supplicant_interface_set_mesh_support (ifaces->data, priv->mesh_support);
-	}
-
-	_LOGD ("AP mode is %ssupported",
-	       (priv->ap_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->ap_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("PMF is %ssupported",
-	       (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->pmf_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("FILS is %ssupported",
-	       (priv->fils_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->fils_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("P2P is %ssupported",
-	       (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->p2p_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("FT is %ssupported",
-	       (priv->ft_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->ft_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("SHA384 is %ssupported",
-	       (priv->sha384_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->sha384_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-	_LOGD ("Mesh is %ssupported",
-	       (priv->mesh_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->mesh_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
-
-	/* EAP-FAST */
-	priv->fast_support = NM_SUPPLICANT_FEATURE_NO;
-	value = g_dbus_proxy_get_cached_property (priv->proxy, "EapMethods");
-	if (value) {
-		if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
-			array = g_variant_get_strv (value, NULL);
-			if (array) {
-				const char **a;
-
-				for (a = array; *a; a++) {
-					if (g_ascii_strcasecmp (*a, "FAST") == 0) {
-						priv->fast_support = NM_SUPPLICANT_FEATURE_YES;
-						break;
+			if (nm_streq (res_key, "EapMethods")) {
+				if (g_variant_is_of_type (res_val, G_VARIANT_TYPE_STRING_ARRAY)) {
+					gs_free const char **array = NULL;
+					const char **a;
+
+					array = g_variant_get_strv (res_val, NULL);
+					if (array) {
+						for (a = array; *a; a++) {
+							if (g_ascii_strcasecmp (*a, "FAST") == 0) {
+								_caps_set (priv, NM_SUPPL_CAP_TYPE_FAST, NM_TERNARY_TRUE);
+								break;
+							}
+						}
 					}
 				}
-				g_free (array);
+				continue;
+			}
+			if (nm_streq (res_key, "WFDIEs")) {
+				_caps_set (priv, NM_SUPPL_CAP_TYPE_WFD, NM_TERNARY_TRUE);
+				continue;
 			}
 		}
-		g_variant_unref (value);
 	}
 
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
-		nm_supplicant_interface_set_fast_support (ifaces->data, priv->fast_support);
+	_LOGD ("supported features:"
+	       " AP%c"
+	       " PMF%c"
+	       " FILS%c"
+	       " P2P%c"
+	       " FT%c"
+	       " SHA384%c"
+	       " MESH%c"
+	       " FAST%c"
+	       " WFD%c"
+	       "",
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_AP),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_PMF),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_FILS),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_P2P),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_FT),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_SHA384),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_MESH),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_FAST),
+	       _caps_to_char (priv, NM_SUPPL_CAP_TYPE_WFD));
+
+	nm_assert (g_hash_table_size (priv->supp_ifaces) == 0);
+	nm_assert (c_list_is_empty (&priv->supp_lst_head));
+
+	_create_iface_proceed_all (self, NULL);
+}
 
-	_LOGD ("EAP-FAST is %ssupported",
-	       (priv->fast_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->fast_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+/*****************************************************************************/
 
-	priv->wfd_support = NM_SUPPLICANT_FEATURE_NO;
-	value = g_dbus_proxy_get_cached_property (priv->proxy, "WFDIEs");
-	if (value) {
-		priv->wfd_support = NM_SUPPLICANT_FEATURE_YES;
-		g_variant_unref (value);
-	}
+void
+_nm_supplicant_manager_unregister_interface (NMSupplicantManager *self,
+                                             NMSupplicantInterface *supp_iface)
+{
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
-		nm_supplicant_interface_set_wfd_support (ifaces->data, priv->fast_support);
+	nm_assert (NM_IS_SUPPLICANT_INTERFACE (supp_iface));
+	nm_assert (c_list_contains (&NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->supp_lst_head, &supp_iface->supp_lst));
 
-	_LOGD ("WFD is %ssupported",
-	       (priv->wfd_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
-	           (priv->wfd_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	c_list_unlink (&supp_iface->supp_lst);
+	if (!g_hash_table_remove (priv->supp_ifaces, nm_supplicant_interface_get_object_path (supp_iface)))
+		nm_assert_not_reached ();
 }
 
 static void
-availability_changed (NMSupplicantManager *self, gboolean available)
+_supp_iface_add (NMSupplicantManager *self,
+                 NMRefString *iface_path,
+                 NMSupplicantInterface *supp_iface)
 {
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	GSList *ifaces, *iter;
-
-	if (!priv->ifaces)
-		return;
 
-	/* setting the supplicant as unavailable might cause the caller to unref
-	 * the supplicant (and thus remove the instance from the list of interfaces.
-	 * Delay that by taking an additional reference first. */
-	ifaces = g_slist_copy (priv->ifaces);
-	for (iter = ifaces; iter; iter = iter->next)
-		g_object_ref (iter->data);
-	for (iter = ifaces; iter; iter = iter->next)
-		nm_supplicant_interface_set_supplicant_available (iter->data, available);
-	g_slist_free_full (ifaces, g_object_unref);
+	c_list_link_tail (&priv->supp_lst_head, &supp_iface->supp_lst);
+	if (!g_hash_table_insert (priv->supp_ifaces, iface_path, supp_iface))
+		nm_assert_not_reached ();
 }
 
 static void
-set_running (NMSupplicantManager *self, gboolean now_running)
+_supp_iface_remove_one (NMSupplicantManager *self,
+                        NMSupplicantInterface *supp_iface,
+                        gboolean force_remove_from_supplicant,
+                        const char *reason)
 {
-	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	gboolean old_available = is_available (self);
-	gboolean new_available;
+#if NM_MORE_ASSERTS
+	_nm_unused gs_unref_object NMSupplicantInterface *supp_iface_keep_alive = g_object_ref (supp_iface);
+#endif
+
+	nm_assert (NM_IS_SUPPLICANT_MANAGER (self));
+	nm_assert (NM_IS_SUPPLICANT_INTERFACE (supp_iface));
+	nm_assert (c_list_contains (&NM_SUPPLICANT_MANAGER_GET_PRIVATE (self)->supp_lst_head, &supp_iface->supp_lst));
+
+	_nm_supplicant_interface_set_state_down (supp_iface, force_remove_from_supplicant, reason);
 
-	priv->running = now_running;
-	new_available = is_available (self);
-	if (old_available != new_available)
-		availability_changed (self, new_available);
+	nm_assert (c_list_is_empty (&supp_iface->supp_lst));
 }
 
 static void
-set_die_count (NMSupplicantManager *self, guint new_die_count)
+_supp_iface_remove_all (NMSupplicantManager *self,
+                        gboolean force_remove_from_supplicant,
+                        const char *reason)
 {
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	gboolean old_available = is_available (self);
-	gboolean new_available;
+	NMSupplicantInterface *supp_iface;
 
-	priv->die_count = new_die_count;
-	new_available = is_available (self);
-	if (old_available != new_available)
-		availability_changed (self, new_available);
+	while ((supp_iface = c_list_first_entry (&priv->supp_lst_head, NMSupplicantInterface, supp_lst)))
+		_supp_iface_remove_one (self, supp_iface, force_remove_from_supplicant, reason);
 }
 
+/*****************************************************************************/
+
 static gboolean
-wpas_die_count_reset_cb (gpointer user_data)
+_available_reset_cb (gpointer user_data)
 {
-	NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data);
+	NMSupplicantManager *self = user_data;
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	/* Reset the die count back to zero, which allows use of the supplicant again */
-	priv->die_count_reset_id = 0;
-	set_die_count (self, 0);
-	_LOGI ("wpa_supplicant die count reset");
-	return FALSE;
+	priv->available_reset_id = 0;
+	nm_assert (priv->available == NM_TERNARY_FALSE);
+	priv->available = NM_TERNARY_DEFAULT;
+	g_signal_emit (self, signals[AVAILABLE_CHANGED], 0);
+	return G_SOURCE_REMOVE;
 }
 
+/*****************************************************************************/
+
 static void
-name_owner_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data)
+name_owner_changed (NMSupplicantManager *self,
+                    const char *name_owner,
+                    gboolean first_time)
 {
-	NMSupplicantManager *self = NM_SUPPLICANT_MANAGER (user_data);
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	char *owner;
-
-	g_return_if_fail (proxy == priv->proxy);
-
-	owner = g_dbus_proxy_get_name_owner (proxy);
-	_LOGI ("wpa_supplicant %s", owner ? "running" : "stopped");
-
-	if (owner) {
-		update_capabilities (self);
-		set_running (self, TRUE);
-	} else if (priv->running) {
-		/* Reschedule the die count reset timeout.  Every time the supplicant
-		 * dies we wait 10 seconds before resetting the counter.  If the
-		 * supplicant died more than twice before the timer is reset, then
-		 * we don't try to talk to the supplicant for a while.
-		 */
-		if (priv->die_count_reset_id)
-			g_source_remove (priv->die_count_reset_id);
-		priv->die_count_reset_id = g_timeout_add_seconds (10, wpas_die_count_reset_cb, self);
-		set_die_count (self, priv->die_count + 1);
-
-		if (die_count_exceeded (priv->die_count)) {
-			_LOGI ("wpa_supplicant die count %d; ignoring for 10 seconds",
-			       priv->die_count);
+	NMTernary available;
+	gboolean available_changed = FALSE;
+
+	nm_assert (!priv->get_name_owner_cancellable);
+	nm_assert (   !name_owner
+	           || name_owner[0]);
+	nm_assert (   (   first_time
+	               && !priv->name_owner)
+	           || (   !first_time
+	               && (!!priv->name_owner) != (!!name_owner)));
+
+	if (first_time) {
+		_LOGD ("wpa_supplicant name owner %s%s%s (%srunning)",
+		       NM_PRINT_FMT_QUOTE_STRING (name_owner),
+		       name_owner ? "" : "not ");
+	} else {
+		_LOGD ("wpa_supplicant name owner \"%s\" %s (%srunning)",
+		       name_owner ?: priv->name_owner->str,
+		       name_owner ? "disappeared" : "appeared",
+		       name_owner ? "" : "not ");
+	}
+
+	nm_ref_string_unref (priv->name_owner);
+	priv->name_owner = nm_ref_string_new (name_owner);
+
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->interface_removed_id);
+
+	if (name_owner) {
+		if (nm_clear_g_source (&priv->poke_name_owner_timeout_id))
+			_LOGT ("poke service \"%s\" completed with name owner change", NM_WPAS_DBUS_SERVICE);
+		nm_clear_g_cancellable (&priv->poke_name_owner_cancellable);
+	}
+
+	nm_clear_g_cancellable (&priv->get_capabilities_cancellable);
+
+	priv->capabilities = NM_SUPPL_CAP_MASK_NONE;
+	if (priv->name_owner) {
+		priv->get_capabilities_cancellable = g_cancellable_new ();
+		nm_dbus_connection_call_get_all (priv->dbus_connection,
+		                                 priv->name_owner->str,
+		                                 NM_WPAS_DBUS_PATH,
+		                                 NM_WPAS_DBUS_INTERFACE,
+		                                 5000,
+		                                 priv->get_capabilities_cancellable,
+		                                 _dbus_get_capabilities_cb,
+		                                 self);
+		priv->interface_removed_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+		                                                                 priv->name_owner->str,
+		                                                                 NM_WPAS_DBUS_INTERFACE,
+		                                                                 "InterfaceRemoved",
+		                                                                 NULL,
+		                                                                 NULL,
+		                                                                 G_DBUS_SIGNAL_FLAGS_NONE,
+		                                                                 _dbus_interface_removed_cb,
+		                                                                 self,
+		                                                                 NULL);
+	}
+
+	/* if supplicant is running (has a name owner), we may use it.
+	 * If this is the first time, and supplicant is not running, we
+	 * may also use it (and assume that we probably could D-Bus activate
+	 * it).
+	 *
+	 * Otherwise, somebody else stopped supplicant. It's no longer useable to
+	 * us and we block auto starting it. The user has to start the service...
+	 *
+	 * Actually, below we reset the hard block after a short timeout. This
+	 * causes the caller to notify that supplicant may now by around and
+	 * retry to D-Bus activate it. */
+	if (priv->name_owner)
+		available = NM_TERNARY_TRUE;
+	else if (first_time)
+		available = NM_TERNARY_DEFAULT;
+	else
+		available = NM_TERNARY_FALSE;
+
+	if (priv->available != available) {
+		priv->available = available;
+		_LOGD ("supplicant is now %savailable",
+		         available == FALSE
+		       ? "not "
+		       : (  available == TRUE
+		          ? ""
+		          : "maybe "));
+		available_changed = TRUE;
+
+		nm_clear_g_source (&priv->available_reset_id);
+		if (available == NM_TERNARY_FALSE) {
+			/* reset the availability from a hard "no" to a "maybe" in a bit. */
+			priv->available_reset_id = g_timeout_add_seconds (60,
+			                                                  _available_reset_cb,
+			                                                  self);
 		}
+	}
+
+	_supp_iface_remove_all (self, TRUE, "name-owner changed");
 
-		priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->fast_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->ft_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-		priv->sha384_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+	if (!priv->name_owner) {
+		if (priv->poke_name_owner_timeout_id) {
+			/* we are still poking for the service to start. Don't cancel
+			 * the pending create requests just yet. */
+		} else {
+			gs_free_error GError *local_error = NULL;
 
-		set_running (self, FALSE);
+			/* When we loose the name owner, we fail all pending creation requests. */
+			nm_utils_error_set (&local_error,
+			                    NM_UTILS_ERROR_UNKNOWN,
+			                    "Name owner lost");
+			_create_iface_proceed_all (self, local_error);
+		}
+	} else {
+		/* We got a name-owner, but we don't do anything. Instead let
+		 * _dbus_get_capabilities_cb() complete and kick of the create-iface
+		 * handles.
+		 *
+		 * Note that before the first name-owner change, all create-iface
+		 * requests fail right away. So we don't have to handle them here
+		 * (by starting to poke the service). */
 	}
 
-	g_free (owner);
+	if (available_changed)
+		g_signal_emit (self, signals[AVAILABLE_CHANGED], 0);
 }
 
 static void
-on_proxy_acquired (GObject *object, GAsyncResult *result, gpointer user_data)
+name_owner_changed_cb (GDBusConnection *connection,
+                       const char *sender_name,
+                       const char *object_path,
+                       const char *interface_name,
+                       const char *signal_name,
+                       GVariant *parameters,
+                       gpointer user_data)
 {
-	NMSupplicantManager *self;
-	NMSupplicantManagerPrivate *priv;
-	GError *error = NULL;
-	GDBusProxy *proxy;
+	gs_unref_object NMSupplicantManager *self = g_object_ref (user_data);
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+	const char *name_owner;
 
-	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (!proxy) {
-		_LOGW ("failed to acquire wpa_supplicant proxy: Wi-Fi and 802.1x will not be available (%s)",
-		       error->message);
-		g_clear_error (&error);
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
 		return;
+
+	if (priv->get_name_owner_cancellable)
+		return;
+
+	g_variant_get (parameters,
+	               "(&s&s&s)",
+	               NULL,
+	               NULL,
+	               &name_owner);
+
+	name_owner = nm_str_not_empty (name_owner);
+
+	if (nm_streq0 (name_owner, nm_ref_string_get_str (priv->name_owner)))
+		return;
+
+	if (   name_owner
+	    && priv->name_owner) {
+		/* odd, we directly switch from one name owner to the next. Can't allow that.
+		 * First clear the name owner before resetting. */
+		name_owner_changed (self, NULL, FALSE);
 	}
+	name_owner_changed (user_data, name_owner, FALSE);
+}
 
-	self = NM_SUPPLICANT_MANAGER (user_data);
+static void
+get_name_owner_cb (const char *name_owner,
+                   GError *error,
+                   gpointer user_data)
+{
+	NMSupplicantManager *self = user_data;
+	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
+
+	if (   !name_owner
+	    && nm_utils_error_is_cancelled (error))
+		return;
+
+	self = user_data;
 	priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	priv->proxy = proxy;
-	g_signal_connect (priv->proxy, "notify::g-name-owner", G_CALLBACK (name_owner_cb), self);
-	name_owner_cb (priv->proxy, NULL, self);
+	g_clear_object (&priv->get_name_owner_cancellable);
+
+	name_owner_changed (self, nm_str_not_empty (name_owner), TRUE);
 }
 
 /*****************************************************************************/
 
-NM_DEFINE_SINGLETON_GETTER (NMSupplicantManager, nm_supplicant_manager_get, NM_TYPE_SUPPLICANT_MANAGER);
-
 static void
 nm_supplicant_manager_init (NMSupplicantManager *self)
 {
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
 
-	priv->cancellable = g_cancellable_new ();
-	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-	                          G_DBUS_PROXY_FLAGS_NONE,
-	                          NULL,
-	                          WPAS_DBUS_SERVICE,
-	                          WPAS_DBUS_PATH,
-	                          WPAS_DBUS_INTERFACE,
-	                          priv->cancellable,
-	                          (GAsyncReadyCallback) on_proxy_acquired,
-	                          self);
+	nm_assert (priv->capabilities == NM_SUPPL_CAP_MASK_NONE);
+	nm_assert (priv->available == NM_TERNARY_FALSE);
+
+	priv->supp_ifaces = g_hash_table_new (nm_direct_hash, NULL);
+	c_list_init (&priv->supp_lst_head);
+	c_list_init (&priv->create_iface_lst_head);
+
+	priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
+
+	if (!priv->dbus_connection) {
+		_LOGI ("no D-Bus connection to talk to wpa_supplicant");
+		return;
+	}
+
+	priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+	                                                                                      NM_WPAS_DBUS_SERVICE,
+	                                                                                      name_owner_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+	priv->get_name_owner_cancellable = g_cancellable_new ();
+	nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+	                                        NM_WPAS_DBUS_SERVICE,
+	                                        -1,
+	                                        priv->get_name_owner_cancellable,
+	                                        get_name_owner_cb,
+	                                        self);
 }
 
 static void
@@ -586,22 +1325,32 @@ dispose (GObject *object)
 {
 	NMSupplicantManager *self = (NMSupplicantManager *) object;
 	NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self);
-	GSList *ifaces;
 
-	nm_clear_g_source (&priv->die_count_reset_id);
+	_supp_iface_remove_all (self, TRUE, "NMSupplicantManager is disposing");
 
-	nm_clear_g_cancellable (&priv->cancellable);
+	nm_assert (c_list_is_empty (&priv->create_iface_lst_head));
 
-	if (priv->ifaces) {
-		for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
-			g_object_remove_toggle_ref (ifaces->data, _sup_iface_last_ref, self);
-		g_slist_free (priv->ifaces);
-		priv->ifaces = NULL;
-	}
+	nm_clear_g_source (&priv->available_reset_id);
+
+	priv->available = NM_TERNARY_FALSE;
+	nm_clear_pointer (&priv->name_owner, nm_ref_string_unref);
+
+	nm_clear_g_source (&priv->poke_name_owner_timeout_id);
+	nm_clear_g_cancellable (&priv->poke_name_owner_cancellable);
 
-	g_clear_object (&priv->proxy);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->interface_removed_id);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->name_owner_changed_id);
+
+	nm_clear_g_cancellable (&priv->get_name_owner_cancellable);
+	nm_clear_g_cancellable (&priv->get_capabilities_cancellable);
 
 	G_OBJECT_CLASS (nm_supplicant_manager_parent_class)->dispose (object);
+
+	g_clear_object (&priv->dbus_connection);
+
+	nm_clear_pointer (&priv->supp_ifaces, g_hash_table_destroy);
 }
 
 static void
@@ -610,5 +1359,11 @@ nm_supplicant_manager_class_init (NMSupplicantManagerClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->dispose = dispose;
-}
 
+	signals[AVAILABLE_CHANGED] =
+	    g_signal_new (NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 0);
+}
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h
index 18ca53b6..b2b814aa 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -17,19 +17,49 @@
 #define NM_IS_SUPPLICANT_MANAGER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_SUPPLICANT_MANAGER))
 #define NM_SUPPLICANT_MANAGER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_SUPPLICANT_MANAGER, NMSupplicantManagerClass))
 
+#define NM_SUPPLICANT_MANAGER_AVAILABLE_CHANGED "available-changed"
+
 typedef struct _NMSupplicantManagerClass NMSupplicantManagerClass;
 
 GType nm_supplicant_manager_get_type (void);
 
 NMSupplicantManager *nm_supplicant_manager_get (void);
 
+NMTernary nm_supplicant_manager_is_available (NMSupplicantManager *self);
+
+GDBusConnection *nm_supplicant_manager_get_dbus_connection (NMSupplicantManager *self);
+NMRefString *nm_supplicant_manager_get_dbus_name_owner (NMSupplicantManager *self);
+NMSupplCapMask nm_supplicant_manager_get_global_capabilities (NMSupplicantManager *self);
+
 void nm_supplicant_manager_set_wfd_ies (NMSupplicantManager *self,
                                         GBytes *wfd_ies);
 
-NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr,
-                                                               const char *ifname,
-                                                               NMSupplicantDriver driver);
+typedef struct _NMSupplMgrCreateIfaceHandle NMSupplMgrCreateIfaceHandle;
+
+typedef void (*NMSupplicantManagerCreateInterfaceCb) (NMSupplicantManager *self,
+                                                      NMSupplMgrCreateIfaceHandle *handle,
+                                                      NMSupplicantInterface *iface,
+                                                      GError *error,
+                                                      gpointer user_data);
+
+NMSupplMgrCreateIfaceHandle *nm_supplicant_manager_create_interface (NMSupplicantManager *self,
+                                                                     int ifindex,
+                                                                     NMSupplicantDriver driver,
+                                                                     NMSupplicantManagerCreateInterfaceCb callback,
+                                                                     gpointer user_data);
+
+void nm_supplicant_manager_create_interface_cancel (NMSupplMgrCreateIfaceHandle *handle);
+
 NMSupplicantInterface *nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
                                                                          const char *object_path);
 
+/*****************************************************************************/
+
+void _nm_supplicant_manager_unregister_interface (NMSupplicantManager *self,
+                                                  NMSupplicantInterface *supp_iface);
+
+void _nm_supplicant_manager_dbus_call_remove_interface (NMSupplicantManager *self,
+                                                        const char *name_owner,
+                                                        const char *iface_path);
+
 #endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index bea17ede..58dbd73a 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -56,7 +56,7 @@ static const char *const key_mgmt_allowed[] = { "WPA-PSK", "WPA-PSK-SHA256", "FT
                                                 "WPA-EAP", "WPA-EAP-SHA256", "FT-EAP", "FT-EAP-SHA384",
                                                 "FILS-SHA256", "FILS-SHA384",
                                                 "IEEE8021X", "SAE", "FT-SAE",
-                                                "NONE", NULL };
+                                                "OWE", "NONE", NULL };
 static const char *const auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
 static const char *const eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
                                                 "PSK", "FAST", "PWD", NULL };
@@ -99,6 +99,7 @@ static const struct Opt opt_table[] = {
 	{ "subject_match",      TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "altsubject_match",   TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "domain_suffix_match",TYPE_BYTES,   0, 0, FALSE,  NULL },
+	{ "domain_match",       TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "ca_cert",            TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "client_cert",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key",        TYPE_BYTES,   0, 65536, FALSE,  NULL },
@@ -110,6 +111,7 @@ static const struct Opt opt_table[] = {
 	{ "subject_match2",     TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "altsubject_match2",  TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "domain_suffix_match2", TYPE_BYTES, 0, 0, FALSE,  NULL },
+	{ "domain_match2",      TYPE_BYTES,   0, 0, FALSE,  NULL },
 	{ "ca_cert2",           TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "client_cert2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
 	{ "private_key2",       TYPE_BYTES,   0, 65536, FALSE,  NULL },
diff --git a/src/supplicant/nm-supplicant-types.h b/src/supplicant/nm-supplicant-types.h
index 2b356354..734ff156 100644
--- a/src/supplicant/nm-supplicant-types.h
+++ b/src/supplicant/nm-supplicant-types.h
@@ -6,19 +6,116 @@
 #ifndef __NETWORKMANAGER_SUPPLICANT_TYPES_H__
 #define __NETWORKMANAGER_SUPPLICANT_TYPES_H__
 
-#define WPAS_DBUS_SERVICE       "fi.w1.wpa_supplicant1"
-#define WPAS_DBUS_PATH          "/fi/w1/wpa_supplicant1"
-#define WPAS_DBUS_INTERFACE     "fi.w1.wpa_supplicant1"
+#include "c-list/src/c-list.h"
+
+#define NM_WPAS_DBUS_SERVICE       "fi.w1.wpa_supplicant1"
+#define NM_WPAS_DBUS_PATH          "/fi/w1/wpa_supplicant1"
+#define NM_WPAS_DBUS_INTERFACE     "fi.w1.wpa_supplicant1"
+
+#if HAVE_WEXT
+#define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211,wext"
+#else
+#define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211"
+#endif
+
+#define NM_WPAS_DBUS_IFACE_INTERFACE             NM_WPAS_DBUS_INTERFACE ".Interface"
+#define NM_WPAS_DBUS_IFACE_INTERFACE_WPS         NM_WPAS_DBUS_INTERFACE ".Interface.WPS"
+#define NM_WPAS_DBUS_IFACE_INTERFACE_P2P_DEVICE  NM_WPAS_DBUS_INTERFACE ".Interface.P2PDevice"
+#define NM_WPAS_DBUS_IFACE_BSS                   NM_WPAS_DBUS_INTERFACE ".BSS"
+#define NM_WPAS_DBUS_IFACE_PEER                  NM_WPAS_DBUS_INTERFACE ".Peer"
+#define NM_WPAS_DBUS_IFACE_GROUP                 NM_WPAS_DBUS_INTERFACE ".Group"
+#define NM_WPAS_DBUS_IFACE_NETWORK               NM_WPAS_DBUS_INTERFACE ".Network"
+#define NM_WPAS_ERROR_INVALID_IFACE              NM_WPAS_DBUS_INTERFACE ".InvalidInterface"
+#define NM_WPAS_ERROR_EXISTS_ERROR               NM_WPAS_DBUS_INTERFACE ".InterfaceExists"
+#define NM_WPAS_ERROR_UNKNOWN_IFACE              NM_WPAS_DBUS_INTERFACE ".InterfaceUnknown"
 
 typedef struct _NMSupplicantManager NMSupplicantManager;
 typedef struct _NMSupplicantInterface NMSupplicantInterface;
 typedef struct _NMSupplicantConfig NMSupplicantConfig;
 
+/*****************************************************************************/
+
 typedef enum {
-	NM_SUPPLICANT_FEATURE_UNKNOWN = 0,  /* Can't detect whether supported or not */
-	NM_SUPPLICANT_FEATURE_NO = 1,       /* Feature definitely not supported */
-	NM_SUPPLICANT_FEATURE_YES = 2,      /* Feature definitely supported */
-} NMSupplicantFeature;
+	NM_SUPPL_CAP_TYPE_AP,
+	NM_SUPPL_CAP_TYPE_PMF,
+	NM_SUPPL_CAP_TYPE_FILS,
+	NM_SUPPL_CAP_TYPE_P2P,
+	NM_SUPPL_CAP_TYPE_FT,
+	NM_SUPPL_CAP_TYPE_SHA384,
+	NM_SUPPL_CAP_TYPE_MESH,
+	NM_SUPPL_CAP_TYPE_FAST,
+	NM_SUPPL_CAP_TYPE_WFD,
+	_NM_SUPPL_CAP_TYPE_NUM,
+} NMSupplCapType;
+
+#define NM_SUPPL_CAP_MASK_NO(type)   ((NMSupplCapMask) (1llu << ((type) * 2u)))
+#define NM_SUPPL_CAP_MASK_YES(type)  ((NMSupplCapMask) (2llu << ((type) * 2u)))
+#define NM_SUPPL_CAP_MASK_MASK(type) ((NMSupplCapMask) (3llu << ((type) * 2u)))
+
+typedef enum {
+	NM_SUPPL_CAP_MASK_NONE = 0,
+	NM_SUPPL_CAP_MASK_ALL = ((1llu << (_NM_SUPPL_CAP_TYPE_NUM * 2)) - 1),
+
+/* usually it's bad to use macros to define enum values (because you cannot find them with ctags/cscope
+ * anymore. In this case, still do it because the alternative is ugly too. */
+#define _NM_SUPPL_CAP_MASK_DEFINE(type) \
+	NM_SUPPL_CAP_MASK_T_##type##_NO   =   (1llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u)), \
+	NM_SUPPL_CAP_MASK_T_##type##_YES  =   (2llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u)), \
+	NM_SUPPL_CAP_MASK_T_##type##_MASK =   (3llu << ((NM_SUPPL_CAP_TYPE_##type) * 2u))
+	_NM_SUPPL_CAP_MASK_DEFINE (AP),
+	_NM_SUPPL_CAP_MASK_DEFINE (FAST),
+	_NM_SUPPL_CAP_MASK_DEFINE (PMF),
+	_NM_SUPPL_CAP_MASK_DEFINE (FILS),
+	_NM_SUPPL_CAP_MASK_DEFINE (P2P),
+	_NM_SUPPL_CAP_MASK_DEFINE (MESH),
+	_NM_SUPPL_CAP_MASK_DEFINE (WFD),
+	_NM_SUPPL_CAP_MASK_DEFINE (FT),
+	_NM_SUPPL_CAP_MASK_DEFINE (SHA384),
+#undef _NM_SUPPL_CAP_MASK_DEFINE
+} NMSupplCapMask;
+
+static inline NMSupplCapMask
+NM_SUPPL_CAP_MASK_SET (NMSupplCapMask features, NMSupplCapType type, NMTernary value)
+{
+	nm_assert (_NM_INT_NOT_NEGATIVE (type));
+	nm_assert (type < _NM_SUPPL_CAP_TYPE_NUM);
+	nm_assert (NM_IN_SET (value, NM_TERNARY_DEFAULT,
+	                             NM_TERNARY_TRUE,
+	                             NM_TERNARY_FALSE));
+	nm_assert (!(features & ~NM_SUPPL_CAP_MASK_ALL));
+
+	features &= ~NM_SUPPL_CAP_MASK_MASK (type);
+	switch (value) {
+	case NM_TERNARY_FALSE:
+		features |= NM_SUPPL_CAP_MASK_NO (type);
+		break;
+	case NM_TERNARY_TRUE:
+		features |= NM_SUPPL_CAP_MASK_YES (type);
+		break;
+	case NM_TERNARY_DEFAULT:
+		break;
+	}
+
+	return features;
+}
+
+static inline NMTernary
+NM_SUPPL_CAP_MASK_GET (NMSupplCapMask features, NMSupplCapType type)
+{
+	int f;
+
+	nm_assert (_NM_INT_NOT_NEGATIVE (type));
+	nm_assert (type < _NM_SUPPL_CAP_TYPE_NUM);
+	nm_assert (!(features & ~NM_SUPPL_CAP_MASK_ALL));
+
+	f = ((int) (features >> (2 * (int) type))) & 0x3;
+
+	nm_assert (NM_IN_SET (f, 0, 1, 2));
+
+	return (NMTernary) (f - 1);
+}
+
+/*****************************************************************************/
 
 /**
  * NMSupplicantError:
@@ -32,12 +129,76 @@ typedef enum {
 } NMSupplicantError;
 
 typedef enum {
+	NM_SUPPLICANT_DRIVER_UNKNOWN,
 	NM_SUPPLICANT_DRIVER_WIRELESS,
 	NM_SUPPLICANT_DRIVER_WIRED,
 	NM_SUPPLICANT_DRIVER_MACSEC,
 } NMSupplicantDriver;
 
+const char *nm_supplicant_driver_to_string (NMSupplicantDriver driver);
+
 #define NM_SUPPLICANT_ERROR (nm_supplicant_error_quark ())
 GQuark nm_supplicant_error_quark (void);
 
+typedef struct _NMSupplicantBssInfo {
+	NMRefString *bss_path;
+
+	NMSupplicantInterface *_self;
+	CList _bss_lst;
+	GCancellable *_init_cancellable;
+
+	GBytes *ssid;
+
+	gint64 last_seen_msec;
+
+	NM80211ApSecurityFlags wpa_flags; /* WPA-related flags */
+	NM80211ApSecurityFlags rsn_flags; /* RSN (WPA2) -related flags */
+
+	guint32 frequency;
+
+	guint32 max_rate;
+
+	guint8 signal_percent;
+
+	guint8 bssid[6 /* ETH_ALEN */];
+
+	NM80211ApFlags ap_flags:5;
+
+	NM80211Mode mode:4;
+
+	bool bssid_valid:1;
+
+	bool metered:1;
+
+	bool _bss_dirty:1;
+
+} NMSupplicantBssInfo;
+
+typedef struct _NMSupplicantPeerInfo{
+	NMRefString *peer_path;
+
+	CList _peer_lst;
+	NMSupplicantInterface *_self;
+	GCancellable *_init_cancellable;
+
+	char *device_name;
+	char *manufacturer;
+	char *model;
+	char *model_number;
+	char *serial;
+
+	GBytes *ies;
+
+	gint64 last_seen_msec;
+
+	guint8 address[6 /* ETH_ALEN */];
+
+	gint8 signal_percent;
+
+	bool address_valid:1;
+
+	bool _peer_dirty:1;
+
+} NMSupplicantPeerInfo;
+
 #endif  /* NM_SUPPLICANT_TYPES_H */
diff --git a/src/supplicant/tests/meson.build b/src/supplicant/tests/meson.build
index 207d22f8..63012d10 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/supplicant/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_unit = 'test-supplicant-config'
 
 exe = executable(
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index 008735b4..f9c71f12 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -86,8 +86,7 @@ static GVariant *
 build_supplicant_config (NMConnection *connection,
                          guint mtu,
                          guint fixed_freq,
-                         gboolean support_pmf,
-                         gboolean support_fils)
+                         NMSupplCapMask capabilities)
 {
 	gs_unref_object NMSupplicantConfig *config = NULL;
 	gs_free_error GError *error = NULL;
@@ -96,7 +95,7 @@ build_supplicant_config (NMConnection *connection,
 	NMSetting8021x *s_8021x;
 	gboolean success;
 
-	config = nm_supplicant_config_new (support_pmf, support_fils, FALSE, FALSE);
+	config = nm_supplicant_config_new (capabilities);
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
@@ -195,7 +194,7 @@ test_wifi_open (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'bssid' value '11:22:33:44:55:66'*");
 	NMTST_EXPECT_NM_INFO ("Config: added 'freq_list' value *");
 	NMTST_EXPECT_NM_INFO ("Config: added 'key_mgmt' value 'NONE'");
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -252,7 +251,7 @@ test_wifi_wep_key (const char *detail,
 	if (!test_bssid)
 		NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-70:86400'*");
 
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -352,7 +351,7 @@ test_wifi_wpa_psk (const char *detail,
 	default:
 		break;
 	}
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
@@ -416,7 +415,7 @@ test_wifi_sae_psk (const char *psk)
 	NMTST_EXPECT_NM_INFO ("Config: added 'proto' value 'RSN'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'pairwise' value 'TKIP CCMP'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'group' value 'TKIP CCMP'");
-	config_dict = build_supplicant_config (connection, 1500, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, 1500, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
@@ -524,7 +523,7 @@ test_wifi_eap_locked_bssid (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'ca_cert' value '*/test-ca-cert.pem'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
-	config_dict = build_supplicant_config (connection, mtu, 0, FALSE, FALSE);
+	config_dict = build_supplicant_config (connection, mtu, 0, NM_SUPPL_CAP_MASK_NONE);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -565,7 +564,7 @@ test_wifi_eap_unlocked_bssid (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-65:300'");
-	config_dict = build_supplicant_config (connection, mtu, 0, FALSE, TRUE);
+	config_dict = build_supplicant_config (connection, mtu, 0, NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -606,7 +605,7 @@ test_wifi_eap_fils_disabled (void)
 	NMTST_EXPECT_NM_INFO ("Config: added 'private_key' value '*/test-cert.p12'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'proactive_key_caching' value '1'");
 	NMTST_EXPECT_NM_INFO ("Config: added 'bgscan' value 'simple:30:-65:300'");
-	config_dict = build_supplicant_config (connection, mtu, 0, TRUE, TRUE);
+	config_dict = build_supplicant_config (connection, mtu, 0, NM_SUPPL_CAP_MASK_T_PMF_YES | NM_SUPPL_CAP_MASK_T_FILS_YES);
 	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
@@ -621,6 +620,44 @@ test_wifi_eap_fils_disabled (void)
 	validate_opt ("wifi-eap", config_dict, "bgscan", TYPE_BYTES, bgscan);
 }
 
+/*****************************************************************************/
+
+static void
+test_suppl_cap_mask (void)
+{
+	NMSupplCapType type;
+
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_AP_NO,  NM_SUPPL_CAP_TYPE_AP), ==, NM_TERNARY_FALSE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_AP_YES, NM_SUPPL_CAP_TYPE_AP), ==, NM_TERNARY_TRUE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_NONE,     NM_SUPPL_CAP_TYPE_AP), ==, NM_TERNARY_DEFAULT);
+
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_FILS_NO,  NM_SUPPL_CAP_TYPE_FILS), ==, NM_TERNARY_FALSE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_T_FILS_YES, NM_SUPPL_CAP_TYPE_FILS), ==, NM_TERNARY_TRUE);
+	g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (NM_SUPPL_CAP_MASK_NONE,       NM_SUPPL_CAP_TYPE_FILS), ==, NM_TERNARY_DEFAULT);
+
+	for (type = 0; type < _NM_SUPPL_CAP_TYPE_NUM; type++) {
+		NMTernary value;
+		NMSupplCapMask feature;
+		NMSupplCapMask feature2;
+
+		feature =   nmtst_get_rand_bool ()
+		          ? 0u
+		          : nmtst_get_rand_uint64 ();
+		feature &= NM_SUPPL_CAP_MASK_ALL;
+
+		value = nmtst_rand_select (NM_TERNARY_DEFAULT,
+		                           NM_TERNARY_FALSE,
+		                           NM_TERNARY_TRUE);
+
+		feature2 = NM_SUPPL_CAP_MASK_SET (feature, type, value);
+
+		g_assert_cmpint (NM_SUPPL_CAP_MASK_GET (feature2, type), ==, value);
+		g_assert_cmpint (feature & ~NM_SUPPL_CAP_MASK_MASK (type), ==, feature2 & ~NM_SUPPL_CAP_MASK_MASK (type));
+	}
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int main (int argc, char **argv)
@@ -634,6 +671,7 @@ int main (int argc, char **argv)
 	g_test_add_func ("/supplicant-config/wifi-eap/unlocked-bssid", test_wifi_eap_unlocked_bssid);
 	g_test_add_func ("/supplicant-config/wifi-eap/fils-disabled", test_wifi_eap_fils_disabled);
 	g_test_add_func ("/supplicant-config/wifi-sae", test_wifi_sae);
+	g_test_add_func ("/supplicant-config/test_suppl_cap_mask", test_suppl_cap_mask);
 
 	return g_test_run ();
 }
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
index 5c4fa3e2..9b87d772 100644
--- a/src/systemd/meson.build
+++ b/src/systemd/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 sources = files(
   'src/libsystemd-network/arp-util.c',
   'src/libsystemd-network/dhcp-identifier.c',
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index a2d0f8bd..5cbebb4a 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -61,6 +61,12 @@ struct sd_dhcp_lease {
         struct in_addr *sip;
         size_t sip_size;
 
+        struct in_addr *pop3_server;
+        size_t pop3_server_size;
+
+        struct in_addr *smtp_server;
+        size_t smtp_server_size;
+
         struct sd_dhcp_route *static_route;
         size_t static_route_size, static_route_allocated;
 
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index 7d22e754..94a6236a 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -10,6 +10,7 @@
 #include "sd-ndisc.h"
 
 #include "alloc-util.h"
+#include "arphrd-list.h"
 #include "condition.h"
 #include "conf-parser.h"
 #include "device-util.h"
@@ -106,6 +107,18 @@ static bool net_condition_test_strv(char * const *patterns, const char *string)
         return has_positive_rule ? match : true;
 }
 
+static bool net_condition_test_ifname(char * const *patterns, const char *ifname, char * const *alternative_names) {
+        if (net_condition_test_strv(patterns, ifname))
+                return true;
+
+        char * const *p;
+        STRV_FOREACH(p, alternative_names)
+                if (net_condition_test_strv(patterns, *p))
+                        return true;
+
+        return false;
+}
+
 static int net_condition_test_property(char * const *match_property, sd_device *device) {
         char * const *p;
 
@@ -157,7 +170,29 @@ static const char *const wifi_iftype_table[NL80211_IFTYPE_MAX+1] = {
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(wifi_iftype, enum nl80211_iftype);
 
+char *link_get_type_string(unsigned short iftype, sd_device *device) {
+        const char *t, *devtype;
+        char *p;
+
+        if (device &&
+            sd_device_get_devtype(device, &devtype) >= 0 &&
+            !isempty(devtype))
+                return strdup(devtype);
+
+        t = arphrd_to_name(iftype);
+        if (!t)
+                return NULL;
+
+        p = strdup(t);
+        if (!p)
+                return NULL;
+
+        ascii_strlower(p);
+        return p;
+}
+
 bool net_match_config(Set *match_mac,
+                      Set *match_permanent_mac,
                       char * const *match_paths,
                       char * const *match_drivers,
                       char * const *match_types,
@@ -166,20 +201,24 @@ bool net_match_config(Set *match_mac,
                       char * const *match_wifi_iftype,
                       char * const *match_ssid,
                       Set *match_bssid,
+                      unsigned short iftype,
                       sd_device *device,
                       const struct ether_addr *dev_mac,
+                      const struct ether_addr *dev_permanent_mac,
                       const char *dev_name,
+                      char * const *alternative_names,
                       enum nl80211_iftype wifi_iftype,
                       const char *ssid,
                       const struct ether_addr *bssid) {
 
-        const char *dev_path = NULL, *dev_driver = NULL, *dev_type = NULL, *mac_str;
+        const char *dev_path = NULL, *dev_driver = NULL, *mac_str;
+        _cleanup_free_ char *dev_type;
+
+        dev_type = link_get_type_string(iftype, device);
 
         if (device) {
                 (void) sd_device_get_property_value(device, "ID_PATH", &dev_path);
                 (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &dev_driver);
-                (void) sd_device_get_devtype(device, &dev_type);
-
                 if (!dev_name)
                         (void) sd_device_get_sysname(device, &dev_name);
                 if (!dev_mac &&
@@ -190,6 +229,12 @@ bool net_match_config(Set *match_mac,
         if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
                 return false;
 
+        if (match_permanent_mac &&
+            (!dev_permanent_mac ||
+             ether_addr_is_null(dev_permanent_mac) ||
+             !set_contains(match_permanent_mac, dev_permanent_mac)))
+                return false;
+
         if (!net_condition_test_strv(match_paths, dev_path))
                 return false;
 
@@ -199,7 +244,7 @@ bool net_match_config(Set *match_mac,
         if (!net_condition_test_strv(match_types, dev_type))
                 return false;
 
-        if (!net_condition_test_strv(match_names, dev_name))
+        if (!net_condition_test_ifname(match_names, dev_name, alternative_names))
                 return false;
 
         if (!net_condition_test_property(match_property, device))
@@ -352,7 +397,7 @@ int config_parse_match_ifnames(
                         return 0;
                 }
 
-                if (!ifname_valid(word)) {
+                if (!ifname_valid_full(word, ltype)) {
                         log_syntax(unit, LOG_ERR, filename, line, 0,
                                    "Interface name is not valid or too long, ignoring assignment: %s", word);
                         continue;
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index 2eb0cba5..6724cbbf 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -8,7 +8,6 @@
 #include "sd-dhcp-lease.h"
 
 #include "conf-parser.h"
-#include "def.h"
 #include "set.h"
 #include "strv.h"
 
@@ -17,6 +16,7 @@
 
 #if 0 /* NM_IGNORED */
 bool net_match_config(Set *match_mac,
+                      Set *match_permanent_mac,
                       char * const *match_path,
                       char * const *match_driver,
                       char * const *match_type,
@@ -25,9 +25,12 @@ bool net_match_config(Set *match_mac,
                       char * const *match_wifi_iftype,
                       char * const *match_ssid,
                       Set *match_bssid,
+                      unsigned short iftype,
                       sd_device *device,
                       const struct ether_addr *dev_mac,
+                      const struct ether_addr *dev_permanent_mac,
                       const char *dev_name,
+                      char * const *alternative_names,
                       enum nl80211_iftype wifi_iftype,
                       const char *ssid,
                       const struct ether_addr *bssid);
@@ -63,5 +66,3 @@ int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t
 
 /* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */
 int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size);
-
-#define NETWORK_DIRS ((const char* const*) CONF_PATHS_STRV("systemd/network"))
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 0266161d..63321cca 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -29,6 +29,7 @@
 #include "random-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "web-util.h"
 
 #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN)  /* Arbitrary limit */
 #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN)
@@ -85,13 +86,15 @@ struct sd_dhcp_client {
         size_t client_id_len;
         char *hostname;
         char *vendor_class_identifier;
+        char *mudurl;
         char **user_class;
         uint32_t mtu;
         uint32_t xid;
         usec_t start_time;
         uint64_t attempt;
         uint64_t max_attempts;
-        OrderedHashmap *options;
+        OrderedHashmap *extra_options;
+        OrderedHashmap *vendor_options;
         usec_t request_sent;
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
@@ -497,6 +500,18 @@ int sd_dhcp_client_set_vendor_class_identifier(
         return free_and_strdup(&client->vendor_class_identifier, vci);
 }
 
+int sd_dhcp_client_set_mud_url(
+                sd_dhcp_client *client,
+                const char *mudurl) {
+
+        assert_return(client, -EINVAL);
+        assert_return(mudurl, -EINVAL);
+        assert_return(strlen(mudurl) <= 255, -EINVAL);
+        assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+        return free_and_strdup(&client->mudurl, mudurl);
+}
+
 int sd_dhcp_client_set_user_class(
                 sd_dhcp_client *client,
                 const char* const* user_class) {
@@ -545,17 +560,17 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt
         return 0;
 }
 
-int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) {
         int r;
 
         assert_return(client, -EINVAL);
         assert_return(v, -EINVAL);
 
-        r = ordered_hashmap_ensure_allocated(&client->options, &dhcp_option_hash_ops);
+        r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp_option_hash_ops);
         if (r < 0)
                 return r;
 
-        r = ordered_hashmap_put(client->options, UINT_TO_PTR(v->option), v);
+        r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
         if (r < 0)
                 return r;
 
@@ -563,6 +578,25 @@ int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v) {
         return 0;
 }
 
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops);
+        if (r < 0)
+                return -ENOMEM;
+
+        r = ordered_hashmap_put(client->vendor_options, v, v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp_option_ref(v);
+
+        return 1;
+}
+
 int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         assert_return(client, -EINVAL);
 
@@ -648,7 +682,7 @@ static int client_message_init(
         assert(ret);
         assert(_optlen);
         assert(_optoffset);
-        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE));
+        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE));
 
         optlen = DHCP_MIN_OPTIONS_SIZE;
         size = sizeof(DHCPPacket) + optlen;
@@ -880,6 +914,15 @@ static int client_send_discover(sd_dhcp_client *client) {
                         return r;
         }
 
+        if (client->mudurl) {
+                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_MUD_URL,
+                                       strlen(client->mudurl),
+                                       client->mudurl);
+                if (r < 0)
+                        return r;
+        }
+
         if (client->user_class) {
                 r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
                                        SD_DHCP_OPTION_USER_CLASS,
@@ -889,13 +932,22 @@ static int client_send_discover(sd_dhcp_client *client) {
                         return r;
         }
 
-        ORDERED_HASHMAP_FOREACH(j, client->options, i) {
+        ORDERED_HASHMAP_FOREACH(j, client->extra_options, i) {
                 r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
                                        j->option, j->length, j->data);
                 if (r < 0)
                         return r;
         }
 
+        if (!ordered_hashmap_isempty(client->vendor_options)) {
+                r = dhcp_option_append(
+                                &discover->dhcp, optlen, &optoffset, 0,
+                                SD_DHCP_OPTION_VENDOR_SPECIFIC,
+                                ordered_hashmap_size(client->vendor_options), client->vendor_options);
+                if (r < 0)
+                        return r;
+        }
+
         r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
                                SD_DHCP_OPTION_END, 0, NULL);
         if (r < 0)
@@ -1008,6 +1060,16 @@ static int client_send_request(sd_dhcp_client *client) {
                         return r;
         }
 
+        if (client->mudurl) {
+                r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_MUD_URL,
+                                       strlen(client->mudurl),
+                                       client->mudurl);
+                if (r < 0)
+                        return r;
+        }
+
+
         r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0,
                                SD_DHCP_OPTION_END, 0, NULL);
         if (r < 0)
@@ -1971,6 +2033,48 @@ int sd_dhcp_client_send_release(sd_dhcp_client *client) {
         return 0;
 }
 
+int sd_dhcp_client_send_decline(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+        assert_return(client->state != DHCP_STATE_STOPPED, -ESTALE);
+        assert_return(client->lease, -EUNATCH);
+
+        _cleanup_free_ DHCPPacket *release = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        r = client_message_init(client, &release, DHCP_DECLINE, &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        release->dhcp.ciaddr = client->lease->address;
+        memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+        r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        r = dhcp_network_send_udp_socket(client->fd,
+                                         client->lease->server_address,
+                                         DHCP_PORT_SERVER,
+                                         &release->dhcp,
+                                         sizeof(DHCPMessage) + optoffset);
+        if (r < 0)
+                return r;
+
+        log_dhcp_client(client, "DECLINE");
+
+        client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
+
+        if (client->state != DHCP_STATE_STOPPED) {
+                r = sd_dhcp_client_start(client);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 int sd_dhcp_client_stop(sd_dhcp_client *client) {
         DHCP_CLIENT_DONT_DESTROY(client);
 
@@ -2035,8 +2139,10 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
         free(client->req_opts);
         free(client->hostname);
         free(client->vendor_class_identifier);
+        free(client->mudurl);
         client->user_class = strv_free(client->user_class);
-        ordered_hashmap_free(client->options);
+        ordered_hashmap_free(client->extra_options);
+        ordered_hashmap_free(client->vendor_options);
         return mfree(client);
 }
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index ac6fe3f4..86f21290 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -131,6 +131,28 @@ int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) {
         return (int) lease->sip_size;
 }
 
+int sd_dhcp_lease_get_pop3_server(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (lease->pop3_server_size <= 0)
+                return -ENODATA;
+
+        *addr = lease->pop3_server;
+        return (int) lease->pop3_server_size;
+}
+
+int sd_dhcp_lease_get_smtp_server(sd_dhcp_lease *lease, const struct in_addr **addr) {
+        assert_return(lease, -EINVAL);
+        assert_return(addr, -EINVAL);
+
+        if (lease->smtp_server_size <= 0)
+                return -ENODATA;
+
+        *addr = lease->smtp_server;
+        return (int) lease->smtp_server_size;
+}
+
 int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) {
         assert_return(lease, -EINVAL);
         assert_return(domainname, -EINVAL);
@@ -281,6 +303,8 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
         free(lease->dns);
         free(lease->ntp);
         free(lease->sip);
+        free(lease->pop3_server);
+        free(lease->smtp_server);
         free(lease->static_route);
         free(lease->client_id);
         free(lease->vendor_specific);
@@ -603,6 +627,18 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                         log_debug_errno(r, "Failed to parse SIP server, ignoring: %m");
                 break;
 
+        case SD_DHCP_OPTION_POP3_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->pop3_server, &lease->pop3_server_size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m");
+                break;
+
+        case SD_DHCP_OPTION_SMTP_SERVER:
+                r = lease_parse_in_addrs(option, len, &lease->smtp_server, &lease->smtp_server_size);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m");
+                break;
+
         case SD_DHCP_OPTION_STATIC_ROUTE:
                 r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, &lease->static_route_allocated);
                 if (r < 0)
@@ -1039,6 +1075,8 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                 *dns = NULL,
                 *ntp = NULL,
                 *sip = NULL,
+                *pop3_server = NULL,
+                *smtp_server = NULL,
                 *mtu = NULL,
                 *routes = NULL,
                 *domains = NULL,
@@ -1068,6 +1106,8 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                            "DNS", &dns,
                            "NTP", &ntp,
                            "SIP", &sip,
+                           "POP3_SERVERS", &pop3_server,
+                           "SMTP_SERVERS", &smtp_server,
                            "MTU", &mtu,
                            "DOMAINNAME", &lease->domainname,
                            "HOSTNAME", &lease->hostname,
@@ -1177,7 +1217,23 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                 if (r < 0)
                         log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip);
                 else
-                        lease->ntp_size = r;
+                        lease->sip_size = r;
+        }
+
+        if (pop3_server) {
+                r = deserialize_in_addrs(&lease->pop3_server, pop3_server);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3_server);
+                else
+                        lease->pop3_server_size = r;
+        }
+
+        if (smtp_server) {
+                r = deserialize_in_addrs(&lease->smtp_server, smtp_server);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp_server);
+                else
+                        lease->smtp_server_size = r;
         }
 
         if (mtu) {
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index e1150f98..826ad56a 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -27,6 +27,7 @@
 #include "socket-util.h"
 #include "string-table.h"
 #include "util.h"
+#include "web-util.h"
 
 #define MAX_MAC_ADDR_LEN INFINIBAND_ALEN
 
@@ -67,6 +68,7 @@ struct sd_dhcp6_client {
         size_t req_opts_allocated;
         size_t req_opts_len;
         char *fqdn;
+        char *mudurl;
         sd_event_source *receive_message;
         usec_t retransmit_time;
         uint8_t retransmit_count;
@@ -369,6 +371,17 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option)
         return 0;
 }
 
+int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, char *mudurl) {
+
+        assert_return(client, -EINVAL);
+        assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY);
+        assert_return(mudurl, -EINVAL);
+        assert_return(strlen(mudurl) <= 255, -EINVAL);
+        assert_return(http_url_is_valid(mudurl), -EINVAL);
+
+        return free_and_strdup(&client->mudurl, mudurl);
+}
+
 int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) {
         assert_return(client, -EINVAL);
         assert_return(delegation, -EINVAL);
@@ -490,6 +503,14 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         case DHCP6_STATE_INFORMATION_REQUEST:
                 message->type = DHCP6_INFORMATION_REQUEST;
 
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
                 break;
 
         case DHCP6_STATE_SOLICITATION:
@@ -513,6 +534,14 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
                 if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd, &client->hint_pd_prefix);
                         if (r < 0)
@@ -551,6 +580,14 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
                 if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
                         if (r < 0)
@@ -577,6 +614,14 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
                                 return r;
                 }
 
+                if (client->mudurl) {
+                        r = dhcp6_option_append(&opt, &optlen,
+                                                SD_DHCP6_OPTION_MUD_URL, strlen(client->mudurl),
+                                                client->mudurl);
+                        if (r < 0)
+                                return r;
+                }
+
                 if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) {
                         r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL);
                         if (r < 0)
@@ -681,8 +726,7 @@ static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void
 }
 
 static usec_t client_timeout_compute_random(usec_t val) {
-        return val - val / 10 +
-                (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
+        return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC;
 }
 
 static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
@@ -692,7 +736,6 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         usec_t max_retransmit_duration = 0;
         uint8_t max_retransmit_count = 0;
         char time_string[FORMAT_TIMESPAN_MAX];
-        uint32_t expire = 0;
 
         assert(s);
         assert(client);
@@ -741,8 +784,9 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 max_retransmit_time = DHCP6_REB_MAX_RT;
 
                 if (event_source_is_enabled(client->timeout_resend_expire) <= 0) {
-                        r = dhcp6_lease_ia_rebind_expire(&client->lease->ia,
-                                                         &expire);
+                        uint32_t expire = 0;
+
+                        r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire);
                         if (r < 0) {
                                 client_stop(client, r);
                                 return 0;
@@ -757,7 +801,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 return 0;
         }
 
-        if (max_retransmit_count &&
+        if (max_retransmit_count > 0 &&
             client->retransmit_count >= max_retransmit_count) {
                 client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
                 return 0;
@@ -771,7 +815,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         if (r >= 0)
                 client->retransmit_count++;
 
-        if (!client->retransmit_time) {
+        if (client->retransmit_time == 0) {
                 client->retransmit_time =
                         client_timeout_compute_random(init_retransmit_time);
 
@@ -779,7 +823,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                         client->retransmit_time += init_retransmit_time / 10;
 
         } else {
-                if (max_retransmit_time &&
+                if (max_retransmit_time > 0 &&
                     client->retransmit_time > max_retransmit_time / 2)
                         client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
                 else
@@ -797,7 +841,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         if (r < 0)
                 goto error;
 
-        if (max_retransmit_duration && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
+        if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
 
                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
                                  max_retransmit_duration / USEC_PER_SEC);
@@ -1528,6 +1572,7 @@ static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
 
         free(client->req_opts);
         free(client->fqdn);
+        free(client->mudurl);
         return mfree(client);
 }
 
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index 3efa8170..9f69088d 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -443,7 +443,7 @@ int sd_ipv4acd_is_running(sd_ipv4acd *acd) {
         return acd->state != IPV4ACD_STATE_INIT;
 }
 
-int sd_ipv4acd_start(sd_ipv4acd *acd) {
+int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) {
         int r;
 
         assert_return(acd, -EINVAL);
@@ -460,7 +460,9 @@ int sd_ipv4acd_start(sd_ipv4acd *acd) {
         safe_close(acd->fd);
         acd->fd = r;
         acd->defend_window = 0;
-        acd->n_conflict = 0;
+
+        if (reset_conflicts)
+                acd->n_conflict = 0;
 
         r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd);
         if (r < 0)
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index 2d17b4fe..a610546c 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -243,7 +243,7 @@ static int ipv4ll_start_internal(sd_ipv4ll *ll, bool reset_generation) {
                 picked_address = true;
         }
 
-        r = sd_ipv4acd_start(ll->acd);
+        r = sd_ipv4acd_start(ll->acd, reset_generation);
         if (r < 0) {
 
                 /* We couldn't start? If so, let's forget the picked address again, the user might make a change and
diff --git a/src/systemd/src/libsystemd/sd-event/event-source.h b/src/systemd/src/libsystemd/sd-event/event-source.h
index 99ab8fc1..08eb9b6a 100644
--- a/src/systemd/src/libsystemd/sd-event/event-source.h
+++ b/src/systemd/src/libsystemd/sd-event/event-source.h
@@ -34,7 +34,7 @@ typedef enum EventSourceType {
  * we know how to dispatch it */
 typedef enum WakeupType {
         WAKEUP_NONE,
-        WAKEUP_EVENT_SOURCE,
+        WAKEUP_EVENT_SOURCE, /* either I/O or pidfd wakeup */
         WAKEUP_CLOCK_DATA,
         WAKEUP_SIGNAL_DATA,
         WAKEUP_INOTIFY_DATA,
@@ -96,6 +96,12 @@ struct sd_event_source {
                         siginfo_t siginfo;
                         pid_t pid;
                         int options;
+                        int pidfd;
+                        bool registered:1; /* whether the pidfd is registered in the epoll */
+                        bool pidfd_owned:1; /* close pidfd when event source is freed */
+                        bool process_owned:1; /* kill+reap process when event source is freed */
+                        bool exited:1; /* true if process exited (i.e. if there's value in SIGKILLing it if we want to get rid of it) */
+                        bool waited:1; /* true if process was waited for (i.e. if there's value in waitid(P_PID)'ing it if we want to get rid of it) */
                 } child;
                 struct {
                         sd_event_handler_t callback;
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index 7c4566e3..3e56e807 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -11,6 +11,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "env-util.h"
 #include "event-source.h"
 #include "fd-util.h"
 #include "fs-util.h"
@@ -30,6 +31,14 @@
 
 #define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC)
 
+static bool EVENT_SOURCE_WATCH_PIDFD(sd_event_source *s) {
+        /* Returns true if this is a PID event source and can be implemented by watching EPOLLIN */
+        return s &&
+                s->type == SOURCE_CHILD &&
+                s->child.pidfd >= 0 &&
+                s->child.options == WEXITED;
+}
+
 static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = {
         [SOURCE_IO] = "io",
         [SOURCE_TIME_REALTIME] = "realtime",
@@ -108,6 +117,9 @@ struct sd_event {
 
         unsigned n_sources;
 
+        struct epoll_event *event_queue;
+        size_t event_queue_allocated;
+
         LIST_HEAD(sd_event_source, sources);
 
         usec_t last_run, last_log;
@@ -279,6 +291,8 @@ static sd_event *event_free(sd_event *e) {
         hashmap_free(e->child_sources);
         set_free(e->post_sources);
 
+        free(e->event_queue);
+
         return mfree(e);
 }
 
@@ -358,8 +372,6 @@ static bool event_pid_changed(sd_event *e) {
 }
 
 static void source_io_unregister(sd_event_source *s) {
-        int r;
-
         assert(s);
         assert(s->type == SOURCE_IO);
 
@@ -369,8 +381,7 @@ static void source_io_unregister(sd_event_source *s) {
         if (!s->io.registered)
                 return;
 
-        r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL);
-        if (r < 0)
+        if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL) < 0)
                 log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m",
                                 strna(s->description), event_source_type_to_string(s->type));
 
@@ -382,22 +393,20 @@ static int source_io_register(
                 int enabled,
                 uint32_t events) {
 
-        struct epoll_event ev;
-        int r;
-
         assert(s);
         assert(s->type == SOURCE_IO);
         assert(enabled != SD_EVENT_OFF);
 
-        ev = (struct epoll_event) {
+        struct epoll_event ev = {
                 .events = events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0),
                 .data.ptr = s,
         };
+        int r;
 
-        if (s->io.registered)
-                r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->io.fd, &ev);
-        else
-                r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_ADD, s->io.fd, &ev);
+        r = epoll_ctl(s->event->epoll_fd,
+                      s->io.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD,
+                      s->io.fd,
+                      &ev);
         if (r < 0)
                 return -errno;
 
@@ -406,6 +415,49 @@ static int source_io_register(
         return 0;
 }
 
+static void source_child_pidfd_unregister(sd_event_source *s) {
+        assert(s);
+        assert(s->type == SOURCE_CHILD);
+
+        if (event_pid_changed(s->event))
+                return;
+
+        if (!s->child.registered)
+                return;
+
+        if (EVENT_SOURCE_WATCH_PIDFD(s))
+                if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->child.pidfd, NULL) < 0)
+                        log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m",
+                                        strna(s->description), event_source_type_to_string(s->type));
+
+        s->child.registered = false;
+}
+
+static int source_child_pidfd_register(sd_event_source *s, int enabled) {
+        int r;
+
+        assert(s);
+        assert(s->type == SOURCE_CHILD);
+        assert(enabled != SD_EVENT_OFF);
+
+        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                struct epoll_event ev = {
+                        .events = EPOLLIN | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0),
+                        .data.ptr = s,
+                };
+
+                if (s->child.registered)
+                        r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->child.pidfd, &ev);
+                else
+                        r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_ADD, s->child.pidfd, &ev);
+                if (r < 0)
+                        return -errno;
+        }
+
+        s->child.registered = true;
+        return 0;
+}
+
 static clockid_t event_source_type_to_clock(EventSourceType t) {
 
         switch (t) {
@@ -495,7 +547,6 @@ static int event_make_signal_data(
                 int sig,
                 struct signal_data **ret) {
 
-        struct epoll_event ev;
         struct signal_data *d;
         bool added = false;
         sigset_t ss_copy;
@@ -562,7 +613,7 @@ static int event_make_signal_data(
 
         d->fd = fd_move_above_stdio(r);
 
-        ev = (struct epoll_event) {
+        struct epoll_event ev = {
                 .events = EPOLLIN,
                 .data.ptr = d,
         };
@@ -616,9 +667,8 @@ static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig)
 
         assert(e);
 
-        /* Rechecks if the specified signal is still something we are
-         * interested in. If not, we'll unmask it, and possibly drop
-         * the signalfd for it. */
+        /* Rechecks if the specified signal is still something we are interested in. If not, we'll unmask it,
+         * and possibly drop the signalfd for it. */
 
         if (sig == SIGCHLD &&
             e->n_enabled_child_sources > 0)
@@ -709,9 +759,13 @@ static void source_disconnect(sd_event_source *s) {
                         }
 
                         (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid));
-                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
                 }
 
+                if (EVENT_SOURCE_WATCH_PIDFD(s))
+                        source_child_pidfd_unregister(s);
+                else
+                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+
                 break;
 
         case SOURCE_DEFER:
@@ -771,9 +825,7 @@ static void source_disconnect(sd_event_source *s) {
         if (s->prepare)
                 prioq_remove(s->event->prepare, s, &s->prepare_index);
 
-        event = s->event;
-
-        s->event = NULL;
+        event = TAKE_PTR(s->event);
         LIST_REMOVE(sources, event->sources, s);
         event->n_sources--;
 
@@ -792,6 +844,44 @@ static void source_free(sd_event_source *s) {
         if (s->type == SOURCE_IO && s->io.owned)
                 s->io.fd = safe_close(s->io.fd);
 
+        if (s->type == SOURCE_CHILD) {
+                /* Eventually the kernel will do this automatically for us, but for now let's emulate this (unreliably) in userspace. */
+
+                if (s->child.process_owned) {
+
+                        if (!s->child.exited) {
+                                bool sent = false;
+
+                                if (s->child.pidfd >= 0) {
+                                        if (pidfd_send_signal(s->child.pidfd, SIGKILL, NULL, 0) < 0) {
+                                                if (errno == ESRCH) /* Already dead */
+                                                        sent = true;
+                                                else if (!ERRNO_IS_NOT_SUPPORTED(errno))
+                                                        log_debug_errno(errno, "Failed to kill process " PID_FMT " via pidfd_send_signal(), re-trying via kill(): %m",
+                                                                        s->child.pid);
+                                        } else
+                                                sent = true;
+                                }
+
+                                if (!sent)
+                                        if (kill(s->child.pid, SIGKILL) < 0)
+                                                if (errno != ESRCH) /* Already dead */
+                                                        log_debug_errno(errno, "Failed to kill process " PID_FMT " via kill(), ignoring: %m",
+                                                                        s->child.pid);
+                        }
+
+                        if (!s->child.waited) {
+                                siginfo_t si = {};
+
+                                /* Reap the child if we can */
+                                (void) waitid(P_PID, s->child.pid, &si, WEXITED);
+                        }
+                }
+
+                if (s->child.pidfd_owned)
+                        s->child.pidfd = safe_close(s->child.pidfd);
+        }
+
         if (s->destroy_callback)
                 s->destroy_callback(s->userdata);
 
@@ -949,33 +1039,31 @@ static int event_setup_timer_fd(
                 struct clock_data *d,
                 clockid_t clock) {
 
-        struct epoll_event ev;
-        int r, fd;
-
         assert(e);
         assert(d);
 
         if (_likely_(d->fd >= 0))
                 return 0;
 
+        _cleanup_close_ int fd = -1;
+        int r;
+
         fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC);
         if (fd < 0)
                 return -errno;
 
         fd = fd_move_above_stdio(fd);
 
-        ev = (struct epoll_event) {
+        struct epoll_event ev = {
                 .events = EPOLLIN,
                 .data.ptr = d,
         };
 
         r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev);
-        if (r < 0) {
-                safe_close(fd);
+        if (r < 0)
                 return -errno;
-        }
 
-        d->fd = fd;
+        d->fd = TAKE_FD(fd);
         return 0;
 }
 
@@ -1076,7 +1164,6 @@ _public_ int sd_event_add_signal(
 
         _cleanup_(source_freep) sd_event_source *s = NULL;
         struct signal_data *d;
-        sigset_t ss;
         int r;
 
         assert_return(e, -EINVAL);
@@ -1088,11 +1175,10 @@ _public_ int sd_event_add_signal(
         if (!callback)
                 callback = signal_exit_callback;
 
-        r = pthread_sigmask(SIG_SETMASK, NULL, &ss);
-        if (r != 0)
-                return -r;
-
-        if (!sigismember(&ss, sig))
+        r = signal_is_blocked(sig);
+        if (r < 0)
+                return r;
+        if (r == 0)
                 return -EBUSY;
 
         if (!e->signal_sources) {
@@ -1126,7 +1212,11 @@ _public_ int sd_event_add_signal(
 
         return 0;
 }
-#endif /* NM_IGNORED */
+
+static bool shall_use_pidfd(void) {
+        /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */
+        return getenv_bool_secure("SYSTEMD_PIDFD") != 0;
+}
 
 _public_ int sd_event_add_child(
                 sd_event *e,
@@ -1148,6 +1238,20 @@ _public_ int sd_event_add_child(
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(!event_pid_changed(e), -ECHILD);
 
+        if (e->n_enabled_child_sources == 0) {
+                /* Caller must block SIGCHLD before using us to watch children, even if pidfd is available,
+                 * for compatibility with pre-pidfd and because we don't want the reap the child processes
+                 * ourselves, i.e. call waitid(), and don't want Linux' default internal logic for that to
+                 * take effect.
+                 *
+                 * (As an optimization we only do this check on the first child event source created.) */
+                r = signal_is_blocked(SIGCHLD);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EBUSY;
+        }
+
         r = hashmap_ensure_allocated(&e->child_sources, NULL);
         if (r < 0)
                 return r;
@@ -1159,32 +1263,147 @@ _public_ int sd_event_add_child(
         if (!s)
                 return -ENOMEM;
 
+        s->wakeup = WAKEUP_EVENT_SOURCE;
         s->child.pid = pid;
         s->child.options = options;
         s->child.callback = callback;
         s->userdata = userdata;
         s->enabled = SD_EVENT_ONESHOT;
 
+        /* We always take a pidfd here if we can, even if we wait for anything else than WEXITED, so that we
+         * pin the PID, and make regular waitid() handling race-free. */
+
+        if (shall_use_pidfd()) {
+                s->child.pidfd = pidfd_open(s->child.pid, 0);
+                if (s->child.pidfd < 0) {
+                        /* Propagate errors unless the syscall is not supported or blocked */
+                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+                                return -errno;
+                } else
+                        s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */
+        } else
+                s->child.pidfd = -1;
+
         r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s);
         if (r < 0)
                 return r;
 
         e->n_enabled_child_sources++;
 
-        r = event_make_signal_data(e, SIGCHLD, NULL);
-        if (r < 0) {
-                e->n_enabled_child_sources--;
-                return r;
-        }
+        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                /* We have a pidfd and we only want to watch for exit */
 
-        e->need_process_child = true;
+                r = source_child_pidfd_register(s, s->enabled);
+                if (r < 0) {
+                        e->n_enabled_child_sources--;
+                        return r;
+                }
+        } else {
+                /* We have no pidfd or we shall wait for some other event than WEXITED */
+
+                r = event_make_signal_data(e, SIGCHLD, NULL);
+                if (r < 0) {
+                        e->n_enabled_child_sources--;
+                        return r;
+                }
+
+                e->need_process_child = true;
+        }
 
         if (ret)
                 *ret = s;
+
         TAKE_PTR(s);
+        return 0;
+}
+
+_public_ int sd_event_add_child_pidfd(
+                sd_event *e,
+                sd_event_source **ret,
+                int pidfd,
+                int options,
+                sd_event_child_handler_t callback,
+                void *userdata) {
+
+
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        pid_t pid;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(pidfd >= 0, -EBADF);
+        assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
+        assert_return(options != 0, -EINVAL);
+        assert_return(callback, -EINVAL);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_pid_changed(e), -ECHILD);
+
+        if (e->n_enabled_child_sources == 0) {
+                r = signal_is_blocked(SIGCHLD);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EBUSY;
+        }
+
+        r = hashmap_ensure_allocated(&e->child_sources, NULL);
+        if (r < 0)
+                return r;
+
+        r = pidfd_get_pid(pidfd, &pid);
+        if (r < 0)
+                return r;
+
+        if (hashmap_contains(e->child_sources, PID_TO_PTR(pid)))
+                return -EBUSY;
+
+        s = source_new(e, !ret, SOURCE_CHILD);
+        if (!s)
+                return -ENOMEM;
+
+        s->wakeup = WAKEUP_EVENT_SOURCE;
+        s->child.pidfd = pidfd;
+        s->child.pid = pid;
+        s->child.options = options;
+        s->child.callback = callback;
+        s->child.pidfd_owned = false; /* If we got the pidfd passed in we don't own it by default (similar to the IO fd case) */
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ONESHOT;
 
+        r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s);
+        if (r < 0)
+                return r;
+
+        e->n_enabled_child_sources++;
+
+        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                /* We only want to watch for WEXITED */
+
+                r = source_child_pidfd_register(s, s->enabled);
+                if (r < 0) {
+                        e->n_enabled_child_sources--;
+                        return r;
+                }
+        } else {
+                /* We shall wait for some other event than WEXITED */
+
+                r = event_make_signal_data(e, SIGCHLD, NULL);
+                if (r < 0) {
+                        e->n_enabled_child_sources--;
+                        return r;
+                }
+
+                e->need_process_child = true;
+        }
+
+        if (ret)
+                *ret = s;
+
+        TAKE_PTR(s);
         return 0;
 }
+#endif /* NM_IGNORED */
 
 _public_ int sd_event_add_defer(
                 sd_event *e,
@@ -1330,7 +1549,6 @@ static int event_make_inotify_data(
 
         _cleanup_close_ int fd = -1;
         struct inotify_data *d;
-        struct epoll_event ev;
         int r;
 
         assert(e);
@@ -1369,7 +1587,7 @@ static int event_make_inotify_data(
                 return r;
         }
 
-        ev = (struct epoll_event) {
+        struct epoll_event ev = {
                 .events = EPOLLIN,
                 .data.ptr = d,
         };
@@ -1769,7 +1987,7 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
                         return r;
                 }
 
-                epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL);
+                (void) epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL);
         }
 
         return 0;
@@ -2030,7 +2248,11 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
                         assert(s->event->n_enabled_child_sources > 0);
                         s->event->n_enabled_child_sources--;
 
-                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+                        if (EVENT_SOURCE_WATCH_PIDFD(s))
+                                source_child_pidfd_unregister(s);
+                        else
+                                event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+
                         break;
 
                 case SOURCE_EXIT:
@@ -2104,12 +2326,25 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
 
                         s->enabled = m;
 
-                        r = event_make_signal_data(s->event, SIGCHLD, NULL);
-                        if (r < 0) {
-                                s->enabled = SD_EVENT_OFF;
-                                s->event->n_enabled_child_sources--;
-                                event_gc_signal_data(s->event, &s->priority, SIGCHLD);
-                                return r;
+                        if (EVENT_SOURCE_WATCH_PIDFD(s)) {
+                                /* yes, we have pidfd */
+
+                                r = source_child_pidfd_register(s, s->enabled);
+                                if (r < 0) {
+                                        s->enabled = SD_EVENT_OFF;
+                                        s->event->n_enabled_child_sources--;
+                                        return r;
+                                }
+                        } else {
+                                /* no pidfd, or something other to watch for than WEXITED */
+
+                                r = event_make_signal_data(s->event, SIGCHLD, NULL);
+                                if (r < 0) {
+                                        s->enabled = SD_EVENT_OFF;
+                                        s->event->n_enabled_child_sources--;
+                                        event_gc_signal_data(s->event, &s->priority, SIGCHLD);
+                                        return r;
+                                }
                         }
 
                         break;
@@ -2232,6 +2467,98 @@ _public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) {
         return 0;
 }
 
+_public_ int sd_event_source_get_child_pidfd(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+
+        if (s->child.pidfd < 0)
+                return -EOPNOTSUPP;
+
+        return s->child.pidfd;
+}
+
+_public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(SIGNAL_VALID(sig), -EINVAL);
+
+        /* If we already have seen indication the process exited refuse sending a signal early. This way we
+         * can be sure we don't accidentally kill the wrong process on PID reuse when pidfds are not
+         * available. */
+        if (s->child.exited)
+                return -ESRCH;
+
+        if (s->child.pidfd >= 0) {
+                siginfo_t copy;
+
+                /* pidfd_send_signal() changes the siginfo_t argument. This is weird, let's hence copy the
+                 * structure here */
+                if (si)
+                        copy = *si;
+
+                if (pidfd_send_signal(s->child.pidfd, sig, si ? &copy : NULL, 0) < 0) {
+                        /* Let's propagate the error only if the system call is not implemented or prohibited */
+                        if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))
+                                return -errno;
+                } else
+                        return 0;
+        }
+
+        /* Flags are only supported for pidfd_send_signal(), not for rt_sigqueueinfo(), hence let's refuse
+         * this here. */
+        if (flags != 0)
+                return -EOPNOTSUPP;
+
+        if (si) {
+                /* We use rt_sigqueueinfo() only if siginfo_t is specified. */
+                siginfo_t copy = *si;
+
+                if (rt_sigqueueinfo(s->child.pid, sig, &copy) < 0)
+                        return -errno;
+        } else if (kill(s->child.pid, sig) < 0)
+                return -errno;
+
+        return 0;
+}
+
+_public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        if (s->child.pidfd < 0)
+                return -EOPNOTSUPP;
+
+        return s->child.pidfd_owned;
+}
+
+_public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        if (s->child.pidfd < 0)
+                return -EOPNOTSUPP;
+
+        s->child.pidfd_owned = own;
+        return 0;
+}
+
+_public_ int sd_event_source_get_child_process_own(sd_event_source *s) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        return s->child.process_owned;
+}
+
+_public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) {
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_CHILD, -EDOM);
+
+        s->child.process_owned = own;
+        return 0;
+}
+
 _public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) {
         assert_return(s, -EINVAL);
         assert_return(mask, -EINVAL);
@@ -2542,6 +2869,12 @@ static int process_child(sd_event *e) {
                 if (s->enabled == SD_EVENT_OFF)
                         continue;
 
+                if (s->child.exited)
+                        continue;
+
+                if (EVENT_SOURCE_WATCH_PIDFD(s)) /* There's a usable pidfd known for this event source? then don't waitid() for it here */
+                        continue;
+
                 zero(s->child.siginfo);
                 r = waitid(P_PID, s->child.pid, &s->child.siginfo,
                            WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options);
@@ -2551,6 +2884,9 @@ static int process_child(sd_event *e) {
                 if (s->child.siginfo.si_pid != 0) {
                         bool zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED);
 
+                        if (zombie)
+                                s->child.exited = true;
+
                         if (!zombie && (s->child.options & WEXITED)) {
                                 /* If the child isn't dead then let's
                                  * immediately remove the state change
@@ -2570,6 +2906,33 @@ static int process_child(sd_event *e) {
         return 0;
 }
 
+static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) {
+        assert(e);
+        assert(s);
+        assert(s->type == SOURCE_CHILD);
+
+        if (s->pending)
+                return 0;
+
+        if (s->enabled == SD_EVENT_OFF)
+                return 0;
+
+        if (!EVENT_SOURCE_WATCH_PIDFD(s))
+                return 0;
+
+        zero(s->child.siginfo);
+        if (waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0)
+                return -errno;
+
+        if (s->child.siginfo.si_pid == 0)
+                return 0;
+
+        if (IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED))
+                s->child.exited = true;
+
+        return source_set_pending(s, true);
+}
+
 static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) {
         bool read_one = false;
         int r;
@@ -2854,8 +3217,10 @@ static int source_dispatch(sd_event_source *s) {
                 r = s->child.callback(s, &s->child.siginfo, s->userdata);
 
                 /* Now, reap the PID for good. */
-                if (zombie)
+                if (zombie) {
                         (void) waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED);
+                        s->child.waited = true;
+                }
 
                 break;
         }
@@ -3056,6 +3421,11 @@ _public_ int sd_event_prepare(sd_event *e) {
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
+        /* Let's check that if we are a default event loop we are executed in the correct thread. We only do
+         * this check here once, since gettid() is typically not cached, and thus want to minimize
+         * syscalls */
+        assert_return(!e->default_event_ptr || e->tid == gettid(), -EREMOTEIO);
+
         if (e->exit_requested)
                 goto pending;
 
@@ -3106,8 +3476,7 @@ pending:
 }
 
 _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
-        struct epoll_event *ev_queue;
-        unsigned ev_queue_max;
+        size_t event_queue_max;
         int r, m, i;
 
         assert_return(e, -EINVAL);
@@ -3121,14 +3490,15 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
                 return 1;
         }
 
-        ev_queue_max = MAX(e->n_sources, 1u);
-        ev_queue = newa(struct epoll_event, ev_queue_max);
+        event_queue_max = MAX(e->n_sources, 1u);
+        if (!GREEDY_REALLOC(e->event_queue, e->event_queue_allocated, event_queue_max))
+                return -ENOMEM;
 
         /* If we still have inotify data buffered, then query the other fds, but don't wait on it */
         if (e->inotify_data_buffered)
                 timeout = 0;
 
-        m = epoll_wait(e->epoll_fd, ev_queue, ev_queue_max,
+        m = epoll_wait(e->epoll_fd, e->event_queue, event_queue_max,
                        timeout == (uint64_t) -1 ? -1 : (int) DIV_ROUND_UP(timeout, USEC_PER_MSEC));
         if (m < 0) {
                 if (errno == EINTR) {
@@ -3144,29 +3514,50 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
 
         for (i = 0; i < m; i++) {
 
-                if (ev_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG))
-                        r = flush_timer(e, e->watchdog_fd, ev_queue[i].events, NULL);
+                if (e->event_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG))
+                        r = flush_timer(e, e->watchdog_fd, e->event_queue[i].events, NULL);
                 else {
-                        WakeupType *t = ev_queue[i].data.ptr;
+                        WakeupType *t = e->event_queue[i].data.ptr;
 
                         switch (*t) {
 
-                        case WAKEUP_EVENT_SOURCE:
-                                r = process_io(e, ev_queue[i].data.ptr, ev_queue[i].events);
+                        case WAKEUP_EVENT_SOURCE: {
+                                sd_event_source *s = e->event_queue[i].data.ptr;
+
+                                assert(s);
+
+                                switch (s->type) {
+
+                                case SOURCE_IO:
+                                        r = process_io(e, s, e->event_queue[i].events);
+                                        break;
+
+                                case SOURCE_CHILD:
+                                        r = process_pidfd(e, s, e->event_queue[i].events);
+                                        break;
+
+                                default:
+                                        assert_not_reached("Unexpected event source type");
+                                }
+
                                 break;
+                        }
 
                         case WAKEUP_CLOCK_DATA: {
-                                struct clock_data *d = ev_queue[i].data.ptr;
-                                r = flush_timer(e, d->fd, ev_queue[i].events, &d->next);
+                                struct clock_data *d = e->event_queue[i].data.ptr;
+
+                                assert(d);
+
+                                r = flush_timer(e, d->fd, e->event_queue[i].events, &d->next);
                                 break;
                         }
 
                         case WAKEUP_SIGNAL_DATA:
-                                r = process_signal(e, ev_queue[i].data.ptr, ev_queue[i].events);
+                                r = process_signal(e, e->event_queue[i].data.ptr, e->event_queue[i].events);
                                 break;
 
                         case WAKEUP_INOTIFY_DATA:
-                                r = event_inotify_data_read(e, ev_queue[i].data.ptr, ev_queue[i].events);
+                                r = event_inotify_data_read(e, e->event_queue[i].data.ptr, e->event_queue[i].events);
                                 break;
 
                         default:
@@ -3450,8 +3841,6 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
                 return e->watchdog;
 
         if (b) {
-                struct epoll_event ev;
-
                 r = sd_watchdog_enabled(false, &e->watchdog_period);
                 if (r <= 0)
                         return r;
@@ -3468,7 +3857,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
                 if (r < 0)
                         goto fail;
 
-                ev = (struct epoll_event) {
+                struct epoll_event ev = {
                         .events = EPOLLIN,
                         .data.ptr = INT_TO_PTR(SOURCE_WATCHDOG),
                 };
@@ -3481,7 +3870,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
 
         } else {
                 if (e->watchdog_fd >= 0) {
-                        epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL);
+                        (void) epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL);
                         e->watchdog_fd = safe_close(e->watchdog_fd);
                 }
         }
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index f8f0883c..3cb96378 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -14,7 +14,7 @@
 #include "stdio-util.h"
 
 #if 0 /* NM_IGNORED */
-char *id128_to_uuid_string(sd_id128_t id, char s[37]) {
+char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) {
         unsigned n, k = 0;
 
         assert(s);
@@ -195,5 +195,18 @@ int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) {
         return memcmp(a, b, 16);
 }
 
+sd_id128_t id128_make_v4_uuid(sd_id128_t id) {
+        /* Stolen from generate_random_uuid() of drivers/char/random.c
+         * in the kernel sources */
+
+        /* Set UUID version to 4 --- truly random generation */
+        id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
+
+        /* Set the UUID variant to DCE */
+        id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
+
+        return id;
+}
+
 DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.h b/src/systemd/src/libsystemd/sd-id128/id128-util.h
index 82a69a77..1901bf11 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.h
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -8,7 +8,9 @@
 #include "hash-funcs.h"
 #include "macro.h"
 
-char *id128_to_uuid_string(sd_id128_t id, char s[37]);
+#define ID128_UUID_STRING_MAX 37
+
+char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]);
 
 bool id128_is_valid(const char *s) _pure_;
 
@@ -28,3 +30,5 @@ int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync);
 void id128_hash_func(const sd_id128_t *p, struct siphash *state);
 int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
 extern const struct hash_ops id128_hash_ops;
+
+sd_id128_t id128_make_v4_uuid(sd_id128_t id);
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index c0d0fe81..d585ff25 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -255,19 +255,6 @@ _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
         return 0;
 }
 
-static sd_id128_t make_v4_uuid(sd_id128_t id) {
-        /* Stolen from generate_random_uuid() of drivers/char/random.c
-         * in the kernel sources */
-
-        /* Set UUID version to 4 --- truly random generation */
-        id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
-
-        /* Set the UUID variant to DCE */
-        id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
-
-        return id;
-}
-
 _public_ int sd_id128_randomize(sd_id128_t *ret) {
         sd_id128_t t;
         int r;
@@ -284,7 +271,7 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
          * only guarantee this for newly generated UUIDs, not for
          * pre-existing ones. */
 
-        *ret = make_v4_uuid(t);
+        *ret = id128_make_v4_uuid(t);
         return 0;
 }
 
@@ -311,7 +298,7 @@ static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret)
         /* We chop off the trailing 16 bytes */
         memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
 
-        *ret = make_v4_uuid(result);
+        *ret = id128_make_v4_uuid(result);
         return 0;
 }
 
diff --git a/src/systemd/src/systemd/_sd-common.h b/src/systemd/src/systemd/_sd-common.h
index b3ee7bbc..8158ee73 100644
--- a/src/systemd/src/systemd/_sd-common.h
+++ b/src/systemd/src/systemd/_sd-common.h
@@ -45,6 +45,18 @@ typedef void (*_sd_destroy_t)(void *userdata);
 #  define _sd_pure_ __attribute__((__pure__))
 #endif
 
+/* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6
+ * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway
+ * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers
+ * are probably going to use something newer anyway. */
+#ifndef _sd_deprecated_
+#  if __GNUC__ >= 6
+#    define _sd_deprecated_ __attribute__((__deprecated__))
+#  else
+#    define _sd_deprecated_
+#  endif
+#endif
+
 #ifndef _SD_STRINGIFY
 #  define _SD_XSTRINGIFY(x) #x
 #  define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x)
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index f97e35b6..da2aa6c7 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -83,6 +83,8 @@ enum {
         SD_DHCP_OPTION_REBINDING_T2_TIME           = 59,
         SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER     = 60,
         SD_DHCP_OPTION_CLIENT_IDENTIFIER           = 61,
+        SD_DHCP_OPTION_SMTP_SERVER                 = 69,
+        SD_DHCP_OPTION_POP3_SERVER                 = 70,
         SD_DHCP_OPTION_USER_CLASS                  = 77,
         SD_DHCP_OPTION_FQDN                        = 81,
         SD_DHCP_OPTION_NEW_POSIX_TIMEZONE          = 100,
@@ -90,6 +92,7 @@ enum {
         SD_DHCP_OPTION_DOMAIN_SEARCH_LIST          = 119,
         SD_DHCP_OPTION_SIP_SERVER                  = 120,
         SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE      = 121,
+        SD_DHCP_OPTION_MUD_URL                     = 161,
         SD_DHCP_OPTION_PRIVATE_BASE                = 224,
        /* Windows 10 option to send when Anonymize=true */
         SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249,
@@ -169,6 +172,9 @@ int sd_dhcp_client_set_hostname(
 int sd_dhcp_client_set_vendor_class_identifier(
                 sd_dhcp_client *client,
                 const char *vci);
+int sd_dhcp_client_set_mud_url(
+                sd_dhcp_client *client,
+                const char *mudurl);
 int sd_dhcp_client_set_user_class(
                 sd_dhcp_client *client,
                 const char* const *user_class);
@@ -179,11 +185,13 @@ int sd_dhcp_client_set_service_type(
                 sd_dhcp_client *client,
                 int type);
 
-int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v);
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v);
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);
 
 int sd_dhcp_client_stop(sd_dhcp_client *client);
 int sd_dhcp_client_start(sd_dhcp_client *client);
 int sd_dhcp_client_send_release(sd_dhcp_client *client);
+int sd_dhcp_client_send_decline(sd_dhcp_client *client);
 int sd_dhcp_client_send_renew(sd_dhcp_client *client);
 
 sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index b80d607f..1ed5bf27 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -45,6 +45,8 @@ int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *ad
 int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_pop3_server(sd_dhcp_lease *lease, const struct in_addr **addr);
+int sd_dhcp_lease_get_smtp_server(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu);
 int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname);
 int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains);
diff --git a/src/systemd/src/systemd/sd-dhcp6-client.h b/src/systemd/src/systemd/sd-dhcp6-client.h
index be34d43e..42d4ec75 100644
--- a/src/systemd/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/src/systemd/sd-dhcp6-client.h
@@ -73,6 +73,7 @@ enum {
         SD_DHCP6_OPTION_FQDN                       = 39,  /* RFC 4704 */
 
         SD_DHCP6_OPTION_NTP_SERVER                 = 56,  /* RFC 5908 */
+        SD_DHCP6_OPTION_MUD_URL                    = 112, /* RFC 8250 */
 
         /* option codes 89-142 are unassigned */
         /* option codes 144-65535 are unassigned */
@@ -120,6 +121,9 @@ int sd_dhcp6_client_get_information_request(
 int sd_dhcp6_client_set_request_option(
                 sd_dhcp6_client *client,
                 uint16_t option);
+int sd_dhcp6_client_set_request_mud_url(
+                sd_dhcp6_client *client,
+                char *mudurl);
 int sd_dhcp6_client_set_prefix_delegation_hint(
                 sd_dhcp6_client *client,
                 uint8_t prefixlen,
diff --git a/src/systemd/src/systemd/sd-event.h b/src/systemd/src/systemd/sd-event.h
index b14c9269..2ec726a8 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/systemd/src/systemd/sd-event.h
@@ -23,6 +23,7 @@
 #include <sys/inotify.h>
 #include <sys/signalfd.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <time.h>
 
 #include "_sd-common.h"
@@ -89,6 +90,7 @@ int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, s
 int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata);
 int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata);
 int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata);
+int sd_event_add_child_pidfd(sd_event *e, sd_event_source **s, int pidfd, int options, sd_event_child_handler_t callback, void *userdata);
 int sd_event_add_inotify(sd_event *e, sd_event_source **s, const char *path, uint32_t mask, sd_event_inotify_handler_t callback, void *userdata);
 int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
 int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
@@ -141,6 +143,16 @@ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec);
 int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock);
 int sd_event_source_get_signal(sd_event_source *s);
 int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid);
+int sd_event_source_get_child_pidfd(sd_event_source *s);
+int sd_event_source_get_child_pidfd_own(sd_event_source *s);
+int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own);
+int sd_event_source_get_child_process_own(sd_event_source *s);
+int sd_event_source_set_child_process_own(sd_event_source *s, int own);
+#if defined _GNU_SOURCE || (defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 199309L)
+int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags);
+#else
+int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags);
+#endif
 int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret);
 int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback);
 int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret);
diff --git a/src/systemd/src/systemd/sd-ipv4acd.h b/src/systemd/src/systemd/sd-ipv4acd.h
index 039ed3c7..ebf723fc 100644
--- a/src/systemd/src/systemd/sd-ipv4acd.h
+++ b/src/systemd/src/systemd/sd-ipv4acd.h
@@ -20,6 +20,7 @@
 
 #include <net/ethernet.h>
 #include <netinet/in.h>
+#include <stdbool.h>
 
 #include "sd-event.h"
 
@@ -44,7 +45,7 @@ int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr);
 int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index);
 int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address);
 int sd_ipv4acd_is_running(sd_ipv4acd *acd);
-int sd_ipv4acd_start(sd_ipv4acd *acd);
+int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts);
 int sd_ipv4acd_stop(sd_ipv4acd *acd);
 sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd);
 sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd);
diff --git a/src/tests/config/meson.build b/src/tests/config/meson.build
index e3ebfa19..cfe9c615 100644
--- a/src/tests/config/meson.build
+++ b/src/tests/config/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 test_config_dir = meson.current_source_dir()
 
 test_unit = 'test-config'
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index 1c28e976..e6c04efd 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -978,7 +978,7 @@ test_config_enable (void)
 	guint match_nm_version = _nm_config_match_nm_version;
 	char *match_env = g_strdup (_nm_config_match_env);
 
-	g_clear_pointer (&_nm_config_match_env, g_free);
+	nm_clear_g_free (&_nm_config_match_env);
 	_nm_config_match_env = g_strdup ("something-else");
 
 	_nm_config_match_nm_version = nm_encode_version (1, 3, 4);
@@ -992,14 +992,14 @@ test_config_enable (void)
 	g_clear_object (&config);
 
 	_nm_config_match_nm_version = nm_encode_version (1, 5, 3);
-	g_clear_pointer (&_nm_config_match_env, g_free);
+	nm_clear_g_free (&_nm_config_match_env);
 	_nm_config_match_env = g_strdup ("test-match-env-1");
 	config = setup_config (NULL, TEST_DIR "/NetworkManager.conf", "", NULL, TEST_DIR "/conf.d", "", NULL);
 	assert_config_value (nm_config_get_data_orig (config), "test-group-config-enable-1", "key1", "enabled");
 	g_clear_object (&config);
 
 	_nm_config_match_nm_version = match_nm_version;
-	g_clear_pointer (&_nm_config_match_env, g_free);
+	nm_clear_g_free (&_nm_config_match_env);
 	_nm_config_match_env = match_env;
 }
 
diff --git a/src/tests/meson.build b/src/tests/meson.build
index ac877e18..df8d1cf8 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -1,3 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
 subdir('config')
 
 test_units = [
diff --git a/src/tests/test-core-with-expect.c b/src/tests/test-core-with-expect.c
index 74e1043b..555b9624 100644
--- a/src/tests/test-core-with-expect.c
+++ b/src/tests/test-core-with-expect.c
@@ -20,7 +20,7 @@
 static void
 test_nm_utils_monotonic_timestamp_as_boottime (void)
 {
-	gint64 timestamp_ns_per_tick, now, now_boottime, now_boottime_2, now_boottime_3;
+	gint64 timestamp_nsec_per_tick, now, now_boottime, now_boottime_2, now_boottime_3;
 	struct timespec tp;
 	clockid_t clockid;
 	guint i;
@@ -35,22 +35,22 @@ test_nm_utils_monotonic_timestamp_as_boottime (void)
 
 		if (clock_gettime (clockid, &tp) != 0)
 			g_assert_not_reached ();
-		now_boottime = ( ((gint64) tp.tv_sec) * NM_UTILS_NS_PER_SECOND ) + ((gint64) tp.tv_nsec);
+		now_boottime = ( ((gint64) tp.tv_sec) * NM_UTILS_NSEC_PER_SEC ) + ((gint64) tp.tv_nsec);
 
-		now = nm_utils_get_monotonic_timestamp_ns ();
+		now = nm_utils_get_monotonic_timestamp_nsec ();
 
 		now_boottime_2 = nm_utils_monotonic_timestamp_as_boottime (now, 1);
 		g_assert_cmpint (now_boottime_2, >=, 0);
 		g_assert_cmpint (now_boottime_2, >=, now_boottime);
-		g_assert_cmpint (now_boottime_2 - now_boottime, <=, NM_UTILS_NS_PER_SECOND / 10);
+		g_assert_cmpint (now_boottime_2 - now_boottime, <=, NM_UTILS_NSEC_PER_SEC / 10);
 
 		g_assert_cmpint (now, ==, nm_utils_monotonic_timestamp_from_boottime (now_boottime_2, 1));
 
-		for (timestamp_ns_per_tick = 1; timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND; timestamp_ns_per_tick *= 10) {
-			now_boottime_3 = nm_utils_monotonic_timestamp_as_boottime (now / timestamp_ns_per_tick, timestamp_ns_per_tick);
+		for (timestamp_nsec_per_tick = 1; timestamp_nsec_per_tick <= NM_UTILS_NSEC_PER_SEC; timestamp_nsec_per_tick *= 10) {
+			now_boottime_3 = nm_utils_monotonic_timestamp_as_boottime (now / timestamp_nsec_per_tick, timestamp_nsec_per_tick);
 
-			g_assert_cmpint (now_boottime_2 / timestamp_ns_per_tick, ==, now_boottime_3);
-			g_assert_cmpint (now / timestamp_ns_per_tick, ==, nm_utils_monotonic_timestamp_from_boottime (now_boottime_3, timestamp_ns_per_tick));
+			g_assert_cmpint (now_boottime_2 / timestamp_nsec_per_tick, ==, now_boottime_3);
+			g_assert_cmpint (now / timestamp_nsec_per_tick, ==, nm_utils_monotonic_timestamp_from_boottime (now_boottime_3, timestamp_nsec_per_tick));
 		}
 	}
 }
diff --git a/src/tests/test-core.c b/src/tests/test-core.c
index 590f7c36..b90a8fac 100644
--- a/src/tests/test-core.c
+++ b/src/tests/test-core.c
@@ -1019,7 +1019,7 @@ _test_connection_sort_autoconnect_priority_one (NMConnection **list, gboolean sh
 	if (shuffle) {
 		for (i = count - 1; i > 0; i--) {
 			j = g_rand_int (nmtst_get_rand ()) % (i + 1);
-			NMTST_SWAP (connections->pdata[i], connections->pdata[j]);
+			NM_SWAP (connections->pdata[i], connections->pdata[j]);
 		}
 	}
 
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index 401d1eee..efc32983 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -246,7 +246,7 @@ test_nm_ip6_config_addresses_sort_check (NMIP6Config *config, NMSettingIP6Config
 		for (i = 0; i < addr_count; i++) {
 			int j = g_rand_int_range (nmtst_get_rand (), i, addr_count);
 
-			NMTST_SWAP (idx[i], idx[j]);
+			NM_SWAP (idx[i], idx[j]);
 			nm_ip6_config_add_address (copy, _nmtst_ip6_config_get_address (config, idx[i]));
 		}
 
diff --git a/src/tests/test-systemd.c b/src/tests/test-systemd.c
index faed0e56..6425c294 100644
--- a/src/tests/test-systemd.c
+++ b/src/tests/test-systemd.c
@@ -27,7 +27,7 @@ _nm_utils_set_testing (NMUtilsTestFlags flags)
 }
 
 gint32
-nm_utils_get_monotonic_timestamp_s (void)
+nm_utils_get_monotonic_timestamp_sec (void)
 {
 	return 1;
 }
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index 974009f9..35bf4afb 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -141,7 +141,7 @@ struct _NMVpnConnectionClass {
 
 G_DEFINE_TYPE (NMVpnConnection, nm_vpn_connection, NM_TYPE_ACTIVE_CONNECTION)
 
-#define NM_VPN_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMVpnConnection, NM_IS_VPN_CONNECTION)
+#define NM_VPN_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMVpnConnection, NM_IS_VPN_CONNECTION, NMActiveConnection)
 
 /*****************************************************************************/
 
@@ -872,7 +872,8 @@ nm_vpn_connection_get_service (NMVpnConnection *self)
 	return nm_setting_vpn_get_service_type (s_vpn);
 }
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_plugin_failure_to_string, NMVpnPluginFailure,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_vpn_plugin_failure_to_string, NMVpnPluginFailure,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED,   "login-failed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_PLUGIN_FAILURE_CONNECT_FAILED, "connect-failed"),
@@ -901,7 +902,8 @@ plugin_failed (NMVpnConnection *self, guint reason)
 	}
 }
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_service_state_to_string, NMVpnServiceState,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_vpn_service_state_to_string, NMVpnServiceState,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_SERVICE_STATE_UNKNOWN,  "unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_VPN_SERVICE_STATE_INIT,     "init"),
@@ -914,7 +916,8 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_service_state_to_string, NMVpnServiceSta
 
 #define vpn_service_state_to_string_a(state) NM_UTILS_LOOKUP_STR_A (_vpn_service_state_to_string, state)
 
-NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_vpn_state_to_string, VpnState,
+static
+NM_UTILS_LOOKUP_STR_DEFINE (_vpn_state_to_string, VpnState,
 	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_STR_ITEM (STATE_UNKNOWN,       "unknown"),
 	NM_UTILS_LOOKUP_STR_ITEM (STATE_WAITING,       "waiting"),
@@ -984,10 +987,10 @@ print_vpn_config (NMVpnConnection *self)
 
 	if (priv->ip4_external_gw) {
 		_LOGI ("Data: VPN Gateway: %s",
-		       nm_utils_inet4_ntop (priv->ip4_external_gw, b1));
+		       _nm_utils_inet4_ntop (priv->ip4_external_gw, b1));
 	} else if (priv->ip6_external_gw) {
 		_LOGI ("Data: VPN Gateway: %s",
-		       nm_utils_inet6_ntop (priv->ip6_external_gw, b1));
+		       _nm_utils_inet6_ntop (priv->ip6_external_gw, b1));
 	}
 
 	_LOGI ("Data: Tunnel Device: %s%s%s", NM_PRINT_FMT_QUOTE_STRING (priv->ip_iface));
@@ -1001,22 +1004,22 @@ print_vpn_config (NMVpnConnection *self)
 		nm_assert (address4);
 
 		if (priv->ip4_internal_gw)
-			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet4_ntop (priv->ip4_internal_gw, b1));
-		_LOGI ("Data:   Internal Address: %s", address4 ? nm_utils_inet4_ntop (address4->address, b1) : "??");
+			_LOGI ("Data:   Internal Gateway: %s", _nm_utils_inet4_ntop (priv->ip4_internal_gw, b1));
+		_LOGI ("Data:   Internal Address: %s", address4 ? _nm_utils_inet4_ntop (address4->address, b1) : "??");
 		_LOGI ("Data:   Internal Prefix: %d", address4 ? (int) address4->plen : -1);
-		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet4_ntop (address4->peer_address, b1));
+		_LOGI ("Data:   Internal Point-to-Point Address: %s", _nm_utils_inet4_ntop (address4->peer_address, b1));
 
 		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->ip4_config, &route) {
 			_LOGI ("Data:   Static Route: %s/%d   Next Hop: %s",
-			       nm_utils_inet4_ntop (route->network, b1),
+			       _nm_utils_inet4_ntop (route->network, b1),
 			       route->plen,
-			       nm_utils_inet4_ntop (route->gateway, b2));
+			       _nm_utils_inet4_ntop (route->gateway, b2));
 		}
 
 		num = nm_ip4_config_get_num_nameservers (priv->ip4_config);
 		for (i = 0; i < num; i++) {
 			_LOGI ("Data:   Internal DNS: %s",
-			       nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (priv->ip4_config, i), b1));
+			       _nm_utils_inet4_ntop (nm_ip4_config_get_nameserver (priv->ip4_config, i), b1));
 		}
 
 		if (nm_ip4_config_get_num_domains (priv->ip4_config) > 0)
@@ -1035,22 +1038,22 @@ print_vpn_config (NMVpnConnection *self)
 		nm_assert (address6);
 
 		if (priv->ip6_internal_gw)
-			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet6_ntop (priv->ip6_internal_gw, b1));
-		_LOGI ("Data:   Internal Address: %s", nm_utils_inet6_ntop (&address6->address, b1));
+			_LOGI ("Data:   Internal Gateway: %s", _nm_utils_inet6_ntop (priv->ip6_internal_gw, b1));
+		_LOGI ("Data:   Internal Address: %s", _nm_utils_inet6_ntop (&address6->address, b1));
 		_LOGI ("Data:   Internal Prefix: %d", address6->plen);
-		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet6_ntop (&address6->peer_address, b1));
+		_LOGI ("Data:   Internal Point-to-Point Address: %s", _nm_utils_inet6_ntop (&address6->peer_address, b1));
 
 		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->ip6_config, &route) {
 			_LOGI ("Data:   Static Route: %s/%d   Next Hop: %s",
-			       nm_utils_inet6_ntop (&route->network, b1),
+			       _nm_utils_inet6_ntop (&route->network, b1),
 			       route->plen,
-			       nm_utils_inet6_ntop (&route->gateway, b2));
+			       _nm_utils_inet6_ntop (&route->gateway, b2));
 		}
 
 		num = nm_ip6_config_get_num_nameservers (priv->ip6_config);
 		for (i = 0; i < num; i++) {
 			_LOGI ("Data:   Internal DNS: %s",
-			       nm_utils_inet6_ntop (nm_ip6_config_get_nameserver (priv->ip6_config, i), b1));
+			       _nm_utils_inet6_ntop (nm_ip6_config_get_nameserver (priv->ip6_config, i), b1));
 		}
 
 		if (nm_ip6_config_get_num_domains (priv->ip6_config) > 0)
@@ -1194,7 +1197,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;
 
 	if (error) {
@@ -1300,7 +1303,7 @@ process_generic_config (NMVpnConnection *self, GVariant *dict)
 		priv->service_can_persist = TRUE;
 	}
 
-	g_clear_pointer (&priv->ip_iface, g_free);
+	nm_clear_g_free (&priv->ip_iface);
 	priv->ip_ifindex = 0;
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_CONFIG_TUNDEV, "&s", &str)) {
@@ -1318,14 +1321,14 @@ process_generic_config (NMVpnConnection *self, GVariant *dict)
 		}
 		if (priv->ip_ifindex <= 0) {
 			_LOGE ("failed to look up VPN interface index for \"%s\"", priv->ip_iface);
-			g_clear_pointer (&priv->ip_iface, g_free);
+			nm_clear_g_free (&priv->ip_iface);
 			priv->ip_ifindex = 0;
 			nm_vpn_connection_config_maybe_complete (self, FALSE);
 			return FALSE;
 		}
 	}
 
-	g_clear_pointer (&priv->banner, g_free);
+	nm_clear_g_free (&priv->banner);
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_CONFIG_BANNER, "&s", &str)) {
 		priv->banner = g_strdup (str);
 		_notify (self, PROP_BANNER);
@@ -1347,7 +1350,7 @@ process_generic_config (NMVpnConnection *self, GVariant *dict)
 
 	/* External world-visible address of the VPN server */
 	priv->ip4_external_gw = 0;
-	g_clear_pointer (&priv->ip6_external_gw, g_free);
+	nm_clear_g_free (&priv->ip6_external_gw);
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY, "u", &u32)) {
 		priv->ip4_external_gw = u32;
@@ -1633,7 +1636,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 		nm_ip4_config_add_route (config, &r, NULL);
 	}
 
-	g_clear_pointer (&priv->ip4_dev_route_blacklist, g_ptr_array_unref);
+	nm_clear_pointer (&priv->ip4_dev_route_blacklist, g_ptr_array_unref);
 
 	nm_ip4_config_add_dependent_routes (config,
 	                                    route_table,
@@ -1699,7 +1702,7 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 	address.plen = 128;
 
 	/* Internal address of the VPN subnet's gateway */
-	g_clear_pointer (&priv->ip6_internal_gw, g_free);
+	nm_clear_g_free (&priv->ip6_internal_gw);
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY, "@ay", &v)) {
 		priv->ip6_internal_gw = ip6_addr_dup_from_variant (v);
 		g_variant_unref (v);
@@ -1880,7 +1883,7 @@ connect_success (NMVpnConnection *self)
 	}
 	priv->connect_timeout = g_timeout_add_seconds (timeout, connect_timeout_cb, self);
 
-	g_clear_pointer (&priv->connect_hash, g_variant_unref);
+	nm_clear_pointer (&priv->connect_hash, g_variant_unref);
 }
 
 static void
@@ -1977,7 +1980,7 @@ really_activate (NMVpnConnection *self, const char *username)
 	priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	g_return_if_fail (priv->vpn_state == STATE_NEED_AUTH);
 
-	g_clear_pointer (&priv->connect_hash, g_variant_unref);
+	nm_clear_pointer (&priv->connect_hash, g_variant_unref);
 	priv->connect_hash = _hash_with_username (_get_applied_connection (self), username);
 	g_variant_ref_sink (priv->connect_hash);
 
@@ -2714,7 +2717,7 @@ device_changed (NMActiveConnection *active,
                 NMDevice *new_device,
                 NMDevice *old_device)
 {
-	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE ((NMVpnConnection *) active);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (active);
 
 	if (!_service_and_connection_can_persist (NM_VPN_CONNECTION (active)))
 		return;
@@ -2761,9 +2764,9 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&priv->start_timeout);
 
-	g_clear_pointer (&priv->connect_hash, g_variant_unref);
+	nm_clear_pointer (&priv->connect_hash, g_variant_unref);
 
-	g_clear_pointer (&priv->ip4_dev_route_blacklist, g_ptr_array_unref);
+	nm_clear_pointer (&priv->ip4_dev_route_blacklist, g_ptr_array_unref);
 
 	nm_clear_g_source (&priv->connect_timeout);
 
@@ -2789,7 +2792,7 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE ((NMVpnConnection *) object);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
 
 	g_free (priv->banner);
 	g_free (priv->ip_iface);
@@ -2812,7 +2815,7 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE ((NMVpnConnection *) object);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
 	NMDevice *parent_dev;
 
 	switch (prop_id) {
diff --git a/src/vpn/nm-vpn-connection.h b/src/vpn/nm-vpn-connection.h
index 43242a8e..b2d3e370 100644
--- a/src/vpn/nm-vpn-connection.h
+++ b/src/vpn/nm-vpn-connection.h
@@ -9,7 +9,7 @@
 
 #include "nm-vpn-dbus-interface.h"
 #include "devices/nm-device.h"
-#include "nm-auth-subject.h"
+#include "nm-libnm-core-intern/nm-auth-subject.h"
 #include "nm-active-connection.h"
 #include "nm-vpn-plugin-info.h"
 
diff --git a/src/vpn/nm-vpn-manager.c b/src/vpn/nm-vpn-manager.c
index 4c94a260..a80ab71e 100644
--- a/src/vpn/nm-vpn-manager.c
+++ b/src/vpn/nm-vpn-manager.c
@@ -244,7 +244,7 @@ nm_vpn_manager_init (NMVpnManager *self)
 static void
 dispose (GObject *object)
 {
-	NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE ((NMVpnManager *) object);
+	NMVpnManagerPrivate *priv = NM_VPN_MANAGER_GET_PRIVATE (object);
 
 	if (priv->monitor_etc) {
 		if (priv->monitor_id_etc)