summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-09-08 17:44:06 +0200
committerMichael Biebl <biebl@debian.org>2018-09-08 17:44:06 +0200
commit8f7a3cbbdd0c0a48277c341dd3a8ec8743ae9735 (patch)
tree4353551fcb59cc822c3cadf2f4888f70601e8fbf /src
parentcaf1db9d6fbc056cc6c76a24574890f6c7895f3d (diff)
New upstream version 1.13.90 upstream/1.13.90
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c4
-rw-r--r--src/devices/adsl/meson.build6
-rw-r--r--src/devices/adsl/nm-device-adsl.c32
-rw-r--r--src/devices/bluetooth/meson.build6
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c73
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c4
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c8
-rw-r--r--src/devices/bluetooth/nm-device-bt.c52
-rw-r--r--src/devices/nm-device-6lowpan.c345
-rw-r--r--src/devices/nm-device-6lowpan.h37
-rw-r--r--src/devices/nm-device-bond.c67
-rw-r--r--src/devices/nm-device-bridge.c96
-rw-r--r--src/devices/nm-device-dummy.c23
-rw-r--r--src/devices/nm-device-ethernet-utils.c25
-rw-r--r--src/devices/nm-device-ethernet-utils.h2
-rw-r--r--src/devices/nm-device-ethernet.c130
-rw-r--r--src/devices/nm-device-factory.c5
-rw-r--r--src/devices/nm-device-generic.c30
-rw-r--r--src/devices/nm-device-infiniband.c48
-rw-r--r--src/devices/nm-device-ip-tunnel.c247
-rw-r--r--src/devices/nm-device-macsec.c44
-rw-r--r--src/devices/nm-device-macvlan.c70
-rw-r--r--src/devices/nm-device-ppp.c37
-rw-r--r--src/devices/nm-device-private.h27
-rw-r--r--src/devices/nm-device-tun.c50
-rw-r--r--src/devices/nm-device-veth.c5
-rw-r--r--src/devices/nm-device-vlan.c96
-rw-r--r--src/devices/nm-device-vxlan.c117
-rw-r--r--src/devices/nm-device-wireguard.c212
-rw-r--r--src/devices/nm-device-wireguard.h41
-rw-r--r--src/devices/nm-device-wpan.c253
-rw-r--r--src/devices/nm-device-wpan.h35
-rw-r--r--src/devices/nm-device.c999
-rw-r--r--src/devices/nm-device.h30
-rw-r--r--src/devices/ovs/meson.build7
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c21
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c26
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c26
-rw-r--r--src/devices/ovs/nm-ovsdb.c2
-rw-r--r--src/devices/team/meson.build6
-rw-r--r--src/devices/team/nm-device-team.c75
-rw-r--r--src/devices/wifi/meson.build6
-rw-r--r--src/devices/wifi/nm-device-iwd.c425
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c54
-rw-r--r--src/devices/wifi/nm-device-wifi.c419
-rw-r--r--src/devices/wifi/nm-iwd-manager.c540
-rw-r--r--src/devices/wifi/nm-iwd-manager.h17
-rw-r--r--src/devices/wifi/nm-wifi-ap.c122
-rw-r--r--src/devices/wifi/nm-wifi-ap.h8
-rw-r--r--src/devices/wifi/nm-wifi-common.c2
-rw-r--r--src/devices/wifi/nm-wifi-utils.c56
-rw-r--r--src/devices/wifi/nm-wifi-utils.h16
-rw-r--r--src/devices/wifi/tests/test-general.c34
-rw-r--r--src/devices/wwan/meson.build6
-rw-r--r--src/devices/wwan/nm-device-modem.c47
-rw-r--r--src/devices/wwan/nm-modem-broadband.c52
-rw-r--r--src/devices/wwan/nm-modem-manager.c14
-rw-r--r--src/devices/wwan/nm-modem-ofono.c47
-rw-r--r--src/devices/wwan/nm-modem.c52
-rw-r--r--src/devices/wwan/nm-modem.h11
-rw-r--r--src/dhcp/meson.build1
-rw-r--r--src/dhcp/nm-dhcp-client.c37
-rw-r--r--src/dhcp/nm-dhcp-client.h6
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c30
-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-helper.c2
-rw-r--r--src/dhcp/nm-dhcp-utils.c2
-rw-r--r--src/dhcp/tests/meson.build1
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c135
-rw-r--r--src/dns/nm-dns-dnsmasq.c2
-rw-r--r--src/dns/nm-dns-manager.c10
-rw-r--r--src/dns/nm-dns-plugin.c4
-rw-r--r--src/dns/nm-dns-plugin.h2
-rw-r--r--src/dns/nm-dns-systemd-resolved.c28
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c11
-rw-r--r--src/main-utils.c2
-rw-r--r--src/main.c3
-rw-r--r--src/meson.build50
-rw-r--r--src/ndisc/nm-ndisc.c2
-rw-r--r--src/nm-active-connection.c78
-rw-r--r--src/nm-auth-subject.c4
-rw-r--r--src/nm-checkpoint-manager.c4
-rw-r--r--src/nm-checkpoint.c25
-rw-r--r--src/nm-config-data.c36
-rw-r--r--src/nm-config-data.h13
-rw-r--r--src/nm-config.c14
-rw-r--r--src/nm-config.h8
-rw-r--r--src/nm-connectivity.c2
-rw-r--r--src/nm-core-utils.c425
-rw-r--r--src/nm-core-utils.h26
-rw-r--r--src/nm-dbus-manager.c2
-rw-r--r--src/nm-dbus-manager.h2
-rw-r--r--src/nm-dcb.c2
-rw-r--r--src/nm-hostname-manager.c7
-rw-r--r--src/nm-ip4-config.c199
-rw-r--r--src/nm-ip4-config.h17
-rw-r--r--src/nm-ip6-config.c49
-rw-r--r--src/nm-ip6-config.h6
-rw-r--r--src/nm-logging.c8
-rw-r--r--src/nm-logging.h6
-rw-r--r--src/nm-manager.c875
-rw-r--r--src/nm-manager.h8
-rw-r--r--src/nm-policy.c417
-rw-r--r--src/nm-policy.h16
-rw-r--r--src/nm-rfkill-manager.c4
-rw-r--r--src/nm-session-monitor.c36
-rw-r--r--src/nm-sleep-monitor.c2
-rw-r--r--src/nm-test-utils-core.h8
-rw-r--r--src/nm-types.h11
-rw-r--r--src/platform/linux/nl802154.h452
-rw-r--r--src/platform/nm-fake-platform.c14
-rw-r--r--src/platform/nm-linux-platform.c1628
-rw-r--r--src/platform/nm-netlink.c146
-rw-r--r--src/platform/nm-netlink.h22
-rw-r--r--src/platform/nm-platform-utils.c737
-rw-r--r--src/platform/nm-platform-utils.h56
-rw-r--r--src/platform/nm-platform.c521
-rw-r--r--src/platform/nm-platform.h106
-rw-r--r--src/platform/nmp-object.c351
-rw-r--r--src/platform/nmp-object.h51
-rw-r--r--src/platform/tests/test-cleanup.c2
-rw-r--r--src/platform/tests/test-common.c65
-rw-r--r--src/platform/tests/test-common.h6
-rw-r--r--src/platform/tests/test-general.c4
-rw-r--r--src/platform/tests/test-link.c243
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c (renamed from src/platform/wifi/wifi-utils-nl80211.c)252
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.h (renamed from src/platform/wifi/wifi-utils-nl80211.h)15
-rw-r--r--src/platform/wifi/nm-wifi-utils-private.h (renamed from src/platform/wifi/wifi-utils-private.h)48
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.c (renamed from src/platform/wifi/wifi-utils-wext.c)139
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.h (renamed from src/platform/wifi/wifi-utils-wext.h)17
-rw-r--r--src/platform/wifi/nm-wifi-utils.c240
-rw-r--r--src/platform/wifi/nm-wifi-utils.h84
-rw-r--r--src/platform/wifi/wifi-utils.c231
-rw-r--r--src/platform/wifi/wifi-utils.h79
-rw-r--r--src/platform/wpan/nm-wpan-utils.c286
-rw-r--r--src/platform/wpan/nm-wpan-utils.h47
-rw-r--r--src/ppp/meson.build1
-rw-r--r--src/ppp/nm-ppp-manager.c6
-rw-r--r--src/settings/nm-agent-manager.c20
-rw-r--r--src/settings/nm-secret-agent.c8
-rw-r--r--src/settings/nm-settings-connection.c233
-rw-r--r--src/settings/nm-settings-connection.h12
-rw-r--r--src/settings/nm-settings-plugin.c183
-rw-r--r--src/settings/nm-settings-plugin.h81
-rw-r--r--src/settings/nm-settings.c276
-rw-r--r--src/settings/nm-settings.h2
-rw-r--r--src/settings/plugins/ibft/meson.build7
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c35
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c2
-rw-r--r--src/settings/plugins/ibft/tests/meson.build1
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build9
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.c4
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c6
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c91
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c1233
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c101
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h21
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c355
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c125
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h24
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/meson.build1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected11
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov19
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected18
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected15
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c243
-rw-r--r--src/settings/plugins/ifupdown/meson.build4
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-connection.c33
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c303
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h50
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c452
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c453
-rw-r--r--src/settings/plugins/ifupdown/tests/meson.build1
-rw-r--r--src/settings/plugins/ifupdown/tests/test-ifupdown.c255
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c12
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c58
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c8
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.h4
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c11
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build1
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c55
-rw-r--r--src/supplicant/nm-supplicant-config.c51
-rw-r--r--src/supplicant/nm-supplicant-interface.c20
-rw-r--r--src/supplicant/nm-supplicant-interface.h4
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c2
-rw-r--r--src/supplicant/tests/meson.build1
-rw-r--r--src/systemd/meson.build1
-rw-r--r--src/systemd/nm-sd.c2
-rw-r--r--src/systemd/sd-adapt/libudev.h5
-rw-r--r--src/systemd/sd-adapt/nm-sd-adapt.h13
-rw-r--r--src/systemd/sd-adapt/sd-device.h (renamed from src/systemd/sd-adapt/env-util.h)0
-rw-r--r--src/systemd/sd-adapt/udev-util.h3
-rw-r--r--src/systemd/sd-adapt/udev.h6
-rw-r--r--src/systemd/src/basic/env-util.c791
-rw-r--r--src/systemd/src/basic/env-util.h50
-rw-r--r--src/systemd/src/basic/ether-addr-util.c3
-rw-r--r--src/systemd/src/basic/ether-addr-util.h4
-rw-r--r--src/systemd/src/basic/fd-util.h8
-rw-r--r--src/systemd/src/basic/fileio.c40
-rw-r--r--src/systemd/src/basic/fs-util.c67
-rw-r--r--src/systemd/src/basic/fs-util.h4
-rw-r--r--src/systemd/src/basic/hash-funcs.c9
-rw-r--r--src/systemd/src/basic/hash-funcs.h4
-rw-r--r--src/systemd/src/basic/hashmap.c33
-rw-r--r--src/systemd/src/basic/hashmap.h4
-rw-r--r--src/systemd/src/basic/in-addr-util.c23
-rw-r--r--src/systemd/src/basic/in-addr-util.h5
-rw-r--r--src/systemd/src/basic/macro.h121
-rw-r--r--src/systemd/src/basic/mempool.c3
-rw-r--r--src/systemd/src/basic/mempool.h4
-rw-r--r--src/systemd/src/basic/parse-util.c7
-rw-r--r--src/systemd/src/basic/path-util.c7
-rw-r--r--src/systemd/src/basic/path-util.h6
-rw-r--r--src/systemd/src/basic/process-util.c9
-rw-r--r--src/systemd/src/basic/random-util.c61
-rw-r--r--src/systemd/src/basic/random-util.h2
-rw-r--r--src/systemd/src/basic/socket-util.c113
-rw-r--r--src/systemd/src/basic/socket-util.h10
-rw-r--r--src/systemd/src/basic/stat-util.c11
-rw-r--r--src/systemd/src/basic/stat-util.h1
-rw-r--r--src/systemd/src/basic/strv.c4
-rw-r--r--src/systemd/src/basic/time-util.c24
-rw-r--r--src/systemd/src/basic/utf8.c3
-rw-r--r--src/systemd/src/basic/util.c30
-rw-r--r--src/systemd/src/basic/util.h14
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c110
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.h16
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-internal.h1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-packet.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-lease-internal.h1
-rw-r--r--src/systemd/src/libsystemd-network/lldp-internal.h5
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.c23
-rw-r--r--src/systemd/src/libsystemd-network/lldp-network.c4
-rw-r--r--src/systemd/src/libsystemd-network/lldp-network.h5
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c15
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h12
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c59
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c67
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-lease.c1
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c1
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c1
-rw-r--r--src/systemd/src/libsystemd-network/sd-lldp.c4
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c4
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c2
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.h3
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c2
-rw-r--r--src/systemd/src/shared/dns-domain.c4
-rw-r--r--src/systemd/src/shared/dns-domain.h3
-rw-r--r--src/systemd/src/systemd/_sd-common.h1
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h7
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h2
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-client.h3
-rw-r--r--src/systemd/src/systemd/sd-dhcp6-lease.h1
-rw-r--r--src/systemd/src/systemd/sd-event.h1
-rw-r--r--src/systemd/src/systemd/sd-id128.h1
-rw-r--r--src/systemd/src/systemd/sd-ipv4acd.h2
-rw-r--r--src/systemd/src/systemd/sd-lldp.h3
-rw-r--r--src/tests/config/meson.build1
-rw-r--r--src/tests/test-general-with-expect.c4
-rw-r--r--src/tests/test-general.c118
-rw-r--r--src/tests/test-wired-defname.c17
-rw-r--r--src/vpn/nm-vpn-connection.c30
-rw-r--r--src/vpn/nm-vpn-connection.h2
270 files changed, 14041 insertions, 6763 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index a3bd6013..1404854d 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -805,9 +805,11 @@ nm_utils_match_connection (NMConnection *const*connections,
 		return NULL;
 
 	for (; *connections; connections++) {
-		NMConnection *candidate = NM_CONNECTION (*connections);
+		NMConnection *candidate = *connections;
 		GHashTable *diffs = NULL;
 
+		nm_assert (NM_IS_CONNECTION (candidate));
+
 		if (match_filter_func) {
 			if (!match_filter_func (candidate, match_filter_data))
 				continue;
diff --git a/src/devices/adsl/meson.build b/src/devices/adsl/meson.build
index ee804d23..7ac0e123 100644
--- a/src/devices/adsl/meson.build
+++ b/src/devices/adsl/meson.build
@@ -20,10 +20,10 @@ libnm_device_plugin_adsl = shared_module(
 
 core_plugins += libnm_device_plugin_adsl
 
-run_target(
+test(
   'check-local-devices-adsl',
-  command: [check_exports, libnm_device_plugin_adsl.full_path(), linker_script_devices],
-  depends: libnm_device_plugin_adsl
+  check_exports,
+  args: [libnm_device_plugin_adsl.full_path(), linker_script_devices],
 )
 
 # FIXME: check_so_symbols replacement
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index 1450a836..c9984f51 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -87,25 +87,23 @@ get_generic_capabilities (NMDevice *dev)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMSettingAdsl *s_adsl;
 	const char *protocol;
 
-	if (!NM_DEVICE_CLASS (nm_device_adsl_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	if (!nm_connection_is_type (connection, NM_SETTING_ADSL_SETTING_NAME))
+	if (!NM_DEVICE_CLASS (nm_device_adsl_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	s_adsl = nm_connection_get_setting_adsl (connection);
-	if (!s_adsl)
-		return FALSE;
 
-	/* FIXME: we don't yet support IPoATM */
 	protocol = nm_setting_adsl_get_protocol (s_adsl);
-	if (g_strcmp0 (protocol, NM_SETTING_ADSL_PROTOCOL_IPOATM) == 0)
+	if (nm_streq0 (protocol, NM_SETTING_ADSL_PROTOCOL_IPOATM)) {
+		/* FIXME: we don't yet support IPoATM */
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "IPoATM protocol is not yet supported");
 		return FALSE;
+	}
 
 	return TRUE;
 }
@@ -669,7 +667,7 @@ nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructed  = constructed;
 	object_class->dispose      = dispose;
@@ -678,14 +676,16 @@ nm_device_adsl_class_init (NMDeviceAdslClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_adsl);
 
-	parent_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->connection_type_check_compatible = NM_SETTING_ADSL_SETTING_NAME;
+
+	device_class->get_generic_capabilities = get_generic_capabilities;
 
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->complete_connection = complete_connection;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->complete_connection = complete_connection;
 
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	parent_class->deactivate = deactivate;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->deactivate = deactivate;
 
 	obj_properties[PROP_ATM_INDEX] =
 	     g_param_spec_int (NM_DEVICE_ADSL_ATM_INDEX, "", "",
diff --git a/src/devices/bluetooth/meson.build b/src/devices/bluetooth/meson.build
index 30284f9e..019f04f6 100644
--- a/src/devices/bluetooth/meson.build
+++ b/src/devices/bluetooth/meson.build
@@ -31,10 +31,10 @@ libnm_device_plugin_bluetooth = shared_module(
 
 core_plugins += libnm_device_plugin_bluetooth
 
-run_target(
+test(
   'check-local-devices-bluetooth',
-  command: [check_exports, libnm_device_plugin_bluetooth.full_path(), linker_script_devices],
-  depends: libnm_device_plugin_bluetooth
+  check_exports,
+  args: [libnm_device_plugin_bluetooth.full_path(), linker_script_devices],
 )
 
 # FIXME: check_so_symbols replacement
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index d8e40d6f..b69b4320 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -94,7 +94,7 @@ typedef struct {
 	NMSettings *settings;
 	GSList *connections;
 
-	NMConnection *pan_connection;
+	NMSettingsConnection *pan_connection;
 	gboolean pan_connection_no_autocreate;
 } NMBluezDevicePrivate;
 
@@ -114,8 +114,9 @@ G_DEFINE_TYPE (NMBluezDevice, nm_bluez_device, G_TYPE_OBJECT)
 /*****************************************************************************/
 
 static void cp_connection_added (NMSettings *settings,
-                                 NMConnection *connection, NMBluezDevice *self);
-static gboolean connection_compatible (NMBluezDevice *self, NMConnection *connection);
+                                 NMSettingsConnection *sett_conn,
+                                 NMBluezDevice *self);
+static gboolean connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn);
 
 /*****************************************************************************/
 
@@ -181,10 +182,10 @@ nm_bluez_device_get_connected (NMBluezDevice *self)
 static void
 pan_connection_check_create (NMBluezDevice *self)
 {
-	NMConnection *connection;
-	NMConnection *added;
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingsConnection *added;
 	NMSetting *setting;
-	char *id;
+	gs_free char *id = NULL;
 	char uuid[37];
 	GError *error = NULL;
 	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
@@ -246,15 +247,14 @@ pan_connection_check_create (NMBluezDevice *self)
 	 * which then already finds the suitable connection in priv->connections. This is confusing,
 	 * so block the signal. check_emit_usable will succeed after this function call returns. */
 	g_signal_handlers_block_by_func (priv->settings, cp_connection_added, self);
-	added = NM_CONNECTION (nm_settings_add_connection (priv->settings, connection, FALSE, &error));
+	added = nm_settings_add_connection (priv->settings, connection, FALSE, &error);
 	g_signal_handlers_unblock_by_func (priv->settings, cp_connection_added, self);
 
 	if (added) {
-		g_assert (!g_slist_find (priv->connections, added));
-		g_assert (connection_compatible (self, added));
-		g_assert (nm_connection_compare (added, connection, NM_SETTING_COMPARE_FLAG_EXACT));
+		nm_assert (!g_slist_find (priv->connections, added));
+		nm_assert (connection_compatible (self, added));
 
-		nm_settings_connection_set_flags (NM_SETTINGS_CONNECTION (added), NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, TRUE);
+		nm_settings_connection_set_flags (added, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, TRUE);
 
 		priv->connections = g_slist_prepend (priv->connections, g_object_ref (added));
 		priv->pan_connection = added;
@@ -263,11 +263,7 @@ pan_connection_check_create (NMBluezDevice *self)
 		nm_log_warn (LOGD_BT, "bluez[%s] couldn't add new Bluetooth connection for NAP device: '%s' (%s): %s",
 		             priv->path, id, uuid, error->message);
 		g_clear_error (&error);
-
 	}
-	g_object_unref (connection);
-
-	g_free (id);
 }
 
 static gboolean
@@ -321,9 +317,10 @@ check_emit_usable_schedule (NMBluezDevice *self)
 /*****************************************************************************/
 
 static gboolean
-connection_compatible (NMBluezDevice *self, NMConnection *connection)
+connection_compatible (NMBluezDevice *self, NMSettingsConnection *sett_conn)
 {
 	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
+	NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
 	NMSettingBluetooth *s_bt;
 	const char *bt_type;
 	const char *bdaddr;
@@ -361,22 +358,24 @@ connection_compatible (NMBluezDevice *self, NMConnection *connection)
 }
 
 static gboolean
-_internal_track_connection (NMBluezDevice *self, NMConnection *connection, gboolean tracked)
+_internal_track_connection (NMBluezDevice *self,
+                            NMSettingsConnection *sett_conn,
+                            gboolean tracked)
 {
 	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
 	gboolean was_tracked;
 
-	was_tracked = !!g_slist_find (priv->connections, connection);
+	was_tracked = !!g_slist_find (priv->connections, sett_conn);
 	if (was_tracked == !!tracked)
 		return FALSE;
 
 	if (tracked)
-		priv->connections = g_slist_prepend (priv->connections, g_object_ref (connection));
+		priv->connections = g_slist_prepend (priv->connections, g_object_ref (sett_conn));
 	else {
-		priv->connections = g_slist_remove (priv->connections, connection);
-		if (priv->pan_connection == connection)
+		priv->connections = g_slist_remove (priv->connections, sett_conn);
+		if (priv->pan_connection == sett_conn)
 			priv->pan_connection = NULL;
-		g_object_unref (connection);
+		g_object_unref (sett_conn);
 	}
 
 	return TRUE;
@@ -384,32 +383,32 @@ _internal_track_connection (NMBluezDevice *self, NMConnection *connection, gbool
 
 static void
 cp_connection_added (NMSettings *settings,
-                     NMConnection *connection,
+                     NMSettingsConnection *sett_conn,
                      NMBluezDevice *self)
 {
-	if (connection_compatible (self, connection)) {
-		if (_internal_track_connection (self, connection, TRUE))
+	if (connection_compatible (self, sett_conn)) {
+		if (_internal_track_connection (self, sett_conn, TRUE))
 			check_emit_usable (self);
 	}
 }
 
 static void
 cp_connection_removed (NMSettings *settings,
-                       NMConnection *connection,
+                       NMSettingsConnection *sett_conn,
                        NMBluezDevice *self)
 {
-	if (_internal_track_connection (self, connection, FALSE))
+	if (_internal_track_connection (self, sett_conn, FALSE))
 		check_emit_usable (self);
 }
 
 static void
 cp_connection_updated (NMSettings *settings,
-                       NMConnection *connection,
+                       NMSettingsConnection *sett_conn,
                        gboolean by_user,
                        NMBluezDevice *self)
 {
-	if (_internal_track_connection (self, connection,
-	                                connection_compatible (self, connection)))
+	if (_internal_track_connection (self, sett_conn,
+	                                connection_compatible (self, sett_conn)))
 		check_emit_usable_schedule (self);
 }
 
@@ -423,10 +422,8 @@ load_connections (NMBluezDevice *self)
 
 	connections = nm_settings_get_connections (priv->settings, NULL);
 	for (i = 0; connections[i]; i++) {
-		NMConnection *connection = (NMConnection *) connections[i];
-
-		if (connection_compatible (self, connection))
-			changed |= _internal_track_connection (self, connection, TRUE);
+		if (connection_compatible (self, connections[i]))
+			changed |= _internal_track_connection (self, connections[i], TRUE);
 	}
 	if (changed)
 		check_emit_usable (self);
@@ -1178,14 +1175,14 @@ dispose (GObject *object)
 {
 	NMBluezDevice *self = NM_BLUEZ_DEVICE (object);
 	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
-	NMConnection *to_delete = NULL;
+	NMSettingsConnection *to_delete = NULL;
 
 	nm_clear_g_source (&priv->check_emit_usable_id);
 
 	if (priv->pan_connection) {
 		/* Check whether we want to remove the created connection. If so, we take a reference
 		 * and delete it at the end of dispose(). */
-		if (NM_FLAGS_HAS (nm_settings_connection_get_flags (NM_SETTINGS_CONNECTION (priv->pan_connection)),
+		if (NM_FLAGS_HAS (nm_settings_connection_get_flags (priv->pan_connection),
 		                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
 			to_delete = g_object_ref (priv->pan_connection);
 
@@ -1219,8 +1216,8 @@ dispose (GObject *object)
 
 	if (to_delete) {
 		nm_log_dbg (LOGD_BT, "bluez[%s] removing Bluetooth connection for NAP device: '%s' (%s)", priv->path,
-		            nm_connection_get_id (to_delete), nm_connection_get_uuid (to_delete));
-		nm_settings_connection_delete (NM_SETTINGS_CONNECTION (to_delete), NULL);
+		            nm_settings_connection_get_id (to_delete), nm_settings_connection_get_uuid (to_delete));
+		nm_settings_connection_delete (to_delete, NULL);
 		g_object_unref (to_delete);
 	}
 
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index c74ecd81..711f2e57 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -244,8 +244,8 @@ setup_bluez5 (NMBluezManager *self)
 
 static void
 watch_name_on_appeared (GDBusConnection *connection,
-                        const gchar *name,
-                        const gchar *name_owner,
+                        const char *name,
+                        const char *name_owner,
                         gpointer user_data)
 {
 	check_bluez_and_try_setup (NM_BLUEZ_MANAGER (user_data));
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index 5d3bd23a..e984212b 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -224,7 +224,7 @@ network_server_unregister_bridge (const NMBtVTableNetworkServer *vtable,
 }
 
 static void
-network_server_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
+network_server_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
 {
 	NetworkServer *network_server;
 
@@ -241,7 +241,7 @@ network_server_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *s
 }
 
 static void
-network_server_added (GDBusProxy *proxy, const gchar *path, const char *addr, NMBluez5Manager *self)
+network_server_added (GDBusProxy *proxy, const char *path, const char *addr, NMBluez5Manager *self)
 {
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 	NetworkServer *network_server;
@@ -342,7 +342,7 @@ device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *se
 }
 
 static void
-device_added (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
+device_added (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
 {
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 	NMBluezDevice *device;
@@ -356,7 +356,7 @@ device_added (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
 }
 
 static void
-device_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
+device_removed (GDBusProxy *proxy, const char *path, NMBluez5Manager *self)
 {
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 	NMBluezDevice *device;
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 92ad0b31..1f650d33 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -36,6 +36,7 @@
 #include "nm-setting-serial.h"
 #include "nm-setting-ppp.h"
 #include "NetworkManagerUtils.h"
+#include "settings/nm-settings-connection.h"
 #include "nm-utils.h"
 #include "nm-bt-error.h"
 #include "platform/nm-platform.h"
@@ -137,7 +138,7 @@ get_generic_capabilities (NMDevice *device)
 
 static gboolean
 can_auto_connect (NMDevice *device,
-                  NMConnection *connection,
+                  NMSettingsConnection *sett_conn,
                   char **specific_object)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
@@ -145,11 +146,11 @@ can_auto_connect (NMDevice *device,
 
 	nm_assert (!specific_object || !*specific_object);
 
-	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->can_auto_connect (device, connection, NULL))
+	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->can_auto_connect (device, sett_conn, NULL))
 		return FALSE;
 
 	/* Can't auto-activate a DUN connection without ModemManager */
-	bt_type = get_connection_bt_type (connection);
+	bt_type = get_connection_bt_type (nm_settings_connection_get_connection (sett_conn));
 	if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE)
 		return FALSE;
 
@@ -157,36 +158,36 @@ can_auto_connect (NMDevice *device,
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
-	NMSettingConnection *s_con;
 	NMSettingBluetooth *s_bt;
 	const char *bdaddr;
 	guint32 bt_type;
 
-	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->check_connection_compatible (device, connection))
+	if (!NM_DEVICE_CLASS (nm_device_bt_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_BLUETOOTH_SETTING_NAME))
+	bt_type = get_connection_bt_type (connection);
+	if (!NM_FLAGS_ALL (priv->capabilities, bt_type)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "device does not support bluetooth type of profile");
 		return FALSE;
+	}
 
 	s_bt = nm_connection_get_setting_bluetooth (connection);
-	if (!s_bt)
-		return FALSE;
-
-	bt_type = get_connection_bt_type (connection);
-	if (!(bt_type & priv->capabilities))
-		return FALSE;
 
 	bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
-	if (!bdaddr)
+	if (!bdaddr) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "profile lacks bdaddr setting");
 		return FALSE;
-	if (!nm_utils_hwaddr_matches (priv->bdaddr, -1, bdaddr, -1))
+	}
+	if (!nm_utils_hwaddr_matches (priv->bdaddr, -1, bdaddr, -1)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "devices bdaddr setting mismatches");
 		return FALSE;
+	}
 
 	return TRUE;
 }
@@ -195,18 +196,25 @@ static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
+                            const char *specific_object,
+                            GError **error)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) device);
 	guint32 bt_type;
 
 	bt_type = get_connection_bt_type (connection);
-	if (!(bt_type & priv->capabilities))
+	if (!(bt_type & priv->capabilities)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "device does not support bluetooth type");
 		return FALSE;
+	}
 
 	/* DUN connections aren't available without ModemManager */
-	if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE)
+	if (bt_type == NM_BT_CAPABILITY_DUN && priv->mm_running == FALSE) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "ModemManager missing for DUN profile");
 		return FALSE;
+	}
 
 	return TRUE;
 }
@@ -1177,6 +1185,8 @@ nm_device_bt_class_init (NMDeviceBtClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_bluetooth);
 
+	device_class->connection_type_check_compatible = NM_SETTING_BLUETOOTH_SETTING_NAME;
+
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->can_auto_connect = can_auto_connect;
 	device_class->deactivate = deactivate;
diff --git a/src/devices/nm-device-6lowpan.c b/src/devices/nm-device-6lowpan.c
new file mode 100644
index 00000000..600d1b80
--- /dev/null
+++ b/src/devices/nm-device-6lowpan.c
@@ -0,0 +1,345 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-6lowpan.h"
+
+#include "nm-device-private.h"
+#include "settings/nm-settings.h"
+#include "platform/nm-platform.h"
+#include "nm-device-factory.h"
+#include "nm-setting-6lowpan.h"
+#include "nm-utils.h"
+
+#include "nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDevice6Lowpan);
+
+/*****************************************************************************/
+
+typedef struct {
+	gulong parent_state_id;
+} NMDevice6LowpanPrivate;
+
+struct _NMDevice6Lowpan {
+	NMDevice parent;
+	NMDevice6LowpanPrivate _priv;
+};
+
+struct _NMDevice6LowpanClass {
+	NMDeviceClass parent;
+};
+
+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)
+
+/*****************************************************************************/
+
+static void
+parent_state_changed (NMDevice *parent,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason,
+                      gpointer user_data)
+{
+	NMDevice6Lowpan *self = NM_DEVICE_6LOWPAN (user_data);
+
+	nm_device_set_unmanaged_by_flags (NM_DEVICE (self), NM_UNMANAGED_PARENT, !nm_device_get_managed (parent, FALSE), reason);
+}
+
+static void
+parent_changed_notify (NMDevice *device,
+                       int old_ifindex,
+                       NMDevice *old_parent,
+                       int new_ifindex,
+                       NMDevice *new_parent)
+{
+	NMDevice6Lowpan *self = NM_DEVICE_6LOWPAN (device);
+	NMDevice6LowpanPrivate *priv = NM_DEVICE_6LOWPAN_GET_PRIVATE (self);
+
+	NM_DEVICE_CLASS (nm_device_6lowpan_parent_class)->parent_changed_notify (device, old_ifindex, old_parent, new_ifindex, new_parent);
+
+	/*  note that @self doesn't have to clear @parent_state_id on dispose,
+	 *  because NMDevice's dispose() will unset the parent, which in turn calls
+	 *  parent_changed_notify(). */
+	nm_clear_g_signal_handler (old_parent, &priv->parent_state_id);
+
+	if (new_parent) {
+		priv->parent_state_id = g_signal_connect (new_parent,
+		                                          NM_DEVICE_STATE_CHANGED,
+		                                          G_CALLBACK (parent_state_changed),
+		                                          device);
+
+		/* Set parent-dependent unmanaged flag */
+		nm_device_set_unmanaged_by_flags (device,
+		                                  NM_UNMANAGED_PARENT,
+		                                  !nm_device_get_managed (new_parent, FALSE),
+		                                  NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED);
+	}
+
+	if (new_ifindex > 0) {
+		/* Recheck availability now that the parent has changed */
+		nm_device_queue_recheck_available (device,
+		                                   NM_DEVICE_STATE_REASON_PARENT_CHANGED,
+		                                   NM_DEVICE_STATE_REASON_PARENT_CHANGED);
+	}
+}
+
+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);
+	NMPlatformError plerr;
+	NMSetting6Lowpan *s_6lowpan;
+	int parent_ifindex;
+
+	s_6lowpan = nm_connection_get_setting_6lowpan (connection);
+	g_return_val_if_fail (s_6lowpan, FALSE);
+
+	parent_ifindex = parent ? nm_device_get_ifindex (parent) : 0;
+
+	if (parent_ifindex <= 0) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+		             "6LoWPAN devices can not be created without a parent interface");
+		g_return_val_if_fail (!parent, FALSE);
+		return FALSE;
+	}
+
+	plerr = nm_platform_link_6lowpan_add (nm_device_get_platform (device), iface, parent_ifindex, out_plink);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
+		             "Failed to create 6lowpan interface '%s' for '%s': %s",
+		             iface,
+		             nm_connection_get_id (connection),
+		             nm_platform_error_to_string_a (plerr));
+		return FALSE;
+	}
+
+	nm_device_parent_set_ifindex (device, parent_ifindex);
+
+	return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *dev)
+{
+	return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static void
+link_changed (NMDevice *device,
+	      const NMPlatformLink *pllink)
+{
+	NMDevice6Lowpan *self = NM_DEVICE_6LOWPAN (device);
+	int parent = 0;
+	int ifindex;
+
+	NM_DEVICE_CLASS (nm_device_6lowpan_parent_class)->link_changed (device, pllink);
+
+	ifindex = nm_device_get_ifindex (device);
+	if (!nm_platform_link_6lowpan_get_properties (nm_device_get_platform (device), ifindex, &parent)) {
+		_LOGW (LOGD_DEVICE, "could not get 6lowpan properties");
+		return;
+	}
+
+	nm_device_parent_set_ifindex (device, parent);
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	if (!nm_device_parent_get_device (device))
+		return FALSE;
+	return NM_DEVICE_CLASS (nm_device_6lowpan_parent_class)->is_available (device, flags);
+}
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMSetting6Lowpan *s_6lowpan;
+
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_6LOWPAN_SETTING_NAME,
+	                           existing_connections,
+	                           NULL,
+	                           _("6LOWPAN connection"),
+	                           NULL,
+	                           TRUE);
+
+	s_6lowpan = nm_connection_get_setting_6lowpan (connection);
+	if (!s_6lowpan) {
+		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "A '6lowpan' setting is required.");
+		return FALSE;
+	}
+
+	/* If there's no 6LoWPAN interface, no parent, and no hardware address in the
+	 * settings, then there's not enough information to complete the setting.
+	 */
+	if (   !nm_setting_6lowpan_get_parent (s_6lowpan)
+	    && !nm_device_match_parent_hwaddr (device, connection, TRUE)) {
+		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "The '6lowpan' setting had no interface name, parent, or hardware address.");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+update_connection (NMDevice *device, NMConnection *connection)
+{
+	NMSetting6Lowpan *s_6lowpan = nm_connection_get_setting_6lowpan (connection);
+
+	if (!s_6lowpan) {
+		s_6lowpan = (NMSetting6Lowpan *) nm_setting_6lowpan_new ();
+		nm_connection_add_setting (connection, (NMSetting *) s_6lowpan);
+	}
+
+	g_object_set (s_6lowpan,
+	              NM_SETTING_6LOWPAN_PARENT,
+	              nm_device_parent_find_for_connection (device,
+	                                                    nm_setting_6lowpan_get_parent (s_6lowpan)),
+	              NULL);
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
+{
+	NMActStageReturn ret;
+
+	ret = NM_DEVICE_CLASS (nm_device_6lowpan_parent_class)->act_stage1_prepare (dev, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	if (!nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE))
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_6lowpan_init (NMDevice6Lowpan *self)
+{
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_6lowpan = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_6LOWPAN,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("Parent", "o", NM_DEVICE_PARENT),
+		),
+	),
+};
+
+static void
+nm_device_6lowpan_class_init (NMDevice6LowpanClass *klass)
+{
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_6lowpan);
+
+	device_class->connection_type_supported = NM_SETTING_6LOWPAN_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_6LOWPAN_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_6LOWPAN);
+
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->complete_connection = complete_connection;
+	device_class->create_and_realize = create_and_realize;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
+	device_class->link_changed = link_changed;
+	device_class->is_available = is_available;
+	device_class->parent_changed_notify = parent_changed_notify;
+	device_class->update_connection = update_connection;
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_6LOWPAN_DEVICE_FACTORY (nm_6lowpan_device_factory_get_type ())
+#define NM_6LOWPAN_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_6LOWPAN_DEVICE_FACTORY, NM6LowpanDeviceFactory))
+
+static NMDevice *
+create_device (NMDeviceFactory *factory,
+               const char *iface,
+               const NMPlatformLink *plink,
+               NMConnection *connection,
+               gboolean *out_ignore)
+{
+	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_6LOWPAN,
+	                                  NM_DEVICE_IFACE, iface,
+	                                  NM_DEVICE_TYPE_DESC, "6LoWPAN",
+	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_6LOWPAN,
+	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_6LOWPAN,
+	                                  NULL);
+}
+
+static const char *
+get_connection_parent (NMDeviceFactory *factory, NMConnection *connection)
+{
+	NMSetting6Lowpan *s_6lowpan;
+
+	g_return_val_if_fail (nm_connection_is_type (connection, NM_SETTING_6LOWPAN_SETTING_NAME), NULL);
+
+	s_6lowpan = nm_connection_get_setting_6lowpan (connection);
+	g_assert (s_6lowpan);
+
+	return nm_setting_6lowpan_get_parent (s_6lowpan);
+}
+
+static char *
+get_connection_iface (NMDeviceFactory *factory,
+                      NMConnection *connection,
+                      const char *parent_iface)
+{
+	NMSetting6Lowpan *s_6lowpan;
+	const char *ifname;
+
+	g_return_val_if_fail (nm_connection_is_type (connection, NM_SETTING_6LOWPAN_SETTING_NAME), NULL);
+
+	s_6lowpan = nm_connection_get_setting_6lowpan (connection);
+	g_assert (s_6lowpan);
+
+	if (!parent_iface)
+		return NULL;
+
+	ifname = nm_connection_get_interface_name (connection);
+	return g_strdup (ifname);
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL (6LOWPAN, 6Lowpan, 6lowpan,
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_6LOWPAN)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_6LOWPAN_SETTING_NAME),
+	factory_class->create_device = create_device;
+	factory_class->get_connection_parent = get_connection_parent;
+	factory_class->get_connection_iface = get_connection_iface;
+);
diff --git a/src/devices/nm-device-6lowpan.h b/src/devices/nm-device-6lowpan.h
new file mode 100644
index 00000000..86edf4af
--- /dev/null
+++ b/src/devices/nm-device-6lowpan.h
@@ -0,0 +1,37 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_6LOWPAN_H__
+#define __NETWORKMANAGER_DEVICE_6LOWPAN_H__
+
+#include "nm-device.h"
+
+#define NM_TYPE_DEVICE_6LOWPAN            (nm_device_6lowpan_get_type ())
+#define NM_DEVICE_6LOWPAN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_6LOWPAN, NMDevice6Lowpan))
+#define NM_DEVICE_6LOWPAN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_6LOWPAN, NMDevice6LowpanClass))
+#define NM_IS_DEVICE_6LOWPAN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_6LOWPAN))
+#define NM_IS_DEVICE_6LOWPAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_6LOWPAN))
+#define NM_DEVICE_6LOWPAN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_6LOWPAN, NMDevice6LowpanClass))
+
+typedef struct _NMDevice6Lowpan NMDevice6Lowpan;
+typedef struct _NMDevice6LowpanClass NMDevice6LowpanClass;
+
+GType nm_device_6lowpan_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_6LOWPAN_H__ */
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index f59ec9ff..6e7e6ffc 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright 2011 - 2016 Red Hat, Inc.
+ * Copyright 2011 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -56,23 +56,6 @@ get_generic_capabilities (NMDevice *dev)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	NMSettingBond *s_bond;
-
-	if (!NM_DEVICE_CLASS (nm_device_bond_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_bond = nm_connection_get_setting_bond (connection);
-	if (!s_bond || !nm_connection_is_type (connection, NM_SETTING_BOND_SETTING_NAME))
-		return FALSE;
-
-	/* FIXME: match bond properties like mode, etc? */
-
-	return TRUE;
-}
-
-static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
@@ -363,7 +346,6 @@ static NMActStageReturn
 act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
 {
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
-	gboolean no_firmware = FALSE;
 
 	ret = NM_DEVICE_CLASS (nm_device_bond_parent_class)->act_stage1_prepare (dev, out_failure_reason);
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
@@ -374,7 +356,7 @@ act_stage1_prepare (NMDevice *dev, NMDeviceStateReason *out_failure_reason)
 	ret = apply_bonding_config (dev);
 	if (ret != NM_ACT_STAGE_RETURN_FAILURE)
 		ret = nm_device_hw_addr_set_cloned (dev, nm_device_get_applied_connection (dev), FALSE);
-	nm_device_bring_up (dev, TRUE, &no_firmware);
+	nm_device_bring_up (dev, TRUE, NULL);
 
 	return ret;
 }
@@ -386,7 +368,7 @@ enslave_slave (NMDevice *device,
                gboolean configure)
 {
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
-	gboolean success = TRUE, no_firmware = FALSE;
+	gboolean success = TRUE;
 	const char *slave_iface = nm_device_get_ip_iface (slave);
 	NMConnection *master_con;
 
@@ -397,7 +379,7 @@ enslave_slave (NMDevice *device,
 		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, &no_firmware);
+		nm_device_bring_up (slave, TRUE, NULL);
 
 		if (!success)
 			return FALSE;
@@ -434,7 +416,7 @@ release_slave (NMDevice *device,
                gboolean configure)
 {
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
-	gboolean success, no_firmware = FALSE;
+	gboolean success;
 	gs_free char *address = NULL;
 
 	if (configure) {
@@ -463,7 +445,7 @@ release_slave (NMDevice *device,
 		 * IFF_UP), so we must bring it back up here to ensure carrier changes and
 		 * other state is noticed by the now-released slave.
 		 */
-		if (!nm_device_bring_up (slave, TRUE, &no_firmware))
+		if (!nm_device_bring_up (slave, TRUE, NULL))
 			_LOGW (LOGD_BOND, "released bond slave could not be brought up.");
 	} else {
 		_LOGI (LOGD_BOND, "bond slave %s was released",
@@ -632,27 +614,28 @@ static void
 nm_device_bond_class_init (NMDeviceBondClass *klass)
 {
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BOND_SETTING_NAME, NM_LINK_TYPE_BOND)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_bond);
 
-	parent_class->is_master = TRUE;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->complete_connection = complete_connection;
-
-	parent_class->update_connection = update_connection;
-	parent_class->master_update_slave_connection = master_update_slave_connection;
-
-	parent_class->create_and_realize = create_and_realize;
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
-	parent_class->enslave_slave = enslave_slave;
-	parent_class->release_slave = release_slave;
-	parent_class->can_reapply_change = can_reapply_change;
-	parent_class->reapply_connection = reapply_connection;
+	device_class->connection_type_supported = NM_SETTING_BOND_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_BOND_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_BOND);
+
+	device_class->is_master = TRUE;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->complete_connection = complete_connection;
+
+	device_class->update_connection = update_connection;
+	device_class->master_update_slave_connection = master_update_slave_connection;
+
+	device_class->create_and_realize = create_and_realize;
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
+	device_class->enslave_slave = enslave_slave;
+	device_class->release_slave = release_slave;
+	device_class->can_reapply_change = can_reapply_change;
+	device_class->reapply_connection = reapply_connection;
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index c81a0253..e79de95c 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -61,41 +61,63 @@ static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
+                            const char *specific_object,
+                            GError **error)
 {
 	NMSettingBluetooth *s_bt;
 
-	if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_available (device, connection, flags, specific_object))
+	if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_available (device, connection, flags, specific_object, error))
 		return FALSE;
 
 	s_bt = _nm_connection_get_setting_bluetooth_for_nap (connection);
 	if (s_bt) {
-		return    nm_bt_vtable_network_server
-		       && nm_bt_vtable_network_server->is_available (nm_bt_vtable_network_server,
-		                                                     nm_setting_bluetooth_get_bdaddr (s_bt));
+		const char *bdaddr;
+
+		if (!nm_bt_vtable_network_server) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "bluetooth plugin not available to activate NAP profile");
+			return FALSE;
+		}
+
+		bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
+		if (!nm_bt_vtable_network_server->is_available (nm_bt_vtable_network_server, bdaddr)) {
+			if (bdaddr)
+				nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                    "not suitable NAP device \"%s\" available", bdaddr);
+			else
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "not suitable NAP device available");
+			return FALSE;
+		}
 	}
 
 	return TRUE;
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMSettingBridge *s_bridge;
 	const char *mac_address;
 
-	if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_compatible (device, connection))
+	if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	s_bridge = nm_connection_get_setting_bridge (connection);
-	if (!s_bridge)
-		return FALSE;
+	if (   nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)
+	    && _nm_connection_get_setting_bluetooth_for_nap (connection)) {
+		s_bridge = nm_connection_get_setting_bridge (connection);
+		if (!s_bridge) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "missing bridge setting for bluetooth NAP profile");
+			return FALSE;
+		}
 
-	if (!nm_connection_is_type (connection, NM_SETTING_BRIDGE_SETTING_NAME)) {
-		if (   nm_connection_is_type (connection, NM_SETTING_BLUETOOTH_SETTING_NAME)
-		    && _nm_connection_get_setting_bluetooth_for_nap (connection)) {
-			/* a bluetooth NAP connection is handled by the bridge */
-		} else
+		/* a bluetooth NAP connection is handled by the bridge.
+		 *
+		 * Proceed... */
+	} else {
+		s_bridge = _nm_connection_check_main_setting (connection, NM_SETTING_BRIDGE_SETTING_NAME, error);
+		if (!s_bridge)
 			return FALSE;
 	}
 
@@ -104,8 +126,11 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		const char *hw_addr;
 
 		hw_addr = nm_device_get_hw_address (device);
-		if (!hw_addr || !nm_utils_hwaddr_matches (hw_addr, -1, mac_address, -1))
+		if (!hw_addr || !nm_utils_hwaddr_matches (hw_addr, -1, mac_address, -1)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "mac address mismatches");
 			return FALSE;
+		}
 	}
 
 	return TRUE;
@@ -505,28 +530,29 @@ static void
 nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 {
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BRIDGE_SETTING_NAME, NM_LINK_TYPE_BRIDGE)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_bridge);
 
-	parent_class->is_master = TRUE;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->check_connection_available = check_connection_available;
-	parent_class->complete_connection = complete_connection;
-
-	parent_class->update_connection = update_connection;
-	parent_class->master_update_slave_connection = master_update_slave_connection;
-
-	parent_class->create_and_realize = create_and_realize;
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->deactivate = deactivate;
-	parent_class->enslave_slave = enslave_slave;
-	parent_class->release_slave = release_slave;
-	parent_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
+	device_class->connection_type_supported = NM_SETTING_BRIDGE_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_BRIDGE);
+
+	device_class->is_master = TRUE;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->check_connection_available = check_connection_available;
+	device_class->complete_connection = complete_connection;
+
+	device_class->update_connection = update_connection;
+	device_class->master_update_slave_connection = master_update_slave_connection;
+
+	device_class->create_and_realize = create_and_realize;
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->deactivate = deactivate;
+	device_class->enslave_slave = enslave_slave;
+	device_class->release_slave = release_slave;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index 9be4d29f..a9059383 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -117,21 +117,6 @@ create_and_realize (NMDevice *device,
 	return TRUE;
 }
 
-static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	NMSettingDummy *s_dummy;
-
-	if (!NM_DEVICE_CLASS (nm_device_dummy_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_dummy = nm_connection_get_setting_dummy (connection);
-	if (!s_dummy)
-		return FALSE;
-
-	return TRUE;
-}
-
 static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -173,13 +158,13 @@ nm_device_dummy_class_init (NMDeviceDummyClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_DUMMY)
-
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_dummy);
 
-	device_class->connection_type = NM_SETTING_DUMMY_SETTING_NAME;
+	device_class->connection_type_supported = NM_SETTING_DUMMY_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_DUMMY_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_DUMMY);
+
 	device_class->complete_connection = complete_connection;
-	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->update_connection = update_connection;
diff --git a/src/devices/nm-device-ethernet-utils.c b/src/devices/nm-device-ethernet-utils.c
index 298e6dff..8bdb1991 100644
--- a/src/devices/nm-device-ethernet-utils.c
+++ b/src/devices/nm-device-ethernet-utils.c
@@ -18,33 +18,24 @@
 
 #include "nm-default.h"
 
-#include <string.h>
-
-#include "nm-connection.h"
-
 #include "nm-device-ethernet-utils.h"
 
+#include "settings/nm-settings-connection.h"
+
 char *
-nm_device_ethernet_utils_get_default_wired_name (NMConnection *const *connections)
+nm_device_ethernet_utils_get_default_wired_name (GHashTable *existing_ids)
 {
 	char *temp;
-	guint j;
 	int i;
 
 	/* Find the next available unique connection name */
-	for (i = 1; i <= 10000; i++) {
+	for (i = 1; i <= G_MAXINT; i++) {
 		temp = g_strdup_printf (_("Wired connection %d"), i);
-		for (j = 0; connections[j]; j++) {
-			if (nm_streq0 (nm_connection_get_id (connections[j]), temp)) {
-				g_free (temp);
-				goto next;
-			}
-		}
-		return temp;
-next:
-		;
+		if (   !existing_ids
+		    || !g_hash_table_contains (existing_ids, temp))
+			return temp;
+		g_free (temp);
 	}
-
 	return NULL;
 }
 
diff --git a/src/devices/nm-device-ethernet-utils.h b/src/devices/nm-device-ethernet-utils.h
index 197d0a9e..7e5c8b31 100644
--- a/src/devices/nm-device-ethernet-utils.h
+++ b/src/devices/nm-device-ethernet-utils.h
@@ -19,6 +19,6 @@
 #ifndef __NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H__
 #define __NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H__
 
-char *nm_device_ethernet_utils_get_default_wired_name (NMConnection *const *connections);
+char *nm_device_ethernet_utils_get_default_wired_name (GHashTable *existing_ids);
 
 #endif  /* NETWORKMANAGER_DEVICE_ETHERNET_UTILS_H */
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index a9a9f968..0fb8fea4 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -343,23 +343,21 @@ match_subchans (NMDeviceEthernet *self, NMSettingWired *s_wired, gboolean *try_m
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
 	NMSettingWired *s_wired;
 
-	if (!NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->check_connection_compatible (device, connection))
+	if (!NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	s_wired = nm_connection_get_setting_wired (connection);
-
 	if (nm_connection_is_type (connection, NM_SETTING_PPPOE_SETTING_NAME)) {
-		/* NOP */
-	} else if (nm_connection_is_type (connection, NM_SETTING_WIRED_SETTING_NAME)) {
+		s_wired = nm_connection_get_setting_wired (connection);
+	} else {
+		s_wired = _nm_connection_check_main_setting (connection, NM_SETTING_WIRED_SETTING_NAME, error);
 		if (!s_wired)
 			return FALSE;
-	} else
-		return FALSE;
+	}
 
 	if (s_wired) {
 		const char *mac, *perm_hw_addr;
@@ -367,28 +365,43 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		const char * const *mac_blacklist;
 		int i;
 
-		if (!match_subchans (self, s_wired, &try_mac))
+		if (!match_subchans (self, s_wired, &try_mac)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "s390 subchannels don't match");
 			return FALSE;
+		}
 
 		perm_hw_addr = nm_device_get_permanent_hw_address (device);
 		mac = nm_setting_wired_get_mac_address (s_wired);
 		if (perm_hw_addr) {
-			if (try_mac && mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1))
+			if (   try_mac
+			    && mac
+			    && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "permanent MAC address doesn't match");
 				return FALSE;
+			}
 
 			/* Check for MAC address blacklist */
 			mac_blacklist = nm_setting_wired_get_mac_address_blacklist (s_wired);
 			for (i = 0; mac_blacklist[i]; i++) {
 				if (!nm_utils_hwaddr_valid (mac_blacklist[i], ETH_ALEN)) {
-					g_warn_if_reached ();
+					nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+					                            "invalid MAC in blacklist");
 					return FALSE;
 				}
 
-				if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1))
+				if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1)) {
+					nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+					                            "permanent MAC address of device blacklisted");
 					return FALSE;
+				}
 			}
-		} else if (mac)
+		} else if (mac) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device has no permanent MAC address to match");
 			return FALSE;
+		}
 	}
 
 	return TRUE;
@@ -1185,7 +1198,6 @@ wake_on_lan_enable (NMDevice *device)
 	NMSettingWiredWakeOnLan wol;
 	NMSettingWired *s_wired;
 	const char *password = NULL;
-	gs_free char *value = NULL;
 
 	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
 	if (s_wired) {
@@ -1195,27 +1207,25 @@ wake_on_lan_enable (NMDevice *device)
 			goto found;
 	}
 
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ethernet.wake-on-lan",
-	                                               device);
-
-	if (value) {
-		wol = _nm_utils_ascii_str_to_int64 (value, 10,
-		                                    NM_SETTING_WIRED_WAKE_ON_LAN_NONE,
-		                                    G_MAXINT32,
-		                                    NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT);
-
-		if (   NM_FLAGS_ANY (wol, NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS)
-		    && !nm_utils_is_power_of_two (wol)) {
-			nm_log_dbg (LOGD_ETHER, "invalid default value %u for wake-on-lan", (guint) wol);
-			wol = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT;
-		}
-		if (wol != NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT)
-			goto found;
+	wol = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                   "ethernet.wake-on-lan",
+	                                                   device,
+	                                                   NM_SETTING_WIRED_WAKE_ON_LAN_NONE,
+	                                                   G_MAXINT32,
+	                                                   NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT);
+
+	if (   NM_FLAGS_ANY (wol, NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS)
+	    && !nm_utils_is_power_of_two (wol)) {
+		nm_log_dbg (LOGD_ETHER, "invalid default value %u for wake-on-lan", (guint) wol);
+		wol = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT;
 	}
+	if (wol != NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT)
+		goto found;
 	wol = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
 found:
-	return nm_platform_ethtool_set_wake_on_lan (nm_device_get_platform (device), nm_device_get_ifindex (device), wol, password);
+	return nm_platform_ethtool_set_wake_on_lan (nm_device_get_platform (device),
+	                                            nm_device_get_ifindex (device),
+	                                            wol, password);
 }
 
 /*****************************************************************************/
@@ -1430,12 +1440,14 @@ new_default_connection (NMDevice *self)
 	NMConnection *connection;
 	NMSettingsConnection *const*connections;
 	NMSetting *setting;
+	gs_unref_hashtable GHashTable *existing_ids = NULL;
 	struct udev_device *dev;
 	const char *perm_hw_addr;
 	const char *uprop = "0";
 	gs_free char *defname = NULL;
 	gs_free char *uuid = NULL;
 	gs_free char *machine_id = NULL;
+	guint i, n_connections;
 
 	if (nm_config_get_no_auto_default_for_device (nm_config_get (), self))
 		return NULL;
@@ -1448,8 +1460,13 @@ new_default_connection (NMDevice *self)
 	setting = nm_setting_connection_new ();
 	nm_connection_add_setting (connection, setting);
 
-	connections = nm_settings_get_connections (nm_device_get_settings (self), NULL);
-	defname = nm_device_ethernet_utils_get_default_wired_name ((NMConnection *const*) connections);
+	connections = nm_settings_get_connections (nm_device_get_settings (self), &n_connections);
+	if (n_connections > 0) {
+		existing_ids = g_hash_table_new (nm_str_hash, g_str_equal);
+		for (i = 0; i < n_connections; i++)
+			g_hash_table_add (existing_ids, (char *) nm_settings_connection_get_id (connections[i]));
+	}
+	defname = nm_device_ethernet_utils_get_default_wired_name (existing_ids);
 	if (!defname)
 		return NULL;
 
@@ -1750,12 +1767,10 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	g_type_class_add_private (object_class, sizeof (NMDeviceEthernetPrivate));
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRED_SETTING_NAME, NM_LINK_TYPE_ETHERNET)
-
 	object_class->dispose = dispose;
 	object_class->finalize = finalize;
 	object_class->get_property = get_property;
@@ -1763,25 +1778,28 @@ nm_device_ethernet_class_init (NMDeviceEthernetClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wired);
 
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->complete_connection = complete_connection;
-	parent_class->new_default_connection = new_default_connection;
-
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	parent_class->get_configured_mtu = get_configured_mtu;
-	parent_class->deactivate = deactivate;
-	parent_class->get_s390_subchannels = get_s390_subchannels;
-	parent_class->update_connection = update_connection;
-	parent_class->carrier_changed_notify = carrier_changed_notify;
-	parent_class->link_changed = link_changed;
-	parent_class->is_available = is_available;
-	parent_class->can_reapply_change = can_reapply_change;
-	parent_class->reapply_connection = reapply_connection;
-
-	parent_class->state_changed = device_state_changed;
+	device_class->connection_type_supported = NM_SETTING_WIRED_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_ETHERNET);
+
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->complete_connection = complete_connection;
+	device_class->new_default_connection = new_default_connection;
+
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->get_configured_mtu = get_configured_mtu;
+	device_class->deactivate = deactivate;
+	device_class->get_s390_subchannels = get_s390_subchannels;
+	device_class->update_connection = update_connection;
+	device_class->carrier_changed_notify = carrier_changed_notify;
+	device_class->link_changed = link_changed;
+	device_class->is_available = is_available;
+	device_class->can_reapply_change = can_reapply_change;
+	device_class->reapply_connection = reapply_connection;
+
+	device_class->state_changed = device_state_changed;
 
 	obj_properties[PROP_SPEED] =
 	    g_param_spec_uint (NM_DEVICE_ETHERNET_SPEED, "", "",
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index bc717bb3..a11ead6e 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2014 Red Hat, Inc.
+ * Copyright (C) 2014 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -417,6 +417,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 		                        callback, user_data); \
 	} G_STMT_END
 
+	_ADD_INTERNAL (nm_6lowpan_device_factory_get_type);
 	_ADD_INTERNAL (nm_bond_device_factory_get_type);
 	_ADD_INTERNAL (nm_bridge_device_factory_get_type);
 	_ADD_INTERNAL (nm_dummy_device_factory_get_type);
@@ -430,6 +431,8 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 	_ADD_INTERNAL (nm_veth_device_factory_get_type);
 	_ADD_INTERNAL (nm_vlan_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);
 
 	load_factories_from_dir (NMPLUGINDIR, callback, user_data);
 }
diff --git a/src/devices/nm-device-generic.c b/src/devices/nm-device-generic.c
index 27eaf8d6..d00aa93b 100644
--- a/src/devices/nm-device-generic.c
+++ b/src/devices/nm-device-generic.c
@@ -86,19 +86,19 @@ realize_start_notify (NMDevice *device, const NMPlatformLink *plink)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMSettingConnection *s_con;
 
-	if (!NM_DEVICE_CLASS (nm_device_generic_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	if (!nm_connection_is_type (connection, NM_SETTING_GENERIC_SETTING_NAME))
+	if (!NM_DEVICE_CLASS (nm_device_generic_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	s_con = nm_connection_get_setting_connection (connection);
-	if (!nm_setting_connection_get_interface_name (s_con))
+	if (!nm_setting_connection_get_interface_name (s_con)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "generic profiles need an interface name");
 		return FALSE;
+	}
 
 	return TRUE;
 }
@@ -220,9 +220,7 @@ nm_device_generic_class_init (NMDeviceGenericClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_GENERIC_SETTING_NAME, NM_LINK_TYPE_ANY)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructor = constructor;
 	object_class->dispose = dispose;
@@ -231,11 +229,15 @@ nm_device_generic_class_init (NMDeviceGenericClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_generic);
 
-	parent_class->realize_start_notify = realize_start_notify;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->get_type_description = get_type_description;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->update_connection = update_connection;
+	device_class->connection_type_supported = NM_SETTING_GENERIC_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_GENERIC_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_ANY);
+
+	device_class->realize_start_notify = realize_start_notify;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->get_type_description = get_type_description;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->update_connection = update_connection;
 
 	obj_properties[PROP_TYPE_DESCRIPTION] =
 	     g_param_spec_string (NM_DEVICE_GENERIC_TYPE_DESCRIPTION, "", "",
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 99bcef00..41fac157 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright 2011 Red Hat, Inc.
+ * Copyright 2011 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -80,7 +80,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMSettingInfiniband *s_infiniband;
 	char ifname_verified[IFNAMSIZ];
 	const char *transport_mode;
-	gboolean ok, no_firmware = FALSE;
+	gboolean ok;
 
 	ret = NM_DEVICE_CLASS (nm_device_infiniband_parent_class)->act_stage1_prepare (device, out_failure_reason);
 	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
@@ -104,7 +104,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	/* With some drivers the interface must be down to set transport mode */
 	nm_device_take_down (device, TRUE);
 	ok = nm_platform_sysctl_set (nm_device_get_platform (device), NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname_verified, "mode"), transport_mode);
-	nm_device_bring_up (device, TRUE, &no_firmware);
+	nm_device_bring_up (device, TRUE, NULL);
 
 	if (!ok) {
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
@@ -123,30 +123,28 @@ get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMSettingInfiniband *s_infiniband;
 
-	if (!NM_DEVICE_CLASS (nm_device_infiniband_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	if (!nm_connection_is_type (connection, NM_SETTING_INFINIBAND_SETTING_NAME))
-		return FALSE;
-
-	s_infiniband = nm_connection_get_setting_infiniband (connection);
-	if (!s_infiniband)
+	if (!NM_DEVICE_CLASS (nm_device_infiniband_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	if (nm_device_is_real (device)) {
 		const char *mac;
 		const char *hw_addr;
 
+		s_infiniband = nm_connection_get_setting_infiniband (connection);
+
 		mac = nm_setting_infiniband_get_mac_address (s_infiniband);
 		if (mac) {
 			hw_addr = nm_device_get_permanent_hw_address (device);
 			if (   !hw_addr
-			    || !nm_utils_hwaddr_matches (mac, -1, hw_addr, -1))
+			    || !nm_utils_hwaddr_matches (mac, -1, hw_addr, -1)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "MAC address mismatches");
 				return FALSE;
+			}
 		}
 	}
 
@@ -367,24 +365,26 @@ nm_device_infiniband_class_init (NMDeviceInfinibandClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_INFINIBAND_SETTING_NAME, NM_LINK_TYPE_INFINIBAND)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_infiniband);
 
-	parent_class->create_and_realize = create_and_realize;
-	parent_class->unrealize = unrealize;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->complete_connection = complete_connection;
-	parent_class->update_connection = update_connection;
+	device_class->connection_type_supported = NM_SETTING_INFINIBAND_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_INFINIBAND_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_INFINIBAND);
+
+	device_class->create_and_realize = create_and_realize;
+	device_class->unrealize = unrealize;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->complete_connection = complete_connection;
+	device_class->update_connection = update_connection;
 
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->get_configured_mtu = get_configured_mtu;
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->get_configured_mtu = get_configured_mtu;
 
 	obj_properties[PROP_IS_PARTITION] =
 	     g_param_spec_boolean (NM_DEVICE_INFINIBAND_IS_PARTITION, "", "",
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 2e266bb9..568403c6 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -179,10 +179,13 @@ clear:
 		goto out;
 	}
 
-	if (priv->mode == NM_IP_TUNNEL_MODE_GRE) {
+	if (NM_IN_SET (priv->mode, NM_IP_TUNNEL_MODE_GRE, NM_IP_TUNNEL_MODE_GRETAP)) {
 		const NMPlatformLnkGre *lnk;
 
-		lnk = nm_platform_link_get_lnk_gre (nm_device_get_platform (device), ifindex, NULL);
+		if (priv->mode == NM_IP_TUNNEL_MODE_GRE)
+			lnk = nm_platform_link_get_lnk_gre (nm_device_get_platform (device), ifindex, NULL);
+		else
+			lnk = nm_platform_link_get_lnk_gretap (nm_device_get_platform (device), ifindex, NULL);
 		if (!lnk) {
 			_LOGW (LOGD_PLATFORM, "could not read %s properties", "gre");
 			goto clear;
@@ -254,11 +257,21 @@ clear:
 		ttl = lnk->ttl;
 		tos = lnk->tos;
 		pmtud = lnk->path_mtu_discovery;
-	} else if (   priv->mode == NM_IP_TUNNEL_MODE_IPIP6
-	           || priv->mode == NM_IP_TUNNEL_MODE_IP6IP6) {
+	} else if (NM_IN_SET (priv->mode,
+	                      NM_IP_TUNNEL_MODE_IPIP6,
+	                      NM_IP_TUNNEL_MODE_IP6IP6,
+	                      NM_IP_TUNNEL_MODE_IP6GRE,
+	                      NM_IP_TUNNEL_MODE_IP6GRETAP)) {
 		const NMPlatformLnkIp6Tnl *lnk;
+		NMPlatform *plat = nm_device_get_platform (device);
+
+		if (priv->mode == NM_IP_TUNNEL_MODE_IP6GRE)
+			lnk = nm_platform_link_get_lnk_ip6gre (plat, ifindex, NULL);
+		else if (priv->mode == NM_IP_TUNNEL_MODE_IP6GRETAP)
+			lnk = nm_platform_link_get_lnk_ip6gretap (plat, ifindex, NULL);
+		else
+			lnk = nm_platform_link_get_lnk_ip6tnl (plat, ifindex, NULL);
 
-		lnk = nm_platform_link_get_lnk_ip6tnl (nm_device_get_platform (device), ifindex, NULL);
 		if (!lnk) {
 			_LOGW (LOGD_PLATFORM, "could not read %s properties", "ip6tnl");
 			goto clear;
@@ -272,6 +285,40 @@ clear:
 		encap_limit = lnk->encap_limit;
 		flow_label = lnk->flow_label;
 		flags = ip6tnl_flags_plat_to_setting (lnk->flags);
+
+		if (NM_IN_SET (priv->mode,
+		               NM_IP_TUNNEL_MODE_IP6GRE,
+		               NM_IP_TUNNEL_MODE_IP6GRETAP)) {
+			if (NM_FLAGS_HAS (lnk->input_flags, NM_GRE_KEY)) {
+				key = g_strdup_printf ("%u", lnk->input_key);
+				if (g_strcmp0 (priv->input_key, key)) {
+					g_free (priv->input_key);
+					priv->input_key = key;
+					_notify (self, PROP_INPUT_KEY);
+				} else
+					g_free (key);
+			} else {
+				if (priv->input_key) {
+					g_clear_pointer (&priv->input_key, g_free);
+					_notify (self, PROP_INPUT_KEY);
+				}
+			}
+
+			if (NM_FLAGS_HAS (lnk->output_flags, NM_GRE_KEY)) {
+				key = g_strdup_printf ("%u", lnk->output_key);
+				if (g_strcmp0 (priv->output_key, key)) {
+					g_free (priv->output_key);
+					priv->output_key = key;
+					_notify (self, PROP_OUTPUT_KEY);
+				} else
+					g_free (key);
+			} else {
+				if (priv->output_key) {
+					g_clear_pointer (&priv->output_key, g_free);
+					_notify (self, PROP_OUTPUT_KEY);
+				}
+			}
+		}
 	} else
 		g_return_if_reached ();
 
@@ -388,8 +435,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
 	NMSettingIPTunnel *s_ip_tunnel = nm_connection_get_setting_ip_tunnel (connection);
-	NMDevice *parent = NULL;
-	const char *setting_parent, *new_parent;
 
 	if (!s_ip_tunnel) {
 		s_ip_tunnel = (NMSettingIPTunnel *) nm_setting_ip_tunnel_new ();
@@ -399,25 +444,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (nm_setting_ip_tunnel_get_mode (s_ip_tunnel) != priv->mode)
 		g_object_set (G_OBJECT (s_ip_tunnel), NM_SETTING_IP_TUNNEL_MODE, priv->mode, NULL);
 
-	parent = nm_device_parent_get_device (device);
-
-	/* Update parent in the connection; default to parent's interface name */
-	if (parent) {
-		new_parent = nm_device_get_iface (parent);
-		setting_parent = nm_setting_ip_tunnel_get_parent (s_ip_tunnel);
-		if (setting_parent && nm_utils_is_uuid (setting_parent)) {
-			NMConnection *parent_connection;
-
-			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device),
-			                                                                         setting_parent);
-			if (parent_connection && nm_device_check_connection_compatible (parent, parent_connection))
-				new_parent = NULL;
-		}
-		if (new_parent)
-			g_object_set (s_ip_tunnel, NM_SETTING_IP_TUNNEL_PARENT, new_parent, NULL);
-	} else
-		g_object_set (s_ip_tunnel, NM_SETTING_IP_TUNNEL_PARENT, NULL, NULL);
+	g_object_set (s_ip_tunnel,
+	              NM_SETTING_IP_TUNNEL_PARENT,
+	              nm_device_parent_find_for_connection (device,
+	                                                    nm_setting_ip_tunnel_get_parent (s_ip_tunnel)),
+	              NULL);
 
 	if (!address_equal_pp (priv->addr_family,
 	                       nm_setting_ip_tunnel_get_local (s_ip_tunnel),
@@ -456,7 +487,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 		                        NULL);
 	}
 
-	if (priv->mode == NM_IP_TUNNEL_MODE_GRE || priv->mode == NM_IP_TUNNEL_MODE_IP6GRE) {
+	if (NM_IN_SET (priv->mode,
+	               NM_IP_TUNNEL_MODE_GRE,
+	               NM_IP_TUNNEL_MODE_GRETAP,
+	               NM_IP_TUNNEL_MODE_IP6GRE,
+	               NM_IP_TUNNEL_MODE_IP6GRETAP)) {
 		if (g_strcmp0 (nm_setting_ip_tunnel_get_input_key (s_ip_tunnel), priv->input_key)) {
 			g_object_set (G_OBJECT (s_ip_tunnel),
 			              NM_SETTING_IP_TUNNEL_INPUT_KEY,
@@ -473,54 +508,79 @@ update_connection (NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE (self);
 	NMSettingIPTunnel *s_ip_tunnel;
 	const char *parent;
 
-	if (!NM_DEVICE_CLASS (nm_device_ip_tunnel_parent_class)->check_connection_compatible (device, connection))
+	if (!NM_DEVICE_CLASS (nm_device_ip_tunnel_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	s_ip_tunnel = nm_connection_get_setting_ip_tunnel (connection);
-	if (!s_ip_tunnel)
-		return FALSE;
 
-	if (nm_setting_ip_tunnel_get_mode (s_ip_tunnel) != priv->mode)
+	if (nm_setting_ip_tunnel_get_mode (s_ip_tunnel) != priv->mode) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "incompatible IP tunnel mode");
 		return FALSE;
+	}
 
 	if (nm_device_is_real (device)) {
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_ip_tunnel_get_parent (s_ip_tunnel);
-		if (parent && !nm_device_match_parent (device, parent))
+		if (parent && !nm_device_match_parent (device, parent)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "IP tunnel parent mismatches");
 			return FALSE;
+		}
 
 		if (!address_equal_pp (priv->addr_family,
 		                       nm_setting_ip_tunnel_get_local (s_ip_tunnel),
-		                       priv->local))
+		                       priv->local)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "local IP tunnel address mismatches");
 			return FALSE;
+		}
 
 		if (!address_equal_pp (priv->addr_family,
 		                       nm_setting_ip_tunnel_get_remote (s_ip_tunnel),
-		                       priv->remote))
+		                       priv->remote)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "remote IP tunnel address mismatches");
 			return FALSE;
+		}
 
-		if (nm_setting_ip_tunnel_get_ttl (s_ip_tunnel) != priv->ttl)
+		if (nm_setting_ip_tunnel_get_ttl (s_ip_tunnel) != priv->ttl) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "TTL of IP tunnel mismatches");
 			return FALSE;
+		}
 
-		if (nm_setting_ip_tunnel_get_tos (s_ip_tunnel) != priv->tos)
+		if (nm_setting_ip_tunnel_get_tos (s_ip_tunnel) != priv->tos) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "TOS of IP tunnel mismatches");
 			return FALSE;
+		}
 
 		if (priv->addr_family == AF_INET) {
-			if (nm_setting_ip_tunnel_get_path_mtu_discovery (s_ip_tunnel) != priv->path_mtu_discovery)
+			if (nm_setting_ip_tunnel_get_path_mtu_discovery (s_ip_tunnel) != priv->path_mtu_discovery) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "MTU discovery setting of IP tunnel mismatches");
 				return FALSE;
+			}
 		} else {
-			if (nm_setting_ip_tunnel_get_encapsulation_limit (s_ip_tunnel) != priv->encap_limit)
+			if (nm_setting_ip_tunnel_get_encapsulation_limit (s_ip_tunnel) != priv->encap_limit) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "encapsulation limit of IP tunnel mismatches");
 				return FALSE;
+			}
 
-			if (nm_setting_ip_tunnel_get_flow_label (s_ip_tunnel) != priv->flow_label)
+			if (nm_setting_ip_tunnel_get_flow_label (s_ip_tunnel) != priv->flow_label) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "flow-label of IP tunnel mismatches");
 				return FALSE;
+			}
 		}
 	}
 
@@ -535,6 +595,8 @@ platform_link_to_tunnel_mode (const NMPlatformLink *link)
 	switch (link->type) {
 	case NM_LINK_TYPE_GRE:
 		return NM_IP_TUNNEL_MODE_GRE;
+	case NM_LINK_TYPE_GRETAP:
+		return NM_IP_TUNNEL_MODE_GRETAP;
 	case NM_LINK_TYPE_IP6TNL:
 		lnk = nm_platform_link_get_lnk_ip6tnl (NM_PLATFORM_GET, link->ifindex, NULL);
 		if (lnk) {
@@ -544,6 +606,10 @@ platform_link_to_tunnel_mode (const NMPlatformLink *link)
 				return NM_IP_TUNNEL_MODE_IP6IP6;
 		}
 		return NM_IP_TUNNEL_MODE_UNKNOWN;
+	case NM_LINK_TYPE_IP6GRE:
+		return NM_IP_TUNNEL_MODE_IP6GRE;
+	case NM_LINK_TYPE_IP6GRETAP:
+		return NM_IP_TUNNEL_MODE_IP6GRETAP;
 	case NM_LINK_TYPE_IPIP:
 		return NM_IP_TUNNEL_MODE_IPIP;
 	case NM_LINK_TYPE_SIT:
@@ -559,15 +625,20 @@ tunnel_mode_to_link_type (NMIPTunnelMode tunnel_mode)
 	switch (tunnel_mode) {
 	case NM_IP_TUNNEL_MODE_GRE:
 		return NM_LINK_TYPE_GRE;
+	case NM_IP_TUNNEL_MODE_GRETAP:
+		return NM_LINK_TYPE_GRETAP;
 	case NM_IP_TUNNEL_MODE_IPIP6:
 	case NM_IP_TUNNEL_MODE_IP6IP6:
 		return NM_LINK_TYPE_IP6TNL;
+	case NM_IP_TUNNEL_MODE_IP6GRE:
+		return NM_LINK_TYPE_IP6GRE;
+	case NM_IP_TUNNEL_MODE_IP6GRETAP:
+		return NM_LINK_TYPE_IP6GRETAP;
 	case NM_IP_TUNNEL_MODE_IPIP:
 		return NM_LINK_TYPE_IPIP;
 	case NM_IP_TUNNEL_MODE_SIT:
 		return NM_LINK_TYPE_SIT;
 	case NM_IP_TUNNEL_MODE_VTI:
-	case NM_IP_TUNNEL_MODE_IP6GRE:
 	case NM_IP_TUNNEL_MODE_VTI6:
 	case NM_IP_TUNNEL_MODE_ISATAP:
 		return NM_LINK_TYPE_UNKNOWN;
@@ -595,11 +666,16 @@ create_and_realize (NMDevice *device,
 	NMPlatformLnkIp6Tnl lnk_ip6tnl = { };
 	const char *str;
 	gint64 val;
+	NMIPTunnelMode mode;
 
 	s_ip_tunnel = nm_connection_get_setting_ip_tunnel (connection);
 	g_assert (s_ip_tunnel);
 
-	switch (nm_setting_ip_tunnel_get_mode (s_ip_tunnel)) {
+	mode = nm_setting_ip_tunnel_get_mode (s_ip_tunnel);
+	switch (mode) {
+	case NM_IP_TUNNEL_MODE_GRETAP:
+		lnk_gre.is_tap = TRUE;
+		/* fall-through */
 	case NM_IP_TUNNEL_MODE_GRE:
 		if (parent)
 			lnk_gre.parent_ifindex = nm_device_get_ifindex (parent);
@@ -700,6 +776,8 @@ create_and_realize (NMDevice *device,
 		break;
 	case NM_IP_TUNNEL_MODE_IPIP6:
 	case NM_IP_TUNNEL_MODE_IP6IP6:
+	case NM_IP_TUNNEL_MODE_IP6GRE:
+	case NM_IP_TUNNEL_MODE_IP6GRETAP:
 		if (parent)
 			lnk_ip6tnl.parent_ifindex = nm_device_get_ifindex (parent);
 
@@ -715,13 +793,44 @@ create_and_realize (NMDevice *device,
 		lnk_ip6tnl.tclass = nm_setting_ip_tunnel_get_tos (s_ip_tunnel);
 		lnk_ip6tnl.encap_limit = nm_setting_ip_tunnel_get_encapsulation_limit (s_ip_tunnel);
 		lnk_ip6tnl.flow_label = nm_setting_ip_tunnel_get_flow_label (s_ip_tunnel);
-		lnk_ip6tnl.proto = nm_setting_ip_tunnel_get_mode (s_ip_tunnel) == NM_IP_TUNNEL_MODE_IPIP6 ? IPPROTO_IPIP : IPPROTO_IPV6;
 		lnk_ip6tnl.flags = ip6tnl_flags_setting_to_plat (nm_setting_ip_tunnel_get_flags (s_ip_tunnel));
 
-		plerr = nm_platform_link_ip6tnl_add (nm_device_get_platform (device), iface, &lnk_ip6tnl, out_plink);
+		if (NM_IN_SET (mode, NM_IP_TUNNEL_MODE_IP6GRE, NM_IP_TUNNEL_MODE_IP6GRETAP)) {
+			val = _nm_utils_ascii_str_to_int64 (nm_setting_ip_tunnel_get_input_key (s_ip_tunnel),
+			                                    10,
+			                                    0,
+			                                    G_MAXUINT32,
+			                                    -1);
+			if (val != -1) {
+				lnk_ip6tnl.input_key = val;
+				lnk_ip6tnl.input_flags = NM_GRE_KEY;
+			}
+
+			val = _nm_utils_ascii_str_to_int64 (nm_setting_ip_tunnel_get_output_key (s_ip_tunnel),
+			                                    10,
+			                                    0,
+			                                    G_MAXUINT32,
+			                                    -1);
+			if (val != -1) {
+				lnk_ip6tnl.output_key = val;
+				lnk_ip6tnl.output_flags = NM_GRE_KEY;
+			}
+
+			lnk_ip6tnl.is_gre = TRUE;
+			lnk_ip6tnl.is_tap = (mode == NM_IP_TUNNEL_MODE_IP6GRETAP);
+
+			plerr = nm_platform_link_ip6gre_add (nm_device_get_platform (device),
+			                                     iface, &lnk_ip6tnl, out_plink);
+		} else {
+			lnk_ip6tnl.proto = nm_setting_ip_tunnel_get_mode (s_ip_tunnel) == NM_IP_TUNNEL_MODE_IPIP6
+			                       ? IPPROTO_IPIP
+			                       : IPPROTO_IPV6;
+			plerr = nm_platform_link_ip6tnl_add (nm_device_get_platform (device),
+			                                     iface, &lnk_ip6tnl, out_plink);
+		}
 		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
-			             "Failed to create IPIP interface '%s' for '%s': %s",
+			             "Failed to create IPv6 tunnel interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
 			             nm_platform_error_to_string_a (plerr));
@@ -852,6 +961,21 @@ set_property (GObject *object, guint prop_id,
 	}
 }
 
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMActStageReturn ret;
+
+	ret = NM_DEVICE_CLASS (nm_device_ip_tunnel_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
+		return NM_ACT_STAGE_RETURN_FAILURE;
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
 /*****************************************************************************/
 
 static void
@@ -864,8 +988,11 @@ constructed (GObject *object)
 {
 	NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE ((NMDeviceIPTunnel *) object);
 
-	if (   priv->mode == NM_IP_TUNNEL_MODE_IPIP6
-	    || priv->mode == NM_IP_TUNNEL_MODE_IP6IP6)
+	if (NM_IN_SET (priv->mode,
+	               NM_IP_TUNNEL_MODE_IPIP6,
+	               NM_IP_TUNNEL_MODE_IP6IP6,
+	               NM_IP_TUNNEL_MODE_IP6GRE,
+	               NM_IP_TUNNEL_MODE_IP6GRETAP))
 		priv->addr_family = AF_INET6;
 	else
 		priv->addr_family = AF_INET;
@@ -925,6 +1052,17 @@ nm_device_ip_tunnel_class_init (NMDeviceIPTunnelClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ip_tunnel);
 
+	device_class->connection_type_supported = NM_SETTING_IP_TUNNEL_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_IP_TUNNEL_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_GRE,
+	                                                        NM_LINK_TYPE_GRETAP,
+	                                                        NM_LINK_TYPE_IP6TNL,
+	                                                        NM_LINK_TYPE_IP6GRE,
+	                                                        NM_LINK_TYPE_IP6GRETAP,
+	                                                        NM_LINK_TYPE_IPIP,
+	                                                        NM_LINK_TYPE_SIT);
+
+	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->link_changed = link_changed;
 	device_class->can_reapply_change = can_reapply_change;
 	device_class->complete_connection = complete_connection;
@@ -935,13 +1073,6 @@ nm_device_ip_tunnel_class_init (NMDeviceIPTunnelClass *klass)
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->unrealize_notify = unrealize_notify;
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass,
-	                               NM_SETTING_IP_TUNNEL_SETTING_NAME,
-	                               NM_LINK_TYPE_GRE,
-	                               NM_LINK_TYPE_IP6TNL,
-	                               NM_LINK_TYPE_IPIP,
-	                               NM_LINK_TYPE_SIT);
-
 	obj_properties[PROP_MODE] =
 	     g_param_spec_uint (NM_DEVICE_IP_TUNNEL_MODE, "", "",
 	                        0, G_MAXUINT, 0,
@@ -1084,7 +1215,13 @@ get_connection_iface (NMDeviceFactory *factory,
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (IP_TUNNEL, IPTunnel, ip_tunnel,
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_GRE, NM_LINK_TYPE_SIT, NM_LINK_TYPE_IPIP)
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_GRE,
+	                                      NM_LINK_TYPE_GRETAP,
+	                                      NM_LINK_TYPE_SIT,
+	                                      NM_LINK_TYPE_IPIP,
+	                                      NM_LINK_TYPE_IP6TNL,
+	                                      NM_LINK_TYPE_IP6GRE,
+	                                      NM_LINK_TYPE_IP6GRETAP)
 	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_IP_TUNNEL_SETTING_NAME),
 	factory_class->create_device = create_device;
 	factory_class->get_connection_parent = get_connection_parent;
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 166bfb57..8ea4c8b5 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -627,21 +627,6 @@ deactivate (NMDevice *device)
 	supplicant_interface_release (self);
 }
 
-static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	NMSettingMacsec *s_macsec;
-
-	if (!NM_DEVICE_CLASS (nm_device_macsec_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_macsec = nm_connection_get_setting_macsec (connection);
-	if (!s_macsec)
-		return FALSE;
-
-	return TRUE;
-}
-
 /******************************************************************/
 
 static NMDeviceCapabilities
@@ -841,27 +826,26 @@ nm_device_macsec_class_init (NMDeviceMacsecClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_MACSEC)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_macsec);
 
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->create_and_realize = create_and_realize;
-	parent_class->deactivate = deactivate;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->link_changed = link_changed;
-	parent_class->is_available = is_available;
-	parent_class->parent_changed_notify = parent_changed_notify;
-	parent_class->state_changed = device_state_changed;
-	parent_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
-
-	parent_class->connection_type = NM_SETTING_MACSEC_SETTING_NAME;
+	device_class->connection_type_supported = NM_SETTING_MACSEC_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_MACSEC_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_MACSEC);
+
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->create_and_realize = create_and_realize;
+	device_class->deactivate = deactivate;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->link_changed = link_changed;
+	device_class->is_available = is_available;
+	device_class->parent_changed_notify = parent_changed_notify;
+	device_class->state_changed = device_state_changed;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
 
 	obj_properties[PROP_SCI] =
 	    g_param_spec_uint64 (NM_DEVICE_MACSEC_SCI, "", "",
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index dc4fb3e8..ff386c82 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -290,40 +290,58 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 /*****************************************************************************/
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE ((NMDeviceMacvlan *) device);
 	NMSettingMacvlan *s_macvlan;
 	const char *parent = NULL;
 
-	if (!NM_DEVICE_CLASS (nm_device_macvlan_parent_class)->check_connection_compatible (device, connection))
+	if (!NM_DEVICE_CLASS (nm_device_macvlan_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	s_macvlan = nm_connection_get_setting_macvlan (connection);
-	if (!s_macvlan)
-		return FALSE;
 
-	if (nm_setting_macvlan_get_tap (s_macvlan) != priv->props.tap)
+	if (nm_setting_macvlan_get_tap (s_macvlan) != priv->props.tap) {
+		if (priv->props.tap) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "macvtap device does not match macvlan profile");
+		} else {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "macvlan device does not match macvtap profile");
+		}
 		return FALSE;
+	}
 
 	/* Before the device is realized some properties will not be set */
 	if (nm_device_is_real (device)) {
 
-		if (setting_mode_to_platform (nm_setting_macvlan_get_mode (s_macvlan)) != priv->props.mode)
+		if (setting_mode_to_platform (nm_setting_macvlan_get_mode (s_macvlan)) != priv->props.mode) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "macvlan mode setting differs");
 			return FALSE;
+		}
 
-		if (nm_setting_macvlan_get_promiscuous (s_macvlan) ==  priv->props.no_promisc)
+		if (nm_setting_macvlan_get_promiscuous (s_macvlan) ==  priv->props.no_promisc) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "macvlan promiscuous setting differs");
 			return FALSE;
+		}
 
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_macvlan_get_parent (s_macvlan);
 		if (parent) {
-			if (!nm_device_match_parent (device, parent))
+			if (!nm_device_match_parent (device, parent)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "macvlan parent setting differs");
 				return FALSE;
+			}
 		} else {
 			/* Parent could be a MAC address in an NMSettingWired */
-			if (!nm_device_match_hwaddr (device, connection, TRUE))
+			if (!nm_device_match_parent_hwaddr (device, connection, TRUE)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "macvlan parent mac setting differs");
 				return FALSE;
+			}
 		}
 	}
 
@@ -359,7 +377,7 @@ complete_connection (NMDevice *device,
 	 * settings, then there's not enough information to complete the setting.
 	 */
 	if (   !nm_setting_macvlan_get_parent (s_macvlan)
-	    && !nm_device_match_hwaddr (device, connection, TRUE)) {
+	    && !nm_device_match_parent_hwaddr (device, connection, TRUE)) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "The 'macvlan' setting had no interface name, parent, or hardware address.");
 		return FALSE;
@@ -373,8 +391,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE ((NMDeviceMacvlan *) device);
 	NMSettingMacvlan *s_macvlan = nm_connection_get_setting_macvlan (connection);
-	NMDevice *parent_device;
-	const char *setting_parent, *new_parent;
 	int new_mode;
 
 	if (!s_macvlan) {
@@ -392,24 +408,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (priv->props.tap != nm_setting_macvlan_get_tap (s_macvlan))
 		g_object_set (s_macvlan, NM_SETTING_MACVLAN_TAP, !!priv->props.tap, NULL);
 
-	/* Update parent in the connection; default to parent's interface name */
-	parent_device = nm_device_parent_get_device (device);
-	if (parent_device) {
-		new_parent = nm_device_get_iface (parent_device);
-		setting_parent = nm_setting_macvlan_get_parent (s_macvlan);
-		if (setting_parent && nm_utils_is_uuid (setting_parent)) {
-			NMConnection *parent_connection;
-
-			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device), setting_parent);
-			if (parent_connection && nm_device_check_connection_compatible (parent_device, parent_connection))
-				new_parent = NULL;
-		}
-		if (new_parent)
-			g_object_set (s_macvlan, NM_SETTING_MACVLAN_PARENT, new_parent, NULL);
-	} else
-		g_object_set (s_macvlan, NM_SETTING_MACVLAN_PARENT, NULL, NULL);
-
+	g_object_set (s_macvlan,
+	              NM_SETTING_MACVLAN_PARENT,
+	              nm_device_parent_find_for_connection (device,
+	                                                    nm_setting_macvlan_get_parent (s_macvlan)),
+	              NULL);
 }
 
 static NMActStageReturn
@@ -495,17 +498,18 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP)
-
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_macvlan);
 
+	device_class->connection_type_supported = NM_SETTING_MACVLAN_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_MACVLAN_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP);
+
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->complete_connection = complete_connection;
-	device_class->connection_type = NM_SETTING_MACVLAN_SETTING_NAME;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index a6abb228..74b4d710 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -49,24 +49,6 @@ 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)
 
-static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	NMSettingPppoe *s_pppoe;
-
-	if (!NM_DEVICE_CLASS (nm_device_ppp_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	if (!nm_streq0 (nm_connection_get_connection_type (connection),
-	                NM_SETTING_PPPOE_SETTING_NAME))
-		return FALSE;
-
-	s_pppoe = nm_connection_get_setting_pppoe (connection);
-	nm_assert (s_pppoe);
-
-	return !!nm_setting_pppoe_get_parent (s_pppoe);
-}
-
 static NMDeviceCapabilities
 get_generic_capabilities (NMDevice *device)
 {
@@ -275,20 +257,21 @@ nm_device_ppp_class_init (NMDevicePppClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_PPPOE_SETTING_NAME, NM_LINK_TYPE_PPP)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->dispose = dispose;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ppp);
 
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->create_and_realize = create_and_realize;
-	parent_class->deactivate = deactivate;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->connection_type_supported = NM_SETTING_PPPOE_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_PPPOE_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_PPP);
+
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->create_and_realize = create_and_realize;
+	device_class->deactivate = deactivate;
+	device_class->get_generic_capabilities = get_generic_capabilities;
 }
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index ba28e9e4..66c715de 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -130,12 +130,21 @@ void nm_device_commit_mtu (NMDevice *self);
 
 /*****************************************************************************/
 
-#define NM_DEVICE_CLASS_DECLARE_TYPES(klass, conn_type, ...) \
-	NM_DEVICE_CLASS (klass)->connection_type = conn_type; \
-	{ \
-		static const NMLinkType link_types[] = { __VA_ARGS__, NM_LINK_TYPE_NONE }; \
-		NM_DEVICE_CLASS (klass)->link_types = link_types; \
-	}
+#define NM_DEVICE_DEFINE_LINK_TYPES(...) \
+	((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, \
+	      }; \
+	      \
+	      _link_types.types; \
+	    })\
+	)
 
 gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
                                              GError **error, const char **whitelist);
@@ -143,8 +152,8 @@ gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setti
 	_nm_device_hash_check_invalid_keys (hash, setting_name, error, ((const char *[]) { __VA_ARGS__, NULL }))
 
 gboolean nm_device_match_parent (NMDevice *device, const char *parent);
-gboolean nm_device_match_hwaddr (NMDevice *device,
-                                 NMConnection *connection,
-                                 gboolean fail_if_no_hwaddr);
+gboolean nm_device_match_parent_hwaddr (NMDevice *device,
+                                        NMConnection *connection,
+                                        gboolean fail_if_no_hwaddr);
 
 #endif /* NM_DEVICE_PRIVATE_H */
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index 3846676c..0f76b23a 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -289,18 +289,14 @@ _same_og (const char *str, gboolean og_valid, guint32 og_num)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (device);
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
 	NMSettingTunMode mode;
 	NMSettingTun *s_tun;
 
-	if (!NM_DEVICE_CLASS (nm_device_tun_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_tun = nm_connection_get_setting_tun (connection);
-	if (!s_tun)
+	if (!NM_DEVICE_CLASS (nm_device_tun_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	if (nm_device_is_real (device)) {
@@ -308,22 +304,43 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		case IFF_TUN: mode = NM_SETTING_TUN_MODE_TUN; break;
 		case IFF_TAP: mode = NM_SETTING_TUN_MODE_TAP; break;
 		default:
-			/* Huh? */
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "invalid tun type on device");
 			return FALSE;
 		}
 
-		if (mode != nm_setting_tun_get_mode (s_tun))
+		s_tun = nm_connection_get_setting_tun (connection);
+
+		if (mode != nm_setting_tun_get_mode (s_tun)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "tun mode setting mismatches");
 			return FALSE;
-		if (!_same_og (nm_setting_tun_get_owner (s_tun), priv->props.owner_valid, priv->props.owner))
+		}
+		if (!_same_og (nm_setting_tun_get_owner (s_tun), priv->props.owner_valid, priv->props.owner)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "tun owner setting mismatches");
 			return FALSE;
-		if (!_same_og (nm_setting_tun_get_group (s_tun), priv->props.group_valid, priv->props.group))
+		}
+		if (!_same_og (nm_setting_tun_get_group (s_tun), priv->props.group_valid, priv->props.group)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "tun group setting mismatches");
 			return FALSE;
-		if (nm_setting_tun_get_pi (s_tun) != priv->props.pi)
+		}
+		if (nm_setting_tun_get_pi (s_tun) != priv->props.pi) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "tun pi setting mismatches");
 			return FALSE;
-		if (nm_setting_tun_get_vnet_hdr (s_tun) != priv->props.vnet_hdr)
+		}
+		if (nm_setting_tun_get_vnet_hdr (s_tun) != priv->props.vnet_hdr) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "tun vnet-hdr setting mismatches");
 			return FALSE;
-		if (nm_setting_tun_get_multi_queue (s_tun) != priv->props.multi_queue)
+		}
+		if (nm_setting_tun_get_multi_queue (s_tun) != priv->props.multi_queue) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "tun multi-queue setting mismatches");
 			return FALSE;
+		}
 	}
 
 	return TRUE;
@@ -430,13 +447,14 @@ nm_device_tun_class_init (NMDeviceTunClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_TUN)
-
 	object_class->get_property = get_property;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_tun);
 
-	device_class->connection_type = NM_SETTING_TUN_SETTING_NAME;
+	device_class->connection_type_supported = NM_SETTING_TUN_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_TUN_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_TUN);
+
 	device_class->link_changed = link_changed;
 	device_class->complete_connection = complete_connection;
 	device_class->check_connection_compatible = check_connection_compatible;
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index 186173eb..6f90758c 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -153,12 +153,13 @@ nm_device_veth_class_init (NMDeviceVethClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_VETH)
-
 	object_class->get_property = get_property;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_veth);
 
+	device_class->connection_type_supported = NULL;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_VETH);
+
 	device_class->can_unmanaged_external_down = can_unmanaged_external_down;
 	device_class->link_changed = link_changed;
 	device_class->parent_changed_notify = parent_changed_notify;
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index b0e746a8..b7f0c4e7 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -331,33 +331,39 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 /*****************************************************************************/
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE ((NMDeviceVlan *) device);
 	NMSettingVlan *s_vlan;
-	const char *parent = NULL;
+	const char *parent;
 
-	if (!NM_DEVICE_CLASS (nm_device_vlan_parent_class)->check_connection_compatible (device, connection))
+	if (!NM_DEVICE_CLASS (nm_device_vlan_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	s_vlan = nm_connection_get_setting_vlan (connection);
-	if (!s_vlan)
-		return FALSE;
-
-	/* Before the device is realized some properties will not be set */
 	if (nm_device_is_real (device)) {
-		if (nm_setting_vlan_get_id (s_vlan) != priv->vlan_id)
+		s_vlan = nm_connection_get_setting_vlan (connection);
+
+		if (nm_setting_vlan_get_id (s_vlan) != priv->vlan_id) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vlan id setting mismatches");
 			return FALSE;
+		}
 
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_vlan_get_parent (s_vlan);
 		if (parent) {
-			if (!nm_device_match_parent (device, parent))
+			if (!nm_device_match_parent (device, parent)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "vlan parent setting differs");
 				return FALSE;
+			}
 		} else {
 			/* Parent could be a MAC address in an NMSettingWired */
-			if (!nm_device_match_hwaddr (device, connection, TRUE))
+			if (!nm_device_match_parent_hwaddr (device, connection, TRUE)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "vlan parent mac setting differs");
 				return FALSE;
+			}
 		}
 	}
 
@@ -368,12 +374,13 @@ static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
+                            const char *specific_object,
+                            GError **error)
 {
 	if (!nm_device_is_real (device))
 		return TRUE;
 
-	return NM_DEVICE_CLASS (nm_device_vlan_parent_class)->check_connection_available (device, connection, flags, specific_object);
+	return NM_DEVICE_CLASS (nm_device_vlan_parent_class)->check_connection_available (device, connection, flags, specific_object, error);
 }
 
 static gboolean
@@ -405,7 +412,7 @@ complete_connection (NMDevice *device,
 	 * settings, then there's not enough information to complete the setting.
 	 */
 	if (   !nm_setting_vlan_get_parent (s_vlan)
-	    && !nm_device_match_hwaddr (device, connection, TRUE)) {
+	    && !nm_device_match_parent_hwaddr (device, connection, TRUE)) {
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "The 'vlan' setting had no interface name, parent, or hardware address.");
 		return FALSE;
@@ -420,10 +427,8 @@ update_connection (NMDevice *device, NMConnection *connection)
 	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE (device);
 	NMSettingVlan *s_vlan = nm_connection_get_setting_vlan (connection);
 	int ifindex = nm_device_get_ifindex (device);
-	const char *setting_parent, *new_parent;
 	const NMPlatformLink *plink;
 	const NMPObject *polnk;
-	NMDevice *parent_device;
 	guint vlan_id;
 	guint vlan_flags;
 
@@ -441,26 +446,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (vlan_id != nm_setting_vlan_get_id (s_vlan))
 		g_object_set (s_vlan, NM_SETTING_VLAN_ID, vlan_id, NULL);
 
-	/* Update parent in the connection; default to parent's interface name */
-	parent_device = nm_device_parent_get_device (device);
-	if (   parent_device
-	    && polnk
-	    && plink->parent > 0
-	    && nm_device_get_ifindex (parent_device) == plink->parent) {
-		new_parent = nm_device_get_iface (parent_device);
-		setting_parent = nm_setting_vlan_get_parent (s_vlan);
-		if (setting_parent && nm_utils_is_uuid (setting_parent)) {
-			NMConnection *parent_connection;
-
-			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device), setting_parent);
-			if (parent_connection && nm_device_check_connection_compatible (parent_device, parent_connection))
-				new_parent = NULL;
-		}
-		if (new_parent)
-			g_object_set (s_vlan, NM_SETTING_VLAN_PARENT, new_parent, NULL);
-	} else
-		g_object_set (s_vlan, NM_SETTING_VLAN_PARENT, NULL, NULL);
+	g_object_set (s_vlan,
+	              NM_SETTING_VLAN_PARENT,
+	              nm_device_parent_find_for_connection (device,
+	                                                    nm_setting_vlan_get_parent (s_vlan)),
+	              NULL);
 
 	if (polnk)
 		vlan_flags = polnk->lnk_vlan.flags;
@@ -599,27 +589,29 @@ nm_device_vlan_class_init (NMDeviceVlanClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_VLAN_SETTING_NAME, NM_LINK_TYPE_VLAN)
+	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_vlan);
 
-	parent_class->create_and_realize = create_and_realize;
-	parent_class->link_changed = link_changed;
-	parent_class->unrealize_notify = unrealize_notify;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->get_configured_mtu = get_configured_mtu;
-	parent_class->is_available = is_available;
-	parent_class->parent_changed_notify = parent_changed_notify;
-
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->check_connection_available = check_connection_available;
-	parent_class->complete_connection = complete_connection;
-	parent_class->update_connection = update_connection;
+	device_class->connection_type_supported = NM_SETTING_VLAN_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_VLAN_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_VLAN);
+
+	device_class->create_and_realize = create_and_realize;
+	device_class->link_changed = link_changed;
+	device_class->unrealize_notify = unrealize_notify;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->get_configured_mtu = get_configured_mtu;
+	device_class->is_available = is_available;
+	device_class->parent_changed_notify = parent_changed_notify;
+
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->check_connection_available = check_connection_available;
+	device_class->complete_connection = complete_connection;
+	device_class->update_connection = update_connection;
 
 	obj_properties[PROP_VLAN_ID] =
 	     g_param_spec_uint (NM_DEVICE_VLAN_ID, "", "",
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index e1252223..c34f4142 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -246,65 +246,108 @@ address_matches (const char *str, in_addr_t addr4, struct in6_addr *addr6)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE ((NMDeviceVxlan *) device);
 	NMSettingVxlan *s_vxlan;
 	const char *parent;
 
-	if (!NM_DEVICE_CLASS (nm_device_vxlan_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_vxlan = nm_connection_get_setting_vxlan (connection);
-	if (!s_vxlan)
+	if (!NM_DEVICE_CLASS (nm_device_vxlan_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	if (nm_device_is_real (device)) {
+		s_vxlan = nm_connection_get_setting_vxlan (connection);
+
 		parent = nm_setting_vxlan_get_parent (s_vxlan);
-		if (parent && !nm_device_match_parent (device, parent))
+		if (parent && !nm_device_match_parent (device, parent)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan parent mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.id != nm_setting_vxlan_get_id (s_vxlan))
+		if (priv->props.id != nm_setting_vxlan_get_id (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan id mismatches");
 			return FALSE;
+		}
 
-		if (!address_matches (nm_setting_vxlan_get_local (s_vxlan), priv->props.local, &priv->props.local6))
+		if (!address_matches (nm_setting_vxlan_get_local (s_vxlan), priv->props.local, &priv->props.local6)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan local address mismatches");
 			return FALSE;
+		}
 
-		if (!address_matches (nm_setting_vxlan_get_remote (s_vxlan), priv->props.group, &priv->props.group6))
+		if (!address_matches (nm_setting_vxlan_get_remote (s_vxlan), priv->props.group, &priv->props.group6)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan remote address mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.src_port_min != nm_setting_vxlan_get_source_port_min (s_vxlan))
+		if (priv->props.src_port_min != nm_setting_vxlan_get_source_port_min (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan source port min mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.src_port_max != nm_setting_vxlan_get_source_port_max (s_vxlan))
+		if (priv->props.src_port_max != nm_setting_vxlan_get_source_port_max (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan source port max mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.dst_port != nm_setting_vxlan_get_destination_port (s_vxlan))
+		if (priv->props.dst_port != nm_setting_vxlan_get_destination_port (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan destination port mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.tos != nm_setting_vxlan_get_tos (s_vxlan))
+		if (priv->props.tos != nm_setting_vxlan_get_tos (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan TOS mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.ttl != nm_setting_vxlan_get_ttl (s_vxlan))
+		if (priv->props.ttl != nm_setting_vxlan_get_ttl (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan TTL mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.learning != nm_setting_vxlan_get_learning (s_vxlan))
+		if (priv->props.learning != nm_setting_vxlan_get_learning (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan learning mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.ageing != nm_setting_vxlan_get_ageing (s_vxlan))
+		if (priv->props.ageing != nm_setting_vxlan_get_ageing (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan ageing mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.proxy != nm_setting_vxlan_get_proxy (s_vxlan))
+		if (priv->props.proxy != nm_setting_vxlan_get_proxy (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan proxy mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.rsc != nm_setting_vxlan_get_rsc (s_vxlan))
+		if (priv->props.rsc != nm_setting_vxlan_get_rsc (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan rsc mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.l2miss != nm_setting_vxlan_get_l2_miss (s_vxlan))
+		if (priv->props.l2miss != nm_setting_vxlan_get_l2_miss (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan l2miss mismatches");
 			return FALSE;
+		}
 
-		if (priv->props.l3miss != nm_setting_vxlan_get_l3_miss (s_vxlan))
+		if (priv->props.l3miss != nm_setting_vxlan_get_l3_miss (s_vxlan)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "vxlan l3miss mismatches");
 			return FALSE;
+		}
 	}
 
 	return TRUE;
@@ -343,9 +386,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE ((NMDeviceVxlan *) device);
 	NMSettingVxlan *s_vxlan = nm_connection_get_setting_vxlan (connection);
-	NMDevice *parent_device;
-	const char *setting_parent;
-	const char *new_parent = NULL;
 
 	if (!s_vxlan) {
 		s_vxlan = (NMSettingVxlan *) nm_setting_vxlan_new ();
@@ -355,23 +395,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 	if (priv->props.id != nm_setting_vxlan_get_id (s_vxlan))
 		g_object_set (G_OBJECT (s_vxlan), NM_SETTING_VXLAN_ID, priv->props.id, NULL);
 
-	parent_device = nm_device_parent_get_device (device);
-
-	/* Update parent in the connection; default to parent's interface name */
-	if (parent_device) {
-		new_parent = nm_device_get_iface (parent_device);
-		setting_parent = nm_setting_vxlan_get_parent (s_vxlan);
-		if (setting_parent && nm_utils_is_uuid (setting_parent)) {
-			NMConnection *parent_connection;
-
-			/* Don't change a parent specified by UUID if it's still valid */
-			parent_connection = (NMConnection *) nm_settings_get_connection_by_uuid (nm_device_get_settings (device),
-			                                                                         setting_parent);
-			if (parent_connection && nm_device_check_connection_compatible (parent_device, parent_connection))
-				new_parent = NULL;
-		}
-	}
-	g_object_set (s_vxlan, NM_SETTING_VXLAN_PARENT, new_parent, NULL);
+	g_object_set (s_vxlan,
+	              NM_SETTING_VXLAN_PARENT,
+	              nm_device_parent_find_for_connection (device,
+	                                                    nm_setting_vxlan_get_parent (s_vxlan)),
+	              NULL);
 
 	if (!address_matches (nm_setting_vxlan_get_remote (s_vxlan), priv->props.group, &priv->props.group6)) {
 		if (priv->props.group) {
@@ -577,15 +605,16 @@ nm_device_vxlan_class_init (NMDeviceVxlanClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_VXLAN)
-
 	object_class->get_property = get_property;
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_vxlan);
 
+	device_class->connection_type_supported = NM_SETTING_VXLAN_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_VXLAN_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_VXLAN);
+
 	device_class->link_changed = link_changed;
 	device_class->unrealize_notify = unrealize_notify;
-	device_class->connection_type = NM_SETTING_VXLAN_SETTING_NAME;
 	device_class->create_and_realize = create_and_realize;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->complete_connection = complete_connection;
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
new file mode 100644
index 00000000..62ec0274
--- /dev/null
+++ b/src/devices/nm-device-wireguard.c
@@ -0,0 +1,212 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Javier Arteaga <jarteaga@jbeta.is>
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-wireguard.h"
+
+#include "nm-device-private.h"
+#include "platform/nm-platform.h"
+#include "nm-device-factory.h"
+
+#include "nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceWireGuard);
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWireGuard,
+	PROP_PUBLIC_KEY,
+	PROP_LISTEN_PORT,
+	PROP_FWMARK,
+);
+
+struct _NMDeviceWireGuard {
+	NMDevice parent;
+	NMPlatformLnkWireGuard props;
+};
+
+struct _NMDeviceWireGuardClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceWireGuard, nm_device_wireguard, NM_TYPE_DEVICE)
+
+/******************************************************************/
+
+static GVariant *
+get_public_key_as_variant (const NMDeviceWireGuard *self)
+{
+	return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+	                                  self->props.public_key, sizeof (self->props.public_key), 1);
+}
+
+static void
+update_properties (NMDevice *device)
+{
+	NMDeviceWireGuard *self;
+	const NMPlatformLink *plink;
+	const NMPlatformLnkWireGuard *props = NULL;
+	int ifindex;
+
+	g_return_if_fail (NM_IS_DEVICE_WIREGUARD (device));
+	self = NM_DEVICE_WIREGUARD (device);
+
+	ifindex = nm_device_get_ifindex (device);
+	props = nm_platform_link_get_lnk_wireguard (nm_device_get_platform (device), ifindex, &plink);
+	if (!props) {
+		_LOGW (LOGD_PLATFORM, "could not get wireguard properties");
+		return;
+	}
+
+	g_object_freeze_notify (G_OBJECT (device));
+
+#define CHECK_PROPERTY_CHANGED(field, prop) \
+	G_STMT_START { \
+		if (self->props.field != props->field) { \
+			self->props.field = props->field; \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+#define CHECK_PROPERTY_CHANGED_ARRAY(field, prop) \
+	G_STMT_START { \
+		if (memcmp (&self->props.field, &props->field, sizeof (props->field)) != 0) { \
+			memcpy (&self->props.field, &props->field, sizeof (props->field)); \
+			_notify (self, prop); \
+		} \
+	} G_STMT_END
+
+	CHECK_PROPERTY_CHANGED_ARRAY (public_key, PROP_PUBLIC_KEY);
+	CHECK_PROPERTY_CHANGED (listen_port, PROP_LISTEN_PORT);
+	CHECK_PROPERTY_CHANGED (fwmark, PROP_FWMARK);
+
+	g_object_thaw_notify (G_OBJECT (device));
+}
+
+static void
+link_changed (NMDevice *device,
+              const NMPlatformLink *pllink)
+{
+	NM_DEVICE_CLASS (nm_device_wireguard_parent_class)->link_changed (device, pllink);
+	update_properties (device);
+}
+
+
+/******************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
+
+	switch (prop_id) {
+	case PROP_PUBLIC_KEY:
+		g_value_take_variant (value, get_public_key_as_variant (self));
+		break;
+	case PROP_LISTEN_PORT:
+		g_value_set_uint (value, self->props.listen_port);
+		break;
+	case PROP_FWMARK:
+		g_value_set_uint (value, self->props.fwmark);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+nm_device_wireguard_init (NMDeviceWireGuard *self)
+{
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WIREGUARD,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("PublicKey",  "ay", NM_DEVICE_WIREGUARD_PUBLIC_KEY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("ListenPort", "q", NM_DEVICE_WIREGUARD_LISTEN_PORT),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("FwMark",     "u", NM_DEVICE_WIREGUARD_FWMARK),
+		),
+	),
+};
+
+static void
+nm_device_wireguard_class_init (NMDeviceWireGuardClass *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_wireguard);
+
+	device_class->connection_type_supported = NULL;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD);
+
+	device_class->link_changed = link_changed;
+
+	obj_properties[PROP_PUBLIC_KEY] =
+	    g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY,
+	                          "", "",
+	                          G_VARIANT_TYPE ("ay"),
+	                          NULL,
+	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_LISTEN_PORT] =
+	    g_param_spec_uint (NM_DEVICE_WIREGUARD_LISTEN_PORT,
+	                       "", "",
+	                       0, G_MAXUINT16, 0,
+	                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_FWMARK] =
+	    g_param_spec_uint (NM_DEVICE_WIREGUARD_FWMARK,
+	                       "", "",
+	                       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_WIREGUARD_DEVICE_FACTORY (nm_wireguard_device_factory_get_type ())
+#define NM_WIREGUARD_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIREGUARD_DEVICE_FACTORY, NMWireGuardDeviceFactory))
+
+static NMDevice *
+create_device (NMDeviceFactory *factory,
+               const char *iface,
+               const NMPlatformLink *plink,
+               NMConnection *connection,
+               gboolean *out_ignore)
+{
+	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_WIREGUARD,
+	                                  NM_DEVICE_IFACE, iface,
+	                                  NM_DEVICE_TYPE_DESC, "WireGuard",
+	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WIREGUARD,
+	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WIREGUARD,
+	                                  NULL);
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL (WIREGUARD, WireGuard, wireguard,
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WIREGUARD),
+	factory_class->create_device = create_device;
+)
diff --git a/src/devices/nm-device-wireguard.h b/src/devices/nm-device-wireguard.h
new file mode 100644
index 00000000..3ad41f9b
--- /dev/null
+++ b/src/devices/nm-device-wireguard.h
@@ -0,0 +1,41 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Javier Arteaga <jarteaga@jbeta.is>
+ */
+
+#ifndef __NM_DEVICE_WIREGUARD_H__
+#define __NM_DEVICE_WIREGUARD_H__
+
+#include "nm-device.h"
+
+#define NM_TYPE_DEVICE_WIREGUARD            (nm_device_wireguard_get_type ())
+#define NM_DEVICE_WIREGUARD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WIREGUARD, NMDeviceWireGuard))
+#define NM_DEVICE_WIREGUARD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WIREGUARD, NMDeviceWireGuardClass))
+#define NM_IS_DEVICE_WIREGUARD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WIREGUARD))
+#define NM_IS_DEVICE_WIREGUARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WIREGUARD))
+#define NM_DEVICE_WIREGUARD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WIREGUARD, NMDeviceWireGuardClass))
+
+#define NM_DEVICE_WIREGUARD_PUBLIC_KEY      "public-key"
+#define NM_DEVICE_WIREGUARD_LISTEN_PORT     "listen-port"
+#define NM_DEVICE_WIREGUARD_FWMARK          "fwmark"
+
+typedef struct _NMDeviceWireGuard NMDeviceWireGuard;
+typedef struct _NMDeviceWireGuardClass NMDeviceWireGuardClass;
+
+GType nm_device_wireguard_get_type (void);
+
+#endif /* __NM_DEVICE_WIREGUARD_H__ */
diff --git a/src/devices/nm-device-wpan.c b/src/devices/nm-device-wpan.c
new file mode 100644
index 00000000..dd2ebac1
--- /dev/null
+++ b/src/devices/nm-device-wpan.c
@@ -0,0 +1,253 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#include "nm-default.h"
+
+#include "nm-manager.h"
+#include "nm-device-wpan.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "nm-act-request.h"
+#include "nm-device-private.h"
+#include "nm-ip4-config.h"
+#include "platform/nm-platform.h"
+#include "nm-device-factory.h"
+#include "nm-setting-wpan.h"
+#include "nm-core-internal.h"
+
+#include "nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceWpan);
+
+/*****************************************************************************/
+
+struct _NMDeviceWpan {
+	NMDevice parent;
+};
+
+struct _NMDeviceWpanClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceWpan, nm_device_wpan, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static gboolean
+complete_connection (NMDevice *device,
+                     NMConnection *connection,
+                     const char *specific_object,
+                     NMConnection *const*existing_connections,
+                     GError **error)
+{
+	NMSettingWpan *s_wpan;
+
+	nm_utils_complete_generic (nm_device_get_platform (device),
+	                           connection,
+	                           NM_SETTING_WPAN_SETTING_NAME,
+	                           existing_connections,
+	                           NULL,
+	                           _("WPAN connection"),
+	                           NULL,
+	                           TRUE);
+
+	s_wpan = nm_connection_get_setting_wpan (connection);
+	if (!s_wpan) {
+		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "A 'wpan' setting is required.");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+update_connection (NMDevice *device, NMConnection *connection)
+{
+	NMSettingWpan *s_wpan = nm_connection_get_setting_wpan (connection);
+
+	if (!s_wpan) {
+		s_wpan = (NMSettingWpan *) nm_setting_wpan_new ();
+		nm_connection_add_setting (connection, (NMSetting *) s_wpan);
+	}
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
+{
+	NMSettingWpan *s_wpan;
+	const char *mac, *hw_addr;
+
+	if (!NM_DEVICE_CLASS (nm_device_wpan_parent_class)->check_connection_compatible (device, connection, error))
+		return FALSE;
+
+	s_wpan = nm_connection_get_setting_wpan (connection);
+
+	mac = nm_setting_wpan_get_mac_address (s_wpan);
+	if (mac) {
+		hw_addr = nm_device_get_hw_address (device);
+		if (!nm_utils_hwaddr_matches (mac, -1, hw_addr, -1)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "MAC address mismatches");
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDeviceWpan *self = NM_DEVICE_WPAN (device);
+	NMConnection *connection;
+	NMSettingWpan *s_wpan;
+	NMPlatform *platform;
+	guint16 pan_id;
+	guint16 short_address;
+	int ifindex;
+	const guint8 *hwaddr;
+	gsize hwaddr_len = 0;
+	const NMPlatformLink *lowpan_plink;
+	NMDevice *lowpan_device = NULL;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+
+	ret = NM_DEVICE_CLASS (nm_device_wpan_parent_class)->act_stage1_prepare (device, out_failure_reason);
+	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
+		return ret;
+
+	platform = nm_device_get_platform (device);
+	g_return_val_if_fail (platform, NM_ACT_STAGE_RETURN_FAILURE);
+
+	ifindex = nm_device_get_ifindex (device);
+	g_return_val_if_fail (ifindex > 0, NM_ACT_STAGE_RETURN_FAILURE);
+
+	connection = nm_device_get_applied_connection (device);
+	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_wpan = nm_connection_get_setting_wpan (connection);
+	g_return_val_if_fail (s_wpan, NM_ACT_STAGE_RETURN_FAILURE);
+
+	hwaddr = nm_platform_link_get_address (platform, ifindex, &hwaddr_len);
+	g_return_val_if_fail (hwaddr, NM_ACT_STAGE_RETURN_FAILURE);
+
+	/* As of kernel 4.16, the 6LoWPAN devices layered on top of WPANs
+	 * need to be DOWN as well as the WPAN device itself in order to
+	 * modify the WPAN properties. */
+	lowpan_plink = nm_platform_link_get_by_address (platform,
+	                                                NM_LINK_TYPE_6LOWPAN,
+	                                                hwaddr,
+	                                                hwaddr_len);
+	if (lowpan_plink && NM_FLAGS_HAS (lowpan_plink->n_ifi_flags, IFF_UP)) {
+		lowpan_device = nm_manager_get_device_by_ifindex (nm_manager_get (),
+		                                                  lowpan_plink->ifindex);
+	}
+
+	if (lowpan_device)
+		nm_device_take_down (lowpan_device, TRUE);
+
+	nm_device_take_down (device, TRUE);
+
+	pan_id = nm_setting_wpan_get_pan_id (s_wpan);
+	if (pan_id != G_MAXUINT16) {
+		if (!nm_platform_wpan_set_pan_id (platform, ifindex, pan_id)) {
+			_LOGW (LOGD_DEVICE, "unable to set the PAN ID");
+			goto out;
+		}
+	}
+
+	short_address = nm_setting_wpan_get_short_address (s_wpan);
+	if (short_address != G_MAXUINT16) {
+		if (!nm_platform_wpan_set_short_addr (platform, ifindex, short_address)) {
+			_LOGW (LOGD_DEVICE, "unable to set the short address");
+			goto out;
+		}
+	}
+
+	ret = NM_ACT_STAGE_RETURN_SUCCESS;
+out:
+        nm_device_bring_up (device, TRUE, NULL);
+
+	if (lowpan_device)
+		nm_device_bring_up (lowpan_device, TRUE, NULL);
+
+	return ret;
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_wpan_init (NMDeviceWpan *self)
+{
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wpan = {
+	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
+		NM_DBUS_INTERFACE_DEVICE_WPAN,
+		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE ("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+		),
+	),
+};
+
+static void
+nm_device_wpan_class_init (NMDeviceWpanClass *klass)
+{
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
+
+	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_wpan);
+
+	device_class->connection_type_supported = NM_SETTING_WPAN_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WPAN_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WPAN);
+
+	device_class->complete_connection = complete_connection;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->update_connection = update_connection;
+	device_class->act_stage1_prepare = act_stage1_prepare;
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_WPAN_DEVICE_FACTORY (nm_wpan_device_factory_get_type ())
+#define NM_WPAN_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WPAN_DEVICE_FACTORY, NMWpanDeviceFactory))
+
+static NMDevice *
+create_device (NMDeviceFactory *factory,
+               const char *iface,
+               const NMPlatformLink *plink,
+               NMConnection *connection,
+               gboolean *out_ignore)
+{
+	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_WPAN,
+	                                  NM_DEVICE_IFACE, iface,
+	                                  NM_DEVICE_TYPE_DESC, "WPAN",
+	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_WPAN,
+	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_WPAN,
+	                                  NULL);
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL (WPAN, Wpan, wpan,
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_WPAN)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_WPAN_SETTING_NAME),
+	factory_class->create_device = create_device;
+);
diff --git a/src/devices/nm-device-wpan.h b/src/devices/nm-device-wpan.h
new file mode 100644
index 00000000..33f24776
--- /dev/null
+++ b/src/devices/nm-device-wpan.h
@@ -0,0 +1,35 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_WPAN_H__
+#define __NETWORKMANAGER_DEVICE_WPAN_H__
+
+#define NM_TYPE_DEVICE_WPAN            (nm_device_wpan_get_type ())
+#define NM_DEVICE_WPAN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_WPAN, NMDeviceWpan))
+#define NM_DEVICE_WPAN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_WPAN, NMDeviceWpanClass))
+#define NM_IS_DEVICE_WPAN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_WPAN))
+#define NM_IS_DEVICE_WPAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_WPAN))
+#define NM_DEVICE_WPAN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_WPAN, NMDeviceWpanClass))
+
+typedef struct _NMDeviceWpan NMDeviceWpan;
+typedef struct _NMDeviceWpanClass NMDeviceWpanClass;
+
+GType nm_device_wpan_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_WPAN_H__ */
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 19e43762..515e6334 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2017 Red Hat, Inc.
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -43,6 +43,7 @@
 #include "nm-utils/nm-random-utils.h"
 #include "nm-utils/unaligned.h"
 
+#include "nm-ethtool-utils.h"
 #include "nm-common-macros.h"
 #include "nm-device-private.h"
 #include "NetworkManagerUtils.h"
@@ -65,6 +66,7 @@
 #include "nm-firewall-manager.h"
 #include "settings/nm-settings-connection.h"
 #include "settings/nm-settings.h"
+#include "nm-setting-ethtool.h"
 #include "nm-auth-utils.h"
 #include "nm-netns.h"
 #include "nm-dispatcher.h"
@@ -172,6 +174,12 @@ struct _NMDeviceConnectivityHandle {
 	bool is_periodic_bump_on_complete:1;
 };
 
+typedef struct {
+	int ifindex;
+	NMEthtoolFeatureStates *features;
+	NMTernary requested[_NM_ETHTOOL_ID_FEATURE_NUM];
+} EthtoolState;
+
 /*****************************************************************************/
 
 enum {
@@ -508,6 +516,8 @@ typedef struct _NMDevicePrivate {
 
 	GHashTable *   ip6_saved_properties;
 
+	EthtoolState  *ethtool_state;
+
 	struct {
 		NMDhcpClient *   client;
 		NMNDiscDHCPLevel mode;
@@ -614,7 +624,7 @@ static void _set_state_full (NMDevice *self,
 static void queued_state_clear (NMDevice *device);
 static gboolean queued_ip4_config_change (gpointer user_data);
 static gboolean queued_ip6_config_change (gpointer user_data);
-static void ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data);
+static void ip_check_ping_watch_cb (GPid pid, int status, gpointer user_data);
 static gboolean ip_config_valid (NMDeviceState state);
 static NMActStageReturn dhcp4_start (NMDevice *self);
 static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll);
@@ -724,6 +734,7 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_device_state_reason_to_str, NMDeviceStateReason,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_OVSDB_FAILED,                   "ovsdb-failed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE,           "ip-address-duplicate"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED,          "ip-method-unsupported"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,     "sriov-configuration-failed"),
 );
 
 #define reason_to_string(reason) \
@@ -739,6 +750,79 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (mtu_source_to_str, NMDeviceMtuSource,
 
 /*****************************************************************************/
 
+static void
+_ethtool_state_reset (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->ethtool_state) {
+		gs_free NMEthtoolFeatureStates *features = priv->ethtool_state->features;
+		gs_free EthtoolState *ethtool_state = g_steal_pointer (&priv->ethtool_state);
+
+		if (!nm_platform_ethtool_set_features (nm_device_get_platform (self),
+		                                       ethtool_state->ifindex,
+		                                       features,
+		                                       ethtool_state->requested,
+		                                       FALSE))
+			_LOGW (LOGD_DEVICE, "ethtool: failure resetting one or more offload features");
+		else
+			_LOGD (LOGD_DEVICE, "ethtool: offload features successfully reset");
+	}
+}
+
+static void
+_ethtool_state_set (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	int ifindex;
+	NMConnection *connection;
+	NMSettingEthtool *s_ethtool;
+	NMPlatform *platform;
+	gs_free EthtoolState *ethtool_state = NULL;
+	gs_free NMEthtoolFeatureStates *features = NULL;
+
+	_ethtool_state_reset (self);
+
+	connection = nm_device_get_applied_connection (self);
+	if (!connection)
+		return;
+
+	ifindex = nm_device_get_ip_ifindex (self);
+	if (ifindex <= 0)
+		return;
+
+	s_ethtool = NM_SETTING_ETHTOOL (nm_connection_get_setting (connection, NM_TYPE_SETTING_ETHTOOL));
+	if (!s_ethtool)
+		return;
+
+	ethtool_state = g_new (EthtoolState, 1);
+	if (nm_setting_ethtool_init_features (s_ethtool, ethtool_state->requested) == 0)
+		return;
+
+	platform = nm_device_get_platform (self);
+
+	features = nm_platform_ethtool_get_link_features (platform, ifindex);
+	if (!features) {
+		_LOGW (LOGD_DEVICE, "ethtool: failure setting offload features (cannot read features)");
+		return;
+	}
+
+	if (!nm_platform_ethtool_set_features (platform,
+	                                       ifindex,
+	                                       features,
+	                                       ethtool_state->requested,
+	                                       TRUE))
+		_LOGW (LOGD_DEVICE, "ethtool: failure setting one or more offload features");
+	else
+		_LOGD (LOGD_DEVICE, "ethtool: offload features successfully set");
+
+	ethtool_state->ifindex = ifindex;
+	ethtool_state->features = g_steal_pointer (&features);
+	priv->ethtool_state = g_steal_pointer (&ethtool_state);
+}
+
+/*****************************************************************************/
+
 NMSettings *
 nm_device_get_settings (NMDevice *self)
 {
@@ -1017,15 +1101,19 @@ nm_device_assume_state_reset (NMDevice *self)
 static void
 init_ip_config_dns_priority (NMDevice *self, NMIPConfig *config)
 {
-	gs_free char *value = NULL;
-	gint priority;
-
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               (nm_ip_config_get_addr_family (config) == AF_INET)
-	                                                 ? "ipv4.dns-priority"
-	                                                 : "ipv6.dns-priority",
-	                                               self);
-	priority = _nm_utils_ascii_str_to_int64 (value, 10, G_MININT, G_MAXINT, 0);
+	const char *property;
+	int priority;
+
+	property = (nm_ip_config_get_addr_family (config) == AF_INET)
+	             ? "ipv4.dns-priority"
+	             : "ipv6.dns-priority";
+
+	priority = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                        property,
+	                                                        self,
+	                                                        G_MININT,
+	                                                        G_MAXINT,
+	                                                        0);
 	nm_ip_config_set_dns_priority (config, priority ?: NM_DNS_PRIORITY_DEFAULT_NORMAL);
 }
 
@@ -1627,6 +1715,41 @@ nm_device_parent_notify_changed (NMDevice *self,
 
 /*****************************************************************************/
 
+const char *
+nm_device_parent_find_for_connection (NMDevice *self,
+                                      const char *current_setting_parent)
+{
+	const char *new_parent;
+	NMDevice *parent_device;
+
+	parent_device = nm_device_parent_get_device (self);
+	if (!parent_device)
+		return NULL;
+
+	new_parent = nm_device_get_iface (parent_device);
+	if (!new_parent)
+		return NULL;
+
+	if (   current_setting_parent
+	    && !nm_streq (current_setting_parent, new_parent)
+	    && nm_utils_is_uuid (current_setting_parent)) {
+		NMSettingsConnection *parent_connection;
+
+		/* Don't change a parent specified by UUID if it's still valid */
+		parent_connection = nm_settings_get_connection_by_uuid (nm_device_get_settings (self),
+		                                                        current_setting_parent);
+		if (   parent_connection
+		    && nm_device_check_connection_compatible (parent_device,
+		                                              nm_settings_connection_get_connection (parent_connection),
+		                                              NULL))
+			return current_setting_parent;
+	}
+
+	return new_parent;
+}
+
+/*****************************************************************************/
+
 static void
 _stats_update_counters (NMDevice *self,
                         guint64 tx_bytes,
@@ -1731,7 +1854,11 @@ static gboolean
 get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMPlatform *platform = nm_device_get_platform (self);
 	const NMPlatformLink *pllink;
+	const guint8 *hwaddr;
+	guint8 pseudo_hwaddr[ETH_ALEN];
+	guint hwaddr_len;
 	int ifindex;
 	gboolean success;
 
@@ -1739,7 +1866,7 @@ get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 	ifindex = nm_device_get_ip_ifindex (self);
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	pllink = nm_platform_link_get (nm_device_get_platform (self), ifindex);
+	pllink = nm_platform_link_get (platform, ifindex);
 	if (   !pllink
 	    || NM_IN_SET (pllink->type, NM_LINK_TYPE_NONE, NM_LINK_TYPE_UNKNOWN))
 		return FALSE;
@@ -1749,9 +1876,35 @@ get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 	if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX)
 		g_return_val_if_reached (FALSE);
 
+	hwaddr = pllink->addr.data;
+	hwaddr_len = pllink->addr.len;
+
+	if (pllink->type == NM_LINK_TYPE_6LOWPAN) {
+		/* If the underlying IEEE 802.15.4 device has a short address we generate
+		 * a "pseudo 48-bit address" that's to be used in the same fashion as a
+		 * wired Ethernet address. The mechanism is specified in Section 6. of
+		 * RFC 4944 */
+		guint16 pan_id;
+		guint16 short_addr;
+
+		short_addr = nm_platform_wpan_get_short_addr (platform, pllink->parent);
+		if (short_addr != G_MAXUINT16) {
+			pan_id = nm_platform_wpan_get_pan_id (platform, pllink->parent);
+			pseudo_hwaddr[0] = short_addr & 0xff;
+			pseudo_hwaddr[1] = (short_addr >> 8) & 0xff;
+			pseudo_hwaddr[2] = 0;
+			pseudo_hwaddr[3] = 0;
+			pseudo_hwaddr[4] = pan_id & 0xff;
+			pseudo_hwaddr[5] = (pan_id >> 8) & 0xff;
+
+			hwaddr = pseudo_hwaddr;
+			hwaddr_len = G_N_ELEMENTS (pseudo_hwaddr);
+		}
+	}
+
 	success = nm_utils_get_ipv6_interface_identifier (pllink->type,
-	                                                  pllink->addr.data,
-	                                                  pllink->addr.len,
+	                                                  hwaddr,
+	                                                  hwaddr_len,
 	                                                  priv->dev_id,
 	                                                  out_iid);
 	if (!success) {
@@ -1865,7 +2018,9 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 	 */
 
 	switch (device_type) {
-	/* 50 is reserved for VPN (NM_VPN_ROUTE_METRIC_DEFAULT) */
+	/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT) */
+	case NM_DEVICE_TYPE_WIREGUARD:
+		return 50;
 	case NM_DEVICE_TYPE_ETHERNET:
 	case NM_DEVICE_TYPE_VETH:
 		return 100;
@@ -1905,10 +2060,14 @@ nm_device_get_route_metric_default (NMDeviceType device_type)
 		return 700;
 	case NM_DEVICE_TYPE_BT:
 		return 750;
+	case NM_DEVICE_TYPE_6LOWPAN:
+		return 775;
 	case NM_DEVICE_TYPE_OVS_BRIDGE:
 	case NM_DEVICE_TYPE_OVS_INTERFACE:
 	case NM_DEVICE_TYPE_OVS_PORT:
 		return 800;
+	case NM_DEVICE_TYPE_WPAN:
+		return 850;
 	case NM_DEVICE_TYPE_GENERIC:
 		return 950;
 	case NM_DEVICE_TYPE_UNKNOWN:
@@ -1953,10 +2112,10 @@ guint32
 nm_device_get_route_metric (NMDevice *self,
                             int addr_family)
 {
-	char *value;
 	gint64 route_metric;
 	NMSettingIPConfig *s_ip;
 	NMConnection *connection;
+	const char *property;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), G_MAXUINT32);
 	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), G_MAXUINT32);
@@ -1981,15 +2140,13 @@ nm_device_get_route_metric (NMDevice *self,
 	/* use the current NMConfigData, which makes this configuration reloadable.
 	 * Note that that means that the route-metric might change between SIGHUP.
 	 * You must cache the returned value if that is a problem. */
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               addr_family == AF_INET ? "ipv4.route-metric" : "ipv6.route-metric", self);
-	if (value) {
-		route_metric = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1);
-		g_free (value);
-
-		if (route_metric >= 0)
-			goto out;
-	}
+	property = addr_family == AF_INET ? "ipv4.route-metric" : "ipv6.route-metric";
+	route_metric = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                            property,
+	                                                            self,
+	                                                            0, G_MAXUINT32, -1);
+	if (route_metric >= 0)
+		goto out;
 
 	route_metric = nm_manager_device_route_metric_reserve (nm_manager_get (),
 	                                                       nm_device_get_ip_ifindex (self),
@@ -2009,21 +2166,37 @@ _get_mdns (NMDevice *self)
 	connection = nm_device_get_applied_connection (self);
 	if (connection)
 		mdns = nm_setting_connection_get_mdns (nm_connection_get_setting_connection (connection));
+	if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT)
+		return mdns;
 
-	if (mdns == NM_SETTING_CONNECTION_MDNS_DEFAULT) {
-		gs_free char *value = NULL;
+	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                    "connection.mdns",
+	                                                    self,
+	                                                    NM_SETTING_CONNECTION_MDNS_NO,
+	                                                    NM_SETTING_CONNECTION_MDNS_YES,
+	                                                    NM_SETTING_CONNECTION_MDNS_DEFAULT);
+}
 
-		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                               "connection.mdns",
-		                                               self);
-		mdns = _nm_utils_ascii_str_to_int64 (value,
-		                                     10,
-		                                     NM_SETTING_CONNECTION_MDNS_NO,
-		                                     NM_SETTING_CONNECTION_MDNS_YES,
-		                                     NM_SETTING_CONNECTION_MDNS_DEFAULT);
-	}
+static NMSettingConnectionLlmnr
+_get_llmnr (NMDevice *self)
+{
+	NMConnection *connection;
+	NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_SETTING_CONNECTION_LLMNR_DEFAULT);
+
+	connection = nm_device_get_applied_connection (self);
+	if (connection)
+		llmnr = nm_setting_connection_get_llmnr (nm_connection_get_setting_connection (connection));
+	if (llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT)
+		return llmnr;
 
-	return mdns;
+	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                    "connection.llmnr",
+	                                                    self,
+	                                                    NM_SETTING_CONNECTION_LLMNR_NO,
+	                                                    NM_SETTING_CONNECTION_LLMNR_YES,
+	                                                    NM_SETTING_CONNECTION_LLMNR_DEFAULT);
 }
 
 guint32
@@ -2066,14 +2239,13 @@ nm_device_get_route_table (NMDevice *self,
 		 * connection. Otherwise, the connection is not active, and the
 		 * connection default doesn't matter. */
 		if (route_table == 0) {
-			gs_free char *value = NULL;
+			const char *property;
 
-			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-			                                               addr_family == AF_INET
-			                                                 ? "ipv4.route-table"
-			                                                 : "ipv6.route-table",
-			                                               self);
-			route_table = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, 0);
+			property = addr_family == AF_INET ? "ipv4.route-table" : "ipv6.route-table";
+			route_table = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+			                                                           property,
+			                                                           self,
+			                                                           0, G_MAXUINT32, 0);
 		}
 	}
 
@@ -2191,6 +2363,22 @@ nm_device_get_settings_connection (NMDevice *self)
 }
 
 NMConnection *
+nm_device_get_settings_connection_get_connection (NMDevice *self)
+{
+	NMSettingsConnection *sett_con;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->act_request.obj)
+		return NULL;
+
+	sett_con = nm_act_request_get_settings_connection (priv->act_request.obj);
+	if (!sett_con)
+		return NULL;
+
+	return nm_settings_connection_get_connection (sett_con);
+}
+
+NMConnection *
 nm_device_get_applied_connection (NMDevice *self)
 {
 	NMDevicePrivate *priv;
@@ -3903,7 +4091,7 @@ nm_device_update_from_platform_link (NMDevice *self, const NMPlatformLink *plink
 }
 
 static void
-device_init_sriov_num_vfs (NMDevice *self)
+device_init_static_sriov_num_vfs (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gs_free char *value = NULL;
@@ -3917,8 +4105,8 @@ device_init_sriov_num_vfs (NMDevice *self)
 		                                          NULL);
 		num_vfs = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXINT32, -1);
 		if (num_vfs >= 0) {
-			nm_platform_link_set_sriov_num_vfs (nm_device_get_platform (self),
-			                                    priv->ifindex, num_vfs);
+			nm_platform_link_set_sriov_params (nm_device_get_platform (self),
+			                                   priv->ifindex, num_vfs, -1);
 		}
 	}
 }
@@ -3937,7 +4125,7 @@ config_changed (NMConfig *config,
 		priv->ignore_carrier = nm_config_data_get_ignore_carrier (config_data, self);
 
 	if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_VALUES))
-		device_init_sriov_num_vfs (self);
+		device_init_static_sriov_num_vfs (self);
 }
 
 static void
@@ -4081,7 +4269,7 @@ realize_start_setup (NMDevice *self,
 
 	nm_device_set_carrier_from_platform (self);
 
-	device_init_sriov_num_vfs (self);
+	device_init_static_sriov_num_vfs (self);
 
 	nm_assert (!priv->stats.timeout_id);
 	real_rate = _stats_refresh_rate_real (priv->stats.refresh_rate_ms);
@@ -4236,6 +4424,9 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 		}
 	}
 
+	nm_clear_g_source (&priv->queued_ip_config_id_4);
+	nm_clear_g_source (&priv->queued_ip_config_id_6);
+
 	g_object_freeze_notify (G_OBJECT (self));
 	NM_DEVICE_GET_CLASS (self)->unrealize_notify (self);
 
@@ -5038,7 +5229,7 @@ nm_device_autoconnect_allowed (NMDevice *self)
 
 static gboolean
 can_auto_connect (NMDevice *self,
-                  NMConnection *connection,
+                  NMSettingsConnection *sett_conn,
                   char **specific_object)
 {
 	nm_assert (!specific_object || !*specific_object);
@@ -5048,27 +5239,27 @@ can_auto_connect (NMDevice *self,
 /**
  * nm_device_can_auto_connect:
  * @self: an #NMDevice
- * @connection: a #NMConnection
+ * @sett_conn: a #NMSettingsConnection
  * @specific_object: (out) (transfer full): on output, the path of an
  *   object associated with the returned connection, to be passed to
  *   nm_manager_activate_connection(), or %NULL.
  *
- * Checks if @connection can be auto-activated on @self right now.
+ * Checks if @sett_conn can be auto-activated on @self right now.
  * This requires, at a minimum, that the connection be compatible with
  * @self, and that it have the #NMSettingConnection:autoconnect property
  * set, and that the device allow auto connections. Some devices impose
  * additional requirements. (Eg, a Wi-Fi connection can only be activated
  * if its SSID was seen in the last scan.)
  *
- * Returns: %TRUE, if the @connection can be auto-activated.
+ * Returns: %TRUE, if the @sett_conn can be auto-activated.
  **/
 gboolean
 nm_device_can_auto_connect (NMDevice *self,
-                            NMConnection *connection,
+                            NMSettingsConnection *sett_conn,
                             char **specific_object)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), FALSE);
 	g_return_val_if_fail (!specific_object || !*specific_object, FALSE);
 
 	/* the caller must ensure that nm_device_autoconnect_allowed() returns
@@ -5080,10 +5271,14 @@ nm_device_can_auto_connect (NMDevice *self,
 	 * over and over again. The caller is supposed to do that. */
 	nm_assert (nm_device_autoconnect_allowed (self));
 
-	if (!nm_device_check_connection_available (self, connection, NM_DEVICE_CHECK_CON_AVAILABLE_NONE, NULL))
+	if (!nm_device_check_connection_available (self,
+	                                           nm_settings_connection_get_connection (sett_conn),
+	                                           NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
+	                                           NULL,
+	                                           NULL))
 		return FALSE;
 
-	if (!NM_DEVICE_GET_CLASS (self)->can_auto_connect (self, connection, specific_object))
+	if (!NM_DEVICE_GET_CLASS (self)->can_auto_connect (self, sett_conn, specific_object))
 		return FALSE;
 
 	return TRUE;
@@ -5204,8 +5399,10 @@ nm_device_generate_connection (NMDevice *self,
 	              NM_SETTING_CONNECTION_INTERFACE_NAME, ifname,
 	              NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL),
 	              NULL);
-	if (klass->connection_type)
-		g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, klass->connection_type, NULL);
+
+	if (klass->connection_type_supported)
+		g_object_set (s_con, NM_SETTING_CONNECTION_TYPE, klass->connection_type_supported, NULL);
+
 	nm_connection_add_setting (connection, s_con);
 
 	/* If the device is a slave, update various slave settings */
@@ -5357,9 +5554,9 @@ nm_device_match_parent (NMDevice *self, const char *parent)
 }
 
 gboolean
-nm_device_match_hwaddr (NMDevice *device,
-                        NMConnection *connection,
-                        gboolean fail_if_no_hwaddr)
+nm_device_match_parent_hwaddr (NMDevice *device,
+                               NMConnection *connection,
+                               gboolean fail_if_no_hwaddr)
 {
 	NMSettingWired *s_wired;
 	NMDevice *parent_device;
@@ -5383,21 +5580,54 @@ nm_device_match_hwaddr (NMDevice *device,
 }
 
 static gboolean
-check_connection_compatible (NMDevice *self, NMConnection *connection)
+check_connection_compatible (NMDevice *self, NMConnection *connection, GError **error)
 {
 	const char *device_iface = nm_device_get_iface (self);
-	gs_free char *conn_iface = nm_manager_get_connection_iface (nm_manager_get (),
-	                                                            connection,
-	                                                            NULL, NULL);
+	gs_free_error GError *local = NULL;
+	gs_free char *conn_iface = NULL;
+	NMDeviceClass *klass;
+	const char *const *patterns;
+	NMSettingMatch *s_match;
+	guint num_patterns;
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (klass->connection_type_check_compatible) {
+		if (!_nm_connection_check_main_setting (connection,
+		                                        klass->connection_type_check_compatible,
+		                                        error))
+			return FALSE;
+	}
+
+	conn_iface = nm_manager_get_connection_iface (nm_manager_get (),
+	                                              connection,
+	                                              NULL,
+	                                              &local);
 
 	/* We always need a interface name for virtual devices, but for
 	 * physical ones a connection without interface name is fine for
 	 * any device. */
-	if (!conn_iface)
-		return !nm_connection_is_virtual (connection);
-
-	if (strcmp (conn_iface, device_iface) != 0)
+	if (!conn_iface) {
+		if (nm_connection_is_virtual (connection)) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                    "cannot get interface name due to %s", local->message);
+			return FALSE;
+		}
+	} else if (!nm_streq0 (conn_iface, device_iface)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "mismatching interface name");
 		return FALSE;
+	}
+
+	s_match = (NMSettingMatch *) nm_connection_get_setting (connection,
+	                                                        NM_TYPE_SETTING_MATCH);
+	if (s_match) {
+		patterns = nm_setting_match_get_interface_names (s_match, &num_patterns);
+		if (!nm_wildcard_match_check (device_iface, patterns, num_patterns)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device does not satisfy match.interface-name property");
+			return FALSE;
+		}
+	}
 
 	return TRUE;
 }
@@ -5406,6 +5636,11 @@ check_connection_compatible (NMDevice *self, NMConnection *connection)
  * nm_device_check_connection_compatible:
  * @self: an #NMDevice
  * @connection: an #NMConnection
+ * @error: optional reason why it is incompatible. Note that the
+ *   error code is set to %NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+ *   if the profile is fundamentally incompatible with the device
+ *   (most commonly, because the device-type does not support the
+ *   connection-type).
  *
  * Checks if @connection could potentially be activated on @self.
  * This means only that @self has the proper capabilities, and that
@@ -5418,12 +5653,12 @@ check_connection_compatible (NMDevice *self, NMConnection *connection)
  *   @self.
  */
 gboolean
-nm_device_check_connection_compatible (NMDevice *self, NMConnection *connection)
+nm_device_check_connection_compatible (NMDevice *self, NMConnection *connection, GError **error)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
 
-	return NM_DEVICE_GET_CLASS (self)->check_connection_compatible (self, connection);
+	return NM_DEVICE_GET_CLASS (self)->check_connection_compatible (self, connection, error);
 }
 
 gboolean
@@ -5439,7 +5674,7 @@ nm_device_check_slave_connection_compatible (NMDevice *self, NMConnection *slave
 		return FALSE;
 
 	/* All masters should have connection type set */
-	connection_type = NM_DEVICE_GET_CLASS (self)->connection_type;
+	connection_type = NM_DEVICE_GET_CLASS (self)->connection_type_supported;
 	g_return_val_if_fail (connection_type, FALSE);
 
 	s_con = nm_connection_get_setting_connection (slave);
@@ -5810,24 +6045,151 @@ lldp_rx_enabled (NMDevice *self)
 
 	lldp = nm_setting_connection_get_lldp (s_con);
 	if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) {
-		gs_free char *value = NULL;
-
-		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                               "connection.lldp",
-		                                               self);
-		lldp = _nm_utils_ascii_str_to_int64 (value, 10,
-		                                     NM_SETTING_CONNECTION_LLDP_DEFAULT,
-		                                     NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
-		                                     NM_SETTING_CONNECTION_LLDP_DEFAULT);
+		lldp = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+		                                                    "connection.lldp",
+		                                                    self,
+		                                                    NM_SETTING_CONNECTION_LLDP_DEFAULT,
+		                                                    NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
+		                                                    NM_SETTING_CONNECTION_LLDP_DEFAULT);
 		if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT)
 			lldp = NM_SETTING_CONNECTION_LLDP_DISABLE;
 	}
 	return lldp == NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
 }
 
+static NMPlatformVF *
+sriov_vf_config_to_platform (NMDevice *self,
+                             NMSriovVF *vf,
+                             GError **error)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gs_free NMPlatformVF *plat_vf = NULL;
+	const guint *vlan_ids;
+	GVariant *variant;
+	guint i, num_vlans;
+	gsize length;
+
+	g_return_val_if_fail (!error || !*error, FALSE);
+
+	vlan_ids = nm_sriov_vf_get_vlan_ids (vf, &num_vlans);
+	plat_vf = g_malloc0 (  sizeof (NMPlatformVF)
+	                     + sizeof (NMPlatformVFVlan) * num_vlans);
+
+	plat_vf->index = nm_sriov_vf_get_index (vf);
+
+	variant = nm_sriov_vf_get_attribute (vf, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK);
+	if (variant)
+		plat_vf->spoofchk = g_variant_get_boolean (variant);
+	else
+		plat_vf->spoofchk = -1;
+
+	variant = nm_sriov_vf_get_attribute (vf, NM_SRIOV_VF_ATTRIBUTE_TRUST);
+	if (variant)
+		plat_vf->trust = g_variant_get_boolean (variant);
+	else
+		plat_vf->trust = -1;
+
+	variant = nm_sriov_vf_get_attribute (vf, NM_SRIOV_VF_ATTRIBUTE_MAC);
+	if (variant) {
+		if (!_nm_utils_hwaddr_aton (g_variant_get_string (variant, NULL),
+		                            plat_vf->mac.data,
+		                            sizeof (plat_vf->mac.data),
+		                            &length)) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_FAILED,
+			             "invalid MAC %s",
+			             g_variant_get_string (variant, NULL));
+			return NULL;
+		}
+		if (length != priv->hw_addr_len) {
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_FAILED,
+			             "wrong MAC length %" G_GSIZE_FORMAT ", should be %u",
+			             length, priv->hw_addr_len);
+			return NULL;
+		}
+		plat_vf->mac.len = length;
+	}
+
+	variant = nm_sriov_vf_get_attribute (vf, NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE);
+	if (variant)
+		plat_vf->min_tx_rate = g_variant_get_uint32 (variant);
+
+	variant = nm_sriov_vf_get_attribute (vf, NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE);
+	if (variant)
+		plat_vf->max_tx_rate = g_variant_get_uint32 (variant);
+
+	plat_vf->num_vlans = num_vlans;
+	plat_vf->vlans = (NMPlatformVFVlan *) (&plat_vf[1]);
+	for (i = 0; i < num_vlans; i++) {
+		plat_vf->vlans[i].id = vlan_ids[i];
+		plat_vf->vlans[i].qos = nm_sriov_vf_get_vlan_qos (vf, vlan_ids[i]);
+		plat_vf->vlans[i].proto_ad = nm_sriov_vf_get_vlan_protocol (vf, vlan_ids[i]) == NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD;
+	}
+
+	return g_steal_pointer (&plat_vf);
+}
+
 static NMActStageReturn
 act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 {
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMSettingSriov *s_sriov;
+	guint i, num;
+
+	if (   priv->ifindex > 0
+	    && nm_device_has_capability (self, NM_DEVICE_CAP_SRIOV)
+	    && (s_sriov = (NMSettingSriov *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
+		nm_auto_freev NMPlatformVF **plat_vfs = NULL;
+		gs_free_error GError *error = NULL;
+		NMSriovVF *vf;
+		int autoprobe;
+
+		autoprobe = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
+		if (autoprobe == NM_TERNARY_DEFAULT) {
+			autoprobe = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+			                                                         "sriov.autoprobe-drivers",
+			                                                         self,
+			                                                         NM_TERNARY_FALSE,
+			                                                         NM_TERNARY_TRUE,
+			                                                         NM_TERNARY_TRUE);
+		}
+
+		num = nm_setting_sriov_get_num_vfs (s_sriov);
+		plat_vfs = g_new0 (NMPlatformVF *, num + 1);
+		for (i = 0; i < num; i++) {
+			vf = nm_setting_sriov_get_vf (s_sriov, i);
+			plat_vfs[i] = sriov_vf_config_to_platform (self, vf, &error);
+			if (!plat_vfs[i]) {
+				_LOGE (LOGD_DEVICE,
+				       "failed to apply SR-IOV VF '%s': %s",
+				       nm_utils_sriov_vf_to_str (vf, FALSE, NULL),
+				       error->message);
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
+				return NM_ACT_STAGE_RETURN_FAILURE;
+			}
+		}
+
+		if (!nm_platform_link_set_sriov_params (nm_device_get_platform (self),
+		                                        priv->ifindex,
+		                                        nm_setting_sriov_get_total_vfs (s_sriov),
+		                                        autoprobe)) {
+			_LOGE (LOGD_DEVICE, "failed to apply SR-IOV parameters");
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+
+		if (!nm_platform_link_set_sriov_vfs (nm_device_get_platform (self),
+		                                     priv->ifindex,
+		                                     (const NMPlatformVF *const *) plat_vfs)) {
+			_LOGE (LOGD_DEVICE, "failed to apply SR-IOV VFs");
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+	}
+
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -6031,6 +6393,8 @@ activate_stage2_device_config (NMDevice *self)
 	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
 		NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
+		_ethtool_state_set (self);
+
 		if (!tc_commit (self)) {
 			_LOGW (LOGD_IP6, "failed applying traffic control rules");
 			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
@@ -6145,27 +6509,22 @@ get_ipv4_dad_timeout (NMDevice *self)
 {
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4 = NULL;
-	gs_free char *value = NULL;
-	gint ret = 0;
+	int timeout = -1;
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection)
 		s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	if (s_ip4)
+		timeout = nm_setting_ip_config_get_dad_timeout (s_ip4);
+	if (timeout >= 0)
+		return timeout;
 
-	if (s_ip4) {
-		ret = nm_setting_ip_config_get_dad_timeout (s_ip4);
-
-		if (ret < 0) {
-			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-			                                               "ipv4.dad-timeout", self);
-			ret = _nm_utils_ascii_str_to_int64 (value, 10, -1,
-			                                    NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
-			                                    -1);
-			ret = ret < 0 ? 0 : ret;
-		}
-	}
-
-	return ret;
+	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                    "ipv4.dad-timeout",
+	                                                    self,
+	                                                    0,
+	                                                    NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
+	                                                    0);
 }
 
 static void
@@ -6185,15 +6544,30 @@ acd_data_destroy (gpointer ptr, GClosure *closure)
 static void
 ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean success)
 {
+	NMConnection *connection;
+	const char *method;
 	NMIP4Config *empty;
 
-	if (success) {
+	connection = nm_device_get_applied_connection (self);
+	nm_assert (connection);
+	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+	nm_assert (NM_IN_STRSET (method,
+	                         NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+	                         NM_SETTING_IP4_CONFIG_METHOD_AUTO));
+
+	if (!success) {
+		nm_device_ip_method_failed (self, AF_INET,
+		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
+		return;
+	}
+
+	if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
 		empty = _ip4_config_new (self);
 		nm_device_activate_schedule_ip4_config_result (self, empty);
 		g_object_unref (empty);
 	} else {
-		nm_device_ip_method_failed (self, AF_INET,
-		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
+		if (NM_DEVICE_GET_PRIVATE (self)->ip4_state != IP_DONE)
+			ip_config_merge_and_apply (self, AF_INET, TRUE);
 	}
 }
 
@@ -6531,6 +6905,7 @@ ensure_con_ip_config (NMDevice *self, int addr_family)
 		nm_ip4_config_merge_setting (NM_IP4_CONFIG (con_ip_config),
 		                             nm_connection_get_setting_ip4_config (connection),
 		                             _get_mdns (self),
+		                             _get_llmnr (self),
 		                             nm_device_get_route_table (self, addr_family, TRUE),
 		                             nm_device_get_route_metric (self, addr_family));
 	} else {
@@ -6943,6 +7318,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
 			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
+			                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
 			                             nm_device_get_route_table (self, AF_INET, TRUE),
 			                             nm_device_get_route_metric (self, AF_INET));
 
@@ -6997,19 +7373,14 @@ get_dhcp_timeout (NMDevice *self, int addr_family)
 	if (timeout)
 		return timeout;
 
-	{
-		gs_free char *value = NULL;
-
-		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                               addr_family == AF_INET
-		                                                 ? "ipv4.dhcp-timeout"
-		                                                 : "ipv6.dhcp-timeout",
-		                                               self);
-		timeout = _nm_utils_ascii_str_to_int64 (value, 10,
-		                                        0, G_MAXINT32, 0);
-		if (timeout)
-			return timeout;
-	}
+	timeout = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                       addr_family == AF_INET
+	                                                         ? "ipv4.dhcp-timeout"
+	                                                         : "ipv6.dhcp-timeout",
+	                                                       self,
+	                                                       0, G_MAXINT32, 0);
+	if (timeout)
+		return timeout;
 
 	klass = NM_DEVICE_GET_CLASS (self);
 	if (klass->get_dhcp_timeout)
@@ -7443,30 +7814,45 @@ act_stage3_ip4_config_start (NMDevice *self,
 
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
 
-	/* Start IPv4 addressing based on the method requested */
-	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
-		ret = dhcp4_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_FAILURE)
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
-	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) == 0) {
-		ret = ipv4ll_start (self);
-		if (ret == NM_ACT_STAGE_RETURN_FAILURE)
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
-	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) {
+	if (NM_IN_STRSET (method,
+	                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+	                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+		NMSettingIPConfig *s_ip4;
 		NMIP4Config **configs, *config;
+		guint num_addresses;
 
-		config = _ip4_config_new (self);
-		nm_ip4_config_merge_setting (config,
-		                             nm_connection_get_setting_ip4_config (connection),
-		                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
-		                             nm_device_get_route_table (self, AF_INET, TRUE),
-		                             nm_device_get_route_metric (self, AF_INET));
+		s_ip4 = nm_connection_get_setting_ip4_config (connection);
+		g_return_val_if_fail (s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
+		num_addresses = nm_setting_ip_config_get_num_addresses (s_ip4);
+
+		if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+			ret = dhcp4_start (self);
+			if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+				NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
+				return ret;
+			}
+		} else {
+			g_return_val_if_fail (num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
+			ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		}
 
-		configs = g_new0 (NMIP4Config *, 2);
-		configs[0] = config;
-		ipv4_dad_start (self, configs, ipv4_manual_method_apply);
-		ret = NM_ACT_STAGE_RETURN_POSTPONE;
-	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) == 0) {
+		if (num_addresses) {
+			config = _ip4_config_new (self);
+			nm_ip4_config_merge_setting (config,
+			                             nm_connection_get_setting_ip4_config (connection),
+			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
+			                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
+			                             nm_device_get_route_table (self, AF_INET, TRUE),
+			                             nm_device_get_route_metric (self, AF_INET));
+			configs = g_new0 (NMIP4Config *, 2);
+			configs[0] = config;
+			ipv4_dad_start (self, configs, ipv4_manual_method_apply);
+		}
+	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+		ret = ipv4ll_start (self);
+		if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
+	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
 		if (out_config) {
 			*out_config = shared4_new_config (self, connection);
 			if (*out_config) {
@@ -7478,7 +7864,7 @@ act_stage3_ip4_config_start (NMDevice *self,
 			}
 		} else
 			g_return_val_if_reached (NM_ACT_STAGE_RETURN_FAILURE);
-	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0)
+	} else if (nm_streq (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
 		ret = NM_ACT_STAGE_RETURN_SUCCESS;
 	else
 		_LOGW (LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
@@ -7743,35 +8129,35 @@ static GBytes *
 generate_duid_llt (const guint8 *hwaddr /* ETH_ALEN bytes */,
                    gint64 time)
 {
-	GByteArray *duid_arr;
+	guint8 *arr;
 	const guint16 duid_type = htons (1);
 	const guint16 hw_type = htons (ARPHRD_ETHER);
 	const guint32 duid_time = htonl (NM_MAX (0, time - EPOCH_DATETIME_200001010000));
 
-	duid_arr = g_byte_array_sized_new (2 + 4 + 2 + ETH_ALEN);
+	arr = g_new (guint8, 2 + 2 + 4 + ETH_ALEN);
 
-	g_byte_array_append (duid_arr, (const guint8 *) &duid_type, 2);
-	g_byte_array_append (duid_arr, (const guint8 *) &hw_type, 2);
-	g_byte_array_append (duid_arr, (const guint8 *) &duid_time, 4);
-	g_byte_array_append (duid_arr, hwaddr, ETH_ALEN);
+	memcpy (&arr[0], &duid_type, 2);
+	memcpy (&arr[2], &hw_type, 2);
+	memcpy (&arr[4], &duid_time, 4);
+	memcpy (&arr[8], hwaddr, ETH_ALEN);
 
-	return g_byte_array_free_to_bytes (duid_arr);
+	return g_bytes_new_take (arr, 2 + 2 + 4 + ETH_ALEN);
 }
 
 static GBytes *
 generate_duid_ll (const guint8 *hwaddr /* ETH_ALEN bytes */)
 {
-	GByteArray *duid_arr;
+	guint8 *arr;
 	const guint16 duid_type = htons (3);
 	const guint16 hw_type = htons (ARPHRD_ETHER);
 
-	duid_arr = g_byte_array_sized_new (2 + 2 + ETH_ALEN);
+	arr = g_new (guint8, 2 + 2 + ETH_ALEN);
 
-	g_byte_array_append (duid_arr, (const guint8 *) &duid_type, 2);
-	g_byte_array_append (duid_arr, (const guint8 *) &hw_type, 2);
-	g_byte_array_append (duid_arr, hwaddr, ETH_ALEN);
+	memcpy (&arr[0], &duid_type, 2);
+	memcpy (&arr[2], &hw_type, 2);
+	memcpy (&arr[4], hwaddr, ETH_ALEN);
 
-	return g_byte_array_free_to_bytes (duid_arr);
+	return g_bytes_new_take (arr, 2 + 2 + ETH_ALEN);
 }
 
 static GBytes *
@@ -8367,10 +8753,10 @@ gint64
 nm_device_get_configured_mtu_from_connection_default (NMDevice *self,
                                                       const char *property_name)
 {
-	gs_free char *str = NULL;
-
-	str = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA, property_name, self);
-	return _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXUINT32, -1);
+	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                    property_name,
+	                                                    self,
+	                                                    0, G_MAXUINT32, -1);
 }
 
 guint32
@@ -9023,7 +9409,8 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 		_LOGD (LOGD_IP6, "will %s userland IPv6LL", detail);
 		plerr = nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), ifindex, enable);
 		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
-			_NMLOG (plerr == NM_PLATFORM_ERROR_NOT_FOUND ? LOGL_DEBUG : LOGL_WARN,
+			_NMLOG ((   plerr == NM_PLATFORM_ERROR_NOT_FOUND
+			         || plerr == NM_PLATFORM_ERROR_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN,
 			        LOGD_IP6,
 			        "failed to %s userspace IPv6LL address handling (%s)",
 			        detail,
@@ -9066,7 +9453,6 @@ static NMSettingIP6ConfigPrivacy
 _ip6_privacy_get (NMDevice *self)
 {
 	NMSettingIP6ConfigPrivacy ip6_privacy;
-	gs_free char *value = NULL;
 	NMConnection *connection;
 
 	g_return_val_if_fail (self, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
@@ -9085,14 +9471,13 @@ _ip6_privacy_get (NMDevice *self)
 		}
 	}
 
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ipv6.ip6-privacy", self);
-
 	/* 2.) use the default value from the configuration. */
-	ip6_privacy = _nm_utils_ascii_str_to_int64 (value, 10,
-	                                            NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
-	                                            NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
-	                                            NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+	ip6_privacy = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                           "ipv6.ip6-privacy",
+	                                                           self,
+	                                                           NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
+	                                                           NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+	                                                           NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 	if (ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN)
 		return ip6_privacy;
 
@@ -10322,6 +10707,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 		nm_ip4_config_merge_setting (priv->con_ip_config_4,
 		                             s_ip4_new,
 		                             _get_mdns (self),
+		                             _get_llmnr (self),
 		                             nm_device_get_route_table (self, AF_INET, TRUE),
 		                             nm_device_get_route_metric (self, AF_INET));
 
@@ -10337,10 +10723,37 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			_set_ip_state (self, AF_INET, IP_WAIT);
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
-		} else {
-			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
-				_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
+			return;
 		}
+
+		if (s_ip4_old && s_ip4_new) {
+			gint64 metric_old, metric_new;
+
+			/* For dynamic IP methods (DHCP, IPv4LL, WWAN) the route metric is
+			 * set at activation/renewal time using the value from static
+			 * configuration. To support runtime change we need to update the
+			 * dynamic configuration in place and tell the DHCP client the new
+			 * value to use for future renewals.
+			 */
+			metric_old = nm_setting_ip_config_get_route_metric (s_ip4_old);
+			metric_new = nm_setting_ip_config_get_route_metric (s_ip4_new);
+
+			if (metric_old != metric_new) {
+				if (priv->dev_ip4_config.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->dev_ip4_config.orig,
+					                                    nm_device_get_route_metric (self, AF_INET));
+				}
+				if (priv->wwan_ip_config_4.orig) {
+					nm_ip4_config_update_routes_metric ((NMIP4Config *) priv->wwan_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, metric_new);
+			}
+		}
+
+		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
+			_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
 	}
 }
 
@@ -10382,10 +10795,36 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 			_set_ip_state (self, AF_INET6, IP_WAIT);
 			if (!nm_device_activate_stage3_ip6_start (self))
 				_LOGW (LOGD_IP6, "Failed to apply IPv6 configuration");
-		} else {
-			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
-				_LOGW (LOGD_IP4, "Failed to reapply IPv6 configuration");
+			return;
+		}
+
+		if (s_ip6_old && s_ip6_new) {
+			gint64 metric_old, metric_new;
+
+			/* See comment in nm_device_reactivate_ip6_config() */
+			metric_old = nm_setting_ip_config_get_route_metric (s_ip6_old);
+			metric_new = nm_setting_ip_config_get_route_metric (s_ip6_new);
+
+			if (metric_old != metric_new) {
+				if (priv->ac_ip6_config.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->ac_ip6_config.orig,
+					                                    nm_device_get_route_metric (self, AF_INET6));
+				}
+				if (priv->dhcp6.ip6_config.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->dhcp6.ip6_config.orig,
+					                                    nm_device_get_route_metric (self, AF_INET6));
+				}
+				if (priv->wwan_ip_config_6.orig) {
+					nm_ip6_config_update_routes_metric ((NMIP6Config *) priv->wwan_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, metric_new);
+			}
 		}
+
+		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
+			_LOGW (LOGD_IP4, "Failed to reapply IPv6 configuration");
 	}
 }
 
@@ -10440,7 +10879,9 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 		                                          NM_SETTING_CONNECTION_AUTOCONNECT,
 		                                          NM_SETTING_CONNECTION_ZONE,
 		                                          NM_SETTING_CONNECTION_METERED,
-		                                          NM_SETTING_CONNECTION_LLDP);
+		                                          NM_SETTING_CONNECTION_LLDP,
+		                                          NM_SETTING_CONNECTION_MDNS,
+		                                          NM_SETTING_CONNECTION_LLMNR);
 	} else if (NM_IN_STRSET (setting_name,
 	                         NM_SETTING_PROXY_SETTING_NAME)) {
 		return TRUE;
@@ -10684,7 +11125,7 @@ reapply_cb (NMDevice *self,
 		nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
 
 	if (!check_and_reapply_connection (self,
-	                                   connection ? : (NMConnection *) nm_device_get_settings_connection (self),
+	                                   connection ?: nm_device_get_settings_connection_get_connection (self),
 	                                   version_id,
 	                                   &audit_args,
 	                                   &local)) {
@@ -11171,7 +11612,7 @@ _carrier_wait_check_act_request_must_queue (NMDevice *self, NMActRequest *req)
 	if (!connection_requires_carrier (connection))
 		return FALSE;
 
-	if (!nm_device_check_connection_available (self, connection, NM_DEVICE_CHECK_CON_AVAILABLE_ALL, NULL)) {
+	if (!nm_device_check_connection_available (self, connection, NM_DEVICE_CHECK_CON_AVAILABLE_ALL, NULL, NULL)) {
 		/* We passed all @flags we have, and no @specific_object.
 		 * This equals maximal availability, if a connection is not available
 		 * in this case, it is not waiting for carrier.
@@ -11182,7 +11623,9 @@ _carrier_wait_check_act_request_must_queue (NMDevice *self, NMActRequest *req)
 		return FALSE;
 	}
 
-	if (nm_device_check_connection_available (self, connection, NM_DEVICE_CHECK_CON_AVAILABLE_ALL & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER, NULL)) {
+	if (nm_device_check_connection_available (self, connection,
+	                                          NM_DEVICE_CHECK_CON_AVAILABLE_ALL & ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER,
+	                                          NULL, NULL)) {
 		/* The connection was available with flags ALL, and it is still available
 		 * if we pretend not to wait for carrier. That means that the
 		 * connection is available now, and does not wait for carrier.
@@ -11470,7 +11913,8 @@ nm_device_set_ip_config (NMDevice *self,
 		                     NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
 		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request.obj)) == NM_ACTIVATION_TYPE_EXTERNAL) {
 			g_object_freeze_notify (G_OBJECT (settings_connection));
-			nm_connection_add_setting (NM_CONNECTION (settings_connection),
+			/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+			nm_connection_add_setting (nm_settings_connection_get_connection (settings_connection),
 			                           IS_IPv4
 			                             ? nm_ip4_config_create_setting (priv->ip_config_4)
 			                             : nm_ip6_config_create_setting (priv->ip_config_6));
@@ -11679,11 +12123,11 @@ spawn_ping (NMDevice *self)
 	gboolean ret;
 
 	args[6] = str_timeout = g_strdup_printf ("%u", priv->gw_ping.deadline);
-	tmp_str = g_strjoinv (" ", (gchar **) args);
+	tmp_str = g_strjoinv (" ", (char **) args);
 	_LOGD (priv->gw_ping.log_domain, "ping: running '%s'", tmp_str);
 
 	ret = g_spawn_async ("/",
-	                     (gchar **) args,
+	                     (char **) args,
 	                      NULL,
 	                      G_SPAWN_DO_NOT_REAP_CHILD,
 	                      NULL,
@@ -11719,7 +12163,7 @@ respawn_ping_cb (gpointer user_data)
 }
 
 static void
-ip_check_ping_watch_cb (GPid pid, gint status, gpointer user_data)
+ip_check_ping_watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -12929,7 +13373,7 @@ nm_device_reapply_settings_immediately (NMDevice *self)
 	                                                               NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY))
 		return;
 
-	s_con_settings = nm_connection_get_setting_connection ((NMConnection *) settings_connection);
+	s_con_settings = nm_connection_get_setting_connection (nm_settings_connection_get_connection (settings_connection));
 	s_con_applied = nm_connection_get_setting_connection (applied_connection);
 
 	if (g_strcmp0 ((zone = nm_setting_connection_get_zone (s_con_settings)),
@@ -13043,38 +13487,89 @@ static gboolean
 _nm_device_check_connection_available (NMDevice *self,
                                        NMConnection *connection,
                                        NMDeviceCheckConAvailableFlags flags,
-                                       const char *specific_object)
+                                       const char *specific_object,
+                                       GError **error)
 {
 	NMDeviceState state;
+	GError *local = NULL;
 
 	/* an unrealized software device is always available, hardware devices never. */
 	if (!nm_device_is_real (self)) {
-		if (nm_device_is_software (self))
-			return nm_device_check_connection_compatible (self, connection);
+		if (nm_device_is_software (self)) {
+			if (!nm_device_check_connection_compatible (self, connection,
+			                                            error ? &local : NULL)) {
+				if (error) {
+					g_return_val_if_fail (local, FALSE);
+					nm_utils_error_set (error,
+					                    local->domain == NM_UTILS_ERROR
+					                      ? local->code
+					                      : NM_UTILS_ERROR_UNKNOWN,
+					                    "profile is not compatible with software device (%s)",
+					                    local->message);
+					g_error_free (local);
+				}
+				return FALSE;
+			}
+			return TRUE;
+		}
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
+		                            "hardware device is not realized");
 		return FALSE;
 	}
 
 	state = nm_device_get_state (self);
-	if (state < NM_DEVICE_STATE_UNMANAGED)
-		return FALSE;
-	if (   state < NM_DEVICE_STATE_UNAVAILABLE
-	    && (   (   !NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)
-	            && !nm_device_get_managed (self, FALSE))
-	        || (    NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)
-	            && !nm_device_get_managed (self, TRUE))))
+	if (state < NM_DEVICE_STATE_UNMANAGED) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
+		                            "device is in unknown state");
 		return FALSE;
+	}
+	if (state < NM_DEVICE_STATE_UNAVAILABLE) {
+		if (NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)) {
+			if (!nm_device_get_managed (self, TRUE)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
+				                            "device is unmanaged");
+				return FALSE;
+			}
+		} else {
+			if (!nm_device_get_managed (self, FALSE)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
+				                            "device is unmanaged for interal request");
+				return FALSE;
+			}
+		}
+	}
 	if (   state < NM_DEVICE_STATE_DISCONNECTED
-	    && !nm_device_is_software (self)
-	    && (   (   !NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)
-	            && !nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE))
-	        || (    NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)
-	            && !nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST))))
-		return FALSE;
+	    && !nm_device_is_software (self)) {
+		if (NM_FLAGS_ANY (flags, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)) {
+			if (!nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_FOR_USER_REQUEST)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "device is not available");
+				return FALSE;
+			}
+		} else {
+			if (!nm_device_is_available (self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "device is not available for internal request");
+				return FALSE;
+			}
+		}
+	}
 
-	if (!nm_device_check_connection_compatible (self, connection))
+	if (!nm_device_check_connection_compatible (self, connection,
+	                                            error ? &local : NULL)) {
+		if (error) {
+			nm_utils_error_set (error,
+			                    local->domain == NM_UTILS_ERROR
+			                      ? local->code
+			                      : NM_UTILS_ERROR_UNKNOWN,
+			                    "profile is not compatible with device (%s)",
+			                    local->message);
+			g_error_free (local);
+		}
 		return FALSE;
+	}
 
-	return NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, flags, specific_object);
+	return NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, flags, specific_object, error);
 }
 
 /**
@@ -13087,6 +13582,7 @@ _nm_device_check_connection_available (NMDevice *self,
  * @specific_object: a device type dependent argument to further
  *   filter the result. Passing a non %NULL specific object can only reduce
  *   the availability of a connection.
+ * @error: optionally give reason why not available.
  *
  * Check if @connection is available to be activated on @self.
  *
@@ -13096,11 +13592,12 @@ gboolean
 nm_device_check_connection_available (NMDevice *self,
                                       NMConnection *connection,
                                       NMDeviceCheckConAvailableFlags flags,
-                                      const char *specific_object)
+                                      const char *specific_object,
+                                      GError **error)
 {
 	gboolean available;
 
-	available = _nm_device_check_connection_available (self, connection, flags, specific_object);
+	available = _nm_device_check_connection_available (self, connection, flags, specific_object, error);
 
 #if NM_MORE_ASSERTS >= 2
 	{
@@ -13110,7 +13607,7 @@ nm_device_check_connection_available (NMDevice *self,
 		gboolean available_all[NM_DEVICE_CHECK_CON_AVAILABLE_ALL + 1] = { FALSE };
 
 		for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++)
-			available_all[i] = _nm_device_check_connection_available (self, connection, i, specific_object);
+			available_all[i] = _nm_device_check_connection_available (self, connection, i, specific_object, NULL);
 
 		for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++) {
 			for (j = 1; j <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; j <<= 1) {
@@ -13137,22 +13634,23 @@ available_connections_del_all (NMDevice *self)
 }
 
 static gboolean
-available_connections_add (NMDevice *self, NMConnection *connection)
+available_connections_add (NMDevice *self, NMSettingsConnection *sett_conn)
 {
-	return g_hash_table_add (self->_priv->available_connections, g_object_ref (connection));
+	return g_hash_table_add (self->_priv->available_connections, g_object_ref (sett_conn));
 }
 
 static gboolean
-available_connections_del (NMDevice *self, NMConnection *connection)
+available_connections_del (NMDevice *self, NMSettingsConnection *sett_conn)
 {
-	return g_hash_table_remove (self->_priv->available_connections, connection);
+	return g_hash_table_remove (self->_priv->available_connections, sett_conn);
 }
 
 static gboolean
 check_connection_available (NMDevice *self,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
+                            const char *specific_object,
+                            GError **error)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -13178,6 +13676,8 @@ check_connection_available (NMDevice *self,
 	if (nm_device_is_master (self))
 		return TRUE;
 
+	nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+	                            "device has no carrier");
 	return FALSE;
 }
 
@@ -13188,7 +13688,7 @@ nm_device_recheck_available_connections (NMDevice *self)
 	NMSettingsConnection *const*connections;
 	gboolean changed = FALSE;
 	GHashTableIter h_iter;
-	NMConnection *connection;
+	NMSettingsConnection *sett_conn;
 	guint i;
 	gs_unref_hashtable GHashTable *prune_list = NULL;
 
@@ -13199,29 +13699,30 @@ nm_device_recheck_available_connections (NMDevice *self)
 	if (g_hash_table_size (priv->available_connections) > 0) {
 		prune_list = g_hash_table_new (nm_direct_hash, NULL);
 		g_hash_table_iter_init (&h_iter, priv->available_connections);
-		while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL))
-			g_hash_table_add (prune_list, connection);
+		while (g_hash_table_iter_next (&h_iter, (gpointer *) &sett_conn, NULL))
+			g_hash_table_add (prune_list, sett_conn);
 	}
 
 	connections = nm_settings_get_connections (priv->settings, NULL);
 	for (i = 0; connections[i]; i++) {
-		connection = (NMConnection *) connections[i];
+		sett_conn = connections[i];
 
 		if (nm_device_check_connection_available (self,
-		                                          connection,
+		                                          nm_settings_connection_get_connection (sett_conn),
 		                                          NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
+		                                          NULL,
 		                                          NULL)) {
-			if (available_connections_add (self, connection))
+			if (available_connections_add (self, sett_conn))
 				changed = TRUE;
 			if (prune_list)
-				g_hash_table_remove (prune_list, connection);
+				g_hash_table_remove (prune_list, sett_conn);
 		}
 	}
 
 	if (prune_list) {
 		g_hash_table_iter_init (&h_iter, prune_list);
-		while (g_hash_table_iter_next (&h_iter, (gpointer *) &connection, NULL)) {
-			if (available_connections_del (self, connection))
+		while (g_hash_table_iter_next (&h_iter, (gpointer *) &sett_conn, NULL)) {
+			if (available_connections_del (self, sett_conn))
 				changed = TRUE;
 		}
 	}
@@ -13248,7 +13749,7 @@ nm_device_get_best_connection (NMDevice *self,
                                GError **error)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMSettingsConnection *connection = NULL;
+	NMSettingsConnection *sett_conn = NULL;
 	NMSettingsConnection *candidate;
 	guint64 best_timestamp = 0;
 	GHashTableIter iter;
@@ -13262,42 +13763,44 @@ nm_device_get_best_connection (NMDevice *self,
 		 */
 		if (    specific_object /* << Optimization: we know that the connection is available without @specific_object.  */
 		    && !nm_device_check_connection_available (self,
-		                                              NM_CONNECTION (candidate),
+		                                              nm_settings_connection_get_connection (candidate),
 		                                              _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
-		                                              specific_object))
+		                                              specific_object,
+		                                              NULL))
 			continue;
 
 		nm_settings_connection_get_timestamp (candidate, &candidate_timestamp);
-		if (!connection || (candidate_timestamp > best_timestamp)) {
-			connection = candidate;
+		if (!sett_conn || (candidate_timestamp > best_timestamp)) {
+			sett_conn = candidate;
 			best_timestamp = candidate_timestamp;
 		}
 	}
 
-	if (!connection) {
+	if (!sett_conn) {
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
 		             "The device '%s' has no connections available for activation.",
 		              nm_device_get_iface (self));
 	}
 
-	return connection;
+	return sett_conn;
 }
 
 static void
-cp_connection_added_or_updated (NMDevice *self, NMConnection *connection)
+cp_connection_added_or_updated (NMDevice *self, NMSettingsConnection *sett_conn)
 {
 	gboolean changed;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
 
 	if (nm_device_check_connection_available (self,
-	                                          connection,
+	                                          nm_settings_connection_get_connection (sett_conn),
 	                                          _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
+	                                          NULL,
 	                                          NULL))
-		changed = available_connections_add (self, connection);
+		changed = available_connections_add (self, sett_conn);
 	else
-		changed = available_connections_del (self, connection);
+		changed = available_connections_del (self, sett_conn);
 
 	if (changed) {
 		_notify (self, PROP_AVAILABLE_CONNECTIONS);
@@ -13306,25 +13809,25 @@ cp_connection_added_or_updated (NMDevice *self, NMConnection *connection)
 }
 
 static void
-cp_connection_added (NMSettings *settings, NMConnection *connection, gpointer user_data)
+cp_connection_added (NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
 {
-	cp_connection_added_or_updated (user_data, connection);
+	cp_connection_added_or_updated (user_data, sett_conn);
 }
 
 static void
-cp_connection_updated (NMSettings *settings, NMConnection *connection, gboolean by_user, gpointer user_data)
+cp_connection_updated (NMSettings *settings, NMSettingsConnection *sett_conn, gboolean by_user, gpointer user_data)
 {
-	cp_connection_added_or_updated (user_data, connection);
+	cp_connection_added_or_updated (user_data, sett_conn);
 }
 
 static void
-cp_connection_removed (NMSettings *settings, NMConnection *connection, gpointer user_data)
+cp_connection_removed (NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
 {
 	NMDevice *self = user_data;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	if (available_connections_del (self, connection)) {
+	if (available_connections_del (self, sett_conn)) {
 		_notify (self, PROP_AVAILABLE_CONNECTIONS);
 		available_connections_check_delete_unrealized (self);
 	}
@@ -13683,6 +14186,8 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 		priv->ip6_mtu_initial = 0;
 	}
 
+	_ethtool_state_reset (self);
+
 	_cleanup_generic_post (self, cleanup_type);
 }
 
@@ -13962,7 +14467,7 @@ _set_state_full (NMDevice *self,
 	NMDeviceState old_state;
 	NMActRequest *req;
 	gboolean no_firmware = FALSE;
-	NMSettingsConnection *connection;
+	NMSettingsConnection *sett_conn;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
@@ -14221,10 +14726,10 @@ _set_state_full (NMDevice *self,
 			break;
 		}
 
-		connection = nm_device_get_settings_connection (self);
+		sett_conn = nm_device_get_settings_connection (self);
 		_LOGW (LOGD_DEVICE | LOGD_WIFI,
 		       "Activation: failed for connection '%s'",
-		       connection ? nm_settings_connection_get_id (connection) : "<unknown>");
+		       sett_conn ? nm_settings_connection_get_id (sett_conn) : "<unknown>");
 
 		/* Notify any slaves of the unexpected failure */
 		nm_device_master_release_slaves (self);
@@ -14234,8 +14739,8 @@ _set_state_full (NMDevice *self,
 		 * failed (zero timestamp), connections that succeeded (non-zero timestamp),
 		 * and those we haven't tried yet (no timestamp).
 		 */
-		if (connection && !nm_settings_connection_get_timestamp (connection, NULL))
-			nm_settings_connection_update_timestamp (connection, (guint64) 0, TRUE);
+		if (sett_conn && !nm_settings_connection_get_timestamp (sett_conn, NULL))
+			nm_settings_connection_update_timestamp (sett_conn, (guint64) 0, TRUE);
 
 		/* Schedule the transition to DISCONNECTED.  The device can't transition
 		 * immediately because we can't change states again from the state
@@ -15149,8 +15654,7 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 {
 	NMConnection *connection;
 	NMSetting8021x *s_8021x;
-	gs_free char *value = NULL;
-	gint timeout;
+	int timeout;
 #define SUPPLICANT_DEFAULT_TIMEOUT 25
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), SUPPLICANT_DEFAULT_TIMEOUT);
@@ -15164,11 +15668,12 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 			return timeout;
 	}
 
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "802-1x.auth-timeout",
-	                                               self);
-	return _nm_utils_ascii_str_to_int64 (value, 10, 1, G_MAXINT32,
-	                                     SUPPLICANT_DEFAULT_TIMEOUT);
+	return nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                    "802-1x.auth-timeout",
+	                                                    self,
+	                                                    1,
+	                                                    G_MAXINT32,
+	                                                    SUPPLICANT_DEFAULT_TIMEOUT);
 }
 
 gboolean
@@ -15191,12 +15696,10 @@ nm_device_auth_retries_try_next (NMDevice *self)
 			auth_retries = nm_setting_connection_get_auth_retries (s_con);
 
 		if (auth_retries == -1) {
-			gs_free char *value = NULL;
-
-			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-			                                               "connection.auth-retries",
-			                                               self);
-			auth_retries = _nm_utils_ascii_str_to_int64 (value, 10, -1, G_MAXINT32, -1);
+			auth_retries = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+			                                                            "connection.auth-retries",
+			                                                            self,
+			                                                            -1, G_MAXINT32, -1);
 		}
 
 		if (auth_retries == 0)
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index d145f8fc..7d74e5b9 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -202,7 +202,12 @@ typedef struct _NMDeviceClass {
 	struct _NMDeviceClass *default_type_description_klass;
 	const char *default_type_description;
 
-	const char *connection_type;
+	const char *connection_type_supported;
+
+	/* most device types, can only handle profiles of a particular type. This
+	 * is the connection.type setting, as checked by nm_device_check_connection_compatible() */
+	const char *connection_type_check_compatible;
+
 	const NMLinkType *link_types;
 
 	/* Whether the device type is a master-type. This depends purely on the
@@ -292,7 +297,7 @@ typedef struct _NMDeviceClass {
 	gboolean    (* get_autoconnect_allowed) (NMDevice *self);
 
 	gboolean    (* can_auto_connect) (NMDevice *self,
-	                                  NMConnection *connection,
+	                                  NMSettingsConnection *sett_conn,
 	                                  char **specific_object);
 
 	guint32     (*get_configured_mtu) (NMDevice *self, NMDeviceMtuSource *out_source);
@@ -301,7 +306,9 @@ typedef struct _NMDeviceClass {
 	 * only the devices type and characteristics.  Does not use any live
 	 * network information like WiFi scan lists etc.
 	 */
-	gboolean    (* check_connection_compatible) (NMDevice *self, NMConnection *connection);
+	gboolean    (* check_connection_compatible) (NMDevice *self,
+	                                             NMConnection *connection,
+	                                             GError **error);
 
 	/* Checks whether the connection is likely available to be activated,
 	 * including any live network information like scan lists.  The connection
@@ -317,7 +324,8 @@ typedef struct _NMDeviceClass {
 	gboolean    (* check_connection_available) (NMDevice *self,
 	                                            NMConnection *connection,
 	                                            NMDeviceCheckConAvailableFlags flags,
-	                                            const char *specific_object);
+	                                            const char *specific_object,
+	                                            GError **error);
 
 	gboolean    (* complete_connection)         (NMDevice *self,
 	                                             NMConnection *connection,
@@ -493,6 +501,9 @@ gboolean        nm_device_parent_notify_changed (NMDevice *self,
                                                  NMDevice *change_candidate,
                                                  gboolean device_removed);
 
+const char     *nm_device_parent_find_for_connection (NMDevice *self,
+                                                      const char *current_setting_parent);
+
 /* Master */
 gboolean        nm_device_is_master             (NMDevice *dev);
 
@@ -501,6 +512,7 @@ NMDevice *      nm_device_get_master            (NMDevice *dev);
 
 NMActRequest *  nm_device_get_act_request       (NMDevice *dev);
 NMSettingsConnection *nm_device_get_settings_connection (NMDevice *dev);
+NMConnection *  nm_device_get_settings_connection_get_connection (NMDevice *self);
 NMConnection *  nm_device_get_applied_connection (NMDevice *dev);
 gboolean        nm_device_has_unmodified_applied_connection (NMDevice *self,
                                                              NMSettingCompareFlags compare_flags);
@@ -524,7 +536,7 @@ gboolean nm_device_master_update_slave_connection (NMDevice *master,
                                                    GError **error);
 
 gboolean nm_device_can_auto_connect (NMDevice *self,
-                                     NMConnection *connection,
+                                     NMSettingsConnection *sett_conn,
                                      char **specific_object);
 
 gboolean nm_device_complete_connection (NMDevice *device,
@@ -533,7 +545,10 @@ gboolean nm_device_complete_connection (NMDevice *device,
                                         NMConnection *const*existing_connections,
                                         GError **error);
 
-gboolean nm_device_check_connection_compatible (NMDevice *device, NMConnection *connection);
+gboolean nm_device_check_connection_compatible (NMDevice *device,
+                                                NMConnection *connection,
+                                                GError **error);
+
 gboolean nm_device_check_slave_connection_compatible (NMDevice *device, NMConnection *connection);
 
 gboolean nm_device_unmanage_on_quit (NMDevice *self);
@@ -742,7 +757,8 @@ NMSettingsConnection *nm_device_get_best_connection (NMDevice *device,
 gboolean   nm_device_check_connection_available (NMDevice *device,
                                                  NMConnection *connection,
                                                  NMDeviceCheckConAvailableFlags flags,
-                                                 const char *specific_object);
+                                                 const char *specific_object,
+                                                 GError **error);
 
 gboolean nm_device_notify_component_added (NMDevice *device, GObject *component);
 
diff --git a/src/devices/ovs/meson.build b/src/devices/ovs/meson.build
index f4edd57c..9d637fe9 100644
--- a/src/devices/ovs/meson.build
+++ b/src/devices/ovs/meson.build
@@ -15,7 +15,6 @@ libnm_device_plugin_ovs = shared_module(
   'nm-device-plugin-ovs',
   sources: sources,
   dependencies: deps,
-  c_args: '-DRUNSTATEDIR="@0@"'.format(nm_runstatedir),
   link_args: ldflags_linker_script_devices,
   link_depends: linker_script_devices,
   install: true,
@@ -24,10 +23,10 @@ libnm_device_plugin_ovs = shared_module(
 
 core_plugins += libnm_device_plugin_ovs
 
-run_target(
+test(
   'check-local-devices-ovs',
-  command: [check_exports, libnm_device_plugin_ovs.full_path(), linker_script_devices],
-  depends: libnm_device_plugin_ovs
+  check_exports,
+  args: [libnm_device_plugin_ovs.full_path(), linker_script_devices],
 )
 
 # FIXME: check_so_symbols replacement
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
index 2e4d1cbd..eff355a3 100644
--- a/src/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -76,21 +76,6 @@ get_generic_capabilities (NMDevice *device)
 	return NM_DEVICE_CAP_IS_SOFTWARE;
 }
 
-static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	const char *connection_type;
-
-	if (!NM_DEVICE_CLASS (nm_device_ovs_bridge_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	connection_type = nm_connection_get_connection_type (connection);
-	if (!nm_streq0 (connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME))
-		return FALSE;
-
-	return TRUE;
-}
-
 static NMActStageReturn
 act_stage3_ip4_config_start (NMDevice *device,
                              NMIP4Config **out_config,
@@ -152,13 +137,15 @@ nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ovs_bridge);
 
-	device_class->connection_type = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
+	device_class->connection_type_supported = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES ();
+
 	device_class->is_master = TRUE;
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
 	device_class->unrealize = unrealize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
 	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	device_class->enslave_slave = enslave_slave;
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index c59af7a6..2b48fae6 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -85,25 +85,20 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	NMSettingConnection *s_con;
 	NMSettingOvsInterface *s_ovs_iface;
 
-	if (!NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->check_connection_compatible (device, connection))
+	if (!NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
-	if (!s_ovs_iface)
-		return FALSE;
-	if (!NM_IN_STRSET (nm_setting_ovs_interface_get_interface_type (s_ovs_iface),
-	                   "internal", "patch")) {
-		return FALSE;
-	}
 
-	s_con = nm_connection_get_setting_connection (connection);
-	if (g_strcmp0 (nm_setting_connection_get_connection_type (s_con),
-	               NM_SETTING_OVS_INTERFACE_SETTING_NAME) != 0) {
+	if (!NM_IN_STRSET (nm_setting_ovs_interface_get_interface_type (s_ovs_iface),
+	                   "internal",
+	                   "patch")) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "unsupported OVS interface type in profile");
 		return FALSE;
 	}
 
@@ -199,11 +194,12 @@ nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_OPENVSWITCH);
-
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ovs_interface);
 
-	device_class->connection_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
+	device_class->connection_type_supported = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_OPENVSWITCH);
+
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index a9a2b24a..1f9afbab 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -70,26 +70,6 @@ get_generic_capabilities (NMDevice *device)
 	return NM_DEVICE_CAP_IS_SOFTWARE;
 }
 
-static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	NMSettingConnection *s_con;
-	const char *connection_type;
-
-	if (!NM_DEVICE_CLASS (nm_device_ovs_port_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	connection_type = nm_setting_connection_get_connection_type (s_con);
-	if (!connection_type)
-		return FALSE;
-
-	if (strcmp (connection_type, NM_SETTING_OVS_PORT_SETTING_NAME) == 0)
-		return TRUE;
-
-	return FALSE;
-}
-
 static NMActStageReturn
 act_stage3_ip4_config_start (NMDevice *device,
                              NMIP4Config **out_config,
@@ -198,12 +178,14 @@ nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_ovs_port);
 
-	device_class->connection_type = NM_SETTING_OVS_PORT_SETTING_NAME;
+	device_class->connection_type_supported = NM_SETTING_OVS_PORT_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_OVS_PORT_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES ();
+
 	device_class->is_master = TRUE;
 	device_class->get_type_description = get_type_description;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
 	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	device_class->enslave_slave = enslave_slave;
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index a9cb5703..b00397cf 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -139,7 +139,7 @@ typedef struct {
 static void
 _call_trace (const char *comment, OvsdbMethodCall *call, json_t *msg)
 {
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 	char *str = NULL;
 
 	if (msg)
diff --git a/src/devices/team/meson.build b/src/devices/team/meson.build
index 159c182a..0f0763bd 100644
--- a/src/devices/team/meson.build
+++ b/src/devices/team/meson.build
@@ -21,10 +21,10 @@ libnm_device_plugin_team = shared_module(
 
 core_plugins += libnm_device_plugin_team
 
-run_target(
+test(
   'check-local-devices-team',
-  command: [check_exports, libnm_device_plugin_team.full_path(), linker_script_devices],
-  depends: libnm_device_plugin_team
+  check_exports,
+  args: [libnm_device_plugin_team.full_path(), linker_script_devices],
 )
 
 # FIXME: check_so_symbols replacement
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index f83ba22b..899932fd 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -1,8 +1,6 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
- * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
- *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -16,6 +14,9 @@
  * You should have received a copy of the GNU General Public License along
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -84,23 +85,6 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	NMSettingTeam *s_team;
-
-	if (!NM_DEVICE_CLASS (nm_device_team_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_team = nm_connection_get_setting_team (connection);
-	if (!s_team || !nm_connection_is_type (connection, NM_SETTING_TEAM_SETTING_NAME))
-		return FALSE;
-
-	/* FIXME: match team properties like mode, etc? */
-
-	return TRUE;
-}
-
-static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
@@ -367,8 +351,8 @@ teamd_timeout_cb (gpointer user_data)
 
 static void
 teamd_dbus_appeared (GDBusConnection *connection,
-                     const gchar *name,
-                     const gchar *name_owner,
+                     const char *name,
+                     const char *name_owner,
                      gpointer user_data)
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
@@ -430,7 +414,7 @@ teamd_dbus_appeared (GDBusConnection *connection,
 
 static void
 teamd_dbus_vanished (GDBusConnection *dbus_connection,
-                     const gchar *name,
+                     const char *name,
                      gpointer user_data)
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
@@ -463,7 +447,7 @@ teamd_dbus_vanished (GDBusConnection *dbus_connection,
 }
 
 static void
-teamd_process_watch_cb (GPid pid, gint status, gpointer user_data)
+teamd_process_watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (user_data);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
@@ -516,7 +500,7 @@ teamd_kill (NMDeviceTeam *self, const char *teamd_binary, GError **error)
 	g_ptr_array_add (argv, (gpointer) nm_device_get_iface (NM_DEVICE (self)));
 	g_ptr_array_add (argv, NULL);
 
-	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (gchar **) argv->pdata)));
+	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
 	return g_spawn_sync ("/", (char **) argv->pdata, NULL, 0, teamd_child_setup, NULL, NULL, NULL, NULL, error);
 }
 
@@ -604,7 +588,7 @@ teamd_start (NMDevice *device, NMConnection *connection)
 		g_ptr_array_add (argv, (gpointer) "-gg");
 	g_ptr_array_add (argv, NULL);
 
-	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (gchar **) argv->pdata)));
+	_LOGD (LOGD_TEAM, "running: %s", (tmp_str = g_strjoinv (" ", (char **) argv->pdata)));
 	if (!g_spawn_async ("/", (char **) argv->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
 	                    teamd_child_setup, NULL, &priv->teamd_pid, &error)) {
 		_LOGW (LOGD_TEAM, "Activation: (team) failed to start teamd: %s", error->message);
@@ -703,7 +687,7 @@ enslave_slave (NMDevice *device,
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
-	gboolean success = TRUE, no_firmware = FALSE;
+	gboolean success = TRUE;
 	const char *slave_iface = nm_device_get_ip_iface (slave);
 	NMSettingTeamPort *s_team_port;
 
@@ -738,7 +722,7 @@ enslave_slave (NMDevice *device,
 		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, &no_firmware);
+		nm_device_bring_up (slave, TRUE, NULL);
 
 		if (!success)
 			return FALSE;
@@ -762,7 +746,7 @@ release_slave (NMDevice *device,
 {
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
-	gboolean success, no_firmware = FALSE;
+	gboolean success;
 
 	if (configure) {
 		success = nm_platform_link_release (nm_device_get_platform (device),
@@ -778,7 +762,7 @@ release_slave (NMDevice *device,
 		 * IFF_UP), so we must bring it back up here to ensure carrier changes and
 		 * other state is noticed by the now-released port.
 		 */
-		if (!nm_device_bring_up (slave, TRUE, &no_firmware))
+		if (!nm_device_bring_up (slave, TRUE, NULL))
 			_LOGW (LOGD_TEAM, "released team port %s could not be brought up",
 			       nm_device_get_ip_iface (slave));
 
@@ -910,9 +894,7 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_TEAM_SETTING_NAME, NM_LINK_TYPE_TEAM)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
@@ -920,19 +902,22 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_team);
 
-	parent_class->is_master = TRUE;
-	parent_class->create_and_realize = create_and_realize;
-	parent_class->get_generic_capabilities = get_generic_capabilities;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->complete_connection = complete_connection;
-	parent_class->update_connection = update_connection;
-	parent_class->master_update_slave_connection = master_update_slave_connection;
-
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
-	parent_class->deactivate = deactivate;
-	parent_class->enslave_slave = enslave_slave;
-	parent_class->release_slave = release_slave;
+	device_class->connection_type_supported = NM_SETTING_TEAM_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_TEAM_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_TEAM);
+
+	device_class->is_master = TRUE;
+	device_class->create_and_realize = create_and_realize;
+	device_class->get_generic_capabilities = get_generic_capabilities;
+	device_class->complete_connection = complete_connection;
+	device_class->update_connection = update_connection;
+	device_class->master_update_slave_connection = master_update_slave_connection;
+
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
+	device_class->deactivate = deactivate;
+	device_class->enslave_slave = enslave_slave;
+	device_class->release_slave = release_slave;
 
 	obj_properties[PROP_CONFIG] =
 	    g_param_spec_string (NM_DEVICE_TEAM_CONFIG, "", "",
diff --git a/src/devices/wifi/meson.build b/src/devices/wifi/meson.build
index a27f8e6a..2745040b 100644
--- a/src/devices/wifi/meson.build
+++ b/src/devices/wifi/meson.build
@@ -33,10 +33,10 @@ libnm_device_plugin_wifi = shared_module(
 
 core_plugins += libnm_device_plugin_wifi
 
-run_target(
+test(
   'check-local-devices-wifi',
-  command: [check_exports, libnm_device_plugin_wifi.full_path(), linker_script_devices],
-  depends: libnm_device_plugin_wifi
+  check_exports,
+  args: [libnm_device_plugin_wifi.full_path(), linker_script_devices],
 )
 
 # FIXME: check_so_symbols replacement
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 7667816a..56b1f853 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -67,7 +67,8 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	GDBusObject *   dbus_obj;
-	GDBusProxy *    dbus_proxy;
+	GDBusProxy *    dbus_device_proxy;
+	GDBusProxy *    dbus_station_proxy;
 	CList           aps_lst_head;
 	NMWifiAP *      current_ap;
 	GCancellable *  cancellable;
@@ -204,11 +205,11 @@ remove_all_aps (NMDeviceIwd *self)
 }
 
 static GVariant *
-vardict_from_network_type (const gchar *type)
+vardict_from_network_type (const char *type)
 {
 	GVariantBuilder builder;
-	const gchar *key_mgmt = "";
-	const gchar *pairwise = "ccmp";
+	const char *key_mgmt = "";
+	const char *pairwise = "ccmp";
 
 	if (!strcmp (type, "psk"))
 		key_mgmt = "wpa-psk";
@@ -235,7 +236,7 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *variant = NULL;
 	GVariantIter *networks;
-	const gchar *path, *name, *type;
+	const char *path, *name, *type;
 	int16_t signal;
 	NMWifiAP *ap, *ap_safe, *new_ap;
 	gboolean changed = FALSE;
@@ -246,7 +247,7 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	                                      G_VARIANT_TYPE ("(a(osns))"),
 	                                      &error);
 	if (!variant) {
-		_LOGE (LOGD_WIFI, "Device.GetOrderedNetworks failed: %s",
+		_LOGE (LOGD_WIFI, "Station.GetOrderedNetworks failed: %s",
 		       error->message);
 		return;
 	}
@@ -296,8 +297,11 @@ get_ordered_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 		props = g_variant_new ("a{sv}", &builder);
 
 		ap = nm_wifi_ap_new_from_properties (path, props);
-		if (name[0] != '\0')
-			nm_wifi_ap_set_ssid (ap, (const guint8 *) name, strlen (name));
+
+		nm_wifi_ap_set_ssid_arr (ap,
+		                         (const guint8 *) name,
+		                         NM_MIN (32, strlen (name)));
+
 		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);
@@ -355,7 +359,7 @@ update_aps (NMDeviceIwd *self)
 	if (!priv->cancellable)
 		priv->cancellable = g_cancellable_new ();
 
-	g_dbus_proxy_call (priv->dbus_proxy, "GetOrderedNetworks",
+	g_dbus_proxy_call (priv->dbus_station_proxy, "GetOrderedNetworks",
 	                   g_variant_new ("()"), G_DBUS_CALL_FLAGS_NONE,
 	                   2000, priv->cancellable,
 	                   get_ordered_networks_cb, self);
@@ -366,7 +370,7 @@ send_disconnect (NMDeviceIwd *self)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	g_dbus_proxy_call (priv->dbus_proxy, "Disconnect", g_variant_new ("()"),
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
 	                   G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
 }
 
@@ -405,7 +409,7 @@ deactivate_async_finish (NMDevice *device, GAsyncResult *res, GError **error)
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (NM_DEVICE_IWD (device));
 	gs_unref_variant GVariant *variant = NULL;
 
-	variant = g_dbus_proxy_call_finish (priv->dbus_proxy, res, error);
+	variant = g_dbus_proxy_call_finish (priv->dbus_station_proxy, res, error);
 	return variant != NULL;
 }
 
@@ -441,39 +445,18 @@ deactivate_async (NMDevice *device,
 	ctx->callback = callback;
 	ctx->user_data = user_data;
 
-	g_dbus_proxy_call (priv->dbus_proxy, "Disconnect", g_variant_new ("()"),
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Disconnect", g_variant_new ("()"),
 	                   G_DBUS_CALL_FLAGS_NONE, -1, cancellable, disconnect_cb, ctx);
 }
 
-static NMIwdNetworkSecurity
-get_connection_iwd_security (NMConnection *connection)
-{
-	NMSettingWirelessSecurity *s_wireless_sec;
-	const char *key_mgmt = NULL;
-
-	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
-	if (!s_wireless_sec)
-		return NM_IWD_NETWORK_SECURITY_NONE;
-
-	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wireless_sec);
-	nm_assert (key_mgmt);
-
-	if (!strcmp (key_mgmt, "none") || !strcmp (key_mgmt, "ieee8021x"))
-		return NM_IWD_NETWORK_SECURITY_WEP;
-
-	if (!strcmp (key_mgmt, "wpa-psk"))
-		return NM_IWD_NETWORK_SECURITY_PSK;
-
-	nm_assert (!strcmp (key_mgmt, "wpa-eap"));
-	return NM_IWD_NETWORK_SECURITY_8021X;
-}
-
 static gboolean
 is_connection_known_network (NMConnection *connection)
 {
 	NMSettingWireless *s_wireless;
+	NMIwdNetworkSecurity security;
+	gboolean security_ok;
 	GBytes *ssid;
-	gs_free gchar *str_ssid = NULL;
+	gs_free char *ssid_utf8 = NULL;
 
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	if (!s_wireless)
@@ -483,68 +466,73 @@ is_connection_known_network (NMConnection *connection)
 	if (!ssid)
 		return FALSE;
 
-	str_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
-	                                  g_bytes_get_size (ssid));
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
+	security = nm_wifi_connection_get_iwd_security (connection, &security_ok);
+	if (!security_ok)
+		return FALSE;
 
 	return nm_iwd_manager_is_known_network (nm_iwd_manager_get (),
-	                                        str_ssid,
-	                                        get_connection_iwd_security (connection));
+	                                        ssid_utf8, security);
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	NMSettingConnection *s_con;
 	NMSettingWireless *s_wireless;
 	const char *mac;
 	const char * const *mac_blacklist;
 	int i;
-	const char *mode;
 	const char *perm_hw_addr;
 
-	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME))
+	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	s_wireless = nm_connection_get_setting_wireless (connection);
-	if (!s_wireless)
-		return FALSE;
 
 	perm_hw_addr = nm_device_get_permanent_hw_address (device);
 	mac = nm_setting_wireless_get_mac_address (s_wireless);
 	if (perm_hw_addr) {
-		if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1))
+		if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device MAC address does not match the profile");
 			return FALSE;
+		}
 
 		/* Check for MAC address blacklist */
 		mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless);
 		for (i = 0; mac_blacklist[i]; i++) {
-			if (!nm_utils_hwaddr_valid (mac_blacklist[i], ETH_ALEN)) {
-				g_warn_if_reached ();
-				return FALSE;
-			}
+			nm_assert (nm_utils_hwaddr_valid (mac_blacklist[i], ETH_ALEN));
 
-			if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1))
+			if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "MAC address blacklisted");
 				return FALSE;
+			}
 		}
-	} else if (mac)
+	} else if (mac) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "device has no valid MAC address as required by profile");
 		return FALSE;
+	}
 
-	mode = nm_setting_wireless_get_mode (s_wireless);
-	if (mode && g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+	if (!NM_IN_STRSET (nm_setting_wireless_get_mode (s_wireless),
+	                   NULL,
+	                   NM_SETTING_WIRELESS_MODE_INFRA)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "IWD only support infrastructure type profiles");
 		return FALSE;
+	}
 
 	/* 8021x networks can only be used if they've been provisioned on the IWD side and
 	 * thus are Known Networks.
 	 */
-	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection))
+	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "802.1x profile is not a known network");
 			return FALSE;
+		}
 	}
 
 	return TRUE;
@@ -554,7 +542,8 @@ static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
+                            const char *specific_object,
+                            GError **error)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
@@ -566,19 +555,28 @@ check_connection_available (NMDevice *device,
 
 	/* Only Infrastrusture mode at this time */
 	mode = nm_setting_wireless_get_mode (s_wifi);
-	if (mode && g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) != 0)
+	if (!NM_IN_STRSET (mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "iwd only supports infrastructure mode connections");
 		return FALSE;
+	}
 
 	/* Hidden SSIDs not supported yet */
-	if (nm_setting_wireless_get_hidden (s_wifi))
+	if (nm_setting_wireless_get_hidden (s_wifi)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "hidden networks not supported by iwd");
 		return FALSE;
+	}
 
 	/* 8021x networks can only be used if they've been provisioned on the IWD side and
 	 * thus are Known Networks.
 	 */
-	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
-		if (!is_connection_known_network (connection))
+	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
+		if (!is_connection_known_network (connection)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "network is not known to iwd");
 			return FALSE;
+		}
 	}
 
 	/* a connection that is available for a certain @specific_object, MUST
@@ -588,14 +586,29 @@ check_connection_available (NMDevice *device,
 		NMWifiAP *ap;
 
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
-		return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
+		if (!ap) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "requested access point not found");
+			return FALSE;
+		}
+		if (!nm_wifi_ap_check_compatible (ap, connection)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "requested access point is not compatible with profile");
+			return FALSE;
+		}
+		return TRUE;
 	}
 
 	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
 		return TRUE;
 
-	/* Check at least one AP is compatible with this connection */
-	return !!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+	if (!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "no compatible access point found");
+		return FALSE;
+	}
+
+	return TRUE;
 }
 
 static gboolean
@@ -609,10 +622,9 @@ complete_connection (NMDevice *device,
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *setting_mac;
-	char *str_ssid = NULL;
+	gs_free char *ssid_utf8 = NULL;
 	NMWifiAP *ap;
-	const GByteArray *ssid = NULL;
-	GByteArray *tmp_ssid = NULL;
+	GBytes *ssid;
 	GBytes *setting_ssid = NULL;
 	const char *perm_hw_addr;
 	const char *mode;
@@ -676,8 +688,7 @@ complete_connection (NMDevice *device,
 	}
 
 	ssid = nm_wifi_ap_get_ssid (ap);
-
-	if (ssid == NULL) {
+	if (!ssid) {
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
@@ -688,30 +699,23 @@ complete_connection (NMDevice *device,
 	if (!nm_wifi_ap_complete_connection (ap,
 	                                     connection,
 	                                     nm_wifi_utils_is_manf_default_ssid (ssid),
-	                                     error)) {
-		if (tmp_ssid)
-			g_byte_array_unref (tmp_ssid);
+	                                     error))
 		return FALSE;
-	}
-
-	str_ssid = nm_utils_ssid_to_utf8 (ssid->data, ssid->len);
 
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
 	nm_utils_complete_generic (nm_device_get_platform (device),
 	                           connection,
 	                           NM_SETTING_WIRELESS_SETTING_NAME,
 	                           existing_connections,
-	                           str_ssid,
-	                           str_ssid,
+	                           ssid_utf8,
+	                           ssid_utf8,
 	                           NULL,
 	                           TRUE);
-	g_free (str_ssid);
-	if (tmp_ssid)
-		g_byte_array_unref (tmp_ssid);
 
 	/* 8021x networks can only be used if they've been provisioned on the IWD side and
 	 * thus are Known Networks.
 	 */
-	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
 		if (!is_connection_known_network (connection)) {
 			g_set_error_literal (error,
 			                     NM_CONNECTION_ERROR,
@@ -753,6 +757,32 @@ complete_connection (NMDevice *device,
 }
 
 static gboolean
+get_variant_boolean (GVariant *v, const char *property)
+{
+	if (!v || !g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "Property %s not cached or not boolean type", property);
+
+		return FALSE;
+	}
+
+	return g_variant_get_boolean (v);
+}
+
+static const char *
+get_variant_state (GVariant *v)
+{
+	if (!v || !g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)) {
+		nm_log_warn (LOGD_DEVICE | LOGD_WIFI,
+		             "State property not cached or not a string");
+
+		return "unknown";
+	}
+
+	return g_variant_get_string (v, NULL);
+}
+
+static gboolean
 is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
@@ -762,8 +792,8 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 	if (!priv->enabled || !priv->dbus_obj)
 		return FALSE;
 
-	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "Powered");
-	return g_variant_get_boolean (value);
+	value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "Powered");
+	return get_variant_boolean (value, "Powered");
 }
 
 static gboolean
@@ -777,11 +807,12 @@ get_autoconnect_allowed (NMDevice *device)
 
 static gboolean
 can_auto_connect (NMDevice *device,
-                  NMConnection *connection,
+                  NMSettingsConnection *sett_conn,
                   char **specific_object)
 {
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
+	NMConnection *connection;
 	NMSettingWireless *s_wifi;
 	NMWifiAP *ap;
 	const char *mode;
@@ -789,9 +820,11 @@ can_auto_connect (NMDevice *device,
 
 	nm_assert (!specific_object || !*specific_object);
 
-	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->can_auto_connect (device, connection, NULL))
+	if (!NM_DEVICE_CLASS (nm_device_iwd_parent_class)->can_auto_connect (device, sett_conn, NULL))
 		return FALSE;
 
+	connection = nm_settings_connection_get_connection (sett_conn);
+
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
@@ -804,7 +837,7 @@ can_auto_connect (NMDevice *device,
 	 * but haven't been successful, since these are often accidental choices
 	 * from the menu and the user may not know the password.
 	 */
-	if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), &timestamp)) {
+	if (nm_settings_connection_get_timestamp (sett_conn, &timestamp)) {
 		if (timestamp == 0)
 			return FALSE;
 	}
@@ -812,7 +845,7 @@ can_auto_connect (NMDevice *device,
 	/* 8021x networks can only be used if they've been provisioned on the IWD side and
 	 * thus are Known Networks.
 	 */
-	if (get_connection_iwd_security (connection) == NM_IWD_NETWORK_SECURITY_8021X) {
+	if (nm_wifi_connection_get_iwd_security (connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) {
 		if (!is_connection_known_network (connection))
 			return FALSE;
 	}
@@ -921,7 +954,7 @@ dbus_request_scan_cb (NMDevice *device,
 	}
 
 	if (!priv->scanning && !priv->scan_requested) {
-		g_dbus_proxy_call (priv->dbus_proxy, "Scan",
+		g_dbus_proxy_call (priv->dbus_station_proxy, "Scan",
 		                   g_variant_new ("()"),
 		                   G_DBUS_CALL_FLAGS_NONE, -1,
 		                   priv->cancellable, scan_cb, self);
@@ -1005,11 +1038,11 @@ static gboolean
 try_reply_agent_request (NMDeviceIwd *self,
                          NMConnection *connection,
                          GDBusMethodInvocation *invocation,
-                         const gchar **setting_name,
-                         const gchar **setting_key,
+                         const char **setting_name,
+                         const char **setting_key,
                          gboolean *replied)
 {
-	const gchar *method_name = g_dbus_method_invocation_get_method_name (invocation);
+	const char *method_name = g_dbus_method_invocation_get_method_name (invocation);
 	NMSettingWirelessSecurity *s_wireless_sec;
 	NMSetting8021x *s_8021x;
 
@@ -1019,7 +1052,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 	*replied = FALSE;
 
 	if (!strcmp (method_name, "RequestPassphrase")) {
-		const gchar *psk;
+		const char *psk;
 
 		if (!s_wireless_sec)
 			return FALSE;
@@ -1039,7 +1072,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 		*setting_key = NM_SETTING_WIRELESS_SECURITY_PSK;
 		return TRUE;
 	} else if (!strcmp (method_name, "RequestPrivateKeyPassphrase")) {
-		const gchar *password;
+		const char *password;
 
 		if (!s_8021x)
 			return FALSE;
@@ -1059,7 +1092,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 		*setting_key = NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD;
 		return TRUE;
 	} else if (!strcmp (method_name, "RequestUserNameAndPassword")) {
-		const gchar *identity, *password;
+		const char *identity, *password;
 
 		if (!s_8021x)
 			return FALSE;
@@ -1083,7 +1116,7 @@ try_reply_agent_request (NMDeviceIwd *self,
 			*setting_key = NM_SETTING_802_1X_PASSWORD;
 		return TRUE;
 	} else if (!strcmp (method_name, "RequestUserPassword")) {
-		const gchar *password;
+		const char *password;
 
 		if (!s_8021x)
 			return FALSE;
@@ -1124,8 +1157,8 @@ wifi_secrets_cb (NMActRequest *req,
 	NMDeviceIwdPrivate *priv;
 	NMDevice *device;
 	GDBusMethodInvocation *invocation;
-	const gchar *setting_name;
-	const gchar *setting_key;
+	const char *setting_name;
+	const char *setting_key;
 	gboolean replied;
 	NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
@@ -1223,12 +1256,12 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	NMConnection *connection;
 	NMSettingWireless *s_wifi;
 	GBytes *ssid;
-	gs_free gchar *str_ssid = NULL;
+	gs_free char *ssid_utf8 = NULL;
 
 	if (!_nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
 	                                 G_VARIANT_TYPE ("()"),
 	                                 &error)) {
-		gs_free gchar *dbus_error = NULL;
+		gs_free char *dbus_error = NULL;
 
 		/* Connection failed; radio problems or if the network wasn't
 		 * open, the passwords or certificates may be wrong.
@@ -1275,17 +1308,13 @@ network_connect_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 	if (!ssid)
 		goto failed;
 
-	str_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
-	                                  g_bytes_get_size (ssid));
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
 
 	_LOGI (LOGD_DEVICE | LOGD_WIFI,
 	       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Connected to '%s'.",
-	       str_ssid);
+	       ssid_utf8);
 	nm_device_activate_schedule_stage3_ip_config_start (device);
 
-	nm_iwd_manager_network_connected (nm_iwd_manager_get (), str_ssid,
-	                                  get_connection_iwd_security (connection));
-
 	return;
 
 failed:
@@ -1299,7 +1328,7 @@ set_powered (NMDeviceIwd *self, gboolean powered)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 
-	g_dbus_proxy_call (priv->dbus_proxy,
+	g_dbus_proxy_call (priv->dbus_device_proxy,
 	                   "org.freedesktop.DBus.Properties.Set",
 	                   g_variant_new ("(ssv)", NM_IWD_DEVICE_INTERFACE,
 	                                  "Powered",
@@ -1452,7 +1481,7 @@ periodic_scan_timeout_cb (gpointer user_data)
 	if (priv->scanning || priv->scan_requested)
 		return FALSE;
 
-	g_dbus_proxy_call (priv->dbus_proxy, "Scan", g_variant_new ("()"),
+	g_dbus_proxy_call (priv->dbus_station_proxy, "Scan", g_variant_new ("()"),
 	                   G_DBUS_CALL_FLAGS_NONE, -1,
 	                   priv->cancellable, scan_cb, self);
 	priv->scan_requested = TRUE;
@@ -1556,7 +1585,7 @@ set_enabled (NMDevice *device, gboolean enabled)
 		return;
 	}
 
-	if (priv->dbus_proxy)
+	if (priv->dbus_obj)
 		set_powered (self, enabled);
 
 	if (enabled) {
@@ -1667,7 +1696,7 @@ set_property (GObject *object, guint prop_id,
 /*****************************************************************************/
 
 static void
-state_changed (NMDeviceIwd *self, const gchar *new_state)
+state_changed (NMDeviceIwd *self, const char *new_state)
 {
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMDevice *device = NM_DEVICE (self);
@@ -1734,7 +1763,8 @@ state_changed (NMDeviceIwd *self, const gchar *new_state)
 	                                 NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
 
 		return;
-	}
+	} else if (nm_streq (new_state, "unknown"))
+		return;
 
 	_LOGE (LOGD_WIFI, "State %s unknown", new_state);
 }
@@ -1761,6 +1791,29 @@ scanning_changed (NMDeviceIwd *self, gboolean new_scanning)
 }
 
 static void
+station_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
+                            GStrv invalidate_properties, gpointer user_data)
+{
+	NMDeviceIwd *self = user_data;
+	GVariantIter *iter;
+	const char *key;
+	GVariant *value;
+
+	g_variant_get (changed_properties, "a{sv}", &iter);
+	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
+		if (!strcmp (key, "State"))
+			state_changed (self, get_variant_state (value));
+
+		if (!strcmp (key, "Scanning"))
+			scanning_changed (self, get_variant_boolean (value, "Scanning"));
+
+		g_variant_unref (value);
+	}
+
+	g_variant_iter_free (iter);
+}
+
+static void
 powered_changed (NMDeviceIwd *self, gboolean new_powered)
 {
 	nm_device_queue_recheck_available (NM_DEVICE (self),
@@ -1769,24 +1822,18 @@ powered_changed (NMDeviceIwd *self, gboolean new_powered)
 }
 
 static void
-properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
-                    GStrv invalidate_properties, gpointer user_data)
+device_properties_changed (GDBusProxy *proxy, GVariant *changed_properties,
+                           GStrv invalidate_properties, gpointer user_data)
 {
 	NMDeviceIwd *self = user_data;
 	GVariantIter *iter;
-	const gchar *key;
+	const char *key;
 	GVariant *value;
 
 	g_variant_get (changed_properties, "a{sv}", &iter);
 	while (g_variant_iter_next (iter, "{&sv}", &key, &value)) {
-		if (!strcmp (key, "State"))
-			state_changed (self, g_variant_get_string (value, NULL));
-
-		if (!strcmp (key, "Scanning"))
-			scanning_changed (self, g_variant_get_boolean (value));
-
 		if (!strcmp (key, "Powered"))
-			powered_changed (self, g_variant_get_boolean (value));
+			powered_changed (self, get_variant_boolean (value, "Powered"));
 
 		g_variant_unref (value);
 	}
@@ -1804,11 +1851,13 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 	if (!nm_g_object_ref_set ((GObject **) &priv->dbus_obj, (GObject *) object))
 		return;
 
-	if (priv->dbus_proxy) {
-		g_signal_handlers_disconnect_by_func (priv->dbus_proxy,
-		                                      properties_changed, self);
-
-		g_clear_object (&priv->dbus_proxy);
+	if (priv->dbus_device_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
+		                                      device_properties_changed, self);
+		g_clear_object (&priv->dbus_device_proxy);
+		g_signal_handlers_disconnect_by_func (priv->dbus_station_proxy,
+		                                      station_properties_changed, self);
+		g_clear_object (&priv->dbus_station_proxy);
 	}
 
 	if (priv->enabled)
@@ -1824,19 +1873,55 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 	}
 
 	interface = g_dbus_object_get_interface (object, NM_IWD_DEVICE_INTERFACE);
-	priv->dbus_proxy = G_DBUS_PROXY (interface);
+	if (!interface) {
+		_LOGE (LOGD_WIFI, "Interface %s not found on obj %s",
+		       NM_IWD_DEVICE_INTERFACE,
+		       g_dbus_object_get_object_path (object));
+		goto error;
+	}
+
+	priv->dbus_device_proxy = G_DBUS_PROXY (interface);
+
+	g_signal_connect (priv->dbus_device_proxy, "g-properties-changed",
+	                  G_CALLBACK (device_properties_changed), self);
 
-	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "Scanning");
-	priv->scanning = g_variant_get_boolean (value);
+	interface = g_dbus_object_get_interface (object, NM_IWD_STATION_INTERFACE);
+	if (!interface) {
+		/* No Station interface on the device object.  Check if the
+		 * "State" property is present on the Device interface, that
+		 * would mean we're dealing with an IWD version from before the
+		 * Device/Station split (0.7 or earlier) and we can easily
+		 * handle that by making priv->dbus_device_proxy and
+		 * priv->dbus_station_proxy both point at the Device interface.
+		 *
+		 * TODO: handle device in a mode initially other than station
+		 * -- also means the Station interface won't be there.
+		 */
+		value = g_dbus_proxy_get_cached_property (priv->dbus_device_proxy, "State");
+		g_variant_unref (value);
+		if (!value) {
+			_LOGE (LOGD_WIFI, "Interface %s not found on obj %s",
+			       NM_IWD_STATION_INTERFACE,
+			       g_dbus_object_get_object_path (object));
+			goto error;
+		}
+
+		interface = g_object_ref (priv->dbus_device_proxy);
+	}
+
+	priv->dbus_station_proxy = G_DBUS_PROXY (interface);
+
+	value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "Scanning");
+	priv->scanning = get_variant_boolean (value, "Scanning");
 	g_variant_unref (value);
 	priv->scan_requested = FALSE;
 
-	value = g_dbus_proxy_get_cached_property (priv->dbus_proxy, "State");
-	state_changed (self, g_variant_get_string (value, NULL));
+	value = g_dbus_proxy_get_cached_property (priv->dbus_station_proxy, "State");
+	state_changed (self, get_variant_state (value));
 	g_variant_unref (value);
 
-	g_signal_connect (priv->dbus_proxy, "g-properties-changed",
-	                  G_CALLBACK (properties_changed), self);
+	g_signal_connect (priv->dbus_station_proxy, "g-properties-changed",
+	                  G_CALLBACK (station_properties_changed), self);
 
 	set_powered (self, priv->enabled);
 
@@ -1845,6 +1930,15 @@ nm_device_iwd_set_dbus_object (NMDeviceIwd *self, GDBusObject *object)
 	 */
 	if (priv->enabled)
 		send_disconnect (self);
+
+	return;
+error:
+	g_clear_object (&priv->dbus_obj);
+	if (priv->dbus_device_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->dbus_device_proxy,
+		                                      device_properties_changed, self);
+		g_clear_object (&priv->dbus_device_proxy);
+	}
 }
 
 gboolean
@@ -1852,8 +1946,8 @@ nm_device_iwd_agent_query (NMDeviceIwd *self,
                            GDBusMethodInvocation *invocation)
 {
 	NMActRequest *req;
-	const gchar *setting_name;
-	const gchar *setting_key;
+	const char *setting_name;
+	const char *setting_key;
 	gboolean replied;
 	NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
@@ -1937,7 +2031,8 @@ dispose (GObject *object)
 
 	cleanup_association_attempt (self, TRUE);
 
-	g_clear_object (&priv->dbus_proxy);
+	g_clear_object (&priv->dbus_device_proxy);
+	g_clear_object (&priv->dbus_station_proxy);
 	g_clear_object (&priv->dbus_obj);
 
 	remove_all_aps (self);
@@ -1952,9 +2047,7 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRELESS_SETTING_NAME, NM_LINK_TYPE_WIFI)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
@@ -1962,25 +2055,29 @@ nm_device_iwd_class_init (NMDeviceIwdClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
 
-	parent_class->can_auto_connect = can_auto_connect;
-	parent_class->is_available = is_available;
-	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->check_connection_available = check_connection_available;
-	parent_class->complete_connection = complete_connection;
-	parent_class->get_enabled = get_enabled;
-	parent_class->set_enabled = set_enabled;
-	parent_class->get_type_description = get_type_description;
-
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->get_configured_mtu = get_configured_mtu;
-	parent_class->deactivate = deactivate;
-	parent_class->deactivate_async = deactivate_async;
-	parent_class->deactivate_async_finish = deactivate_async_finish;
-	parent_class->can_reapply_change = can_reapply_change;
-
-	parent_class->state_changed = device_state_changed;
+	device_class->connection_type_supported = NM_SETTING_WIRELESS_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIRELESS_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIFI);
+
+	device_class->can_auto_connect = can_auto_connect;
+	device_class->is_available = is_available;
+	device_class->get_autoconnect_allowed = get_autoconnect_allowed;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->check_connection_available = check_connection_available;
+	device_class->complete_connection = complete_connection;
+	device_class->get_enabled = get_enabled;
+	device_class->set_enabled = set_enabled;
+	device_class->get_type_description = get_type_description;
+
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->get_configured_mtu = get_configured_mtu;
+	device_class->deactivate = deactivate;
+	device_class->deactivate_async = deactivate_async;
+	device_class->deactivate_async_finish = deactivate_async_finish;
+	device_class->can_reapply_change = can_reapply_change;
+
+	device_class->state_changed = device_state_changed;
 
 	klass->scanning_prohibited = scanning_prohibited;
 
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index fd7bf3f7..6f833635 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -80,28 +80,6 @@ G_DEFINE_TYPE (NMDeviceOlpcMesh, nm_device_olpc_mesh, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
-{
-	NMSettingConnection *s_con;
-	NMSettingOlpcMesh *s_mesh;
-
-	if (!NM_DEVICE_CLASS (nm_device_olpc_mesh_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_OLPC_MESH_SETTING_NAME))
-		return FALSE;
-
-	s_mesh = nm_connection_get_setting_olpc_mesh (connection);
-	if (!s_mesh)
-		return FALSE;
-
-	return TRUE;
-}
-
-static gboolean
 get_autoconnect_allowed (NMDevice *device)
 {
 	return FALSE;
@@ -117,7 +95,6 @@ complete_connection (NMDevice *device,
                      GError **error)
 {
 	NMSettingOlpcMesh *s_mesh;
-	GByteArray *tmp;
 
 	s_mesh = nm_connection_get_setting_olpc_mesh (connection);
 	if (!s_mesh) {
@@ -126,10 +103,10 @@ complete_connection (NMDevice *device,
 	}
 
 	if (!nm_setting_olpc_mesh_get_ssid (s_mesh)) {
-		tmp = g_byte_array_sized_new (strlen (DEFAULT_SSID));
-		g_byte_array_append (tmp, (const guint8 *) DEFAULT_SSID, strlen (DEFAULT_SSID));
-		g_object_set (G_OBJECT (s_mesh), NM_SETTING_OLPC_MESH_SSID, tmp, NULL);
-		g_byte_array_free (tmp, TRUE);
+		gs_unref_bytes GBytes *ssid = NULL;
+
+		ssid = g_bytes_new_static (DEFAULT_SSID, NM_STRLEN (DEFAULT_SSID));
+		g_object_set (G_OBJECT (s_mesh), NM_SETTING_OLPC_MESH_SSID, ssid, NULL);
 	}
 
 	if (!nm_setting_olpc_mesh_get_dhcp_anycast_address (s_mesh)) {
@@ -515,9 +492,7 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_OLPC_MESH_SETTING_NAME, NM_LINK_TYPE_OLPC_MESH)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructed = constructed;
 	object_class->get_property = get_property;
@@ -525,14 +500,17 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_olpc_mesh);
 
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
-	parent_class->complete_connection = complete_connection;
-	parent_class->is_available = is_available;
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->state_changed = state_changed;
-	parent_class->get_dhcp_timeout = get_dhcp_timeout;
+	device_class->connection_type_supported = NM_SETTING_OLPC_MESH_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_OLPC_MESH_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_OLPC_MESH);
+
+	device_class->get_autoconnect_allowed = get_autoconnect_allowed;
+	device_class->complete_connection = complete_connection;
+	device_class->is_available = is_available;
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->state_changed = state_changed;
+	device_class->get_dhcp_timeout = get_dhcp_timeout;
 
 	obj_properties[PROP_COMPANION] =
 	     g_param_spec_string (NM_DEVICE_OLPC_MESH_COMPANION, "", "",
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 6bd06524..64ab82cb 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -121,6 +121,8 @@ typedef struct {
 	gint32 hw_addr_scan_expire;
 
 	guint             wps_timeout_id;
+
+	NMSettingWirelessWakeOnWLan wowlan_restore;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -436,7 +438,7 @@ periodic_update (NMDeviceWifi *self)
 		percent = nm_platform_wifi_get_quality (nm_device_get_platform (NM_DEVICE (self)), ifindex);
 		if (percent >= 0 || ++priv->invalid_strength_counter > 3) {
 			if (nm_wifi_ap_set_strength (priv->current_ap, (gint8) percent)) {
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 				_ap_dump (self, LOGL_TRACE, priv->current_ap, "updated", 0);
 #endif
 			}
@@ -508,6 +510,22 @@ remove_all_aps (NMDeviceWifi *self)
 	nm_device_recheck_available_connections (NM_DEVICE (self));
 }
 
+static gboolean
+wake_on_wlan_restore (NMDeviceWifi *self)
+{
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMSettingWirelessWakeOnWLan w;
+
+	w = priv->wowlan_restore;
+	if (w == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
+		return TRUE;
+
+	priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+	return nm_platform_wifi_set_wake_on_wlan (NM_PLATFORM_GET,
+	                                          nm_device_get_ifindex (NM_DEVICE (self)),
+	                                          w);
+}
+
 static void
 deactivate (NMDevice *device)
 {
@@ -524,6 +542,9 @@ deactivate (NMDevice *device)
 
 	set_current_ap (self, NULL, TRUE);
 
+	if (!wake_on_wlan_restore (self))
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "Cannot unconfigure WoWLAN.");
+
 	/* Clear any critical protocol notification in the Wi-Fi stack */
 	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
 
@@ -583,11 +604,10 @@ is_adhoc_wpa (NMConnection *connection)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
 	NMSettingWireless *s_wireless;
 	const char *mac;
 	const char * const *mac_blacklist;
@@ -595,24 +615,19 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 	const char *mode;
 	const char *perm_hw_addr;
 
-	if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->check_connection_compatible (device, connection))
-		return FALSE;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_WIRELESS_SETTING_NAME))
+	if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
 	s_wireless = nm_connection_get_setting_wireless (connection);
-	if (!s_wireless)
-		return FALSE;
 
 	perm_hw_addr = nm_device_get_permanent_hw_address (device);
 	mac = nm_setting_wireless_get_mac_address (s_wireless);
 	if (perm_hw_addr) {
-		if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1))
+		if (mac && !nm_utils_hwaddr_matches (mac, -1, perm_hw_addr, -1)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "device MAC address does not match the profile");
 			return FALSE;
+		}
 
 		/* Check for MAC address blacklist */
 		mac_blacklist = nm_setting_wireless_get_mac_address_blacklist (s_wireless);
@@ -622,27 +637,45 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 				return FALSE;
 			}
 
-			if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1))
+			if (nm_utils_hwaddr_matches (mac_blacklist[i], -1, perm_hw_addr, -1)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "MAC address blacklisted");
 				return FALSE;
+			}
 		}
-	} else if (mac)
+	} else if (mac) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "device has no valid MAC address as required by profile");
 		return FALSE;
+	}
 
-	if (is_adhoc_wpa (connection))
+	if (is_adhoc_wpa (connection)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "Ad-Hoc WPA networks are not supported");
 		return FALSE;
+	}
 
 	/* Early exit if supplicant or device doesn't support requested mode */
 	mode = nm_setting_wireless_get_mode (s_wireless);
 	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0) {
-		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC))
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_ADHOC)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "the device does not support Ad-Hoc networks");
 			return FALSE;
+		}
 	} else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
-		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP))
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_AP)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "the device does not support Access Point mode");
 			return FALSE;
+		}
 
 		if (priv->sup_iface) {
-			if (nm_supplicant_interface_get_ap_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_NO)
+			if (nm_supplicant_interface_get_ap_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_NO) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "wpa_supplicant does not support Access Point mode");
 				return FALSE;
+			}
 		}
 	}
 
@@ -657,7 +690,8 @@ static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
+                            const char *specific_object,
+                            GError **error)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
@@ -674,7 +708,17 @@ check_connection_available (NMDevice *device,
 		NMWifiAP *ap;
 
 		ap = nm_wifi_ap_lookup_for_device (NM_DEVICE (self), specific_object);
-		return ap ? nm_wifi_ap_check_compatible (ap, connection) : FALSE;
+		if (!ap) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "requested access point not found");
+			return FALSE;
+		}
+		if (!nm_wifi_ap_check_compatible (ap, connection)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "requested access point is not compatible with profile");
+			return FALSE;
+		}
+		return TRUE;
 	}
 
 	/* Ad-Hoc and AP connections are always available because they may be
@@ -693,11 +737,17 @@ check_connection_available (NMDevice *device,
 	 * activating but the network isn't available let the device recheck
 	 * availability.
 	 */
-	if (nm_setting_wireless_get_hidden (s_wifi) || NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
+	if (   nm_setting_wireless_get_hidden (s_wifi)
+	    || NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_IGNORE_AP))
 		return TRUE;
 
-	/* check at least one AP is compatible with this connection */
-	return !!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection);
+	if (!nm_wifi_aps_find_first_compatible (&priv->aps_lst_head, connection)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "no compatible access point found");
+		return FALSE;
+	}
+
+	return TRUE;
 }
 
 static gboolean
@@ -711,10 +761,9 @@ complete_connection (NMDevice *device,
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
 	const char *setting_mac;
-	char *str_ssid = NULL;
+	gs_free char *ssid_utf8 = NULL;
 	NMWifiAP *ap;
-	const GByteArray *ssid = NULL;
-	GByteArray *tmp_ssid = NULL;
+	GBytes *ssid = NULL;
 	GBytes *setting_ssid = NULL;
 	gboolean hidden = FALSE;
 	const char *perm_hw_addr;
@@ -786,19 +835,14 @@ complete_connection (NMDevice *device,
 
 	if (ap)
 		ssid = nm_wifi_ap_get_ssid (ap);
+
 	if (ssid == NULL) {
 		/* The AP must be hidden.  Connecting to a WiFi AP requires the SSID
 		 * as part of the initial handshake, so check the connection details
 		 * for the SSID.  The AP object will still be used for encryption
 		 * settings and such.
 		 */
-		setting_ssid = nm_setting_wireless_get_ssid (s_wifi);
-		if (setting_ssid) {
-			ssid = tmp_ssid = g_byte_array_new ();
-			g_byte_array_append (tmp_ssid,
-			                     g_bytes_get_data (setting_ssid, NULL),
-			                     g_bytes_get_size (setting_ssid));
-		}
+		ssid = nm_setting_wireless_get_ssid (s_wifi);
 	}
 
 	if (ssid == NULL) {
@@ -821,11 +865,8 @@ complete_connection (NMDevice *device,
 		if (!nm_wifi_ap_complete_connection (ap,
 		                                     connection,
 		                                     nm_wifi_utils_is_manf_default_ssid (ssid),
-		                                     error)) {
-			if (tmp_ssid)
-				g_byte_array_unref (tmp_ssid);
+		                                     error))
 			return FALSE;
-		}
 	}
 
 	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
@@ -838,24 +879,18 @@ complete_connection (NMDevice *device,
 		                     NM_CONNECTION_ERROR_INVALID_SETTING,
 		                     _("WPA Ad-Hoc disabled due to kernel bugs"));
 		g_prefix_error (error, "%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME);
-		if (tmp_ssid)
-			g_byte_array_unref (tmp_ssid);
 		return FALSE;
 	}
 
-	str_ssid = nm_utils_ssid_to_utf8 (ssid->data, ssid->len);
-
+	ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
 	nm_utils_complete_generic (nm_device_get_platform (device),
 	                           connection,
 	                           NM_SETTING_WIRELESS_SETTING_NAME,
 	                           existing_connections,
-	                           str_ssid,
-	                           str_ssid,
+	                           ssid_utf8,
+	                           ssid_utf8,
 	                           NULL,
 	                           TRUE);
-	g_free (str_ssid);
-	if (tmp_ssid)
-		g_byte_array_unref (tmp_ssid);
 
 	if (hidden)
 		g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
@@ -923,11 +958,12 @@ get_autoconnect_allowed (NMDevice *device)
 
 static gboolean
 can_auto_connect (NMDevice *device,
-                  NMConnection *connection,
+                  NMSettingsConnection *sett_conn,
                   char **specific_object)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	NMConnection *connection;
 	NMSettingWireless *s_wifi;
 	NMWifiAP *ap;
 	const char *method, *mode;
@@ -935,26 +971,28 @@ can_auto_connect (NMDevice *device,
 
 	nm_assert (!specific_object || !*specific_object);
 
-	if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->can_auto_connect (device, connection, NULL))
+	if (!NM_DEVICE_CLASS (nm_device_wifi_parent_class)->can_auto_connect (device, sett_conn, NULL))
 		return FALSE;
 
+	connection = nm_settings_connection_get_connection (sett_conn);
+
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
 	/* Always allow autoconnect for AP and non-autoconf Ad-Hoc */
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
 	mode = nm_setting_wireless_get_mode (s_wifi);
-	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0)
+	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
 		return TRUE;
-	else if (   g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0
-	         && g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) != 0)
+	else if (   nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)
+	         && !nm_streq0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
 		return TRUE;
 
 	/* Don't autoconnect to networks that have been tried at least once
 	 * but haven't been successful, since these are often accidental choices
 	 * from the menu and the user may not know the password.
 	 */
-	if (nm_settings_connection_get_timestamp (NM_SETTINGS_CONNECTION (connection), &timestamp)) {
+	if (nm_settings_connection_get_timestamp (sett_conn, &timestamp)) {
 		if (timestamp == 0)
 			return FALSE;
 	}
@@ -1033,7 +1071,6 @@ static GPtrArray *
 ssids_options_to_ptrarray (GVariant *value, GError **error)
 {
 	GPtrArray *ssids = NULL;
-	GByteArray *ssid_array;
 	GVariant *v;
 	const guint8 *bytes;
 	gsize len;
@@ -1049,7 +1086,7 @@ ssids_options_to_ptrarray (GVariant *value, GError **error)
 	}
 
 	if (num_ssids) {
-		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_byte_array_unref);
+		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_bytes_unref);
 		for (i = 0; i < num_ssids; i++) {
 			v = g_variant_get_child_value (value, i);
 			bytes = g_variant_get_fixed_array (v, &len, sizeof (guint8));
@@ -1062,9 +1099,7 @@ ssids_options_to_ptrarray (GVariant *value, GError **error)
 				return NULL;
 			}
 
-			ssid_array = g_byte_array_new ();
-			g_byte_array_append (ssid_array, bytes, len);
-			g_ptr_array_add (ssids, ssid_array);
+			g_ptr_array_add (ssids, g_bytes_new (bytes, len));
 		}
 	}
 	return ssids;
@@ -1232,14 +1267,15 @@ check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 
 static gboolean
 hidden_filter_func (NMSettings *settings,
-                    NMSettingsConnection *connection,
+                    NMSettingsConnection *set_con,
                     gpointer user_data)
 {
+	NMConnection *connection = nm_settings_connection_get_connection (set_con);
 	NMSettingWireless *s_wifi;
 
-	if (!nm_connection_is_type (NM_CONNECTION (connection), NM_SETTING_WIRELESS_SETTING_NAME))
+	if (!nm_connection_is_type (connection, NM_SETTING_WIRELESS_SETTING_NAME))
 		return FALSE;
-	s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (connection));
+	s_wifi = nm_connection_get_setting_wireless (connection);
 	if (!s_wifi)
 		return FALSE;
 	if (nm_streq0 (nm_setting_wireless_get_mode (s_wifi), NM_SETTING_WIRELESS_MODE_AP))
@@ -1255,7 +1291,7 @@ build_hidden_probe_list (NMDeviceWifi *self)
 	gs_free NMSettingsConnection **connections = NULL;
 	guint i, len;
 	GPtrArray *ssids = NULL;
-	static GByteArray *nullssid = NULL;
+	static GBytes *nullssid = NULL;
 
 	/* Need at least two: wildcard SSID and one or more hidden SSIDs */
 	if (max_scan_ssids < 2)
@@ -1270,30 +1306,23 @@ build_hidden_probe_list (NMDeviceWifi *self)
 
 	g_qsort_with_data (connections, len, sizeof (NMSettingsConnection *), nm_settings_connection_cmp_timestamp_p_with_data, NULL);
 
-	ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_byte_array_unref);
+	ssids = g_ptr_array_new_full (max_scan_ssids, (GDestroyNotify) g_bytes_unref);
 
 	/* Add wildcard SSID using a static wildcard SSID used for every scan */
 	if (G_UNLIKELY (nullssid == NULL))
-		nullssid = g_byte_array_new ();
-	g_ptr_array_add (ssids, g_byte_array_ref (nullssid));
+		nullssid = g_bytes_new_static ("", 0);
+	g_ptr_array_add (ssids, g_bytes_ref (nullssid));
 
 	for (i = 0; connections[i]; i++) {
 		NMSettingWireless *s_wifi;
 		GBytes *ssid;
-		GByteArray *ssid_array;
 
 		if (i >= max_scan_ssids - 1)
 			break;
 
-		s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (NM_CONNECTION (connections[i]));
-		g_assert (s_wifi);
+		s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (nm_settings_connection_get_connection (connections[i]));
 		ssid = nm_setting_wireless_get_ssid (s_wifi);
-		g_assert (ssid);
-		ssid_array = g_byte_array_new ();
-		g_byte_array_append (ssid_array,
-		                     g_bytes_get_data (ssid, NULL),
-		                     g_bytes_get_size (ssid));
-		g_ptr_array_add (ssids, ssid_array);
+		g_ptr_array_add (ssids, g_bytes_ref (ssid));
 	}
 
 	return ssids;
@@ -1320,24 +1349,22 @@ request_wireless_scan (NMDeviceWifi *self,
 
 		_LOGD (LOGD_WIFI, "wifi-scan: scanning requested");
 
-		if (!ssids) {
+		if (!ssids)
 			ssids = hidden_ssids = build_hidden_probe_list (self);
-		}
 
 		if (_LOGD_ENABLED (LOGD_WIFI)) {
 			if (ssids) {
-				const GByteArray *ssid;
 				guint i;
-				char *foo;
 
 				for (i = 0; i < ssids->len; i++) {
-					ssid = g_ptr_array_index (ssids, i);
-					foo = ssid->len > 0
-					      ? nm_utils_ssid_to_utf8 (ssid->data, ssid->len)
-					      : NULL;
-					_LOGD (LOGD_WIFI, "wifi-scan: (%u) probe scanning SSID %s%s%s",
-					       i, NM_PRINT_FMT_QUOTED (foo, "\"", foo, "\"", "*any*"));
-					g_free (foo);
+					gs_free char *ssid_str = NULL;
+					GBytes *ssid = ssids->pdata[i];
+
+					ssid_str = g_bytes_get_size (ssid) > 0
+					           ? _nm_utils_ssid_to_string (ssid)
+					           : NULL;
+					_LOGD (LOGD_WIFI, "wifi-scan: (%u) probe scanning SSID %s",
+					       i, ssid_str ?: "*any*");
 				}
 			} else
 				_LOGD (LOGD_WIFI, "wifi-scan: no SSIDs to probe scan");
@@ -1345,7 +1372,9 @@ request_wireless_scan (NMDeviceWifi *self,
 
 		_hw_addr_set_scanning (self, FALSE);
 
-		nm_supplicant_interface_request_scan (priv->sup_iface, ssids);
+		nm_supplicant_interface_request_scan (priv->sup_iface,
+		                                      ssids ? (GBytes *const*) ssids->pdata : NULL,
+		                                      ssids ? ssids->len : 0u);
 		request_started = TRUE;
 	} else
 		_LOGD (LOGD_WIFI, "wifi-scan: scanning requested but not allowed at this time");
@@ -1484,17 +1513,13 @@ try_fill_ssid_for_hidden_ap (NMDeviceWifi *self,
 	 * and if a match is found, copy over the SSID */
 	connections = nm_settings_get_connections (nm_device_get_settings ((NMDevice *) self), NULL);
 	for (i = 0; connections[i]; i++) {
-		NMConnection *connection = (NMConnection *) connections[i];
+		NMSettingsConnection *sett_conn = connections[i];
 		NMSettingWireless *s_wifi;
 
-		s_wifi = nm_connection_get_setting_wireless (connection);
+		s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (sett_conn));
 		if (s_wifi) {
-			if (nm_settings_connection_has_seen_bssid (NM_SETTINGS_CONNECTION (connection), bssid)) {
-				GBytes *ssid = nm_setting_wireless_get_ssid (s_wifi);
-
-				nm_wifi_ap_set_ssid (ap,
-				                     g_bytes_get_data (ssid, NULL),
-				                     g_bytes_get_size (ssid));
+			if (nm_settings_connection_has_seen_bssid (sett_conn, bssid)) {
+				nm_wifi_ap_set_ssid (ap, nm_setting_wireless_get_ssid (s_wifi));
 				break;
 			}
 		}
@@ -1510,7 +1535,7 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMDeviceState state;
 	NMWifiAP *found_ap = NULL;
-	const GByteArray *ssid;
+	GBytes *ssid;
 
 	g_return_if_fail (self != NULL);
 	g_return_if_fail (properties != NULL);
@@ -1539,15 +1564,19 @@ supplicant_iface_bss_updated_cb (NMSupplicantInterface *iface,
 
 		/* Let the manager try to fill in the SSID from seen-bssids lists */
 		ssid = nm_wifi_ap_get_ssid (ap);
-		if (!ssid || nm_utils_is_empty_ssid (ssid->data, ssid->len)) {
+		if (!ssid || _nm_utils_is_empty_ssid (ssid)) {
 			/* Try to fill the SSID from the AP database */
 			try_fill_ssid_for_hidden_ap (self, ap);
 
 			ssid = nm_wifi_ap_get_ssid (ap);
-			if (ssid && (nm_utils_is_empty_ssid (ssid->data, ssid->len) == FALSE)) {
+			if (   ssid
+			    && !_nm_utils_is_empty_ssid (ssid)) {
+				gs_free char *s = NULL;
+
 				/* Yay, matched it, no longer treat as hidden */
-				_LOGD (LOGD_WIFI, "matched hidden AP %s => '%s'",
-				       nm_wifi_ap_get_address (ap), nm_utils_escape_ssid (ssid->data, ssid->len));
+				_LOGD (LOGD_WIFI, "matched hidden AP %s => %s",
+				       nm_wifi_ap_get_address (ap),
+				       (s = _nm_utils_ssid_to_string (ssid)));
 			} else {
 				/* Didn't have an entry for this AP in the database */
 				_LOGD (LOGD_WIFI, "failed to match hidden AP %s",
@@ -1859,7 +1888,7 @@ need_new_8021x_secrets (NMDeviceWifi *self,
 static gboolean
 need_new_wpa_psk (NMDeviceWifi *self,
                   NMSupplicantInterfaceState old_state,
-                  gint disconnect_reason,
+                  int disconnect_reason,
                   const char **setting_name)
 {
 	NMSettingWirelessSecurity *s_wsec;
@@ -2004,6 +2033,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 			NMConnection *connection;
 			NMSettingWireless *s_wifi;
 			GBytes *ssid;
+			gs_free char *ssid_str = NULL;
 
 			connection = nm_device_get_applied_connection (NM_DEVICE (self));
 			g_return_if_fail (connection);
@@ -2015,11 +2045,11 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 			g_return_if_fail (ssid);
 
 			_LOGI (LOGD_DEVICE | LOGD_WIFI,
-			       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  %s '%s'.",
-			       priv->mode == NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot" :
-			       "Connected to wireless network",
-			       ssid ? nm_utils_escape_ssid (g_bytes_get_data (ssid, NULL),
-			                                    g_bytes_get_size (ssid)) : "(none)");
+			       "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. %s %s",
+			       priv->mode == NM_802_11_MODE_AP
+			       ? "Started Wi-Fi Hotspot"
+			       : "Connected to wireless network",
+			       (ssid_str = _nm_utils_ssid_to_string (ssid)));
 			nm_device_activate_schedule_stage3_ip_config_start (device);
 		} else if (devstate == NM_DEVICE_STATE_ACTIVATED)
 			periodic_update (self);
@@ -2127,9 +2157,11 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 
 	if (new_ap != priv->current_ap) {
 		const char *new_bssid = NULL;
-		const GByteArray *new_ssid = NULL;
+		GBytes *new_ssid = NULL;
 		const char *old_bssid = NULL;
-		const GByteArray *old_ssid = NULL;
+		GBytes *old_ssid = NULL;
+		gs_free char *new_ssid_s = NULL;
+		gs_free char *old_ssid_s = NULL;
 
 		/* Don't ever replace a "fake" current AP if we don't know about the
 		 * supplicant's current BSS yet.  It'll get replaced when we receive
@@ -2150,9 +2182,9 @@ supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
 
 		_LOGD (LOGD_WIFI, "roamed from BSSID %s (%s) to %s (%s)",
 		       old_bssid ?: "(none)",
-		       old_ssid ? nm_utils_escape_ssid (old_ssid->data, old_ssid->len) : "(none)",
+		       (old_ssid_s = _nm_utils_ssid_to_string (old_ssid)),
 		       new_bssid ?: "(none)",
-		       new_ssid ? nm_utils_escape_ssid (new_ssid->data, new_ssid->len) : "(none)");
+		       (new_ssid_s = _nm_utils_ssid_to_string (new_ssid)));
 
 		set_current_ap (self, new_ap, TRUE);
 	}
@@ -2332,7 +2364,6 @@ build_supplicant_config (NMDeviceWifi *self,
 	NMSettingWirelessSecurity *s_wireless_sec;
 	NMSettingWirelessSecurityPmf pmf;
 	NMSettingWirelessSecurityFils fils;
-	gs_free char *value = NULL;
 
 	g_return_val_if_fail (priv->sup_iface, NULL);
 
@@ -2374,25 +2405,23 @@ build_supplicant_config (NMDeviceWifi *self,
 		/* Configure PMF (802.11w) */
 		pmf = nm_setting_wireless_security_get_pmf (s_wireless_sec);
 		if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT) {
-			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-			                                               "wifi-sec.pmf",
-			                                               NM_DEVICE (self));
-			pmf = _nm_utils_ascii_str_to_int64 (value, 10,
-			                                    NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
-			                                    NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED,
-			                                    NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
+			pmf = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+			                                                   "wifi-sec.pmf",
+			                                                   NM_DEVICE (self),
+			                                                   NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE,
+			                                                   NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED,
+			                                                   NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL);
 		}
 
 		/* Configure FILS (802.11ai) */
 		fils = nm_setting_wireless_security_get_fils (s_wireless_sec);
 		if (fils == NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT) {
-			value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-			                                               "wifi-sec.fils",
-			                                               NM_DEVICE (self));
-			fils = _nm_utils_ascii_str_to_int64 (value, 10,
-			                                     NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE,
-			                                     NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED,
-			                                     NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL);
+			fils = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+			                                                    "wifi-sec.fils",
+			                                                    NM_DEVICE (self),
+			                                                    NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE,
+			                                                    NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED,
+			                                                    NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL);
 		}
 
 		s_8021x = nm_connection_get_setting_802_1x (connection);
@@ -2426,9 +2455,9 @@ error:
 static gboolean
 wake_on_wlan_enable (NMDeviceWifi *self)
 {
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWirelessWakeOnWLan wowl;
 	NMSettingWireless *s_wireless;
-	gs_free char *value = NULL;
 
 	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_WIRELESS);
 	if (s_wireless) {
@@ -2437,32 +2466,39 @@ wake_on_wlan_enable (NMDeviceWifi *self)
 			goto found;
 	}
 
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "wifi.wake-on-wlan",
-	                                               NM_DEVICE (self));
-
-	if (value) {
-		wowl = _nm_utils_ascii_str_to_int64 (value, 10,
-		                                     NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE,
-		                                     G_MAXINT32,
-		                                     NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT);
+	wowl = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                    "wifi.wake-on-wlan",
+	                                                    NM_DEVICE (self),
+	                                                    NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE,
+	                                                    G_MAXINT32,
+	                                                    NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT);
 
-		if (NM_FLAGS_ANY (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS)) {
-			if (!nm_utils_is_power_of_two (wowl)) {
-				_LOGD (LOGD_WIFI, "invalid default value %u for wake-on-wlan: "
-				       "'default' and 'ignore' are exclusive flags", (guint) wowl);
-				wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
-			}
-		} else if (NM_FLAGS_ANY (wowl, ~NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL)) {
-			_LOGD (LOGD_WIFI, "invalid default value %u for wake-on-wlan", (guint) wowl);
+	if (NM_FLAGS_ANY (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS)) {
+		if (!nm_utils_is_power_of_two (wowl)) {
+			_LOGD (LOGD_WIFI, "invalid default value %u for wake-on-wlan: "
+			       "'default' and 'ignore' are exclusive flags", (guint) wowl);
 			wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
 		}
-		if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
-			goto found;
+	} else if (NM_FLAGS_ANY (wowl, ~NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL)) {
+		_LOGD (LOGD_WIFI, "invalid default value %u for wake-on-wlan", (guint) wowl);
+		wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT;
 	}
+	if (wowl != NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT)
+		goto found;
+
 	wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
 found:
-	return nm_platform_wifi_set_wake_on_wlan (NM_PLATFORM_GET, nm_device_get_ifindex (NM_DEVICE (self)), wowl);
+	if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE) {
+		priv->wowlan_restore = wowl;
+		return TRUE;
+	}
+
+	priv->wowlan_restore = nm_platform_wifi_get_wake_on_wlan (NM_PLATFORM_GET,
+	                                                          nm_device_get_ifindex (NM_DEVICE (self)));
+
+	return nm_platform_wifi_set_wake_on_wlan (NM_PLATFORM_GET,
+	                                          nm_device_get_ifindex (NM_DEVICE (self)),
+	                                          wowl);
 }
 
 static NMActStageReturn
@@ -2597,31 +2633,29 @@ set_powersave (NMDevice *device)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMSettingWireless *s_wireless;
-	NMSettingWirelessPowersave powersave;
-	gs_free char *value = NULL;
+	NMSettingWirelessPowersave val;
 
 	s_wireless = (NMSettingWireless *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRELESS);
 	g_return_if_fail (s_wireless);
 
-	powersave = nm_setting_wireless_get_powersave (s_wireless);
-	if (powersave == NM_SETTING_WIRELESS_POWERSAVE_DEFAULT) {
-		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                               "wifi.powersave",
-		                                               device);
-		powersave = _nm_utils_ascii_str_to_int64 (value, 10,
-		                                          NM_SETTING_WIRELESS_POWERSAVE_IGNORE,
-		                                          NM_SETTING_WIRELESS_POWERSAVE_ENABLE,
-		                                          NM_SETTING_WIRELESS_POWERSAVE_IGNORE);
+	val = nm_setting_wireless_get_powersave (s_wireless);
+	if (val == NM_SETTING_WIRELESS_POWERSAVE_DEFAULT) {
+		val = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+		                                                   "wifi.powersave",
+		                                                   device,
+		                                                   NM_SETTING_WIRELESS_POWERSAVE_IGNORE,
+		                                                   NM_SETTING_WIRELESS_POWERSAVE_ENABLE,
+		                                                   NM_SETTING_WIRELESS_POWERSAVE_IGNORE);
 	}
 
-	_LOGT (LOGD_WIFI, "powersave is set to %u", (unsigned) powersave);
+	_LOGT (LOGD_WIFI, "powersave is set to %u", (unsigned) val);
 
-	if (powersave == NM_SETTING_WIRELESS_POWERSAVE_IGNORE)
+	if (val == NM_SETTING_WIRELESS_POWERSAVE_IGNORE)
 		return;
 
 	nm_platform_wifi_set_powersave (nm_device_get_platform (device),
 	                                nm_device_get_ifindex (device),
-	                                powersave == NM_SETTING_WIRELESS_POWERSAVE_ENABLE);
+	                                val == NM_SETTING_WIRELESS_POWERSAVE_ENABLE);
 }
 
 static NMActStageReturn
@@ -2658,8 +2692,6 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wireless);
 
-	wake_on_wlan_enable (self);
-
 	/* If we need secrets, get them */
 	setting_name = nm_connection_need_secrets (connection, NULL);
 	if (setting_name) {
@@ -2676,6 +2708,9 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		goto out;
 	}
 
+	if (!wake_on_wlan_enable (self))
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "Cannot configure WoWLAN.");
+
 	/* have secrets, or no secrets required */
 	if (nm_connection_get_setting_wireless_security (connection)) {
 		_LOGI (LOGD_DEVICE | LOGD_WIFI,
@@ -2726,8 +2761,10 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	ret = NM_ACT_STAGE_RETURN_POSTPONE;
 
 out:
-	if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+	if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
 		cleanup_association_attempt (self, TRUE);
+		wake_on_wlan_restore (self);
+	}
 
 	if (config) {
 		/* Supplicant interface object refs the config; we no longer care about
@@ -3133,7 +3170,8 @@ reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_n
 
 	_LOGD (LOGD_DEVICE, "reapplying wireless settings");
 
-	wake_on_wlan_enable (self);
+	if (!wake_on_wlan_enable (self))
+		_LOGW (LOGD_DEVICE | LOGD_WIFI, "Cannot configure WoWLAN.");
 }
 
 /*****************************************************************************/
@@ -3206,6 +3244,7 @@ nm_device_wifi_init (NMDeviceWifi *self)
 	c_list_init (&priv->aps_lst_head);
 
 	priv->mode = NM_802_11_MODE_INFRA;
+	priv->wowlan_restore = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
 }
 
 static void
@@ -3273,9 +3312,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
-	NMDeviceClass *parent_class = NM_DEVICE_CLASS (klass);
-
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_WIRELESS_SETTING_NAME, NM_LINK_TYPE_WIFI)
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
 	object_class->constructed = constructed;
 	object_class->get_property = get_property;
@@ -3285,29 +3322,33 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&nm_interface_info_device_wireless);
 
-	parent_class->can_auto_connect = can_auto_connect;
-	parent_class->get_autoconnect_allowed = get_autoconnect_allowed;
-	parent_class->is_available = is_available;
-	parent_class->check_connection_compatible = check_connection_compatible;
-	parent_class->check_connection_available = check_connection_available;
-	parent_class->complete_connection = complete_connection;
-	parent_class->get_enabled = get_enabled;
-	parent_class->set_enabled = set_enabled;
-
-	parent_class->act_stage1_prepare = act_stage1_prepare;
-	parent_class->act_stage2_config = act_stage2_config;
-	parent_class->get_configured_mtu = get_configured_mtu;
-	parent_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
-	parent_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
-	parent_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
-	parent_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
-	parent_class->deactivate = deactivate;
-	parent_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
-	parent_class->unmanaged_on_quit = unmanaged_on_quit;
-	parent_class->can_reapply_change = can_reapply_change;
-	parent_class->reapply_connection = reapply_connection;
-
-	parent_class->state_changed = device_state_changed;
+	device_class->connection_type_supported = NM_SETTING_WIRELESS_SETTING_NAME;
+	device_class->connection_type_check_compatible = NM_SETTING_WIRELESS_SETTING_NAME;
+	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_WIFI);
+
+	device_class->can_auto_connect = can_auto_connect;
+	device_class->get_autoconnect_allowed = get_autoconnect_allowed;
+	device_class->is_available = is_available;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->check_connection_available = check_connection_available;
+	device_class->complete_connection = complete_connection;
+	device_class->get_enabled = get_enabled;
+	device_class->set_enabled = set_enabled;
+
+	device_class->act_stage1_prepare = act_stage1_prepare;
+	device_class->act_stage2_config = act_stage2_config;
+	device_class->get_configured_mtu = get_configured_mtu;
+	device_class->act_stage3_ip4_config_start = act_stage3_ip4_config_start;
+	device_class->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
+	device_class->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
+	device_class->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
+	device_class->deactivate = deactivate;
+	device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
+	device_class->unmanaged_on_quit = unmanaged_on_quit;
+	device_class->can_reapply_change = can_reapply_change;
+	device_class->reapply_connection = reapply_connection;
+
+	device_class->state_changed = device_state_changed;
 
 	klass->scanning_prohibited = scanning_prohibited;
 
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index d6511296..1f28d263 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -29,13 +29,21 @@
 #include "nm-core-internal.h"
 #include "nm-manager.h"
 #include "nm-device-iwd.h"
+#include "nm-wifi-utils.h"
 #include "nm-utils/nm-random-utils.h"
+#include "settings/nm-settings.h"
 
 /*****************************************************************************/
 
 typedef struct {
-	gchar *name;
+	const char *name;
 	NMIwdNetworkSecurity security;
+	char buf[0];
+} KnownNetworkId;
+
+typedef struct {
+	GDBusProxy *known_network;
+	NMSettingsConnection *mirror_connection;
 } KnownNetworkData;
 
 typedef struct {
@@ -44,8 +52,8 @@ typedef struct {
 	gboolean running;
 	GDBusObjectManager *object_manager;
 	guint agent_id;
-	gchar *agent_path;
-	GSList *known_networks;
+	char *agent_path;
+	GHashTable *known_networks;
 } NMIwdManagerPrivate;
 
 struct _NMIwdManager {
@@ -83,20 +91,49 @@ G_DEFINE_TYPE (NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+static void mirror_8021x_connection_take_and_delete (NMSettingsConnection *sett_conn);
+
+/*****************************************************************************/
+
+static const char *
+get_variant_string_or_null (GVariant *v)
+{
+	if (!v)
+		return NULL;
+
+	if (   !g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)
+	    && !g_variant_is_of_type (v, G_VARIANT_TYPE_OBJECT_PATH))
+		return NULL;
+
+	return g_variant_get_string (v, NULL);
+}
+
+static const char *
+get_property_string_or_null (GDBusProxy *proxy, const char *property)
+{
+	gs_unref_variant GVariant *value = NULL;
+
+	if (!proxy || !property)
+		return NULL;
+
+	value = g_dbus_proxy_get_cached_property (proxy, property);
+
+	return get_variant_string_or_null (value);
+}
+
 static void
 agent_dbus_method_cb (GDBusConnection *connection,
-                      const gchar *sender, const gchar *object_path,
-                      const gchar *interface_name, const gchar *method_name,
+                      const char *sender, const char *object_path,
+                      const char *interface_name, const char *method_name,
                       GVariant *parameters,
                       GDBusMethodInvocation *invocation,
                       gpointer user_data)
 {
 	NMIwdManager *self = user_data;
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	const gchar *network_path, *device_path, *ifname;
+	const char *network_path, *device_path, *ifname;
 	gs_unref_object GDBusInterface *network = NULL, *device_obj = NULL;
-	gs_unref_variant GVariant *value = NULL;
-	gint ifindex;
+	int ifindex;
 	NMDevice *device;
 	gs_free char *name_owner = NULL;
 
@@ -113,9 +150,8 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	network = g_dbus_object_manager_get_interface (priv->object_manager,
 	                                               network_path,
 	                                               NM_IWD_NETWORK_INTERFACE);
-	value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (network), "Device");
-	device_path = g_variant_get_string (value, NULL);
 
+	device_path = get_property_string_or_null (G_DBUS_PROXY (network), "Device");
 	if (!device_path) {
 		_LOGD ("agent-request: device not cached for network %s in IWD Agent request",
 		       network_path);
@@ -125,10 +161,8 @@ agent_dbus_method_cb (GDBusConnection *connection,
 	device_obj = g_dbus_object_manager_get_interface (priv->object_manager,
 	                                                  device_path,
 	                                                  NM_IWD_DEVICE_INTERFACE);
-	g_variant_unref (value);
-	value = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (device_obj), "Name");
-	ifname = g_variant_get_string (value, NULL);
 
+	ifname = get_property_string_or_null (G_DBUS_PROXY (device_obj), "Name");
 	if (!ifname) {
 		_LOGD ("agent-request: name not cached for device %s in IWD Agent request",
 		       device_path);
@@ -207,12 +241,12 @@ static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE
 
 static guint
 iwd_agent_export (GDBusConnection *connection, gpointer user_data,
-                  gchar **agent_path, GError **error)
+                  char **agent_path, GError **error)
 {
 	static const GDBusInterfaceVTable vtable = {
 		.method_call = agent_dbus_method_cb,
 	};
-	gchar path[50];
+	char path[50];
 	unsigned int rnd;
 	guint id;
 
@@ -251,38 +285,68 @@ register_agent (NMIwdManager *self)
 
 /*****************************************************************************/
 
-static void
-set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface,
-                        GDBusObject *object)
+static KnownNetworkId *
+known_network_id_new (const char *name, NMIwdNetworkSecurity security)
 {
-	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	GDBusProxy *proxy;
-	GVariant *value;
-	const char *ifname;
-	gint ifindex;
-	NMDevice *device;
+	KnownNetworkId *id;
+	gsize strsize = strlen (name) + 1;
 
-	if (!priv->running)
-		return;
+	id = g_malloc (sizeof (KnownNetworkId) + strsize);
+	id->name = id->buf;
+	id->security = security;
+	memcpy (id->buf, name, strsize);
 
-	g_return_if_fail (G_IS_DBUS_PROXY (interface));
+	return id;
+}
 
-	proxy = G_DBUS_PROXY (interface);
+static guint
+known_network_id_hash (KnownNetworkId *id)
+{
+	NMHashState h;
 
-	if (strcmp (g_dbus_proxy_get_interface_name (proxy),
-	            NM_IWD_DEVICE_INTERFACE))
+	nm_hash_init (&h, 1947951703u);
+	nm_hash_update_val (&h, id->security);
+	nm_hash_update_str (&h, id->name);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+known_network_id_equal (KnownNetworkId *a, KnownNetworkId *b)
+{
+	return    a->security == b->security
+	       && nm_streq (a->name, b->name);
+}
+
+static void
+known_network_data_free (KnownNetworkData *network)
+{
+	if (!network)
 		return;
 
-	value = g_dbus_proxy_get_cached_property (proxy, "Name");
-	if (!value) {
+	g_object_unref (network->known_network);
+	mirror_8021x_connection_take_and_delete (network->mirror_connection);
+	g_slice_free (KnownNetworkData, network);
+}
+
+/*****************************************************************************/
+
+static void
+set_device_dbus_object (NMIwdManager *self, GDBusProxy *proxy,
+                        GDBusObject *object)
+{
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	const char *ifname;
+	int ifindex;
+	NMDevice *device;
+
+	ifname = get_property_string_or_null (proxy, "Name");
+	if (!ifname) {
 		_LOGE ("Name not cached for Device at %s",
 		       g_dbus_proxy_get_object_path (proxy));
 		return;
 	}
 
-	ifname = g_variant_get_string (value, NULL);
 	ifindex = if_nametoindex (ifname);
-	g_variant_unref (value);
 
 	if (!ifindex) {
 		_LOGE ("if_nametoindex failed for Name %s for Device at %s: %i",
@@ -299,13 +363,183 @@ set_device_dbus_object (NMIwdManager *self, GDBusInterface *interface,
 	nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
 }
 
+/* Create an in-memory NMConnection for a WPA2-Enterprise network that
+ * has been preprovisioned with an IWD config file so that NM autoconnect
+ * mechanism and the clients know this networks needs no additional EAP
+ * configuration from the user.  Only do this if no existing connection
+ * SSID and security type match that network yet.
+ */
+static NMSettingsConnection *
+mirror_8021x_connection (NMIwdManager *self,
+                         const char *name)
+{
+	NMSettings *settings = NM_SETTINGS_GET;
+	NMSettingsConnection *const*iter;
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingsConnection *settings_connection;
+	char uuid[37];
+	NMSetting *setting;
+	GError *error = NULL;
+	gs_unref_bytes GBytes *new_ssid = NULL;
+
+	for (iter = nm_settings_get_connections (settings, NULL); *iter; iter++) {
+		NMSettingsConnection *sett_conn = *iter;
+		NMConnection *conn = nm_settings_connection_get_connection (sett_conn);
+		NMIwdNetworkSecurity security;
+		gs_free char *ssid_name = NULL;
+		NMSettingWireless *s_wifi;
+
+		security = nm_wifi_connection_get_iwd_security (conn, NULL);
+		if (security != NM_IWD_NETWORK_SECURITY_8021X)
+			continue;
+
+		s_wifi = nm_connection_get_setting_wireless (conn);
+		if (!s_wifi)
+			continue;
+
+		ssid_name = _nm_utils_ssid_to_utf8 (nm_setting_wireless_get_ssid (s_wifi));
+
+		/* We already have an NMSettingsConnection matching this
+		 * KnownNetwork, whether it's saved or an in-memory connection
+		 * potentially created by ourselves.  Nothing to do here.
+		 */
+		if (nm_streq (ssid_name, name))
+			return NULL;
+	}
+
+	connection = nm_simple_connection_new ();
+
+	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_CONNECTION,
+	                                    NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
+	                                    NM_SETTING_CONNECTION_ID, name,
+	                                    NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_buf (uuid),
+	                                    NM_SETTING_CONNECTION_READ_ONLY, TRUE,
+	                                    NULL));
+	nm_connection_add_setting (connection, setting);
+
+	new_ssid = g_bytes_new (name, strlen (name));
+	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_WIRELESS,
+	                                    NM_SETTING_WIRELESS_SSID, new_ssid,
+	                                    NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA,
+	                                    NULL));
+	nm_connection_add_setting (connection, setting);
+
+	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_WIRELESS_SECURITY,
+	                                    NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open",
+	                                    NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
+	                                    NULL));
+	nm_connection_add_setting (connection, setting);
+
+	setting = NM_SETTING (g_object_new (NM_TYPE_SETTING_802_1X, NULL));
+	nm_setting_802_1x_add_eap_method (NM_SETTING_802_1X (setting), "external");
+	nm_connection_add_setting (connection, setting);
+
+	if (!nm_connection_normalize (connection, NULL, NULL, NULL))
+		return NULL;
+
+	settings_connection = nm_settings_add_connection (settings, connection,
+	                                                  FALSE, &error);
+	if (!settings_connection) {
+		_LOGW ("failed to add a mirror NMConnection for IWD's Known Network '%s': %s",
+		       name, error->message);
+		g_error_free (error);
+		return NULL;
+	}
+
+	nm_settings_connection_set_flags (settings_connection,
+	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED |
+	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED,
+	                                  TRUE);
+	return settings_connection;
+}
+
+static void
+mirror_8021x_connection_take_and_delete (NMSettingsConnection *sett_conn)
+{
+	NMSettingsConnectionIntFlags flags;
+
+	if (!sett_conn)
+		return;
+
+	flags = nm_settings_connection_get_flags (sett_conn);
+
+	/* If connection has not been saved since we created it
+	 * in interface_added it too can be removed now. */
+	if (NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
+		nm_settings_connection_delete (sett_conn, NULL);
+
+	g_object_unref (sett_conn);
+}
+
 static void
 interface_added (GDBusObjectManager *object_manager, GDBusObject *object,
                  GDBusInterface *interface, gpointer user_data)
 {
 	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusProxy *proxy;
+	const char *iface_name;
+
+	if (!priv->running)
+		return;
+
+	g_return_if_fail (G_IS_DBUS_PROXY (interface));
 
-	set_device_dbus_object (self, interface, object);
+	proxy = G_DBUS_PROXY (interface);
+	iface_name = g_dbus_proxy_get_interface_name (proxy);
+
+	if (nm_streq (iface_name, NM_IWD_DEVICE_INTERFACE)) {
+		set_device_dbus_object (self, proxy, object);
+		return;
+	}
+
+	if (nm_streq (iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) {
+		KnownNetworkId *id;
+		KnownNetworkData *data;
+		NMIwdNetworkSecurity security;
+		const char *type_str, *name;
+		NMSettingsConnection *sett_conn = NULL;
+
+		type_str = get_property_string_or_null (proxy, "Type");
+		name = get_property_string_or_null (proxy, "Name");
+		if (!type_str || !name)
+			return;
+
+		if (nm_streq (type_str, "open"))
+			security = NM_IWD_NETWORK_SECURITY_NONE;
+		else if (nm_streq (type_str, "psk"))
+			security = NM_IWD_NETWORK_SECURITY_PSK;
+		else if (nm_streq (type_str, "8021x"))
+			security = NM_IWD_NETWORK_SECURITY_8021X;
+		else
+			return;
+
+		id = known_network_id_new (name, security);
+
+		data = g_hash_table_lookup (priv->known_networks, id);
+		if (data)
+			g_free (id);
+		else {
+			data = g_slice_new0 (KnownNetworkData);
+			data->known_network = g_object_ref (proxy);
+			g_hash_table_insert (priv->known_networks, id, data);
+		}
+
+		if (security == NM_IWD_NETWORK_SECURITY_8021X) {
+			sett_conn = mirror_8021x_connection (self, name);
+
+			if (   sett_conn
+			    && sett_conn != data->mirror_connection) {
+				NMSettingsConnection *sett_conn_old = data->mirror_connection;
+
+				data->mirror_connection = nm_g_object_ref (sett_conn);
+				mirror_8021x_connection_take_and_delete (sett_conn_old);
+			}
+		} else
+			mirror_8021x_connection_take_and_delete (g_steal_pointer (&data->mirror_connection));
+
+		return;
+	}
 }
 
 static void
@@ -313,15 +547,41 @@ interface_removed (GDBusObjectManager *object_manager, GDBusObject *object,
                    GDBusInterface *interface, gpointer user_data)
 {
 	NMIwdManager *self = user_data;
+	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
+	GDBusProxy *proxy;
+	const char *iface_name;
 
-	/*
-	 * TODO: we may need to save the GDBusInterface or GDBusObject
-	 * pointer in the hash table because we may be no longer able to
-	 * access the Name property or map the name to ifindex with
-	 * if_nametoindex at this point.
-	 */
+	g_return_if_fail (G_IS_DBUS_PROXY (interface));
+
+	proxy = G_DBUS_PROXY (interface);
+	iface_name = g_dbus_proxy_get_interface_name (proxy);
+
+	if (nm_streq (iface_name, NM_IWD_DEVICE_INTERFACE)) {
+		set_device_dbus_object (self, proxy, NULL);
+		return;
+	}
+
+	if (nm_streq (iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) {
+		KnownNetworkId id;
+		const char *type_str;
+
+		type_str = get_property_string_or_null (proxy, "Type");
+		id.name = get_property_string_or_null (proxy, "Name");
+		if (!type_str || !id.name)
+			return;
+
+		if (nm_streq (type_str, "open"))
+			id.security = NM_IWD_NETWORK_SECURITY_NONE;
+		else if (nm_streq (type_str, "psk"))
+			id.security = NM_IWD_NETWORK_SECURITY_PSK;
+		else if (nm_streq (type_str, "8021x"))
+			id.security = NM_IWD_NETWORK_SECURITY_8021X;
+		else
+			return;
 
-	set_device_dbus_object (self, interface, NULL);
+		g_hash_table_remove (priv->known_networks, &id);
+		return;
+	}
 }
 
 static gboolean
@@ -341,106 +601,42 @@ object_added (NMIwdManager *self, GDBusObject *object)
 	GList *interfaces, *iter;
 
 	interfaces = g_dbus_object_get_interfaces (object);
+
 	for (iter = interfaces; iter; iter = iter->next) {
 		GDBusInterface *interface = G_DBUS_INTERFACE (iter->data);
 
-		set_device_dbus_object (self, interface, object);
+		interface_added (NULL, object, interface, self);
 	}
 
 	g_list_free_full (interfaces, g_object_unref);
 }
 
 static void
-known_network_free (KnownNetworkData *network)
-{
-	g_free (network->name);
-	g_free (network);
-}
-
-static void
-list_known_networks_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+release_object_manager (NMIwdManager *self)
 {
-	NMIwdManager *self = user_data;
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	gs_free_error GError *error = NULL;
-	gs_unref_variant GVariant *variant = NULL;
-	GVariantIter *networks, *props;
-
-	variant = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source), res,
-	                                      G_VARIANT_TYPE ("(aa{sv})"),
-	                                      &error);
-	if (!variant) {
-		_LOGE ("ListKnownNetworks() failed: %s", error->message);
-		return;
-	}
 
-	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
-	priv->known_networks = NULL;
-
-	g_variant_get (variant, "(aa{sv})", &networks);
-
-	while (g_variant_iter_next (networks, "a{sv}", &props)) {
-		const gchar *key;
-		const gchar *name = NULL;
-		const gchar *type = NULL;
-		GVariant *val;
-		KnownNetworkData *network_data;
-
-		while (g_variant_iter_next (props, "{&sv}", &key, &val)) {
-			if (!strcmp (key, "Name"))
-				name = g_variant_get_string (val, NULL);
-
-			if (!strcmp (key, "Type"))
-				type = g_variant_get_string (val, NULL);
-
-			g_variant_unref (val);
-		}
+	if (!priv->object_manager)
+		return;
 
-		if (!name || !type)
-			goto next;
+	g_signal_handlers_disconnect_by_data (priv->object_manager, self);
 
-		network_data = g_new (KnownNetworkData, 1);
-		network_data->name = g_strdup (name);
-		if (!strcmp (type, "open"))
-			network_data->security = NM_IWD_NETWORK_SECURITY_NONE;
-		else if (!strcmp (type, "psk"))
-			network_data->security = NM_IWD_NETWORK_SECURITY_PSK;
-		else if (!strcmp (type, "8021x"))
-			network_data->security = NM_IWD_NETWORK_SECURITY_8021X;
+	if (priv->agent_id) {
+		GDBusConnection *agent_connection;
+		GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
 
-		priv->known_networks = g_slist_append (priv->known_networks,
-		                                       network_data);
+		agent_connection = g_dbus_object_manager_client_get_connection (omc);
 
-next:
-		g_variant_iter_free (props);
+		/* We're is called when we're shutting down (i.e. our DBus connection
+		 * is being closed, and IWD will detect this) or IWD was stopped so
+		 * in either case calling UnregisterAgent will not do anything.
+		 */
+		g_dbus_connection_unregister_object (agent_connection, priv->agent_id);
+		priv->agent_id = 0;
+		nm_clear_g_free (&priv->agent_path);
 	}
 
-	g_variant_iter_free (networks);
-
-	/* For completness we may want to call nm_device_emit_recheck_auto_activate
-	 * and nm_device_recheck_available_connections for all affected devices
-	 * now but the ListKnownNetworks call should have been really fast,
-	 * faster than any scan on any newly created devices could have happened.
-	 */
-}
-
-static void
-update_known_networks (NMIwdManager *self)
-{
-	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	GDBusInterface *known_networks_if;
-
-	known_networks_if = g_dbus_object_manager_get_interface (priv->object_manager,
-	                                                         "/",
-	                                                         NM_IWD_KNOWN_NETWORKS_INTERFACE);
-
-	g_dbus_proxy_call (G_DBUS_PROXY (known_networks_if),
-	                   "ListKnownNetworks",
-	                   g_variant_new ("()"),
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   priv->cancellable, list_known_networks_cb, self);
-
-	g_object_unref (known_networks_if);
+	g_clear_object (&priv->object_manager);
 }
 
 static void prepare_object_manager (NMIwdManager *self);
@@ -455,8 +651,7 @@ name_owner_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
 	nm_assert (object_manager == priv->object_manager);
 
 	if (_om_has_name_owner (object_manager)) {
-		g_signal_handlers_disconnect_by_data (object_manager, self);
-		g_clear_object (&priv->object_manager);
+		release_object_manager (self);
 		prepare_object_manager (self);
 	} else {
 		const CList *tmp_lst;
@@ -492,28 +687,14 @@ device_added (NMManager *manager, NMDevice *device, gpointer user_data)
 	objects = g_dbus_object_manager_get_objects (priv->object_manager);
 	for (iter = objects; iter; iter = iter->next) {
 		GDBusObject *object = G_DBUS_OBJECT (iter->data);
-		GDBusInterface *interface;
-		GDBusProxy *proxy;
-		GVariant *value;
+		gs_unref_object GDBusInterface *interface = NULL;
 		const char *obj_ifname;
 
 		interface = g_dbus_object_get_interface (object,
 		                                         NM_IWD_DEVICE_INTERFACE);
-		if (!interface)
-			continue;
-
-		proxy = G_DBUS_PROXY (interface);
-		value = g_dbus_proxy_get_cached_property (proxy, "Name");
-		if (!value) {
-			g_object_unref (interface);
-			continue;
-		}
+		obj_ifname = get_property_string_or_null ((GDBusProxy *) interface, "Name");
 
-		obj_ifname = g_variant_get_string (value, NULL);
-		g_variant_unref (value);
-		g_object_unref (interface);
-
-		if (strcmp (nm_device_get_iface (device), obj_ifname))
+		if (!obj_ifname || strcmp (nm_device_get_iface (device), obj_ifname))
 			continue;
 
 		nm_device_iwd_set_dbus_object (NM_DEVICE_IWD (device), object);
@@ -535,7 +716,7 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 	object_manager = g_dbus_object_manager_client_new_for_bus_finish (result, &error);
 	if (object_manager == NULL) {
 		_LOGE ("failed to acquire IWD Object Manager: Wi-Fi will not be available (%s)",
-		       NM_G_ERROR_MSG (error));
+		       error->message);
 		g_clear_error (&error);
 		return;
 	}
@@ -549,11 +730,13 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 
 	connection = g_dbus_object_manager_client_get_connection (G_DBUS_OBJECT_MANAGER_CLIENT (object_manager));
 
-	priv->agent_id = iwd_agent_export (connection, self,
-	                                   &priv->agent_path, &error);
+	priv->agent_id = iwd_agent_export (connection,
+	                                   self,
+	                                   &priv->agent_path,
+	                                   &error);
 	if (!priv->agent_id) {
-		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks will not work: %s",
-		       NM_G_ERROR_MSG (error));
+		_LOGE ("failed to export the IWD Agent: PSK/8021x WiFi networks may not work: %s",
+		       error->message);
 		g_clear_error (&error);
 	}
 
@@ -567,6 +750,8 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 		g_signal_connect (priv->object_manager, "interface-removed",
 		                  G_CALLBACK (interface_removed), self);
 
+		g_hash_table_remove_all (priv->known_networks);
+
 		objects = g_dbus_object_manager_get_objects (object_manager);
 		for (iter = objects; iter; iter = iter->next)
 			object_added (self, G_DBUS_OBJECT (iter->data));
@@ -575,8 +760,6 @@ got_object_manager (GObject *object, GAsyncResult *result, gpointer user_data)
 
 		if (priv->agent_id)
 			register_agent (self);
-
-		update_known_networks (self);
 	}
 }
 
@@ -594,36 +777,13 @@ prepare_object_manager (NMIwdManager *self)
 }
 
 gboolean
-nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
+nm_iwd_manager_is_known_network (NMIwdManager *self, const char *name,
                                  NMIwdNetworkSecurity security)
 {
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	const GSList *iter;
-
-	for (iter = priv->known_networks; iter; iter = g_slist_next (iter)) {
-		const KnownNetworkData *network = iter->data;
-
-		if (!strcmp (network->name, name) && network->security == security)
-			return true;
-	}
+	KnownNetworkId kn_id = { name, security };
 
-	return false;
-}
-
-void
-nm_iwd_manager_network_connected (NMIwdManager *self, const gchar *name,
-                                  NMIwdNetworkSecurity security)
-{
-	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
-	KnownNetworkData *network_data;
-
-	if (nm_iwd_manager_is_known_network (self, name, security))
-		return;
-
-	network_data = g_new (KnownNetworkData, 1);
-	network_data->name = g_strdup (name);
-	network_data->security = security;
-	priv->known_networks = g_slist_append (priv->known_networks, network_data);
+	return g_hash_table_contains (priv->known_networks, &kn_id);
 }
 
 /*****************************************************************************/
@@ -642,6 +802,11 @@ nm_iwd_manager_init (NMIwdManager *self)
 
 	priv->cancellable = g_cancellable_new ();
 
+	priv->known_networks = g_hash_table_new_full ((GHashFunc) known_network_id_hash,
+	                                              (GEqualFunc) known_network_id_equal,
+	                                              g_free,
+	                                              (GDestroyNotify) known_network_data_free);
+
 	prepare_object_manager (self);
 }
 
@@ -651,30 +816,11 @@ dispose (GObject *object)
 	NMIwdManager *self = (NMIwdManager *) object;
 	NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE (self);
 
-	if (priv->object_manager) {
-		if (priv->agent_id) {
-			GDBusConnection *connection;
-			GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT (priv->object_manager);
-
-			/* No need to unregister the agent as IWD will detect
-			 * our DBus connection being closed.
-			 */
-
-			connection = g_dbus_object_manager_client_get_connection (omc);
-
-			g_dbus_connection_unregister_object (connection, priv->agent_id);
-			priv->agent_id = 0;
-		}
-
-		g_clear_object (&priv->object_manager);
-	}
-
-	nm_clear_g_free (&priv->agent_path);
+	release_object_manager (self);
 
 	nm_clear_g_cancellable (&priv->cancellable);
 
-	g_slist_free_full (priv->known_networks, (GDestroyNotify) known_network_free);
-	priv->known_networks = NULL;
+	nm_clear_pointer (&priv->known_networks, g_hash_table_destroy);
 
 	if (priv->manager) {
 		g_signal_handlers_disconnect_by_data (priv->manager, self);
diff --git a/src/devices/wifi/nm-iwd-manager.h b/src/devices/wifi/nm-iwd-manager.h
index 8e6b66ff..cf0e7262 100644
--- a/src/devices/wifi/nm-iwd-manager.h
+++ b/src/devices/wifi/nm-iwd-manager.h
@@ -22,6 +22,7 @@
 #define __NETWORKMANAGER_IWD_MANAGER_H__
 
 #include "devices/nm-device.h"
+#include "nm-wifi-utils.h"
 
 #define NM_IWD_BUS_TYPE                 G_BUS_TYPE_SYSTEM
 #define NM_IWD_SERVICE                  "net.connman.iwd"
@@ -33,15 +34,11 @@
 #define NM_IWD_AGENT_INTERFACE          "net.connman.iwd.Agent"
 #define NM_IWD_WSC_INTERFACE            \
 	"net.connman.iwd.WiFiSimpleConfiguration"
-#define NM_IWD_KNOWN_NETWORKS_INTERFACE "net.connman.iwd.KnownNetworks"
+#define NM_IWD_KNOWN_NETWORK_INTERFACE  "net.connman.iwd.KnownNetwork"
 #define NM_IWD_SIGNAL_AGENT_INTERFACE   "net.connman.iwd.SignalLevelAgent"
-
-typedef enum {
-	NM_IWD_NETWORK_SECURITY_NONE,
-	NM_IWD_NETWORK_SECURITY_WEP,
-	NM_IWD_NETWORK_SECURITY_PSK,
-	NM_IWD_NETWORK_SECURITY_8021X,
-} NMIwdNetworkSecurity;
+#define NM_IWD_AP_INTERFACE             "net.connman.iwd.AccessPoint"
+#define NM_IWD_ADHOC_INTERFACE          "net.connman.iwd.AdHoc"
+#define NM_IWD_STATION_INTERFACE        "net.connman.iwd.Station"
 
 #define NM_TYPE_IWD_MANAGER              (nm_iwd_manager_get_type ())
 #define NM_IWD_MANAGER(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IWD_MANAGER, NMIwdManager))
@@ -57,9 +54,7 @@ GType nm_iwd_manager_get_type (void);
 
 NMIwdManager *nm_iwd_manager_get (void);
 
-gboolean nm_iwd_manager_is_known_network (NMIwdManager *self, const gchar *name,
+gboolean nm_iwd_manager_is_known_network (NMIwdManager *self, const char *name,
                                           NMIwdNetworkSecurity security);
-void nm_iwd_manager_network_connected (NMIwdManager *self, const gchar *name,
-                                       NMIwdNetworkSecurity security);
 
 #endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index dd6d1deb..e5573383 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -58,7 +58,7 @@ struct _NMWifiAPPrivate {
 	char *supplicant_path;   /* D-Bus object path of this AP from wpa_supplicant */
 
 	/* Scanned or cached values */
-	GByteArray *       ssid;
+	GBytes *           ssid;
 	char *             address;
 	NM80211Mode        mode;
 	guint8             strength;
@@ -95,7 +95,7 @@ nm_wifi_ap_get_supplicant_path (NMWifiAP *ap)
 	return NM_WIFI_AP_GET_PRIVATE (ap)->supplicant_path;
 }
 
-const GByteArray *
+GBytes *
 nm_wifi_ap_get_ssid (const NMWifiAP *ap)
 {
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NULL);
@@ -103,43 +103,57 @@ nm_wifi_ap_get_ssid (const NMWifiAP *ap)
 	return NM_WIFI_AP_GET_PRIVATE (ap)->ssid;
 }
 
-static GVariant *
-nm_wifi_ap_get_ssid_as_variant (const NMWifiAP *self)
+gboolean
+nm_wifi_ap_set_ssid_arr (NMWifiAP *ap,
+                         const guint8 *ssid,
+                         gsize ssid_len)
 {
-	const NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE (self);
+	NMWifiAPPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
+
+	if (ssid_len > 32)
+		g_return_val_if_reached (FALSE);
+
+	priv = NM_WIFI_AP_GET_PRIVATE (ap);
+
+	if (nm_utils_gbytes_equal_mem (priv->ssid, ssid, ssid_len))
+		return FALSE;
+
+	nm_clear_pointer (&priv->ssid, g_bytes_unref);
+	if (ssid_len > 0)
+		priv->ssid = g_bytes_new (ssid, ssid_len);
 
-	if (priv->ssid) {
-		return g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                  priv->ssid->data, priv->ssid->len, 1);
-	} else
-		return g_variant_new_array (G_VARIANT_TYPE_BYTE, NULL, 0);
+	_notify (ap, PROP_SSID);
+	return TRUE;
 }
 
 gboolean
-nm_wifi_ap_set_ssid (NMWifiAP *ap, const guint8 *ssid, gsize len)
+nm_wifi_ap_set_ssid (NMWifiAP *ap, GBytes *ssid)
 {
 	NMWifiAPPrivate *priv;
+	gsize l;
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
-	g_return_val_if_fail (ssid == NULL || len > 0, FALSE);
 
-	priv = NM_WIFI_AP_GET_PRIVATE (ap);
-
-	/* same SSID */
-	if ((ssid && priv->ssid) && (len == priv->ssid->len)) {
-		if (!memcmp (ssid, priv->ssid->data, len))
-			return FALSE;
+	if (ssid) {
+		l = g_bytes_get_size (ssid);
+		if (l == 0 || l > 32)
+			g_return_val_if_reached (FALSE);
 	}
 
-	if (priv->ssid) {
-		g_byte_array_free (priv->ssid, TRUE);
-		priv->ssid = NULL;
-	}
+	priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
-	if (ssid) {
-		priv->ssid = g_byte_array_new ();
-		g_byte_array_append (priv->ssid, ssid, len);
-	}
+	if (ssid == priv->ssid)
+		return FALSE;
+	if (   ssid
+	    && priv->ssid
+	    && g_bytes_equal (ssid, priv->ssid))
+		return FALSE;
+
+	nm_clear_pointer (&priv->ssid, g_bytes_unref);
+	if (ssid)
+		priv->ssid = g_bytes_ref (ssid);
 
 	_notify (ap, PROP_SSID);
 	return TRUE;
@@ -814,10 +828,16 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 		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))
-			changed |= nm_wifi_ap_set_ssid (ap, bytes, len);
+		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);
 	}
@@ -961,7 +981,7 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 	const char *supplicant_id = "-";
 	const char *export_path;
 	guint32 chan;
-	char b1[200];
+	gs_free char *ssid_to_free = NULL;
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (self), NULL);
 
@@ -977,10 +997,9 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 		export_path = "/";
 
 	g_snprintf (str_buf, buf_len,
-	            "%17s %-32s [ %c %3u %3u%% %c W:%04X R:%04X ] %3us sup:%s [nm:%s]",
+	            "%17s %-35s [ %c %3u %3u%% %c W:%04X R:%04X ] %3us sup:%s [nm:%s]",
 	            priv->address ?: "(none)",
-	            nm_sprintf_buf (b1, "%s%s%s",
-	                            NM_PRINT_FMT_QUOTED (priv->ssid, "\"", nm_utils_escape_ssid (priv->ssid->data, priv->ssid->len), "\"", "(none)")),
+	            (ssid_to_free = _nm_utils_ssid_to_string (priv->ssid)),
 	            (priv->mode == NM_802_11_MODE_ADHOC
 	                 ? '*'
 	                 : (priv->hotspot
@@ -1032,15 +1051,12 @@ nm_wifi_ap_check_compatible (NMWifiAP *self,
 		return FALSE;
 
 	ssid = nm_setting_wireless_get_ssid (s_wireless);
-	if (   (ssid && !priv->ssid)
-	    || (priv->ssid && !ssid))
-		return FALSE;
-
-	if (   ssid && priv->ssid &&
-	    !nm_utils_same_ssid (g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid),
-	                         priv->ssid->data, priv->ssid->len,
-	                         TRUE))
-		return FALSE;
+	if (ssid != priv->ssid) {
+		if (!ssid || !priv->ssid)
+			return FALSE;
+		if (!g_bytes_equal (ssid, priv->ssid))
+			return FALSE;
+	}
 
 	bssid = nm_setting_wireless_get_bssid (s_wireless);
 	if (bssid && (!priv->address || !nm_utils_hwaddr_matches (bssid, -1, priv->address, -1)))
@@ -1126,7 +1142,8 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_uint (value, priv->rsn_flags);
 		break;
 	case PROP_SSID:
-		g_value_take_variant (value, nm_wifi_ap_get_ssid_as_variant (self));
+		g_value_take_variant (value,
+		                      nm_utils_gbytes_to_variant_ay (priv->ssid));
 		break;
 	case PROP_FREQUENCY:
 		g_value_set_uint (value, priv->freq);
@@ -1146,7 +1163,7 @@ get_property (GObject *object, guint prop_id,
 	case PROP_LAST_SEEN:
 		g_value_set_int (value,
 		                 priv->last_seen > 0
-		                     ? (gint) 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_NS_PER_SECOND)
 		                     : -1);
 		break;
 	default:
@@ -1202,7 +1219,6 @@ nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
 	NMWifiAPPrivate *priv;
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
-	GBytes *ssid;
 	const char *mode, *band, *key_mgmt;
 	guint32 channel;
 	NM80211ApSecurityFlags flags;
@@ -1213,14 +1229,12 @@ nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless != NULL, NULL);
 
-	ssid = nm_setting_wireless_get_ssid (s_wireless);
-	g_return_val_if_fail (ssid != NULL, NULL);
-	g_return_val_if_fail (g_bytes_get_size (ssid) > 0, NULL);
-
 	ap = (NMWifiAP *) g_object_new (NM_TYPE_WIFI_AP, NULL);
 	priv = NM_WIFI_AP_GET_PRIVATE (ap);
 	priv->fake = TRUE;
-	nm_wifi_ap_set_ssid (ap, g_bytes_get_data (ssid, NULL), g_bytes_get_size (ssid));
+
+	nm_wifi_ap_set_ssid (ap,
+	                     nm_setting_wireless_get_ssid (s_wireless));
 
 	// FIXME: bssid too?
 
@@ -1334,7 +1348,7 @@ finalize (GObject *object)
 
 	g_free (priv->supplicant_path);
 	if (priv->ssid)
-		g_byte_array_free (priv->ssid, TRUE);
+		g_bytes_unref (priv->ssid);
 	g_free (priv->address);
 
 	G_OBJECT_CLASS (nm_wifi_ap_parent_class)->finalize (object);
@@ -1520,8 +1534,8 @@ nm_wifi_ap_lookup_for_device (NMDevice *device, const char *exported_path)
 
 	g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
 
-	ap = (NMWifiAP *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
-	                                                 exported_path);
+	ap = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (device)),
+	                                    exported_path);
 	if (   !ap
 	    || !NM_IS_WIFI_AP (ap)
 	    || ap->wifi_device != device)
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 4fdeee93..7462e9d1 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -72,10 +72,12 @@ gboolean          nm_wifi_ap_complete_connection      (NMWifiAP *self,
                                                        GError **error);
 
 const char *      nm_wifi_ap_get_supplicant_path      (NMWifiAP *ap);
-const GByteArray *nm_wifi_ap_get_ssid                 (const NMWifiAP *ap);
-gboolean          nm_wifi_ap_set_ssid                 (NMWifiAP *ap,
+GBytes           *nm_wifi_ap_get_ssid                 (const NMWifiAP *ap);
+gboolean          nm_wifi_ap_set_ssid_arr             (NMWifiAP *ap,
                                                        const guint8 *ssid,
-                                                       gsize len);
+                                                       gsize ssid_len);
+gboolean          nm_wifi_ap_set_ssid                 (NMWifiAP *ap,
+                                                       GBytes *ssid);
 const char *      nm_wifi_ap_get_address              (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_address              (NMWifiAP *ap,
                                                        const char *addr);
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/devices/wifi/nm-wifi-common.c
index 8e079d11..c95620e7 100644
--- a/src/devices/wifi/nm-wifi-common.c
+++ b/src/devices/wifi/nm-wifi-common.c
@@ -196,7 +196,7 @@ const NMDBusInterfaceInfoExtended nm_interface_info_device_wireless = {
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",            "s",  NM_DEVICE_HW_ADDRESS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("PermHwAddress",        "s",  NM_DEVICE_PERM_HW_ADDRESS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",                 "u",  NM_DEVICE_WIFI_MODE),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("BitRate",              "u",  NM_DEVICE_WIFI_BITRATE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Bitrate",              "u",  NM_DEVICE_WIFI_BITRATE),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AccessPoints",         "ao", NM_DEVICE_WIFI_ACCESS_POINTS),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ActiveAccessPoint",    "o",  NM_DEVICE_WIFI_ACTIVE_ACCESS_POINT),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WirelessCapabilities", "u",  NM_DEVICE_WIFI_CAPABILITIES),
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 044bd392..0f7836be 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -525,7 +525,7 @@ verify_adhoc (NMSettingWirelessSecurity *s_wsec,
 }
 
 gboolean
-nm_wifi_utils_complete_connection (const GByteArray *ap_ssid,
+nm_wifi_utils_complete_connection (GBytes *ap_ssid,
                                    const char *bssid,
                                    NM80211Mode ap_mode,
                                    guint32 ap_flags,
@@ -538,7 +538,7 @@ nm_wifi_utils_complete_connection (const GByteArray *ap_ssid,
 	NMSettingWireless *s_wifi;
 	NMSettingWirelessSecurity *s_wsec;
 	NMSetting8021x *s_8021x;
-	GBytes *ssid, *ap_ssid_bytes;
+	GBytes *ssid;
 	const char *mode, *key_mgmt, *auth_alg, *leap_username;
 	gboolean adhoc = FALSE;
 
@@ -548,20 +548,17 @@ nm_wifi_utils_complete_connection (const GByteArray *ap_ssid,
 	s_8021x = nm_connection_get_setting_802_1x (connection);
 
 	/* Fill in missing SSID */
-	ap_ssid_bytes = ap_ssid ? g_bytes_new (ap_ssid->data, ap_ssid->len) : NULL;
 	ssid = nm_setting_wireless_get_ssid (s_wifi);
 	if (!ssid)
-		g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_SSID, ap_ssid_bytes, NULL);
-	else if (!ap_ssid_bytes || !g_bytes_equal (ssid, ap_ssid_bytes)) {
+		g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_SSID, ap_ssid, NULL);
+	else if (!ap_ssid || !g_bytes_equal (ssid, ap_ssid)) {
 		g_set_error_literal (error,
 		                     NM_CONNECTION_ERROR,
 		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
 		                     _("connection does not match access point"));
 		g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_SSID);
-		g_bytes_unref (ap_ssid_bytes);
 		return FALSE;
 	}
-	g_bytes_unref (ap_ssid_bytes);
 
 	if (lock_bssid && !nm_setting_wireless_get_bssid (s_wifi))
 		g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_BSSID, bssid, NULL);
@@ -764,7 +761,7 @@ nm_wifi_utils_complete_connection (const GByteArray *ap_ssid,
 }
 
 guint32
-nm_wifi_utils_level_to_quality (gint val)
+nm_wifi_utils_level_to_quality (int val)
 {
 	if (val < 0) {
 		/* Assume dBm already; rough conversion: best = -40, worst = -100 */
@@ -783,8 +780,10 @@ nm_wifi_utils_level_to_quality (gint val)
 }
 
 gboolean
-nm_wifi_utils_is_manf_default_ssid (const GByteArray *ssid)
+nm_wifi_utils_is_manf_default_ssid (GBytes *ssid)
 {
+	const guint8 *ssid_p;
+	gsize ssid_l;
 	int i;
 	/*
 	 * List of manufacturer default SSIDs that are often unchanged by users.
@@ -806,11 +805,46 @@ nm_wifi_utils_is_manf_default_ssid (const GByteArray *ssid)
 		"TURBONETT",
 	};
 
+	ssid_p = g_bytes_get_data (ssid, &ssid_l);
+
 	for (i = 0; i < G_N_ELEMENTS (manf_defaults); i++) {
-		if (ssid->len == strlen (manf_defaults[i])) {
-			if (memcmp (manf_defaults[i], ssid->data, ssid->len) == 0)
+		if (ssid_l == strlen (manf_defaults[i])) {
+			if (memcmp (manf_defaults[i], ssid_p, ssid_l) == 0)
 				return TRUE;
 		}
 	}
 	return FALSE;
 }
+
+NMIwdNetworkSecurity
+nm_wifi_connection_get_iwd_security (NMConnection *connection,
+                                     gboolean *mapped)
+{
+	NMSettingWirelessSecurity *s_wireless_sec;
+	const char *key_mgmt = NULL;
+
+	if (!nm_connection_get_setting_wireless (connection))
+		goto error;
+
+	NM_SET_OUT (mapped, TRUE);
+
+	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
+	if (!s_wireless_sec)
+		return NM_IWD_NETWORK_SECURITY_NONE;
+
+	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wireless_sec);
+	nm_assert (key_mgmt);
+
+	if (NM_IN_STRSET (key_mgmt, "none", "ieee8021x"))
+		return NM_IWD_NETWORK_SECURITY_WEP;
+
+	if (nm_streq (key_mgmt, "wpa-psk"))
+		return NM_IWD_NETWORK_SECURITY_PSK;
+
+	if (nm_streq (key_mgmt, "wpa-eap"))
+		return NM_IWD_NETWORK_SECURITY_8021X;
+
+error:
+	NM_SET_OUT (mapped, FALSE);
+	return NM_IWD_NETWORK_SECURITY_NONE;
+}
diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h
index def64dd6..03238c24 100644
--- a/src/devices/wifi/nm-wifi-utils.h
+++ b/src/devices/wifi/nm-wifi-utils.h
@@ -27,7 +27,14 @@
 #include "nm-setting-wireless-security.h"
 #include "nm-setting-8021x.h"
 
-gboolean nm_wifi_utils_complete_connection (const GByteArray *ssid,
+typedef enum {
+	NM_IWD_NETWORK_SECURITY_NONE,
+	NM_IWD_NETWORK_SECURITY_WEP,
+	NM_IWD_NETWORK_SECURITY_PSK,
+	NM_IWD_NETWORK_SECURITY_8021X,
+} NMIwdNetworkSecurity;
+
+gboolean nm_wifi_utils_complete_connection (GBytes *ssid,
                                             const char *bssid,
                                             NM80211Mode mode,
                                             guint32 flags,
@@ -37,8 +44,11 @@ gboolean nm_wifi_utils_complete_connection (const GByteArray *ssid,
                                             gboolean lock_bssid,
                                             GError **error);
 
-guint32 nm_wifi_utils_level_to_quality (gint val);
+guint32 nm_wifi_utils_level_to_quality (int val);
+
+gboolean nm_wifi_utils_is_manf_default_ssid (GBytes *ssid);
 
-gboolean nm_wifi_utils_is_manf_default_ssid (const GByteArray *ssid);
+NMIwdNetworkSecurity nm_wifi_connection_get_iwd_security (NMConnection *connection,
+                                                          gboolean *mapped);
 
 #endif  /* __NM_WIFI_UTILS_H__ */
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index 89eebb22..f752bbfc 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -74,8 +74,7 @@ complete_connection (const char *ssid,
                      NMConnection *src,
                      GError **error)
 {
-	GByteArray *tmp;
-	gboolean success;
+	gs_unref_bytes GBytes *ssid_b = NULL;
 	NMSettingWireless *s_wifi;
 
 	/* Add a wifi setting if one doesn't exist */
@@ -85,20 +84,17 @@ complete_connection (const char *ssid,
 		nm_connection_add_setting (src, NM_SETTING (s_wifi));
 	}
 
-	tmp = g_byte_array_sized_new (strlen (ssid));
-	g_byte_array_append (tmp, (const guint8 *) ssid, strlen (ssid));
-
-	success = nm_wifi_utils_complete_connection (tmp,
-	                                             bssid,
-	                                             mode,
-	                                             flags,
-	                                             wpa_flags,
-	                                             rsn_flags,
-	                                             src,
-	                                             lock_bssid,
-	                                             error);
-	g_byte_array_free (tmp, TRUE);
-	return success;
+	ssid_b = g_bytes_new (ssid, strlen (ssid));
+
+	return nm_wifi_utils_complete_connection (ssid_b,
+	                                          bssid,
+	                                          mode,
+	                                          flags,
+	                                          wpa_flags,
+	                                          rsn_flags,
+	                                          src,
+	                                          lock_bssid,
+	                                          error);
 }
 
 typedef struct {
@@ -127,7 +123,7 @@ set_items (NMSetting *setting, const KeyData *items)
 			g_assert (item->str == NULL);
 			g_object_set (G_OBJECT (setting), item->key, item->uint, NULL);
 		} else if (pspec->value_type == G_TYPE_INT) {
-			gint foo = (gint) item->uint;
+			int foo = (int) item->uint;
 
 			g_assert (item->str == NULL);
 			g_object_set (G_OBJECT (setting), item->key, foo, NULL);
@@ -1462,7 +1458,7 @@ main (int argc, char **argv)
 	                      test_ap_wpa_eap_connection_5);
 
 #define ADD_FUNC(func) do { \
-		gchar *name_idx = g_strdup_printf ("/wifi/wpa_psk/" G_STRINGIFY (func) "/%zd", i); \
+		char *name_idx = g_strdup_printf ("/wifi/wpa_psk/" G_STRINGIFY (func) "/%zd", i); \
 		g_test_add_data_func (name_idx, (gconstpointer) i, func); \
 		g_free (name_idx); \
 	} while (0)
@@ -1487,7 +1483,7 @@ main (int argc, char **argv)
 
 #undef ADD_FUNC
 #define ADD_FUNC(func) do { \
-		gchar *name_idx = g_strdup_printf ("/wifi/rsn_psk/" G_STRINGIFY (func) "/%zd", i); \
+		char *name_idx = g_strdup_printf ("/wifi/rsn_psk/" G_STRINGIFY (func) "/%zd", i); \
 		g_test_add_data_func (name_idx, (gconstpointer) i, func); \
 		g_free (name_idx); \
 	} while (0)
diff --git a/src/devices/wwan/meson.build b/src/devices/wwan/meson.build
index abf58594..2c0ea5a1 100644
--- a/src/devices/wwan/meson.build
+++ b/src/devices/wwan/meson.build
@@ -35,10 +35,10 @@ libnm_wwan_dep = declare_dependency(
 
 core_plugins += libnm_wwan
 
-run_target(
+test(
   'check-wwan',
-  command: [check_exports, libnm_wwan.full_path(), linker_script],
-  depends: libnm_wwan
+  check_exports,
+  args: [libnm_wwan.full_path(), linker_script],
 )
 
 sources = files(
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 352b1c3e..4119d598 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -397,34 +397,67 @@ get_type_description (NMDevice *device)
 }
 
 static gboolean
-check_connection_compatible (NMDevice *device, NMConnection *connection)
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
 {
-	if (!NM_DEVICE_CLASS (nm_device_modem_parent_class)->check_connection_compatible (device, connection))
+	GError *local = NULL;
+
+	if (!NM_DEVICE_CLASS (nm_device_modem_parent_class)->check_connection_compatible (device, connection, error))
 		return FALSE;
 
-	return nm_modem_check_connection_compatible (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem, connection);
+	if (!nm_modem_check_connection_compatible (NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device)->modem,
+	                                           connection,
+	                                           error ? &local : NULL)) {
+		if (error) {
+			g_set_error (error,
+			             NM_UTILS_ERROR,
+			             g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE)
+			              ? NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE
+			              : NM_UTILS_ERROR_UNKNOWN,
+			             "modem is incompatible with connection: %s",
+			             local->message);
+			g_error_free (local);
+		}
+		return FALSE;
+	}
+	return TRUE;
 }
 
 static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
+                            const char *specific_object,
+                            GError **error)
 {
 	NMDeviceModem *self = NM_DEVICE_MODEM (device);
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self);
 	NMModemState state;
 
-	if (!priv->rf_enabled || !priv->modem)
+	if (!priv->rf_enabled) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "RFKILL for modem enabled");
 		return FALSE;
+	}
+
+	if (!priv->modem) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "modem not available");
+		return FALSE;
+	}
 
 	state = nm_modem_get_state (priv->modem);
-	if (state <= NM_MODEM_STATE_INITIALIZING)
+	if (state <= NM_MODEM_STATE_INITIALIZING) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "modem not initalized");
 		return FALSE;
+	}
 
 	if (state == NM_MODEM_STATE_LOCKED) {
-		if (!nm_connection_get_setting_gsm (connection))
+		if (!nm_connection_get_setting_gsm (connection)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "modem is locked without pin available");
 			return FALSE;
+		}
 	}
 
 	return TRUE;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index e5678b96..253574d5 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -256,7 +256,7 @@ create_cdma_connect_properties (NMConnection *connection)
 {
 	NMSettingCdma *setting;
 	MMSimpleConnectProperties *properties;
-	const gchar *str;
+	const char *str;
 
 	setting = nm_connection_get_setting_cdma (connection);
 	properties = mm_simple_connect_properties_new ();
@@ -274,7 +274,7 @@ create_gsm_connect_properties (NMConnection *connection)
 	NMSettingGsm *setting;
 	NMSettingPpp *s_ppp;
 	MMSimpleConnectProperties *properties;
-	const gchar *str;
+	const char *str;
 
 	setting = nm_connection_get_setting_gsm (connection);
 	properties = mm_simple_connect_properties_new ();
@@ -617,44 +617,40 @@ act_stage1_prepare (NMModem *_self,
 /*****************************************************************************/
 
 static gboolean
-check_connection_compatible (NMModem *_self, NMConnection *connection)
+check_connection_compatible_with_modem (NMModem *_self, NMConnection *connection, GError **error)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
 	MMModemCapability modem_caps;
-	NMSettingConnection *s_con;
 
 	modem_caps = mm_modem_get_current_capabilities (self->_priv.modem_iface);
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
 
 	if (MODEM_CAPS_3GPP (modem_caps)) {
-		NMSettingGsm *s_gsm;
-
-		if (!g_str_equal (nm_setting_connection_get_connection_type (s_con),
-		                  NM_SETTING_GSM_SETTING_NAME))
-			return FALSE;
-
-		s_gsm = nm_connection_get_setting_gsm (connection);
-		if (!s_gsm)
+		if (!_nm_connection_check_main_setting (connection, NM_SETTING_GSM_SETTING_NAME, error))
 			return FALSE;
 
 		return TRUE;
 	}
 
 	if (MODEM_CAPS_3GPP2 (modem_caps)) {
-		NMSettingCdma *s_cdma;
-
-		if (!g_str_equal (nm_setting_connection_get_connection_type (s_con),
-		                  NM_SETTING_CDMA_SETTING_NAME))
-			return FALSE;
-
-		s_cdma = nm_connection_get_setting_cdma (connection);
-		if (!s_cdma)
+		if (!_nm_connection_check_main_setting (connection, NM_SETTING_CDMA_SETTING_NAME, error))
 			return FALSE;
 
 		return TRUE;
 	}
 
+	if (   !_nm_connection_check_main_setting (connection, NM_SETTING_GSM_SETTING_NAME, NULL)
+	    && !_nm_connection_check_main_setting (connection, NM_SETTING_CDMA_SETTING_NAME, NULL)) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+		                    "connection type %s is not supported by modem",
+		                    nm_connection_get_connection_type (connection));
+		return FALSE;
+	}
+
+	nm_utils_error_set (error,
+	                    NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+	                    "modem lacks capabilities for %s profile",
+	                    nm_connection_get_connection_type (connection));
 	return FALSE;
 }
 
@@ -868,12 +864,12 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	GError *error = NULL;
 	gs_unref_object NMIP4Config *config = NULL;
 	const char *data_port;
-	const gchar *address_string;
-	const gchar *gw_string;
+	const char *address_string;
+	const char *gw_string;
 	guint32 address_network;
 	guint32 gw = 0;
 	NMPlatformIP4Address address;
-	const gchar **dns;
+	const char **dns;
 	guint i;
 	guint32 ip4_route_table, ip4_route_metric;
 	NMPlatformIP4Route *r;
@@ -979,10 +975,10 @@ stage3_ip6_done (NMModemBroadband *self)
 	GError *error = NULL;
 	NMIP6Config *config = NULL;
 	const char *data_port;
-	const gchar *address_string;
+	const char *address_string;
 	NMPlatformIP6Address address;
 	NMModemIPMethod ip_method;
-	const gchar **dns;
+	const char **dns;
 	guint i;
 
 	g_assert (self->_priv.ipv6_config);
@@ -1476,7 +1472,7 @@ nm_modem_broadband_class_init (NMModemBroadbandClass *klass)
 	modem_class->deactivate_cleanup = deactivate_cleanup;
 	modem_class->set_mm_enabled = set_mm_enabled;
 	modem_class->get_user_pass = get_user_pass;
-	modem_class->check_connection_compatible = check_connection_compatible;
+	modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
 	modem_class->complete_connection = complete_connection;
 	modem_class->act_stage1_prepare = act_stage1_prepare;
 	modem_class->owns_port = owns_port;
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index 59cd2bca..dfc102f3 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -170,7 +170,7 @@ modm_handle_object_added (MMManager *modem_manager,
                           NMModemManager *self)
 {
 	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
-	const gchar *path;
+	const char *path;
 	MMModem *modem_iface;
 	NMModem *modem;
 	GError *error = NULL;
@@ -211,7 +211,7 @@ modm_handle_object_removed (MMManager *manager,
 {
 	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	NMModem *modem;
-	const gchar *path;
+	const char *path;
 
 	path = mm_object_get_path (modem_object);
 	modem = (NMModem *) g_hash_table_lookup (priv->modems, path);
@@ -246,7 +246,7 @@ modm_handle_name_owner_changed (MMManager *modem_manager,
                                 NMModemManager *self)
 {
 	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
-	gchar *name_owner;
+	char *name_owner;
 
 	/* Quit poking, if any */
 	nm_clear_g_source (&priv->modm.relaunch_id);
@@ -341,7 +341,7 @@ static void
 modm_manager_check_name_owner (NMModemManager *self)
 {
 	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
-	gs_free gchar *name_owner = NULL;
+	gs_free char *name_owner = NULL;
 
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->modm.manager));
 	if (name_owner) {
@@ -606,14 +606,14 @@ ofono_create_modem (NMModemManager *self, const char *path)
 
 static void
 ofono_signal_cb (GDBusProxy *proxy,
-                 gchar *sender_name,
-                 gchar *signal_name,
+                 char *sender_name,
+                 char *signal_name,
                  GVariant *parameters,
                  gpointer user_data)
 {
 	NMModemManager *self = NM_MODEM_MANAGER (user_data);
 	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
-	gchar *object_path;
+	char *object_path;
 	NMModem *modem;
 
 	if (g_strcmp0 (signal_name, "ModemAdded") == 0) {
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 8b81ac41..ea668590 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -279,47 +279,42 @@ deactivate_cleanup (NMModem *modem, NMDevice *device)
 }
 
 static gboolean
-check_connection_compatible (NMModem *modem,
-                             NMConnection *connection)
+check_connection_compatible_with_modem (NMModem *modem,
+                                        NMConnection *connection,
+                                        GError **error)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (modem);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
-	NMSettingGsm *s_gsm;
-	const char *uuid;
 	const char *id;
 
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-
-	uuid = nm_connection_get_uuid (connection);
-	id = nm_connection_get_id (connection);
-
-	s_gsm = nm_connection_get_setting_gsm (connection);
-	if (!s_gsm)
+	if (!_nm_connection_check_main_setting (connection, NM_SETTING_GSM_SETTING_NAME, NULL)) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+		                    "connection type %s is not supported by ofono modem",
+		                    nm_connection_get_connection_type (connection));
 		return FALSE;
+	}
 
 	if (!priv->imsi) {
-		_LOGW ("skipping %s/%s: no IMSI", uuid, id);
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "modem has no IMSI");
 		return FALSE;
 	}
 
-	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_GSM_SETTING_NAME)) {
-		_LOGD ("skipping %s/%s: not GSM", uuid, id);
-		return FALSE;
-	}
+	id = nm_connection_get_id (connection);
 
-	if (!g_strrstr (id, "/context")) {
-		_LOGD ("skipping %s/%s: unexpected ID", uuid, id);
+	if (!strstr (id, "/context")) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "the connection ID has no context");
 		return FALSE;
 	}
 
-	if (!g_strrstr (id, priv->imsi)) {
-		_LOGD ("skipping %s/%s: ID doesn't contain IMSI", uuid, id);
+	if (!strstr (id, priv->imsi)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+		                            "the connection ID does not contain the IMSI");
 		return FALSE;
 	}
 
-	_LOGD ("%s/%s compatible with IMSI %s", uuid, id, priv->imsi);
 	return TRUE;
 }
 
@@ -830,8 +825,8 @@ context_property_changed (GDBusProxy *proxy,
 	gboolean ret = FALSE;
 	gs_unref_variant GVariant *v_dict = NULL;
 	const char *interface;
-	const gchar *s;
-	const gchar **array, **iter;
+	const char *s;
+	const char **array, **iter;
 	guint32 address_network, gateway_network;
 	guint32 ip4_route_table, ip4_route_metric;
 	int ifindex;
@@ -1326,7 +1321,7 @@ nm_modem_ofono_class_init (NMModemOfonoClass *klass)
 	modem_class->disconnect = disconnect;
 	modem_class->disconnect_finish = disconnect_finish;
 	modem_class->deactivate_cleanup = deactivate_cleanup;
-	modem_class->check_connection_compatible = check_connection_compatible;
+	modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
 
 	modem_class->act_stage1_prepare = act_stage1_prepare;
 	modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 617d5c4a..59b081e9 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -291,7 +291,7 @@ nm_modem_get_supported_ip_types (NMModem *self)
 	return NM_MODEM_GET_PRIVATE (self)->ip_types;
 }
 
-const gchar *
+const char *
 nm_modem_ip_type_to_string (NMModemIPType ip_type)
 {
 	switch (ip_type) {
@@ -1027,35 +1027,29 @@ nm_modem_act_stage2_config (NMModem *self,
 /*****************************************************************************/
 
 gboolean
-nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
+nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection, GError **error)
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
 
-	if (g_str_equal (nm_setting_connection_get_connection_type (s_con),
-	                 NM_SETTING_GSM_SETTING_NAME)) {
+	if (nm_streq0 (nm_connection_get_connection_type (connection),
+	               NM_SETTING_GSM_SETTING_NAME)) {
 		NMSettingGsm *s_gsm;
 		const char *str;
 
-		s_gsm = nm_connection_get_setting_gsm (connection);
+		s_gsm = _nm_connection_check_main_setting (connection, NM_SETTING_GSM_SETTING_NAME, error);
 		if (!s_gsm)
 			return FALSE;
 
 		str = nm_setting_gsm_get_device_id (s_gsm);
 		if (str) {
 			if (!priv->device_id) {
-				_LOGD ("%s/%s has device-id, device does not",
-				       nm_connection_get_uuid (connection),
-				       nm_connection_get_id (connection));
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "GSM profile has device-id, device does not");
 				return FALSE;
 			}
-			if (strcmp (str, priv->device_id)) {
-				_LOGD ("%s/%s device-id mismatch",
-				       nm_connection_get_uuid (connection),
-				       nm_connection_get_id (connection));
+			if (!nm_streq (str, priv->device_id)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "device has differing device-id than GSM profile");
 				return FALSE;
 			}
 		}
@@ -1065,30 +1059,26 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 		 * are only compared if present on the device.
 		 */
 
-		str = nm_setting_gsm_get_sim_id (s_gsm);
-		if (str && priv->sim_id) {
-			if (strcmp (str, priv->sim_id)) {
-				_LOGD ("%s/%s sim-id mismatch",
-				       nm_connection_get_uuid (connection),
-				       nm_connection_get_id (connection));
+		if (   priv->sim_id
+		    && (str = nm_setting_gsm_get_sim_id (s_gsm))) {
+			if (!nm_streq (str, priv->sim_id)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "device has differing sim-id than GSM profile");
 				return FALSE;
 			}
 		}
 
-		str = nm_setting_gsm_get_sim_operator_id (s_gsm);
-		if (str && priv->sim_operator_id) {
-			if (strcmp (str, priv->sim_operator_id)) {
-				_LOGD ("%s/%s sim-operator-id mismatch",
-				       nm_connection_get_uuid (connection),
-				       nm_connection_get_id (connection));
+		if (   priv->sim_operator_id
+		    && (str = nm_setting_gsm_get_sim_operator_id (s_gsm))) {
+			if (!nm_streq (str, priv->sim_operator_id)) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "device has differing sim-operator-id than GSM profile");
 				return FALSE;
 			}
 		}
 	}
 
-	if (NM_MODEM_GET_CLASS (self)->check_connection_compatible)
-		return NM_MODEM_GET_CLASS (self)->check_connection_compatible (self, connection);
-	return FALSE;
+	return NM_MODEM_GET_CLASS (self)->check_connection_compatible_with_modem (self, connection, error);
 }
 
 /*****************************************************************************/
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index cad9ece4..c73745ce 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -121,8 +121,9 @@ typedef struct {
 	                                            const char **user,
 	                                            const char **pass);
 
-	gboolean (*check_connection_compatible)    (NMModem *modem,
-	                                            NMConnection *connection);
+	gboolean (*check_connection_compatible_with_modem) (NMModem *modem,
+	                                                    NMConnection *connection,
+	                                                    GError **error);
 
 	gboolean (*complete_connection)            (NMModem *modem,
 	                                            NMConnection *connection,
@@ -185,7 +186,9 @@ void        nm_modem_get_capabilities (NMModem *self,
                                        NMDeviceModemCapabilities *modem_caps,
                                        NMDeviceModemCapabilities *current_caps);
 
-gboolean nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection);
+gboolean nm_modem_check_connection_compatible (NMModem *self,
+                                               NMConnection *connection,
+                                               GError **error);
 
 gboolean nm_modem_complete_connection (NMModem *self,
                                        NMConnection *connection,
@@ -273,7 +276,7 @@ void nm_modem_emit_ip6_config_result (NMModem *self,
                                       NMIP6Config *config,
                                       GError *error);
 
-const gchar *nm_modem_ip_type_to_string (NMModemIPType ip_type);
+const char *nm_modem_ip_type_to_string (NMModemIPType ip_type);
 
 guint32 nm_modem_get_configured_mtu (NMDevice *self, NMDeviceMtuSource *out_source);
 
diff --git a/src/dhcp/meson.build b/src/dhcp/meson.build
index 289a16ca..76707bca 100644
--- a/src/dhcp/meson.build
+++ b/src/dhcp/meson.build
@@ -3,7 +3,6 @@ name = 'nm-dhcp-helper'
 cflags = [
   '-DG_LOG_DOMAIN="@0@"'.format(name),
   '-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_GLIB',
-  '-DNMRUNDIR="@0@"'.format(nm_pkgrundir),
 ]
 
 executable(
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 360bd367..9fc7d2c1 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -51,7 +51,7 @@ enum {
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpClient,
 	PROP_ADDR_FAMILY,
 	PROP_FLAGS,
 	PROP_HWADDR,
@@ -163,6 +163,17 @@ nm_dhcp_client_get_route_table (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_table;
 }
 
+void
+nm_dhcp_client_set_route_table (NMDhcpClient *self, guint32 route_table)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	if (route_table != priv->route_table) {
+		priv->route_table = route_table;
+		_notify (self, PROP_ROUTE_TABLE);
+	}
+}
+
 guint32
 nm_dhcp_client_get_route_metric (NMDhcpClient *self)
 {
@@ -171,6 +182,17 @@ nm_dhcp_client_get_route_metric (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_metric;
 }
 
+void
+nm_dhcp_client_set_route_metric (NMDhcpClient *self, guint32 route_metric)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	if (route_metric != priv->route_metric) {
+		priv->route_metric = route_metric;
+		_notify (self, PROP_ROUTE_METRIC);
+	}
+}
+
 guint32
 nm_dhcp_client_get_timeout (NMDhcpClient *self)
 {
@@ -423,7 +445,7 @@ transaction_timeout (gpointer user_data)
 }
 
 static void
-daemon_watch_cb (GPid pid, gint status, gpointer user_data)
+daemon_watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDhcpClient *self = NM_DHCP_CLIENT (user_data);
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
@@ -712,7 +734,7 @@ maybe_add_option (NMDhcpClient *self,
 gboolean
 nm_dhcp_client_handle_event (gpointer unused,
                              const char *iface,
-                             gint pid,
+                             int pid,
                              GVariant *options,
                              const char *reason,
                              NMDhcpClient *self)
@@ -834,6 +856,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_ROUTE_METRIC:
 		g_value_set_uint (value, priv->route_metric);
 		break;
+	case PROP_ROUTE_TABLE:
+		g_value_set_uint (value, priv->route_table);
+		break;
 	case PROP_TIMEOUT:
 		g_value_set_uint (value, priv->timeout);
 		break;
@@ -889,11 +914,9 @@ set_property (GObject *object, guint prop_id,
 		priv->uuid = g_value_dup_string (value);
 		break;
 	case PROP_ROUTE_TABLE:
-		/* construct-only */
 		priv->route_table = g_value_get_uint (value);
 		break;
 	case PROP_ROUTE_METRIC:
-		/* construct-only */
 		priv->route_metric = g_value_get_uint (value);
 		break;
 	case PROP_TIMEOUT:
@@ -1002,13 +1025,13 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	obj_properties[PROP_ROUTE_TABLE] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_TABLE, "", "",
 	                       0, G_MAXUINT32, RT_TABLE_MAIN,
-	                       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_READWRITE |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_ROUTE_METRIC] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_ROUTE_METRIC, "", "",
 	                       0, G_MAXUINT32, 0,
-	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                       G_PARAM_READWRITE |
 	                       G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_TIMEOUT] =
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index f3d0b7d1..b50ea515 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -131,8 +131,12 @@ GBytes *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
 
+void nm_dhcp_client_set_route_table (NMDhcpClient *self, guint32 route_table);
+
 guint32 nm_dhcp_client_get_route_metric (NMDhcpClient *self);
 
+void nm_dhcp_client_set_route_metric (NMDhcpClient *self, guint32 route_metric);
+
 guint32 nm_dhcp_client_get_timeout (NMDhcpClient *self);
 
 GBytes *nm_dhcp_client_get_client_id (NMDhcpClient *self);
@@ -176,7 +180,7 @@ void nm_dhcp_client_set_state (NMDhcpClient *self,
 
 gboolean nm_dhcp_client_handle_event (gpointer unused,
                                       const char *iface,
-                                      gint pid,
+                                      int pid,
                                       GVariant *options,
                                       const char *reason,
                                       NMDhcpClient *self);
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 3290dd65..d8f8dd98 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -316,6 +316,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 
 	if (orig_contents) {
 		char **lines, **line;
+		int nest = 0;
 		gboolean in_alsoreq = FALSE;
 		gboolean in_req = FALSE;
 		char intf[IFNAMSIZ];
@@ -330,18 +331,24 @@ nm_dhcp_dhclient_create_config (const char *interface,
 			if (!strlen (g_strstrip (p)))
 				continue;
 
-			if (   !intf[0]
-			    && g_str_has_prefix (p, "interface")
-			    && !in_req) {
-				if (read_interface (p, intf, sizeof (intf)))
-					continue;
-			}
-
-			if (intf[0] && strchr (p, '}')) {
+			if (in_req) {
+				/* pass */
+			} else if (strchr (p, '{')) {
+				nest++;
+				if (   !intf[0]
+				    && g_str_has_prefix (p, "interface"))
+					if (read_interface (p, intf, sizeof (intf)))
+						continue;
+			} else if (strchr (p, '}')) {
+				if (nest)
+					nest--;
 				intf[0] = '\0';
 				continue;
 			}
 
+			if (nest && !intf[0])
+				continue;
+
 			if (intf[0] && !nm_streq (intf, interface))
 				continue;
 
@@ -523,6 +530,10 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 	guint i, len;
 	guint8 octal;
 
+	/* FIXME: it's wrong to have an "unescape-duid" function. dhclient
+	 * defines a file format with escaping. So we need a general unescape
+	 * function that can handle dhclient syntax. */
+
 	len = strlen (duid);
 	unescaped = g_byte_array_sized_new (len);
 	for (i = 0; i < len; i++) {
@@ -536,6 +547,9 @@ nm_dhcp_dhclient_unescape_duid (const char *duid)
 				g_byte_array_append (unescaped, &octal, 1);
 				i += 2;
 			} else {
+				/* FIXME: don't warn on untrusted data. Either signal an error, or accept
+				 * it silently. */
+
 				/* One of ", ', $, `, \, |, or & */
 				g_warn_if_fail (p[i] == '"' || p[i] == '\'' || p[i] == '$' ||
 				                p[i] == '`' || p[i] == '\\' || p[i] == '|' ||
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index 3bd14ebe..8408e3f9 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -447,7 +447,7 @@ dhclient_start (NMDhcpClient *client,
 	g_ptr_array_add (argv, NULL);
 
 	_LOGD ("running: %s",
-	       (cmd_str = g_strjoinv (" ", (gchar **) argv->pdata)));
+	       (cmd_str = g_strjoinv (" ", (char **) argv->pdata)));
 
 	if (!g_spawn_async (NULL, (char **) argv->pdata, NULL,
 	                    G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index 12aa57aa..92aa7f8c 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -144,7 +144,7 @@ dhcpcanon_start (NMDhcpClient *client,
 	g_ptr_array_add (argv, (gpointer) iface);
 	g_ptr_array_add (argv, NULL);
 
-	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
+	cmd_str = g_strjoinv (" ", (char **) argv->pdata);
 	g_free (cmd_str);
 
 	if (g_spawn_async (NULL, (char **) argv->pdata, NULL,
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index c4bcb084..10094e5c 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -153,7 +153,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	g_ptr_array_add (argv, (gpointer) iface);
 	g_ptr_array_add (argv, NULL);
 
-	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
+	cmd_str = g_strjoinv (" ", (char **) argv->pdata);
 	_LOGD ("running: %s", cmd_str);
 	g_free (cmd_str);
 
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 8ea55061..2b48d7a5 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -31,7 +31,7 @@
 
 /*****************************************************************************/
 
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 #define _NMLOG_ENABLED(level) TRUE
 #else
 #define _NMLOG_ENABLED(level) ((level) <= LOG_ERR)
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 9185a135..6bbc670b 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -594,7 +594,7 @@ ip6_add_domain_search (gpointer data, gpointer user_data)
 NMPlatformIP6Address
 nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options)
 {
-	gs_strfreev gchar **split_addr = NULL;
+	gs_strfreev char **split_addr = NULL;
 	NMPlatformIP6Address address = { 0, };
 	struct in6_addr tmp_addr;
 	char *str = NULL;
diff --git a/src/dhcp/tests/meson.build b/src/dhcp/tests/meson.build
index 9d8be427..0fee26b2 100644
--- a/src/dhcp/tests/meson.build
+++ b/src/dhcp/tests/meson.build
@@ -8,7 +8,6 @@ foreach test_unit: test_units
     test_unit,
     test_unit + '.c',
     dependencies: test_nm_dep,
-    c_args: nm_build_cflags,
   )
 
   test(
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 2f369aac..14a1c786 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -86,7 +86,7 @@ test_config (const char *orig,
 	if (expected_new_client_id) {
 		g_assert (new_client_id);
 		g_assert (g_bytes_equal (new_client_id, expected_new_client_id));
-	 } else
+	} else
 		g_assert (new_client_id == NULL);
 }
 
@@ -675,27 +675,18 @@ test_existing_multiline_alsoreq (void)
 static void
 test_one_duid (const char *escaped, const guint8 *unescaped, guint len)
 {
-	GBytes *t;
-	char *w;
-	gsize t_len;
-	gconstpointer t_arr;
-
-	t = nm_dhcp_dhclient_unescape_duid (escaped);
-	g_assert (t);
-	t_arr = g_bytes_get_data (t, &t_len);
-	g_assert (t_arr);
-	g_assert_cmpint (t_len, ==, len);
-	g_assert_cmpint (memcmp (t_arr, unescaped, len), ==, 0);
-	g_bytes_unref (t);
-
-	t = g_bytes_new_static (unescaped, len);
-	w = nm_dhcp_dhclient_escape_duid (t);
+	gs_unref_bytes GBytes *t1 = NULL;
+	gs_unref_bytes GBytes *t2 = NULL;
+	gs_free char *w = NULL;
+
+	t1 = nm_dhcp_dhclient_unescape_duid (escaped);
+	g_assert (t1);
+	g_assert (nm_utils_gbytes_equal_mem (t1, unescaped, len));
+
+	t2 = g_bytes_new (unescaped, len);
+	w = nm_dhcp_dhclient_escape_duid (t2);
 	g_assert (w);
-	g_assert_cmpint (strlen (escaped), ==, strlen (w));
 	g_assert_cmpstr (escaped, ==, w);
-
-	g_bytes_unref (t);
-	g_free (w);
 }
 
 static void
@@ -735,15 +726,11 @@ test_read_duid_from_leasefile (void)
 	                            0x13, 0x60, 0x67, 0x20, 0xec, 0x4c, 0x70 };
 	gs_unref_bytes GBytes *duid = NULL;
 	GError *error = NULL;
-	gconstpointer duid_arr;
-	gsize duid_len;
 
 	duid = nm_dhcp_dhclient_read_duid (TEST_DIR"/test-dhclient-duid.leases", &error);
-	g_assert_no_error (error);
-	g_assert (duid);
-	duid_arr = g_bytes_get_data (duid, &duid_len);
-	g_assert_cmpint (duid_len, ==, sizeof (expected));
-	g_assert_cmpint (memcmp (duid_arr, expected, duid_len), ==, 0);
+	nmtst_assert_success (duid, error);
+
+	g_assert (nm_utils_gbytes_equal_mem (duid, expected, G_N_ELEMENTS (expected)));
 }
 
 static void
@@ -962,6 +949,99 @@ test_interface2 (void)
 }
 
 static void
+test_structured (void)
+{
+	gs_unref_bytes GBytes *new_client_id = NULL;
+	const guint8 bytes[] = "sad-and-useless";
+
+	static const char *const orig = \
+		"interface \"eth0\"   {  \n"
+		"    send host-name \"useless.example.com\";\n"
+		"    hardware ethernet de:ad:80:86:ba:be;\n"
+		"    send dhcp-client-identifier \"sad-and-useless\";\n"
+		"    script \"/bin/useless\";\n"
+		"    send dhcp-lease-time 8086;\n"
+		"    request subnet-mask, broadcast-address, time-offset, routers,\n"
+		"        domain-search, domain-name, host-name;\n"
+		"    require subnet-mask;\n"
+		"}  \n"
+		"\n"
+		"    interface \"eth1\"   {  \n"
+		"    send host-name \"sad.example.com\";\n"
+		"    hardware ethernet de:ca:f6:66:ca:fe;\n"
+		"    send dhcp-client-identifier \"useless-and-miserable\";\n"
+		"    script \"/bin/miserable\";\n"
+		"    send dhcp-lease-time 1337;\n"
+		"    request subnet-mask, broadcast-address, time-offset, routers,\n"
+		"        domain-search, domain-name, domain-name-servers, host-name;\n"
+		"    require subnet-mask, domain-name-servers;\n"
+		"    }  \n"
+		"\n"
+		"pseudo \"secondary\" \"eth0\"   {  \n"
+		"    send dhcp-client-identifier \"sad-useless-and-secondary\";\n"
+		"    script \"/bin/secondary\";\n"
+		"    send host-name \"secondary.useless.example.com\";\n"
+		"    send dhcp-lease-time 666;\n"
+		"    request routers;\n"
+		"    require routers;\n"
+		"    }  \n"
+		"\n"
+		"    pseudo \"tertiary\" \"eth0\"   {  \n"
+		"   send dhcp-client-identifier \"sad-useless-and-tertiary\";\n"
+		"  script \"/bin/tertiary\";\n"
+		" send host-name \"tertiary.useless.example.com\";\n"
+		"}  \n"
+		"\n"
+		"  alias{  \n"
+		"    interface \"eth0\";\n"
+		"    fixed-address 192.0.2.1;\n"
+		"    option subnet-mask 255.255.255.0;\n"
+		"  }  \n"
+		"  lease   {  \n"
+		"    interface \"eth0\";\n"
+		"    fixed-address 192.0.2.2;\n"
+		"    option subnet-mask 255.255.255.0;\n"
+		"  }  \n";
+
+	static const char *const expected = \
+		"# Created by NetworkManager\n"
+		"# Merged from /path/to/dhclient.conf\n"
+		"\n"
+		"send host-name \"useless.example.com\";\n"
+		"hardware ethernet de:ad:80:86:ba:be;\n"
+		"send dhcp-client-identifier \"sad-and-useless\";\n"
+		"send dhcp-lease-time 8086;\n"
+		"require subnet-mask;\n"
+		"\n"
+		"option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n"
+		"option ms-classless-static-routes code 249 = array of unsigned integer 8;\n"
+		"option wpad code 252 = string;\n"
+		"\n"
+		"request; # override dhclient defaults\n"
+		"also request subnet-mask;\n"
+		"also request broadcast-address;\n"
+		"also request time-offset;\n"
+		"also request routers;\n"
+		"also request domain-search;\n"
+		"also request domain-name;\n"
+		"also request host-name;\n"
+		"also request rfc3442-classless-static-routes;\n"
+		"also request ms-classless-static-routes;\n"
+		"also request static-routes;\n"
+		"also request wpad;\n"
+		"also request ntp-servers;\n"
+		"\n";
+
+	new_client_id = g_bytes_new (bytes, sizeof (bytes) - 1);
+	test_config (orig, expected,
+	             AF_INET, NULL, 0, FALSE,
+	             NULL,
+	             new_client_id,
+	             "eth0",
+	             NULL);
+}
+
+static void
 test_config_req_intf (void)
 {
 	static const char *const orig = \
@@ -1046,6 +1126,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/dhcp/dhclient/interface/1", test_interface1);
 	g_test_add_func ("/dhcp/dhclient/interface/2", test_interface2);
 	g_test_add_func ("/dhcp/dhclient/config/req_intf", test_config_req_intf);
+	g_test_add_func ("/dhcp/dhclient/structured", test_structured);
 
 	g_test_add_func ("/dhcp/dhclient/read_duid_from_leasefile", test_read_duid_from_leasefile);
 	g_test_add_func ("/dhcp/dhclient/read_commented_duid_from_leasefile", test_read_commented_duid_from_leasefile);
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index b5b93280..90a08d59 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -407,7 +407,7 @@ update (NMDnsPlugin *plugin,
 /*****************************************************************************/
 
 static void
-child_quit (NMDnsPlugin *plugin, gint status)
+child_quit (NMDnsPlugin *plugin, int status)
 {
 	NMDnsDnsmasq *self = NM_DNS_DNSMASQ (plugin);
 	NMDnsDnsmasqPrivate *priv = NM_DNS_DNSMASQ_GET_PRIVATE (self);
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index a29fd81f..6a59b41c 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -310,7 +310,7 @@ _config_data_free (NMDnsConfigData *data)
 	g_slice_free (NMDnsConfigData, data);
 }
 
-static gint
+static int
 _ip_config_data_cmp (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
 {
 	int a_prio, b_prio;
@@ -333,7 +333,7 @@ _ip_config_data_cmp (const NMDnsIPConfigData *a, const NMDnsIPConfigData *b)
 	return 0;
 }
 
-static gint
+static int
 _ip_config_lst_cmp (const CList *a,
                     const CList *b,
                     const void *user_data)
@@ -482,7 +482,7 @@ merge_one_ip_config (NMResolvConfData *rc,
 }
 
 static GPid
-run_netconfig (NMDnsManager *self, GError **error, gint *stdin_fd)
+run_netconfig (NMDnsManager *self, GError **error, int *stdin_fd)
 {
 	char *argv[5];
 	gs_free char *tmp = NULL;
@@ -533,7 +533,7 @@ dispatch_netconfig (NMDnsManager *self,
                     GError **error)
 {
 	GPid pid;
-	gint fd;
+	int fd;
 	int status;
 	gssize l;
 	nm_auto_free_gstring GString *str = NULL;
@@ -970,6 +970,8 @@ compute_hash (NMDnsManager *self, const NMGlobalDnsConfig *global, guint8 buffer
 	else {
 		const CList *head;
 
+		/* FIXME(ip-config-checksum): this relies on the fact that an IP
+		 * configuration without DNS parameters gives a zero checksum. */
 		head = _ip_config_lst_head (self);
 		c_list_for_each_entry (ip_data, head, ip_config_lst)
 			nm_ip_config_hash (ip_data->ip_config, sum, TRUE);
diff --git a/src/dns/nm-dns-plugin.c b/src/dns/nm-dns-plugin.c
index d9400e3e..48f04b00 100644
--- a/src/dns/nm-dns-plugin.c
+++ b/src/dns/nm-dns-plugin.c
@@ -124,7 +124,7 @@ _clear_pidfile (NMDnsPlugin *self)
 static void
 kill_existing (const char *progname, const char *pidfile, const char *kill_match)
 {
-	glong pid;
+	long pid;
 	gs_free char *contents = NULL;
 	gs_free char *cmdline_contents = NULL;
 	guint64 start_time;
@@ -167,7 +167,7 @@ out:
 }
 
 static void
-watch_cb (GPid pid, gint status, gpointer user_data)
+watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDnsPlugin *self = NM_DNS_PLUGIN (user_data);
 	NMDnsPluginPrivate *priv = NM_DNS_PLUGIN_GET_PRIVATE (self);
diff --git a/src/dns/nm-dns-plugin.h b/src/dns/nm-dns-plugin.h
index 80b77d95..8a55921d 100644
--- a/src/dns/nm-dns-plugin.h
+++ b/src/dns/nm-dns-plugin.h
@@ -70,7 +70,7 @@ typedef struct {
 	 * by NMDnsManager.  If the subclass decides the exit status (as returned
 	 * by waitpid(2)) is fatal it should then emit the 'failed' signal.
 	 */
-	void (*child_quit) (NMDnsPlugin *self, gint status);
+	void (*child_quit) (NMDnsPlugin *self, int status);
 } NMDnsPluginClass;
 
 GType nm_dns_plugin_get_type (void);
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index 1a73aae1..544f19c1 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -65,7 +65,6 @@ typedef struct {
 	GDBusProxy *resolve;
 	GCancellable *init_cancellable;
 	GCancellable *update_cancellable;
-	GCancellable *mdns_cancellable;
 	CList request_queue_lst_head;
 } NMDnsSystemdResolvedPrivate;
 
@@ -192,7 +191,8 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 	GVariantBuilder dns, domains;
 	NMCListElem *elem;
 	NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
-	const char *mdns_arg = NULL;
+	NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+	const char *mdns_arg = NULL, *llmnr_arg = NULL;
 
 	g_variant_builder_init (&dns, G_VARIANT_TYPE ("(ia(iay))"));
 	g_variant_builder_add (&dns, "i", ic->ifindex);
@@ -208,8 +208,10 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 
 		update_add_ip_config (self, &dns, &domains, data);
 
-		if (NM_IS_IP4_CONFIG (ip_config))
+		if (NM_IS_IP4_CONFIG (ip_config)) {
 			mdns = NM_MAX (mdns, nm_ip4_config_mdns_get (NM_IP4_CONFIG (ip_config)));
+			llmnr = NM_MAX (llmnr, nm_ip4_config_llmnr_get (NM_IP4_CONFIG (ip_config)));
+		}
 	}
 
 	g_variant_builder_close (&dns);
@@ -231,6 +233,22 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 	}
 	nm_assert (mdns_arg);
 
+	switch (llmnr) {
+	case NM_SETTING_CONNECTION_LLMNR_NO:
+		llmnr_arg = "no";
+		break;
+	case NM_SETTING_CONNECTION_LLMNR_RESOLVE:
+		llmnr_arg = "resolve";
+		break;
+	case NM_SETTING_CONNECTION_LLMNR_YES:
+		llmnr_arg = "yes";
+		break;
+	case NM_SETTING_CONNECTION_LLMNR_DEFAULT:
+		llmnr_arg = "";
+		break;
+	}
+	nm_assert (llmnr_arg);
+
 	_request_item_append (&priv->request_queue_lst_head,
 	                      "SetLinkDNS",
 	                      g_variant_builder_end (&dns));
@@ -240,6 +258,9 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 	_request_item_append (&priv->request_queue_lst_head,
 	                      "SetLinkMulticastDNS",
 	                      g_variant_new ("(is)", ic->ifindex, mdns_arg ?: ""));
+	_request_item_append (&priv->request_queue_lst_head,
+	                      "SetLinkLLMNR",
+	                      g_variant_new ("(is)", ic->ifindex, llmnr_arg ?: ""));
 }
 
 static void
@@ -410,7 +431,6 @@ dispose (GObject *object)
 	g_clear_object (&priv->resolve);
 	nm_clear_g_cancellable (&priv->init_cancellable);
 	nm_clear_g_cancellable (&priv->update_cancellable);
-	nm_clear_g_cancellable (&priv->mdns_cancellable);
 
 	G_OBJECT_CLASS (nm_dns_systemd_resolved_parent_class)->dispose (object);
 }
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 041e75ea..09889441 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -104,7 +104,7 @@ nm_cmd_line_to_str (NMCmdLine *cmd)
 	char *str;
 
 	g_ptr_array_add (cmd->array, NULL);
-	str = g_strjoinv (" ", (gchar **) cmd->array->pdata);
+	str = g_strjoinv (" ", (char **) cmd->array->pdata);
 	g_ptr_array_remove_index (cmd->array, cmd->array->len - 1);
 
 	return str;
@@ -119,7 +119,7 @@ nm_cmd_line_add_string (NMCmdLine *cmd, const char *str)
 /*****************************************************************************/
 
 static void
-dm_watch_cb (GPid pid, gint status, gpointer user_data)
+dm_watch_cb (GPid pid, int status, gpointer user_data)
 {
 	NMDnsMasqManager *manager = NM_DNSMASQ_MANAGER (user_data);
 	NMDnsMasqManagerPrivate *priv = NM_DNSMASQ_MANAGER_GET_PRIVATE (manager);
@@ -187,7 +187,7 @@ create_dm_cmd_line (const char *iface,
 	 * as the gateway or whatever.  So tell dnsmasq not to use any config file
 	 * at all.
 	 */
-	nm_cmd_line_add_string (cmd, "--conf-file");
+	nm_cmd_line_add_string (cmd, "--conf-file=/dev/null");
 
 	nm_cmd_line_add_string (cmd, "--no-hosts");
 	nm_cmd_line_add_string (cmd, "--keep-in-foreground");
@@ -249,6 +249,11 @@ create_dm_cmd_line (const char *iface,
 
 	nm_cmd_line_add_string (cmd, "--dhcp-lease-max=50");
 
+	g_string_append (s, "--dhcp-leasefile=" NMSTATEDIR);
+	g_string_append_printf (s, "/dnsmasq-%s.leases", iface);
+	nm_cmd_line_add_string (cmd, s->str);
+	g_string_truncate (s, 0);
+
 	g_string_append (s, "--pid-file=");
 	g_string_append (s, pidfile);
 	nm_cmd_line_add_string (cmd, s->str);
diff --git a/src/main-utils.c b/src/main-utils.c
index 0f790536..a597ede7 100644
--- a/src/main-utils.c
+++ b/src/main-utils.c
@@ -177,7 +177,7 @@ nm_main_utils_ensure_not_running_pidfile (const char *pidfile)
 	gs_free char *contents = NULL;
 	gs_free char *proc_cmdline = NULL;
 	gsize len = 0;
-	glong pid;
+	long pid;
 	const char *process_name;
 	const char *prgname = g_get_prgname ();
 
diff --git a/src/main.c b/src/main.c
index 7da0c3c2..a5bfb6af 100644
--- a/src/main.c
+++ b/src/main.c
@@ -59,7 +59,6 @@
 #endif
 
 #define NM_DEFAULT_PID_FILE          NMRUNDIR "/NetworkManager.pid"
-#define NM_DEFAULT_SYSTEM_STATE_FILE NMSTATEDIR "/NetworkManager.state"
 
 #define CONFIG_ATOMIC_SECTION_PREFIXES ((char **) NULL)
 
@@ -444,7 +443,7 @@ done:
 	 * state here. We don't bother updating the state as devices
 	 * change during regular operation. If NM is killed with SIGKILL,
 	 * it misses to update the state. */
-	nm_manager_write_device_state (manager);
+	nm_manager_write_device_state_all (manager);
 
 	nm_manager_stop (manager);
 
diff --git a/src/meson.build b/src/meson.build
index e60ab93f..83fc3eac 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -17,35 +17,7 @@ nm_dep = declare_dependency(
   compile_args: nm_cflags
 )
 
-cflags = nm_cflags + [
-  '-DPREFIX="@0@"'.format(nm_prefix),
-  '-DBINDIR="@0@"'.format(nm_bindir),
-  '-DDATADIR="@0@"'.format(nm_datadir),
-  '-DLIBEXECDIR="@0@"'.format(nm_libexecdir),
-  '-DLOCALSTATEDIR="@0@"'.format(nm_localstatedir),
-  '-DRUNSTATEDIR="@0@"'.format(nm_runstatedir),
-  '-DSBINDIR="@0@"'.format(nm_sbindir),
-  '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir),
-  '-DRUNDIR="@0@"'.format(nm_pkgrundir),
-  '-DNMCONFDIR="@0@"'.format(nm_pkgconfdir),
-  '-DNMLOCALEDIR="@0@"'.format(nm_localedir),
-  '-DNMPLUGINDIR="@0@"'.format(nm_plugindir),
-  '-DNMRUNDIR="@0@"'.format(nm_pkgrundir),
-  '-DNMSTATEDIR="@0@"'.format(nm_pkgstatedir),
-  '-DNMLIBDIR="@0@"'.format(nm_pkglibdir)
-]
-
-if enable_dhcpcanon
-  cflags += '-DDHCPCANON_PATH="@0@"'.format(dhcpcanon.path())
-endif
-
-if enable_dhclient
-  cflags += '-DDHCLIENT_PATH="@0@"'.format(dhclient.path())
-endif
-
-if enable_dhcpcd
-  cflags += '-DDHCPCD_PATH="@0@"'.format(dhcpcd.path())
-endif
+cflags = nm_cflags
 
 sources = files(
   'dhcp/nm-dhcp-client.c',
@@ -55,8 +27,9 @@ sources = files(
   'ndisc/nm-lndp-ndisc.c',
   'ndisc/nm-ndisc.c',
   'platform/nm-netlink.c',
-  'platform/wifi/wifi-utils-nl80211.c',
-  'platform/wifi/wifi-utils.c',
+  'platform/wifi/nm-wifi-utils-nl80211.c',
+  'platform/wifi/nm-wifi-utils.c',
+  'platform/wpan/nm-wpan-utils.c',
   'platform/nm-linux-platform.c',
   'platform/nm-platform.c',
   'platform/nm-platform-utils.c',
@@ -79,7 +52,7 @@ deps = [
 ]
 
 if enable_wext
-  sources += files('platform/wifi/wifi-utils-wext.c')
+  sources += files('platform/wifi/nm-wifi-utils-wext.c')
 endif
 
 libnetwork_manager_base = static_library(
@@ -92,6 +65,7 @@ libnetwork_manager_base = static_library(
 
 sources = files(
   'devices/nm-acd-manager.c',
+  'devices/nm-device-6lowpan.c',
   'devices/nm-device-bond.c',
   'devices/nm-device-bridge.c',
   'devices/nm-device.c',
@@ -109,6 +83,8 @@ sources = files(
   'devices/nm-device-veth.c',
   'devices/nm-device-vlan.c',
   'devices/nm-device-vxlan.c',
+  'devices/nm-device-wireguard.c',
+  'devices/nm-device-wpan.c',
   'devices/nm-lldp-listener.c',
   'dhcp/nm-dhcp-dhclient.c',
   'dhcp/nm-dhcp-dhclient-utils.c',
@@ -170,12 +146,10 @@ sources = files(
 deps = [
   dl_dep,
   libndp_dep,
-  # FIXME: Some files use introspection/dbus* headers, so
-  #        this dependency might be needed
-  #libnmdbus_dep,
   libudev_dep,
   nm_core_dep,
-  shared_n_acd_dep
+  shared_n_acd_dep,
+  logind_dep,
 ]
 
 if enable_concheck
@@ -194,10 +168,6 @@ if enable_selinux
   deps += selinux_dep
 endif
 
-if enable_session_tracking
-  deps += logind_dep
-endif
-
 libnetwork_manager = static_library(
   nm_name,
   sources: sources,
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index 04f86312..879eec67 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -1192,7 +1192,7 @@ set_property (GObject *object, guint prop_id,
 	switch (prop_id) {
 	case PROP_PLATFORM:
 		/* construct-only */
-		priv->platform = g_value_get_object (value) ? : NM_PLATFORM_GET;
+		priv->platform = g_value_get_object (value) ?: NM_PLATFORM_GET;
 		if (!priv->platform)
 			g_return_if_reached ();
 
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index aa83d6d5..a2f9ae4a 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -174,7 +174,7 @@ NM_UTILS_FLAGS2STR_DEFINE_STATIC (_state_flags_to_string, NMActivationStateFlags
 /*****************************************************************************/
 
 static void
-_settings_connection_updated (NMSettingsConnection *connection,
+_settings_connection_updated (NMSettingsConnection *sett_conn,
                               gboolean by_user,
                               gpointer user_data)
 {
@@ -196,24 +196,24 @@ _settings_connection_updated (NMSettingsConnection *connection,
 }
 
 static void
-_set_settings_connection (NMActiveConnection *self, NMSettingsConnection *connection)
+_set_settings_connection (NMActiveConnection *self, NMSettingsConnection *sett_conn)
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
-	if (priv->settings_connection.obj == connection)
+	if (priv->settings_connection.obj == sett_conn)
 		return;
 
 	if (priv->settings_connection.obj) {
 		g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_updated, self);
 		g_signal_handlers_disconnect_by_func (priv->settings_connection.obj, _settings_connection_flags_changed, self);
 	}
-	if (connection) {
-		g_signal_connect (connection, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, (GCallback) _settings_connection_updated, self);
+	if (sett_conn) {
+		g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, (GCallback) _settings_connection_updated, self);
 		if (nm_active_connection_get_activation_type (self) == NM_ACTIVATION_TYPE_EXTERNAL)
-			g_signal_connect (connection, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, (GCallback) _settings_connection_flags_changed, self);
+			g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, (GCallback) _settings_connection_flags_changed, self);
 	}
 
-	nm_dbus_track_obj_path_set (&priv->settings_connection, connection, TRUE);
+	nm_dbus_track_obj_path_set (&priv->settings_connection, sett_conn, TRUE);
 }
 
 NMActiveConnectionState
@@ -367,13 +367,13 @@ nm_active_connection_set_state_flags_full (NMActiveConnection *self,
 const char *
 nm_active_connection_get_settings_connection_id (NMActiveConnection *self)
 {
-	NMSettingsConnection *con;
+	NMSettingsConnection *sett_conn;
 
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
 
-	con = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj;
-	return con
-	       ? nm_connection_get_id (NM_CONNECTION (con))
+	sett_conn = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->settings_connection.obj;
+	return sett_conn
+	       ? nm_settings_connection_get_id (sett_conn)
 	       : NULL;
 }
 
@@ -388,31 +388,31 @@ _nm_active_connection_get_settings_connection (NMActiveConnection *self)
 NMSettingsConnection *
 nm_active_connection_get_settings_connection (NMActiveConnection *self)
 {
-	NMSettingsConnection *con;
+	NMSettingsConnection *sett_conn;
 
-	con = _nm_active_connection_get_settings_connection (self);
+	sett_conn = _nm_active_connection_get_settings_connection (self);
 
 	/* Only call this function on an active-connection that is already
 	 * fully set-up (i.e. that has a settings-connection). Other uses
 	 * indicate a bug. */
-	g_return_val_if_fail (con, NULL);
-	return con;
+	g_return_val_if_fail (sett_conn, NULL);
+	return sett_conn;
 }
 
 NMConnection *
 nm_active_connection_get_applied_connection (NMActiveConnection *self)
 {
-	NMConnection *con;
+	NMConnection *connection;
 
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), NULL);
 
-	con = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->applied_connection;
+	connection = NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->applied_connection;
 
 	/* Only call this function on an active-connection that is already
 	 * fully set-up (i.e. that has a settings-connection). Other uses
 	 * indicate a bug. */
-	g_return_val_if_fail (con, NULL);
-	return con;
+	g_return_val_if_fail (connection, NULL);
+	return connection;
 }
 
 static void
@@ -447,7 +447,7 @@ _set_applied_connection_take (NMActiveConnection *self,
 
 void
 nm_active_connection_set_settings_connection (NMActiveConnection *self,
-                                              NMSettingsConnection *connection)
+                                              NMSettingsConnection *sett_conn)
 {
 	NMActiveConnectionPrivate *priv;
 
@@ -455,7 +455,7 @@ nm_active_connection_set_settings_connection (NMActiveConnection *self,
 
 	priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
 	g_return_if_fail (!priv->settings_connection.obj);
 	g_return_if_fail (!priv->applied_connection);
 
@@ -468,10 +468,10 @@ nm_active_connection_set_settings_connection (NMActiveConnection *self,
 	 * For example, we'd have to cancel all pending seret requests. */
 	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (self)));
 
-	_set_settings_connection (self, connection);
+	_set_settings_connection (self, sett_conn);
 
 	_set_applied_connection_take (self,
-	                              nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection.obj)));
+	                              nm_simple_connection_new_clone (nm_settings_connection_get_connection (priv->settings_connection.obj)));
 }
 
 gboolean
@@ -503,8 +503,10 @@ nm_active_connection_clear_secrets (NMActiveConnection *self)
 
 	if (nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection.obj,
 	                                                              priv->applied_connection,
-	                                                              NM_SETTING_COMPARE_FLAG_NONE))
-		nm_connection_clear_secrets ((NMConnection *) priv->settings_connection.obj);
+	                                                              NM_SETTING_COMPARE_FLAG_NONE)) {
+		/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+		nm_connection_clear_secrets (nm_settings_connection_get_connection (priv->settings_connection.obj));
+	}
 	nm_connection_clear_secrets (priv->applied_connection);
 }
 
@@ -921,7 +923,7 @@ _settings_connection_flags_changed (NMSettingsConnection *settings_connection,
 
 	_set_activation_type_managed (self);
 	if (!nm_device_reapply (nm_active_connection_get_device (self),
-	                        NM_CONNECTION (nm_active_connection_get_settings_connection (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);
@@ -1112,7 +1114,7 @@ nm_active_connection_authorize (NMActiveConnection *self,
 {
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 	const char *wifi_permission = NULL;
-	NMConnection *con;
+	NMConnection *connection;
 
 	g_return_if_fail (result_func);
 	g_return_if_fail (!priv->auth.call_id_network_control);
@@ -1122,11 +1124,11 @@ nm_active_connection_authorize (NMActiveConnection *self,
 		g_return_if_fail (NM_IS_CONNECTION (initial_connection));
 		g_return_if_fail (!priv->settings_connection.obj);
 		g_return_if_fail (!priv->applied_connection);
-		con = initial_connection;
+		connection = initial_connection;
 	} else {
 		g_return_if_fail (NM_IS_SETTINGS_CONNECTION (priv->settings_connection.obj));
 		g_return_if_fail (NM_IS_CONNECTION (priv->applied_connection));
-		con = priv->applied_connection;
+		connection = priv->applied_connection;
 	}
 
 	priv->auth.call_id_network_control = nm_auth_manager_check_authorization (nm_auth_manager_get (),
@@ -1137,7 +1139,7 @@ nm_active_connection_authorize (NMActiveConnection *self,
 	                                                                          self);
 
 	/* Shared wifi connections require special permissions too */
-	wifi_permission = nm_utils_get_shared_wifi_permission (con);
+	wifi_permission = nm_utils_get_shared_wifi_permission (connection);
 	if (wifi_permission) {
 		priv->auth.call_id_wifi_shared_permission = nm_auth_manager_check_authorization (nm_auth_manager_get (),
 		                                                                                 priv->subject,
@@ -1227,13 +1229,13 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_string (value, nm_dbus_track_obj_path_get (&priv->settings_connection));
 		break;
 	case PROP_ID:
-		g_value_set_string (value, nm_connection_get_id (NM_CONNECTION (priv->settings_connection.obj)));
+		g_value_set_string (value, nm_settings_connection_get_id (priv->settings_connection.obj));
 		break;
 	case PROP_UUID:
-		g_value_set_string (value, nm_connection_get_uuid (NM_CONNECTION (priv->settings_connection.obj)));
+		g_value_set_string (value, nm_settings_connection_get_uuid (priv->settings_connection.obj));
 		break;
 	case PROP_TYPE:
-		g_value_set_string (value, nm_connection_get_connection_type (NM_CONNECTION (priv->settings_connection.obj)));
+		g_value_set_string (value, nm_settings_connection_get_connection_type (priv->settings_connection.obj));
 		break;
 
 	case PROP_SPECIFIC_OBJECT:
@@ -1304,16 +1306,16 @@ set_property (GObject *object, guint prop_id,
 	NMActiveConnection *self = (NMActiveConnection *) object;
 	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
 	const char *tmp;
-	NMSettingsConnection *con;
+	NMSettingsConnection *sett_conn;
 	NMConnection *acon;
 	int i;
 
 	switch (prop_id) {
 	case PROP_INT_SETTINGS_CONNECTION:
 		/* construct-only */
-		con = g_value_get_object (value);
-		if (con)
-			_set_settings_connection (self, con);
+		sett_conn = g_value_get_object (value);
+		if (sett_conn)
+			_set_settings_connection (self, sett_conn);
 		break;
 	case PROP_INT_APPLIED_CONNECTION:
 		/* construct-only */
@@ -1410,7 +1412,7 @@ constructed (GObject *object)
 
 	if (   !priv->applied_connection
 	    && priv->settings_connection.obj)
-		priv->applied_connection = nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection.obj));
+		priv->applied_connection = nm_simple_connection_new_clone (nm_settings_connection_get_connection (priv->settings_connection.obj));
 
 	_LOGD ("constructed (%s, version-id %llu, type %s)",
 	       G_OBJECT_TYPE_NAME (self),
diff --git a/src/nm-auth-subject.c b/src/nm-auth-subject.c
index 117a3815..9ed65e2d 100644
--- a/src/nm-auth-subject.c
+++ b/src/nm-auth-subject.c
@@ -200,7 +200,7 @@ _new_unix_process (GDBusMethodInvocation *context,
 		return NULL;
 
 	g_return_val_if_fail (dbus_sender && *dbus_sender, NULL);
-	/* polkit glib library stores uid and pid as gint. There might be some
+	/* 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);
@@ -356,7 +356,7 @@ constructed (GObject *object)
 		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 gint. */
+		 * 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)) {
diff --git a/src/nm-checkpoint-manager.c b/src/nm-checkpoint-manager.c
index 7e8c8aea..0c2af831 100644
--- a/src/nm-checkpoint-manager.c
+++ b/src/nm-checkpoint-manager.c
@@ -265,8 +265,8 @@ nm_checkpoint_manager_lookup_by_path (NMCheckpointManager *self, const char *pat
 
 	g_return_val_if_fail (self, NULL);
 
-	checkpoint = (NMCheckpoint *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (GET_MANAGER (self))),
-	                                                             path);
+	checkpoint = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (GET_MANAGER (self))),
+	                                            path);
 	if (   !checkpoint
 	    || !NM_IS_CHECKPOINT (checkpoint)) {
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_INVALID_ARGUMENTS,
diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c
index a17f7eda..b0cf1f51 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -159,7 +159,7 @@ find_settings_connection (NMCheckpoint *self,
 {
 	NMCheckpointPrivate *priv = NM_CHECKPOINT_GET_PRIVATE (self);
 	NMActiveConnection *active;
-	NMSettingsConnection *connection;
+	NMSettingsConnection *sett_conn;
 	const char *uuid, *ac_uuid;
 	const CList *tmp_clist;
 
@@ -167,14 +167,14 @@ find_settings_connection (NMCheckpoint *self,
 	*need_update = FALSE;
 
 	uuid = nm_connection_get_uuid (dev_checkpoint->settings_connection);
-	connection = nm_settings_get_connection_by_uuid (nm_settings_get (), uuid);
+	sett_conn = nm_settings_get_connection_by_uuid (nm_settings_get (), uuid);
 
-	if (!connection)
+	if (!sett_conn)
 		return NULL;
 
 	/* Now check if the connection changed, ... */
 	if (!nm_connection_compare (dev_checkpoint->settings_connection,
-	                            NM_CONNECTION (connection),
+	                            nm_settings_connection_get_connection (sett_conn),
 	                            NM_SETTING_COMPARE_FLAG_EXACT)) {
 		_LOGT ("rollback: settings connection %s changed", uuid);
 		*need_update = TRUE;
@@ -193,7 +193,7 @@ find_settings_connection (NMCheckpoint *self,
 	if (!active) {
 		_LOGT ("rollback: connection %s is not active", uuid);
 		*need_activation = TRUE;
-		return connection;
+		return sett_conn;
 	}
 
 	/* ... or if the connection was reactivated/reapplied */
@@ -202,7 +202,7 @@ find_settings_connection (NMCheckpoint *self,
 		*need_activation = TRUE;
 	}
 
-	return connection;
+	return sett_conn;
 }
 
 GVariant *
@@ -337,8 +337,8 @@ activate:
 				                                     dev_checkpoint->activation_reason,
 				                                     &local_error)) {
 					_LOGW ("rollback: reactivation of connection %s/%s failed: %s",
-					       nm_connection_get_id ((NMConnection *) connection),
-					       nm_connection_get_uuid ((NMConnection *) connection),
+					       nm_settings_connection_get_id (connection),
+					       nm_settings_connection_get_uuid (connection),
 					       local_error->message);
 					g_clear_error (&local_error);
 					result = NM_ROLLBACK_RESULT_ERR_FAILED;
@@ -436,12 +436,9 @@ device_checkpoint_create (NMDevice *device)
 		applied_connection = nm_act_request_get_applied_connection (act_request);
 
 		dev_checkpoint->applied_connection = nm_simple_connection_new_clone (applied_connection);
-		dev_checkpoint->settings_connection =
-		    nm_simple_connection_new_clone (NM_CONNECTION (settings_connection));
-		dev_checkpoint->ac_version_id =
-		    nm_active_connection_version_id_get (NM_ACTIVE_CONNECTION (act_request));
-		dev_checkpoint->activation_reason =
-		    nm_active_connection_get_activation_reason (NM_ACTIVE_CONNECTION (act_request));
+		dev_checkpoint->settings_connection = nm_simple_connection_new_clone (nm_settings_connection_get_connection (settings_connection));
+		dev_checkpoint->ac_version_id = nm_active_connection_version_id_get (NM_ACTIVE_CONNECTION (act_request));
+		dev_checkpoint->activation_reason = nm_active_connection_get_activation_reason (NM_ACTIVE_CONNECTION (act_request));
 	}
 
 	return dev_checkpoint;
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 813ef9c8..8d84e74a 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -183,11 +183,11 @@ nm_config_data_has_value (const NMConfigData *self, const char *group, const cha
 	return !!value;
 }
 
-gint
-nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, const char *key, gint default_value)
+int
+nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, const char *key, int default_value)
 {
 	char *str;
-	gint value = default_value;
+	int value = default_value;
 
 	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), default_value);
 	g_return_val_if_fail (group && *group, default_value);
@@ -655,12 +655,12 @@ nm_config_data_log (const NMConfigData *self,
 			const char *group = default_values[g].group;
 			gssize idx;
 
-			idx = _nm_utils_array_find_binary_search ((gconstpointer *) groups_full->pdata,
-			                                          sizeof (char *),
-			                                          groups_full->len,
-			                                          &group,
-			                                          (GCompareDataFunc) _nm_config_data_log_sort,
-			                                          NULL);
+			idx = nm_utils_array_find_binary_search ((gconstpointer *) groups_full->pdata,
+			                                         sizeof (char *),
+			                                         groups_full->len,
+			                                         &group,
+			                                         (GCompareDataFunc) _nm_config_data_log_sort,
+			                                         NULL);
 			if (idx < 0)
 				g_ptr_array_insert (groups_full, (~idx), (gpointer) group);
 		}
@@ -1335,8 +1335,8 @@ gboolean
 nm_config_data_get_device_config_boolean (const NMConfigData *self,
                                           const char *property,
                                           NMDevice *device,
-                                          gint val_no_match,
-                                          gint val_invalid)
+                                          int val_no_match,
+                                          int val_invalid)
 {
 	gs_free char *value = NULL;
 	gboolean has_match;
@@ -1371,6 +1371,20 @@ nm_config_data_get_connection_default (const NMConfigData *self,
 	return value;
 }
 
+gint64
+nm_config_data_get_connection_default_int64 (const NMConfigData *self,
+                                             const char *property,
+                                             NMDevice *device,
+                                             gint64 min,
+                                             gint64 max,
+                                             gint64 fallback)
+{
+	gs_free char *value = NULL;
+
+	value = nm_config_data_get_connection_default (self, property, device);
+	return _nm_utils_ascii_str_to_int64 (value, 10, min, max, fallback);
+}
+
 static void
 _get_connection_info_init (MatchSectionInfo *connection_info, GKeyFile *keyfile, char *group)
 {
diff --git a/src/nm-config-data.h b/src/nm-config-data.h
index 650fbe14..b52ddcc4 100644
--- a/src/nm-config-data.h
+++ b/src/nm-config-data.h
@@ -154,7 +154,7 @@ const char *nm_config_data_get_config_description (const NMConfigData *config_da
 gboolean nm_config_data_has_group (const NMConfigData *self, const char *group);
 gboolean nm_config_data_has_value (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags);
 char *nm_config_data_get_value (const NMConfigData *config_data, const char *group, const char *key, NMConfigGetValueFlags flags);
-gint nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, const char *key, gint default_value);
+int nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, const char *key, int default_value);
 gint64 nm_config_data_get_value_int64 (const NMConfigData *self, const char *group, const char *key, guint base, gint64 min, gint64 max, gint64 fallback);
 
 char **nm_config_data_get_plugins (const NMConfigData *config_data, gboolean allow_default);
@@ -181,6 +181,13 @@ char *nm_config_data_get_connection_default (const NMConfigData *self,
                                              const char *property,
                                              NMDevice *device);
 
+gint64 nm_config_data_get_connection_default_int64 (const NMConfigData *self,
+                                                    const char *property,
+                                                    NMDevice *device,
+                                                    gint64 min,
+                                                    gint64 max,
+                                                    gint64 fallback);
+
 char *nm_config_data_get_device_config (const NMConfigData *self,
                                         const char *property,
                                         NMDevice *device,
@@ -195,8 +202,8 @@ char *nm_config_data_get_device_config_by_pllink (const NMConfigData *self,
 gboolean nm_config_data_get_device_config_boolean (const NMConfigData *self,
                                                    const char *property,
                                                    NMDevice *device,
-                                                   gint val_no_match,
-                                                   gint val_invalid);
+                                                   int val_no_match,
+                                                   int val_invalid);
 
 char **nm_config_data_get_groups (const NMConfigData *self);
 char **nm_config_data_get_keys (const NMConfigData *self, const char *group);
diff --git a/src/nm-config.c b/src/nm-config.c
index d4682ed6..102070e9 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -167,18 +167,18 @@ static void _set_config_data (NMConfig *self, NMConfigData *new_data, NMConfigCh
 
 /*****************************************************************************/
 
-gint
+int
 nm_config_parse_boolean (const char *str,
-                         gint default_value)
+                         int default_value)
 {
 	return _nm_utils_ascii_str_to_bool (str, default_value);
 }
 
-gint
+int
 nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
                                const char *section,
                                const char *key,
-                               gint default_value)
+                               int default_value)
 {
 	gs_free char *str = NULL;
 
@@ -1690,7 +1690,7 @@ nm_config_set_values (NMConfig *self,
 	GError *local = NULL;
 	NMConfigData *new_data = NULL;
 	gs_strfreev char **groups = NULL;
-	gint g;
+	int g;
 
 	g_return_if_fail (NM_IS_CONFIG (self));
 
@@ -1948,7 +1948,7 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
 	gs_free char *perm_hw_addr_fake = NULL;
 	gsize connection_uuid_len;
 	gsize perm_hw_addr_fake_len;
-	gint nm_owned = -1;
+	int nm_owned = -1;
 	char *p;
 	guint32 route_metric_default_effective;
 	guint32 route_metric_default_aspired;
@@ -2126,7 +2126,7 @@ nm_config_device_state_write (int ifindex,
                               NMConfigDeviceStateManagedType managed,
                               const char *perm_hw_addr_fake,
                               const char *connection_uuid,
-                              gint nm_owned,
+                              int nm_owned,
                               guint32 route_metric_default_aspired,
                               guint32 route_metric_default_effective)
 {
diff --git a/src/nm-config.h b/src/nm-config.h
index 5d027ce0..1b013ff3 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -159,13 +159,13 @@ void _nm_config_state_set (NMConfig *config,
 #define nm_config_state_set(config, allow_persist, force_persist, ...) \
     _nm_config_state_set (config, allow_persist, force_persist, ##__VA_ARGS__, 0)
 
-gint nm_config_parse_boolean (const char *str, gint default_value);
+int nm_config_parse_boolean (const char *str, int default_value);
 
 GKeyFile *nm_config_create_keyfile (void);
-gint nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
+int nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
                                     const char *section,
                                     const char *key,
-                                    gint default_value);
+                                    int default_value);
 gint64 nm_config_keyfile_get_int64 (const GKeyFile *keyfile,
                                     const char *section,
                                     const char *key,
@@ -234,7 +234,7 @@ gboolean nm_config_device_state_write (int ifindex,
                                        NMConfigDeviceStateManagedType managed,
                                        const char *perm_hw_addr_fake,
                                        const char *connection_uuid,
-                                       gint nm_owned,
+                                       int nm_owned,
                                        guint32 route_metric_default_aspired,
                                        guint32 route_metric_default_effective);
 
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 1ce6d3cf..3b779677 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -240,7 +240,7 @@ _con_curl_check_connectivity (CURLM *mhandle, int sockfd, int ev_bitmask)
 	NMConnectivityCheckHandle *cb_data;
 	CURLMsg *msg;
 	CURLcode eret;
-	gint m_left;
+	int m_left;
 	long response_code;
 	CURLMcode ret;
 	int running_handles;
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index 54ccdb83..ca8c9526 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright 2004 - 2014 Red Hat, Inc.
+ * Copyright 2004 - 2018 Red Hat, Inc.
  * Copyright 2005 - 2008 Novell, Inc.
  */
 
@@ -25,6 +25,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <string.h>
 #include <unistd.h>
 #include <stdlib.h>
@@ -37,6 +38,7 @@
 #include <net/ethernet.h>
 
 #include "nm-utils/nm-random-utils.h"
+#include "nm-utils/nm-io-utils.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-connection.h"
@@ -45,15 +47,6 @@
 #include "nm-setting-wireless.h"
 #include "nm-setting-wireless-security.h"
 
-/*
- * Some toolchains (E.G. uClibc 0.9.33 and earlier) don't export
- * CLOCK_BOOTTIME even though the kernel supports it, so provide a
- * local definition
- */
-#ifndef CLOCK_BOOTTIME
-#define CLOCK_BOOTTIME 7
-#endif
-
 G_STATIC_ASSERT (sizeof (NMUtilsTestFlags) <= sizeof (int));
 static int _nm_utils_testing = 0;
 
@@ -532,7 +525,7 @@ _kc_waited_to_string (char *buf, gint64 wait_start_us)
 }
 
 static void
-_kc_cb_watch_child (GPid pid, gint status, gpointer user_data)
+_kc_cb_watch_child (GPid pid, int status, gpointer user_data)
 {
 	KillChildAsyncData *data = user_data;
 	char buf_exit[KC_EXIT_TO_STRING_BUF_SIZE], buf_wait[KC_WAITED_TO_STRING];
@@ -1455,7 +1448,7 @@ match_config_eval (const char *str, const char *tag, guint cur_nm_version)
 {
 	gs_free char *s_ver = NULL;
 	gs_strfreev char **s_ver_tokens = NULL;
-	gint v_maj = -1, v_min = -1, v_mic = -1;
+	int v_maj = -1, v_min = -1, v_mic = -1;
 	guint c_maj = -1, c_min = -1, c_mic = -1;
 	guint n_tokens;
 
@@ -1587,9 +1580,9 @@ nm_match_spec_split (const char *value)
 	/* Copied from glibs g_key_file_parse_value_as_string() function
 	 * and adjusted. */
 
-	string_value = g_new (gchar, strlen (value) + 1);
+	string_value = g_new (char, strlen (value) + 1);
 
-	p = (gchar *) value;
+	p = (char *) value;
 
 	/* skip over leading whitespace */
 	while (g_ascii_isspace (*p))
@@ -1739,6 +1732,33 @@ nm_match_spec_join (GSList *specs)
 	return g_string_free (str, FALSE);
 }
 
+gboolean
+nm_wildcard_match_check (const char *str,
+                         const char *const *patterns,
+                         guint num_patterns)
+{
+	guint i, neg = 0;
+
+	for (i = 0; i < num_patterns; i++) {
+		if (patterns[i][0] == '!') {
+			neg++;
+			if (!fnmatch (patterns[i] + 1, str, 0))
+				return FALSE;
+		}
+	}
+
+	if (neg == num_patterns)
+		return TRUE;
+
+	for (i = 0; i < num_patterns; i++) {
+		if (   patterns[i][0] != '!'
+		    && !fnmatch (patterns[i], str, 0))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
 /*****************************************************************************/
 
 char *
@@ -2036,7 +2056,7 @@ typedef struct
 	NMSettingDiffResult diff_result;
 } LogConnectionSettingItem;
 
-static gint
+static int
 _log_connection_sort_hashes_fcn (gconstpointer a, gconstpointer b)
 {
 	const LogConnectionSettingData *v1 = a;
@@ -2079,7 +2099,7 @@ _log_connection_sort_hashes (NMConnection *connection, NMConnection *diff_base,
 	return sorted_hashes;
 }
 
-static gint
+static int
 _log_connection_sort_names_fcn (gconstpointer a, gconstpointer b)
 {
 	const LogConnectionSettingItem *v1 = a;
@@ -2541,262 +2561,6 @@ nm_utils_machine_id_read (void)
 
 /*****************************************************************************/
 
-_nm_printf (3, 4)
-static int
-_get_contents_error (GError **error, int errsv, const char *format, ...)
-{
-	if (errsv < 0)
-		errsv = -errsv;
-	else if (!errsv)
-		errsv = errno;
-
-	if (error) {
-		char *msg;
-		va_list args;
-
-		va_start (args, format);
-		msg = g_strdup_vprintf (format, args);
-		va_end (args);
-		g_set_error (error,
-		             G_FILE_ERROR,
-		             g_file_error_from_errno (errsv),
-		             "%s: %s",
-		             msg, g_strerror (errsv));
-		g_free (msg);
-	}
-	return -errsv;
-}
-
-/**
- * nm_utils_fd_get_contents:
- * @fd: open file descriptor to read. The fd will not be closed,
- *   but don't rely on its state afterwards.
- * @close_fd: if %TRUE, @fd will be closed by the function.
- *  Passing %TRUE here might safe a syscall for dup().
- * @max_length: allocate at most @max_length bytes. If the
- *   file is larger, reading will fail. Set to zero to use
- *   a very large default.
- *
- *   WARNING: @max_length is here to avoid a crash for huge/unlimited files.
- *   For example, stat(/sys/class/net/enp0s25/ifindex) gives a filesize of
- *   4K, although the actual real is small. @max_length is the memory
- *   allocated in the process of reading the file, thus it must be at least
- *   the size reported by fstat.
- *   If you set it to 1K, read will fail because fstat() claims the
- *   file is larger.
- *
- * @contents: the output buffer with the file read. It is always
- *   NUL terminated. The buffer is at most @max_length long, including
- *  the NUL byte. That is, it reads only files up to a length of
- *  @max_length - 1 bytes.
- * @length: optional output argument of the read file size.
- *
- * A reimplementation of g_file_get_contents() with a few differences:
- *   - accepts an open fd, instead of a path name. This allows you to
- *     use openat().
- *   - limits the maxium filesize to max_length.
- *
- * Returns: a negative error code on failure.
- */
-int
-nm_utils_fd_get_contents (int fd,
-                          gboolean close_fd,
-                          gsize max_length,
-                          char **contents,
-                          gsize *length,
-                          GError **error)
-{
-	nm_auto_close int fd_keeper = close_fd ? fd : -1;
-	struct stat stat_buf;
-	gs_free char *str = NULL;
-
-	g_return_val_if_fail (fd >= 0, -EINVAL);
-	g_return_val_if_fail (contents, -EINVAL);
-	g_return_val_if_fail (!error || !*error, -EINVAL);
-
-	if (fstat (fd, &stat_buf) < 0)
-		return _get_contents_error (error, 0, "failure during fstat");
-
-	if (!max_length) {
-		/* default to a very large size, but not extreme */
-		max_length = 2 * 1024 * 1024;
-	}
-
-	if (   stat_buf.st_size > 0
-	    && S_ISREG (stat_buf.st_mode)) {
-		const gsize n_stat = stat_buf.st_size;
-		ssize_t n_read;
-
-		if (n_stat > max_length - 1)
-			return _get_contents_error (error, EMSGSIZE, "file too large (%zu+1 bytes with maximum %zu bytes)", n_stat, max_length);
-
-		str = g_try_malloc (n_stat + 1);
-		if (!str)
-			return _get_contents_error (error, ENOMEM, "failure to allocate buffer of %zu+1 bytes", n_stat);
-
-		n_read = nm_utils_fd_read_loop (fd, str, n_stat, TRUE);
-		if (n_read < 0)
-			return _get_contents_error (error, n_read, "error reading %zu bytes from file descriptor", n_stat);
-		str[n_read] = '\0';
-
-		if (n_read < n_stat) {
-			char *tmp;
-
-			tmp = g_try_realloc (str, n_read + 1);
-			if (!tmp)
-				return _get_contents_error (error, ENOMEM, "failure to reallocate buffer with %zu bytes", n_read + 1);
-			str = tmp;
-		}
-		NM_SET_OUT (length, n_read);
-	} else {
-		nm_auto_fclose FILE *f = NULL;
-		char buf[4096];
-		gsize n_have, n_alloc;
-		int fd2;
-
-		if (fd_keeper >= 0)
-			fd2 = nm_steal_fd (&fd_keeper);
-		else {
-			fd2 = fcntl (fd, F_DUPFD_CLOEXEC, 0);
-			if (fd2 < 0)
-				return _get_contents_error (error, 0, "error during dup");
-		}
-
-		if (!(f = fdopen (fd2, "r"))) {
-			nm_close (fd2);
-			return _get_contents_error (error, 0, "failure during fdopen");
-		}
-
-		n_have = 0;
-		n_alloc = 0;
-
-		while (!feof (f)) {
-			int errsv;
-			gsize n_read;
-
-			n_read = fread (buf, 1, sizeof (buf), f);
-			errsv = errno;
-			if (ferror (f))
-				return _get_contents_error (error, errsv, "error during fread");
-
-			if (   n_have > G_MAXSIZE - 1 - n_read
-			    || n_have + n_read + 1 > max_length) {
-				return _get_contents_error (error, EMSGSIZE, "file stream too large (%zu+1 bytes with maximum %zu bytes)",
-				                            (n_have > G_MAXSIZE - 1 - n_read) ? G_MAXSIZE : n_have + n_read,
-				                            max_length);
-			}
-
-			if (n_have + n_read + 1 >= n_alloc) {
-				char *tmp;
-
-				if (str) {
-					if (n_alloc >= max_length / 2)
-						n_alloc = max_length;
-					else
-						n_alloc *= 2;
-				} else
-					n_alloc = NM_MIN (n_read + 1, sizeof (buf));
-
-				tmp = g_try_realloc (str, n_alloc);
-				if (!tmp)
-					return _get_contents_error (error, ENOMEM, "failure to allocate buffer of %zu bytes", n_alloc);
-				str = tmp;
-			}
-
-			memcpy (str + n_have, buf, n_read);
-			n_have += n_read;
-		}
-
-		if (n_alloc == 0)
-			str = g_new0 (gchar, 1);
-		else {
-			str[n_have] = '\0';
-			if (n_have + 1 < n_alloc) {
-				char *tmp;
-
-				tmp = g_try_realloc (str, n_have + 1);
-				if (!tmp)
-					return _get_contents_error (error, ENOMEM, "failure to truncate buffer to %zu bytes", n_have + 1);
-				str = tmp;
-			}
-		}
-
-		NM_SET_OUT (length, n_have);
-	}
-
-	*contents = g_steal_pointer (&str);
-	return 0;
-}
-
-/**
- * nm_utils_file_get_contents:
- * @dirfd: optional file descriptor to use openat(). If negative, use plain open().
- * @filename: the filename to open. Possibly relative to @dirfd.
- * @max_length: allocate at most @max_length bytes.
- *   WARNING: see nm_utils_fd_get_contents() hint about @max_length.
- * @contents: the output buffer with the file read. It is always
- *   NUL terminated. The buffer is at most @max_length long, including
- *  the NUL byte. That is, it reads only files up to a length of
- *  @max_length - 1 bytes.
- * @length: optional output argument of the read file size.
- *
- * A reimplementation of g_file_get_contents() with a few differences:
- *   - accepts an @dirfd to open @filename relative to that path via openat().
- *   - limits the maxium filesize to max_length.
- *   - uses O_CLOEXEC on internal file descriptor
- *
- * Returns: a negative error code on failure.
- */
-int
-nm_utils_file_get_contents (int dirfd,
-                            const char *filename,
-                            gsize max_length,
-                            char **contents,
-                            gsize *length,
-                            GError **error)
-{
-	int fd;
-	int errsv;
-
-	g_return_val_if_fail (filename && filename[0], -EINVAL);
-
-	if (dirfd >= 0) {
-		fd = openat (dirfd, filename, O_RDONLY | O_CLOEXEC);
-		if (fd < 0) {
-			errsv = errno;
-
-			g_set_error (error,
-			             G_FILE_ERROR,
-			             g_file_error_from_errno (errsv),
-			             "Failed to open file \"%s\" with openat: %s",
-			             filename,
-			             g_strerror (errsv));
-			return -errsv;
-		}
-	} else {
-		fd = open (filename, O_RDONLY | O_CLOEXEC);
-		if (fd < 0) {
-			errsv = errno;
-
-			g_set_error (error,
-			             G_FILE_ERROR,
-			             g_file_error_from_errno (errsv),
-			             "Failed to open file \"%s\": %s",
-			             filename,
-			             g_strerror (errsv));
-			return -errsv;
-		}
-	}
-	return nm_utils_fd_get_contents (fd,
-	                                 TRUE,
-	                                 max_length,
-	                                 contents,
-	                                 length,
-	                                 error);
-}
-
-/*****************************************************************************/
-
 static gboolean
 _secret_key_read (guint8 **out_secret_key,
                   gsize *out_key_len)
@@ -2923,6 +2687,7 @@ nm_utils_get_boot_id (void)
 		gs_free char *contents = NULL;
 
 		nm_utils_file_get_contents (-1, "/proc/sys/kernel/random/boot_id", 0,
+		                            NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
 		                            &contents, NULL, NULL);
 		if (contents) {
 			g_strstrip (contents);
@@ -3017,7 +2782,6 @@ nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
 		out_iid->id_u8[0] |= 0x02;
 		return TRUE;
 	case NM_LINK_TYPE_GRE:
-	case NM_LINK_TYPE_GRETAP:
 		/* Hardware address is the network-endian IPv4 address */
 		g_return_val_if_fail (hwaddr_len == 4, FALSE);
 		addr = * (guint32 *) hwaddr;
@@ -3027,6 +2791,11 @@ nm_utils_get_ipv6_interface_identifier (NMLinkType link_type,
 		out_iid->id_u8[3] = 0xFE;
 		memcpy (out_iid->id_u8 + 4, &addr, 4);
 		return TRUE;
+	case NM_LINK_TYPE_6LOWPAN:
+		/* The hardware address is already 64-bit. This is the case for
+		* IEEE 802.15.4 networks. */
+		memcpy (out_iid->id_u8, hwaddr, sizeof (out_iid->id_u8));
+		return TRUE;
 	default:
 		if (hwaddr_len == ETH_ALEN) {
 			/* Translate 48-bit MAC address to a 64-bit Modified EUI-64.  See
@@ -3661,8 +3430,8 @@ nm_utils_g_value_set_strv (GValue *value, GPtrArray *strings)
 /*****************************************************************************/
 
 static gboolean
-debug_key_matches (const gchar *key,
-                   const gchar *token,
+debug_key_matches (const char *key,
+                   const char *token,
                    guint        length)
 {
 	/* may not call GLib functions: see note in g_parse_debug_string() */
@@ -3964,116 +3733,12 @@ nm_utils_get_reverse_dns_domains_ip6 (const struct in6_addr *ip, guint8 plen, GP
 #undef N_SHIFT
 }
 
-/**
- * Copied from GLib's g_file_set_contents() et al., but allows
- * specifying a mode for the new file.
- */
-gboolean
-nm_utils_file_set_contents (const gchar *filename,
-                            const gchar *contents,
-                            gssize length,
-                            mode_t mode,
-                            GError **error)
-{
-	gs_free char *tmp_name = NULL;
-	struct stat statbuf;
-	int errsv;
-	gssize s;
-	int fd;
-
-	g_return_val_if_fail (filename, FALSE);
-	g_return_val_if_fail (contents || !length, FALSE);
-	g_return_val_if_fail (!error || !*error, FALSE);
-	g_return_val_if_fail (length >= -1, FALSE);
-
-	if (length == -1)
-		length = strlen (contents);
-
-	tmp_name = g_strdup_printf ("%s.XXXXXX", filename);
-	fd = g_mkstemp_full (tmp_name, O_RDWR, mode);
-	if (fd < 0) {
-		errsv = errno;
-		g_set_error (error,
-		             G_FILE_ERROR,
-		             g_file_error_from_errno (errsv),
-		             "failed to create file %s: %s",
-		             tmp_name,
-		             g_strerror (errsv));
-		return FALSE;
-	}
-
-	while (length > 0) {
-		s = write (fd, contents, length);
-		if (s < 0) {
-			errsv = errno;
-			if (errsv == EINTR)
-				continue;
-
-			nm_close (fd);
-			unlink (tmp_name);
-
-			g_set_error (error,
-			             G_FILE_ERROR,
-			             g_file_error_from_errno (errsv),
-			             "failed to write to file %s: %s",
-			             tmp_name,
-			             g_strerror (errsv));
-			return FALSE;
-		}
-
-		g_assert (s <= length);
-
-		contents += s;
-		length -= s;
-	}
-
-	/* If the final destination exists and is > 0 bytes, we want to sync the
-	 * newly written file to ensure the data is on disk when we rename over
-	 * the destination. Otherwise if we get a system crash we can lose both
-	 * the new and the old file on some filesystems. (I.E. those that don't
-	 * guarantee the data is written to the disk before the metadata.)
-	 */
-	if (   lstat (filename, &statbuf) == 0
-	    && statbuf.st_size > 0
-	    && fsync (fd) != 0) {
-		errsv = errno;
-
-		nm_close (fd);
-		unlink (tmp_name);
-
-		g_set_error (error,
-		             G_FILE_ERROR,
-		             g_file_error_from_errno (errsv),
-		             "failed to fsync %s: %s",
-		             tmp_name,
-		             g_strerror (errsv));
-		return FALSE;
-	}
-
-	nm_close (fd);
-
-	if (rename (tmp_name, filename)) {
-		errsv = errno;
-		unlink (tmp_name);
-		g_set_error (error,
-		             G_FILE_ERROR,
-		             g_file_error_from_errno (errsv),
-		             "failed to rename %s to %s: %s",
-		             tmp_name,
-		             filename,
-		             g_strerror (errsv));
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
 struct plugin_info {
 	char *path;
 	struct stat st;
 };
 
-static gint
+static int
 read_device_factory_paths_sort_fcn (gconstpointer a, gconstpointer b)
 {
 	const struct plugin_info *da = a;
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 08530400..30d1360a 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -204,9 +204,9 @@ typedef enum {
 
 NMMatchSpecMatchType nm_match_spec_device (const GSList *specs,
                                            const char *interface_name,
+                                           const char *device_type,
                                            const char *driver,
                                            const char *driver_version,
-                                           const char *device_type,
                                            const char *hwaddr,
                                            const char *s390_subchannels);
 NMMatchSpecMatchType nm_match_spec_config (const GSList *specs,
@@ -215,6 +215,10 @@ NMMatchSpecMatchType nm_match_spec_config (const GSList *specs,
 GSList *nm_match_spec_split (const char *value);
 char *nm_match_spec_join (GSList *specs);
 
+gboolean nm_wildcard_match_check (const char *str,
+                                  const char *const *patterns,
+                                  guint num_patterns);
+
 /*****************************************************************************/
 
 const char *nm_utils_get_ip_config_method (NMConnection *connection,
@@ -260,26 +264,6 @@ gboolean nm_utils_sysctl_ip_conf_is_path (int addr_family, const char *path, con
 
 gboolean nm_utils_is_specific_hostname (const char *name);
 
-int nm_utils_fd_get_contents (int fd,
-                              gboolean close_fd,
-                              gsize max_length,
-                              char **contents,
-                              gsize *length,
-                              GError **error);
-
-int nm_utils_file_get_contents (int dirfd,
-                                const char *filename,
-                                gsize max_length,
-                                char **contents,
-                                gsize *length,
-                                GError **error);
-
-gboolean nm_utils_file_set_contents (const gchar *filename,
-                                     const gchar *contents,
-                                     gssize length,
-                                     mode_t mode,
-                                     GError **error);
-
 char *nm_utils_machine_id_read (void);
 gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid);
 
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index 7f1121f6..0a421aa9 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -1084,7 +1084,7 @@ _obj_unregister (NMDBusManager *self,
 	                               NULL);
 }
 
-NMDBusObject *
+gpointer
 nm_dbus_manager_lookup_object (NMDBusManager *self, const char *path)
 {
 	NMDBusManagerPrivate *priv;
diff --git a/src/nm-dbus-manager.h b/src/nm-dbus-manager.h
index a0741316..04c42bb0 100644
--- a/src/nm-dbus-manager.h
+++ b/src/nm-dbus-manager.h
@@ -61,7 +61,7 @@ gboolean nm_dbus_manager_is_stopping (NMDBusManager *self);
 
 GDBusConnection *nm_dbus_manager_get_connection (NMDBusManager *self);
 
-NMDBusObject *nm_dbus_manager_lookup_object (NMDBusManager *self, const char *path);
+gpointer nm_dbus_manager_lookup_object (NMDBusManager *self, const char *path);
 
 void _nm_dbus_manager_obj_export (NMDBusObject *obj);
 void _nm_dbus_manager_obj_unexport (NMDBusObject *obj);
diff --git a/src/nm-dcb.c b/src/nm-dcb.c
index 140e7c36..5a46fa00 100644
--- a/src/nm-dcb.c
+++ b/src/nm-dcb.c
@@ -104,7 +104,7 @@ G_STMT_START { \
 
 #define SET_APP(f, s, tag) \
 G_STMT_START { \
-	gint prio = nm_setting_dcb_get_app_##tag##_priority (s); \
+	int prio = nm_setting_dcb_get_app_##tag##_priority (s); \
  \
 	SET_FLAGS (f, "app:" #tag); \
 	if ((f & NM_SETTING_DCB_FLAG_ENABLE) && (prio >= 0)) { \
diff --git a/src/nm-hostname-manager.c b/src/nm-hostname-manager.c
index f569ab86..88ff1604 100644
--- a/src/nm-hostname-manager.c
+++ b/src/nm-hostname-manager.c
@@ -48,8 +48,7 @@
 #define HOSTNAME_FILE_UCASE_HOSTNAME "/etc/HOSTNAME"
 #define HOSTNAME_FILE_GENTOO         "/etc/conf.d/hostname"
 
-#define IFCFG_DIR                    SYSCONFDIR "/sysconfig/network"
-#define CONF_DHCP                    IFCFG_DIR "/dhcp"
+#define CONF_DHCP                    SYSCONFDIR "/sysconfig/network/dhcp"
 
 #if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) + defined(HOSTNAME_PERSIST_GENTOO)) > 1
 #error "Can only define one of HOSTNAME_PERSIST_*"
@@ -103,7 +102,7 @@ NM_DEFINE_SINGLETON_GETTER (NMHostnameManager, nm_hostname_manager_get, NM_TYPE_
 /*****************************************************************************/
 
 #if defined(HOSTNAME_PERSIST_GENTOO)
-static gchar *
+static char *
 read_hostname_gentoo (const char *path)
 {
 	gs_free char *contents = NULL;
@@ -129,7 +128,7 @@ read_hostname_gentoo (const char *path)
 #endif
 
 #if defined(HOSTNAME_PERSIST_SLACKWARE)
-static gchar *
+static char *
 read_hostname_slackware (const char *path)
 {
 	gs_free char *contents = NULL;
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 6ae81b57..573c479a 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -278,10 +278,12 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMIP4Config,
 	PROP_ROUTE_DATA,
 	PROP_ROUTES,
 	PROP_GATEWAY,
+	PROP_NAMESERVER_DATA,
 	PROP_NAMESERVERS,
 	PROP_DOMAINS,
 	PROP_SEARCHES,
 	PROP_DNS_OPTIONS,
+	PROP_WINS_SERVER_DATA,
 	PROP_WINS_SERVERS,
 	PROP_DNS_PRIORITY,
 );
@@ -291,8 +293,9 @@ typedef struct {
 	guint32 mtu;
 	int ifindex;
 	NMIPConfigSource mtu_source;
-	gint dns_priority;
+	int dns_priority;
 	NMSettingConnectionMdns mdns;
+	NMSettingConnectionLlmnr llmnr;
 	GArray *nameservers;
 	GPtrArray *domains;
 	GPtrArray *searches;
@@ -509,8 +512,8 @@ _notify_addresses (NMIP4Config *self)
 
 	nm_clear_g_variant (&priv->address_data_variant);
 	nm_clear_g_variant (&priv->addresses_variant);
-	_notify (self, PROP_ADDRESS_DATA);
-	_notify (self, PROP_ADDRESSES);
+	nm_gobject_notify_together (self, PROP_ADDRESS_DATA,
+	                                  PROP_ADDRESSES);
 }
 
 static void
@@ -521,13 +524,13 @@ _notify_routes (NMIP4Config *self)
 	nm_assert (priv->best_default_route == _nm_ip4_config_best_default_route_find (self));
 	nm_clear_g_variant (&priv->route_data_variant);
 	nm_clear_g_variant (&priv->routes_variant);
-	_notify (self, PROP_ROUTE_DATA);
-	_notify (self, PROP_ROUTES);
+	nm_gobject_notify_together (self, PROP_ROUTE_DATA,
+	                                  PROP_ROUTES);
 }
 
 /*****************************************************************************/
 
-static gint
+static int
 _addresses_sort_cmp_get_prio (in_addr_t addr)
 {
 	if (nm_utils_ip4_address_is_link_local (addr))
@@ -538,7 +541,7 @@ _addresses_sort_cmp_get_prio (in_addr_t addr)
 static int
 _addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
 {
-	gint p1, p2;
+	int p1, p2;
 	const NMPlatformIP4Address *a1 = NMP_OBJECT_CAST_IP4_ADDRESS (*((const NMPObject **) a));
 	const NMPlatformIP4Address *a2 = NMP_OBJECT_CAST_IP4_ADDRESS (*((const NMPObject **) b));
 	guint32 n1, n2;
@@ -646,6 +649,37 @@ nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 }
 
 void
+nm_ip4_config_update_routes_metric (NMIP4Config *self, gint64 metric)
+{
+	gs_free NMPlatformIP4Route *routes = NULL;
+	gboolean need_update = FALSE;
+	const NMPlatformIP4Route *r;
+	NMDedupMultiIter iter;
+	guint num = 0, i = 0;
+
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
+		if (r->metric != metric)
+			need_update = TRUE;
+		num++;
+	}
+	if (!need_update)
+		return;
+
+	routes = g_new (NMPlatformIP4Route, num);
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
+		routes[i] = *r;
+		routes[i].metric = metric;
+		i++;
+	}
+
+	g_object_freeze_notify (G_OBJECT (self));
+	nm_ip4_config_reset_routes (self);
+	for (i = 0; i < num; i++)
+		nm_ip4_config_add_route (self, &routes[i], NULL);
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+void
 nm_ip4_config_add_dependent_routes (NMIP4Config *self,
                                     guint32 route_table,
                                     guint32 route_metric,
@@ -883,6 +917,7 @@ void
 nm_ip4_config_merge_setting (NMIP4Config *self,
                              NMSettingIPConfig *setting,
                              NMSettingConnectionMdns mdns,
+                             NMSettingConnectionLlmnr llmnr,
                              guint32 route_table,
                              guint32 route_metric)
 {
@@ -1000,6 +1035,7 @@ nm_ip4_config_merge_setting (NMIP4Config *self,
 		nm_ip4_config_set_dns_priority (self, priority);
 
 	nm_ip4_config_mdns_set (self, mdns);
+	nm_ip4_config_llmnr_set (self, llmnr);
 
 	g_object_thaw_notify (G_OBJECT (self));
 }
@@ -1220,6 +1256,10 @@ nm_ip4_config_merge (NMIP4Config *dst,
 	nm_ip4_config_mdns_set (dst,
 	                        NM_MAX (nm_ip4_config_mdns_get (src),
 	                                nm_ip4_config_mdns_get (dst)));
+	/* LLMNR */
+	nm_ip4_config_llmnr_set (dst,
+	                         NM_MAX (nm_ip4_config_llmnr_get (src),
+	                                 nm_ip4_config_llmnr_get (dst)));
 
 	g_object_thaw_notify (G_OBJECT (dst));
 }
@@ -1336,7 +1376,7 @@ nm_ip4_config_subtract (NMIP4Config *dst,
 {
 	NMIP4ConfigPrivate *dst_priv;
 	guint i;
-	gint idx;
+	int idx;
 	const NMPlatformIP4Address *a;
 	const NMPlatformIP4Route *r;
 	NMDedupMultiIter ipconf_iter;
@@ -1461,6 +1501,10 @@ nm_ip4_config_subtract (NMIP4Config *dst,
 	if (nm_ip4_config_mdns_get (src) == nm_ip4_config_mdns_get (dst))
 		nm_ip4_config_mdns_set (dst, NM_SETTING_CONNECTION_MDNS_DEFAULT);
 
+	/* LLMNR */
+	if (nm_ip4_config_llmnr_get (src) == nm_ip4_config_llmnr_get (dst))
+		nm_ip4_config_llmnr_set (dst, NM_SETTING_CONNECTION_LLMNR_DEFAULT);
+
 	g_object_thaw_notify (G_OBJECT (dst));
 }
 
@@ -1562,6 +1606,7 @@ _nm_ip4_config_intersect_helper (NMIP4Config *dst,
 	/* ignore NIS */
 	/* ignore WINS */
 	/* ignore mdns */
+	/* ignore LLMNR */
 
 	if (update_dst)
 		g_object_thaw_notify (G_OBJECT (dst));
@@ -1840,7 +1885,15 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_relevant_changes = TRUE;
 	}
 
-	dst_priv->mdns = src_priv->mdns;
+	if (src_priv->mdns != dst_priv->mdns) {
+		dst_priv->mdns = src_priv->mdns;
+		has_relevant_changes = TRUE;
+	}
+
+	if (src_priv->llmnr != dst_priv->llmnr) {
+		dst_priv->llmnr = src_priv->llmnr;
+		has_relevant_changes = TRUE;
+	}
 
 	/* DNS priority */
 	if (src_priv->dns_priority != dst_priv->dns_priority) {
@@ -2276,7 +2329,8 @@ nm_ip4_config_reset_nameservers (NMIP4Config *self)
 
 	if (priv->nameservers->len != 0) {
 		g_array_set_size (priv->nameservers, 0);
-		_notify (self, PROP_NAMESERVERS);
+		nm_gobject_notify_together (self, PROP_NAMESERVER_DATA,
+		                                  PROP_NAMESERVERS);
 	}
 }
 
@@ -2293,7 +2347,8 @@ nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 new)
 			return;
 
 	g_array_append_val (priv->nameservers, new);
-	_notify (self, PROP_NAMESERVERS);
+	nm_gobject_notify_together (self, PROP_NAMESERVER_DATA,
+	                                  PROP_NAMESERVERS);
 }
 
 void
@@ -2304,7 +2359,8 @@ nm_ip4_config_del_nameserver (NMIP4Config *self, guint i)
 	g_return_if_fail (i < priv->nameservers->len);
 
 	g_array_remove_index (priv->nameservers, i);
-	_notify (self, PROP_NAMESERVERS);
+	nm_gobject_notify_together (self, PROP_NAMESERVER_DATA,
+	                                  PROP_NAMESERVERS);
 }
 
 guint
@@ -2526,10 +2582,23 @@ nm_ip4_config_mdns_set (NMIP4Config *self,
 	NM_IP4_CONFIG_GET_PRIVATE (self)->mdns = mdns;
 }
 
+NMSettingConnectionLlmnr
+nm_ip4_config_llmnr_get (const NMIP4Config *self)
+{
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->llmnr;
+}
+
+void
+nm_ip4_config_llmnr_set (NMIP4Config *self,
+                         NMSettingConnectionLlmnr llmnr)
+{
+	NM_IP4_CONFIG_GET_PRIVATE (self)->llmnr = llmnr;
+}
+
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_dns_priority (NMIP4Config *self, gint priority)
+nm_ip4_config_set_dns_priority (NMIP4Config *self, int priority)
 {
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
@@ -2539,7 +2608,7 @@ nm_ip4_config_set_dns_priority (NMIP4Config *self, gint priority)
 	}
 }
 
-gint
+int
 nm_ip4_config_get_dns_priority (const NMIP4Config *self)
 {
 	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
@@ -2622,7 +2691,8 @@ nm_ip4_config_reset_wins (NMIP4Config *self)
 
 	if (priv->wins->len != 0) {
 		g_array_set_size (priv->wins, 0);
-		_notify (self, PROP_WINS_SERVERS);
+		nm_gobject_notify_together (self, PROP_WINS_SERVER_DATA,
+		                                  PROP_WINS_SERVERS);
 	}
 }
 
@@ -2639,7 +2709,8 @@ nm_ip4_config_add_wins (NMIP4Config *self, guint32 wins)
 			return;
 
 	g_array_append_val (priv->wins, wins);
-	_notify (self, PROP_WINS_SERVERS);
+	nm_gobject_notify_together (self, PROP_WINS_SERVER_DATA,
+	                                  PROP_WINS_SERVERS);
 }
 
 void
@@ -2650,7 +2721,8 @@ nm_ip4_config_del_wins (NMIP4Config *self, guint i)
 	g_return_if_fail (i < priv->wins->len);
 
 	g_array_remove_index (priv->wins, i);
-	_notify (self, PROP_WINS_SERVERS);
+	nm_gobject_notify_together (self, PROP_WINS_SERVER_DATA,
+	                                  PROP_WINS_SERVERS);
 }
 
 guint
@@ -2812,6 +2884,7 @@ nm_ip4_config_hash (const NMIP4Config *self, GChecksum *sum, gboolean dns_only)
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP4Address *address;
 	const NMPlatformIP4Route *route;
+	int val;
 
 	g_return_if_fail (self);
 	g_return_if_fail (sum);
@@ -2858,6 +2931,25 @@ nm_ip4_config_hash (const NMIP4Config *self, GChecksum *sum, gboolean dns_only)
 		s = nm_ip4_config_get_dns_option (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
+
+	val = nm_ip4_config_mdns_get (self);
+	if (val != NM_SETTING_CONNECTION_MDNS_DEFAULT)
+		g_checksum_update (sum, (const guint8 *) &val, sizeof (val));
+
+	val = nm_ip4_config_llmnr_get (self);
+	if (val != NM_SETTING_CONNECTION_LLMNR_DEFAULT)
+		g_checksum_update (sum, (const guint8 *) &val, sizeof (val));
+
+	/* FIXME(ip-config-checksum): the DNS priority should be considered relevant
+	 * and added into the checksum as well, but this can't be done right now
+	 * because in the DNS manager we rely on the fact that an empty
+	 * configuration (i.e. just created) has a zero checksum. This is needed to
+	 * avoid rewriting resolv.conf when there is no change.
+	 *
+	 * The DNS priority initial value depends on the connection type (VPN or
+	 * not), so it's a bit difficult to add it to checksum maintaining the
+	 * assumption of checksum(empty)=0
+	 */
 }
 
 /**
@@ -2913,6 +3005,7 @@ get_property (GObject *object, guint prop_id,
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP4Route *route;
 	GVariantBuilder builder_data, builder_legacy;
+	guint i;
 
 	switch (prop_id) {
 	case PROP_IFINDEX:
@@ -2931,7 +3024,7 @@ get_property (GObject *object, guint prop_id,
 		head_entry = nm_ip4_config_lookup_addresses (self);
 		if (head_entry) {
 			gs_free const NMPObject **addresses = NULL;
-			guint naddr, i;
+			guint naddr;
 
 			addresses = (const NMPObject **) nm_dedup_multi_objs_to_array_head (head_entry, NULL, NULL, &naddr);
 			nm_assert (addresses && naddr);
@@ -3067,6 +3160,26 @@ out_routes_cached:
 		} else
 			g_value_set_string (value, NULL);
 		break;
+	case PROP_NAMESERVER_DATA:
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
+
+		for (i = 0; i < priv->nameservers->len; i++) {
+			GVariantBuilder nested_builder;
+			char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+
+			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}",
+			                       "address",
+			                       g_variant_new_string (addr_str));
+			g_variant_builder_add (&builder_data, "a{sv}", &nested_builder);
+		}
+
+		g_value_take_variant (value,
+		                      g_variant_builder_end (&builder_data));
+		break;
 	case PROP_NAMESERVERS:
 		g_value_take_variant (value,
 		                      g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
@@ -3086,6 +3199,19 @@ out_routes_cached:
 	case PROP_DNS_PRIORITY:
 		g_value_set_int (value, priv->dns_priority);
 		break;
+	case PROP_WINS_SERVER_DATA:
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("as"));
+		for (i = 0; i < priv->wins->len; i++) {
+			char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+
+			g_variant_builder_add (&builder_data,
+			                       "s",
+			                       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));
+		break;
 	case PROP_WINS_SERVERS:
 		g_value_take_variant (value,
 		                      g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
@@ -3139,6 +3265,7 @@ nm_ip4_config_init (NMIP4Config *self)
 	                                        NMP_OBJECT_TYPE_IP4_ROUTE);
 
 	priv->mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+	priv->llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
 	priv->nameservers = g_array_new (FALSE, FALSE, sizeof (guint32));
 	priv->domains = g_ptr_array_new_with_free_func (g_free);
 	priv->searches = g_ptr_array_new_with_free_func (g_free);
@@ -3193,17 +3320,19 @@ static const NMDBusInterfaceInfoExtended interface_info_ip4_config = {
 			&nm_signal_info_property_changed_legacy,
 		),
 		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Addresses",   "aau",    NM_IP4_CONFIG_ADDRESSES),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AddressData", "aa{sv}", NM_IP4_CONFIG_ADDRESS_DATA),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Gateway",     "s",      NM_IP4_CONFIG_GATEWAY),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Routes",      "aau",    NM_IP4_CONFIG_ROUTES),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RouteData",   "aa{sv}", NM_IP4_CONFIG_ROUTE_DATA),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Nameservers", "au",     NM_IP4_CONFIG_NAMESERVERS),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Domains",     "as",     NM_IP4_CONFIG_DOMAINS),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Searches",    "as",     NM_IP4_CONFIG_SEARCHES),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsOptions",  "as",     NM_IP4_CONFIG_DNS_OPTIONS),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsPriority", "i",      NM_IP4_CONFIG_DNS_PRIORITY),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WinsServers", "au",     NM_IP4_CONFIG_WINS_SERVERS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Addresses",      "aau",    NM_IP4_CONFIG_ADDRESSES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("AddressData",    "aa{sv}", NM_IP4_CONFIG_ADDRESS_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Gateway",        "s",      NM_IP4_CONFIG_GATEWAY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Routes",         "aau",    NM_IP4_CONFIG_ROUTES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("RouteData",      "aa{sv}", NM_IP4_CONFIG_ROUTE_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("NameserverData", "aa{sv}", NM_IP4_CONFIG_NAMESERVER_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Nameservers",    "au",     NM_IP4_CONFIG_NAMESERVERS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Domains",        "as",     NM_IP4_CONFIG_DOMAINS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Searches",       "as",     NM_IP4_CONFIG_SEARCHES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsOptions",     "as",     NM_IP4_CONFIG_DNS_OPTIONS),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("DnsPriority",    "i",      NM_IP4_CONFIG_DNS_PRIORITY),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("WinsServerData", "as",     NM_IP4_CONFIG_WINS_SERVER_DATA),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("WinsServers",    "au",     NM_IP4_CONFIG_WINS_SERVERS),
 		),
 	),
 	.legacy_property_changed = TRUE,
@@ -3262,6 +3391,12 @@ nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 	                         NULL,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_NAMESERVER_DATA] =
+	    g_param_spec_variant (NM_IP4_CONFIG_NAMESERVER_DATA, "", "",
+	                          G_VARIANT_TYPE ("aa{sv}"),
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_NAMESERVERS] =
 	    g_param_spec_variant (NM_IP4_CONFIG_NAMESERVERS, "", "",
 	                          G_VARIANT_TYPE ("au"),
@@ -3288,6 +3423,12 @@ nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 	                       G_MININT32, G_MAXINT32, 0,
 	                       G_PARAM_READABLE |
 	                       G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_WINS_SERVER_DATA] =
+	    g_param_spec_variant (NM_IP4_CONFIG_WINS_SERVER_DATA, "", "",
+	                          G_VARIANT_TYPE ("as"),
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_WINS_SERVERS] =
 	    g_param_spec_variant (NM_IP4_CONFIG_WINS_SERVERS, "", "",
 	                          G_VARIANT_TYPE ("au"),
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 78aca14e..d9ac969c 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -138,16 +138,18 @@ typedef struct _NMIP4ConfigClass NMIP4ConfigClass;
 #define NM_IP4_CONFIG_ADDRESS_DATA "address-data"
 #define NM_IP4_CONFIG_ROUTE_DATA "route-data"
 #define NM_IP4_CONFIG_GATEWAY "gateway"
-#define NM_IP4_CONFIG_NAMESERVERS "nameservers"
+#define NM_IP4_CONFIG_NAMESERVER_DATA "nameserver-data"
 #define NM_IP4_CONFIG_DOMAINS "domains"
 #define NM_IP4_CONFIG_SEARCHES "searches"
 #define NM_IP4_CONFIG_DNS_OPTIONS "dns-options"
 #define NM_IP4_CONFIG_DNS_PRIORITY "dns-priority"
-#define NM_IP4_CONFIG_WINS_SERVERS "wins-servers"
+#define NM_IP4_CONFIG_WINS_SERVER_DATA "wins-server-data"
 
 /* deprecated */
 #define NM_IP4_CONFIG_ADDRESSES "addresses"
 #define NM_IP4_CONFIG_ROUTES "routes"
+#define NM_IP4_CONFIG_NAMESERVERS "nameservers"
+#define NM_IP4_CONFIG_WINS_SERVERS "wins-servers"
 
 GType nm_ip4_config_get_type (void);
 
@@ -173,6 +175,7 @@ gboolean nm_ip4_config_commit (const NMIP4Config *self,
 void nm_ip4_config_merge_setting (NMIP4Config *self,
                                   NMSettingIPConfig *setting,
                                   NMSettingConnectionMdns mdns,
+                                  NMSettingConnectionLlmnr llmnr,
                                   guint32 route_table,
                                   guint32 route_metric);
 NMSetting *nm_ip4_config_create_setting (const NMIP4Config *self);
@@ -201,6 +204,9 @@ in_addr_t nmtst_ip4_config_get_gateway (NMIP4Config *config);
 NMSettingConnectionMdns nm_ip4_config_mdns_get (const NMIP4Config *self);
 void                    nm_ip4_config_mdns_set (NMIP4Config *self,
                                                 NMSettingConnectionMdns mdns);
+NMSettingConnectionLlmnr nm_ip4_config_llmnr_get (const NMIP4Config *self);
+void                     nm_ip4_config_llmnr_set (NMIP4Config *self,
+                                                  NMSettingConnectionLlmnr llmnr);
 
 const NMDedupMultiHeadEntry *nm_ip4_config_lookup_addresses (const NMIP4Config *self);
 void nm_ip4_config_reset_addresses (NMIP4Config *self);
@@ -223,6 +229,7 @@ const NMPlatformIP4Route *_nmtst_ip4_config_get_route (const NMIP4Config *self,
 const NMPlatformIP4Route *nm_ip4_config_get_direct_route_for_host (const NMIP4Config *self,
                                                                    in_addr_t host,
                                                                    guint32 route_table);
+void nm_ip4_config_update_routes_metric (NMIP4Config *self, gint64 metric);
 
 void nm_ip4_config_reset_nameservers (NMIP4Config *self);
 void nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 nameserver);
@@ -256,8 +263,8 @@ void nm_ip4_config_del_dns_option (NMIP4Config *self, guint i);
 guint nm_ip4_config_get_num_dns_options (const NMIP4Config *self);
 const char * nm_ip4_config_get_dns_option (const NMIP4Config *self, guint i);
 
-void nm_ip4_config_set_dns_priority (NMIP4Config *self, gint priority);
-gint nm_ip4_config_get_dns_priority (const NMIP4Config *self);
+void nm_ip4_config_set_dns_priority (NMIP4Config *self, int priority);
+int nm_ip4_config_get_dns_priority (const NMIP4Config *self);
 
 void nm_ip4_config_reset_nis_servers (NMIP4Config *self);
 void nm_ip4_config_add_nis_server (NMIP4Config *self, guint32 nis);
@@ -430,7 +437,7 @@ nm_ip_config_get_dns_priority (const NMIPConfig *self)
 }
 
 static inline void
-nm_ip_config_set_dns_priority (NMIPConfig *self, gint priority)
+nm_ip_config_set_dns_priority (NMIPConfig *self, int priority)
 {
 	_NM_IP_CONFIG_DISPATCH_VOID (self, nm_ip4_config_set_dns_priority, nm_ip6_config_set_dns_priority, priority);
 }
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 9807d388..378b76d6 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -208,8 +208,8 @@ _notify_addresses (NMIP6Config *self)
 
 	nm_clear_g_variant (&priv->address_data_variant);
 	nm_clear_g_variant (&priv->addresses_variant);
-	_notify (self, PROP_ADDRESS_DATA);
-	_notify (self, PROP_ADDRESSES);
+	nm_gobject_notify_together (self, PROP_ADDRESS_DATA,
+	                                  PROP_ADDRESSES);
 }
 
 static void
@@ -220,13 +220,13 @@ _notify_routes (NMIP6Config *self)
 	nm_assert (priv->best_default_route == _nm_ip6_config_best_default_route_find (self));
 	nm_clear_g_variant (&priv->route_data_variant);
 	nm_clear_g_variant (&priv->routes_variant);
-	_notify (self, PROP_ROUTE_DATA);
-	_notify (self, PROP_ROUTES);
+	nm_gobject_notify_together (self, PROP_ROUTE_DATA,
+	                                  PROP_ROUTES);
 }
 
 /*****************************************************************************/
 
-static gint
+static int
 _addresses_sort_cmp_get_prio (const struct in6_addr *addr)
 {
 	if (IN6_IS_ADDR_V4MAPPED (addr))
@@ -249,7 +249,7 @@ _addresses_sort_cmp (const NMPlatformIP6Address *a1,
                      const NMPlatformIP6Address *a2,
                      gboolean prefer_temp)
 {
-	gint p1, p2, c;
+	int p1, p2, c;
 	gboolean perm1, perm2, tent1, tent2;
 	gboolean ipv6_privacy1, ipv6_privacy2;
 
@@ -428,6 +428,37 @@ nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 }
 
 void
+nm_ip6_config_update_routes_metric (NMIP6Config *self, gint64 metric)
+{
+	gs_free NMPlatformIP6Route *routes = NULL;
+	gboolean need_update = FALSE;
+	const NMPlatformIP6Route *r;
+	NMDedupMultiIter iter;
+	guint num = 0, i = 0;
+
+	nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) {
+		if (r->metric != metric)
+			need_update = TRUE;
+		num++;
+	}
+	if (!need_update)
+		return;
+
+	routes = g_new (NMPlatformIP6Route, num);
+	nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) {
+		routes[i] = *r;
+		routes[i].metric = metric;
+		i++;
+	}
+
+	g_object_freeze_notify (G_OBJECT (self));
+	nm_ip6_config_reset_routes (self);
+	for (i = 0; i < num; i++)
+		nm_ip6_config_add_route (self, &routes[i], NULL);
+	g_object_thaw_notify (G_OBJECT (self));
+}
+
+void
 nm_ip6_config_add_dependent_routes (NMIP6Config *self,
                                     guint32 route_table,
                                     guint32 route_metric)
@@ -948,7 +979,7 @@ nm_ip6_config_subtract (NMIP6Config *dst,
 {
 	NMIP6ConfigPrivate *dst_priv;
 	guint i;
-	gint idx;
+	int idx;
 	const NMPlatformIP6Address *a;
 	const NMPlatformIP6Route *r;
 	NMDedupMultiIter ipconf_iter;
@@ -2258,7 +2289,7 @@ nm_ip6_config_get_dns_option (const NMIP6Config *self, guint i)
 /*****************************************************************************/
 
 void
-nm_ip6_config_set_dns_priority (NMIP6Config *self, gint priority)
+nm_ip6_config_set_dns_priority (NMIP6Config *self, int priority)
 {
 	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
@@ -2268,7 +2299,7 @@ nm_ip6_config_set_dns_priority (NMIP6Config *self, gint priority)
 	}
 }
 
-gint
+int
 nm_ip6_config_get_dns_priority (const NMIP6Config *self)
 {
 	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 9762ef48..425b2049 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -194,8 +194,8 @@ void nm_ip6_config_del_dns_option (NMIP6Config *self, guint i);
 guint nm_ip6_config_get_num_dns_options (const NMIP6Config *self);
 const char * nm_ip6_config_get_dns_option (const NMIP6Config *self, guint i);
 
-void nm_ip6_config_set_dns_priority (NMIP6Config *self, gint priority);
-gint nm_ip6_config_get_dns_priority (const NMIP6Config *self);
+void nm_ip6_config_set_dns_priority (NMIP6Config *self, int priority);
+int nm_ip6_config_get_dns_priority (const NMIP6Config *self);
 
 const NMPObject *nm_ip6_config_nmpobj_lookup (const NMIP6Config *self,
                                               const NMPObject *needle);
@@ -224,4 +224,6 @@ void nm_ip6_config_reset_routes_ndisc (NMIP6Config *self,
                                        guint32 route_metric,
                                        gboolean kernel_support_rta_pref);
 
+void nm_ip6_config_update_routes_metric (NMIP6Config *self, gint64 metric);
+
 #endif /* __NETWORKMANAGER_IP6_CONFIG_H__ */
diff --git a/src/nm-logging.c b/src/nm-logging.c
index c5553791..9e7d3892 100644
--- a/src/nm-logging.c
+++ b/src/nm-logging.c
@@ -54,9 +54,9 @@
 void (*_nm_logging_clear_platform_logging_cache) (void);
 
 static void
-nm_log_handler (const gchar *log_domain,
+nm_log_handler (const char *log_domain,
                 GLogLevelFlags level,
-                const gchar *message,
+                const char *message,
                 gpointer ignored);
 
 typedef struct {
@@ -745,9 +745,9 @@ _nm_log_impl (const char *file,
 /*****************************************************************************/
 
 static void
-nm_log_handler (const gchar *log_domain,
+nm_log_handler (const char *log_domain,
                 GLogLevelFlags level,
-                const gchar *message,
+                const char *message,
                 gpointer ignored)
 {
 	int syslog_priority;
diff --git a/src/nm-logging.h b/src/nm-logging.h
index 70c14a5e..0737bbd6 100644
--- a/src/nm-logging.h
+++ b/src/nm-logging.h
@@ -271,7 +271,7 @@ gboolean nm_logging_syslog_enabled (void);
 
 /* _LOGT() and _LOGt() both log with level TRACE, but the latter is disabled by default,
  * unless building with --with-more-logging. */
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 #define _LOGt_ENABLED(...)    _NMLOG_ENABLED (LOGL_TRACE, ##__VA_ARGS__)
 #define _LOGt(...)            _NMLOG (LOGL_TRACE, __VA_ARGS__)
 #define _LOGt_err(errsv, ...) _NMLOG_err (errsv, LOGL_TRACE, __VA_ARGS__)
@@ -311,7 +311,7 @@ gboolean nm_logging_syslog_enabled (void);
 #define _LOG2W_err(errsv, ...) _NMLOG2_err (errsv, LOGL_WARN , __VA_ARGS__)
 #define _LOG2E_err(errsv, ...) _NMLOG2_err (errsv, LOGL_ERR  , __VA_ARGS__)
 
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 #define _LOG2t_ENABLED(...)    _NMLOG2_ENABLED (LOGL_TRACE, ##__VA_ARGS__)
 #define _LOG2t(...)            _NMLOG2 (LOGL_TRACE, __VA_ARGS__)
 #define _LOG2t_err(errsv, ...) _NMLOG2_err (errsv, LOGL_TRACE, __VA_ARGS__)
@@ -342,7 +342,7 @@ gboolean nm_logging_syslog_enabled (void);
 #define _LOG3W_err(errsv, ...) _NMLOG3_err (errsv, LOGL_WARN , __VA_ARGS__)
 #define _LOG3E_err(errsv, ...) _NMLOG3_err (errsv, LOGL_ERR  , __VA_ARGS__)
 
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 #define _LOG3t_ENABLED(...)    _NMLOG3_ENABLED (LOGL_TRACE, ##__VA_ARGS__)
 #define _LOG3t(...)            _NMLOG3 (LOGL_TRACE, __VA_ARGS__)
 #define _LOG3t_err(errsv, ...) _NMLOG3_err (errsv, LOGL_TRACE, __VA_ARGS__)
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 289dcf83..f8be6d37 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -310,7 +310,8 @@ static void _emit_device_added_removed (NMManager *self,
 
 static NMActiveConnection *_new_active_connection (NMManager *self,
                                                    gboolean is_vpn,
-                                                   NMConnection *connection,
+                                                   NMSettingsConnection *sett_conn,
+                                                   NMConnection *incompl_conn,
                                                    NMConnection *applied,
                                                    const char *specific_object,
                                                    NMDevice *device,
@@ -319,7 +320,7 @@ static NMActiveConnection *_new_active_connection (NMManager *self,
                                                    NMActivationReason activation_reason,
                                                    GError **error);
 
-static void policy_activating_device_changed (GObject *object, GParamSpec *pspec, gpointer user_data);
+static void policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data);
 
 static gboolean find_master (NMManager *self,
                              NMConnection *connection,
@@ -331,7 +332,8 @@ static gboolean find_master (NMManager *self,
 
 static void nm_manager_update_state (NMManager *manager);
 
-static void connection_changed (NMManager *self, NMConnection *connection);
+static void connection_changed (NMManager *self,
+                                NMSettingsConnection *sett_conn);
 static void device_sleep_cb (NMDevice *device,
                              GParamSpec *pspec,
                              NMManager *self);
@@ -353,7 +355,7 @@ static void active_connection_parent_active (NMActiveConnection *active,
                                              NMManager *self);
 
 static NMActiveConnection *active_connection_find (NMManager *self,
-                                                   NMSettingsConnection *settings_connection,
+                                                   NMSettingsConnection *sett_conn,
                                                    const char *uuid,
                                                    NMActiveConnectionState max_state,
                                                    GPtrArray **out_all_matching);
@@ -947,7 +949,7 @@ nm_manager_get_active_connections (NMManager *manager)
 
 static NMActiveConnection *
 active_connection_find (NMManager *self,
-                        NMSettingsConnection *settings_connection,
+                        NMSettingsConnection *sett_conn,
                         const char *uuid,
                         NMActiveConnectionState max_state /* candidates in state @max_state will be found */,
                         GPtrArray **out_all_matching)
@@ -957,16 +959,18 @@ active_connection_find (NMManager *self,
 	NMActiveConnection *best_ac = NULL;
 	GPtrArray *all = NULL;
 
-	nm_assert (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection));
+	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
 	nm_assert (!out_all_matching || !*out_all_matching);
 
 	c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
-		NMSettingsConnection *con;
+		NMSettingsConnection *ac_conn;
 
-		con = nm_active_connection_get_settings_connection (ac);
-		if (settings_connection && con != settings_connection)
+		ac_conn = nm_active_connection_get_settings_connection (ac);
+		if (   sett_conn
+		    && sett_conn != ac_conn)
 			continue;
-		if (uuid && !nm_streq0 (uuid, nm_connection_get_uuid (NM_CONNECTION (con))))
+		if (   uuid
+		    && !nm_streq0 (uuid, nm_settings_connection_get_uuid (ac_conn)))
 			continue;
 		if (nm_active_connection_get_state (ac) > max_state)
 			continue;
@@ -1005,47 +1009,73 @@ active_connection_find (NMManager *self,
 
 static NMActiveConnection *
 active_connection_find_by_connection (NMManager *self,
+                                      NMSettingsConnection *sett_conn,
                                       NMConnection *connection,
                                       NMActiveConnectionState max_state,
                                       GPtrArray **out_all_matching)
 {
-	gboolean is_settings_connection;
-
 	nm_assert (NM_IS_MANAGER (self));
-	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (!connection || NM_IS_CONNECTION (connection));
+	nm_assert (sett_conn || connection);
+	nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));
 
-	is_settings_connection = NM_IS_SETTINGS_CONNECTION (connection);
 	/* Depending on whether connection is a settings connection,
 	 * either lookup by object-identity of @connection, or compare the UUID */
 	return active_connection_find (self,
-	                               is_settings_connection ? NM_SETTINGS_CONNECTION (connection) : NULL,
-	                               is_settings_connection ? NULL : nm_connection_get_uuid (connection),
+	                               sett_conn,
+	                               sett_conn ? NULL : nm_connection_get_uuid (connection),
 	                               max_state,
 	                               out_all_matching);
 }
 
+typedef struct {
+	NMManager *self;
+	gboolean for_auto_activation;
+} GetActivatableConnectionsFilterData;
+
 static gboolean
 _get_activatable_connections_filter (NMSettings *settings,
-                                     NMSettingsConnection *connection,
+                                     NMSettingsConnection *sett_conn,
                                      gpointer user_data)
 {
-	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
+	const GetActivatableConnectionsFilterData *d = user_data;
+	NMConnectionMultiConnect multi_connect;
+
+	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
 	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 		return FALSE;
 
+	multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn));
+	if (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE
+	    || (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE
+	        && !d->for_auto_activation))
+		return TRUE;
+
 	/* the connection is activatable, if it has no active-connections that are in state
 	 * activated, activating, or waiting to be activated. */
-	return !active_connection_find (user_data, connection, NULL, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NULL);
+	return !active_connection_find (d->self,
+	                                sett_conn,
+	                                NULL,
+	                                NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+	                                NULL);
 }
 
 NMSettingsConnection **
-nm_manager_get_activatable_connections (NMManager *manager, guint *out_len, gboolean sort)
+nm_manager_get_activatable_connections (NMManager *manager,
+                                        gboolean for_auto_activation,
+                                        gboolean sort,
+                                        guint *out_len)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
+	const GetActivatableConnectionsFilterData d = {
+		.self = manager,
+		.for_auto_activation = for_auto_activation,
+	};
 
 	return nm_settings_get_connections_clone (priv->settings, out_len,
 	                                          _get_activatable_connections_filter,
-	                                          manager,
+	                                          (gpointer) &d,
 	                                          sort ? nm_settings_connection_cmp_autoconnect_priority_p_with_data : NULL,
 	                                          NULL);
 }
@@ -1056,8 +1086,8 @@ active_connection_get_by_path (NMManager *self, const char *path)
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMActiveConnection *ac;
 
-	ac = (NMActiveConnection *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
-	                                                           path);
+	ac = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                    path);
 	if (   !ac
 	    || !NM_IS_ACTIVE_CONNECTION (ac)
 	    || c_list_is_empty (&ac->active_connections_lst))
@@ -1193,8 +1223,8 @@ nm_manager_get_device_by_path (NMManager *self, const char *path)
 
 	g_return_val_if_fail (path, NULL);
 
-	device = (NMDevice *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
-	                                                     path);
+	device = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                        path);
 	if (   !device
 	    || !NM_IS_DEVICE (device)
 	    || c_list_is_empty (&device->devices_lst))
@@ -1244,7 +1274,7 @@ find_device_by_permanent_hw_addr (NMManager *self, const char *hwaddr)
 }
 
 static NMDevice *
-find_device_by_ip_iface (NMManager *self, const gchar *iface)
+find_device_by_ip_iface (NMManager *self, const char *iface)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMDevice *device;
@@ -1290,7 +1320,7 @@ find_device_by_iface (NMManager *self,
 
 		if (strcmp (nm_device_get_iface (candidate), iface))
 			continue;
-		if (connection && !nm_device_check_connection_compatible (candidate, connection))
+		if (connection && !nm_device_check_connection_compatible (candidate, connection, NULL))
 			continue;
 		if (slave) {
 			if (!nm_device_is_master (candidate))
@@ -1459,20 +1489,23 @@ manager_device_state_changed (NMDevice *device,
 	    && new_state > NM_DEVICE_STATE_UNMANAGED)
 		retry_connections_for_parent_device (self, device);
 
-	switch (new_state) {
-	case NM_DEVICE_STATE_UNMANAGED:
-	case NM_DEVICE_STATE_UNAVAILABLE:
-	case NM_DEVICE_STATE_DISCONNECTED:
-	case NM_DEVICE_STATE_PREPARE:
-	case NM_DEVICE_STATE_FAILED:
+	if (NM_IN_SET (new_state,
+	               NM_DEVICE_STATE_UNMANAGED,
+	               NM_DEVICE_STATE_UNAVAILABLE,
+	               NM_DEVICE_STATE_DISCONNECTED,
+	               NM_DEVICE_STATE_PREPARE,
+	               NM_DEVICE_STATE_FAILED))
 		_notify (self, PROP_ACTIVE_CONNECTIONS);
-		break;
-	default:
-		break;
-	}
 
-	if (   new_state == NM_DEVICE_STATE_UNAVAILABLE
-	    || new_state == NM_DEVICE_STATE_DISCONNECTED)
+	if (NM_IN_SET (new_state,
+	               NM_DEVICE_STATE_UNMANAGED,
+	               NM_DEVICE_STATE_DISCONNECTED,
+	               NM_DEVICE_STATE_ACTIVATED))
+		nm_manager_write_device_state (self, device);
+
+	if (NM_IN_SET (new_state,
+	               NM_DEVICE_STATE_UNAVAILABLE,
+	               NM_DEVICE_STATE_DISCONNECTED))
 		nm_settings_device_added (priv->settings, device);
 }
 
@@ -1707,7 +1740,9 @@ find_parent_device_for_connection (NMManager *self, NMConnection *connection, NM
 			return candidate;
 
 		if (   !first_compatible
-		    && nm_device_check_connection_compatible (candidate, NM_CONNECTION (parent_connection)))
+		    && nm_device_check_connection_compatible (candidate,
+		                                              nm_settings_connection_get_connection (parent_connection),
+		                                              NULL))
 			first_compatible = candidate;
 	}
 
@@ -1801,13 +1836,13 @@ const char *
 nm_manager_iface_for_uuid (NMManager *self, const char *uuid)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMSettingsConnection *connection;
+	NMSettingsConnection *sett_conn;
 
-	connection = nm_settings_get_connection_by_uuid (priv->settings, uuid);
-	if (!connection)
+	sett_conn = nm_settings_get_connection_by_uuid (priv->settings, uuid);
+	if (!sett_conn)
 		return NULL;
 
-	return nm_connection_get_interface_name (NM_CONNECTION (connection));
+	return nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn));
 }
 
 NMDevice *
@@ -1877,7 +1912,7 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 
 	/* See if there's a device that is already compatible with this connection */
 	c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) {
-		if (nm_device_check_connection_compatible (dev_candidate, connection)) {
+		if (nm_device_check_connection_compatible (dev_candidate, connection, NULL)) {
 			if (nm_device_is_real (dev_candidate)) {
 				_LOG3D (LOGD_DEVICE, connection, "already created virtual interface name %s",
 				       iface);
@@ -1930,10 +1965,10 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 	                                                 NULL, NULL,
 	                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
 	for (i = 0; connections[i]; i++) {
-		NMConnection *candidate = NM_CONNECTION (connections[i]);
+		NMConnection *candidate = nm_settings_connection_get_connection (connections[i]);
 		NMSettingConnection *s_con;
 
-		if (!nm_device_check_connection_compatible (device, candidate))
+		if (!nm_device_check_connection_compatible (device, candidate, NULL))
 			continue;
 
 		s_con = nm_connection_get_setting_connection (candidate);
@@ -1976,18 +2011,19 @@ retry_connections_for_parent_device (NMManager *self, NMDevice *device)
 	                                                 NULL, NULL,
 	                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
 	for (i = 0; connections[i]; i++) {
-		NMConnection *candidate = NM_CONNECTION (connections[i]);
+		NMSettingsConnection *sett_conn = connections[i];
+		NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
 		gs_free_error GError *error = NULL;
 		gs_free char *ifname = NULL;
 		NMDevice *parent;
 
-		parent = find_parent_device_for_connection (self, candidate, NULL);
+		parent = find_parent_device_for_connection (self, connection, NULL);
 		if (parent == device) {
 			/* Only try to activate devices that don't already exist */
-			ifname = nm_manager_get_connection_iface (self, candidate, &parent, &error);
+			ifname = nm_manager_get_connection_iface (self, connection, &parent, &error);
 			if (ifname) {
 				if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname))
-					connection_changed (self, candidate);
+					connection_changed (self, sett_conn);
 			}
 		}
 	}
@@ -1995,9 +2031,10 @@ retry_connections_for_parent_device (NMManager *self, NMDevice *device)
 
 static void
 connection_changed (NMManager *self,
-                    NMConnection *connection)
+                    NMSettingsConnection *sett_conn)
 {
 	NMDevice *device;
+	NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
 
 	if (!nm_connection_is_virtual (connection))
 		return;
@@ -2014,20 +2051,20 @@ connection_changed (NMManager *self,
 
 static void
 connection_added_cb (NMSettings *settings,
-                     NMConnection *connection,
+                     NMSettingsConnection *sett_conn,
                      NMManager *self)
 {
-	connection_changed (self, connection);
+	connection_changed (self, sett_conn);
 }
 
 static void
 connection_updated_cb (NMSettings *settings,
-                       NMConnection *connection,
+                       NMSettingsConnection *sett_conn,
                        gboolean by_user,
                        NMManager *self)
 {
 	if (by_user)
-		connection_changed (self, connection);
+		connection_changed (self, sett_conn);
 }
 
 /*****************************************************************************/
@@ -2370,6 +2407,20 @@ done:
 	g_clear_error (&error);
 }
 
+static gboolean
+new_activation_allowed_for_connection (NMManager *self,
+                                       NMSettingsConnection *connection)
+{
+	if (NM_IN_SET (_nm_connection_get_multi_connect (nm_settings_connection_get_connection (connection)),
+	               NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE,
+	               NM_CONNECTION_MULTI_CONNECT_MULTIPLE))
+		return TRUE;
+
+	return !active_connection_find (self, connection, NULL,
+	                                NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+	                                NULL);
+}
+
 /**
  * get_existing_connection:
  * @manager: #NMManager instance
@@ -2386,12 +2437,12 @@ get_existing_connection (NMManager *self,
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gs_unref_object NMConnection *connection = NULL;
-	NMSettingsConnection *added = NULL;
+	NMSettingsConnection *added;
 	GError *error = NULL;
 	gs_free_error GError *gen_error = NULL;
 	NMDevice *master = NULL;
 	int ifindex = nm_device_get_ifindex (device);
-	NMSettingsConnection *matched;
+	NMSettingsConnection *matched = NULL;
 	NMSettingsConnection *connection_checked = NULL;
 	gboolean assume_state_guess_assume = FALSE;
 	const char *assume_state_connection_uuid = NULL;
@@ -2457,28 +2508,25 @@ get_existing_connection (NMManager *self,
 	 */
 	if (   assume_state_connection_uuid
 	    && (connection_checked = nm_settings_get_connection_by_uuid (priv->settings, assume_state_connection_uuid))
-	    && !active_connection_find (self, connection_checked, NULL,
-	                                NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
-	                                NULL)
-	    && nm_device_check_connection_compatible (device, NM_CONNECTION (connection_checked))) {
+	    && new_activation_allowed_for_connection (self, connection_checked)
+	    && nm_device_check_connection_compatible (device,
+	                                              nm_settings_connection_get_connection (connection_checked),
+	                                              NULL)) {
 
 		if (connection) {
-			NMConnection *const connections[] = {
-				NM_CONNECTION (connection_checked),
-				NULL,
-			};
-
-			matched = NM_SETTINGS_CONNECTION (nm_utils_match_connection (connections,
-			                                                             connection,
-			                                                             TRUE,
-			                                                             nm_device_has_carrier (device),
-			                                                             nm_device_get_route_metric (device, AF_INET),
-			                                                             nm_device_get_route_metric (device, AF_INET6),
-			                                                             NULL, NULL));
+			NMConnection *con = nm_settings_connection_get_connection (connection_checked);
+
+			if (nm_utils_match_connection ((NMConnection *[]) { con, NULL },
+			                               connection,
+			                               TRUE,
+			                               nm_device_has_carrier (device),
+			                               nm_device_get_route_metric (device, AF_INET),
+			                               nm_device_get_route_metric (device, AF_INET6),
+			                               NULL, NULL))
+				matched = connection_checked;
 		} else
 			matched = connection_checked;
-	} else
-		matched = NULL;
+	}
 
 	if (!matched && only_by_uuid) {
 		_LOG2D (LOGD_DEVICE, device, "assume: cannot generate connection: %s",
@@ -2487,32 +2535,51 @@ get_existing_connection (NMManager *self,
 	}
 
 	if (!matched && assume_state_guess_assume) {
-		gs_free NMSettingsConnection **connections = NULL;
+		gs_free NMSettingsConnection **sett_conns = NULL;
 		guint len, i, j;
 
 		/* the state file doesn't indicate a connection UUID to assume. Search the
 		 * persistent connections for a matching candidate. */
-		connections = nm_manager_get_activatable_connections (self, &len, FALSE);
+		sett_conns = nm_manager_get_activatable_connections (self, FALSE, FALSE, &len);
 		if (len > 0) {
 			for (i = 0, j = 0; i < len; i++) {
-				NMConnection *con = NM_CONNECTION (connections[i]);
+				NMSettingsConnection *sett_conn = sett_conns[i];
 
-				if (   con != NM_CONNECTION (connection_checked)
-				    && nm_device_check_connection_compatible (device, con))
-					connections[j++] = connections[i];
+				if (   sett_conn != connection_checked
+				    && nm_device_check_connection_compatible (device,
+				                                              nm_settings_connection_get_connection (sett_conn),
+				                                              NULL))
+					sett_conns[j++] = sett_conn;
 			}
-			connections[j] = NULL;
+			sett_conns[j] = NULL;
 			len = j;
-			g_qsort_with_data (connections, len, sizeof (connections[0]),
-			                   nm_settings_connection_cmp_timestamp_p_with_data, NULL);
-
-			matched = NM_SETTINGS_CONNECTION (nm_utils_match_connection ((NMConnection *const*) connections,
-			                                                             connection,
-			                                                             FALSE,
-			                                                             nm_device_has_carrier (device),
-			                                                             nm_device_get_route_metric (device, AF_INET),
-			                                                             nm_device_get_route_metric (device, AF_INET6),
-			                                                             NULL, NULL));
+			if (len > 0) {
+				gs_free NMConnection **conns = NULL;
+				NMConnection *con;
+
+				g_qsort_with_data (sett_conns, len, sizeof (sett_conns[0]),
+				                   nm_settings_connection_cmp_timestamp_p_with_data, NULL);
+
+				conns = nm_settings_connections_array_to_connections (sett_conns, len);
+
+				con = nm_utils_match_connection (conns,
+				                                 connection,
+				                                 FALSE,
+				                                 nm_device_has_carrier (device),
+				                                 nm_device_get_route_metric (device, AF_INET),
+				                                 nm_device_get_route_metric (device, AF_INET6),
+				                                 NULL,
+				                                 NULL);
+				if (con) {
+					for (i = 0; i < len; i++) {
+						if (conns[i] == con) {
+							matched = sett_conns[i];
+							break;
+						}
+					}
+					nm_assert (matched);
+				}
+			}
 		}
 	}
 
@@ -2541,7 +2608,7 @@ get_existing_connection (NMManager *self,
 		return NULL;
 	}
 
-	nm_settings_connection_set_flags (NM_SETTINGS_CONNECTION (added),
+	nm_settings_connection_set_flags (added,
 	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED |
 	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
 	                                  TRUE);
@@ -2553,7 +2620,7 @@ static gboolean
 recheck_assume_connection (NMManager *self,
                            NMDevice *device)
 {
-	NMSettingsConnection *connection;
+	NMSettingsConnection *sett_conn;
 	gboolean was_unmanaged = FALSE;
 	gboolean generated = FALSE;
 	NMDeviceState state;
@@ -2575,9 +2642,9 @@ recheck_assume_connection (NMManager *self,
 		return FALSE;
 	}
 
-	connection = get_existing_connection (self, device, &generated);
+	sett_conn = get_existing_connection (self, device, &generated);
 	/* log  no reason. get_existing_connection() already does it. */
-	if (!connection)
+	if (!sett_conn)
 		return FALSE;
 
 	nm_device_sys_iface_state_set (device,
@@ -2609,7 +2676,8 @@ recheck_assume_connection (NMManager *self,
 		subject = nm_auth_subject_new_internal ();
 		active = _new_active_connection (self,
 		                                 FALSE,
-		                                 NM_CONNECTION (connection),
+		                                 sett_conn,
+		                                 NULL,
 		                                 NULL,
 		                                 NULL,
 		                                 device,
@@ -2620,7 +2688,7 @@ recheck_assume_connection (NMManager *self,
 
 		if (!active) {
 			_LOGW (LOGD_DEVICE, "assume: assumed connection %s failed to activate: %s",
-			       nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)),
+			       nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)),
 			       error->message);
 			g_error_free (error);
 
@@ -2632,7 +2700,7 @@ recheck_assume_connection (NMManager *self,
 
 			if (generated) {
 				_LOG2D (LOGD_DEVICE, device, "assume: deleting generated connection after assuming failed");
-				nm_settings_connection_delete (connection, NULL);
+				nm_settings_connection_delete (sett_conn, NULL);
 			} else {
 				if (nm_device_sys_iface_state_get (device) == NM_DEVICE_SYS_IFACE_STATE_ASSUME)
 					nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
@@ -2642,7 +2710,14 @@ recheck_assume_connection (NMManager *self,
 
 		/* If the device is a slave or VLAN, find the master ActiveConnection */
 		master_ac = NULL;
-		if (find_master (self, NM_CONNECTION (connection), device, NULL, NULL, &master_ac, NULL) && master_ac)
+		if (   find_master (self,
+		                    nm_settings_connection_get_connection (sett_conn),
+		                    device,
+		                    NULL,
+		                    NULL,
+		                    &master_ac,
+		                    NULL)
+		    && master_ac)
 			nm_active_connection_set_master (active, master_ac);
 
 		active_connection_add (self, active);
@@ -3008,10 +3083,21 @@ platform_link_added (NMManager *self,
 			continue;
 
 		if (nm_device_is_real (candidate)) {
-			/* Ignore the link added event since there's already a realized
-			 * device with the link's name.
+			/* There's already a realized device with the link's name
+			 * and a different ifindex.
 			 */
-			nm_device_update_from_platform_link (candidate, plink);
+			if (nm_device_get_ifindex (candidate) <= 0)
+				nm_device_update_from_platform_link (candidate, plink);
+			else {
+				/* The ifindex of a device can't be changed after
+				 * initialization because it is used as a key by
+				 * the dns-manager.
+				 */
+				_LOGD (LOGD_DEVICE, "(%s): removing old device %p after ifindex change from %d to %d",
+				       plink->name, candidate, nm_device_get_ifindex (candidate), ifindex);
+				remove_device (self, candidate, FALSE, TRUE);
+				goto add;
+			}
 			return;
 		} else if (nm_device_realize_start (candidate,
 		                                    plink,
@@ -3031,6 +3117,7 @@ platform_link_added (NMManager *self,
 		/* Try next unrealized device */
 	}
 
+add:
 	/* Try registered device factories */
 	factory = nm_device_factory_manager_find_factory_for_link_type (plink->type);
 	if (factory) {
@@ -3234,9 +3321,11 @@ nm_manager_get_devices (NMManager *manager)
 
 static NMDevice *
 nm_manager_get_best_device_for_connection (NMManager *self,
+                                           NMSettingsConnection *sett_conn,
                                            NMConnection *connection,
                                            gboolean for_user_request,
-                                           GHashTable *unavailable_devices)
+                                           GHashTable *unavailable_devices,
+                                           GError **error)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMActiveConnectionState ac_state;
@@ -3245,16 +3334,25 @@ nm_manager_get_best_device_for_connection (NMManager *self,
 	NMDevice *device;
 	NMDeviceCheckConAvailableFlags flags;
 	gs_unref_ptrarray GPtrArray *all_ac_arr = NULL;
+	gs_free_error GError *local_best = NULL;
+
+	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (!connection || NM_IS_CONNECTION (connection));
+	nm_assert (sett_conn || connection);
+	nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));
+
+	if (!connection)
+		connection = nm_settings_connection_get_connection (sett_conn);
 
 	flags = for_user_request ? NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST : NM_DEVICE_CHECK_CON_AVAILABLE_NONE;
 
-	ac = active_connection_find_by_connection (self, connection, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, &all_ac_arr);
+	ac = active_connection_find_by_connection (self, sett_conn, connection, NM_ACTIVE_CONNECTION_STATE_DEACTIVATING, &all_ac_arr);
 	if (ac) {
 
 		ac_device = nm_active_connection_get_device (ac);
 		if (   ac_device
 		    && (   (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device))
-		        || !nm_device_check_connection_available (ac_device, connection, flags, NULL)))
+		        || !nm_device_check_connection_available (ac_device, connection, flags, NULL, NULL)))
 			ac_device = NULL;
 
 		if (all_ac_arr) {
@@ -3271,7 +3369,7 @@ nm_manager_get_best_device_for_connection (NMManager *self,
 
 				if (   !ac_device2
 				    || (unavailable_devices && g_hash_table_contains (unavailable_devices, ac_device2))
-				    || !nm_device_check_connection_available (ac_device2, connection, flags, NULL))
+				    || !nm_device_check_connection_available (ac_device2, connection, flags, NULL, NULL))
 					continue;
 
 				ac_state2 = nm_active_connection_get_state (ac2);
@@ -3319,15 +3417,53 @@ found_better:
 
 	/* Pick the first device that's compatible with the connection. */
 	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		GError *local = NULL;
 
-		if (unavailable_devices && g_hash_table_contains (unavailable_devices, device))
+		if (   unavailable_devices
+		    && g_hash_table_contains (unavailable_devices, device))
 			continue;
 
-		if (nm_device_check_connection_available (device, connection, flags, NULL))
+		if (nm_device_check_connection_available (device,
+		                                          connection,
+		                                          flags,
+		                                          NULL,
+		                                          error ? &local : NULL))
 			return device;
+
+		if (error) {
+			gboolean reset_error;
+
+			if (!local_best)
+				reset_error = TRUE;
+			else if (local_best->domain != NM_UTILS_ERROR)
+				reset_error = (local->domain == NM_UTILS_ERROR);
+			else {
+				reset_error = (   local->domain == NM_UTILS_ERROR
+			                   && local_best->code < local->code);
+			}
+
+			if (reset_error) {
+				g_clear_error (&local_best);
+				g_set_error (&local_best,
+				             local->domain,
+				             local->code,
+				             "device %s not available because %s",
+				             nm_device_get_iface (device),
+				             local->message);
+			}
+			g_error_free (local);
+		}
 	}
 
-	/* No luck. :( */
+	if (error) {
+		if (local_best)
+			g_propagate_error (error, g_steal_pointer (&local_best));
+		else {
+			nm_utils_error_set_literal (error,
+			                            NM_UTILS_ERROR_UNKNOWN,
+			                            "no suitable device found");
+		}
+	}
 	return NULL;
 }
 
@@ -3496,7 +3632,7 @@ find_master (NMManager *self,
 	NMSettingConnection *s_con;
 	const char *master;
 	NMDevice *master_device = NULL;
-	NMSettingsConnection *master_connection = NULL;
+	NMSettingsConnection *master_connection;
 
 	s_con = nm_connection_get_setting_connection (connection);
 	g_assert (s_con);
@@ -3515,7 +3651,9 @@ find_master (NMManager *self,
 		}
 
 		master_connection = nm_device_get_settings_connection (master_device);
-		if (master_connection && !is_compatible_with_slave (NM_CONNECTION (master_connection), connection)) {
+		if (   master_connection
+		    && !is_compatible_with_slave (nm_settings_connection_get_connection (master_connection),
+		                                  connection)) {
 			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
 			             "The active connection on %s is not compatible",
 			             nm_device_get_iface (master_device));
@@ -3615,7 +3753,9 @@ ensure_master_active_connection (NMManager *self,
 		 * be already activated on the device, eg returned from find_master().
 		 */
 		g_assert (!master_connection || master_connection == device_connection);
-		if (device_connection && !is_compatible_with_slave (NM_CONNECTION (device_connection), connection)) {
+		if (   device_connection
+		    && !is_compatible_with_slave (nm_settings_connection_get_connection (device_connection),
+		                                  connection)) {
 			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
 			             "The active connection %s is not compatible",
 			             nm_connection_get_id (connection));
@@ -3640,17 +3780,22 @@ ensure_master_active_connection (NMManager *self,
 			g_assert (master_connection == NULL);
 
 			/* Find a compatible connection and activate this device using it */
-			connections = nm_manager_get_activatable_connections (self, NULL, TRUE);
+			connections = nm_manager_get_activatable_connections (self, FALSE, TRUE, NULL);
 			for (i = 0; connections[i]; i++) {
 				NMSettingsConnection *candidate = connections[i];
+				NMConnection *cand_conn = nm_settings_connection_get_connection (candidate);
 
 				/* Ensure eg bond/team slave and the candidate master is a
 				 * bond/team master
 				 */
-				if (!is_compatible_with_slave (NM_CONNECTION (candidate), connection))
+				if (!is_compatible_with_slave (cand_conn, connection))
 					continue;
 
-				if (nm_device_check_connection_available (master_device, NM_CONNECTION (candidate), NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL)) {
+				if (nm_device_check_connection_available (master_device,
+				                                          cand_conn,
+				                                          NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
+				                                          NULL,
+				                                          NULL)) {
 					master_ac = nm_manager_activate_connection (self,
 					                                            candidate,
 					                                            NULL,
@@ -3686,7 +3831,11 @@ ensure_master_active_connection (NMManager *self,
 				continue;
 			}
 
-			if (!nm_device_check_connection_available (candidate, NM_CONNECTION (master_connection), NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL))
+			if (!nm_device_check_connection_available (candidate,
+			                                           nm_settings_connection_get_connection (master_connection),
+			                                           NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
+			                                           NULL,
+			                                           NULL))
 				continue;
 
 			if (!nm_device_is_software (candidate)) {
@@ -3725,18 +3874,18 @@ typedef struct {
 /**
  * find_slaves:
  * @manager: #NMManager object
- * @connection: the master #NMSettingsConnection to find slave connections for
- * @device: the master #NMDevice for the @connection
+ * @sett_conn: the master #NMSettingsConnection to find slave connections for
+ * @device: the master #NMDevice for the @sett_conn
  * @out_n_slaves: on return, the number of slaves found
  *
- * Given an #NMSettingsConnection, attempts to find its slaves. If @connection is not
+ * Given an #NMSettingsConnection, attempts to find its slaves. If @sett_conn is not
  * master, or has not any slaves, this will return %NULL.
  *
- * Returns: an array of #SlaveConnectionInfo for given master @connection, or %NULL
+ * Returns: an array of #SlaveConnectionInfo for given master @sett_conn, or %NULL
  **/
 static SlaveConnectionInfo *
 find_slaves (NMManager *manager,
-             NMSettingsConnection *connection,
+             NMSettingsConnection *sett_conn,
              NMDevice *device,
              guint *out_n_slaves)
 {
@@ -3751,7 +3900,7 @@ find_slaves (NMManager *manager,
 
 	nm_assert (out_n_slaves);
 
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
 	g_return_val_if_fail (s_con, NULL);
 
 	devices = g_hash_table_new (nm_direct_hash, NULL);
@@ -3766,15 +3915,23 @@ find_slaves (NMManager *manager,
 	for (i = 0; i < n_all_connections; i++) {
 		NMSettingsConnection *master_connection = NULL;
 		NMDevice *master_device = NULL, *slave_device;
-		NMConnection *candidate = NM_CONNECTION (all_connections[i]);
-
-		find_master (manager, candidate, NULL, &master_connection, &master_device, NULL, NULL);
-		if (   (master_connection && master_connection == connection)
+		NMSettingsConnection *candidate = all_connections[i];
+
+		find_master (manager,
+		             nm_settings_connection_get_connection (candidate),
+		             NULL,
+		             &master_connection,
+		             &master_device,
+		             NULL,
+		             NULL);
+		if (   (master_connection && master_connection == sett_conn)
 		    || (master_device && master_device == device)) {
 			slave_device = nm_manager_get_best_device_for_connection (manager,
 			                                                          candidate,
+			                                                          NULL,
 			                                                          FALSE,
-			                                                          devices);
+			                                                          devices,
+			                                                          NULL);
 
 			if (!slaves) {
 				/* what we allocate is quite likely much too large. Don't bother, it is only
@@ -3783,7 +3940,7 @@ find_slaves (NMManager *manager,
 			}
 
 			nm_assert (n_slaves < n_all_connections);
-			slaves[n_slaves].connection = NM_SETTINGS_CONNECTION (candidate),
+			slaves[n_slaves].connection = candidate,
 			slaves[n_slaves].device = slave_device,
 			n_slaves++;
 
@@ -3802,32 +3959,29 @@ static gboolean
 should_connect_slaves (NMConnection *connection, NMDevice *device)
 {
 	NMSettingConnection *s_con;
-	NMSettingConnectionAutoconnectSlaves autoconnect_slaves;
-	gs_free char *value = NULL;
+	NMSettingConnectionAutoconnectSlaves val;
 
 	s_con = nm_connection_get_setting_connection (connection);
 	g_assert (s_con);
 
-	/* Check autoconnect-slaves property */
-	autoconnect_slaves = nm_setting_connection_get_autoconnect_slaves (s_con);
-	if (autoconnect_slaves != NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT)
+	val = nm_setting_connection_get_autoconnect_slaves (s_con);
+	if (val != NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT)
 		goto out;
 
-	/* Check configuration default for autoconnect-slaves property */
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "connection.autoconnect-slaves", device);
-	if (value)
-		autoconnect_slaves = _nm_utils_ascii_str_to_int64 (value, 10, 0, 1, -1);
+	val = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+	                                                   "connection.autoconnect-slaves",
+	                                                   device,
+	                                                   0, 1, -1);
 
 out:
-	if (autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO)
+	if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_NO)
 		return FALSE;
-	if (autoconnect_slaves == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES)
+	if (val == NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_YES)
 		return TRUE;
 	return FALSE;
 }
 
-static gint
+static int
 compare_slaves (gconstpointer a, gconstpointer b, gpointer sort_by_name)
 {
 	const SlaveConnectionInfo *a_info = a;
@@ -3855,7 +4009,8 @@ autoconnect_slaves (NMManager *self,
 {
 	GError *local_err = NULL;
 
-	if (should_connect_slaves (NM_CONNECTION (master_connection), master_device)) {
+	if (should_connect_slaves (nm_settings_connection_get_connection (master_connection),
+	                           master_device)) {
 		gs_free SlaveConnectionInfo *slaves = NULL;
 		guint i, n_slaves = 0;
 
@@ -3996,7 +4151,7 @@ active_connection_parent_active (NMActiveConnection *active,
 {
 	NMDevice *device = nm_active_connection_get_device (active);
 	GError *error = NULL;
-	NMSettingsConnection *connection;
+	NMSettingsConnection *sett_conn;
 	NMDevice *parent;
 
 	g_signal_handlers_disconnect_by_func (active,
@@ -4012,10 +4167,13 @@ active_connection_parent_active (NMActiveConnection *active,
 		return;
 	}
 
-	connection = nm_active_connection_get_settings_connection (active);
+	sett_conn = nm_active_connection_get_settings_connection (active);
 	parent = nm_active_connection_get_device (parent_ac);
 
-	if (!nm_device_create_and_realize (device, (NMConnection *) connection, parent, &error)) {
+	if (!nm_device_create_and_realize (device,
+	                                   nm_settings_connection_get_connection (sett_conn),
+	                                   parent,
+	                                   &error)) {
 		_LOGW (LOGD_CORE, "Could not realize device '%s': %s",
 		       nm_device_get_iface (device), error->message);
 		nm_active_connection_set_state_fail (active,
@@ -4033,11 +4191,13 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 {
 	NMDevice *device, *master_device = NULL;
 	NMConnection *applied;
-	NMSettingsConnection *connection;
+	NMSettingsConnection *sett_conn;
 	NMSettingsConnection *master_connection = NULL;
 	NMConnection *existing_connection = NULL;
 	NMActiveConnection *master_ac = NULL;
 	NMAuthSubject *subject;
+	GError *local = NULL;
+	NMConnectionMultiConnect multi_connect;
 
 	g_return_val_if_fail (NM_IS_MANAGER (self), FALSE);
 	g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (active), FALSE);
@@ -4048,8 +4208,8 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	device = nm_active_connection_get_device (active);
 	g_return_val_if_fail (device != NULL, FALSE);
 
-	connection = nm_active_connection_get_settings_connection (active);
-	nm_assert (connection);
+	sett_conn = nm_active_connection_get_settings_connection (active);
+	nm_assert (sett_conn);
 
 	applied = nm_active_connection_get_applied_connection (active);
 
@@ -4071,10 +4231,13 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	}
 
 	/* Final connection must be available on device */
-	if (!nm_device_check_connection_available (device, applied, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL)) {
+	if (!nm_device_check_connection_available (device, applied, NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST, NULL, &local)) {
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
-		             "Connection '%s' is not available on the device %s at this time.",
-		             nm_settings_connection_get_id (connection), nm_device_get_iface (device));
+		             "Connection '%s' is not available on device %s because %s",
+		             nm_settings_connection_get_id (sett_conn),
+		             nm_device_get_iface (device),
+		             local->message);
+		g_error_free (local);
 		return FALSE;
 	}
 
@@ -4085,7 +4248,9 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	if (!nm_device_is_real (device)) {
 		NMDevice *parent;
 
-		parent = find_parent_device_for_connection (self, (NMConnection *) connection, NULL);
+		parent = find_parent_device_for_connection (self,
+		                                            nm_settings_connection_get_connection (sett_conn),
+		                                            NULL);
 
 		if (parent && !nm_device_is_real (parent)) {
 			NMSettingsConnection *parent_con;
@@ -4097,7 +4262,11 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 				return FALSE;
 			}
 
-			parent_ac = nm_manager_activate_connection (self, parent_con, NULL, NULL, parent,
+			parent_ac = nm_manager_activate_connection (self,
+			                                            parent_con,
+			                                            NULL,
+			                                            NULL,
+			                                            parent,
 			                                            subject,
 			                                            NM_ACTIVATION_TYPE_MANAGED,
 			                                            nm_active_connection_get_activation_reason (active),
@@ -4115,7 +4284,10 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 			nm_active_connection_set_parent (active, parent_ac);
 		} else {
 			/* We can realize now; no need to wait for a parent device. */
-			if (!nm_device_create_and_realize (device, (NMConnection *) connection, parent, error)) {
+			if (!nm_device_create_and_realize (device,
+			                                   nm_settings_connection_get_connection (sett_conn),
+			                                   parent,
+			                                   error)) {
 				g_prefix_error (error, "%s failed to create resources: ", nm_device_get_iface (device));
 				return FALSE;
 			}
@@ -4123,11 +4295,15 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	}
 
 	/* Try to find the master connection/device if the connection has a dependency */
-	if (!find_master (self, applied, device,
-	                  &master_connection, &master_device, &master_ac,
+	if (!find_master (self,
+	                  applied,
+	                  device,
+	                  &master_connection,
+	                  &master_device,
+	                  &master_ac,
 	                  error)) {
 		g_prefix_error (error, "Can not find a master for %s: ",
-		                nm_settings_connection_get_id (connection));
+		                nm_settings_connection_get_id (sett_conn));
 		return FALSE;
 	}
 
@@ -4137,21 +4313,23 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	if (master_connection || master_device) {
 		if (master_connection) {
 			_LOGD (LOGD_CORE, "Activation of '%s' requires master connection '%s'",
-			       nm_settings_connection_get_id (connection),
+			       nm_settings_connection_get_id (sett_conn),
 			       nm_settings_connection_get_id (master_connection));
 		}
 		if (master_device) {
 			_LOGD (LOGD_CORE, "Activation of '%s' requires master device '%s'",
-			       nm_settings_connection_get_id (connection),
+			       nm_settings_connection_get_id (sett_conn),
 			       nm_device_get_ip_iface (master_device));
 		}
 
 		/* Ensure eg bond slave and the candidate master is a bond master */
-		if (master_connection && !is_compatible_with_slave (NM_CONNECTION (master_connection), applied)) {
+		if (   master_connection
+		    && !is_compatible_with_slave (nm_settings_connection_get_connection (master_connection),
+		                                  applied)) {
 			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_DEPENDENCY_FAILED,
 			             "The master connection '%s' is not compatible with '%s'",
 			             nm_settings_connection_get_id (master_connection),
-			             nm_settings_connection_get_id (connection));
+			             nm_settings_connection_get_id (sett_conn));
 			return FALSE;
 		}
 
@@ -4170,7 +4348,7 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 					                nm_device_get_ip_iface (device));
 				} else {
 					g_prefix_error (error, "Master connection '%s' can't be activated: ",
-					                nm_settings_connection_get_id (connection));
+					                nm_settings_connection_get_id (sett_conn));
 				}
 				return FALSE;
 			}
@@ -4187,22 +4365,30 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 
 		nm_active_connection_set_master (active, master_ac);
 		_LOGD (LOGD_CORE, "Activation of '%s' depends on active connection %p %s",
-		       nm_settings_connection_get_id (connection),
+		       nm_settings_connection_get_id (sett_conn),
 		       master_ac,
 		       nm_dbus_object_get_path (NM_DBUS_OBJECT  (master_ac)) ?: "");
 	}
 
 	/* Check slaves for master connection and possibly activate them */
-	autoconnect_slaves (self, connection, device, nm_active_connection_get_subject (active));
-
-	{
+	autoconnect_slaves (self, sett_conn, device, nm_active_connection_get_subject (active));
+
+	multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn));
+	if (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE
+	    || (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE
+	        && NM_IN_SET (nm_active_connection_get_activation_reason (active),
+	                      NM_ACTIVATION_REASON_ASSUME,
+	                      NM_ACTIVATION_REASON_AUTOCONNECT_SLAVES,
+	                      NM_ACTIVATION_REASON_USER_REQUEST))) {
+		/* the profile can be activated multiple times. Proceed. */
+	} else {
 		gs_unref_ptrarray GPtrArray *all_ac_arr = NULL;
 		NMActiveConnection *ac;
 		guint i, n_all;
 
 		/* Disconnect the connection if already connected or queued for activation.
 		 * The connection cannot be active multiple times (at the same time).  */
-		ac = active_connection_find (self, connection, NULL, NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+		ac = active_connection_find (self, sett_conn, NULL, NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
 		                             &all_ac_arr);
 		if (ac) {
 			n_all = all_ac_arr ? all_ac_arr->len : ((guint) 1);
@@ -4265,7 +4451,7 @@ _internal_activate_generic (NMManager *self, NMActiveConnection *active, GError
 		 * is exported, make sure the manager's activating-connection property
 		 * is up-to-date.
 		 */
-		policy_activating_device_changed (G_OBJECT (priv->policy), NULL, self);
+		policy_activating_ac_changed (G_OBJECT (priv->policy), NULL, self);
 	}
 
 	return success;
@@ -4274,7 +4460,8 @@ _internal_activate_generic (NMManager *self, NMActiveConnection *active, GError
 static NMActiveConnection *
 _new_active_connection (NMManager *self,
                         gboolean is_vpn,
-                        NMConnection *connection,
+                        NMSettingsConnection *sett_conn,
+                        NMConnection *incompl_conn,
                         NMConnection *applied,
                         const char *specific_object,
                         NMDevice *device,
@@ -4284,18 +4471,19 @@ _new_active_connection (NMManager *self,
                         GError **error)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMSettingsConnection *settings_connection = NULL;
 	NMDevice *parent_device;
 
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
-	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
-
-	nm_assert (is_vpn == _connection_is_vpn (connection));
+	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (!incompl_conn || NM_IS_CONNECTION (incompl_conn));
+	nm_assert ((!incompl_conn) ^ (!sett_conn));
+	nm_assert (NM_IS_AUTH_SUBJECT (subject));
+	nm_assert (is_vpn == _connection_is_vpn (sett_conn
+	                                         ? nm_settings_connection_get_connection (sett_conn)
+	                                         : incompl_conn));
 	nm_assert (is_vpn || NM_IS_DEVICE (device));
 	nm_assert (!nm_streq0 (specific_object, "/"));
-
-	if (NM_IS_SETTINGS_CONNECTION (connection))
-		settings_connection = (NMSettingsConnection *) connection;
+	nm_assert (!applied || NM_IS_CONNECTION (applied));
+	nm_assert (!is_vpn || !applied);
 
 	if (is_vpn) {
 		NMActiveConnection *parent;
@@ -4303,21 +4491,20 @@ _new_active_connection (NMManager *self,
 		/* FIXME: for VPN connections, we don't allow re-activating an
 		 * already active connection. It's a bug, and should be fixed together
 		 * when reworking VPN handling. */
-		if (active_connection_find_by_connection (self, connection, NM_ACTIVE_CONNECTION_STATE_ACTIVATED, NULL)) {
+		if (active_connection_find_by_connection (self,
+		                                          sett_conn,
+		                                          incompl_conn,
+		                                          NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+		                                          NULL)) {
 			g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE,
 			             "Connection '%s' is already active",
-			             nm_connection_get_id (connection));
+			             sett_conn ? nm_settings_connection_get_id (sett_conn) : nm_connection_get_id (incompl_conn));
 			return NULL;
 		}
 
-		/* FIXME: apparently, activation here only works if @connection is
-		 * a settings-connection. Which is not the case during AddAndActivatate.
-		 * Probably, AddAndActivate is broken for VPN. */
 		if (activation_type != NM_ACTIVATION_TYPE_MANAGED)
 			g_return_val_if_reached (NULL);
 
-		g_return_val_if_fail (!settings_connection || NM_IS_SETTINGS_CONNECTION (settings_connection), NULL);
-
 		if (specific_object) {
 			/* Find the specific connection the client requested we use */
 			parent = active_connection_get_by_path (self, specific_object);
@@ -4348,14 +4535,14 @@ _new_active_connection (NMManager *self,
 			return NULL;
 		}
 
-		return (NMActiveConnection *) nm_vpn_connection_new (settings_connection,
+		return (NMActiveConnection *) nm_vpn_connection_new (sett_conn,
 		                                                     parent_device,
 		                                                     nm_dbus_object_get_path (NM_DBUS_OBJECT (parent)),
 		                                                     activation_reason,
 		                                                     subject);
 	}
 
-	return (NMActiveConnection *) nm_act_request_new (settings_connection,
+	return (NMActiveConnection *) nm_act_request_new (sett_conn,
 	                                                  applied,
 	                                                  specific_object,
 	                                                  subject,
@@ -4416,17 +4603,17 @@ fail:
 /**
  * nm_manager_activate_connection():
  * @self: the #NMManager
- * @connection: the #NMSettingsConnection to activate on @device
+ * @sett_conn: the #NMSettingsConnection to activate on @device
  * @applied: (allow-none): the applied connection to activate on @device
  * @specific_object: the specific object path, if any, for the activation
- * @device: the #NMDevice to activate @connection on. Can be %NULL for VPNs.
+ * @device: the #NMDevice to activate @sett_conn on. Can be %NULL for VPNs.
  * @subject: the subject which requested activation
  * @activation_type: whether to assume the connection. That is, take over gracefully,
  *   non-destructible.
  * @activation_reason: the reason for activation
  * @error: return location for an error
  *
- * Begins a new internally-initiated activation of @connection on @device.
+ * Begins a new internally-initiated activation of @sett_conn on @device.
  * @subject should be the subject of the activation that triggered this
  * one, or if this is an autoconnect request, a new internal subject.
  * The returned #NMActiveConnection is owned by the Manager and should be
@@ -4434,11 +4621,11 @@ fail:
  * is supplied, it shall not be modified by the caller afterwards.
  *
  * Returns: (transfer none): the new #NMActiveConnection that tracks
- * activation of @connection on @device
+ * activation of @sett_conn on @device
  */
 NMActiveConnection *
 nm_manager_activate_connection (NMManager *self,
-                                NMSettingsConnection *connection,
+                                NMSettingsConnection *sett_conn,
                                 NMConnection *applied,
                                 const char *specific_object,
                                 NMDevice *device,
@@ -4453,15 +4640,15 @@ nm_manager_activate_connection (NMManager *self,
 	gboolean is_vpn;
 
 	g_return_val_if_fail (NM_IS_MANAGER (self), NULL);
-	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), NULL);
-	is_vpn = _connection_is_vpn (NM_CONNECTION (connection));
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), NULL);
+	is_vpn = _connection_is_vpn (nm_settings_connection_get_connection (sett_conn));
 	g_return_val_if_fail (is_vpn || NM_IS_DEVICE (device), NULL);
 	g_return_val_if_fail (!error || !*error, NULL);
 	nm_assert (!nm_streq0 (specific_object, "/"));
 
 	priv = NM_MANAGER_GET_PRIVATE (self);
 
-	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (connection),
+	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
 	                                          subject,
 	                                          NM_MANAGER_ERROR,
 	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -4479,7 +4666,7 @@ nm_manager_activate_connection (NMManager *self,
 			continue;
 
 		active = async_op_data->ac_auth.active;
-		if (   connection == nm_active_connection_get_settings_connection (active)
+		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))
@@ -4490,7 +4677,8 @@ nm_manager_activate_connection (NMManager *self,
 
 	active = _new_active_connection (self,
 	                                 is_vpn,
-	                                 NM_CONNECTION (connection),
+	                                 sett_conn,
+	                                 NULL,
 	                                 applied,
 	                                 specific_object,
 	                                 device,
@@ -4513,7 +4701,9 @@ nm_manager_activate_connection (NMManager *self,
  * validate_activation_request:
  * @self: the #NMManager
  * @context: the D-Bus context of the requestor
- * @connection: the partial or complete #NMConnection to be activated
+ * @sett_conn: the #NMSettingsConnection to be activated, or %NULL if there
+ *   is only a partial activation.
+ * @connection: the partial #NMConnection to be activated (if @sett_conn is unspecified)
  * @device_path: the object path of the device to be activated, or NULL
  * @out_device: on successful reutrn, the #NMDevice to be activated with @connection
  *   The caller may pass in a device which shortcuts the lookup by path.
@@ -4532,6 +4722,7 @@ nm_manager_activate_connection (NMManager *self,
 static NMAuthSubject *
 validate_activation_request (NMManager *self,
                              GDBusMethodInvocation *context,
+                             NMSettingsConnection *sett_conn,
                              NMConnection *connection,
                              const char *device_path,
                              NMDevice **out_device,
@@ -4542,10 +4733,16 @@ validate_activation_request (NMManager *self,
 	gboolean is_vpn = FALSE;
 	gs_unref_object NMAuthSubject *subject = NULL;
 
-	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (!sett_conn || NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (!connection || NM_IS_CONNECTION (connection));
+	nm_assert (sett_conn || connection);
+	nm_assert (!connection || !sett_conn || connection == nm_settings_connection_get_connection (sett_conn));
 	nm_assert (out_device);
 	nm_assert (out_is_vpn);
 
+	if (!connection)
+		connection = nm_settings_connection_get_connection (sett_conn);
+
 	/* Validate the caller */
 	subject = nm_auth_subject_new_unix_process_from_context (context);
 	if (!subject) {
@@ -4581,17 +4778,20 @@ validate_activation_request (NMManager *self,
 			return NULL;
 		}
 	} else if (!is_vpn) {
-		device = nm_manager_get_best_device_for_connection (self, connection, TRUE, NULL);
+		gs_free_error GError *local = NULL;
+
+		device = nm_manager_get_best_device_for_connection (self, sett_conn, connection, TRUE, NULL, &local);
 		if (!device) {
 			gs_free char *iface = NULL;
 
 			/* VPN and software-device connections don't need a device yet,
 			 * but non-virtual connections do ... */
 			if (!nm_connection_is_virtual (connection)) {
-				g_set_error_literal (error,
-				                     NM_MANAGER_ERROR,
-				                     NM_MANAGER_ERROR_UNKNOWN_DEVICE,
-				                     "No suitable device found for this connection.");
+				g_set_error (error,
+				             NM_MANAGER_ERROR,
+				             NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+				             "No suitable device found for this connection (%s).",
+				             local->message);
 				return NULL;
 			}
 
@@ -4677,7 +4877,7 @@ impl_manager_activate_connection (NMDBusObject *obj,
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gs_unref_object NMActiveConnection *active = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
-	NMSettingsConnection *connection = NULL;
+	NMSettingsConnection *sett_conn = NULL;
 	NMDevice *device = NULL;
 	gboolean is_vpn = FALSE;
 	GError *error = NULL;
@@ -4697,8 +4897,8 @@ impl_manager_activate_connection (NMDBusObject *obj,
 	 * (since this is an explicit request, not an auto-activation request).
 	 */
 	if (connection_path) {
-		connection = nm_settings_get_connection_by_path (priv->settings, connection_path);
-		if (!connection) {
+		sett_conn = nm_settings_get_connection_by_path (priv->settings, connection_path);
+		if (!sett_conn) {
 			error = g_error_new_literal (NM_MANAGER_ERROR,
 			                             NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
 			                             "Connection could not be found.");
@@ -4718,14 +4918,15 @@ impl_manager_activate_connection (NMDBusObject *obj,
 			goto error;
 		}
 
-		connection = nm_device_get_best_connection (device, specific_object_path, &error);
-		if (!connection)
+		sett_conn = nm_device_get_best_connection (device, specific_object_path, &error);
+		if (!sett_conn)
 			goto error;
 	}
 
 	subject = validate_activation_request (self,
 	                                       invocation,
-	                                       NM_CONNECTION (connection),
+	                                       sett_conn,
+	                                       NULL,
 	                                       device_path,
 	                                       &device,
 	                                       &is_vpn,
@@ -4735,7 +4936,8 @@ impl_manager_activate_connection (NMDBusObject *obj,
 
 	active = _new_active_connection (self,
 	                                 is_vpn,
-	                                 NM_CONNECTION (connection),
+	                                 sett_conn,
+	                                 NULL,
 	                                 NULL,
 	                                 specific_object_path,
 	                                 device,
@@ -4759,8 +4961,8 @@ impl_manager_activate_connection (NMDBusObject *obj,
 	return;
 
 error:
-	if (connection) {
-		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, connection, FALSE, NULL,
+	if (sett_conn) {
+		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ACTIVATE, sett_conn, FALSE, NULL,
 		                            subject, error->message);
 	}
 	g_dbus_method_invocation_take_error (invocation, error);
@@ -4875,7 +5077,7 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 {
 	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *incompl_conn = NULL;
 	NMActiveConnection *active = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
@@ -4884,6 +5086,7 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	gs_unref_variant GVariant *settings = NULL;
 	const char *device_path;
 	const char *specific_object_path;
+	gs_free NMConnection **conns = NULL;
 
 	g_variant_get (parameters, "(@a{sa{sv}}&o&o)", &settings, &device_path, &specific_object_path);
 
@@ -4897,13 +5100,14 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	 * specific data being in the connection till then (especially in
 	 * validate_activation_request()).
 	 */
-	connection = nm_simple_connection_new ();
+	incompl_conn = nm_simple_connection_new ();
 	if (settings && g_variant_n_children (settings))
-		_nm_connection_replace_settings (connection, settings, NM_SETTING_PARSE_FLAGS_STRICT, NULL);
+		_nm_connection_replace_settings (incompl_conn, settings, NM_SETTING_PARSE_FLAGS_STRICT, NULL);
 
 	subject = validate_activation_request (self,
 	                                       invocation,
-	                                       connection,
+	                                       NULL,
+	                                       incompl_conn,
 	                                       device_path,
 	                                       &device,
 	                                       &is_vpn,
@@ -4913,7 +5117,7 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 
 	if (is_vpn) {
 		/* Try to fill the VPN's connection setting and name at least */
-		if (!nm_connection_get_setting_vpn (connection)) {
+		if (!nm_connection_get_setting_vpn (incompl_conn)) {
 			error = g_error_new_literal (NM_CONNECTION_ERROR,
 			                             NM_CONNECTION_ERROR_MISSING_SETTING,
 			                             "VPN connections require a 'vpn' setting");
@@ -4921,27 +5125,31 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 			goto error;
 		}
 
+		conns = nm_settings_connections_array_to_connections (nm_settings_get_connections (priv->settings, NULL), -1);
+
 		nm_utils_complete_generic (priv->platform,
-		                           connection,
+		                           incompl_conn,
 		                           NM_SETTING_VPN_SETTING_NAME,
-		                           (NMConnection *const*) nm_settings_get_connections (priv->settings, NULL),
+		                           conns,
 		                           NULL,
 		                           _("VPN connection"),
 		                           NULL,
 		                           FALSE); /* No IPv6 by default for now */
 	} else {
+		conns = nm_settings_connections_array_to_connections (nm_settings_get_connections (priv->settings, NULL), -1);
 		/* Let each device subclass complete the connection */
 		if (!nm_device_complete_connection (device,
-		                                    connection,
+		                                    incompl_conn,
 		                                    specific_object_path,
-		                                    (NMConnection *const*) nm_settings_get_connections (priv->settings, NULL),
+		                                    conns,
 		                                    &error))
 			goto error;
 	}
 
 	active = _new_active_connection (self,
 	                                 is_vpn,
-	                                 connection,
+	                                 NULL,
+	                                 incompl_conn,
 	                                 NULL,
 	                                 specific_object_path,
 	                                 device,
@@ -4952,15 +5160,16 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	if (!active)
 		goto error;
 
-	nm_active_connection_authorize (active, connection,
+	nm_active_connection_authorize (active,
+	                                incompl_conn,
 	                                _async_op_complete_ac_auth_cb,
 	                                _async_op_data_new_ac_auth_add_and_activate (self,
 	                                                                             active,
 	                                                                             invocation,
-	                                                                             connection));
+	                                                                             incompl_conn));
 
 	/* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */
-	g_steal_pointer (&connection);
+	g_steal_pointer (&incompl_conn);
 	g_steal_pointer (&active);
 	return;
 
@@ -5077,7 +5286,7 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 	NMManager *self = NM_MANAGER (obj);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	NMActiveConnection *ac;
-	NMSettingsConnection *connection = NULL;
+	NMSettingsConnection *sett_conn = NULL;
 	GError *error = NULL;
 	NMAuthSubject *subject = NULL;
 	NMAuthChain *chain;
@@ -5088,9 +5297,9 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 	/* Find the connection by its object path */
 	ac = active_connection_get_by_path (self, active_path);
 	if (ac)
-		connection = nm_active_connection_get_settings_connection (ac);
+		sett_conn = nm_active_connection_get_settings_connection (ac);
 
-	if (!connection) {
+	if (!sett_conn) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_CONNECTION_NOT_ACTIVE,
 		                             "The connection was not active.");
@@ -5106,7 +5315,7 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 		goto done;
 	}
 
-	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (connection),
+	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
 	                                          subject,
 	                                          NM_MANAGER_ERROR,
 	                                          NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -5128,8 +5337,9 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 
 done:
 	if (error) {
-		if (connection) {
-			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DEACTIVATE, connection, FALSE, NULL,
+		if (sett_conn) {
+			nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DEACTIVATE,
+			                            sett_conn, FALSE, NULL,
 			                            subject, error->message);
 		}
 		g_dbus_method_invocation_take_error (invocation, error);
@@ -5878,66 +6088,76 @@ start_factory (NMDeviceFactory *factory, gpointer user_data)
 	nm_device_factory_start (factory);
 }
 
-void
-nm_manager_write_device_state (NMManager *self)
+gboolean
+nm_manager_write_device_state (NMManager *self, NMDevice *device)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMDevice *device;
-	gs_unref_hashtable GHashTable *seen_ifindexes = NULL;
-	gint nm_owned;
+	int ifindex;
+	gboolean managed;
+	NMConfigDeviceStateManagedType managed_type;
+	const char *uuid = NULL;
+	const char *perm_hw_addr_fake = NULL;
+	gboolean perm_hw_addr_is_fake;
+	guint32 route_metric_default_aspired;
+	guint32 route_metric_default_effective;
+	int nm_owned;
+
+	ifindex = nm_device_get_ip_ifindex (device);
+	if (ifindex <= 0)
+		return FALSE;
+	if (ifindex == 1) {
+		/* ignore loopback */
+		return FALSE;
+	}
 
-	seen_ifindexes = g_hash_table_new (nm_direct_hash, NULL);
+	if (!nm_platform_link_get (priv->platform, ifindex))
+		return FALSE;
 
-	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
-		int ifindex;
-		gboolean managed;
-		NMConfigDeviceStateManagedType managed_type;
-		NMConnection *settings_connection;
-		const char *uuid = NULL;
-		const char *perm_hw_addr_fake = NULL;
-		gboolean perm_hw_addr_is_fake;
-		guint32 route_metric_default_aspired;
-		guint32 route_metric_default_effective;
-
-		ifindex = nm_device_get_ip_ifindex (device);
-		if (ifindex <= 0)
-			continue;
-		if (ifindex == 1) {
-			/* ignore loopback */
-			continue;
-		}
+	managed = nm_device_get_managed (device, FALSE);
+	if (managed) {
+		NMSettingsConnection *sett_conn;
 
-		if (!nm_platform_link_get (priv->platform, ifindex))
-			continue;
+		sett_conn = nm_device_get_settings_connection (device);
+		if (sett_conn)
+			uuid = nm_settings_connection_get_uuid (sett_conn);
+		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
+	} else if (nm_device_get_unmanaged_flags (device, NM_UNMANAGED_USER_EXPLICIT))
+		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
+	else
+		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN;
 
-		managed = nm_device_get_managed (device, FALSE);
-		if (managed) {
-			settings_connection = NM_CONNECTION (nm_device_get_settings_connection (device));
-			if (settings_connection)
-				uuid = nm_connection_get_uuid (settings_connection);
-			managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
-		} else if (nm_device_get_unmanaged_flags (device, NM_UNMANAGED_USER_EXPLICIT))
-			managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
-		else
-			managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN;
+	perm_hw_addr_fake = nm_device_get_permanent_hw_address_full (device, FALSE, &perm_hw_addr_is_fake);
+	if (perm_hw_addr_fake && !perm_hw_addr_is_fake)
+		perm_hw_addr_fake = NULL;
+
+	nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
+
+	route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN,
+	                                                           TRUE, &route_metric_default_aspired);
 
-		perm_hw_addr_fake = nm_device_get_permanent_hw_address_full (device, FALSE, &perm_hw_addr_is_fake);
-		if (perm_hw_addr_fake && !perm_hw_addr_is_fake)
-			perm_hw_addr_fake = NULL;
+	return nm_config_device_state_write (ifindex,
+	                                     managed_type,
+	                                     perm_hw_addr_fake,
+	                                     uuid,
+	                                     nm_owned,
+	                                     route_metric_default_aspired,
+	                                     route_metric_default_effective);
+}
 
-		nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
+void
+nm_manager_write_device_state_all (NMManager *self)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *seen_ifindexes = NULL;
+	NMDevice *device;
 
-		route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN,
-		                                                           TRUE, &route_metric_default_aspired);
+	seen_ifindexes = g_hash_table_new (nm_direct_hash, NULL);
 
-		if (nm_config_device_state_write (ifindex,
-		                                  managed_type,
-		                                  perm_hw_addr_fake,
-		                                  uuid,
-		                                  nm_owned,
-		                                  route_metric_default_aspired,
-		                                  route_metric_default_effective))
-			g_hash_table_add (seen_ifindexes, GINT_TO_POINTER (ifindex));
+	c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
+		if (nm_manager_write_device_state (self, device)) {
+			g_hash_table_add (seen_ifindexes,
+			                  GINT_TO_POINTER (nm_device_get_ip_ifindex (device)));
+		}
 	}
 
 	nm_config_device_state_prune_unseen (seen_ifindexes);
@@ -6017,7 +6237,7 @@ nm_manager_start (NMManager *self, GError **error)
 	                                                 NULL, NULL,
 	                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
 	for (i = 0; connections[i]; i++)
-		connection_changed (self, NM_CONNECTION (connections[i]));
+		connection_changed (self, connections[i]);
 
 	nm_clear_g_source (&priv->devices_inited_id);
 	priv->devices_inited_id = g_idle_add_full (G_PRIORITY_LOW + 10, devices_inited_cb, self, NULL);
@@ -6117,25 +6337,19 @@ connection_metered_changed (GObject *object,
 }
 
 static void
-policy_default_device_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
+policy_default_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMDevice *best;
 	NMActiveConnection *ac;
 
 	/* Note: this assumes that it's not possible for the IP4 default
 	 * route to be going over the default-ip6-device. If that changes,
 	 * we need something more complicated here.
 	 */
-	best = nm_policy_get_default_ip4_device (priv->policy);
-	if (!best)
-		best = nm_policy_get_default_ip6_device (priv->policy);
-
-	if (best)
-		ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (best));
-	else
-		ac = NULL;
+	ac = nm_policy_get_default_ip4_ac (priv->policy);
+	if (!ac)
+		ac = nm_policy_get_default_ip6_ac (priv->policy);
 
 	if (ac != priv->primary_connection) {
 		if (priv->primary_connection) {
@@ -6148,10 +6362,12 @@ policy_default_device_changed (GObject *object, GParamSpec *pspec, gpointer user
 		priv->primary_connection = ac ? g_object_ref (ac) : NULL;
 
 		if (priv->primary_connection) {
-			g_signal_connect (priv->primary_connection, NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED,
+			g_signal_connect (priv->primary_connection,
+			                  NM_ACTIVE_CONNECTION_DEVICE_METERED_CHANGED,
 			                  G_CALLBACK (connection_metered_changed), self);
 		}
-		_LOGD (LOGD_CORE, "PrimaryConnection now %s", ac ? nm_active_connection_get_settings_connection_id (ac) : "(none)");
+		_LOGD (LOGD_CORE, "PrimaryConnection now %s",
+		       ac ? nm_active_connection_get_settings_connection_id (ac) : "(none)");
 		_notify (self, PROP_PRIMARY_CONNECTION);
 		_notify (self, PROP_PRIMARY_CONNECTION_TYPE);
 		nm_manager_update_metered (self);
@@ -6159,34 +6375,29 @@ policy_default_device_changed (GObject *object, GParamSpec *pspec, gpointer user
 }
 
 static void
-policy_activating_device_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
+policy_activating_ac_changed (GObject *object, GParamSpec *pspec, gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	NMDevice *activating, *best;
-	NMActiveConnection *ac;
+	NMActiveConnection *activating, *best;
 
-	/* We only look at activating-ip6-device if activating-ip4-device
-	 * AND default-ip4-device are NULL; if default-ip4-device is
-	 * non-NULL, then activating-ip6-device is irrelevant, since while
-	 * that device might become the new default-ip6-device, it can't
-	 * become primary-connection while default-ip4-device is set to
+	/* We only look at activating-ip6-ac if activating-ip4-ac
+	 * AND default-ip4-ac are NULL; if default-ip4-ac is
+	 * non-NULL, then activating-ip6-ac is irrelevant, since while
+	 * that AC might become the new default-ip6-ac, it can't
+	 * become primary-connection while default-ip4-ac is set to
 	 * something else.
 	 */
-	activating = nm_policy_get_activating_ip4_device (priv->policy);
-	best = nm_policy_get_default_ip4_device (priv->policy);
+	activating = nm_policy_get_activating_ip4_ac (priv->policy);
+	best = nm_policy_get_default_ip4_ac (priv->policy);
 	if (!activating && !best)
-		activating = nm_policy_get_activating_ip6_device (priv->policy);
+		activating = nm_policy_get_activating_ip6_ac (priv->policy);
 
-	if (activating)
-		ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (activating));
-	else
-		ac = NULL;
-
-	if (ac != priv->activating_connection) {
-		g_clear_object (&priv->activating_connection);
-		priv->activating_connection = ac ? g_object_ref (ac) : NULL;
-		_LOGD (LOGD_CORE, "ActivatingConnection now %s", ac ? nm_active_connection_get_settings_connection_id (ac) : "(none)");
+	if (nm_g_object_ref_set (&priv->activating_connection, activating)) {
+		_LOGD (LOGD_CORE, "ActivatingConnection now %s",
+		       activating
+		           ? nm_active_connection_get_settings_connection_id (activating)
+		           : "(none)");
 		_notify (self, PROP_ACTIVATING_CONNECTION);
 	}
 }
@@ -6691,12 +6902,12 @@ nm_manager_set_capability (NMManager *self,
 
 	priv = NM_MANAGER_GET_PRIVATE (self);
 
-	idx = _nm_utils_array_find_binary_search (&g_array_index (priv->capabilities, guint32, 0),
-	                                          sizeof (guint32),
-	                                          priv->capabilities->len,
-	                                          &cap_i,
-	                                          nm_cmp_uint32_p_with_data,
-	                                          NULL);
+	idx = nm_utils_array_find_binary_search (&g_array_index (priv->capabilities, guint32, 0),
+	                                         sizeof (guint32),
+	                                         priv->capabilities->len,
+	                                         &cap_i,
+	                                         nm_cmp_uint32_p_with_data,
+	                                         NULL);
 	if (idx >= 0)
 		return;
 
@@ -6777,14 +6988,14 @@ constructed (GObject *object)
 	 */
 
 	priv->policy = nm_policy_new (self, priv->settings);
-	g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP4_DEVICE,
-	                  G_CALLBACK (policy_default_device_changed), self);
-	g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP6_DEVICE,
-	                  G_CALLBACK (policy_default_device_changed), self);
-	g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP4_DEVICE,
-	                  G_CALLBACK (policy_activating_device_changed), self);
-	g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP6_DEVICE,
-	                  G_CALLBACK (policy_activating_device_changed), self);
+	g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP4_AC,
+	                  G_CALLBACK (policy_default_ac_changed), self);
+	g_signal_connect (priv->policy, "notify::" NM_POLICY_DEFAULT_IP6_AC,
+	                  G_CALLBACK (policy_default_ac_changed), self);
+	g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP4_AC,
+	                  G_CALLBACK (policy_activating_ac_changed), self);
+	g_signal_connect (priv->policy, "notify::" NM_POLICY_ACTIVATING_IP6_AC,
+	                  G_CALLBACK (policy_activating_ac_changed), self);
 
 	priv->config = g_object_ref (nm_config_get ());
 	g_signal_connect (G_OBJECT (priv->config),
@@ -7122,8 +7333,8 @@ dispose (GObject *object)
 	}
 
 	if (priv->policy) {
-		g_signal_handlers_disconnect_by_func (priv->policy, policy_default_device_changed, self);
-		g_signal_handlers_disconnect_by_func (priv->policy, policy_activating_device_changed, self);
+		g_signal_handlers_disconnect_by_func (priv->policy, policy_default_ac_changed, self);
+		g_signal_handlers_disconnect_by_func (priv->policy, policy_activating_ac_changed, self);
 		g_clear_object (&priv->policy);
 	}
 
diff --git a/src/nm-manager.h b/src/nm-manager.h
index 5a462846..11cba1a5 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -98,10 +98,12 @@ const CList * nm_manager_get_active_connections        (NMManager *manager);
 	    iter = c_list_entry (iter->active_connections_lst.next, NMActiveConnection, active_connections_lst))
 
 NMSettingsConnection **nm_manager_get_activatable_connections (NMManager *manager,
-                                                               guint *out_len,
-                                                               gboolean sort);
+                                                               gboolean for_auto_activation,
+                                                               gboolean sort,
+                                                               guint *out_len);
 
-void          nm_manager_write_device_state (NMManager *manager);
+void          nm_manager_write_device_state_all (NMManager *manager);
+gboolean      nm_manager_write_device_state (NMManager *manager, NMDevice *device);
 
 /* Device handling */
 
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 125f2704..7f8c665c 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -56,10 +56,10 @@
 NM_GOBJECT_PROPERTIES_DEFINE (NMPolicy,
 	PROP_MANAGER,
 	PROP_SETTINGS,
-	PROP_DEFAULT_IP4_DEVICE,
-	PROP_DEFAULT_IP6_DEVICE,
-	PROP_ACTIVATING_IP4_DEVICE,
-	PROP_ACTIVATING_IP6_DEVICE,
+	PROP_DEFAULT_IP4_AC,
+	PROP_DEFAULT_IP6_AC,
+	PROP_ACTIVATING_IP4_AC,
+	PROP_ACTIVATING_IP6_AC,
 );
 
 typedef struct {
@@ -79,8 +79,8 @@ typedef struct {
 
 	NMHostnameManager *hostname_manager;
 
-	NMDevice *default_device4, *activating_device4;
-	NMDevice *default_device6, *activating_device6;
+	NMActiveConnection *default_ac4, *activating_ac4;
+	NMActiveConnection *default_ac6, *activating_ac6;
 
 	struct {
 		GInetAddress *addr;
@@ -146,6 +146,7 @@ _PRIV_TO_SELF (NMPolicyPrivate *priv)
 
 static void schedule_activate_all (NMPolicy *self);
 static void schedule_activate_check (NMPolicy *self, NMDevice *device);
+static NMDevice *get_default_device (NMPolicy *self, int addr_family);
 
 /*****************************************************************************/
 
@@ -367,43 +368,56 @@ device_ip6_subnet_needed (NMDevice *device,
 	_LOGD (LOGD_IP6, "ipv6-pd: %s needs a subnet",
 	       nm_device_get_iface (device));
 
-	if (!priv->default_device6) {
+	if (!priv->default_ac6) {
 		/* We request the prefixes when the default IPv6 device is set. */
 		_LOGI (LOGD_IP6, "ipv6-pd: no device to obtain a subnet to share on %s from",
 		       nm_device_get_iface (device));
 		return;
 	}
-	ip6_subnet_from_device (self, priv->default_device6, device);
-	nm_device_copy_ip6_dns_config (device, priv->default_device6);
+	ip6_subnet_from_device (self, get_default_device (self, AF_INET6), device);
+	nm_device_copy_ip6_dns_config (device, get_default_device (self, AF_INET6));
 }
 
 /*****************************************************************************/
 
 static NMDevice *
-get_best_ip_device (NMPolicy *self,
-                    int addr_family,
-                    gboolean fully_activated)
+get_default_device (NMPolicy *self, int addr_family)
+{
+	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+	NMActiveConnection *ac;
+
+	nm_assert_addr_family (addr_family);
+
+	ac = (addr_family == AF_INET) ? priv->default_ac4 : priv->default_ac6;
+
+	return ac ? nm_active_connection_get_device (ac) : NULL;
+}
+
+static NMActiveConnection *
+get_best_active_connection (NMPolicy *self,
+                            int addr_family,
+                            gboolean fully_activated)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	const CList *tmp_lst;
 	NMDevice *device;
-	NMDevice *best_device;
-	NMDevice *prev_device;
 	guint32 best_metric = G_MAXUINT32;
 	gboolean best_is_fully_activated = FALSE;
+	NMActiveConnection *best_ac, *prev_ac;
 
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	/* we prefer the current device in case of identical metric.
-	 * Hence, try that one first.*/
-	best_device = NULL;
-	prev_device =   addr_family == AF_INET
-	              ? (fully_activated ? priv->default_device4 : priv->activating_device4)
-	              : (fully_activated ? priv->default_device6 : priv->activating_device6);
+	/* we prefer the current AC in case of identical metric.
+	 * Hence, try that one first. */
+	prev_ac = addr_family == AF_INET
+	              ? (fully_activated ? priv->default_ac4 : priv->activating_ac4)
+	              : (fully_activated ? priv->default_ac6 : priv->activating_ac6);
+	best_ac = NULL;
 
 	nm_manager_for_each_device (priv->manager, device, tmp_lst) {
 		NMDeviceState state;
 		const NMPObject *r;
+		NMActiveConnection *ac;
 		NMConnection *connection;
 		guint32 metric;
 		gboolean is_fully_activated;
@@ -435,26 +449,29 @@ get_best_ip_device (NMPolicy *self,
 		} else
 			continue;
 
-		if (   !best_device
+		ac = (NMActiveConnection *) nm_device_get_act_request (device);
+		nm_assert (ac);
+
+		if (   !best_ac
 		    || (!best_is_fully_activated && is_fully_activated)
 		    || (   metric < best_metric
-		        || (metric == best_metric && device == prev_device))) {
-			best_device = device;
+		        || (metric == best_metric && ac == prev_ac))) {
+			best_ac = ac;
 			best_metric = metric;
 			best_is_fully_activated = is_fully_activated;
 		}
 	}
 
 	if (   !fully_activated
-	    && best_device
+	    && best_ac
 	    && best_is_fully_activated) {
-		/* There's only a best activating device if the best device
+		/* There's a best activating AC only if the best device
 		 * among all activating and already-activated devices is a
 		 * still-activating one. */
 		return NULL;
 	}
 
-	return best_device;
+	return best_ac;
 }
 
 static gboolean
@@ -668,6 +685,7 @@ update_system_hostname (NMPolicy *self, const char *msg)
 	gboolean external_hostname = FALSE;
 	const NMPlatformIP4Address *addr4;
 	const NMPlatformIP6Address *addr6;
+	NMDevice *device;
 
 	g_return_if_fail (self != NULL);
 
@@ -720,11 +738,11 @@ update_system_hostname (NMPolicy *self, const char *msg)
 		return;
 	}
 
-	if (priv->default_device4) {
+	if (priv->default_ac4) {
 		NMDhcp4Config *dhcp4_config;
 
 		/* Grab a hostname out of the device's DHCP4 config */
-		dhcp4_config = nm_device_get_dhcp4_config (priv->default_device4);
+		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");
 			if (dhcp_hostname && dhcp_hostname[0]) {
@@ -740,11 +758,11 @@ update_system_hostname (NMPolicy *self, const char *msg)
 		}
 	}
 
-	if (priv->default_device6) {
+	if (priv->default_ac6) {
 		NMDhcp6Config *dhcp6_config;
 
 		/* Grab a hostname out of the device's DHCP6 config */
-		dhcp6_config = nm_device_get_dhcp6_config (priv->default_device6);
+		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");
 			if (dhcp_hostname && dhcp_hostname[0]) {
@@ -779,7 +797,7 @@ update_system_hostname (NMPolicy *self, const char *msg)
 
 	priv->dhcp_hostname = FALSE;
 
-	if (!priv->default_device4 && !priv->default_device6) {
+	if (!priv->default_ac4 && !priv->default_ac6) {
 		/* No best device; fall back to the last hostname set externally
 		 * to NM or if there wasn't one, 'localhost.localdomain'
 		 */
@@ -798,8 +816,11 @@ update_system_hostname (NMPolicy *self, const char *msg)
 	/* No configured hostname, no automatically determined hostname, and no
 	 * bootup hostname. Start reverse DNS of the current IPv4 or IPv6 address.
 	 */
-	ip4_config = priv->default_device4 ? nm_device_get_ip4_config (priv->default_device4) : NULL;
-	ip6_config = priv->default_device6 ? nm_device_get_ip6_config (priv->default_device6) : NULL;
+	device = get_default_device (self, AF_INET);
+	ip4_config = device ? nm_device_get_ip4_config (device) : NULL;
+
+	device = get_default_device (self, AF_INET6);
+	ip6_config = device ? nm_device_get_ip6_config (device) : NULL;
 
 	if (   ip4_config
 	    && (addr4 = nm_ip4_config_get_first_address (ip4_config))) {
@@ -852,16 +873,19 @@ get_best_ip_config (NMPolicy *self,
                     NMVpnConnection **out_vpn)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	NMDevice *device;
-	gpointer conf;
+	gpointer conf, best_conf = NULL;
 	const CList *tmp_list;
 	NMActiveConnection *ac;
+	guint64 best_metric = G_MAXUINT64;
+	NMVpnConnection *best_vpn = NULL;
 
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
 		NMVpnConnection *candidate;
 		NMVpnConnectionState vpn_state;
+		const NMPObject *obj;
+		guint32 metric;
 
 		if (!NM_IS_VPN_CONNECTION (ac))
 			continue;
@@ -879,40 +903,45 @@ get_best_ip_config (NMPolicy *self,
 		if (!conf)
 			continue;
 
-		if (addr_family == AF_INET) {
-			if (!nm_ip4_config_best_default_route_get (conf))
-				continue;
-		} else {
-			if (!nm_ip6_config_best_default_route_get (conf))
-				continue;
+		if (addr_family == AF_INET)
+			obj = nm_ip4_config_best_default_route_get (conf);
+		else
+			obj = nm_ip6_config_best_default_route_get (conf);
+		if (!obj)
+			continue;
+
+		metric = NMP_OBJECT_CAST_IPX_ROUTE (obj)->rx.metric;
+		if (metric <= best_metric) {
+			best_metric = metric;
+			best_conf = conf;
+			best_vpn = candidate;
 		}
+	}
 
-		/* FIXME: in case of multiple VPN candidates, choose the one with the
-		 * best metric. */
+	if (best_metric != G_MAXUINT64) {
 		NM_SET_OUT (out_device, NULL);
-		NM_SET_OUT (out_vpn, candidate);
-		NM_SET_OUT (out_ac, ac);
-		NM_SET_OUT (out_ip_iface, nm_vpn_connection_get_ip_iface (candidate, TRUE));
-		return conf;
+		NM_SET_OUT (out_vpn, best_vpn);
+		NM_SET_OUT (out_ac, NM_ACTIVE_CONNECTION (best_vpn));
+		NM_SET_OUT (out_ip_iface, nm_vpn_connection_get_ip_iface (best_vpn, TRUE));
+		return best_conf;
 	}
 
-	device = get_best_ip_device (self, addr_family, TRUE);
-	if (device) {
-		NMActRequest *req;
+	ac = get_best_active_connection (self, addr_family, TRUE);
+	if (ac) {
+		NMDevice *device = nm_active_connection_get_device (ac);
+
+		nm_assert (device);
 
 		if (addr_family == AF_INET)
 			conf = nm_device_get_ip4_config (device);
 		else
 			conf = nm_device_get_ip6_config (device);
-		req = nm_device_get_act_request (device);
 
-		if (conf && req) {
-			NM_SET_OUT (out_device, device);
-			NM_SET_OUT (out_vpn, NULL);
-			NM_SET_OUT (out_ac, NM_ACTIVE_CONNECTION (req));
-			NM_SET_OUT (out_ip_iface, nm_device_get_ip_iface (device));
-			return conf;
-		}
+		NM_SET_OUT (out_device, device);
+		NM_SET_OUT (out_vpn, NULL);
+		NM_SET_OUT (out_ac, ac);
+		NM_SET_OUT (out_ip_iface, nm_device_get_ip_iface (device));
+		return conf;
 	}
 
 	NM_SET_OUT (out_device, NULL);
@@ -937,17 +966,17 @@ update_ip4_routing (NMPolicy *self, gboolean force_update)
 	 * so we can get (vpn != NULL && best == NULL).
 	 */
 	if (!get_best_ip_config (self, AF_INET, &ip_iface, &best_ac, &best, &vpn)) {
-		if (nm_clear_g_object (&priv->default_device4)) {
-			_LOGt (LOGD_DNS, "set-default-device-4: %p", NULL);
-			_notify (self, PROP_DEFAULT_IP4_DEVICE);
+		if (nm_clear_g_object (&priv->default_ac4)) {
+			_LOGt (LOGD_DNS, "set-default-ac-4: %p", NULL);
+			_notify (self, PROP_DEFAULT_IP4_AC);
 		}
 		return;
 	}
 	g_assert ((best || vpn) && best_ac);
 
 	if (   !force_update
-	    && best
-	    && best == priv->default_device4)
+	    && best_ac
+	    && best_ac == priv->default_ac4)
 		return;
 
 	if (best) {
@@ -959,19 +988,16 @@ update_ip4_routing (NMPolicy *self, gboolean force_update)
 		}
 	}
 
-	if (vpn)
-		best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
-
 	update_default_ac (self, AF_INET, best_ac);
 
-	if (!nm_g_object_ref_set (&priv->default_device4, best))
+	if (!nm_g_object_ref_set (&priv->default_ac4, best_ac))
 		return;
-	_LOGt (LOGD_DNS, "set-default-device-4: %p", priv->default_device4);
+	_LOGt (LOGD_DNS, "set-default-ac-4: %p", priv->default_ac4);
 
 	_LOGI (LOGD_CORE, "set '%s' (%s) as default for IPv4 routing and DNS",
 	       nm_connection_get_id (nm_active_connection_get_applied_connection (best_ac)),
 	       ip_iface);
-	_notify (self, PROP_DEFAULT_IP4_DEVICE);
+	_notify (self, PROP_DEFAULT_IP4_AC);
 }
 
 static void
@@ -985,7 +1011,7 @@ update_ip6_dns_delegation (NMPolicy *self)
 	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, priv->default_device6);
+			nm_device_copy_ip6_dns_config (device, get_default_device (self, AF_INET6));
 	}
 }
 
@@ -1001,7 +1027,7 @@ update_ip6_prefix_delegation (NMPolicy *self)
 	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))
-			ip6_subnet_from_device (self, priv->default_device6, device);
+			ip6_subnet_from_device (self, get_default_device (self, AF_INET6), device);
 	}
 }
 
@@ -1020,17 +1046,17 @@ update_ip6_routing (NMPolicy *self, gboolean force_update)
 	 * so we can get (vpn != NULL && best == NULL).
 	 */
 	if (!get_best_ip_config (self, AF_INET6, &ip_iface, &best_ac, &best, &vpn)) {
-		if (nm_clear_g_object (&priv->default_device6)) {
-			_LOGt (LOGD_DNS, "set-default-device-6: %p", NULL);
-			_notify (self, PROP_DEFAULT_IP6_DEVICE);
+		if (nm_clear_g_object (&priv->default_ac6)) {
+			_LOGt (LOGD_DNS, "set-default-ac-6: %p", NULL);
+			_notify (self, PROP_DEFAULT_IP6_AC);
 		}
 		return;
 	}
 	g_assert ((best || vpn) && best_ac);
 
 	if (   !force_update
-	    && best
-	    && best == priv->default_device6)
+	    && best_ac
+	    && best_ac == priv->default_ac6)
 		return;
 
 	if (best) {
@@ -1042,21 +1068,18 @@ update_ip6_routing (NMPolicy *self, gboolean force_update)
 		}
 	}
 
-	if (vpn)
-		best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
-
 	update_default_ac (self, AF_INET6, best_ac);
 
-	if (!nm_g_object_ref_set (&priv->default_device6, best))
+	if (!nm_g_object_ref_set (&priv->default_ac6, best_ac))
 		return;
-	_LOGt (LOGD_DNS, "set-default-device-6: %p", priv->default_device6);
+	_LOGt (LOGD_DNS, "set-default-ac-6: %p", priv->default_ac6);
 
 	update_ip6_prefix_delegation (self);
 
 	_LOGI (LOGD_CORE, "set '%s' (%s) as default for IPv6 routing and DNS",
 	       nm_connection_get_id (nm_active_connection_get_applied_connection (best_ac)),
 	       ip_iface);
-	_notify (self, PROP_DEFAULT_IP6_DEVICE);
+	_notify (self, PROP_DEFAULT_IP6_AC);
 }
 
 static void
@@ -1104,23 +1127,23 @@ update_routing_and_dns (NMPolicy *self, gboolean force_update)
 }
 
 static void
-check_activating_devices (NMPolicy *self)
+check_activating_active_connections (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	NMDevice *best4, *best6 = NULL;
+	NMActiveConnection *best4, *best6 = NULL;
 
-	best4 = get_best_ip_device (self, AF_INET, FALSE);
-	best6 = get_best_ip_device (self, AF_INET6, FALSE);
+	best4 = get_best_active_connection (self, AF_INET, FALSE);
+	best6 = get_best_active_connection (self, AF_INET6, FALSE);
 
 	g_object_freeze_notify (G_OBJECT (self));
 
-	if (nm_g_object_ref_set (&priv->activating_device4, best4)) {
-		_LOGt (LOGD_DNS, "set-activating-device-4: %p", priv->activating_device4);
-		_notify (self, PROP_ACTIVATING_IP4_DEVICE);
+	if (nm_g_object_ref_set (&priv->activating_ac4, best4)) {
+		_LOGt (LOGD_DNS, "set-activating-ac-4: %p", priv->activating_ac4);
+		_notify (self, PROP_ACTIVATING_IP4_AC);
 	}
-	if (nm_g_object_ref_set (&priv->activating_device6, best6)) {
-		_LOGt (LOGD_DNS, "set-activating-device-6: %p", priv->activating_device6);
-		_notify (self, PROP_ACTIVATING_IP6_DEVICE);
+	if (nm_g_object_ref_set (&priv->activating_ac6, best6)) {
+		_LOGt (LOGD_DNS, "set-activating-ac-6: %p", priv->activating_ac6);
+		_notify (self, PROP_ACTIVATING_IP6_AC);
 	}
 
 	g_object_thaw_notify (G_OBJECT (self));
@@ -1212,30 +1235,33 @@ auto_activate_device (NMPolicy *self,
 	if (!nm_device_autoconnect_allowed (device))
 		return;
 
-	connections = nm_manager_get_activatable_connections (priv->manager, &len, TRUE);
+	connections = nm_manager_get_activatable_connections (priv->manager, TRUE, TRUE, &len);
 	if (!connections[0])
 		return;
 
 	/* Find the first connection that should be auto-activated */
 	best_connection = NULL;
 	for (i = 0; i < len; i++) {
-		NMSettingsConnection *candidate = NM_SETTINGS_CONNECTION (connections[i]);
+		NMSettingsConnection *candidate = connections[i];
+		NMConnection *cand_conn;
 		NMSettingConnection *s_con;
 		const char *permission;
 
 		if (nm_settings_connection_autoconnect_is_blocked (candidate))
 			continue;
 
-		s_con = nm_connection_get_setting_connection (NM_CONNECTION (candidate));
+		cand_conn = nm_settings_connection_get_connection (candidate);
+
+		s_con = nm_connection_get_setting_connection (cand_conn);
 		if (!nm_setting_connection_get_autoconnect (s_con))
 			continue;
 
-		permission = nm_utils_get_shared_wifi_permission (NM_CONNECTION (candidate));
+		permission = nm_utils_get_shared_wifi_permission (cand_conn);
 		if (   permission
 		    && !nm_settings_connection_check_permission (candidate, permission))
 			continue;
 
-		if (nm_device_can_auto_connect (device, (NMConnection *) candidate, &specific_object)) {
+		if (nm_device_can_auto_connect (device, candidate, &specific_object)) {
 			best_connection = candidate;
 			break;
 		}
@@ -1418,34 +1444,36 @@ reset_autoconnect_all (NMPolicy *self,
 
 	connections = nm_settings_get_connections (priv->settings, NULL);
 	for (i = 0; connections[i]; i++) {
-		NMSettingsConnection *connection = connections[i];
+		NMSettingsConnection *sett_conn = connections[i];
 
 		if (   device
-		    && !nm_device_check_connection_compatible (device, NM_CONNECTION (connection)))
+		    && !nm_device_check_connection_compatible (device,
+		                                               nm_settings_connection_get_connection (sett_conn),
+		                                               NULL))
 			continue;
 
 		if (only_no_secrets) {
 			/* we only reset the no-secrets blocked flag. */
-			if (nm_settings_connection_autoconnect_blocked_reason_set (connection,
+			if (nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
 			                                                           NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
 			                                                           FALSE)) {
 				/* maybe the connection is still blocked afterwards for other reasons
 				 * and in the larger picture nothing changed. But it's too complicated
 				 * to find out exactly. Just assume, something changed to be sure. */
-				if (!nm_settings_connection_autoconnect_is_blocked (connection))
+				if (!nm_settings_connection_autoconnect_is_blocked (sett_conn))
 					changed = TRUE;
 			}
 		} else {
 			/* we reset the tries-count and any blocked-reason */
-			if (nm_settings_connection_autoconnect_retries_get (connection) == 0)
+			if (nm_settings_connection_autoconnect_retries_get (sett_conn) == 0)
 				changed = TRUE;
-			nm_settings_connection_autoconnect_retries_reset (connection);
+			nm_settings_connection_autoconnect_retries_reset (sett_conn);
 
-			if (nm_settings_connection_autoconnect_blocked_reason_set (connection,
+			if (nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
 			                                                             NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL
 			                                                           & ~NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
 			                                                           FALSE)) {
-				if (!nm_settings_connection_autoconnect_is_blocked (connection))
+				if (!nm_settings_connection_autoconnect_is_blocked (sett_conn))
 					changed = TRUE;
 			}
 		}
@@ -1566,7 +1594,9 @@ static void
 activate_slave_connections (NMPolicy *self, NMDevice *device)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	const char *master_device, *master_uuid_settings = NULL, *master_uuid_applied = NULL;
+	const char *master_device;
+	const char *master_uuid_settings = NULL;
+	const char *master_uuid_applied = NULL;
 	guint i;
 	NMActRequest *req;
 	gboolean internal_activation = FALSE;
@@ -1578,16 +1608,18 @@ activate_slave_connections (NMPolicy *self, NMDevice *device)
 
 	req = nm_device_get_act_request (device);
 	if (req) {
-		NMConnection *con;
+		NMConnection *connection;
+		NMSettingsConnection *sett_conn;
 		NMAuthSubject *subject;
 
-		con = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (req));
-		if (con)
-			master_uuid_applied = nm_connection_get_uuid (con);
-		con = NM_CONNECTION (nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (req)));
-		if (con) {
-			master_uuid_settings = nm_connection_get_uuid (con);
-			if (!g_strcmp0 (master_uuid_settings, master_uuid_applied))
+		connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (req));
+		if (connection)
+			master_uuid_applied = nm_connection_get_uuid (connection);
+
+		sett_conn = nm_active_connection_get_settings_connection (NM_ACTIVE_CONNECTION (req));
+		if (sett_conn) {
+			master_uuid_settings = nm_settings_connection_get_uuid (sett_conn);
+			if (nm_streq0 (master_uuid_settings, master_uuid_applied))
 				master_uuid_settings = NULL;
 		}
 
@@ -1598,11 +1630,11 @@ activate_slave_connections (NMPolicy *self, NMDevice *device)
 	changed = FALSE;
 	connections = nm_settings_get_connections (priv->settings, NULL);
 	for (i = 0; connections[i]; i++) {
-		NMSettingsConnection *connection = connections[i];
+		NMSettingsConnection *sett_conn = connections[i];
 		NMSettingConnection *s_slave_con;
 		const char *slave_master;
 
-		s_slave_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+		s_slave_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
 		slave_master = nm_setting_connection_get_master (s_slave_con);
 		if (!slave_master)
 			continue;
@@ -1612,14 +1644,14 @@ activate_slave_connections (NMPolicy *self, NMDevice *device)
 			continue;
 
 		if (!internal_activation) {
-			if (nm_settings_connection_autoconnect_retries_get (connection) == 0)
+			if (nm_settings_connection_autoconnect_retries_get (sett_conn) == 0)
 				changed = TRUE;
-			nm_settings_connection_autoconnect_retries_reset (connection);
+			nm_settings_connection_autoconnect_retries_reset (sett_conn);
 		}
-		if (nm_settings_connection_autoconnect_blocked_reason_set (connection,
+		if (nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
 		                                                           NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
 		                                                           FALSE)) {
-			if (!nm_settings_connection_autoconnect_is_blocked (connection))
+			if (!nm_settings_connection_autoconnect_is_blocked (sett_conn))
 				changed = TRUE;
 		}
 	}
@@ -1635,7 +1667,6 @@ activate_secondary_connections (NMPolicy *self,
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	NMSettingConnection *s_con;
-	NMSettingsConnection *settings_con;
 	NMActiveConnection *ac;
 	PendingSecondaryData *secondary_data;
 	GSList *secondary_ac_list = NULL;
@@ -1644,22 +1675,25 @@ activate_secondary_connections (NMPolicy *self,
 	gboolean success = TRUE;
 
 	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
+	nm_assert (s_con);
 
 	for (i = 0; i < nm_setting_connection_get_num_secondaries (s_con); i++) {
+		NMSettingsConnection *sett_conn;
 		const char *sec_uuid = nm_setting_connection_get_secondary (s_con, i);
 		NMActRequest *req;
 
-		settings_con = nm_settings_get_connection_by_uuid (priv->settings, sec_uuid);
-		if (!settings_con) {
+		sett_conn = nm_settings_get_connection_by_uuid (priv->settings, sec_uuid);
+		if (!sett_conn) {
 			_LOGW (LOGD_DEVICE, "secondary connection '%s' auto-activation failed: The connection doesn't exist.",
 			       sec_uuid);
 			success = FALSE;
 			break;
 		}
-		if (!nm_connection_is_type (NM_CONNECTION (settings_con), NM_SETTING_VPN_SETTING_NAME)) {
+
+		if (!nm_connection_is_type (nm_settings_connection_get_connection (sett_conn),
+		                            NM_SETTING_VPN_SETTING_NAME)) {
 			_LOGW (LOGD_DEVICE, "secondary connection '%s (%s)' auto-activation failed: The connection is not a VPN.",
-			       nm_settings_connection_get_id (settings_con), sec_uuid);
+			       nm_settings_connection_get_id (sett_conn), sec_uuid);
 			success = FALSE;
 			break;
 		}
@@ -1668,10 +1702,10 @@ activate_secondary_connections (NMPolicy *self,
 		g_assert (req);
 
 		_LOGD (LOGD_DEVICE, "activating secondary connection '%s (%s)' for base connection '%s (%s)'",
-		       nm_settings_connection_get_id (settings_con), sec_uuid,
+		       nm_settings_connection_get_id (sett_conn), sec_uuid,
 		       nm_connection_get_id (connection), nm_connection_get_uuid (connection));
 		ac = nm_manager_activate_connection (priv->manager,
-		                                     settings_con,
+		                                     sett_conn,
 		                                     NULL,
 		                                     nm_dbus_object_get_path (NM_DBUS_OBJECT (req)),
 		                                     device,
@@ -1683,7 +1717,7 @@ activate_secondary_connections (NMPolicy *self,
 			secondary_ac_list = g_slist_append (secondary_ac_list, g_object_ref (ac));
 		else {
 			_LOGW (LOGD_DEVICE, "secondary connection '%s (%s)' auto-activation failed: (%d) %s",
-			       nm_settings_connection_get_id (settings_con), sec_uuid,
+			       nm_settings_connection_get_id (sett_conn), sec_uuid,
 			       error->code,
 			       error->message);
 			g_clear_error (&error);
@@ -1711,7 +1745,7 @@ device_state_changed (NMDevice *device,
 	NMPolicyPrivate *priv = user_data;
 	NMPolicy *self = _PRIV_TO_SELF (priv);
 	NMActiveConnection *ac;
-	NMSettingsConnection *connection = nm_device_get_settings_connection (device);
+	NMSettingsConnection *sett_conn = nm_device_get_settings_connection (device);
 	NMIP4Config *ip4_config;
 	NMIP6Config *ip6_config;
 	NMSettingConnection *s_con = NULL;
@@ -1729,8 +1763,8 @@ device_state_changed (NMDevice *device,
 		/* Block autoconnect of the just-failed connection for situations
 		 * where a retry attempt would just fail again.
 		 */
-		if (connection) {
-			nm_settings_connection_autoconnect_blocked_reason_set (connection,
+		if (sett_conn) {
+			nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
 			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
 			                                                       TRUE);
 		}
@@ -1744,7 +1778,7 @@ device_state_changed (NMDevice *device,
 		/* Mark the connection invalid if it failed during activation so that
 		 * it doesn't get automatically chosen over and over and over again.
 		 */
-		if (   connection
+		if (   sett_conn
 		    && old_state >= NM_DEVICE_STATE_PREPARE
 		    && old_state <= NM_DEVICE_STATE_ACTIVATED) {
 			gboolean block_no_secrets = FALSE;
@@ -1765,7 +1799,7 @@ device_state_changed (NMDevice *device,
 				 * That can happen when nm_settings_connection_get_secrets() fails early without actually
 				 * consulting any agents.
 				 */
-				con_v = nm_settings_connection_get_last_secret_agent_version_id (connection);
+				con_v = nm_settings_connection_get_last_secret_agent_version_id (sett_conn);
 				if (   con_v == 0
 				    || con_v == nm_agent_manager_get_agent_version_id (priv->agent_mgr))
 					block_no_secrets = TRUE;
@@ -1773,33 +1807,35 @@ device_state_changed (NMDevice *device,
 
 			if (block_no_secrets) {
 				_LOGD (LOGD_DEVICE, "connection '%s' now blocked from autoconnect due to no secrets",
-				       nm_settings_connection_get_id (connection));
-				nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, TRUE);
+				       nm_settings_connection_get_id (sett_conn));
+				nm_settings_connection_autoconnect_blocked_reason_set (sett_conn, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, TRUE);
 			} else {
-				tries = nm_settings_connection_autoconnect_retries_get (connection);
+				tries = nm_settings_connection_autoconnect_retries_get (sett_conn);
 				if (tries > 0) {
 					_LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; %d tries left",
-					       nm_settings_connection_get_id (connection), tries - 1);
-					_connection_autoconnect_retries_set (self, connection, tries - 1);
+					       nm_settings_connection_get_id (sett_conn), tries - 1);
+					_connection_autoconnect_retries_set (self, sett_conn, tries - 1);
 				} else if (tries != 0) {
 					_LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; infinite tries left",
-					       nm_settings_connection_get_id (connection));
+					       nm_settings_connection_get_id (sett_conn));
 				}
 			}
 
-			nm_connection_clear_secrets (NM_CONNECTION (connection));
+			/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+			nm_connection_clear_secrets (nm_settings_connection_get_connection (sett_conn));
 		}
 		break;
 	case NM_DEVICE_STATE_ACTIVATED:
-		if (connection) {
+		if (sett_conn) {
 			/* Reset auto retries back to default since connection was successful */
-			nm_settings_connection_autoconnect_retries_reset (connection);
+			nm_settings_connection_autoconnect_retries_reset (sett_conn);
 
 			/* And clear secrets so they will always be requested from the
 			 * settings service when the next connection is made.
 			 */
 
-			nm_connection_clear_secrets (NM_CONNECTION (connection));
+			/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+			nm_connection_clear_secrets (nm_settings_connection_get_connection (sett_conn));
 		}
 
 		/* Add device's new IPv4 and IPv6 configs to DNS */
@@ -1823,7 +1859,7 @@ device_state_changed (NMDevice *device,
 			update_routing_and_dns (self, FALSE);
 		break;
 	case NM_DEVICE_STATE_DEACTIVATING:
-		if (connection) {
+		if (sett_conn) {
 			NMSettingsAutoconnectBlockedReason blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE;
 
 			switch (nm_device_state_reason_check (reason)) {
@@ -1838,10 +1874,10 @@ device_state_changed (NMDevice *device,
 			}
 			if (blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) {
 				_LOGD (LOGD_DEVICE, "blocking autoconnect of connection '%s': %s",
-				       nm_settings_connection_get_id (connection),
+				       nm_settings_connection_get_id (sett_conn),
 				       NM_UTILS_LOOKUP_STR (nm_device_state_reason_to_str,
 				                            nm_device_state_reason_check (reason)));
-				nm_settings_connection_autoconnect_blocked_reason_set (connection, blocked_reason, TRUE);
+				nm_settings_connection_autoconnect_blocked_reason_set (sett_conn, blocked_reason, TRUE);
 			}
 		}
 		ip6_remove_device_prefix_delegations (self, device);
@@ -1877,20 +1913,25 @@ device_state_changed (NMDevice *device,
 		break;
 	case NM_DEVICE_STATE_IP_CONFIG:
 		/* We must have secrets if we got here. */
-		if (connection)
-			nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL, FALSE);
+		if (sett_conn)
+			nm_settings_connection_autoconnect_blocked_reason_set (sett_conn, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL, FALSE);
 		break;
 	case NM_DEVICE_STATE_SECONDARIES:
-		if (connection)
-			s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
-		if (s_con && nm_setting_connection_get_num_secondaries (s_con) > 0) {
+		if (sett_conn)
+			s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
+		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);
 
 			/* Activate secondary (VPN) connections */
-			if (!activate_secondary_connections (self, NM_CONNECTION (connection), device))
-				nm_device_queue_state (device, NM_DEVICE_STATE_FAILED,
+			if (!activate_secondary_connections (self,
+			                                     nm_settings_connection_get_connection (sett_conn),
+			                                     device)) {
+				nm_device_queue_state (device,
+				                       NM_DEVICE_STATE_FAILED,
 				                       NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED);
+			}
 		} else
 			nm_device_queue_state (device, NM_DEVICE_STATE_ACTIVATED,
 			                       NM_DEVICE_STATE_REASON_NONE);
@@ -1900,7 +1941,7 @@ device_state_changed (NMDevice *device,
 		break;
 	}
 
-	check_activating_devices (self);
+	check_activating_active_connections (self);
 }
 
 static void
@@ -2382,28 +2423,28 @@ secret_agent_registered (NMSettings *settings,
 		schedule_activate_all (self);
 }
 
-NMDevice *
-nm_policy_get_default_ip4_device (NMPolicy *self)
+NMActiveConnection *
+nm_policy_get_default_ip4_ac (NMPolicy *self)
 {
-	return NM_POLICY_GET_PRIVATE (self)->default_device4;
+	return NM_POLICY_GET_PRIVATE (self)->default_ac4;
 }
 
-NMDevice *
-nm_policy_get_default_ip6_device (NMPolicy *self)
+NMActiveConnection *
+nm_policy_get_default_ip6_ac (NMPolicy *self)
 {
-	return NM_POLICY_GET_PRIVATE (self)->default_device6;
+	return NM_POLICY_GET_PRIVATE (self)->default_ac6;
 }
 
-NMDevice *
-nm_policy_get_activating_ip4_device (NMPolicy *self)
+NMActiveConnection *
+nm_policy_get_activating_ip4_ac (NMPolicy *self)
 {
-	return NM_POLICY_GET_PRIVATE (self)->activating_device4;
+	return NM_POLICY_GET_PRIVATE (self)->activating_ac4;
 }
 
-NMDevice *
-nm_policy_get_activating_ip6_device (NMPolicy *self)
+NMActiveConnection *
+nm_policy_get_activating_ip6_ac (NMPolicy *self)
 {
-	return NM_POLICY_GET_PRIVATE (self)->activating_device6;
+	return NM_POLICY_GET_PRIVATE (self)->activating_ac6;
 }
 
 /*****************************************************************************/
@@ -2425,17 +2466,17 @@ get_property (GObject *object, guint prop_id,
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 
 	switch (prop_id) {
-	case PROP_DEFAULT_IP4_DEVICE:
-		g_value_set_object (value, priv->default_device4);
+	case PROP_DEFAULT_IP4_AC:
+		g_value_set_object (value, priv->default_ac4);
 		break;
-	case PROP_DEFAULT_IP6_DEVICE:
-		g_value_set_object (value, priv->default_device6);
+	case PROP_DEFAULT_IP6_AC:
+		g_value_set_object (value, priv->default_ac6);
 		break;
-	case PROP_ACTIVATING_IP4_DEVICE:
-		g_value_set_object (value, priv->activating_device4);
+	case PROP_ACTIVATING_IP4_AC:
+		g_value_set_object (value, priv->activating_ac4);
 		break;
-	case PROP_ACTIVATING_IP6_DEVICE:
-		g_value_set_object (value, priv->activating_device6);
+	case PROP_ACTIVATING_IP6_AC:
+		g_value_set_object (value, priv->activating_ac6);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2576,10 +2617,10 @@ dispose (GObject *object)
 	g_clear_object (&priv->lookup.addr);
 	g_clear_object (&priv->lookup.resolver);
 
-	nm_clear_g_object (&priv->default_device4);
-	nm_clear_g_object (&priv->default_device6);
-	nm_clear_g_object (&priv->activating_device4);
-	nm_clear_g_object (&priv->activating_device6);
+	nm_clear_g_object (&priv->default_ac4);
+	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);
 
 	c_list_for_each_entry_safe (data, data_safe, &priv->pending_activation_checks, pending_lst)
@@ -2685,23 +2726,23 @@ nm_policy_class_init (NMPolicyClass *policy_class)
 	                         G_PARAM_WRITABLE |
 	                         G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_DEFAULT_IP4_DEVICE] =
-	    g_param_spec_object (NM_POLICY_DEFAULT_IP4_DEVICE, "", "",
-	                         NM_TYPE_DEVICE,
+	obj_properties[PROP_DEFAULT_IP4_AC] =
+	    g_param_spec_object (NM_POLICY_DEFAULT_IP4_AC, "", "",
+	                         NM_TYPE_ACTIVE_CONNECTION,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_DEFAULT_IP6_DEVICE] =
-	    g_param_spec_object (NM_POLICY_DEFAULT_IP6_DEVICE, "", "",
+	obj_properties[PROP_DEFAULT_IP6_AC] =
+	    g_param_spec_object (NM_POLICY_DEFAULT_IP6_AC, "", "",
 	                         NM_TYPE_DEVICE,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_ACTIVATING_IP4_DEVICE] =
-	    g_param_spec_object (NM_POLICY_ACTIVATING_IP4_DEVICE, "", "",
+	obj_properties[PROP_ACTIVATING_IP4_AC] =
+	    g_param_spec_object (NM_POLICY_ACTIVATING_IP4_AC, "", "",
 	                         NM_TYPE_DEVICE,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_ACTIVATING_IP6_DEVICE] =
-	    g_param_spec_object (NM_POLICY_ACTIVATING_IP6_DEVICE, "", "",
+	obj_properties[PROP_ACTIVATING_IP6_AC] =
+	    g_param_spec_object (NM_POLICY_ACTIVATING_IP6_AC, "", "",
 	                         NM_TYPE_DEVICE,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
diff --git a/src/nm-policy.h b/src/nm-policy.h
index 2c96e6d0..916ede00 100644
--- a/src/nm-policy.h
+++ b/src/nm-policy.h
@@ -31,10 +31,10 @@
 
 #define NM_POLICY_MANAGER               "manager"
 #define NM_POLICY_SETTINGS              "settings"
-#define NM_POLICY_DEFAULT_IP4_DEVICE    "default-ip4-device"
-#define NM_POLICY_DEFAULT_IP6_DEVICE    "default-ip6-device"
-#define NM_POLICY_ACTIVATING_IP4_DEVICE "activating-ip4-device"
-#define NM_POLICY_ACTIVATING_IP6_DEVICE "activating-ip6-device"
+#define NM_POLICY_DEFAULT_IP4_AC        "default-ip4-ac"
+#define NM_POLICY_DEFAULT_IP6_AC        "default-ip6-ac"
+#define NM_POLICY_ACTIVATING_IP4_AC     "activating-ip4-ac"
+#define NM_POLICY_ACTIVATING_IP6_AC     "activating-ip6-ac"
 
 typedef struct _NMPolicyClass NMPolicyClass;
 
@@ -42,10 +42,10 @@ GType nm_policy_get_type (void);
 
 NMPolicy *nm_policy_new (NMManager *manager, NMSettings *settings);
 
-NMDevice *nm_policy_get_default_ip4_device (NMPolicy *policy);
-NMDevice *nm_policy_get_default_ip6_device (NMPolicy *policy);
-NMDevice *nm_policy_get_activating_ip4_device (NMPolicy *policy);
-NMDevice *nm_policy_get_activating_ip6_device (NMPolicy *policy);
+NMActiveConnection *nm_policy_get_default_ip4_ac (NMPolicy *policy);
+NMActiveConnection *nm_policy_get_default_ip6_ac (NMPolicy *policy);
+NMActiveConnection *nm_policy_get_activating_ip4_ac (NMPolicy *policy);
+NMActiveConnection *nm_policy_get_activating_ip6_ac (NMPolicy *policy);
 
 /**
  * NMPolicyHostnameMode
diff --git a/src/nm-rfkill-manager.c b/src/nm-rfkill-manager.c
index b2909e9d..a7bc694a 100644
--- a/src/nm-rfkill-manager.c
+++ b/src/nm-rfkill-manager.c
@@ -65,7 +65,7 @@ typedef struct {
 	char *path;
 	char *driver;
 	RfKillType rtype;
-	gint state;
+	int state;
 	gboolean platform;
 } Killswitch;
 
@@ -158,7 +158,7 @@ killswitch_destroy (Killswitch *ks)
 }
 
 static RfKillState
-sysfs_state_to_nm_state (gint sysfs_state)
+sysfs_state_to_nm_state (int sysfs_state)
 {
 	switch (sysfs_state) {
 	case 0:
diff --git a/src/nm-session-monitor.c b/src/nm-session-monitor.c
index 23d41d7e..b67c537c 100644
--- a/src/nm-session-monitor.c
+++ b/src/nm-session-monitor.c
@@ -28,24 +28,26 @@
 #include <string.h>
 #include <sys/stat.h>
 
-#if defined (SESSION_TRACKING_SYSTEMD) && defined (SESSION_TRACKING_ELOGIND)
+#if SESSION_TRACKING_SYSTEMD && SESSION_TRACKING_ELOGIND
 #error Cannot build both systemd-logind and elogind support
 #endif
 
-#ifdef SESSION_TRACKING_SYSTEMD
+#if SESSION_TRACKING_SYSTEMD
 #include <systemd/sd-login.h>
 #define LOGIND_NAME "systemd-logind"
 #endif
 
-#ifdef SESSION_TRACKING_ELOGIND
+#if SESSION_TRACKING_ELOGIND
 #include <elogind/sd-login.h>
 #define LOGIND_NAME "elogind"
-/* Re-Use SESSION_TRACKING_SYSTEMD as elogind substitutes systemd-login */
-#define SESSION_TRACKING_SYSTEMD 1
 #endif
 
 #include "NetworkManagerUtils.h"
 
+#define SESSION_TRACKING_XLOGIND (SESSION_TRACKING_SYSTEMD || SESSION_TRACKING_ELOGIND)
+
+#define CKDB_PATH "/var/run/ConsoleKit/database"
+
 /*****************************************************************************/
 
 enum {
@@ -58,14 +60,14 @@ static guint signals[LAST_SIGNAL] = { 0 };
 struct _NMSessionMonitor {
 	GObject parent;
 
-#ifdef SESSION_TRACKING_SYSTEMD
+#if SESSION_TRACKING_XLOGIND
 	struct {
 		sd_login_monitor *monitor;
 		guint watch;
 	} sd;
 #endif
 
-#ifdef SESSION_TRACKING_CONSOLEKIT
+#if SESSION_TRACKING_CONSOLEKIT
 	struct {
 		GFileMonitor *monitor;
 		GHashTable *cache;
@@ -85,7 +87,7 @@ G_DEFINE_TYPE (NMSessionMonitor, nm_session_monitor, G_TYPE_OBJECT);
 
 /*****************************************************************************/
 
-#ifdef SESSION_TRACKING_SYSTEMD
+#if SESSION_TRACKING_XLOGIND
 static gboolean
 st_sd_session_exists (NMSessionMonitor *monitor, uid_t uid, gboolean active)
 {
@@ -143,11 +145,11 @@ st_sd_finalize (NMSessionMonitor *monitor)
 	}
 	nm_clear_g_source (&monitor->sd.watch);
 }
-#endif /* SESSION_TRACKING_SYSTEMD */
+#endif /* SESSION_TRACKING_XLOGIND */
 
 /*****************************************************************************/
 
-#ifdef SESSION_TRACKING_CONSOLEKIT
+#if SESSION_TRACKING_CONSOLEKIT
 typedef struct {
 	gboolean active;
 } CkSession;
@@ -186,7 +188,7 @@ ck_load_cache (GHashTable *cache)
 		if (error)
 			goto out;
 
-		g_hash_table_insert (cache, GUINT_TO_POINTER (uid), g_memdup (&session, sizeof session));
+		g_hash_table_insert (cache, GUINT_TO_POINTER (uid), nm_memdup (&session, sizeof session));
 	}
 
 	finished = TRUE;
@@ -352,12 +354,12 @@ nm_session_monitor_session_exists (NMSessionMonitor *self,
 {
 	g_return_val_if_fail (NM_IS_SESSION_MONITOR (self), FALSE);
 
-#ifdef SESSION_TRACKING_SYSTEMD
+#if SESSION_TRACKING_XLOGIND
 	if (st_sd_session_exists (self, uid, active))
 		return TRUE;
 #endif
 
-#ifdef SESSION_TRACKING_CONSOLEKIT
+#if SESSION_TRACKING_CONSOLEKIT
 	if (ck_session_exists (self, uid, active))
 		return TRUE;
 #endif
@@ -370,12 +372,12 @@ nm_session_monitor_session_exists (NMSessionMonitor *self,
 static void
 nm_session_monitor_init (NMSessionMonitor *monitor)
 {
-#ifdef SESSION_TRACKING_SYSTEMD
+#if SESSION_TRACKING_XLOGIND
 	st_sd_init (monitor);
 	_LOGD ("using "LOGIND_NAME" session tracking");
 #endif
 
-#ifdef SESSION_TRACKING_CONSOLEKIT
+#if SESSION_TRACKING_CONSOLEKIT
 	ck_init (monitor);
 	_LOGD ("using ConsoleKit session tracking");
 #endif
@@ -384,11 +386,11 @@ nm_session_monitor_init (NMSessionMonitor *monitor)
 static void
 finalize (GObject *object)
 {
-#ifdef SESSION_TRACKING_SYSTEMD
+#if SESSION_TRACKING_XLOGIND
 	st_sd_finalize (NM_SESSION_MONITOR (object));
 #endif
 
-#ifdef SESSION_TRACKING_CONSOLEKIT
+#if SESSION_TRACKING_CONSOLEKIT
 	ck_finalize (NM_SESSION_MONITOR (object));
 #endif
 
diff --git a/src/nm-sleep-monitor.c b/src/nm-sleep-monitor.c
index a7e3a7c3..54d75773 100644
--- a/src/nm-sleep-monitor.c
+++ b/src/nm-sleep-monitor.c
@@ -84,7 +84,7 @@ struct _NMSleepMonitor {
 	/* used both during construction of proxy and during Inhibit call. */
 	GCancellable *cancellable;
 
-	gint inhibit_fd;
+	int inhibit_fd;
 	GSList *handles_active;
 	GSList *handles_stale;
 
diff --git a/src/nm-test-utils-core.h b/src/nm-test-utils-core.h
index c76f510f..5e89cb39 100644
--- a/src/nm-test-utils-core.h
+++ b/src/nm-test-utils-core.h
@@ -216,8 +216,8 @@ nmtst_platform_ip4_routes_equal (const NMPlatformIP4Route *a, const NMPlatformIP
 	g_assert (b);
 
 	if (ignore_order) {
-		a = c_a = g_memdup (a, sizeof (NMPlatformIP4Route) * len);
-		b = c_b = g_memdup (b, sizeof (NMPlatformIP4Route) * len);
+		a = c_a = nm_memdup (a, sizeof (NMPlatformIP4Route) * len);
+		b = c_b = nm_memdup (b, sizeof (NMPlatformIP4Route) * len);
 		g_qsort_with_data (c_a, len, sizeof (NMPlatformIP4Route), _nmtst_platform_ip4_routes_equal_sort, NULL);
 		g_qsort_with_data (c_b, len, sizeof (NMPlatformIP4Route), _nmtst_platform_ip4_routes_equal_sort, NULL);
 	}
@@ -269,8 +269,8 @@ nmtst_platform_ip6_routes_equal (const NMPlatformIP6Route *a, const NMPlatformIP
 	g_assert (b);
 
 	if (ignore_order) {
-		a = c_a = g_memdup (a, sizeof (NMPlatformIP6Route) * len);
-		b = c_b = g_memdup (b, sizeof (NMPlatformIP6Route) * len);
+		a = c_a = nm_memdup (a, sizeof (NMPlatformIP6Route) * len);
+		b = c_b = nm_memdup (b, sizeof (NMPlatformIP6Route) * len);
 		g_qsort_with_data (c_a, len, sizeof (NMPlatformIP6Route), _nmtst_platform_ip6_routes_equal_sort, NULL);
 		g_qsort_with_data (c_b, len, sizeof (NMPlatformIP6Route), _nmtst_platform_ip6_routes_equal_sort, NULL);
 	}
diff --git a/src/nm-types.h b/src/nm-types.h
index 1477d226..a0a7f620 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2012 - 2017 Red Hat, Inc.
+ * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_TYPES_H__
@@ -150,6 +150,8 @@ typedef enum {
 	NM_LINK_TYPE_WIFI,
 	NM_LINK_TYPE_WWAN_NET,   /* WWAN kernel netdevice */
 	NM_LINK_TYPE_WIMAX,
+	NM_LINK_TYPE_WPAN,
+	NM_LINK_TYPE_6LOWPAN,
 
 	/* Software types */
 	NM_LINK_TYPE_BNEP = 0x10000,   /* Bluetooth Ethernet emulation */
@@ -158,6 +160,8 @@ typedef enum {
 	NM_LINK_TYPE_GRETAP,
 	NM_LINK_TYPE_IFB,
 	NM_LINK_TYPE_IP6TNL,
+	NM_LINK_TYPE_IP6GRE,
+	NM_LINK_TYPE_IP6GRETAP,
 	NM_LINK_TYPE_IPIP,
 	NM_LINK_TYPE_LOOPBACK,
 	NM_LINK_TYPE_MACSEC,
@@ -170,6 +174,7 @@ typedef enum {
 	NM_LINK_TYPE_VETH,
 	NM_LINK_TYPE_VLAN,
 	NM_LINK_TYPE_VXLAN,
+	NM_LINK_TYPE_WIREGUARD,
 
 	/* Software types with slaves */
 	NM_LINK_TYPE_BRIDGE = 0x10000 | 0x20000,
@@ -192,8 +197,11 @@ typedef enum {
 	NMP_OBJECT_TYPE_TFILTER,
 
 	NMP_OBJECT_TYPE_LNK_GRE,
+	NMP_OBJECT_TYPE_LNK_GRETAP,
 	NMP_OBJECT_TYPE_LNK_INFINIBAND,
 	NMP_OBJECT_TYPE_LNK_IP6TNL,
+	NMP_OBJECT_TYPE_LNK_IP6GRE,
+	NMP_OBJECT_TYPE_LNK_IP6GRETAP,
 	NMP_OBJECT_TYPE_LNK_IPIP,
 	NMP_OBJECT_TYPE_LNK_MACSEC,
 	NMP_OBJECT_TYPE_LNK_MACVLAN,
@@ -202,6 +210,7 @@ typedef enum {
 	NMP_OBJECT_TYPE_LNK_TUN,
 	NMP_OBJECT_TYPE_LNK_VLAN,
 	NMP_OBJECT_TYPE_LNK_VXLAN,
+	NMP_OBJECT_TYPE_LNK_WIREGUARD,
 
 	__NMP_OBJECT_TYPE_LAST,
 	NMP_OBJECT_TYPE_MAX = __NMP_OBJECT_TYPE_LAST - 1,
diff --git a/src/platform/linux/nl802154.h b/src/platform/linux/nl802154.h
new file mode 100644
index 00000000..ddcee128
--- /dev/null
+++ b/src/platform/linux/nl802154.h
@@ -0,0 +1,452 @@
+#ifndef __NL802154_H
+#define __NL802154_H
+/*
+ * 802.15.4 netlink interface public header
+ *
+ * Copyright 2014 Alexander Aring <aar@pengutronix.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#define NL802154_GENL_NAME "nl802154"
+
+enum nl802154_commands {
+/* don't change the order or add anything between, this is ABI! */
+/* currently we don't shipping this file via uapi, ignore the above one */
+	NL802154_CMD_UNSPEC,
+
+	NL802154_CMD_GET_WPAN_PHY,		/* can dump */
+	NL802154_CMD_SET_WPAN_PHY,
+	NL802154_CMD_NEW_WPAN_PHY,
+	NL802154_CMD_DEL_WPAN_PHY,
+
+	NL802154_CMD_GET_INTERFACE,		/* can dump */
+	NL802154_CMD_SET_INTERFACE,
+	NL802154_CMD_NEW_INTERFACE,
+	NL802154_CMD_DEL_INTERFACE,
+
+	NL802154_CMD_SET_CHANNEL,
+
+	NL802154_CMD_SET_PAN_ID,
+	NL802154_CMD_SET_SHORT_ADDR,
+
+	NL802154_CMD_SET_TX_POWER,
+	NL802154_CMD_SET_CCA_MODE,
+	NL802154_CMD_SET_CCA_ED_LEVEL,
+
+	NL802154_CMD_SET_MAX_FRAME_RETRIES,
+
+	NL802154_CMD_SET_BACKOFF_EXPONENT,
+	NL802154_CMD_SET_MAX_CSMA_BACKOFFS,
+
+	NL802154_CMD_SET_LBT_MODE,
+
+	NL802154_CMD_SET_ACKREQ_DEFAULT,
+
+	NL802154_CMD_SET_WPAN_PHY_NETNS,
+
+	/* add new commands above here */
+
+#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
+	NL802154_CMD_SET_SEC_PARAMS,
+	NL802154_CMD_GET_SEC_KEY,		/* can dump */
+	NL802154_CMD_NEW_SEC_KEY,
+	NL802154_CMD_DEL_SEC_KEY,
+	NL802154_CMD_GET_SEC_DEV,		/* can dump */
+	NL802154_CMD_NEW_SEC_DEV,
+	NL802154_CMD_DEL_SEC_DEV,
+	NL802154_CMD_GET_SEC_DEVKEY,		/* can dump */
+	NL802154_CMD_NEW_SEC_DEVKEY,
+	NL802154_CMD_DEL_SEC_DEVKEY,
+	NL802154_CMD_GET_SEC_LEVEL,		/* can dump */
+	NL802154_CMD_NEW_SEC_LEVEL,
+	NL802154_CMD_DEL_SEC_LEVEL,
+#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
+
+	/* used to define NL802154_CMD_MAX below */
+	__NL802154_CMD_AFTER_LAST,
+	NL802154_CMD_MAX = __NL802154_CMD_AFTER_LAST - 1
+};
+
+enum nl802154_attrs {
+/* don't change the order or add anything between, this is ABI! */
+/* currently we don't shipping this file via uapi, ignore the above one */
+	NL802154_ATTR_UNSPEC,
+
+	NL802154_ATTR_WPAN_PHY,
+	NL802154_ATTR_WPAN_PHY_NAME,
+
+	NL802154_ATTR_IFINDEX,
+	NL802154_ATTR_IFNAME,
+	NL802154_ATTR_IFTYPE,
+
+	NL802154_ATTR_WPAN_DEV,
+
+	NL802154_ATTR_PAGE,
+	NL802154_ATTR_CHANNEL,
+
+	NL802154_ATTR_PAN_ID,
+	NL802154_ATTR_SHORT_ADDR,
+
+	NL802154_ATTR_TX_POWER,
+
+	NL802154_ATTR_CCA_MODE,
+	NL802154_ATTR_CCA_OPT,
+	NL802154_ATTR_CCA_ED_LEVEL,
+
+	NL802154_ATTR_MAX_FRAME_RETRIES,
+
+	NL802154_ATTR_MAX_BE,
+	NL802154_ATTR_MIN_BE,
+	NL802154_ATTR_MAX_CSMA_BACKOFFS,
+
+	NL802154_ATTR_LBT_MODE,
+
+	NL802154_ATTR_GENERATION,
+
+	NL802154_ATTR_CHANNELS_SUPPORTED,
+	NL802154_ATTR_SUPPORTED_CHANNEL,
+
+	NL802154_ATTR_EXTENDED_ADDR,
+
+	NL802154_ATTR_WPAN_PHY_CAPS,
+
+	NL802154_ATTR_SUPPORTED_COMMANDS,
+
+	NL802154_ATTR_ACKREQ_DEFAULT,
+
+	NL802154_ATTR_PAD,
+
+	NL802154_ATTR_PID,
+	NL802154_ATTR_NETNS_FD,
+
+	/* add attributes here, update the policy in nl802154.c */
+
+#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
+	NL802154_ATTR_SEC_ENABLED,
+	NL802154_ATTR_SEC_OUT_LEVEL,
+	NL802154_ATTR_SEC_OUT_KEY_ID,
+	NL802154_ATTR_SEC_FRAME_COUNTER,
+
+	NL802154_ATTR_SEC_LEVEL,
+	NL802154_ATTR_SEC_DEVICE,
+	NL802154_ATTR_SEC_DEVKEY,
+	NL802154_ATTR_SEC_KEY,
+#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
+
+	__NL802154_ATTR_AFTER_LAST,
+	NL802154_ATTR_MAX = __NL802154_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_iftype {
+	/* for backwards compatibility TODO */
+	NL802154_IFTYPE_UNSPEC = -1,
+
+	NL802154_IFTYPE_NODE,
+	NL802154_IFTYPE_MONITOR,
+	NL802154_IFTYPE_COORD,
+
+	/* keep last */
+	NUM_NL802154_IFTYPES,
+	NL802154_IFTYPE_MAX = NUM_NL802154_IFTYPES - 1
+};
+
+/**
+ * enum nl802154_wpan_phy_capability_attr - wpan phy capability attributes
+ *
+ * @__NL802154_CAP_ATTR_INVALID: attribute number 0 is reserved
+ * @NL802154_CAP_ATTR_CHANNELS: a nested attribute for nl802154_channel_attr
+ * @NL802154_CAP_ATTR_TX_POWERS: a nested attribute for
+ *	nl802154_wpan_phy_tx_power
+ * @NL802154_CAP_ATTR_MIN_CCA_ED_LEVEL: minimum value for cca_ed_level
+ * @NL802154_CAP_ATTR_MAX_CCA_ED_LEVEL: maxmimum value for cca_ed_level
+ * @NL802154_CAP_ATTR_CCA_MODES: nl802154_cca_modes flags
+ * @NL802154_CAP_ATTR_CCA_OPTS: nl802154_cca_opts flags
+ * @NL802154_CAP_ATTR_MIN_MINBE: minimum of minbe value
+ * @NL802154_CAP_ATTR_MAX_MINBE: maximum of minbe value
+ * @NL802154_CAP_ATTR_MIN_MAXBE: minimum of maxbe value
+ * @NL802154_CAP_ATTR_MAX_MINBE: maximum of maxbe value
+ * @NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS: minimum of csma backoff value
+ * @NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS: maximum of csma backoffs value
+ * @NL802154_CAP_ATTR_MIN_FRAME_RETRIES: minimum of frame retries value
+ * @NL802154_CAP_ATTR_MAX_FRAME_RETRIES: maximum of frame retries value
+ * @NL802154_CAP_ATTR_IFTYPES: nl802154_iftype flags
+ * @NL802154_CAP_ATTR_LBT: nl802154_supported_bool_states flags
+ * @NL802154_CAP_ATTR_MAX: highest cap attribute currently defined
+ * @__NL802154_CAP_ATTR_AFTER_LAST: internal use
+ */
+enum nl802154_wpan_phy_capability_attr {
+	__NL802154_CAP_ATTR_INVALID,
+
+	NL802154_CAP_ATTR_IFTYPES,
+
+	NL802154_CAP_ATTR_CHANNELS,
+	NL802154_CAP_ATTR_TX_POWERS,
+
+	NL802154_CAP_ATTR_CCA_ED_LEVELS,
+	NL802154_CAP_ATTR_CCA_MODES,
+	NL802154_CAP_ATTR_CCA_OPTS,
+
+	NL802154_CAP_ATTR_MIN_MINBE,
+	NL802154_CAP_ATTR_MAX_MINBE,
+
+	NL802154_CAP_ATTR_MIN_MAXBE,
+	NL802154_CAP_ATTR_MAX_MAXBE,
+
+	NL802154_CAP_ATTR_MIN_CSMA_BACKOFFS,
+	NL802154_CAP_ATTR_MAX_CSMA_BACKOFFS,
+
+	NL802154_CAP_ATTR_MIN_FRAME_RETRIES,
+	NL802154_CAP_ATTR_MAX_FRAME_RETRIES,
+
+	NL802154_CAP_ATTR_LBT,
+
+	/* keep last */
+	__NL802154_CAP_ATTR_AFTER_LAST,
+	NL802154_CAP_ATTR_MAX = __NL802154_CAP_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl802154_cca_modes - cca modes
+ *
+ * @__NL802154_CCA_INVALID: cca mode number 0 is reserved
+ * @NL802154_CCA_ENERGY: Energy above threshold
+ * @NL802154_CCA_CARRIER: Carrier sense only
+ * @NL802154_CCA_ENERGY_CARRIER: Carrier sense with energy above threshold
+ * @NL802154_CCA_ALOHA: CCA shall always report an idle medium
+ * @NL802154_CCA_UWB_SHR: UWB preamble sense based on the SHR of a frame
+ * @NL802154_CCA_UWB_MULTIPLEXED: UWB preamble sense based on the packet with
+ *	the multiplexed preamble
+ * @__NL802154_CCA_ATTR_AFTER_LAST: Internal
+ * @NL802154_CCA_ATTR_MAX: Maximum CCA attribute number
+ */
+enum nl802154_cca_modes {
+	__NL802154_CCA_INVALID,
+	NL802154_CCA_ENERGY,
+	NL802154_CCA_CARRIER,
+	NL802154_CCA_ENERGY_CARRIER,
+	NL802154_CCA_ALOHA,
+	NL802154_CCA_UWB_SHR,
+	NL802154_CCA_UWB_MULTIPLEXED,
+
+	/* keep last */
+	__NL802154_CCA_ATTR_AFTER_LAST,
+	NL802154_CCA_ATTR_MAX = __NL802154_CCA_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl802154_cca_opts - additional options for cca modes
+ *
+ * @NL802154_CCA_OPT_ENERGY_CARRIER_OR: NL802154_CCA_ENERGY_CARRIER with OR
+ * @NL802154_CCA_OPT_ENERGY_CARRIER_AND: NL802154_CCA_ENERGY_CARRIER with AND
+ */
+enum nl802154_cca_opts {
+	NL802154_CCA_OPT_ENERGY_CARRIER_AND,
+	NL802154_CCA_OPT_ENERGY_CARRIER_OR,
+
+	/* keep last */
+	__NL802154_CCA_OPT_ATTR_AFTER_LAST,
+	NL802154_CCA_OPT_ATTR_MAX = __NL802154_CCA_OPT_ATTR_AFTER_LAST - 1
+};
+
+/**
+ * enum nl802154_supported_bool_states - bool states for bool capability entry
+ *
+ * @NL802154_SUPPORTED_BOOL_FALSE: indicates to set false
+ * @NL802154_SUPPORTED_BOOL_TRUE: indicates to set true
+ * @__NL802154_SUPPORTED_BOOL_INVALD: reserved
+ * @NL802154_SUPPORTED_BOOL_BOTH: indicates to set true and false
+ * @__NL802154_SUPPORTED_BOOL_AFTER_LAST: Internal
+ * @NL802154_SUPPORTED_BOOL_MAX: highest value for bool states
+ */
+enum nl802154_supported_bool_states {
+	NL802154_SUPPORTED_BOOL_FALSE,
+	NL802154_SUPPORTED_BOOL_TRUE,
+	/* to handle them in a mask */
+	__NL802154_SUPPORTED_BOOL_INVALD,
+	NL802154_SUPPORTED_BOOL_BOTH,
+
+	/* keep last */
+	__NL802154_SUPPORTED_BOOL_AFTER_LAST,
+	NL802154_SUPPORTED_BOOL_MAX = __NL802154_SUPPORTED_BOOL_AFTER_LAST - 1
+};
+
+#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
+
+enum nl802154_dev_addr_modes {
+	NL802154_DEV_ADDR_NONE,
+	__NL802154_DEV_ADDR_INVALID,
+	NL802154_DEV_ADDR_SHORT,
+	NL802154_DEV_ADDR_EXTENDED,
+
+	/* keep last */
+	__NL802154_DEV_ADDR_AFTER_LAST,
+	NL802154_DEV_ADDR_MAX = __NL802154_DEV_ADDR_AFTER_LAST - 1
+};
+
+enum nl802154_dev_addr_attrs {
+	NL802154_DEV_ADDR_ATTR_UNSPEC,
+
+	NL802154_DEV_ADDR_ATTR_PAN_ID,
+	NL802154_DEV_ADDR_ATTR_MODE,
+	NL802154_DEV_ADDR_ATTR_SHORT,
+	NL802154_DEV_ADDR_ATTR_EXTENDED,
+	NL802154_DEV_ADDR_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_DEV_ADDR_ATTR_AFTER_LAST,
+	NL802154_DEV_ADDR_ATTR_MAX = __NL802154_DEV_ADDR_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_key_id_modes {
+	NL802154_KEY_ID_MODE_IMPLICIT,
+	NL802154_KEY_ID_MODE_INDEX,
+	NL802154_KEY_ID_MODE_INDEX_SHORT,
+	NL802154_KEY_ID_MODE_INDEX_EXTENDED,
+
+	/* keep last */
+	__NL802154_KEY_ID_MODE_AFTER_LAST,
+	NL802154_KEY_ID_MODE_MAX = __NL802154_KEY_ID_MODE_AFTER_LAST - 1
+};
+
+enum nl802154_key_id_attrs {
+	NL802154_KEY_ID_ATTR_UNSPEC,
+
+	NL802154_KEY_ID_ATTR_MODE,
+	NL802154_KEY_ID_ATTR_INDEX,
+	NL802154_KEY_ID_ATTR_IMPLICIT,
+	NL802154_KEY_ID_ATTR_SOURCE_SHORT,
+	NL802154_KEY_ID_ATTR_SOURCE_EXTENDED,
+	NL802154_KEY_ID_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_KEY_ID_ATTR_AFTER_LAST,
+	NL802154_KEY_ID_ATTR_MAX = __NL802154_KEY_ID_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_seclevels {
+	NL802154_SECLEVEL_NONE,
+	NL802154_SECLEVEL_MIC32,
+	NL802154_SECLEVEL_MIC64,
+	NL802154_SECLEVEL_MIC128,
+	NL802154_SECLEVEL_ENC,
+	NL802154_SECLEVEL_ENC_MIC32,
+	NL802154_SECLEVEL_ENC_MIC64,
+	NL802154_SECLEVEL_ENC_MIC128,
+
+	/* keep last */
+	__NL802154_SECLEVEL_AFTER_LAST,
+	NL802154_SECLEVEL_MAX = __NL802154_SECLEVEL_AFTER_LAST - 1
+};
+
+enum nl802154_frames {
+	NL802154_FRAME_BEACON,
+	NL802154_FRAME_DATA,
+	NL802154_FRAME_ACK,
+	NL802154_FRAME_CMD,
+
+	/* keep last */
+	__NL802154_FRAME_AFTER_LAST,
+	NL802154_FRAME_MAX = __NL802154_FRAME_AFTER_LAST - 1
+};
+
+enum nl802154_cmd_frames {
+	__NL802154_CMD_FRAME_INVALID,
+	NL802154_CMD_FRAME_ASSOC_REQUEST,
+	NL802154_CMD_FRAME_ASSOC_RESPONSE,
+	NL802154_CMD_FRAME_DISASSOC_NOTIFY,
+	NL802154_CMD_FRAME_DATA_REQUEST,
+	NL802154_CMD_FRAME_PAN_ID_CONFLICT_NOTIFY,
+	NL802154_CMD_FRAME_ORPHAN_NOTIFY,
+	NL802154_CMD_FRAME_BEACON_REQUEST,
+	NL802154_CMD_FRAME_COORD_REALIGNMENT,
+	NL802154_CMD_FRAME_GTS_REQUEST,
+
+	/* keep last */
+	__NL802154_CMD_FRAME_AFTER_LAST,
+	NL802154_CMD_FRAME_MAX = __NL802154_CMD_FRAME_AFTER_LAST - 1
+};
+
+enum nl802154_seclevel_attrs {
+	NL802154_SECLEVEL_ATTR_UNSPEC,
+
+	NL802154_SECLEVEL_ATTR_LEVELS,
+	NL802154_SECLEVEL_ATTR_FRAME,
+	NL802154_SECLEVEL_ATTR_CMD_FRAME,
+	NL802154_SECLEVEL_ATTR_DEV_OVERRIDE,
+
+	/* keep last */
+	__NL802154_SECLEVEL_ATTR_AFTER_LAST,
+	NL802154_SECLEVEL_ATTR_MAX = __NL802154_SECLEVEL_ATTR_AFTER_LAST - 1
+};
+
+/* TODO what is this? couldn't find in mib */
+enum {
+	NL802154_DEVKEY_IGNORE,
+	NL802154_DEVKEY_RESTRICT,
+	NL802154_DEVKEY_RECORD,
+
+	/* keep last */
+	__NL802154_DEVKEY_AFTER_LAST,
+	NL802154_DEVKEY_MAX = __NL802154_DEVKEY_AFTER_LAST - 1
+};
+
+enum nl802154_dev {
+	NL802154_DEV_ATTR_UNSPEC,
+
+	NL802154_DEV_ATTR_FRAME_COUNTER,
+	NL802154_DEV_ATTR_PAN_ID,
+	NL802154_DEV_ATTR_SHORT_ADDR,
+	NL802154_DEV_ATTR_EXTENDED_ADDR,
+	NL802154_DEV_ATTR_SECLEVEL_EXEMPT,
+	NL802154_DEV_ATTR_KEY_MODE,
+	NL802154_DEV_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_DEV_ATTR_AFTER_LAST,
+	NL802154_DEV_ATTR_MAX = __NL802154_DEV_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_devkey {
+	NL802154_DEVKEY_ATTR_UNSPEC,
+
+	NL802154_DEVKEY_ATTR_FRAME_COUNTER,
+	NL802154_DEVKEY_ATTR_EXTENDED_ADDR,
+	NL802154_DEVKEY_ATTR_ID,
+	NL802154_DEVKEY_ATTR_PAD,
+
+	/* keep last */
+	__NL802154_DEVKEY_ATTR_AFTER_LAST,
+	NL802154_DEVKEY_ATTR_MAX = __NL802154_DEVKEY_ATTR_AFTER_LAST - 1
+};
+
+enum nl802154_key {
+	NL802154_KEY_ATTR_UNSPEC,
+
+	NL802154_KEY_ATTR_ID,
+	NL802154_KEY_ATTR_USAGE_FRAMES,
+	NL802154_KEY_ATTR_USAGE_CMDS,
+	NL802154_KEY_ATTR_BYTES,
+
+	/* keep last */
+	__NL802154_KEY_ATTR_AFTER_LAST,
+	NL802154_KEY_ATTR_MAX = __NL802154_KEY_ATTR_AFTER_LAST - 1
+};
+
+#define NL802154_KEY_SIZE		16
+#define NL802154_CMD_FRAME_NR_IDS	256
+
+#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
+
+#endif /* __NL802154_H */
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index f8f4627e..82f9e2fb 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -602,12 +602,6 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static gboolean
-link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
-{
-	return TRUE;
-}
-
 static const char *
 link_get_udi (NMPlatform *platform, int ifindex)
 {
@@ -893,12 +887,6 @@ wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid)
 	return FALSE;
 }
 
-static GByteArray *
-wifi_get_ssid (NMPlatform *platform, int ifindex)
-{
-	return NULL;
-}
-
 static guint32
 wifi_get_frequency (NMPlatform *platform, int ifindex)
 {
@@ -1422,7 +1410,6 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 
 	platform_class->link_set_address = link_set_address;
 	platform_class->link_set_mtu = link_set_mtu;
-	platform_class->link_set_sriov_num_vfs = link_set_sriov_num_vfs;
 
 	platform_class->link_get_driver_info = link_get_driver_info;
 
@@ -1442,7 +1429,6 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 
 	platform_class->wifi_get_capabilities = wifi_get_capabilities;
 	platform_class->wifi_get_bssid = wifi_get_bssid;
-	platform_class->wifi_get_ssid = wifi_get_ssid;
 	platform_class->wifi_get_frequency = wifi_get_frequency;
 	platform_class->wifi_get_quality = wifi_get_quality;
 	platform_class->wifi_get_rate = wifi_get_rate;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 9bd662a1..e73d5d8c 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2012 - 2017 Red Hat, Inc.
+ * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
 #include "nm-default.h"
 
@@ -44,15 +44,18 @@
 #include "nm-core-internal.h"
 #include "nm-setting-vlan.h"
 
+#include "nm-utils/nm-secret-utils.h"
 #include "nm-netlink.h"
 #include "nm-core-utils.h"
 #include "nmp-object.h"
 #include "nmp-netns.h"
 #include "nm-platform-utils.h"
 #include "nm-platform-private.h"
-#include "wifi/wifi-utils.h"
-#include "wifi/wifi-utils-wext.h"
+#include "wifi/nm-wifi-utils.h"
+#include "wifi/nm-wifi-utils-wext.h"
+#include "wpan/nm-wpan-utils.h"
 #include "nm-utils/unaligned.h"
+#include "nm-utils/nm-io-utils.h"
 #include "nm-utils/nm-udev-utils.h"
 
 /*****************************************************************************/
@@ -154,6 +157,13 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 
 /*****************************************************************************/
 
+/* Appeared in in kernel prior to 3.13 dated 19 January, 2014 */
+#ifndef ARPHRD_6LOWPAN
+#define ARPHRD_6LOWPAN 825
+#endif
+
+/*****************************************************************************/
+
 #define IFLA_MACSEC_UNSPEC              0
 #define IFLA_MACSEC_SCI                 1
 #define IFLA_MACSEC_PORT                2
@@ -173,63 +183,79 @@ G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
 
 /*****************************************************************************/
 
-#define _NMLOG_PREFIX_NAME                "platform-linux"
-#define _NMLOG_DOMAIN                     LOGD_PLATFORM
-#define _NMLOG2_DOMAIN                    LOGD_PLATFORM
-#define _NMLOG(level, ...)                _LOG     (       level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
-#define _NMLOG_err(errsv, level, ...)     _LOG_err (errsv, level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
-#define _NMLOG2(level, ...)               _LOG     (       level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
-#define _NMLOG2_err(errsv, level, ...)    _LOG_err (errsv, level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
+#define WG_CMD_GET_DEVICE 0
+#define WG_CMD_SET_DEVICE 1
+
+#define WGDEVICE_A_UNSPEC                      0
+#define WGDEVICE_A_IFINDEX                     1
+#define WGDEVICE_A_IFNAME                      2
+#define WGDEVICE_A_PRIVATE_KEY                 3
+#define WGDEVICE_A_PUBLIC_KEY                  4
+#define WGDEVICE_A_FLAGS                       5
+#define WGDEVICE_A_LISTEN_PORT                 6
+#define WGDEVICE_A_FWMARK                      7
+#define WGDEVICE_A_PEERS                       8
+#define WGDEVICE_A_MAX                         8
+
+#define WGPEER_A_UNSPEC                        0
+#define WGPEER_A_PUBLIC_KEY                    1
+#define WGPEER_A_PRESHARED_KEY                 2
+#define WGPEER_A_FLAGS                         3
+#define WGPEER_A_ENDPOINT                      4
+#define WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL 5
+#define WGPEER_A_LAST_HANDSHAKE_TIME           6
+#define WGPEER_A_RX_BYTES                      7
+#define WGPEER_A_TX_BYTES                      8
+#define WGPEER_A_ALLOWEDIPS                    9
+#define WGPEER_A_MAX                           9
+
+#define WGALLOWEDIP_A_UNSPEC                   0
+#define WGALLOWEDIP_A_FAMILY                   1
+#define WGALLOWEDIP_A_IPADDR                   2
+#define WGALLOWEDIP_A_CIDR_MASK                3
+#define WGALLOWEDIP_A_MAX                      3
 
-#define _LOG_print(__level, __domain, __errsv, self, ...) \
-    G_STMT_START { \
-        char __prefix[32]; \
-        const char *__p_prefix = _NMLOG_PREFIX_NAME; \
-        NMPlatform *const __self = (self); \
-        \
-        if (__self && nm_platform_get_log_with_ptr (__self)) { \
-            g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
-            __p_prefix = __prefix; \
-        } \
-        _nm_log (__level, __domain, __errsv, NULL, NULL, \
-                 "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                 __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
-    } G_STMT_END
+/*****************************************************************************/
 
-#define _LOG(level, domain, self, ...) \
-    G_STMT_START { \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = (domain); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            _LOG_print (__level, __domain, 0, self, __VA_ARGS__); \
-        } \
-    } G_STMT_END
+/* Redefine VF enums and structures that are not available on older kernels. */
+
+#define IFLA_VF_UNSPEC                 0
+#define IFLA_VF_MAC                    1
+#define IFLA_VF_VLAN                   2
+#define IFLA_VF_TX_RATE                3
+#define IFLA_VF_SPOOFCHK               4
+#define IFLA_VF_LINK_STATE             5
+#define IFLA_VF_RATE                   6
+#define IFLA_VF_RSS_QUERY_EN           7
+#define IFLA_VF_STATS                  8
+#define IFLA_VF_TRUST                  9
+#define IFLA_VF_IB_NODE_GUID           10
+#define IFLA_VF_IB_PORT_GUID           11
+#define IFLA_VF_VLAN_LIST              12
+
+#define IFLA_VF_VLAN_INFO_UNSPEC       0
+#define IFLA_VF_VLAN_INFO              1
+
+/* valid for TRUST, SPOOFCHK, LINK_STATE, RSS_QUERY_EN */
+struct _ifla_vf_setting {
+	guint32 vf;
+	guint32 setting;
+};
 
-#define _LOG_err(errsv, level, domain, self, ...) \
-    G_STMT_START { \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = (domain); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            int __errsv = (errsv); \
-            \
-            /* The %m format specifier (GNU extension) would alread allow you to specify the error
-             * message conveniently (and nm_log would get that right too). But we don't want to depend
-             * on that, so instead append the message at the end.
-             * Currently users are expected not to use %m in the format string. */ \
-            _LOG_print (__level, __domain, __errsv, self, \
-                        _NM_UTILS_MACRO_FIRST (__VA_ARGS__) ": %s (%d)" \
-                        _NM_UTILS_MACRO_REST (__VA_ARGS__), \
-                        g_strerror (__errsv), __errsv); \
-        } \
-    } G_STMT_END
+struct _ifla_vf_rate {
+	guint32 vf;
+	guint32 min_tx_rate;
+	guint32 max_tx_rate;
+};
 
-#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
+struct _ifla_vf_vlan_info {
+	guint32 vf;
+	guint32 vlan; /* 0 - 4095, 0 disables VLAN filter */
+	guint32 qos;
+	guint16 vlan_proto; /* VLAN protocol, either 802.1Q or 802.1ad */
+};
 
-/******************************************************************
- * Forward declarations and enums
- ******************************************************************/
+/*****************************************************************************/
 
 typedef enum {
 	INFINIBAND_ACTION_CREATE_CHILD,
@@ -307,6 +333,133 @@ typedef enum {
 	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS,
 } WaitForNlResponseResult;
 
+typedef enum {
+	DELAYED_ACTION_RESPONSE_TYPE_VOID                       = 0,
+	DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS    = 1,
+	DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET                  = 2,
+} DelayedActionWaitForNlResponseType;
+
+typedef struct {
+	guint32 seq_number;
+	WaitForNlResponseResult seq_result;
+	DelayedActionWaitForNlResponseType response_type;
+	gint64 timeout_abs_ns;
+	WaitForNlResponseResult *out_seq_result;
+	char **out_errmsg;
+	union {
+		int *out_refresh_all_in_progress;
+		NMPObject **out_route_get;
+		gpointer out_data;
+	} response;
+} DelayedActionWaitForNlResponseData;
+
+/*****************************************************************************/
+
+typedef struct {
+	struct nl_sock *genl;
+
+	struct nl_sock *nlh;
+	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;
+
+	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+
+	bool sysctl_get_warned;
+	GHashTable *sysctl_get_prev_values;
+
+	NMUdevClient *udev_client;
+
+	struct {
+		/* which delayed actions are scheduled, as marked in @flags.
+		 * Some types have additional arguments in the fields below. */
+		DelayedActionType flags;
+
+		/* counter that a refresh all action is in progress, separated
+		 * by type. */
+		int refresh_all_in_progress[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+
+		GPtrArray *list_master_connected;
+		GPtrArray *list_refresh_link;
+		GArray *list_wait_for_nl_response;
+
+		int is_handling;
+	} delayed_action;
+} NMLinuxPlatformPrivate;
+
+struct _NMLinuxPlatform {
+	NMPlatform parent;
+	NMLinuxPlatformPrivate _priv;
+};
+
+struct _NMLinuxPlatformClass {
+	NMPlatformClass parent;
+};
+
+G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
+
+#define NM_LINUX_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME                "platform-linux"
+#define _NMLOG_DOMAIN                     LOGD_PLATFORM
+#define _NMLOG2_DOMAIN                    LOGD_PLATFORM
+#define _NMLOG(level, ...)                _LOG     (       level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
+#define _NMLOG_err(errsv, level, ...)     _LOG_err (errsv, level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
+#define _NMLOG2(level, ...)               _LOG     (       level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
+#define _NMLOG2_err(errsv, level, ...)    _LOG_err (errsv, level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
+
+#define _LOG_print(__level, __domain, __errsv, self, ...) \
+    G_STMT_START { \
+        char __prefix[32]; \
+        const char *__p_prefix = _NMLOG_PREFIX_NAME; \
+        NMPlatform *const __self = (self); \
+        \
+        if (__self && nm_platform_get_log_with_ptr (__self)) { \
+            g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \
+            __p_prefix = __prefix; \
+        } \
+        _nm_log (__level, __domain, __errsv, NULL, NULL, \
+                 "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                 __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+    } G_STMT_END
+
+#define _LOG(level, domain, self, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            _LOG_print (__level, __domain, 0, self, __VA_ARGS__); \
+        } \
+    } G_STMT_END
+
+#define _LOG_err(errsv, level, domain, self, ...) \
+    G_STMT_START { \
+        const NMLogLevel __level = (level); \
+        const NMLogDomain __domain = (domain); \
+        \
+        if (nm_logging_enabled (__level, __domain)) { \
+            int __errsv = (errsv); \
+            \
+            /* The %m format specifier (GNU extension) would alread allow you to specify the error
+             * message conveniently (and nm_log would get that right too). But we don't want to depend
+             * on that, so instead append the message at the end.
+             * Currently users are expected not to use %m in the format string. */ \
+            _LOG_print (__level, __domain, __errsv, self, \
+                        _NM_UTILS_MACRO_FIRST (__VA_ARGS__) ": %s (%d)" \
+                        _NM_UTILS_MACRO_REST (__VA_ARGS__), \
+                        g_strerror (__errsv), __errsv); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
 static void delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
 static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
 static void do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name);
@@ -317,6 +470,7 @@ static void cache_on_change (NMPlatform *platform,
                              const NMPObject *obj_new);
 static void cache_prune_all (NMPlatform *platform);
 static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks);
+static struct nl_sock *_genl_sock (NMLinuxPlatform *platform);
 
 /*****************************************************************************/
 
@@ -533,6 +687,8 @@ static const LinkDesc linktypes[] = {
 	{ 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 },
@@ -540,6 +696,8 @@ static const LinkDesc linktypes[] = {
 	{ 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 },
@@ -552,6 +710,7 @@ static const LinkDesc linktypes[] = {
 	{ 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" },
@@ -725,19 +884,21 @@ _addrtime_get_lifetimes (guint32 timestamp,
 /*****************************************************************************/
 
 static const NMPObject *
-_lookup_cached_link (const NMPCache *cache, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
+_lookup_cached_link (const NMPCache *cache,
+                     int ifindex,
+                     gboolean *completed_from_cache,
+                     const NMPObject **link_cached)
 {
 	const NMPObject *obj;
 
 	nm_assert (completed_from_cache && link_cached);
 
 	if (!*completed_from_cache) {
-		obj = ifindex > 0 && cache ? nmp_cache_lookup_link (cache, ifindex) : NULL;
+		obj = ifindex > 0 && cache
+		      ? nmp_cache_lookup_link (cache, ifindex)
+		      : NULL;
 
-		if (obj && obj->_link.netlink.is_in_netlink)
-			*link_cached = obj;
-		else
-			*link_cached = NULL;
+		*link_cached = obj;
 		*completed_from_cache = TRUE;
 	}
 	return *link_cached;
@@ -755,7 +916,9 @@ _linktype_read_devtype (int dirfd)
 
 	nm_assert (dirfd >= 0);
 
-	if (nm_utils_file_get_contents (dirfd, "uevent", 1*1024*1024, &contents, NULL, NULL) < 0)
+	if (nm_utils_file_get_contents (dirfd, "uevent", 1*1024*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+	                                &contents, NULL, NULL) < 0)
 		return NULL;
 	for (cont = contents; cont; cont = end) {
 		end = strpbrk (cont, "\r\n");
@@ -806,6 +969,7 @@ _linktype_get_type (NMPlatform *platform,
 		 * when moving interfce to other netns). Thus here there is a tiny potential
 		 * of messing stuff up. */
 		if (   obj
+		    && obj->_link.netlink.is_in_netlink
 		    && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE)
 		    && nm_streq (ifname, obj->link.name)
 		    && (   !kind
@@ -841,6 +1005,10 @@ _linktype_get_type (NMPlatform *platform,
 		return NM_LINK_TYPE_IP6TNL;
 	else if (arptype == ARPHRD_PPP)
 		return NM_LINK_TYPE_PPP;
+	else if (arptype == ARPHRD_IEEE802154)
+		return NM_LINK_TYPE_WPAN;
+	else if (arptype == ARPHRD_6LOWPAN)
+		return NM_LINK_TYPE_6LOWPAN;
 
 	{
 		NMPUtilsEthtoolDriverInfo driver_info;
@@ -885,7 +1053,7 @@ _linktype_get_type (NMPlatform *platform,
 			}
 
 			/* Fallback for drivers that don't call SET_NETDEV_DEVTYPE() */
-			if (wifi_utils_is_wifi (dirfd, ifname_verified))
+			if (nm_wifi_utils_is_wifi (dirfd, ifname_verified))
 				return NM_LINK_TYPE_WIFI;
 		}
 
@@ -953,18 +1121,21 @@ _nl_addattr_l (struct nlmsghdr *n,
  * NMPObject/netlink functions
  ******************************************************************/
 
-#define _check_addr_or_errout(tb, attr, addr_len) \
+#define _check_addr_or_return_val(tb, attr, addr_len, ret_val) \
 	({ \
 	    const struct nlattr *__t = (tb)[(attr)]; \
 		\
 	    if (__t) { \
 			if (nla_len (__t) != (addr_len)) { \
-				goto errout; \
+				return ret_val; \
 			} \
 		} \
 		!!__t; \
 	})
 
+#define _check_addr_or_return_null(tb, attr, addr_len) \
+	_check_addr_or_return_val (tb, attr, addr_len, NULL)
+
 /*****************************************************************************/
 
 /* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */
@@ -991,20 +1162,19 @@ _parse_af_inet6 (NMPlatform *platform,
 	gboolean token_valid = FALSE;
 	gboolean addr_gen_mode_valid = FALSE;
 	guint8 i6_addr_gen_mode_inv = 0;
-	gboolean success = FALSE;
 
 	err = nla_parse_nested (tb, IFLA_INET6_MAX, attr, policy);
 	if (err < 0)
-		goto errout;
+		return FALSE;
 
 	if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
-		goto errout;
+		return FALSE;
 	if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
-		goto errout;
+		return FALSE;
 	if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
-		goto errout;
+		return FALSE;
 
-	if (_check_addr_or_errout (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr))) {
+	if (_check_addr_or_return_val (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr), FALSE)) {
 		nla_memcpy (&i6_token, tb[IFLA_INET6_TOKEN], sizeof (struct in6_addr));
 		token_valid = TRUE;
 	}
@@ -1020,12 +1190,11 @@ _parse_af_inet6 (NMPlatform *platform,
 		if (i6_addr_gen_mode_inv == 0) {
 			/* an inverse addrgenmode of zero is unexpected. We need to reserve zero
 			 * to signal "unset". */
-			goto errout;
+			return FALSE;
 		}
 		addr_gen_mode_valid = TRUE;
 	}
 
-	success = TRUE;
 	if (token_valid) {
 		*out_token_valid = token_valid;
 		nm_utils_ipv6_interface_identifier_get_from_addr (out_token, &i6_token);
@@ -1034,8 +1203,7 @@ _parse_af_inet6 (NMPlatform *platform,
 		*out_addr_gen_mode_valid = addr_gen_mode_valid;
 		*out_addr_gen_mode_inv = i6_addr_gen_mode_inv;
 	}
-errout:
-	return success;
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -1059,15 +1227,23 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 	int err;
 	NMPObject *obj;
 	NMPlatformLnkGre *props;
+	gboolean is_tap;
+
+	if (!info_data || !kind)
+		return NULL;
 
-	if (!info_data || g_strcmp0 (kind, "gre"))
+	if (nm_streq (kind, "gretap"))
+		is_tap = TRUE;
+	else if (nm_streq (kind, "gre"))
+		is_tap = FALSE;
+	else
 		return NULL;
 
 	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
 	if (err < 0)
 		return NULL;
 
-	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_GRE, NULL);
+	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_GRETAP : NMP_OBJECT_TYPE_LNK_GRE, NULL);
 	props = &obj->lnk_gre;
 
 	props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32 (tb[IFLA_GRE_LINK]) : 0;
@@ -1080,6 +1256,7 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 	props->tos = tb[IFLA_GRE_TOS] ? nla_get_u8 (tb[IFLA_GRE_TOS]) : 0;
 	props->ttl = tb[IFLA_GRE_TTL] ? nla_get_u8 (tb[IFLA_GRE_TTL]) : 0;
 	props->path_mtu_discovery = !tb[IFLA_GRE_PMTUDISC] || !!nla_get_u8 (tb[IFLA_GRE_PMTUDISC]);
+	props->is_tap = is_tap;
 
 	return obj;
 }
@@ -1200,6 +1377,79 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 	return obj;
 }
 
+static NMPObject *
+_parse_lnk_ip6gre (const char *kind, struct nlattr *info_data)
+{
+	static const struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+		[IFLA_GRE_LINK]        = { .type = NLA_U32 },
+		[IFLA_GRE_IFLAGS]      = { .type = NLA_U16 },
+		[IFLA_GRE_OFLAGS]      = { .type = NLA_U16 },
+		[IFLA_GRE_IKEY]        = { .type = NLA_U32 },
+		[IFLA_GRE_OKEY]        = { .type = NLA_U32 },
+		[IFLA_GRE_LOCAL]       = { .type = NLA_UNSPEC,
+		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_GRE_REMOTE]      = { .type = NLA_UNSPEC,
+		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_GRE_TTL]         = { .type = NLA_U8 },
+		[IFLA_GRE_ENCAP_LIMIT] = { .type = NLA_U8 },
+		[IFLA_GRE_FLOWINFO]    = { .type = NLA_U32 },
+		[IFLA_GRE_FLAGS]       = { .type = NLA_U32 },
+	};
+	struct nlattr *tb[IFLA_GRE_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkIp6Tnl *props;
+	guint32 flowinfo;
+	gboolean is_tap;
+
+	if (!info_data || !kind)
+		return NULL;
+
+	if (nm_streq (kind, "ip6gre"))
+		is_tap = FALSE;
+	else if (nm_streq (kind, "ip6gretap"))
+		is_tap = TRUE;
+	else
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (is_tap ? NMP_OBJECT_TYPE_LNK_IP6GRETAP : NMP_OBJECT_TYPE_LNK_IP6GRE, NULL);
+	props = &obj->lnk_ip6tnl;
+	props->is_gre = TRUE;
+	props->is_tap = is_tap;
+
+	if (tb[IFLA_GRE_LINK])
+		props->parent_ifindex = nla_get_u32 (tb[IFLA_GRE_LINK]);
+	if (tb[IFLA_GRE_IFLAGS])
+		props->input_flags = ntohs (nla_get_u16 (tb[IFLA_GRE_IFLAGS]));
+	if (tb[IFLA_GRE_OFLAGS])
+		props->output_flags = ntohs (nla_get_u16 (tb[IFLA_GRE_OFLAGS]));
+	if (tb[IFLA_GRE_IKEY])
+		props->input_key = ntohl (nla_get_u32 (tb[IFLA_GRE_IKEY]));
+	if (tb[IFLA_GRE_OKEY])
+		props->output_key = ntohl (nla_get_u32 (tb[IFLA_GRE_OKEY]));
+	if (tb[IFLA_GRE_LOCAL])
+		memcpy (&props->local, nla_data (tb[IFLA_GRE_LOCAL]), sizeof (props->local));
+	if (tb[IFLA_GRE_REMOTE])
+		memcpy (&props->remote, nla_data (tb[IFLA_GRE_REMOTE]), sizeof (props->remote));
+	if (tb[IFLA_GRE_TTL])
+		props->ttl = nla_get_u8 (tb[IFLA_GRE_TTL]);
+	if (tb[IFLA_GRE_ENCAP_LIMIT])
+		props->encap_limit = nla_get_u8 (tb[IFLA_GRE_ENCAP_LIMIT]);
+	if (tb[IFLA_GRE_FLOWINFO]) {
+		flowinfo = ntohl (nla_get_u32 (tb[IFLA_GRE_FLOWINFO]));
+		props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK;
+		props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT;
+	}
+	if (tb[IFLA_GRE_FLAGS])
+		props->flags = nla_get_u32 (tb[IFLA_GRE_FLAGS]);
+
+	return obj;
+}
+
 /*****************************************************************************/
 
 static NMPObject *
@@ -1663,6 +1913,378 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 
 /*****************************************************************************/
 
+static gboolean
+_wireguard_update_from_allowed_ips_nla (NMPWireGuardAllowedIP *allowed_ip,
+                                        struct nlattr *nlattr)
+{
+	static const struct nla_policy policy[WGALLOWEDIP_A_MAX + 1] = {
+		[WGALLOWEDIP_A_FAMILY]    = { .type = NLA_U16 },
+		[WGALLOWEDIP_A_IPADDR]    = { .minlen = sizeof (struct in_addr) },
+		[WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[WGALLOWEDIP_A_MAX + 1];
+	int family;
+	int addr_len;
+
+	if (nla_parse_nested (tb, WGALLOWEDIP_A_MAX, nlattr, policy) < 0)
+		return FALSE;
+
+	if (!tb[WGALLOWEDIP_A_FAMILY])
+		return FALSE;
+
+	family = nla_get_u16 (tb[WGALLOWEDIP_A_FAMILY]);
+	if (family == AF_INET)
+		addr_len = sizeof (in_addr_t);
+	else if (family == AF_INET6)
+		addr_len = sizeof (struct in6_addr);
+	else
+		return FALSE;
+
+	_check_addr_or_return_val (tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE);
+
+	memset (allowed_ip, 0, sizeof (NMPWireGuardAllowedIP));
+
+	allowed_ip->family = family;
+	nm_assert ((int) allowed_ip->family == family);
+
+	if (tb[WGALLOWEDIP_A_IPADDR])
+		nla_memcpy (&allowed_ip->addr, tb[WGALLOWEDIP_A_IPADDR], addr_len);
+	if (tb[WGALLOWEDIP_A_CIDR_MASK])
+		allowed_ip->mask = nla_get_u8 (tb[WGALLOWEDIP_A_CIDR_MASK]);
+
+	return TRUE;
+}
+
+typedef struct {
+	CList lst;
+	NMPWireGuardPeer data;
+} WireGuardPeerConstruct;
+
+static gboolean
+_wireguard_update_from_peers_nla (CList *peers,
+                                  GArray **p_allowed_ips,
+                                  struct nlattr *peer_attr)
+{
+	static const struct nla_policy policy[WGPEER_A_MAX + 1] = {
+		[WGPEER_A_PUBLIC_KEY]                    = { .minlen = NMP_WIREGUARD_PUBLIC_KEY_LEN },
+		[WGPEER_A_PRESHARED_KEY]                 = { },
+		[WGPEER_A_FLAGS]                         = { .type = NLA_U32 },
+		[WGPEER_A_ENDPOINT]                      = { },
+		[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = { .type = NLA_U16 },
+		[WGPEER_A_LAST_HANDSHAKE_TIME]           = { },
+		[WGPEER_A_RX_BYTES]                      = { .type = NLA_U64 },
+		[WGPEER_A_TX_BYTES]                      = { .type = NLA_U64 },
+		[WGPEER_A_ALLOWEDIPS]                    = { .type = NLA_NESTED },
+	};
+	WireGuardPeerConstruct *peer_c;
+	struct nlattr *tb[WGPEER_A_MAX + 1];
+
+	if (nla_parse_nested (tb, WGPEER_A_MAX, peer_attr, policy) < 0)
+		return FALSE;
+
+	if (!tb[WGPEER_A_PUBLIC_KEY])
+		return FALSE;
+
+	/* a peer with the same public key as last peer is just a continuation for extra AllowedIPs */
+	peer_c = c_list_last_entry (peers, WireGuardPeerConstruct, lst);
+	if (   peer_c
+	    && !memcmp (nla_data (tb[WGPEER_A_PUBLIC_KEY]), peer_c->data.public_key, NMP_WIREGUARD_PUBLIC_KEY_LEN)) {
+		G_STATIC_ASSERT_EXPR (NMP_WIREGUARD_PUBLIC_KEY_LEN == sizeof (peer_c->data.public_key));
+		/* this message is a continuation of the previous peer.
+		 * Only parse WGPEER_A_ALLOWEDIPS below. */
+	}
+	else {
+		/* otherwise, start a new peer */
+		peer_c = g_slice_new0 (WireGuardPeerConstruct);
+		c_list_link_tail (peers, &peer_c->lst);
+
+		nla_memcpy (&peer_c->data.public_key, tb[WGPEER_A_PUBLIC_KEY], sizeof (peer_c->data.public_key));
+
+		if (tb[WGPEER_A_PRESHARED_KEY]) {
+			nla_memcpy (&peer_c->data.preshared_key, tb[WGPEER_A_PRESHARED_KEY], sizeof (peer_c->data.preshared_key));
+			/* FIXME(netlink-bzero-secret) */
+			nm_explicit_bzero (nla_data (tb[WGPEER_A_PRESHARED_KEY]),
+			                   nla_len (tb[WGPEER_A_PRESHARED_KEY]));
+		}
+		if (tb[WGPEER_A_ENDPOINT]) {
+			const struct sockaddr *addr = nla_data (tb[WGPEER_A_ENDPOINT]);
+			unsigned short family;
+
+			G_STATIC_ASSERT (sizeof (addr->sa_family) == sizeof (family));
+			memcpy (&family, &addr->sa_family, sizeof (addr->sa_family));
+
+			if (   family == AF_INET
+			    && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in)) {
+				const struct sockaddr_in *addr4 = (const struct sockaddr_in *) addr;
+
+				peer_c->data.endpoint_family = AF_INET;
+				peer_c->data.endpoint_port = unaligned_read_be16 (&addr4->sin_port);
+				peer_c->data.endpoint_addr.addr4 = unaligned_read_ne32 (&addr4->sin_addr.s_addr);
+				memcpy (&peer_c->data.endpoint_addr.addr4, &addr4->sin_addr.s_addr, 4);
+			} else if (   family == AF_INET6
+			           && nla_len (tb[WGPEER_A_ENDPOINT]) == sizeof (struct sockaddr_in6)) {
+				const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *) addr;
+
+				peer_c->data.endpoint_family = AF_INET6;
+				peer_c->data.endpoint_port = unaligned_read_be16 (&addr6->sin6_port);
+				memcpy (&peer_c->data.endpoint_addr.addr6, &addr6->sin6_addr, 16);
+			}
+		}
+		if (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL])
+			peer_c->data.persistent_keepalive_interval = nla_get_u64 (tb[WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL]);
+		if (tb[WGPEER_A_LAST_HANDSHAKE_TIME])
+			nla_memcpy (&peer_c->data.last_handshake_time, tb[WGPEER_A_LAST_HANDSHAKE_TIME], sizeof (peer_c->data.last_handshake_time));
+		if (tb[WGPEER_A_RX_BYTES])
+			peer_c->data.rx_bytes = nla_get_u64 (tb[WGPEER_A_RX_BYTES]);
+		if (tb[WGPEER_A_TX_BYTES])
+			peer_c->data.tx_bytes = nla_get_u64 (tb[WGPEER_A_TX_BYTES]);
+	}
+
+	if (tb[WGPEER_A_ALLOWEDIPS]) {
+		struct nlattr *attr;
+		int rem;
+		GArray *allowed_ips = *p_allowed_ips;
+
+		nla_for_each_nested (attr, tb[WGPEER_A_ALLOWEDIPS], rem) {
+			if (!allowed_ips) {
+				allowed_ips = g_array_new (FALSE, FALSE, sizeof (NMPWireGuardAllowedIP));
+				*p_allowed_ips = allowed_ips;
+				g_array_set_size (allowed_ips, 1);
+			} else
+				g_array_set_size (allowed_ips, allowed_ips->len + 1);
+
+			if (!_wireguard_update_from_allowed_ips_nla (&g_array_index (allowed_ips,
+			                                                             NMPWireGuardAllowedIP,
+			                                                             allowed_ips->len - 1),
+			                                            attr)) {
+				/* we ignore the error of parsing one allowed-ip. */
+				g_array_set_size (allowed_ips, allowed_ips->len - 1);
+				continue;
+			}
+
+			if (!peer_c->data._construct_idx_end)
+				peer_c->data._construct_idx_start = allowed_ips->len - 1;
+			peer_c->data._construct_idx_end = allowed_ips->len;
+		}
+	}
+
+	return TRUE;
+}
+
+typedef struct {
+	const int ifindex;
+	NMPObject *obj;
+	CList peers;
+	GArray *allowed_ips;
+} WireGuardParseData;
+
+static int
+_wireguard_get_device_cb (struct nl_msg *msg, void *arg)
+{
+	static const struct nla_policy policy[WGDEVICE_A_MAX + 1] = {
+		[WGDEVICE_A_IFINDEX]     = { .type = NLA_U32 },
+		[WGDEVICE_A_IFNAME]      = { .type = NLA_NUL_STRING, .maxlen = IFNAMSIZ },
+		[WGDEVICE_A_PRIVATE_KEY] = { },
+		[WGDEVICE_A_PUBLIC_KEY]  = { },
+		[WGDEVICE_A_FLAGS]       = { .type = NLA_U32 },
+		[WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 },
+		[WGDEVICE_A_FWMARK]      = { .type = NLA_U32 },
+		[WGDEVICE_A_PEERS]       = { .type = NLA_NESTED },
+	};
+	WireGuardParseData *parse_data = arg;
+	struct nlattr *tb[WGDEVICE_A_MAX + 1];
+	int nlerr;
+
+	nlerr = genlmsg_parse (nlmsg_hdr (msg), 0, tb, WGDEVICE_A_MAX, policy);
+	if (nlerr < 0)
+		return NL_SKIP;
+
+	if (tb[WGDEVICE_A_IFINDEX]) {
+		int ifindex;
+
+		ifindex = (int) nla_get_u32 (tb[WGDEVICE_A_IFINDEX]);
+		if (   ifindex <= 0
+		    || parse_data->ifindex != ifindex)
+			return NL_SKIP;
+	} else {
+		if (!parse_data->obj)
+			return NL_SKIP;
+	}
+
+	if (parse_data->obj) {
+		/* we already have an object instance. This means the netlink message
+		 * is a continuation, only providing more WGDEVICE_A_PEERS data below. */
+	} else {
+		NMPObject *obj;
+		NMPlatformLnkWireGuard *props;
+
+		obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_WIREGUARD, NULL);
+		props = &obj->lnk_wireguard;
+
+		if (tb[WGDEVICE_A_PRIVATE_KEY]) {
+			nla_memcpy (props->private_key, tb[WGDEVICE_A_PRIVATE_KEY], sizeof (props->private_key));
+			/* FIXME(netlink-bzero-secret): extend netlink library to wipe memory. For now,
+			 * just hack it here (yes, this does not cover all places where the
+			 * private key was copied). */
+			nm_explicit_bzero (nla_data (tb[WGDEVICE_A_PRIVATE_KEY]),
+			                   nla_len (tb[WGDEVICE_A_PRIVATE_KEY]));
+		}
+		if (tb[WGDEVICE_A_PUBLIC_KEY])
+			nla_memcpy (props->public_key, tb[WGDEVICE_A_PUBLIC_KEY], sizeof (props->public_key));
+		if (tb[WGDEVICE_A_LISTEN_PORT])
+			props->listen_port = nla_get_u16 (tb[WGDEVICE_A_LISTEN_PORT]);
+		if (tb[WGDEVICE_A_FWMARK])
+			props->fwmark = nla_get_u32 (tb[WGDEVICE_A_FWMARK]);
+
+		parse_data->obj = obj;
+	}
+
+	if (tb[WGDEVICE_A_PEERS]) {
+		struct nlattr *attr;
+		int rem;
+
+		nla_for_each_nested (attr, tb[WGDEVICE_A_PEERS], rem) {
+			if (!_wireguard_update_from_peers_nla (&parse_data->peers, &parse_data->allowed_ips, attr)) {
+				/* we ignore the error of parsing one peer.
+				 * _wireguard_update_from_peers_nla() leaves the @peers array in the
+				 * desired state. */
+			}
+		}
+	}
+
+	return NL_OK;
+}
+
+static const NMPObject *
+_wireguard_read_info (NMPlatform *platform /* used only as logging context */,
+                     struct nl_sock *genl,
+                     int wireguard_family_id,
+                     int ifindex)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	NMPObject *obj = NULL;
+	WireGuardPeerConstruct *peer_c;
+	WireGuardPeerConstruct *peer_c_safe;
+	gs_unref_array GArray *allowed_ips = NULL;
+	WireGuardParseData parse_data = {
+		.ifindex = ifindex,
+	};
+	guint i;
+
+	nm_assert (genl);
+	nm_assert (wireguard_family_id >= 0);
+	nm_assert (ifindex > 0);
+
+	msg = nlmsg_alloc ();
+
+	if (!genlmsg_put (msg,
+	                  NL_AUTO_PORT,
+	                  NL_AUTO_SEQ,
+	                  wireguard_family_id,
+	                  0,
+	                  NLM_F_DUMP,
+	                  WG_CMD_GET_DEVICE,
+	                  1))
+		return NULL;
+
+	NLA_PUT_U32 (msg, WGDEVICE_A_IFINDEX, (guint32) ifindex);
+
+	if (nl_send_auto (genl, msg) < 0)
+		return NULL;
+
+	c_list_init (&parse_data.peers);
+
+	/* we ignore errors, and return whatever we could successfully
+	 * parse. */
+	nl_recvmsgs (genl,
+	             &((const struct nl_cb) {
+	                 .valid_cb = _wireguard_get_device_cb,
+	                 .valid_arg = (gpointer) &parse_data,
+	             }));
+
+	/* unpack: transfer ownership */
+	obj = parse_data.obj;
+	allowed_ips = parse_data.allowed_ips;
+
+	if (!obj) {
+		while ((peer_c = c_list_first_entry (&parse_data.peers, WireGuardPeerConstruct, lst))) {
+			c_list_unlink_stale (&peer_c->lst);
+			nm_explicit_bzero (&peer_c->data.preshared_key, sizeof (peer_c->data.preshared_key));
+			g_slice_free (WireGuardPeerConstruct, peer_c);
+		}
+		return NULL;
+	}
+
+	/* we receive peers/allowed-ips possibly in separate netlink messages. Hence, while
+	 * parsing the dump, we don't know upfront how many peers/allowed-ips we will receive.
+	 *
+	 * We solve that, by collecting all peers with a CList. It's done this way,
+	 * because a GArray would require growing the array, but we want to bzero()
+	 * the preshared-key of each peer while reallocating. The CList apprach avoids
+	 * that.
+	 *
+	 * For allowed-ips, we instead track one GArray, which are all appended
+	 * there. The realloc/resize of the GArray is fine there. However,
+	 * while we build the GArray, we don't yet have the final pointers.
+	 * Hence, while constructing, we track the indexes with peer->_construct_idx_*
+	 * fields. These indexes must be convered to actual pointers blow.
+	 *
+	 * This is all done during parsing. In the final NMPObjectLnkWireGuard we
+	 * don't want the CList anymore and repackage the NMPObject tightly. The
+	 * reason is, that NMPObject instances are immutable and long-living. Spend
+	 * a bit effort below during construction to obtain a most suitable representation
+	 * in this regard. */
+	obj->_lnk_wireguard.peers_len = c_list_length (&parse_data.peers);
+	obj->_lnk_wireguard.peers = obj->_lnk_wireguard.peers_len > 0
+	                            ? g_new (NMPWireGuardPeer, obj->_lnk_wireguard.peers_len)
+	                            : NULL;
+
+	/* duplicate allowed_ips instead of using the pointer. The GArray possibly has more
+	 * space allocated then we need, and we want to get rid of this excess buffer.
+	 * Note that NMPObject instance is possibly put into the cache and long-living. */
+	obj->_lnk_wireguard._allowed_ips_buf_len = allowed_ips ? allowed_ips->len : 0u;
+	obj->_lnk_wireguard._allowed_ips_buf = obj->_lnk_wireguard._allowed_ips_buf_len > 0
+	                                       ? (NMPWireGuardAllowedIP *) nm_memdup (allowed_ips->data,
+	                                                                              sizeof (NMPWireGuardAllowedIP) * allowed_ips->len)
+	                                       : NULL;
+
+	i = 0;
+	c_list_for_each_entry_safe (peer_c, peer_c_safe, &parse_data.peers, lst) {
+		NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &obj->_lnk_wireguard.peers[i++];
+
+		*peer = peer_c->data;
+
+		c_list_unlink_stale (&peer_c->lst);
+		nm_explicit_bzero (&peer_c->data.preshared_key, sizeof (peer_c->data.preshared_key));
+		g_slice_free (WireGuardPeerConstruct, peer_c);
+
+		if (peer->_construct_idx_end != 0) {
+			guint len;
+
+			nm_assert (obj->_lnk_wireguard._allowed_ips_buf);
+			nm_assert (peer->_construct_idx_end > peer->_construct_idx_start);
+			nm_assert (peer->_construct_idx_start < obj->_lnk_wireguard._allowed_ips_buf_len);
+			nm_assert (peer->_construct_idx_end <= obj->_lnk_wireguard._allowed_ips_buf_len);
+
+			len = peer->_construct_idx_end - peer->_construct_idx_start;
+			peer->allowed_ips = &obj->_lnk_wireguard._allowed_ips_buf[peer->_construct_idx_start];
+			peer->allowed_ips_len = len;
+		} else {
+			nm_assert (!peer->_construct_idx_start);
+			nm_assert (!peer->_construct_idx_end);
+			peer->allowed_ips = NULL;
+			peer->allowed_ips_len = 0;
+		}
+	}
+
+	return obj;
+
+nla_put_failure:
+	g_return_val_if_reached (NULL);
+}
+
+/*****************************************************************************/
+
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
@@ -1707,37 +2329,39 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	const char *nl_info_kind = NULL;
 	int err;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	NMPObject *obj_result = NULL;
 	gboolean completed_from_cache_val = FALSE;
 	gboolean *completed_from_cache = cache ? &completed_from_cache_val : NULL;
 	const NMPObject *link_cached = NULL;
-	NMPObject *lnk_data = NULL;
+	const NMPObject *lnk_data = NULL;
 	gboolean address_complete_from_cache = TRUE;
 	gboolean lnk_data_complete_from_cache = TRUE;
+	gboolean need_ext_data = FALSE;
 	gboolean af_inet6_token_valid = FALSE;
 	gboolean af_inet6_addr_gen_mode_valid = FALSE;
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*ifi)))
 		return NULL;
-	ifi = nlmsg_data(nlh);
+	ifi = nlmsg_data (nlh);
 
 	if (ifi->ifi_family != AF_UNSPEC)
 		return NULL;
+	if (ifi->ifi_index <= 0)
+		return NULL;
 
 	obj = nmp_object_new_link (ifi->ifi_index);
 
 	if (id_only)
-		goto id_only_handled;
+		return g_steal_pointer (&obj);
 
 	err = nlmsg_parse (nlh, sizeof (*ifi), tb, IFLA_MAX, policy);
 	if (err < 0)
-		goto errout;
+		return NULL;
 
 	if (!tb[IFLA_IFNAME])
-		goto errout;
+		return NULL;
 	nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
 	if (!obj->link.name[0])
-		goto errout;
+		return NULL;
 
 	if (!tb[IFLA_MTU]) {
 		/* Kernel has two places that send RTM_GETLINK messages:
@@ -1752,14 +2376,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		 * To some extent this is a hack and correct approach is to
 		 * merge objects per-field.
 		 */
-		goto errout;
+		return NULL;
 	}
 	obj->link.mtu = nla_get_u32 (tb[IFLA_MTU]);
 
 	if (tb[IFLA_LINKINFO]) {
 		err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], policy_link_info);
 		if (err < 0)
-			goto errout;
+			return NULL;
 
 		if (li[IFLA_INFO_KIND])
 			nl_info_kind = nla_get_string (li[IFLA_INFO_KIND]);
@@ -1838,6 +2462,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	switch (obj->link.type) {
 	case NM_LINK_TYPE_GRE:
+	case NM_LINK_TYPE_GRETAP:
 		lnk_data = _parse_lnk_gre (nl_info_kind, nl_info_data);
 		break;
 	case NM_LINK_TYPE_INFINIBAND:
@@ -1846,6 +2471,10 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_IP6TNL:
 		lnk_data = _parse_lnk_ip6tnl (nl_info_kind, nl_info_data);
 		break;
+	case NM_LINK_TYPE_IP6GRE:
+	case NM_LINK_TYPE_IP6GRETAP:
+		lnk_data = _parse_lnk_ip6gre (nl_info_kind, nl_info_data);
+		break;
 	case NM_LINK_TYPE_IPIP:
 		lnk_data = _parse_lnk_ipip (nl_info_kind, nl_info_data);
 		break;
@@ -1868,6 +2497,15 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	case NM_LINK_TYPE_VXLAN:
 		lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data);
 		break;
+	case NM_LINK_TYPE_WIFI:
+	case NM_LINK_TYPE_OLPC_MESH:
+	case NM_LINK_TYPE_WPAN:
+		need_ext_data = TRUE;
+		lnk_data_complete_from_cache = FALSE;
+		break;
+	case NM_LINK_TYPE_WIREGUARD:
+		lnk_data_complete_from_cache = TRUE;
+		break;
 	default:
 		lnk_data_complete_from_cache = FALSE;
 		break;
@@ -1875,12 +2513,14 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	if (   completed_from_cache
 	    && (   lnk_data_complete_from_cache
+	        || need_ext_data
 	        || address_complete_from_cache
 	        || !af_inet6_token_valid
 	        || !af_inet6_addr_gen_mode_valid
 	        || !tb[IFLA_STATS64])) {
 		_lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached);
-		if (link_cached) {
+		if (   link_cached
+		    && link_cached->_link.netlink.is_in_netlink) {
 			if (   lnk_data_complete_from_cache
 			    && link_cached->link.type == obj->link.type
 			    && link_cached->_link.netlink.lnk
@@ -1893,8 +2533,16 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 				 * Also, sometimes the info-data is missing for updates. In this case
 				 * we want to keep the previously received lnk_data. */
 				nmp_object_unref (lnk_data);
-				lnk_data = (NMPObject *) nmp_object_ref (link_cached->_link.netlink.lnk);
+				lnk_data = nmp_object_ref (link_cached->_link.netlink.lnk);
 			}
+
+			if (   need_ext_data
+			    && link_cached->link.type == obj->link.type
+			    && link_cached->_link.ext_data) {
+				/* Prefer reuse of existing ext_data object */
+				obj->_link.ext_data = g_object_ref (link_cached->_link.ext_data);
+			}
+
 			if (address_complete_from_cache)
 				obj->link.addr = link_cached->link.addr;
 			if (!af_inet6_token_valid)
@@ -1912,12 +2560,71 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 
 	obj->_link.netlink.lnk = lnk_data;
 
+	if (   need_ext_data
+	    && obj->_link.ext_data == NULL) {
+		switch (obj->link.type) {
+		case NM_LINK_TYPE_WIFI:
+			obj->_link.ext_data = (GObject *) nm_wifi_utils_new (ifi->ifi_index,
+			                                                     _genl_sock (NM_LINUX_PLATFORM (platform)),
+			                                                     TRUE);
+			break;
+		case NM_LINK_TYPE_OLPC_MESH:
+#if HAVE_WEXT
+			/* The kernel driver now uses nl80211, but we force use of WEXT because
+			 * the cfg80211 interactions are not quite ready to support access to
+			 * mesh control through nl80211 just yet.
+			 */
+			obj->_link.ext_data = (GObject *) nm_wifi_utils_wext_new (ifi->ifi_index, FALSE);
+#endif
+			break;
+		case NM_LINK_TYPE_WPAN:
+			obj->_link.ext_data = (GObject *) nm_wpan_utils_new (ifi->ifi_index,
+			                                                     _genl_sock (NM_LINUX_PLATFORM (platform)),
+			                                                     TRUE);
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+	}
+
+	if (obj->link.type == NM_LINK_TYPE_WIREGUARD) {
+		const NMPObject *lnk_data_new = NULL;
+		struct nl_sock *genl = NM_LINUX_PLATFORM_GET_PRIVATE (platform)->genl;
+
+		/* The WireGuard kernel module does not yet send link update
+		 * notifications, so we don't actually update the cache. For
+		 * now, always refetch link data here. */
+
+		_lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached);
+		if (   link_cached
+		    && link_cached->_link.netlink.is_in_netlink
+		    && link_cached->link.type == NM_LINK_TYPE_WIREGUARD)
+			obj->_link.wireguard_family_id = link_cached->_link.wireguard_family_id;
+		else
+			obj->_link.wireguard_family_id = -1;
+
+		if (obj->_link.wireguard_family_id < 0)
+			obj->_link.wireguard_family_id = genl_ctrl_resolve (genl, "wireguard");
+
+		if (obj->_link.wireguard_family_id >= 0) {
+			lnk_data_new = _wireguard_read_info (platform,
+			                                     genl,
+			                                     obj->_link.wireguard_family_id,
+			                                     obj->link.ifindex);
+		}
+
+		if (   lnk_data_new
+		    && obj->_link.netlink.lnk
+		    && nmp_object_equal (obj->_link.netlink.lnk, lnk_data_new))
+			nmp_object_unref (lnk_data_new);
+		else {
+			nmp_object_unref (obj->_link.netlink.lnk);
+			obj->_link.netlink.lnk = lnk_data_new;
+		}
+	}
+
 	obj->_link.netlink.is_in_netlink = TRUE;
-id_only_handled:
-	obj_result = obj;
-	obj = NULL;
-errout:
-	return obj_result;
+	return g_steal_pointer (&obj);
 }
 
 /* Copied and heavily modified from libnl3's addr_msg_parser(). */
@@ -1934,7 +2641,6 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	int err;
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	NMPObject *obj_result = NULL;
 	int addr_len;
 	guint32 lifetime, preferred, timestamp;
 
@@ -1943,19 +2649,19 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	ifa = nlmsg_data(nlh);
 
 	if (!NM_IN_SET (ifa->ifa_family, AF_INET, AF_INET6))
-		goto errout;
+		return NULL;
 	is_v4 = ifa->ifa_family == AF_INET;
 
 	err = nlmsg_parse (nlh, sizeof(*ifa), tb, IFA_MAX, policy);
 	if (err < 0)
-		goto errout;
+		return NULL;
 
 	addr_len = is_v4
 	           ? sizeof (in_addr_t)
 	           : sizeof (struct in6_addr);
 
 	if (ifa->ifa_prefixlen > (is_v4 ? 32 : 128))
-		goto errout;
+		return NULL;
 
 	/*****************************************************************/
 
@@ -1964,8 +2670,8 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_address.ifindex = ifa->ifa_index;
 	obj->ip_address.plen = ifa->ifa_prefixlen;
 
-	_check_addr_or_errout (tb, IFA_ADDRESS, addr_len);
-	_check_addr_or_errout (tb, IFA_LOCAL, addr_len);
+	_check_addr_or_return_null (tb, IFA_ADDRESS, addr_len);
+	_check_addr_or_return_null (tb, IFA_LOCAL, addr_len);
 	if (is_v4) {
 		/* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they
 		 * are effectively 0.0.0.0 (all-zero). */
@@ -2029,10 +2735,7 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 	                         &obj->ip_address.lifetime,
 	                         &obj->ip_address.preferred);
 
-	obj_result = obj;
-	obj = NULL;
-errout:
-	return obj_result;
+	return g_steal_pointer (&obj);
 }
 
 /* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */
@@ -2055,7 +2758,6 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	int err;
 	gboolean is_v4;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	NMPObject *obj_result = NULL;
 	int addr_len;
 	struct {
 		gboolean is_present;
@@ -2074,14 +2776,14 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	 *****************************************************************/
 
 	if (!NM_IN_SET (rtm->rtm_family, AF_INET, AF_INET6))
-		goto errout;
+		return NULL;
 
 	if (rtm->rtm_type != RTN_UNICAST)
-		goto errout;
+		return NULL;
 
 	err = nlmsg_parse (nlh, sizeof (struct rtmsg), tb, RTA_MAX, policy);
 	if (err < 0)
-		goto errout;
+		return NULL;
 
 	/*****************************************************************/
 
@@ -2091,7 +2793,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	           : sizeof (struct in6_addr);
 
 	if (rtm->rtm_dst_len > (is_v4 ? 32 : 128))
-		goto errout;
+		return NULL;
 
 	/*****************************************************************
 	 * parse nexthops. Only handle routes with one nh.
@@ -2107,7 +2809,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 			if (nh.is_present) {
 				/* we don't support multipath routes. */
-				goto errout;
+				return NULL;
 			}
 			nh.is_present = TRUE;
 
@@ -2121,9 +2823,9 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 				                 rtnh->rtnh_len - sizeof (*rtnh),
 				                 policy);
 				if (err < 0)
-					goto errout;
+					return NULL;
 
-				if (_check_addr_or_errout (ntb, RTA_GATEWAY, addr_len))
+				if (_check_addr_or_return_null (ntb, RTA_GATEWAY, addr_len))
 					memcpy (&nh.gateway, nla_data (ntb[RTA_GATEWAY]), addr_len);
 			}
 
@@ -2140,7 +2842,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 		if (tb[RTA_OIF])
 			ifindex = nla_get_u32 (tb[RTA_OIF]);
-		if (_check_addr_or_errout (tb, RTA_GATEWAY, addr_len))
+		if (_check_addr_or_return_null (tb, RTA_GATEWAY, addr_len))
 			memcpy (&gateway, nla_data (tb[RTA_GATEWAY]), addr_len);
 
 		if (!nh.is_present) {
@@ -2154,10 +2856,10 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 			 * verify that it is a duplicate and ignore old-style nexthop. */
 			if (   nh.ifindex != ifindex
 			    || memcmp (&nh.gateway, &gateway, addr_len) != 0)
-				goto errout;
+				return NULL;
 		}
 	} else if (!nh.is_present)
-		goto errout;
+		return NULL;
 
 	/*****************************************************************/
 
@@ -2176,7 +2878,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 		err = nla_parse_nested (mtb, RTAX_MAX, tb[RTA_METRICS], rtax_policy);
 		if (err < 0)
-			goto errout;
+			return NULL;
 
 		if (mtb[RTAX_LOCK])
 			lock = nla_get_u32 (mtb[RTAX_LOCK]);
@@ -2204,7 +2906,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 	obj->ip_route.ifindex = nh.ifindex;
 
-	if (_check_addr_or_errout (tb, RTA_DST, addr_len))
+	if (_check_addr_or_return_null (tb, RTA_DST, addr_len))
 		memcpy (obj->ip_route.network_ptr, nla_data (tb[RTA_DST]), addr_len);
 
 	obj->ip_route.plen = rtm->rtm_dst_len;
@@ -2220,7 +2922,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	if (is_v4)
 		obj->ip4_route.scope_inv = nm_platform_route_scope_inv (rtm->rtm_scope);
 
-	if (_check_addr_or_errout (tb, RTA_PREFSRC, addr_len)) {
+	if (_check_addr_or_return_null (tb, RTA_PREFSRC, addr_len)) {
 		if (is_v4)
 			memcpy (&obj->ip4_route.pref_src, nla_data (tb[RTA_PREFSRC]), addr_len);
 		else
@@ -2231,7 +2933,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 		obj->ip4_route.tos = rtm->rtm_tos;
 	else {
 		if (tb[RTA_SRC]) {
-			_check_addr_or_errout (tb, RTA_SRC, addr_len);
+			_check_addr_or_return_null (tb, RTA_SRC, addr_len);
 			memcpy (&obj->ip6_route.src, nla_data (tb[RTA_SRC]), addr_len);
 		}
 		obj->ip6_route.src_plen = rtm->rtm_src_len;
@@ -2261,10 +2963,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_route.r_rtm_flags = rtm->rtm_flags;
 	obj->ip_route.rt_source = nmp_utils_ip_config_source_from_rtprot (rtm->rtm_protocol);
 
-	obj_result = obj;
-	obj = NULL;
-errout:
-	return obj_result;
+	return g_steal_pointer (&obj);
 }
 
 static NMPObject *
@@ -2941,103 +3640,16 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
-/******************************************************************
- * NMPlatform types and functions
- ******************************************************************/
-
-typedef enum {
-	DELAYED_ACTION_RESPONSE_TYPE_VOID                       = 0,
-	DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS    = 1,
-	DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET                  = 2,
-} DelayedActionWaitForNlResponseType;
-
-typedef struct {
-	guint32 seq_number;
-	WaitForNlResponseResult seq_result;
-	DelayedActionWaitForNlResponseType response_type;
-	gint64 timeout_abs_ns;
-	WaitForNlResponseResult *out_seq_result;
-	char **out_errmsg;
-	union {
-		gint *out_refresh_all_in_progess;
-		NMPObject **out_route_get;
-		gpointer out_data;
-	} response;
-} DelayedActionWaitForNlResponseData;
-
-typedef struct {
-	struct nl_sock *nlh;
-	guint32 nlh_seq_next;
-#ifdef NM_MORE_LOGGING
-	guint32 nlh_seq_last_handled;
-#endif
-	guint32 nlh_seq_last_seen;
-	GIOChannel *event_channel;
-	guint event_id;
-
-	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
-
-	bool sysctl_get_warned;
-	GHashTable *sysctl_get_prev_values;
-
-	NMUdevClient *udev_client;
-
-	struct {
-		/* which delayed actions are scheduled, as marked in @flags.
-		 * Some types have additional arguments in the fields below. */
-		DelayedActionType flags;
-
-		/* counter that a refresh all action is in progress, separated
-		 * by type. */
-		gint refresh_all_in_progess[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
-
-		GPtrArray *list_master_connected;
-		GPtrArray *list_refresh_link;
-		GArray *list_wait_for_nl_response;
-
-		gint is_handling;
-	} delayed_action;
-
-	GHashTable *wifi_data;
-} NMLinuxPlatformPrivate;
-
-struct _NMLinuxPlatform {
-	NMPlatform parent;
-	NMLinuxPlatformPrivate _priv;
-};
-
-struct _NMLinuxPlatformClass {
-	NMPlatformClass parent;
-};
-
-G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
-
-#define NM_LINUX_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMLinuxPlatform, NM_IS_LINUX_PLATFORM, NMPlatform)
+/*****************************************************************************/
 
-NMPlatform *
-nm_linux_platform_new (gboolean log_with_ptr, gboolean netns_support)
+static struct nl_sock *
+_genl_sock (NMLinuxPlatform *platform)
 {
-	gboolean use_udev = FALSE;
-
-	if (   nmp_netns_is_initial ()
-	    && access ("/sys", W_OK) == 0)
-		use_udev = TRUE;
-
-	return g_object_new (NM_TYPE_LINUX_PLATFORM,
-	                     NM_PLATFORM_LOG_WITH_PTR, log_with_ptr,
-	                     NM_PLATFORM_USE_UDEV, use_udev,
-	                     NM_PLATFORM_NETNS_SUPPORT, netns_support,
-	                     NULL);
-}
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 
-void
-nm_linux_platform_setup (void)
-{
-	nm_platform_setup (nm_linux_platform_new (FALSE, FALSE));
+	return priv->genl;
 }
 
-/*****************************************************************************/
-
 #define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \
 	G_STMT_START { \
 		const char *const _pathid = (pathid); \
@@ -3064,7 +3676,9 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, c
 	char *contents;
 	gs_free char *value_escaped = g_strescape (value, NULL);
 
-	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024, &contents, NULL, &error) < 0) {
+	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+	                                &contents, NULL, &error) < 0) {
 		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", pathid, value_escaped, error->message);
 		g_clear_error (&error);
 		return;
@@ -3281,7 +3895,9 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 		pathid = path;
 	}
 
-	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024, &contents, NULL, &error) < 0) {
+	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024,
+	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+	                                &contents, NULL, &error) < 0) {
 		/* We assume FAILED means EOPNOTSUP */
 		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
 		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NODEV)
@@ -3452,7 +4068,7 @@ delayed_action_refresh_all_in_progress (NMPlatform *platform, DelayedActionType
 	if (NM_FLAGS_ANY (priv->delayed_action.flags, action_type))
 		return TRUE;
 
-	if (priv->delayed_action.refresh_all_in_progess[delayed_action_refresh_all_to_idx (action_type)] > 0)
+	if (priv->delayed_action.refresh_all_in_progress[delayed_action_refresh_all_to_idx (action_type)] > 0)
 		return TRUE;
 
 	return FALSE;
@@ -3482,10 +4098,10 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
 	case DELAYED_ACTION_RESPONSE_TYPE_VOID:
 		break;
 	case DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS:
-		if (data->response.out_refresh_all_in_progess) {
-			nm_assert (*data->response.out_refresh_all_in_progess > 0);
-			*data->response.out_refresh_all_in_progess -= 1;
-			data->response.out_refresh_all_in_progess = NULL;
+		if (data->response.out_refresh_all_in_progress) {
+			nm_assert (*data->response.out_refresh_all_in_progress > 0);
+			*data->response.out_refresh_all_in_progress -= 1;
+			data->response.out_refresh_all_in_progress = NULL;
 		}
 		break;
 	case DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET:
@@ -3510,7 +4126,7 @@ delayed_action_wait_for_nl_response_complete_check (NMPlatform *platform,
 	guint i;
 	guint32 next_seq_number = 0;
 	gint64 next_timeout_abs_ns = 0;
-	gint now_ns = 0;
+	int now_ns = 0;
 
 	for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
 		const DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
@@ -3932,7 +4548,10 @@ cache_on_change (NMPlatform *platform,
 
 			if (   !obj_new->_link.netlink.lnk
 			    && NM_IN_SET (obj_new->link.type, NM_LINK_TYPE_GRE,
+			                                      NM_LINK_TYPE_GRETAP,
 			                                      NM_LINK_TYPE_IP6TNL,
+			                                      NM_LINK_TYPE_IP6GRE,
+			                                      NM_LINK_TYPE_IP6GRETAP,
 			                                      NM_LINK_TYPE_INFINIBAND,
 			                                      NM_LINK_TYPE_MACVLAN,
 			                                      NM_LINK_TYPE_MACVLAN,
@@ -4205,11 +4824,11 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		const NMPClass *klass = nmp_class_from_type (obj_type);
 		nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 		int nle;
-		gint *out_refresh_all_in_progess;
+		int *out_refresh_all_in_progress;
 
-		out_refresh_all_in_progess = &priv->delayed_action.refresh_all_in_progess[delayed_action_refresh_all_to_idx (iflags)];
-		nm_assert (*out_refresh_all_in_progess >= 0);
-		*out_refresh_all_in_progess += 1;
+		out_refresh_all_in_progress = &priv->delayed_action.refresh_all_in_progress[delayed_action_refresh_all_to_idx (iflags)];
+		nm_assert (*out_refresh_all_in_progress >= 0);
+		*out_refresh_all_in_progress += 1;
 
 		/* clear any delayed action that request a refresh of this object type. */
 		priv->delayed_action.flags &= ~iflags;
@@ -4243,9 +4862,9 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		if (nle < 0)
 			continue;
 
-		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progess) < 0) {
-			nm_assert (*out_refresh_all_in_progess > 0);
-			*out_refresh_all_in_progess -= 1;
+		if (_nl_send_nlmsg (platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS, out_refresh_all_in_progress) < 0) {
+			nm_assert (*out_refresh_all_in_progress > 0);
+			*out_refresh_all_in_progress -= 1;
 		}
 	}
 }
@@ -4274,10 +4893,10 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number)
 			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
 
 			if (   data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS
-			    && data->response.out_refresh_all_in_progess
+			    && data->response.out_refresh_all_in_progress
 			    && data->seq_number == priv->nlh_seq_last_seen) {
-				*data->response.out_refresh_all_in_progess -= 1;
-				data->response.out_refresh_all_in_progess = NULL;
+				*data->response.out_refresh_all_in_progress -= 1;
+				data->response.out_refresh_all_in_progress = NULL;
 				break;
 			}
 		}
@@ -4318,7 +4937,7 @@ event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResu
 		}
 	}
 
-#ifdef NM_MORE_LOGGING
+#if NM_MORE_LOGGING
 	if (seq_number != priv->nlh_seq_last_handled)
 		_LOGt ("netlink: recvmsg: unwaited sequence number %u", seq_number);
 	priv->nlh_seq_last_handled = seq_number;
@@ -4741,6 +5360,9 @@ retry:
 	} else if (NM_IN_SET (-((int) seq_result), ENODEV)) {
 		log_level = LOGL_DEBUG;
 		result = NM_PLATFORM_ERROR_NOT_FOUND;
+	} else if (-((int) seq_result) == EAFNOSUPPORT) {
+		log_level = LOGL_DEBUG;
+		result = NM_PLATFORM_ERROR_OPNOTSUPP;
 	} else {
 		log_level = LOGL_WARN;
 		result = NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -4841,8 +5463,6 @@ link_set_netns (NMPlatform *platform,
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGD ("link: move link %d to network namespace with fd %d", ifindex, netns_fd);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5031,7 +5651,6 @@ static NMPlatformError
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	gs_free char *mac = NULL;
 	const ChangeLinkData d = {
 		.set_address = {
 			.address = address,
@@ -5042,10 +5661,6 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 	if (!address || !length)
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
-	_LOGD ("link: change %d: address: %s (%lu bytes)", ifindex,
-	       (mac = nm_utils_hwaddr_ntoa (address, length)),
-	       (unsigned long) length);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5067,8 +5682,6 @@ link_set_name (NMPlatform *platform, int ifindex, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGD ("link: change %d: name: %s", ifindex, name);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5104,8 +5717,6 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGD ("link: change %d: mtu: %u", ifindex, (unsigned) mtu);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5123,16 +5734,18 @@ nla_put_failure:
 }
 
 static gboolean
-link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
+link_set_sriov_params (NMPlatform *platform,
+                       int ifindex,
+                       guint num_vfs,
+                       int autoprobe)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	nm_auto_close int dirfd = -1;
-	int total, current;
+	gboolean current_autoprobe;
+	guint total, current_num;
 	char ifname[IFNAMSIZ];
 	char buf[64];
 
-	_LOGD ("link: change %d: num VFs: %u", ifindex, num_vfs);
-
 	if (!nm_platform_netns_push (platform, &netns))
 		return FALSE;
 
@@ -5140,46 +5753,69 @@ link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
 	if (!dirfd)
 		return FALSE;
 
-	total = nm_platform_sysctl_get_int32 (platform,
-	                                      NMP_SYSCTL_PATHID_NETDIR (dirfd,
-	                                                                ifname,
-	                                                                "device/sriov_totalvfs"),
-	                                      -1);
-	if (total < 1)
+	total = nm_platform_sysctl_get_int_checked (platform,
+	                                            NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                                      ifname,
+	                                                                      "device/sriov_totalvfs"),
+	                                            10, 0, G_MAXUINT, 0);
+	if (errno)
 		return FALSE;
 	if (num_vfs > total) {
 		_LOGW ("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs);
 		num_vfs = total;
 	}
 
-	current = nm_platform_sysctl_get_int32 (platform,
-	                                        NMP_SYSCTL_PATHID_NETDIR (dirfd,
-	                                                                  ifname,
-	                                                                  "device/sriov_numvfs"),
-	                                        -1);
-	if (current == num_vfs)
+	/*
+	 * Take special care when setting new values:
+	 *  - don't touch anything if the right values are already set
+	 *  - to change the number of VFs or autoprobe we need to destroy existing VFs
+	 *  - the autoprobe setting is irrelevant when numvfs is zero
+	 */
+	current_num = nm_platform_sysctl_get_int_checked (platform,
+	                                                  NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                                            ifname,
+	                                                                            "device/sriov_numvfs"),
+	                                                  10, 0, G_MAXUINT, 0);
+	current_autoprobe = nm_platform_sysctl_get_int_checked (platform,
+	                                                        NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                                                  ifname,
+	                                                                                  "device/sriov_drivers_autoprobe"),
+	                                                        10, 0, G_MAXUINT, 0);
+	if (   current_num == num_vfs
+	    && (autoprobe == -1 || current_autoprobe == autoprobe))
 		return TRUE;
 
-	if (current != 0) {
-		/* We need to destroy all other VFs before changing the value */
+	if (current_num != 0) {
+		/* We need to destroy all other VFs before changing any value */
 		if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
 		                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
 		                                                       ifname,
 		                                                      "device/sriov_numvfs"),
 		                             "0")) {
-			_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", 0, strerror (errno));
+			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", strerror (errno));
 			return FALSE;
 		}
-		if (num_vfs == 0)
-			return TRUE;
 	}
 
-	/* Finally, set the desired value */
+	if (num_vfs == 0)
+		return TRUE;
+
+	if (   autoprobe >= 0
+	    && current_autoprobe != autoprobe
+	    && !nm_platform_sysctl_set (NM_PLATFORM_GET,
+	                                NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                          ifname,
+	                                                          "device/sriov_drivers_autoprobe"),
+	                                nm_sprintf_buf (buf, "%d", autoprobe))) {
+		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", autoprobe, strerror (errno));
+		return FALSE;
+	}
+
 	if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
 	                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                       ifname,
 	                                                       "device/sriov_numvfs"),
-	                             nm_sprintf_buf (buf, "%d", num_vfs))) {
+	                             nm_sprintf_buf (buf, "%u", num_vfs))) {
 		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, strerror (errno));
 		return FALSE;
 	}
@@ -5187,6 +5823,101 @@ link_set_sriov_num_vfs (NMPlatform *platform, int ifindex, guint num_vfs)
 	return TRUE;
 }
 
+static gboolean
+link_set_sriov_vfs (NMPlatform *platform, int ifindex, const NMPlatformVF *const *vfs)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *list, *info, *vlan_list;
+	guint i;
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+
+	if (!(list = nla_nest_start (nlmsg, IFLA_VFINFO_LIST)))
+		goto nla_put_failure;
+
+	for (i = 0; vfs[i]; i++) {
+		const NMPlatformVF *vf = vfs[i];
+
+		if (!(info = nla_nest_start (nlmsg, IFLA_VF_INFO)))
+			goto nla_put_failure;
+
+		if (vf->spoofchk >= 0) {
+			struct _ifla_vf_setting ivs = { 0 };
+
+			ivs.vf = vf->index;
+			ivs.setting = vf->spoofchk;
+			NLA_PUT (nlmsg, IFLA_VF_SPOOFCHK, sizeof (ivs), &ivs);
+		}
+
+		if (vf->trust >= 0) {
+			struct _ifla_vf_setting ivs = { 0 };
+
+			ivs.vf = vf->index;
+			ivs.setting = vf->trust;
+			NLA_PUT (nlmsg, IFLA_VF_TRUST, sizeof (ivs), &ivs);
+		}
+
+		if (vf->mac.len) {
+			struct ifla_vf_mac ivm = { 0 };
+
+			ivm.vf = vf->index;
+			memcpy (ivm.mac, vf->mac.data, vf->mac.len);
+			NLA_PUT (nlmsg, IFLA_VF_MAC, sizeof (ivm), &ivm);
+		}
+
+		if (vf->min_tx_rate || vf->max_tx_rate) {
+			struct _ifla_vf_rate ivr = { 0 };
+
+			ivr.vf = vf->index;
+			ivr.min_tx_rate = vf->min_tx_rate;
+			ivr.max_tx_rate = vf->max_tx_rate;
+			NLA_PUT (nlmsg, IFLA_VF_RATE, sizeof (ivr), &ivr);
+		}
+
+		/* Kernel only supports one VLAN per VF now. If this
+		 * changes in the future, we need to figure out how to
+		 * clear existing VLANs and set new ones in one message
+		 * with the new API.*/
+		if (vf->num_vlans > 1) {
+			_LOGW ("multiple VLANs per VF are not supported at the moment");
+			return FALSE;
+		} else {
+			struct _ifla_vf_vlan_info ivvi = { 0 };
+
+			if (!(vlan_list = nla_nest_start (nlmsg, IFLA_VF_VLAN_LIST)))
+				goto nla_put_failure;
+
+			ivvi.vf = vf->index;
+			if (vf->num_vlans == 1) {
+				ivvi.vlan = vf->vlans[0].id;
+				ivvi.qos = vf->vlans[0].qos;
+				ivvi.vlan_proto = htons (vf->vlans[0].proto_ad ? ETH_P_8021AD : ETH_P_8021Q);
+			} else {
+				/* Clear existing VLAN */
+				ivvi.vlan = 0;
+				ivvi.qos = 0;
+				ivvi.vlan_proto = htons (ETH_P_8021Q);
+			}
+
+			NLA_PUT (nlmsg, IFLA_VF_VLAN_INFO, sizeof (ivvi), &ivvi);
+			nla_nest_end (nlmsg, vlan_list);
+		}
+		nla_nest_end (nlmsg, info);
+	}
+	nla_nest_end (nlmsg, list);
+
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
 static char *
 link_get_physical_port_id (NMPlatform *platform, int ifindex)
 {
@@ -5213,7 +5944,7 @@ link_get_dev_id (NMPlatform *platform, int ifindex)
 	                                           16, 0, G_MAXUINT16, 0);
 }
 
-static int
+static gboolean
 vlan_add (NMPlatform *platform,
           const char *name,
           int parent,
@@ -5229,10 +5960,6 @@ vlan_add (NMPlatform *platform,
 	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
 
 	vlan_flags &= (guint32) NM_VLAN_FLAGS_ALL;
-
-	_LOGD ("link: add vlan '%s', parent %d, vlan id %d, flags %X",
-	       name, parent, vlan_id, (unsigned) vlan_flags);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5259,7 +5986,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_gre_add (NMPlatform *platform,
               const char *name,
               const NMPlatformLnkGre *props,
@@ -5268,14 +5995,6 @@ link_gre_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
-
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "gre",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5289,7 +6008,7 @@ link_gre_add (NMPlatform *platform,
 	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
 		goto nla_put_failure;
 
-	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "gre");
+	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;
@@ -5303,18 +6022,20 @@ link_gre_add (NMPlatform *platform,
 	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_U32 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
-	NLA_PUT_U32 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
+	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, NM_LINK_TYPE_GRE, name, nlmsg, out_link);
+	return do_add_link_with_lookup (platform,
+	                                props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE,
+	                                name, nlmsg, out_link);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_ip6tnl_add (NMPlatform *platform,
                  const char *name,
                  const NMPlatformLnkIp6Tnl *props,
@@ -5323,15 +6044,9 @@ link_ip6tnl_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
 	guint32 flowinfo;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ip6tnl",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet6_ntop (&props->local, NULL),
-	       nm_utils_inet6_ntop (&props->remote, buffer));
+	g_return_val_if_fail (!props->is_gre, FALSE);
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5376,7 +6091,69 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+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,
+	                          0,
+	                          0);
+	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);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
+static gboolean
 link_ipip_add (NMPlatform *platform,
                const char *name,
                const NMPlatformLnkIpIp *props,
@@ -5385,14 +6162,6 @@ link_ipip_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
-
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ipip",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5427,7 +6196,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_macsec_add (NMPlatform *platform,
                  const char *name,
                  int parent,
@@ -5438,11 +6207,6 @@ link_macsec_add (NMPlatform *platform,
 	struct nlattr *info;
 	struct nlattr *data;
 
-	_LOGD ("adding macsec '%s' parent %u sci %llx",
-	       name,
-	       parent,
-	       (unsigned long long) props->sci);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5489,7 +6253,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_macvlan_add (NMPlatform *platform,
                   const char *name,
                   int parent,
@@ -5500,12 +6264,6 @@ link_macvlan_add (NMPlatform *platform,
 	struct nlattr *info;
 	struct nlattr *data;
 
-	_LOGD ("adding %s '%s' parent %u mode %u",
-	       props->tap ? "macvtap" : "macvlan",
-	       name,
-	       parent,
-	       props->mode);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5538,7 +6296,7 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static int
+static gboolean
 link_sit_add (NMPlatform *platform,
               const char *name,
               const NMPlatformLnkSit *props,
@@ -5547,14 +6305,6 @@ link_sit_add (NMPlatform *platform,
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	struct nlattr *info;
 	struct nlattr *data;
-	char buffer[INET_ADDRSTRLEN];
-
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "sit",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
 
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
@@ -5658,9 +6408,6 @@ link_vxlan_add (NMPlatform *platform,
 
 	g_return_val_if_fail (props, FALSE);
 
-	_LOGD ("link: add vxlan '%s', parent %d, vxlan id %d",
-	       name, props->parent_ifindex, props->id);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -5718,6 +6465,41 @@ 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,
+	                          0,
+	                          0);
+	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);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
+
+
 static void
 _vlan_change_vlan_qos_mapping_create (gboolean is_ingress_map,
                                       gboolean reset_all,
@@ -5812,9 +6594,6 @@ link_vlan_change (NMPlatform *platform,
 	guint new_n_egress_map = 0;
 	gs_free NMVlanQosMapping *new_ingress_map = NULL;
 	gs_free NMVlanQosMapping *new_egress_map = NULL;
-	char s_flags[64];
-	char s_ingress[256];
-	char s_egress[256];
 
 	obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
 	if (   !obj_cache
@@ -5845,26 +6624,6 @@ link_vlan_change (NMPlatform *platform,
 	                                      &new_egress_map,
 	                                      &new_n_egress_map);
 
-	_LOGD ("link: change %d: vlan:%s%s%s",
-	       ifindex,
-	       flags_mask
-	           ? nm_sprintf_buf (s_flags, " flags 0x%x/0x%x", (unsigned) flags_set, (unsigned) flags_mask)
-	           : "",
-	       new_n_ingress_map
-	           ? nm_platform_vlan_qos_mapping_to_string (" ingress-qos-map",
-	                                                     new_ingress_map,
-	                                                     new_n_ingress_map,
-	                                                     s_ingress,
-	                                                     sizeof (s_ingress))
-	           : "",
-	       new_n_egress_map
-	           ? nm_platform_vlan_qos_mapping_to_string (" egress-qos-map",
-	                                                     new_egress_map,
-	                                                     new_n_egress_map,
-	                                                     s_egress,
-	                                                     sizeof (s_egress))
-	           : "");
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5891,8 +6650,6 @@ link_enslave (NMPlatform *platform, int master, int slave)
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	int ifindex = slave;
 
-	_LOGD ("link: change %d: enslave: master %d", slave, master);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -5980,43 +6737,26 @@ infiniband_partition_delete (NMPlatform *platform, int parent, int p_key)
 
 /*****************************************************************************/
 
-static WifiData *
-wifi_get_wifi_data (NMPlatform *platform, int ifindex)
+static GObject *
+get_ext_data (NMPlatform *platform, int ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPlatformLink *pllink;
-	WifiData *wifi_data;
-
-	wifi_data = g_hash_table_lookup (priv->wifi_data, GINT_TO_POINTER (ifindex));
-	pllink = nm_platform_link_get (platform, ifindex);
-
-	if (!wifi_data) {
-		if (pllink) {
-			if (pllink->type == NM_LINK_TYPE_WIFI)
-				wifi_data = wifi_utils_init (ifindex, TRUE);
-			else if (pllink->type == NM_LINK_TYPE_OLPC_MESH) {
-				/* The kernel driver now uses nl80211, but we force use of WEXT because
-				 * the cfg80211 interactions are not quite ready to support access to
-				 * mesh control through nl80211 just yet.
-				 */
-#if HAVE_WEXT
-				wifi_data = wifi_wext_init (ifindex, FALSE);
-#endif
-			}
+	const NMPObject *obj;
 
-			if (wifi_data)
-				g_hash_table_insert (priv->wifi_data, GINT_TO_POINTER (ifindex), wifi_data);
-		}
-	}
+	obj = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
+	if (!obj)
+		return NULL;
 
-	return wifi_data;
+	return obj->_link.ext_data;
 }
+
+/*****************************************************************************/
+
 #define WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, retval) \
 	nm_auto_pop_netns NMPNetns *netns = NULL; \
-	WifiData *wifi_data; \
+	NMWifiUtils *wifi_data; \
 	if (!nm_platform_netns_push (platform, &netns)) \
 		return retval; \
-	wifi_data = wifi_get_wifi_data (platform, ifindex); \
+	wifi_data = NM_WIFI_UTILS (get_ext_data (platform, ifindex)); \
 	if (!wifi_data) \
 		return retval;
 
@@ -6025,7 +6765,7 @@ wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabiliti
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
 	if (caps)
-		*caps = wifi_utils_get_caps (wifi_data);
+		*caps = nm_wifi_utils_get_caps (wifi_data);
 	return TRUE;
 }
 
@@ -6033,63 +6773,70 @@ static gboolean
 wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_get_bssid (wifi_data, bssid);
+	return nm_wifi_utils_get_bssid (wifi_data, bssid);
 }
 
 static guint32
 wifi_get_frequency (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
-	return wifi_utils_get_freq (wifi_data);
+	return nm_wifi_utils_get_freq (wifi_data);
 }
 
 static gboolean
 wifi_get_quality (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_get_qual (wifi_data);
+	return nm_wifi_utils_get_qual (wifi_data);
 }
 
 static guint32
 wifi_get_rate (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_get_rate (wifi_data);
+	return nm_wifi_utils_get_rate (wifi_data);
 }
 
 static NM80211Mode
 wifi_get_mode (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, NM_802_11_MODE_UNKNOWN);
-	return wifi_utils_get_mode (wifi_data);
+	return nm_wifi_utils_get_mode (wifi_data);
 }
 
 static void
 wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
-	wifi_utils_set_mode (wifi_data, mode);
+	nm_wifi_utils_set_mode (wifi_data, mode);
 }
 
 static void
 wifi_set_powersave (NMPlatform *platform, int ifindex, guint32 powersave)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
-	wifi_utils_set_powersave (wifi_data, powersave);
+	nm_wifi_utils_set_powersave (wifi_data, powersave);
 }
 
 static guint32
 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
-	return wifi_utils_find_freq (wifi_data, freqs);
+	return nm_wifi_utils_find_freq (wifi_data, freqs);
 }
 
 static void
 wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, );
-	wifi_utils_indicate_addressing_running (wifi_data, running);
+	nm_wifi_utils_indicate_addressing_running (wifi_data, running);
+}
+
+static NMSettingWirelessWakeOnWLan
+wifi_get_wake_on_wlan (NMPlatform *platform, int ifindex)
+{
+	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
+	return nm_wifi_utils_get_wake_on_wlan (wifi_data);
 }
 
 static gboolean
@@ -6097,7 +6844,7 @@ wifi_set_wake_on_wlan (NMPlatform *platform, int ifindex,
                        NMSettingWirelessWakeOnWLan wowl)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_set_wake_on_wlan (wifi_data, wowl);
+	return nm_wifi_utils_set_wake_on_wlan (wifi_data, wowl);
 }
 
 /*****************************************************************************/
@@ -6148,21 +6895,56 @@ static guint32
 mesh_get_channel (NMPlatform *platform, int ifindex)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, 0);
-	return wifi_utils_get_mesh_channel (wifi_data);
+	return nm_wifi_utils_get_mesh_channel (wifi_data);
 }
 
 static gboolean
 mesh_set_channel (NMPlatform *platform, int ifindex, guint32 channel)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_set_mesh_channel (wifi_data, channel);
+	return nm_wifi_utils_set_mesh_channel (wifi_data, channel);
 }
 
 static gboolean
 mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 {
 	WIFI_GET_WIFI_DATA_NETNS (wifi_data, platform, ifindex, FALSE);
-	return wifi_utils_set_mesh_ssid (wifi_data, ssid, len);
+	return nm_wifi_utils_set_mesh_ssid (wifi_data, ssid, len);
+}
+
+/*****************************************************************************/
+
+#define WPAN_GET_WPAN_DATA(wpan_data, platform, ifindex, retval) \
+	NMWpanUtils *wpan_data = NM_WPAN_UTILS (get_ext_data (platform, ifindex)); \
+	if (!wpan_data) \
+		return retval;
+
+static guint16
+wpan_get_pan_id (NMPlatform *platform, int ifindex)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, G_MAXINT16);
+	return nm_wpan_utils_get_pan_id (wpan_data);
+}
+
+static gboolean
+wpan_set_pan_id (NMPlatform *platform, int ifindex, guint16 pan_id)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, FALSE);
+	return nm_wpan_utils_set_pan_id (wpan_data, pan_id);
+}
+
+static guint16
+wpan_get_short_addr (NMPlatform *platform, int ifindex)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, G_MAXINT16);
+	return nm_wpan_utils_get_short_addr (wpan_data);
+}
+
+static gboolean
+wpan_set_short_addr (NMPlatform *platform, int ifindex, guint16 short_addr)
+{
+	WPAN_GET_WPAN_DATA (wpan_data, platform, ifindex, FALSE);
+	return nm_wpan_utils_set_short_addr (wpan_data, short_addr);
 }
 
 /*****************************************************************************/
@@ -6179,12 +6961,12 @@ link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 	if (type == NM_LINK_TYPE_ETHERNET)
 		return nmp_utils_ethtool_get_wake_on_lan (ifindex);
 	else if (type == NM_LINK_TYPE_WIFI) {
-		WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
+		NMWifiUtils *wifi_data = NM_WIFI_UTILS (get_ext_data (platform, ifindex));
 
 		if (!wifi_data)
 			return FALSE;
 
-		return wifi_utils_get_wowlan (wifi_data);
+		return nm_wifi_utils_get_wake_on_wlan (wifi_data) != NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE;
 	} else
 		return FALSE;
 }
@@ -6998,6 +7780,14 @@ handle_udev_event (NMUdevClient *udev_client,
 
 /*****************************************************************************/
 
+void
+nm_linux_platform_setup (void)
+{
+	nm_platform_setup (nm_linux_platform_new (FALSE, FALSE));
+}
+
+/*****************************************************************************/
+
 static void
 nm_linux_platform_init (NMLinuxPlatform *self)
 {
@@ -7006,7 +7796,6 @@ nm_linux_platform_init (NMLinuxPlatform *self)
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
 	priv->delayed_action.list_wait_for_nl_response = g_array_new (FALSE, TRUE, sizeof (DelayedActionWaitForNlResponseData));
-	priv->wifi_data = g_hash_table_new_full (nm_direct_hash, NULL, NULL, (GDestroyNotify) wifi_utils_unref);
 }
 
 static void
@@ -7036,6 +7825,18 @@ constructed (GObject *_object)
 	                                   nmp_netns_get_current () == nmp_netns_get_initial () ? "/main" : "")),
 	       nm_platform_get_use_udev (platform) ? "use" : "no");
 
+
+	priv->genl = nl_socket_alloc ();
+	g_assert (priv->genl);
+
+	nle = nl_connect (priv->genl, NETLINK_GENERIC);
+	if (nle) {
+		_LOGE ("unable to connect the generic netlink socket \"%s\" (%d)",
+		       nl_geterror (nle), -nle);
+		nl_socket_free (priv->genl);
+		priv->genl = NULL;
+	}
+
 	priv->nlh = nl_socket_alloc ();
 	g_assert (priv->nlh);
 
@@ -7127,6 +7928,22 @@ constructed (GObject *_object)
 	}
 }
 
+NMPlatform *
+nm_linux_platform_new (gboolean log_with_ptr, gboolean netns_support)
+{
+	gboolean use_udev = FALSE;
+
+	if (   nmp_netns_is_initial ()
+	    && access ("/sys", W_OK) == 0)
+		use_udev = TRUE;
+
+	return g_object_new (NM_TYPE_LINUX_PLATFORM,
+	                     NM_PLATFORM_LOG_WITH_PTR, log_with_ptr,
+	                     NM_PLATFORM_USE_UDEV, use_udev,
+	                     NM_PLATFORM_NETNS_SUPPORT, netns_support,
+	                     NULL);
+}
+
 static void
 dispose (GObject *object)
 {
@@ -7154,12 +7971,12 @@ finalize (GObject *object)
 	g_ptr_array_unref (priv->delayed_action.list_refresh_link);
 	g_array_unref (priv->delayed_action.list_wait_for_nl_response);
 
+	nl_socket_free (priv->genl);
+
 	g_source_remove (priv->event_id);
 	g_io_channel_unref (priv->event_channel);
 	nl_socket_free (priv->nlh);
 
-	g_hash_table_unref (priv->wifi_data);
-
 	if (priv->sysctl_get_prev_values) {
 		sysctl_clear_cache_list = g_slist_remove (sysctl_clear_cache_list, object);
 		g_hash_table_destroy (priv->sysctl_get_prev_values);
@@ -7205,7 +8022,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_get_permanent_address = link_get_permanent_address;
 	platform_class->link_set_mtu = link_set_mtu;
 	platform_class->link_set_name = link_set_name;
-	platform_class->link_set_sriov_num_vfs = link_set_sriov_num_vfs;
+	platform_class->link_set_sriov_params = link_set_sriov_params;
+	platform_class->link_set_sriov_vfs = link_set_sriov_vfs;
 
 	platform_class->link_get_physical_port_id = link_get_physical_port_id;
 	platform_class->link_get_dev_id = link_get_dev_id;
@@ -7238,19 +8056,27 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->wifi_set_powersave = wifi_set_powersave;
 	platform_class->wifi_find_frequency = wifi_find_frequency;
 	platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
+	platform_class->wifi_get_wake_on_wlan = wifi_get_wake_on_wlan;
 	platform_class->wifi_set_wake_on_wlan = wifi_set_wake_on_wlan;
 
 	platform_class->mesh_get_channel = mesh_get_channel;
 	platform_class->mesh_set_channel = mesh_set_channel;
 	platform_class->mesh_set_ssid = mesh_set_ssid;
 
+	platform_class->wpan_get_pan_id = wpan_get_pan_id;
+	platform_class->wpan_set_pan_id = wpan_set_pan_id;
+	platform_class->wpan_get_short_addr = wpan_get_short_addr;
+	platform_class->wpan_set_short_addr = wpan_set_short_addr;
+
 	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-netlink.c b/src/platform/nm-netlink.c
index 4cb19780..fa8a812c 100644
--- a/src/platform/nm-netlink.c
+++ b/src/platform/nm-netlink.c
@@ -52,7 +52,6 @@ struct nl_msg {
 	struct ucred            nm_creds;
 	struct nlmsghdr *       nm_nlh;
 	size_t                  nm_size;
-	int                     nm_refcnt;
 };
 
 struct nl_sock {
@@ -259,20 +258,6 @@ nlmsg_reserve (struct nl_msg *n, size_t len, int pad)
 
 /*****************************************************************************/
 
-static int
- get_default_page_size (void)
-{
-	static int val = 0;
-	int v;
-
-	if (G_UNLIKELY (val == 0)) {
-		v = getpagesize ();
-		g_assert (v > 0);
-		val = v;
-	}
-	return val;
-}
-
 struct nlattr *
 nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
 {
@@ -298,6 +283,22 @@ nla_reserve (struct nl_msg *msg, int attrtype, int attrlen)
 	return nla;
 }
 
+/*****************************************************************************/
+
+static int
+get_default_page_size (void)
+{
+	static int val = 0;
+	int v;
+
+	if (G_UNLIKELY (val == 0)) {
+		v = getpagesize ();
+		g_assert (v > 0);
+		val = v;
+	}
+	return val;
+}
+
 struct nl_msg *
 nlmsg_alloc_size (size_t len)
 {
@@ -308,7 +309,6 @@ nlmsg_alloc_size (size_t len)
 
 	nm = g_slice_new0 (struct nl_msg);
 
-	nm->nm_refcnt = 1;
 	nm->nm_protocol = -1;
 	nm->nm_size = len;
 	nm->nm_nlh = g_malloc0 (len);
@@ -331,48 +331,40 @@ nlmsg_alloc (void)
 	return nlmsg_alloc_size (get_default_page_size ());
 }
 
-/**
- * Allocate a new netlink message with maximum payload size specified.
- */
 struct nl_msg *
-nlmsg_alloc_inherit (struct nlmsghdr *hdr)
+nlmsg_alloc_convert (struct nlmsghdr *hdr)
 {
 	struct nl_msg *nm;
 
-	nm = nlmsg_alloc ();
-	if (hdr) {
-		struct nlmsghdr *new = nm->nm_nlh;
-
-		new->nlmsg_type = hdr->nlmsg_type;
-		new->nlmsg_flags = hdr->nlmsg_flags;
-		new->nlmsg_seq = hdr->nlmsg_seq;
-		new->nlmsg_pid = hdr->nlmsg_pid;
-	}
-
+	nm = nlmsg_alloc_size (NLMSG_ALIGN (hdr->nlmsg_len));
+	memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
 	return nm;
 }
 
 struct nl_msg *
-nlmsg_alloc_convert (struct nlmsghdr *hdr)
+nlmsg_alloc_simple (int nlmsgtype, int flags)
 {
 	struct nl_msg *nm;
+	struct nlmsghdr *new;
 
-	nm = nlmsg_alloc_size (NLMSG_ALIGN (hdr->nlmsg_len));
-	memcpy(nm->nm_nlh, hdr, hdr->nlmsg_len);
+	nm = nlmsg_alloc ();
+	new = nm->nm_nlh;
+	new->nlmsg_type = nlmsgtype;
+	new->nlmsg_flags = flags;
 	return nm;
 }
 
-struct nl_msg *
-nlmsg_alloc_simple (int nlmsgtype, int flags)
+void nlmsg_free (struct nl_msg *msg)
 {
-	struct nlmsghdr nlh = {
-		.nlmsg_type = nlmsgtype,
-		.nlmsg_flags = flags,
-	};
+	if (!msg)
+		return;
 
-	return nlmsg_alloc_inherit (&nlh);
+	g_free (msg->nm_nlh);
+	g_slice_free (struct nl_msg, msg);
 }
 
+/*****************************************************************************/
+
 int
 nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
 {
@@ -386,6 +378,8 @@ nlmsg_append (struct nl_msg *n, void *data, size_t len, int pad)
 	return 0;
 }
 
+/*****************************************************************************/
+
 int
 nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
              int maxtype, const struct nla_policy *policy)
@@ -639,22 +633,6 @@ errout:
 
 /*****************************************************************************/
 
-void nlmsg_free (struct nl_msg *msg)
-{
-	if (!msg)
-		return;
-
-	if (msg->nm_refcnt < 1)
-		g_return_if_reached ();
-
-	msg->nm_refcnt--;
-
-	if (msg->nm_refcnt <= 0) {
-		g_free (msg->nm_nlh);
-		g_slice_free (struct nl_msg, msg);
-	}
-}
-
 int
 nlmsg_get_proto (struct nl_msg *msg)
 {
@@ -813,7 +791,7 @@ int
 genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	int result = -ENOMEM;
+	int nlerr;
 	gint32 response_data = -1;
 	const struct nl_cb cb = {
 		.valid_cb = _genl_parse_getfamily,
@@ -824,31 +802,29 @@ genl_ctrl_resolve (struct nl_sock *sk, const char *name)
 
 	if (!genlmsg_put (msg, NL_AUTO_PORT, NL_AUTO_SEQ, GENL_ID_CTRL,
 	                  0, 0, CTRL_CMD_GETFAMILY, 1))
-		goto out;
+		return -ENOMEM;
 
-	if (nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name) < 0)
-		goto out;
+	nlerr = nla_put_string (msg, CTRL_ATTR_FAMILY_NAME, name);
+	if (nlerr < 0)
+		return nlerr;
 
-	result = nl_send_auto (sk, msg);
-	if (result < 0)
-		goto out;
+	nlerr = nl_send_auto (sk, msg);
+	if (nlerr < 0)
+		return nlerr;
 
-	result = nl_recvmsgs (sk, &cb);
-	if (result < 0)
-		goto out;
+	nlerr = nl_recvmsgs (sk, &cb);
+	if (nlerr < 0)
+		return nlerr;
 
 	/* If search was successful, request may be ACKed after data */
-	result = nl_wait_for_ack (sk, NULL);
-	if (result < 0)
-		goto out;
+	nlerr = nl_wait_for_ack (sk, NULL);
+	if (nlerr < 0)
+		return nlerr;
 
-	if (response_data > 0)
-		result = response_data;
-	else
-		result = -ENOENT;
+	if (response_data < 0)
+		return -NLE_UNSPEC;
 
-out:
-	return result;
+	return response_data;
 }
 
 /*****************************************************************************/
@@ -1131,6 +1107,10 @@ do { \
 		case NL_STOP: \
 			goto stop; \
 		default: \
+			if (err >= 0) { \
+				nm_assert_not_reached (); \
+				err = -NLE_BUG; \
+			} \
 			goto out; \
 		} \
 	} \
@@ -1238,11 +1218,12 @@ continue_reading:
 					else if (err == NL_SKIP)
 						goto skip;
 					else if (err == NL_STOP) {
-						err = -e->error;
+						err = -nl_syserr2nlerr (e->error);
 						goto out;
 					}
+					nm_assert (err == NL_OK);
 				} else {
-					err = -e->error;
+					err = -nl_syserr2nlerr (e->error);
 					goto out;
 				}
 			} else
@@ -1273,6 +1254,7 @@ out:
 	if (interrupted)
 		err = -NLE_DUMP_INTR;
 
+	nm_assert (err <= 0);
 	return err ?: nrecv;
 }
 
@@ -1328,7 +1310,7 @@ nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsign
 		memcpy(CMSG_DATA(cmsg), creds, sizeof (struct ucred));
 	}
 
-	return nl_sendmsg(sk, msg, &hdr);
+	return nl_sendmsg (sk, msg, &hdr);
 }
 
 void
@@ -1365,9 +1347,9 @@ nl_send (struct nl_sock *sk, struct nl_msg *msg)
 
 int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
 {
-	nl_complete_msg(sk, msg);
+	nl_complete_msg (sk, msg);
 
-	return nl_send(sk, msg);
+	return nl_send (sk, msg);
 }
 
 int
@@ -1399,7 +1381,7 @@ nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla,
 	if (page_size == 0)
 		page_size = getpagesize() * 4;
 
-	iov.iov_len = sk->s_bufsize ? : page_size;
+	iov.iov_len = sk->s_bufsize ?: page_size;
 	iov.iov_base = g_malloc (iov.iov_len);
 
 	if (   creds
@@ -1470,7 +1452,7 @@ retry:
 				continue;
 			if (cmsg->cmsg_type != SCM_CREDENTIALS)
 				continue;
-			tmpcreds = g_memdup (CMSG_DATA(cmsg), sizeof (*tmpcreds));
+			tmpcreds = nm_memdup (CMSG_DATA(cmsg), sizeof (*tmpcreds));
 			break;
 		}
 	}
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index 185269ba..187685d8 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -62,7 +62,7 @@ nl_errno (int err)
 	 * normalizes the error and returns its positive value. */
 	return err >= 0
 	       ? err
-	       : ((err == G_MININT) ? NLE_BUG : -errno);
+	       : ((err == G_MININT) ? NLE_BUG : -err);
 }
 
 static inline int
@@ -177,6 +177,12 @@ nla_get_u8 (const struct nlattr *nla)
 }
 
 static inline uint8_t
+nla_get_s8 (const struct nlattr *nla)
+{
+	return *(const int8_t *) nla_data (nla);
+}
+
+static inline uint8_t
 nla_get_u8_cond (/*const*/ struct nlattr *const*tb, int attr, uint8_t default_val)
 {
 	nm_assert (tb);
@@ -197,6 +203,12 @@ nla_get_u32(const struct nlattr *nla)
 	return *(const uint32_t *) nla_data (nla);
 }
 
+static inline int32_t
+nla_get_s32(const struct nlattr *nla)
+{
+	return *(const int32_t *) nla_data (nla);
+}
+
 uint64_t nla_get_u64 (const struct nlattr *nla);
 
 static inline char *
@@ -232,12 +244,18 @@ nla_put_string (struct nl_msg *msg, int attrtype, const char *str)
 #define NLA_PUT_U8(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint8_t, attrtype, value)
 
+#define NLA_PUT_S8(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, int8_t, attrtype, value)
+
 #define NLA_PUT_U16(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint16_t, attrtype, value)
 
 #define NLA_PUT_U32(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint32_t, attrtype, value)
 
+#define NLA_PUT_S32(msg, attrtype, value) \
+	NLA_PUT_TYPE(msg, int32_t, attrtype, value)
+
 #define NLA_PUT_U64(msg, attrtype, value) \
 	NLA_PUT_TYPE(msg, uint64_t, attrtype, value)
 
@@ -296,8 +314,6 @@ struct nl_msg *nlmsg_alloc (void);
 
 struct nl_msg *nlmsg_alloc_size (size_t max);
 
-struct nl_msg *nlmsg_alloc_inherit (struct nlmsghdr *hdr);
-
 struct nl_msg *nlmsg_alloc_convert (struct nlmsghdr *hdr);
 
 struct nl_msg *nlmsg_alloc_simple (int nlmsgtype, int flags);
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 0d224443..216b1547 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -36,12 +36,15 @@
 
 #include "nm-utils.h"
 #include "nm-setting-wired.h"
+#include "nm-ethtool-utils.h"
 
 #include "nm-core-utils.h"
 
-/******************************************************************
+#define ONOFF(bool_val) ((bool_val) ? "on" : "off")
+
+/******************************************************************************
  * utils
- ******************************************************************/
+ *****************************************************************************/
 
 extern char *if_indextoname (unsigned __ifindex, char *__ifname);
 unsigned if_nametoindex (const char *__ifname);
@@ -63,9 +66,45 @@ nmp_utils_if_nametoindex (const char *ifname)
 	return if_nametoindex (ifname);
 }
 
-/******************************************************************
+/*****************************************************************************/
+
+typedef struct {
+	int fd;
+	int ifindex;
+	char ifname[IFNAMSIZ];
+} SocketHandle;
+
+static int
+socket_handle_init (SocketHandle *shandle, int ifindex)
+{
+	if (!nmp_utils_if_indextoname (ifindex, shandle->ifname)) {
+		shandle->ifindex = 0;
+		return -ENODEV;
+	}
+
+	shandle->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+	if (shandle->fd < 0) {
+		shandle->ifindex = 0;
+		return -errno;
+	}
+
+	shandle->ifindex = ifindex;
+	return 0;
+}
+
+static void
+socket_handle_destroy (SocketHandle *shandle)
+{
+	if (shandle->ifindex) {
+		shandle->ifindex = 0;
+		nm_close (shandle->fd);
+	}
+}
+#define nm_auto_socket_handle nm_auto(socket_handle_destroy)
+
+/******************************************************************************
  * ethtool
- ******************************************************************/
+ *****************************************************************************/
 
 NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
 	NM_UTILS_ENUM2STR (ETHTOOL_GDRVINFO,   "ETHTOOL_GDRVINFO"),
@@ -77,6 +116,7 @@ NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
 	NM_UTILS_ENUM2STR (ETHTOOL_GSTATS,     "ETHTOOL_GSTATS"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GSTRINGS,   "ETHTOOL_GSTRINGS"),
 	NM_UTILS_ENUM2STR (ETHTOOL_GWOL,       "ETHTOOL_GWOL"),
+	NM_UTILS_ENUM2STR (ETHTOOL_SFEATURES,  "ETHTOOL_SFEATURES"),
 	NM_UTILS_ENUM2STR (ETHTOOL_SSET,       "ETHTOOL_SSET"),
 	NM_UTILS_ENUM2STR (ETHTOOL_SWOL,       "ETHTOOL_SWOL"),
 );
@@ -87,6 +127,8 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
 	return _ethtool_cmd_to_string (*((guint32 *) edata), buf, len);
 }
 
+/*****************************************************************************/
+
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
 #define ethtool_cmd_speed(pedata) ((pedata)->speed)
 
@@ -94,117 +136,553 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
 	G_STMT_START { (pedata)->speed = (guint16) (speed); } G_STMT_END
 #endif
 
-static gboolean
-ethtool_get (int ifindex, gpointer edata)
+static int
+ethtool_call_handle (SocketHandle *shandle, gpointer edata)
 {
-	char ifname[IFNAMSIZ];
+	struct ifreq ifr = {
+		.ifr_data = edata,
+	};
 	char sbuf[50];
+	int errsv;
+
+	nm_assert (shandle);
+	nm_assert (shandle->ifindex);
+	nm_assert (shandle->ifname[0]);
+	nm_assert (strlen (shandle->ifname) < IFNAMSIZ);
+	nm_assert (edata);
+
+	memcpy (ifr.ifr_name, shandle->ifname, IFNAMSIZ);
+	if (ioctl (shandle->fd, SIOCETHTOOL, &ifr) < 0) {
+		errsv = errno;
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s",
+		              shandle->ifindex,
+		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
+		              shandle->ifname,
+		              strerror (errsv));
+		return -errsv;
+	}
 
-	nm_assert (ifindex > 0);
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
+	              shandle->ifindex,
+	              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
+	              shandle->ifname);
+	return 0;
+}
 
-	/* ethtool ioctl API uses the ifname to refer to an interface. That is racy
-	 * as interfaces can be renamed *sigh*.
-	 *
-	 * Note that we anyway have to verify whether the interface exists, before
-	 * calling ioctl for a non-existing ifname. This is to prevent autoloading
-	 * of kernel modules *sigh*.
-	 * Thus, as we anyway verify the existence of ifname before doing the call,
-	 * go one step further and lookup the ifname everytime anew.
-	 *
-	 * This does not solve the renaming race, but it minimizes the time for
-	 * the race to happen as much as possible. */
+static int
+ethtool_call_ifindex (int ifindex, gpointer edata)
+{
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
+	char sbuf[50];
 
-	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: request fails resolving ifindex: %s",
+	nm_assert (edata);
+
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
 		              ifindex,
 		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              g_strerror (errno));
+		              g_strerror (-r));
+		return r;
+	}
+
+	return ethtool_call_handle (&shandle, edata);
+}
+
+/*****************************************************************************/
+
+static struct ethtool_gstrings *
+ethtool_get_stringset (SocketHandle *shandle, int stringset_id)
+{
+	struct {
+		struct ethtool_sset_info info;
+		guint32 sentinel;
+	} sset_info = { };
+	gs_free struct ethtool_gstrings *gstrings = NULL;
+	guint32 i, len;
+
+	sset_info.info.cmd = ETHTOOL_GSSET_INFO;
+	sset_info.info.reserved = 0;
+	sset_info.info.sset_mask = (1ULL << stringset_id);
+
+	if (ethtool_call_handle (shandle, &sset_info) < 0)
+		return NULL;
+	if (!sset_info.info.sset_mask)
+		return NULL;
+
+	len = sset_info.info.data[0];
+
+	gstrings = g_malloc0 (sizeof (*gstrings) + (len * ETH_GSTRING_LEN));
+	gstrings->cmd = ETHTOOL_GSTRINGS;
+	gstrings->string_set = stringset_id;
+	gstrings->len = len;
+	if (gstrings->len > 0) {
+		if (ethtool_call_handle (shandle, gstrings) < 0)
+			return NULL;
+		for (i = 0; i < gstrings->len; i++) {
+			/* ensure NUL terminated */
+			gstrings->data[i * ETH_GSTRING_LEN + (ETH_GSTRING_LEN - 1)] = '\0';
+		}
+	}
+
+	return g_steal_pointer (&gstrings);
+}
+
+static int
+ethtool_gstrings_find (const struct ethtool_gstrings *gstrings, const char *needle)
+{
+	guint32 i;
+
+	/* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
+	 * that means, we cannot possibly request longer names. */
+	nm_assert (needle && strlen (needle) < ETH_GSTRING_LEN);
+
+	for (i = 0; i < gstrings->len; i++) {
+		if (nm_streq ((char *) &gstrings->data[i * ETH_GSTRING_LEN], needle))
+			return i;
+	}
+	return -1;
+}
+
+static int
+ethtool_get_stringset_index (SocketHandle *shandle, int stringset_id, const char *needle)
+{
+	gs_free struct ethtool_gstrings *gstrings = NULL;
+
+	/* ethtool_get_stringset() always ensures NUL terminated strings at ETH_GSTRING_LEN.
+	 * that means, we cannot possibly request longer names. */
+	nm_assert (needle && strlen (needle) < ETH_GSTRING_LEN);
+
+	gstrings = ethtool_get_stringset (shandle, stringset_id);
+	if (gstrings)
+		return ethtool_gstrings_find (gstrings, needle);
+	return -1;
+}
+
+/*****************************************************************************/
+
+static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = {
+#define ETHT_FEAT(eid, ...) \
+	{ \
+		.ethtool_id = eid, \
+		.n_kernel_names = NM_NARG (__VA_ARGS__), \
+		.kernel_names = ((const char *const[]) { __VA_ARGS__ }), \
+	}
+
+	/* the order does only matter for one thing: if it happens that more than one NMEthtoolID
+	 * reference the same kernel-name, then the one that is mentioned *later* will win in
+	 * case these NMEthtoolIDs are set. That mostly only makes sense for ethtool-ids which
+	 * refer to multiple features ("feature-tso"), while also having more specific ids
+	 * ("feature-tx-tcp-segmentation"). */
+
+	/* names from ethtool utility, which are aliases for multiple features. */
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_SG,                           "tx-scatter-gather",
+	                                                               "tx-scatter-gather-fraglist"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TSO,                          "tx-tcp-segmentation",
+	                                                               "tx-tcp-ecn-segmentation",
+	                                                               "tx-tcp-mangleid-segmentation",
+	                                                               "tx-tcp6-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX,                           "tx-checksum-ipv4",
+	                                                               "tx-checksum-ip-generic",
+	                                                               "tx-checksum-ipv6",
+	                                                               "tx-checksum-fcoe-crc",
+	                                                               "tx-checksum-sctp"),
+
+	/* names from ethtool utility, which are aliases for one feature. */
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GRO,                          "rx-gro"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_GSO,                          "tx-generic-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_LRO,                          "rx-lro"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_NTUPLE,                       "rx-ntuple-filter"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX,                           "rx-checksum"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXHASH,                       "rx-hashing"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RXVLAN,                       "rx-vlan-hw-parse"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TXVLAN,                       "tx-vlan-hw-insert"),
+
+	/* names of features, as known by kernel. */
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD,               "esp-hw-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,       "esp-tx-csum-hw-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_FCOE_MTU,                     "fcoe-mtu"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_HIGHDMA,                      "highdma"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD,                "hw-tc-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD,               "l2-fwd-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_LOOPBACK,                     "loopback"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_ALL,                       "rx-all"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_FCS,                       "rx-fcs"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_GRO_HW,                    "rx-gro-hw"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD,   "rx-udp_tunnel-port-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER,               "rx-vlan-filter"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER,          "rx-vlan-stag-filter"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE,        "rx-vlan-stag-hw-parse"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD,                "tls-hw-record"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD,            "tls-hw-tx-offload"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC,         "tx-checksum-fcoe-crc"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4,             "tx-checksum-ipv4"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6,             "tx-checksum-ipv6"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC,       "tx-checksum-ip-generic"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP,             "tx-checksum-sctp"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION,          "tx-esp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION,         "tx-fcoe-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION,     "tx-gre-csum-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION,          "tx-gre-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL,               "tx-gso-partial"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST,                "tx-gso-robust"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION,       "tx-ipxip4-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION,       "tx-ipxip6-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY,              "tx-nocache-copy"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER,            "tx-scatter-gather"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST,   "tx-scatter-gather-fraglist"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION,         "tx-sctp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,         "tx-tcp6-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION,      "tx-tcp-ecn-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,          "tx-tcp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION,          "tx-udp-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,      "tx-udp_tnl-segmentation"),
+	ETHT_FEAT (NM_ETHTOOL_ID_FEATURE_TX_VLAN_STAG_HW_INSERT,       "tx-vlan-stag-hw-insert"),
+};
+
+/* the number of kernel features that we handle. It essentially is the sum of all
+ * kernel_names. So, all ethtool-ids that reference exactly one kernel-name
+ * (_NM_ETHTOOL_ID_FEATURE_NUM) + some extra, for ethtool-ids that are aliases
+ * for multiple kernel-names. */
+#define N_ETHTOOL_KERNEL_FEATURES (((guint) _NM_ETHTOOL_ID_FEATURE_NUM) + 8u)
+
+static void
+_ASSERT_ethtool_feature_infos (void)
+{
+#if NM_MORE_ASSERTS > 10
+	guint i, k, n;
+	bool found[_NM_ETHTOOL_ID_FEATURE_NUM] = { };
+
+	G_STATIC_ASSERT_EXPR (G_N_ELEMENTS (_ethtool_feature_infos) == _NM_ETHTOOL_ID_FEATURE_NUM);
+
+	n = 0;
+	for (i = 0; i < G_N_ELEMENTS (_ethtool_feature_infos); i++) {
+		NMEthtoolFeatureState kstate;
+		const NMEthtoolFeatureInfo *inf = &_ethtool_feature_infos[i];
+
+		g_assert (inf->ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST);
+		g_assert (inf->ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
+		g_assert (inf->n_kernel_names > 0);
+
+		for (k = 0; k < i; k++)
+			g_assert (inf->ethtool_id != _ethtool_feature_infos[k].ethtool_id);
+
+		g_assert (!found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]);
+		found[inf->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = TRUE;
+
+		kstate.idx_kernel_name = inf->n_kernel_names - 1;
+		g_assert ((guint) kstate.idx_kernel_name == (guint) (inf->n_kernel_names - 1));
+
+		n += inf->n_kernel_names;
+		for (k = 0; k < inf->n_kernel_names; k++) {
+			g_assert (nm_utils_strv_find_first ((char **) inf->kernel_names,
+			                                    k,
+			                                    inf->kernel_names[k]) < 0);
+		}
+	}
+
+	for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
+		g_assert (found[i]);
+
+	g_assert (n == N_ETHTOOL_KERNEL_FEATURES);
+#endif
+}
+
+static NMEthtoolFeatureStates *
+ethtool_get_features (SocketHandle *shandle)
+{
+	gs_free NMEthtoolFeatureStates *states = NULL;
+	gs_free struct ethtool_gstrings *ss_features = NULL;
+
+	_ASSERT_ethtool_feature_infos ();
+
+	ss_features = ethtool_get_stringset (shandle, ETH_SS_FEATURES);
+	if (!ss_features)
+		return NULL;
+
+	if (ss_features->len > 0) {
+		gs_free struct ethtool_gfeatures *gfeatures = NULL;
+		guint idx;
+		const NMEthtoolFeatureState *states_list0 = NULL;
+		const NMEthtoolFeatureState *const*states_plist0 = NULL;
+		guint states_plist_n = 0;
+
+		gfeatures = g_malloc0 (  sizeof (struct ethtool_gfeatures)
+		                       + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0])));
+
+		gfeatures->cmd = ETHTOOL_GFEATURES;
+		gfeatures->size = NM_DIV_ROUND_UP (ss_features->len, 32u);
+		if (ethtool_call_handle (shandle, gfeatures) < 0)
+			return NULL;
+
+		for (idx = 0; idx < G_N_ELEMENTS (_ethtool_feature_infos); idx++) {
+			const NMEthtoolFeatureInfo *info = &_ethtool_feature_infos[idx];
+			guint idx_kernel_name;
+
+			for (idx_kernel_name = 0; idx_kernel_name < info->n_kernel_names; idx_kernel_name++) {
+				NMEthtoolFeatureState *kstate;
+				const char *kernel_name = info->kernel_names[idx_kernel_name];
+				int i_feature;
+				guint i_block;
+				guint32 i_flag;
+
+				i_feature = ethtool_gstrings_find (ss_features, kernel_name);
+				if (i_feature < 0)
+					continue;
+
+				i_block = ((guint) i_feature) / 32u;
+				i_flag = (guint32) (1u << (((guint) i_feature) % 32u));
+
+				if (!states) {
+					states = g_malloc0 (sizeof (NMEthtoolFeatureStates)
+					                    + (N_ETHTOOL_KERNEL_FEATURES * sizeof (NMEthtoolFeatureState))
+					                    + ((N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos)) * sizeof (NMEthtoolFeatureState *)));
+					states_list0 = &states->states_list[0];
+					states_plist0 = (gpointer) &states_list0[N_ETHTOOL_KERNEL_FEATURES];
+					states->n_ss_features = ss_features->len;
+				}
+
+				nm_assert (states->n_states < N_ETHTOOL_KERNEL_FEATURES);
+				kstate = (NMEthtoolFeatureState *) &states_list0[states->n_states];
+				states->n_states++;
+
+				kstate->info = info;
+				kstate->idx_ss_features = i_feature;
+				kstate->idx_kernel_name = idx_kernel_name;
+				kstate->available     = !!(gfeatures->features[i_block].available     & i_flag);
+				kstate->requested     = !!(gfeatures->features[i_block].requested     & i_flag);
+				kstate->active        = !!(gfeatures->features[i_block].active        & i_flag);
+				kstate->never_changed = !!(gfeatures->features[i_block].never_changed & i_flag);
+
+				nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
+
+				if (!states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST])
+					states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST] = &states_plist0[states_plist_n];
+				((const NMEthtoolFeatureState **) states_plist0)[states_plist_n] = kstate;
+				states_plist_n++;
+			}
+
+			if (states && states->states_indexed[info->ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]) {
+				nm_assert (states_plist_n < N_ETHTOOL_KERNEL_FEATURES + G_N_ELEMENTS (_ethtool_feature_infos));
+				nm_assert (!states_plist0[states_plist_n]);
+				states_plist_n++;
+			}
+		}
+	}
+
+	return g_steal_pointer (&states);
+}
+
+NMEthtoolFeatureStates *
+nmp_utils_ethtool_get_features (int ifindex)
+{
+	nm_auto_socket_handle SocketHandle shandle = { };
+	NMEthtoolFeatureStates *features;
+	int r;
+
+	g_return_val_if_fail (ifindex > 0, 0);
+
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "get-features",
+		              g_strerror (-r));
 		return FALSE;
 	}
 
-	{
-		nm_auto_close int fd = -1;
-		struct ifreq ifr = {
-			.ifr_data = edata,
-		};
+	features = ethtool_get_features (&shandle);
 
-		memcpy (ifr.ifr_name, ifname, sizeof (ifname));
+	if (!features) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure getting features",
+		              ifindex,
+		              "get-features");
+		return NULL;
+	}
 
-		fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-		if (fd < 0) {
-			nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed creating socket for ioctl: %s",
-			              ifindex,
-			              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-			              ifname,
-			              g_strerror (errno));
-			return FALSE;
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: retrieved kernel features",
+	              ifindex,
+	              "get-features");
+	return features;
+}
+
+static const char *
+_ethtool_feature_state_to_string (char *buf, gsize buf_size, const NMEthtoolFeatureState *s, const char *prefix)
+{
+	int l;
+
+	l = g_snprintf (buf, buf_size,
+	                "%s %s%s",
+	                prefix ?: "",
+	                ONOFF (s->active),
+	                (!s->available || s->never_changed)
+	                  ? ", [fixed]"
+	                  : ((s->requested != s->active)
+	                       ? (s->requested ? ", [requested on]" : ", [requested off]")
+	                       : ""));
+	nm_assert (l < buf_size);
+	return buf;
+}
+
+gboolean
+nmp_utils_ethtool_set_features (int ifindex,
+                                const NMEthtoolFeatureStates *features,
+                                const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                gboolean do_set /* or reset */)
+{
+	nm_auto_socket_handle SocketHandle shandle = { };
+	gs_free struct ethtool_sfeatures *sfeatures = NULL;
+	int r;
+	guint i, j;
+	struct {
+		const NMEthtoolFeatureState *f_state;
+		NMTernary requested;
+	} set_states[N_ETHTOOL_KERNEL_FEATURES];
+	guint set_states_n = 0;
+	gboolean success = TRUE;
+
+	g_return_val_if_fail (ifindex > 0, 0);
+	g_return_val_if_fail (features, 0);
+	g_return_val_if_fail (requested, 0);
+
+	nm_assert (features->n_states <= N_ETHTOOL_KERNEL_FEATURES);
+
+	for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
+		const NMEthtoolFeatureState *const*states_indexed;
+
+		if (requested[i] == NM_TERNARY_DEFAULT)
+			continue;
+
+		if (!(states_indexed = features->states_indexed[i])) {
+			if (do_set) {
+				nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: set feature %s: skip (not found)",
+				              ifindex,
+				              "set-features",
+				              nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname);
+				success = FALSE;
+			}
+			continue;
 		}
 
-		if (ioctl (fd, SIOCETHTOOL, &ifr) < 0) {
-			nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s",
+		for (j = 0; states_indexed[j]; j++) {
+			const NMEthtoolFeatureState *s = states_indexed[j];
+			char sbuf[255];
+
+			if (set_states_n >= G_N_ELEMENTS (set_states))
+				g_return_val_if_reached (FALSE);
+
+			if (s->never_changed) {
+				nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s (skip feature marked as never changed)",
+				              ifindex,
+				              "set-features",
+				              do_set ? "set" : "reset",
+				              nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+				              s->info->kernel_names[s->idx_kernel_name],
+				              ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
+				              _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:"));
+				continue;
+			}
+
+			nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s feature %s (%s): %s, %s",
 			              ifindex,
-			              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-			              ifname,
-			              strerror (errno));
-			return FALSE;
+			              "set-features",
+			              do_set ? "set" : "reset",
+			              nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+			              s->info->kernel_names[s->idx_kernel_name],
+			              ONOFF (do_set ? requested[i] == NM_TERNARY_TRUE : s->active),
+			              _ethtool_feature_state_to_string (sbuf, sizeof (sbuf), s, do_set ? " currently:" : " before:"));
+
+			if (   do_set
+			    && (!s->available || s->never_changed)
+			    && (s->active != (requested[i] == NM_TERNARY_TRUE))) {
+				/* we request to change a flag which kernel reported as fixed.
+				 * While the ethtool operation will silently succeed, mark the request
+				 * as failure. */
+				success = FALSE;
+			}
+
+			set_states[set_states_n].f_state = s;
+			set_states[set_states_n].requested = requested[i];
+			set_states_n++;
 		}
+	}
 
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
+	if (set_states_n == 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: no feature requested",
 		              ifindex,
-		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              ifname);
+		              "set-features");
 		return TRUE;
 	}
-}
 
-static int
-ethtool_get_stringset_index (int ifindex, int stringset_id, const char *string)
-{
-	gs_free struct ethtool_sset_info *info = NULL;
-	gs_free struct ethtool_gstrings *strings = NULL;
-	guint32 len, i;
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "set-features",
+		              g_strerror (-r));
+		return FALSE;
+	}
 
-	g_return_val_if_fail (ifindex > 0, -1);
+	sfeatures = g_malloc0 (sizeof (struct ethtool_sfeatures)
+	                       + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0])));
+	sfeatures->cmd = ETHTOOL_SFEATURES;
+	sfeatures->size = NM_DIV_ROUND_UP (features->n_ss_features, 32U);
 
-	info = g_malloc0 (sizeof (*info) + sizeof (guint32));
-	info->cmd = ETHTOOL_GSSET_INFO;
-	info->reserved = 0;
-	info->sset_mask = 1ULL << stringset_id;
+	for (i = 0; i < set_states_n; i++) {
+		const NMEthtoolFeatureState *s = set_states[i].f_state;
+		guint i_block;
+		guint32 i_flag;
+		gboolean is_requested;
 
-	if (!ethtool_get (ifindex, info))
-		return -1;
-	if (!info->sset_mask)
-		return -1;
+		i_block = s->idx_ss_features / 32u;
+		i_flag = (guint32) (1u << (s->idx_ss_features % 32u));
 
-	len = info->data[0];
+		sfeatures->features[i_block].valid |= i_flag;
 
-	strings = g_malloc0 (sizeof (*strings) + len * ETH_GSTRING_LEN);
-	strings->cmd = ETHTOOL_GSTRINGS;
-	strings->string_set = stringset_id;
-	strings->len = len;
-	if (!ethtool_get (ifindex, strings))
-		return -1;
+		if (do_set)
+			is_requested = (set_states[i].requested == NM_TERNARY_TRUE);
+		else
+			is_requested = s->active;
 
-	for (i = 0; i < len; i++) {
-		if (!strcmp ((char *) &strings->data[i * ETH_GSTRING_LEN], string))
-			return i;
+		if (is_requested)
+			sfeatures->features[i_block].requested |= i_flag;
+		else
+			sfeatures->features[i_block].requested &= ~i_flag;
 	}
 
-	return -1;
+	if ((r = ethtool_call_handle (&shandle, sfeatures)) < 0) {
+		success = FALSE;
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)",
+		              ifindex,
+		              "set-features",
+		              g_strerror (-r));
+		return FALSE;
+	}
+
+	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: %s",
+	              ifindex,
+	              "set-features",
+	              success
+	                ? "successfully setting features"
+	                : "at least some of the features were not successfuly set");
+	return success;
 }
 
+/*****************************************************************************/
+
 gboolean
 nmp_utils_ethtool_get_driver_info (int ifindex,
                                    NMPUtilsEthtoolDriverInfo *data)
 {
 	struct ethtool_drvinfo *drvinfo;
-	G_STATIC_ASSERT (sizeof (*data) == sizeof (*drvinfo));
-	G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, driver)     == offsetof (struct ethtool_drvinfo, driver));
-	G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, version)    == offsetof (struct ethtool_drvinfo, version));
-	G_STATIC_ASSERT (offsetof (NMPUtilsEthtoolDriverInfo, fw_version) == offsetof (struct ethtool_drvinfo, fw_version));
-	G_STATIC_ASSERT (sizeof (data->driver)     == sizeof (drvinfo->driver));
-	G_STATIC_ASSERT (sizeof (data->version)    == sizeof (drvinfo->version));
-	G_STATIC_ASSERT (sizeof (data->fw_version) == sizeof (drvinfo->fw_version));
+
+	G_STATIC_ASSERT_EXPR (sizeof (*data) == sizeof (*drvinfo));
+	G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, driver)     == offsetof (struct ethtool_drvinfo, driver));
+	G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, version)    == offsetof (struct ethtool_drvinfo, version));
+	G_STATIC_ASSERT_EXPR (offsetof (NMPUtilsEthtoolDriverInfo, fw_version) == offsetof (struct ethtool_drvinfo, fw_version));
+	G_STATIC_ASSERT_EXPR (sizeof (data->driver)     == sizeof (drvinfo->driver));
+	G_STATIC_ASSERT_EXPR (sizeof (data->version)    == sizeof (drvinfo->version));
+	G_STATIC_ASSERT_EXPR (sizeof (data->fw_version) == sizeof (drvinfo->fw_version));
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 	g_return_val_if_fail (data, FALSE);
@@ -213,7 +691,7 @@ nmp_utils_ethtool_get_driver_info (int ifindex,
 
 	memset (drvinfo, 0, sizeof (*drvinfo));
 	drvinfo->cmd = ETHTOOL_GDRVINFO;
-	return ethtool_get (ifindex, drvinfo);
+	return ethtool_call_ifindex (ifindex, drvinfo) >= 0;
 }
 
 gboolean
@@ -233,7 +711,7 @@ nmp_utils_ethtool_get_permanent_address (int ifindex,
 	edata.e.cmd = ETHTOOL_GPERMADDR;
 	edata.e.size = NM_UTILS_HWADDR_LEN_MAX;
 
-	if (!ethtool_get (ifindex, &edata.e))
+	if (ethtool_call_ifindex (ifindex, &edata.e) < 0)
 		return FALSE;
 
 	if (edata.e.size > NM_UTILS_HWADDR_LEN_MAX)
@@ -252,8 +730,8 @@ nmp_utils_ethtool_get_permanent_address (int ifindex,
 		}
 		return FALSE;
 	}
-not_all_0or1:
 
+not_all_0or1:
 	memcpy (buf, edata.e.data, edata.e.size);
 	*length = edata.e.size;
 	return TRUE;
@@ -270,20 +748,30 @@ nmp_utils_ethtool_supports_carrier_detect (int ifindex)
 	 * assume the device supports carrier-detect, otherwise we assume it
 	 * doesn't.
 	 */
-	return ethtool_get (ifindex, &edata);
+	return ethtool_call_ifindex (ifindex, &edata) >= 0;
 }
 
 gboolean
 nmp_utils_ethtool_supports_vlans (int ifindex)
 {
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
 	gs_free struct ethtool_gfeatures *features = NULL;
 	int idx, block, bit, size;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	idx = ethtool_get_stringset_index (ifindex, ETH_SS_FEATURES, "vlan-challenged");
-	if (idx == -1) {
-		nm_log_dbg (LOGD_PLATFORM, "ethtool: vlan-challenged ethtool feature does not exist for %d?", ifindex);
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "support-vlans",
+		              g_strerror (-r));
+		return FALSE;
+	}
+
+	idx = ethtool_get_stringset_index (&shandle, ETH_SS_FEATURES, "vlan-challenged");
+	if (idx < 0) {
+		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: vlan-challenged ethtool feature does not exist?", ifindex);
 		return FALSE;
 	}
 
@@ -295,7 +783,7 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 	features->cmd = ETHTOOL_GFEATURES;
 	features->size = size;
 
-	if (!ethtool_get (ifindex, features))
+	if (ethtool_call_handle (&shandle, features) < 0)
 		return FALSE;
 
 	return !(features->features[block].active & (1 << bit));
@@ -304,21 +792,32 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 int
 nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 {
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
+
 	gs_free struct ethtool_stats *stats = NULL;
 	int peer_ifindex_stat;
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	peer_ifindex_stat = ethtool_get_stringset_index (ifindex, ETH_SS_STATS, "peer_ifindex");
-	if (peer_ifindex_stat == -1) {
-		nm_log_dbg (LOGD_PLATFORM, "ethtool: peer_ifindex stat for %d does not exist?", ifindex);
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
+		              ifindex,
+		              "get-peer-ifindex",
+		              g_strerror (-r));
+		return FALSE;
+	}
+
+	peer_ifindex_stat = ethtool_get_stringset_index (&shandle, ETH_SS_STATS, "peer_ifindex");
+	if (peer_ifindex_stat < 0) {
+		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex);
 		return FALSE;
 	}
 
 	stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64));
 	stats->cmd = ETHTOOL_GSTATS;
 	stats->n_stats = peer_ifindex_stat + 1;
-	if (!ethtool_get (ifindex, stats))
+	if (ethtool_call_ifindex (ifindex, stats) < 0)
 		return 0;
 
 	return stats->data[peer_ifindex_stat];
@@ -333,7 +832,7 @@ nmp_utils_ethtool_get_wake_on_lan (int ifindex)
 
 	memset (&wol, 0, sizeof (wol));
 	wol.cmd = ETHTOOL_GWOL;
-	if (!ethtool_get (ifindex, &wol))
+	if (ethtool_call_ifindex (ifindex, &wol) < 0)
 		return FALSE;
 
 	return wol.wolopts != 0;
@@ -351,7 +850,7 @@ nmp_utils_ethtool_get_link_settings (int ifindex,
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (!ethtool_get (ifindex, &edata))
+	if (ethtool_call_ifindex (ifindex, &edata) < 0)
 		return FALSE;
 
 	if (out_autoneg)
@@ -433,9 +932,13 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 	                      || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), FALSE);
 
 	/* retrieve first current settings */
-	if (!ethtool_get (ifindex, &edata))
+	if (ethtool_call_ifindex (ifindex, &edata) < 0)
 		return FALSE;
 
+	/* FIXME: try first new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API
+	 * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3f1ac7a700d039c61d8d8b99f28d605d489a60cf
+	 */
+
 	/* then change the needed ones */
 	edata.cmd = ETHTOOL_SSET;
 	if (autoneg) {
@@ -485,7 +988,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 		}
 	}
 
-	return ethtool_get (ifindex, &edata);
+	return ethtool_call_ifindex (ifindex, &edata) >= 0;
 }
 
 gboolean
@@ -500,8 +1003,8 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 	if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)
 		return TRUE;
 
-	nm_log_dbg (LOGD_PLATFORM, "setting Wake-on-LAN options 0x%x, password '%s'",
-	            (unsigned) wol, wol_password);
+	nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'",
+	            ifindex, (unsigned) wol, wol_password);
 
 	wol_info.cmd = ETHTOOL_SWOL;
 	wol_info.wolopts = 0;
@@ -521,45 +1024,41 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 
 	if (wol_password) {
 		if (!nm_utils_hwaddr_aton (wol_password, wol_info.sopass, ETH_ALEN)) {
-			nm_log_dbg (LOGD_PLATFORM, "couldn't parse Wake-on-LAN password '%s'", wol_password);
+			nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: couldn't parse Wake-on-LAN password '%s'", ifindex, wol_password);
 			return FALSE;
 		}
 		wol_info.wolopts |= WAKE_MAGICSECURE;
 	}
 
-	return ethtool_get (ifindex, &wol_info);
+	return ethtool_call_ifindex (ifindex, &wol_info) >= 0;
 }
 
-/******************************************************************
+/******************************************************************************
  * mii
- ******************************************************************/
+ *****************************************************************************/
 
 gboolean
 nmp_utils_mii_supports_carrier_detect (int ifindex)
 {
-	char ifname[IFNAMSIZ];
-	nm_auto_close int fd = -1;
+	nm_auto_socket_handle SocketHandle shandle = { };
+	int r;
 	struct ifreq ifr;
 	struct mii_ioctl_data *mii;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: request fails resolving ifindex: %s", ifindex, g_strerror (errno));
-		return FALSE;
-	}
-
-	fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-	if (fd < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: couldn't open control socket: %s", ifindex, ifname, g_strerror (errno));
+	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: failed creating ethtool socket: %s",
+		              ifindex,
+		              g_strerror (-r));
 		return FALSE;
 	}
 
 	memset (&ifr, 0, sizeof (struct ifreq));
-	memcpy (ifr.ifr_name, ifname, IFNAMSIZ);
+	memcpy (ifr.ifr_name, shandle.ifname, IFNAMSIZ);
 
-	if (ioctl (fd, SIOCGMIIPHY, &ifr) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, ifname, strerror (errno));
+	if (ioctl (shandle.fd, SIOCGMIIPHY, &ifr) < 0) {
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, strerror (errno));
 		return FALSE;
 	}
 
@@ -567,18 +1066,18 @@ nmp_utils_mii_supports_carrier_detect (int ifindex)
 	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
 	mii->reg_num = MII_BMSR;
 
-	if (ioctl (fd, SIOCGMIIREG, &ifr) != 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, ifname, strerror (errno));
+	if (ioctl (shandle.fd, SIOCGMIIREG, &ifr) != 0) {
+		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, strerror (errno));
 		return FALSE;
 	}
 
-	nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, ifname, mii->val_out);
+	nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, shandle.ifname, mii->val_out);
 	return TRUE;
 }
 
-/******************************************************************
+/******************************************************************************
  * udev
- ******************************************************************/
+ *****************************************************************************/
 
 const char *
 nmp_utils_udev_get_driver (struct udev_device *udevice)
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index 97f5630a..bad77aee 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -23,6 +23,9 @@
 
 #include "nm-platform.h"
 #include "nm-setting-wired.h"
+#include "nm-ethtool-utils.h"
+
+/*****************************************************************************/
 
 const char *nmp_utils_ethtool_get_driver (int ifindex);
 gboolean nmp_utils_ethtool_supports_carrier_detect (int ifindex);
@@ -35,6 +38,10 @@ gboolean nmp_utils_ethtool_set_wake_on_lan (int ifindex, NMSettingWiredWakeOnLan
 gboolean nmp_utils_ethtool_get_link_settings (int ifindex, gboolean *out_autoneg, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex);
 gboolean nmp_utils_ethtool_set_link_settings (int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex);
 
+gboolean  nmp_utils_ethtool_get_permanent_address (int ifindex,
+                                                   guint8 *buf,
+                                                   size_t *length);
+
 typedef struct {
 	/* We don't want to include <linux/ethtool.h> in header files,
 	 * thus create a ABI compatible version of struct ethtool_drvinfo.*/
@@ -55,9 +62,52 @@ typedef struct {
 gboolean nmp_utils_ethtool_get_driver_info (int ifindex,
                                             NMPUtilsEthtoolDriverInfo *data);
 
-gboolean  nmp_utils_ethtool_get_permanent_address (int ifindex,
-                                                   guint8 *buf,
-                                                   size_t *length);
+typedef struct {
+	NMEthtoolID ethtool_id;
+
+	guint8 n_kernel_names;
+
+	/* one NMEthtoolID refers to one or more kernel_names. The reason for supporting this complexity
+	 * (where one NMSettingEthtool option refers to multiple kernel features)  is to follow what
+	 * ethtool does, where "tx" is an alias for multiple features. */
+	const char *const*kernel_names;
+} NMEthtoolFeatureInfo;
+
+typedef struct {
+	const NMEthtoolFeatureInfo *info;
+
+	guint idx_ss_features;
+
+	/* one NMEthtoolFeatureInfo references one or more kernel_names. This is the index
+	 * of the matching info->kernel_names */
+	guint8 idx_kernel_name;
+
+	bool available:1;
+	bool requested:1;
+	bool active:1;
+	bool never_changed:1;
+} NMEthtoolFeatureState;
+
+struct _NMEthtoolFeatureStates {
+	guint n_states;
+
+	guint n_ss_features;
+
+	/* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
+	const NMEthtoolFeatureState *const*states_indexed[_NM_ETHTOOL_ID_FEATURE_NUM];
+
+	/* the same content, here as a list of n_states entries. */
+	const NMEthtoolFeatureState states_list[];
+};
+
+NMEthtoolFeatureStates *nmp_utils_ethtool_get_features (int ifindex);
+
+gboolean nmp_utils_ethtool_set_features (int ifindex,
+                                         const NMEthtoolFeatureStates *features,
+                                         const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                         gboolean do_set /* or reset */);
+
+/*****************************************************************************/
 
 gboolean nmp_utils_mii_supports_carrier_detect (int ifindex);
 
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index f75019e8..7ddcf41a 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2012 - 2017 Red Hat, Inc.
+ * Copyright (C) 2012 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -27,6 +27,8 @@
 #include <unistd.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
 #include <string.h>
 #include <linux/ip.h>
 #include <linux/if_tun.h>
@@ -74,8 +76,6 @@ G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OF
         } \
     } G_STMT_END
 
-#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
-
 /*****************************************************************************/
 
 static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
@@ -807,6 +807,7 @@ _nm_platform_link_get_by_address_match_link (const NMPObject *obj, struct _nm_pl
  **/
 const NMPlatformLink *
 nm_platform_link_get_by_address (NMPlatform *self,
+                                 NMLinkType link_type,
                                  gconstpointer address,
                                  size_t length)
 {
@@ -827,7 +828,7 @@ nm_platform_link_get_by_address (NMPlatform *self,
 		g_return_val_if_reached (NULL);
 
 	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (self),
-	                                  0, NULL, TRUE, NM_LINK_TYPE_NONE,
+	                                  0, NULL, TRUE, link_type,
 	                                  (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, &d);
 	return NMP_OBJECT_CAST_LINK (obj);
 }
@@ -901,7 +902,7 @@ nm_platform_link_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding link '%s' of type '%s' (%d)"
+	_LOGD ("link: adding link '%s': %s (%d)"
 	       "%s%s" /* address */
 	       "%s%s" /* veth peer */
 	       "",
@@ -985,7 +986,7 @@ nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd)
 	if (!pllink)
 		return FALSE;
 
-	_LOGD ("link: ifindex %d changing network namespace to %d", ifindex, netns_fd);
+	_LOGD ("link: move link %d to network namespace with fd %d", ifindex, netns_fd);
 	return klass->link_set_netns (self, ifindex, netns_fd);
 }
 
@@ -1354,15 +1355,18 @@ nm_platform_link_set_user_ipv6ll_enabled (NMPlatform *self, int ifindex, gboolea
 NMPlatformError
 nm_platform_link_set_address (NMPlatform *self, int ifindex, gconstpointer address, size_t length)
 {
+	gs_free char *mac = NULL;
+
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (ifindex > 0, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (length > 0, NM_PLATFORM_ERROR_BUG);
 
-	_LOGD ("link: setting %s (%d) hardware address",
+	_LOGD ("link: setting %s (%d) hardware address to %s",
 	       nm_strquote_a (20, nm_platform_link_get_name (self, ifindex)),
-	       ifindex);
+	       ifindex,
+	       (mac = nm_utils_hwaddr_ntoa (address, length)));
 	return klass->link_set_address (self, ifindex, address, length);
 }
 
@@ -1459,18 +1463,52 @@ nm_platform_link_supports_sriov (NMPlatform *self, int ifindex)
 	return klass->link_supports_sriov (self, ifindex);
 }
 
+/**
+ * nm_platform_link_set_sriov_params:
+ * @self: platform instance
+ * @ifindex: the index of the interface to change
+ * @num_vfs: the number of VFs to create
+ * @autoprobe: -1 to keep the current autoprobe-drivers value,
+ *   or {0,1} to set a new value
+ */
 gboolean
-nm_platform_link_set_sriov_num_vfs (NMPlatform *self, int ifindex, guint num_vfs)
+nm_platform_link_set_sriov_params (NMPlatform *self,
+                                   int ifindex,
+                                   guint num_vfs,
+                                   int autoprobe)
 {
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_val_if_fail (NM_IN_SET (autoprobe, -1, 0, 1), FALSE);
 
-	_LOGD ("link: setting %u VFs for %s (%d)",
+	_LOGD ("link: setting %u total VFs and autoprobe %d for %s (%d)",
 	       num_vfs,
+	       autoprobe,
 	       nm_strquote_a (25, nm_platform_link_get_name (self, ifindex)),
 	       ifindex);
-	return klass->link_set_sriov_num_vfs (self, ifindex, num_vfs);
+	return klass->link_set_sriov_params (self, ifindex, num_vfs, autoprobe);
+}
+
+gboolean
+nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs)
+{
+	guint i;
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	_LOGD ("link: setting VFs for \"%s\" (%d):",
+	       nm_platform_link_get_name (self, ifindex),
+	       ifindex);
+
+	for (i = 0; vfs[i]; i++) {
+		const NMPlatformVF *vf = vfs[i];
+
+		_LOGD ("link:   VF %s", nm_platform_vf_to_string (vf, NULL, 0));
+	}
+
+	return klass->link_set_sriov_vfs (self, ifindex, vfs);
 }
 
 /**
@@ -1852,6 +1890,12 @@ nm_platform_link_get_lnk_gre (NMPlatform *self, int ifindex, const NMPlatformLin
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_GRE, out_link);
 }
 
+const NMPlatformLnkGre *
+nm_platform_link_get_lnk_gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_GRETAP, out_link);
+}
+
 const NMPlatformLnkInfiniband *
 nm_platform_link_get_lnk_infiniband (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -1864,6 +1908,18 @@ nm_platform_link_get_lnk_ip6tnl (NMPlatform *self, int ifindex, const NMPlatform
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6TNL, out_link);
 }
 
+const NMPlatformLnkIp6Tnl *
+nm_platform_link_get_lnk_ip6gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6GRE, out_link);
+}
+
+const NMPlatformLnkIp6Tnl *
+nm_platform_link_get_lnk_ip6gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_IP6GRETAP, out_link);
+}
+
 const NMPlatformLnkIpIp *
 nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -1912,6 +1968,12 @@ nm_platform_link_get_lnk_vxlan (NMPlatform *self, int ifindex, const NMPlatformL
 	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_VXLAN, out_link);
 }
 
+const NMPlatformLnkWireGuard *
+nm_platform_link_get_lnk_wireguard (NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+	return _link_get_lnk (self, ifindex, NM_LINK_TYPE_WIREGUARD, out_link);
+}
+
 /*****************************************************************************/
 
 /**
@@ -1996,8 +2058,9 @@ nm_platform_link_vlan_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding vlan '%s' parent %d vlanid %d vlanflags %x",
+	_LOGD ("link: adding link '%s': vlan parent %d vlanid %d vlanflags %x",
 	       name, parent, vlanid, vlanflags);
+
 	if (!klass->vlan_add (self, name, parent, vlanid, vlanflags, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
@@ -2028,8 +2091,9 @@ nm_platform_link_vxlan_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding vxlan '%s' parent %d id %d",
-	       name, props->parent_ifindex, props->id);
+	_LOGD ("link: adding link '%s': %s",
+	       name, nm_platform_lnk_vxlan_to_string (props, NULL, 0));
+
 	if (!klass->link_vxlan_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
@@ -2080,13 +2144,78 @@ nm_platform_link_tun_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding tun '%s' %s",
+	_LOGD ("link: adding link '%s': %s",
 	       name, nm_platform_lnk_tun_to_string (props, b, sizeof (b)));
+
 	if (!klass->link_tun_add (self, name, props, out_link, out_fd))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
+/**
+ * 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.
+ */
+NMPlatformError
+nm_platform_link_6lowpan_add (NMPlatform *self,
+                              const char *name,
+                              int parent,
+                              const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_6LOWPAN, out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("adding link '%s': 6lowpan parent %u", name, parent);
+
+	if (!klass->link_6lowpan_add (self, name, parent, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+gboolean
+nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out_parent)
+{
+	const NMPlatformLink *plink;
+	_CHECK_SELF (self, klass, FALSE);
+
+	plink = nm_platform_link_get (self, ifindex);
+
+	if (!plink)
+		return FALSE;
+	if (plink->type != NM_LINK_TYPE_6LOWPAN)
+		return FALSE;
+
+	if (plink->parent != 0) {
+		NM_SET_OUT (out_parent, plink->parent);
+		return TRUE;
+	}
+
+	/* As of 4.16 kernel does not expose the peer_ifindex as IFA_LINK.
+	 * Find the WPAN device with the same MAC address. */
+	if (out_parent) {
+		const NMPlatformLink *parent_plink;
+
+		parent_plink = nm_platform_link_get_by_address (self, NM_LINK_TYPE_WPAN,
+		                                                plink->addr.data,
+		                                                plink->addr.len);
+		NM_SET_OUT (out_parent, parent_plink ? parent_plink->ifindex : -1);
+	}
+
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -2310,23 +2439,18 @@ nm_platform_link_gre_add (NMPlatform *self,
                           const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
 
-	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_GRE, out_link);
+	plerr = _link_add_check_existing (self, name, props->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "gre",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_gre_to_string (props, NULL, 0));
 
 	if (!klass->link_gre_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2372,6 +2496,9 @@ _infiniband_add_add_or_delete (NMPlatform *self,
 		if (!klass->infiniband_partition_add (self, parent, p_key, out_link))
 			return NM_PLATFORM_ERROR_UNSPECIFIED;
 	} else {
+		_LOGD ("link: deleting infiniband partition %s for parent '%s' (%d), key %d",
+		       name, parent_link->name, parent, p_key);
+
 		if (!klass->infiniband_partition_delete (self, parent, p_key))
 			return NM_PLATFORM_ERROR_UNSPECIFIED;
 	}
@@ -2472,23 +2599,19 @@ nm_platform_link_ip6tnl_add (NMPlatform *self,
                              const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET6_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (!props->is_gre, NM_PLATFORM_ERROR_BUG);
 
 	plerr = _link_add_check_existing (self, name, NM_LINK_TYPE_IP6TNL, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ip6tnl",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet6_ntop (&props->local, NULL),
-	       nm_utils_inet6_ntop (&props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
 
 	if (!klass->link_ip6tnl_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2496,6 +2619,46 @@ nm_platform_link_ip6tnl_add (NMPlatform *self,
 }
 
 /**
+ * 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.
+ */
+NMPlatformError
+nm_platform_link_ip6gre_add (NMPlatform *self,
+                             const char *name,
+                             const NMPlatformLnkIp6Tnl *props,
+                             const NMPlatformLink **out_link)
+{
+	NMPlatformError plerr;
+
+	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
+
+	g_return_val_if_fail (props, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (props->is_gre, NM_PLATFORM_ERROR_BUG);
+
+	plerr = _link_add_check_existing (self,
+	                                  name,
+	                                  props->is_tap
+	                                      ? NM_LINK_TYPE_IP6GRETAP
+	                                      : NM_LINK_TYPE_IP6GRE,
+	                                  out_link);
+	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
+		return plerr;
+
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_ip6tnl_to_string (props, NULL, 0));
+
+	if (!klass->link_ip6gre_add (self, name, props, out_link))
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return NM_PLATFORM_ERROR_SUCCESS;
+}
+
+/**
  * nm_platform_ipip_add:
  * @self: platform instance
  * @name: name of the new interface
@@ -2511,7 +2674,6 @@ nm_platform_link_ipip_add (NMPlatform *self,
                            const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
@@ -2522,12 +2684,8 @@ nm_platform_link_ipip_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "ipip",
-	       name,
-		   props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_ipip_to_string (props, NULL, 0));
 
 	if (!klass->link_ipip_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2538,6 +2696,7 @@ nm_platform_link_ipip_add (NMPlatform *self,
  * 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
  *
@@ -2561,10 +2720,8 @@ nm_platform_link_macsec_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("adding macsec '%s' parent %u sci %llx",
-	       name,
-	       parent,
-	       (unsigned long long) props->sci);
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_macsec_to_string (props, NULL, 0));
 
 	if (!klass->link_macsec_add (self, name, parent, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2601,11 +2758,8 @@ nm_platform_link_macvlan_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("adding %s '%s' parent %u mode %u",
-	       props->tap ? "macvtap" : "macvlan",
-	       name,
-	       parent,
-	       props->mode);
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_macvlan_to_string (props, NULL, 0));
 
 	if (!klass->link_macvlan_add (self, name, parent, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2628,7 +2782,6 @@ nm_platform_link_sit_add (NMPlatform *self,
                           const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
-	char buffer[INET_ADDRSTRLEN];
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
@@ -2639,12 +2792,8 @@ nm_platform_link_sit_add (NMPlatform *self,
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD (LOG_FMT_IP_TUNNEL,
-	       "sit",
-	       name,
-	       props->parent_ifindex,
-	       nm_utils_inet4_ntop (props->local, NULL),
-	       nm_utils_inet4_ntop (props->remote, buffer));
+	_LOGD ("adding link '%s': %s",
+	       name, nm_platform_lnk_sit_to_string (props, NULL, 0));
 
 	if (!klass->link_sit_add (self, name, props, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
@@ -2888,6 +3037,16 @@ nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gbo
 	klass->wifi_indicate_addressing_running (self, ifindex, running);
 }
 
+NMSettingWirelessWakeOnWLan
+nm_platform_wifi_get_wake_on_wlan (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wifi_get_wake_on_wlan (self, ifindex);
+}
+
 gboolean
 nm_platform_wifi_set_wake_on_wlan (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl)
 {
@@ -2929,6 +3088,46 @@ nm_platform_mesh_set_ssid (NMPlatform *self, int ifindex, const guint8 *ssid, gs
 	return klass->mesh_set_ssid (self, ifindex, ssid, len);
 }
 
+guint16
+nm_platform_wpan_get_pan_id (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_get_pan_id (self, ifindex);
+}
+
+gboolean
+nm_platform_wpan_set_pan_id (NMPlatform *self, int ifindex, guint16 pan_id)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_set_pan_id (self, ifindex, pan_id);
+}
+
+guint16
+nm_platform_wpan_get_short_addr (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_get_short_addr (self, ifindex);
+}
+
+gboolean
+nm_platform_wpan_set_short_addr (NMPlatform *self, int ifindex, guint16 short_addr)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return klass->wpan_set_short_addr (self, ifindex, short_addr);
+}
+
 #define TO_STRING_DEV_BUF_SIZE (5+15+1)
 static const char *
 _to_string_dev (NMPlatform *self, int ifindex, char *buf, size_t size)
@@ -3014,6 +3213,32 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_platform_link_duplex_type_to_string, NMPlatformLi
 
 /*****************************************************************************/
 
+NMEthtoolFeatureStates *
+nm_platform_ethtool_get_link_features (NMPlatform *self, int ifindex)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, NULL);
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	return nmp_utils_ethtool_get_features (ifindex);
+}
+
+gboolean
+nm_platform_ethtool_set_features (NMPlatform *self,
+                                  int ifindex,
+                                  const NMEthtoolFeatureStates *features,
+                                  const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                  gboolean do_set /* or reset */)
+{
+	_CHECK_SELF_NETNS (self, klass, netns, FALSE);
+
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	return nmp_utils_ethtool_set_features (ifindex, features, requested, do_set);
+}
+
+/*****************************************************************************/
+
 const NMDedupMultiHeadEntry *
 nm_platform_lookup_all (NMPlatform *platform,
                         NMPCacheIdType cache_id_type,
@@ -3622,7 +3847,7 @@ ip6_address_scope_priority (const struct in6_addr *addr)
 	return 3;
 }
 
-static gint
+static int
 ip6_address_scope_cmp (gconstpointer a, gconstpointer b)
 {
 	const NMPlatformIP6Address *x = NMP_OBJECT_CAST_IP6_ADDRESS (*(const void **) a);
@@ -4957,7 +5182,7 @@ nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len
 		return buf;
 
 	g_snprintf (buf, len,
-	            "gre"
+	            lnk->is_tap ? "gretap" : "gre"
 	            "%s" /* remote */
 	            "%s" /* local */
 	            "%s" /* parent_ifindex */
@@ -5014,12 +5239,18 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	char str_encap[30];
 	char str_proto[30];
 	char str_parent_ifindex[30];
+	char *str_type;
 
 	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
 		return buf;
 
+	if (lnk->is_gre)
+		str_type = lnk->is_tap ? "ip6gretap" : "ip6gre";
+	else
+		str_type = "ip6tnl";
+
 	g_snprintf (buf, len,
-	            "ip6tnl"
+	            "%s" /* type */
 	            "%s" /* remote */
 	            "%s" /* local */
 	            "%s" /* parent_ifindex */
@@ -5030,6 +5261,7 @@ nm_platform_lnk_ip6tnl_to_string (const NMPlatformLnkIp6Tnl *lnk, char *buf, gsi
 	            "%s" /* proto */
 	            " 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)),
 	            lnk->parent_ifindex ? nm_sprintf_buf (str_parent_ifindex, " dev %d", lnk->parent_ifindex) : "",
@@ -5114,7 +5346,8 @@ nm_platform_lnk_macvlan_to_string (const NMPlatformLnkMacvlan *lnk, char *buf, g
 		return buf;
 
 	g_snprintf (buf, len,
-	            "macvlan mode %u %s",
+	            "%s mode %u %s",
+	            lnk->tap ? "macvtap" : "macvlan",
 	            lnk->mode,
 	            lnk->no_promisc ? "not-promisc" : "promisc");
 	return buf;
@@ -5298,6 +5531,91 @@ nm_platform_lnk_vxlan_to_string (const NMPlatformLnkVxlan *lnk, char *buf, gsize
 	return buf;
 }
 
+const char *
+nm_platform_wireguard_peer_to_string (const NMPWireGuardPeer *peer, char *buf, gsize len)
+{
+	gs_free char *public_key_b64 = NULL;
+	char s_endpoint[NM_UTILS_INET_ADDRSTRLEN + 100];
+	char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+	guint i;
+
+	nm_utils_to_string_buffer_init (&buf, &len);
+
+	if (peer->endpoint_family == AF_INET) {
+		nm_sprintf_buf (s_endpoint,
+		                " endpoint %s:%u",
+		                nm_utils_inet4_ntop (peer->endpoint_addr.addr4, s_addr),
+		                (guint) peer->endpoint_port);
+	} else if (peer->endpoint_family == AF_INET6) {
+		nm_sprintf_buf (s_endpoint,
+		                " endpoint [%s]:%u",
+		                nm_utils_inet6_ntop (&peer->endpoint_addr.addr6, s_addr),
+		                (guint) peer->endpoint_port);
+	} else
+		s_endpoint[0] = '\0';
+
+	public_key_b64 = g_base64_encode (peer->public_key, sizeof (peer->public_key));
+
+	nm_utils_strbuf_append (&buf, &len,
+	                        "public-key %s"
+	                        "%s" /* preshared-key */
+	                        "%s" /* endpoint */
+	                        " rx %"G_GUINT64_FORMAT
+	                        " tx %"G_GUINT64_FORMAT
+	                        "%s", /* allowed-ips */
+	                        public_key_b64,
+	                        nm_utils_mem_all_zero (peer->preshared_key, sizeof (peer->preshared_key))
+	                          ? ""
+	                          : " preshared-key (hidden)",
+	                        s_endpoint,
+	                        peer->rx_bytes,
+	                        peer->tx_bytes,
+	                        peer->allowed_ips_len > 0
+	                          ? " allowed-ips"
+	                          : "");
+
+	for (i = 0; i < peer->allowed_ips_len; i++) {
+		const NMPWireGuardAllowedIP *allowed_ip = &peer->allowed_ips[i];
+
+		nm_utils_strbuf_append (&buf, &len,
+		                        " %s/%u",
+		                        nm_utils_inet_ntop (allowed_ip->family, &allowed_ip->addr, s_addr),
+		                        allowed_ip->mask);
+	}
+
+	return buf;
+}
+
+const char *
+nm_platform_lnk_wireguard_to_string (const NMPlatformLnkWireGuard *lnk, char *buf, gsize len)
+{
+	gs_free char *public_b64 = NULL;
+
+	if (!nm_utils_to_string_buffer_init_null (lnk, &buf, &len))
+		return buf;
+
+	if (!nm_utils_mem_all_zero (lnk->public_key, sizeof (lnk->public_key)))
+		public_b64 = g_base64_encode (lnk->public_key, sizeof (lnk->public_key));
+
+	g_snprintf (buf, len,
+	            "wireguard"
+	            "%s%s" /* public-key */
+	            "%s" /* private-key */
+	            " listen-port %u"
+	            " fwmark 0x%x",
+	            public_b64
+	              ? " public-key "
+	              : "",
+	            public_b64 ?: "",
+	            nm_utils_mem_all_zero (lnk->private_key, sizeof (lnk->private_key))
+	              ? ""
+	              : " private-key (hidden)",
+	            lnk->listen_port,
+	            lnk->fwmark);
+
+	return buf;
+}
+
 /**
  * nm_platform_ip4_address_to_string:
  * @route: pointer to NMPlatformIP4Address address structure
@@ -5775,6 +6093,56 @@ nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b)
 	return 0;
 }
 
+const char *
+nm_platform_vf_to_string (const NMPlatformVF *vf, char *buf, gsize len)
+{
+	char str_mac[128], mac[128];
+	char str_spoof_check[64];
+	char str_trust[64];
+	char str_min_tx_rate[64];
+	char str_max_tx_rate[64];
+	nm_auto_free_gstring GString *gstr_vlans = NULL;
+	guint i;
+
+	if (!nm_utils_to_string_buffer_init_null (vf, &buf, &len))
+		return buf;
+
+	if (vf->mac.len) {
+		nm_utils_hwaddr_ntoa_buf (vf->mac.data, vf->mac.len, TRUE, mac, sizeof (mac));
+		nm_sprintf_buf (str_mac, " mac %s", mac);
+	} else
+		str_mac[0] = '\0';
+
+	if (vf->num_vlans) {
+		gstr_vlans = g_string_new ("");
+		for (i = 0; i < vf->num_vlans; i++) {
+			g_string_append_printf (gstr_vlans, " vlan %u", (unsigned) vf->vlans[i].id);
+			if (vf->vlans[i].qos)
+				g_string_append_printf (gstr_vlans, " qos %u", (unsigned) vf->vlans[i].qos);
+			if (vf->vlans[i].proto_ad)
+				g_string_append (gstr_vlans, " proto 802.1ad");
+		}
+	}
+
+	g_snprintf (buf, len,
+	            "%u"    /* index */
+	            "%s"    /* MAC */
+	            "%s"    /* spoof check */
+	            "%s"    /* trust */
+	            "%s"    /* min tx rate */
+	            "%s"    /* max tx rate */
+	            "%s",   /* VLANs */
+	            vf->index,
+	            str_mac,
+	            vf->spoofchk >= 0 ? nm_sprintf_buf (str_spoof_check, " spoofchk %d", vf->spoofchk) : "",
+	            vf->trust >= 0 ? nm_sprintf_buf (str_trust, " trust %d", vf->trust) : "",
+	            vf->min_tx_rate ? nm_sprintf_buf (str_min_tx_rate, " min_tx_rate %u", (unsigned) vf->min_tx_rate) : "",
+	            vf->max_tx_rate ? nm_sprintf_buf (str_max_tx_rate, " max_tx_rate %u", (unsigned) vf->max_tx_rate) : "",
+	            gstr_vlans ? gstr_vlans->str : "");
+
+	return buf;
+}
+
 void
 nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h)
 {
@@ -5843,7 +6211,8 @@ nm_platform_lnk_gre_hash_update (const NMPlatformLnkGre *obj, NMHashState *h)
 	                     obj->output_key,
 	                     obj->ttl,
 	                     obj->tos,
-	                     (bool) obj->path_mtu_discovery);
+	                     (bool) obj->path_mtu_discovery,
+	                     (bool) obj->is_tap);
 }
 
 int
@@ -5860,6 +6229,7 @@ nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b)
 	NM_CMP_FIELD (a, b, ttl);
 	NM_CMP_FIELD (a, b, tos);
 	NM_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	NM_CMP_FIELD_BOOL (a, b, is_tap);
 	return 0;
 }
 
@@ -5891,7 +6261,13 @@ nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState
 	                     obj->encap_limit,
 	                     obj->proto,
 	                     obj->flow_label,
-	                     obj->flags);
+	                     obj->flags,
+	                     obj->input_flags,
+	                     obj->output_flags,
+	                     obj->input_key,
+	                     obj->output_key,
+	                     (bool) obj->is_gre,
+	                     (bool) obj->is_tap);
 }
 
 int
@@ -5907,6 +6283,12 @@ nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6
 	NM_CMP_FIELD (a, b, flow_label);
 	NM_CMP_FIELD (a, b, proto);
 	NM_CMP_FIELD (a, b, flags);
+	NM_CMP_FIELD (a, b, input_flags);
+	NM_CMP_FIELD (a, b, output_flags);
+	NM_CMP_FIELD (a, b, input_key);
+	NM_CMP_FIELD (a, b, output_key);
+	NM_CMP_FIELD_BOOL (a, b, is_gre);
+	NM_CMP_FIELD_BOOL (a, b, is_tap);
 	return 0;
 }
 
@@ -6124,6 +6506,27 @@ nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan
 }
 
 void
+nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->listen_port,
+	                     obj->fwmark);
+	nm_hash_update (h, obj->private_key, sizeof (obj->private_key));
+	nm_hash_update (h, obj->public_key, sizeof (obj->public_key));
+}
+
+int
+nm_platform_lnk_wireguard_cmp (const NMPlatformLnkWireGuard *a, const NMPlatformLnkWireGuard *b)
+{
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, listen_port);
+	NM_CMP_FIELD (a, b, fwmark);
+	NM_CMP_FIELD_MEMCMP (a, b, private_key);
+	NM_CMP_FIELD_MEMCMP (a, b, public_key);
+	return 0;
+}
+
+void
 nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashState *h)
 {
 	nm_hash_update_vals (h,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 866df736..11495aff 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2009 - 2017 Red Hat, Inc.
+ * Copyright (C) 2009 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_PLATFORM_H__
@@ -608,6 +608,26 @@ extern const NMPlatformVTableRoute nm_platform_vtable_route_v4;
 extern const NMPlatformVTableRoute nm_platform_vtable_route_v6;
 
 typedef struct {
+	guint16 id;
+	guint32 qos;
+	bool proto_ad:1;
+} NMPlatformVFVlan;
+
+typedef struct {
+	guint32 index;
+	guint32 min_tx_rate;
+	guint32 max_tx_rate;
+	guint num_vlans;
+	NMPlatformVFVlan *vlans;
+	struct {
+		guint8 data[20]; /* NM_UTILS_HWADDR_LEN_MAX */
+		guint8 len;
+	} mac;
+	gint8 spoofchk;
+	gint8 trust;
+} NMPlatformVF;
+
+typedef struct {
 	in_addr_t local;
 	in_addr_t remote;
 	int parent_ifindex;
@@ -618,6 +638,7 @@ typedef struct {
 	guint8 ttl;
 	guint8 tos;
 	bool path_mtu_discovery:1;
+	bool is_tap:1;
 } NMPlatformLnkGre;
 
 typedef struct {
@@ -635,6 +656,14 @@ typedef struct {
 	guint8 proto;
 	guint flow_label;
 	guint32 flags;
+
+	/* IP6GRE only */
+	guint32 input_key;
+	guint32 output_key;
+	guint16 input_flags;
+	guint16 output_flags;
+	bool is_tap:1;
+	bool is_gre:1;
 } NMPlatformLnkIp6Tnl;
 
 typedef struct {
@@ -723,6 +752,16 @@ typedef struct {
 	bool l3miss:1;
 } NMPlatformLnkVxlan;
 
+#define NMP_WIREGUARD_PUBLIC_KEY_LEN 32
+#define NMP_WIREGUARD_SYMMETRIC_KEY_LEN 32
+
+typedef struct {
+	guint32 fwmark;
+	guint16 listen_port;
+	guint8 private_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
+	guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
+} NMPlatformLnkWireGuard;
+
 typedef enum {
 	NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
 	NM_PLATFORM_LINK_DUPLEX_HALF,
@@ -786,7 +825,8 @@ typedef struct {
 	NMPlatformError (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
 	NMPlatformError (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
 	gboolean (*link_set_name) (NMPlatform *, int ifindex, const char *name);
-	gboolean (*link_set_sriov_num_vfs) (NMPlatform *, int ifindex, guint num_vfs);
+	gboolean (*link_set_sriov_params) (NMPlatform *, int ifindex, guint num_vfs, int autoprobe);
+	gboolean (*link_set_sriov_vfs) (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 
 	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
 	guint    (*link_get_dev_id) (NMPlatform *, int ifindex);
@@ -829,6 +869,10 @@ typedef struct {
 	                             const char *name,
 	                             const NMPlatformLnkIp6Tnl *props,
 	                             const NMPlatformLink **out_link);
+	gboolean (*link_ip6gre_add) (NMPlatform *,
+	                             const char *name,
+	                             const NMPlatformLnkIp6Tnl *props,
+	                             const NMPlatformLink **out_link);
 	gboolean (*link_ipip_add) (NMPlatform *,
 	                           const char *name,
 	                           const NMPlatformLnkIpIp *props,
@@ -854,12 +898,16 @@ typedef struct {
 	                          const NMPlatformLink **out_link,
 	                          int *out_fd);
 
+	gboolean (*link_6lowpan_add) (NMPlatform *platform,
+	                              const char *name,
+	                              int parent,
+	                              const NMPlatformLink **out_link);
+
 	gboolean (*infiniband_partition_add) (NMPlatform *, int parent, int p_key, const NMPlatformLink **out_link);
 	gboolean (*infiniband_partition_delete) (NMPlatform *, int parent, int p_key);
 
 	gboolean    (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps);
 	gboolean    (*wifi_get_bssid)        (NMPlatform *, int ifindex, guint8 *bssid);
-	GByteArray *(*wifi_get_ssid)         (NMPlatform *, int ifindex);
 	guint32     (*wifi_get_frequency)    (NMPlatform *, int ifindex);
 	int         (*wifi_get_quality)      (NMPlatform *, int ifindex);
 	guint32     (*wifi_get_rate)         (NMPlatform *, int ifindex);
@@ -868,12 +916,18 @@ typedef struct {
 	void        (*wifi_set_powersave)    (NMPlatform *, int ifindex, guint32 powersave);
 	guint32     (*wifi_find_frequency)   (NMPlatform *, int ifindex, const guint32 *freqs);
 	void        (*wifi_indicate_addressing_running) (NMPlatform *, int ifindex, gboolean running);
+	NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan) (NMPlatform *, int ifindex);
 	gboolean    (*wifi_set_wake_on_wlan) (NMPlatform *, int ifindex, NMSettingWirelessWakeOnWLan wowl);
 
 	guint32     (*mesh_get_channel)      (NMPlatform *, int ifindex);
 	gboolean    (*mesh_set_channel)      (NMPlatform *, int ifindex, guint32 channel);
 	gboolean    (*mesh_set_ssid)         (NMPlatform *, int ifindex, const guint8 *ssid, gsize len);
 
+	guint16     (*wpan_get_pan_id)       (NMPlatform *, int ifindex);
+	gboolean    (*wpan_set_pan_id)       (NMPlatform *, int ifindex, guint16 pan_id);
+	guint16     (*wpan_get_short_addr)   (NMPlatform *, int ifindex);
+	gboolean    (*wpan_set_short_addr)   (NMPlatform *, int ifindex, guint16 short_addr);
+
 	gboolean (*object_delete) (NMPlatform *, const NMPObject *obj);
 
 	gboolean (*ip4_address_add) (NMPlatform *,
@@ -1078,7 +1132,7 @@ const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
                                            gboolean visible_only);
 const NMPlatformLink *nm_platform_link_get (NMPlatform *self, int ifindex);
 const NMPlatformLink *nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname);
-const NMPlatformLink *nm_platform_link_get_by_address (NMPlatform *self, gconstpointer address, size_t length);
+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);
 NMPlatformError nm_platform_link_dummy_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
@@ -1162,7 +1216,8 @@ gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex,
 NMPlatformError nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
 NMPlatformError nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
 gboolean nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name);
-gboolean nm_platform_link_set_sriov_num_vfs (NMPlatform *self, int ifindex, guint num_vfs);
+gboolean nm_platform_link_set_sriov_params (NMPlatform *self, int ifindex, guint num_vfs, int autoprobe);
+gboolean nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 
 char    *nm_platform_link_get_physical_port_id (NMPlatform *self, int ifindex);
 guint    nm_platform_link_get_dev_id (NMPlatform *self, int ifindex);
@@ -1187,7 +1242,10 @@ char *nm_platform_sysctl_slave_get_option (NMPlatform *self, int ifindex, const
 
 const NMPObject *nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link);
 const NMPlatformLnkGre *nm_platform_link_get_lnk_gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkGre *nm_platform_link_get_lnk_gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkIp6Tnl *nm_platform_link_get_lnk_ip6tnl (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIp6Tnl *nm_platform_link_get_lnk_ip6gre (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIp6Tnl *nm_platform_link_get_lnk_ip6gretap (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkInfiniband *nm_platform_link_get_lnk_infiniband (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkIpIp *nm_platform_link_get_lnk_ipip (NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
@@ -1198,6 +1256,7 @@ const NMPlatformLnkSit *nm_platform_link_get_lnk_sit (NMPlatform *self, int ifin
 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 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);
 
 NMPlatformError nm_platform_link_vlan_add (NMPlatform *self,
                                            const char *name,
@@ -1247,12 +1306,18 @@ void        nm_platform_wifi_set_mode         (NMPlatform *self, int ifindex, NM
 void        nm_platform_wifi_set_powersave    (NMPlatform *self, int ifindex, guint32 powersave);
 guint32     nm_platform_wifi_find_frequency   (NMPlatform *self, int ifindex, const guint32 *freqs);
 void        nm_platform_wifi_indicate_addressing_running (NMPlatform *self, int ifindex, gboolean running);
+NMSettingWirelessWakeOnWLan nm_platform_wifi_get_wake_on_wlan (NMPlatform *self, int ifindex);
 gboolean    nm_platform_wifi_set_wake_on_wlan (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl);
 
 guint32     nm_platform_mesh_get_channel      (NMPlatform *self, int ifindex);
 gboolean    nm_platform_mesh_set_channel      (NMPlatform *self, int ifindex, guint32 channel);
 gboolean    nm_platform_mesh_set_ssid         (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
 
+guint16     nm_platform_wpan_get_pan_id       (NMPlatform *platform, int ifindex);
+gboolean    nm_platform_wpan_set_pan_id       (NMPlatform *platform, int ifindex, guint16 pan_id);
+guint16     nm_platform_wpan_get_short_addr   (NMPlatform *platform, int ifindex);
+gboolean    nm_platform_wpan_set_short_addr   (NMPlatform *platform, int ifindex, guint16 short_addr);
+
 void                   nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, guint8 plen);
 const struct in6_addr *nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr);
 
@@ -1266,6 +1331,10 @@ NMPlatformError nm_platform_link_ip6tnl_add (NMPlatform *self,
                                              const char *name,
                                              const NMPlatformLnkIp6Tnl *props,
                                              const NMPlatformLink **out_link);
+NMPlatformError nm_platform_link_ip6gre_add (NMPlatform *self,
+                                             const char *name,
+                                             const NMPlatformLnkIp6Tnl *props,
+                                             const NMPlatformLink **out_link);
 NMPlatformError nm_platform_link_ipip_add (NMPlatform *self,
                                            const char *name,
                                            const NMPlatformLnkIpIp *props,
@@ -1289,6 +1358,13 @@ NMPlatformError nm_platform_link_tun_add (NMPlatform *self,
                                           const NMPlatformLnkTun *props,
                                           const NMPlatformLink **out_link,
                                           int *out_fd);
+NMPlatformError 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);
 
 const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
 
@@ -1375,12 +1451,14 @@ const char *nm_platform_lnk_sit_to_string (const NMPlatformLnkSit *lnk, char *bu
 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_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);
 const char *nm_platform_ip6_address_to_string (const NMPlatformIP6Address *address, char *buf, gsize len);
 const char *nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsize len);
 const char *nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len);
 const char *nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len);
 const char *nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsize len);
+const char *nm_platform_vf_to_string (const NMPlatformVF *vf, char *buf, gsize len);
 
 const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     const NMVlanQosMapping *map,
@@ -1388,6 +1466,11 @@ const char *nm_platform_vlan_qos_mapping_to_string (const char *name,
                                                     char *buf,
                                                     gsize len);
 
+struct _NMPWireGuardPeer;
+const char *nm_platform_wireguard_peer_to_string (const struct _NMPWireGuardPeer *peer,
+                                                  char *buf,
+                                                  gsize len);
+
 int nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b);
 int nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b);
 int nm_platform_lnk_infiniband_cmp (const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b);
@@ -1399,6 +1482,7 @@ int nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *
 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_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);
 int nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b);
 
@@ -1435,6 +1519,7 @@ void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *
 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_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h);
+void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, NMHashState *h);
 
 void nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h);
 void nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h);
@@ -1452,6 +1537,17 @@ int nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPla
 gboolean nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, int ifindex, NMSettingWiredWakeOnLan wol, const char *wol_password);
 gboolean nm_platform_ethtool_set_link_settings (NMPlatform *self, int ifindex, gboolean autoneg, guint32 speed, NMPlatformLinkDuplexType duplex);
 gboolean nm_platform_ethtool_get_link_settings (NMPlatform *self, int ifindex, gboolean *out_autoneg, guint32 *out_speed, NMPlatformLinkDuplexType *out_duplex);
+
+typedef struct _NMEthtoolFeatureStates NMEthtoolFeatureStates;
+
+NMEthtoolFeatureStates *nm_platform_ethtool_get_link_features (NMPlatform *self,
+                                                               int ifindex);
+gboolean nm_platform_ethtool_set_features (NMPlatform *self,
+                                           int ifindex,
+                                           const NMEthtoolFeatureStates *features,
+                                           const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
+                                           gboolean do_set /* or reset */);
+
 const char * nm_platform_link_duplex_type_to_string (NMPlatformLinkDuplexType duplex);
 
 void nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 50e6825b..fdc27440 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2015 - 2017 Red Hat, Inc.
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -27,10 +27,14 @@
 #include <libudev.h>
 
 #include "nm-utils.h"
+#include "nm-utils/nm-secret-utils.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
 
+#include "wifi/nm-wifi-utils.h"
+#include "wpan/nm-wpan-utils.h"
+
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN LOGD_PLATFORM
@@ -337,12 +341,103 @@ _vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
 		g_clear_pointer (dst_map, g_free);
 		*dst_n_map = src_n_map;
 		if (src_n_map > 0)
-			*dst_map = g_memdup (src_map, sizeof (*src_map) * src_n_map);
+			*dst_map = nm_memdup (src_map, sizeof (*src_map) * src_n_map);
 	}
 }
 
 /*****************************************************************************/
 
+static void
+_wireguard_allowed_ip_hash_update (const NMPWireGuardAllowedIP *ip,
+                                   NMHashState *h)
+{
+	nm_hash_update_vals (h, ip->family,
+	                        ip->mask);
+
+	if (ip->family == AF_INET)
+		nm_hash_update_val (h, ip->addr.addr4);
+	else if (ip->family == AF_INET6)
+		nm_hash_update_val (h, ip->addr.addr6);
+}
+
+static int
+_wireguard_allowed_ip_cmp (const NMPWireGuardAllowedIP *a,
+                           const NMPWireGuardAllowedIP *b)
+{
+	NM_CMP_SELF (a, b);
+
+	NM_CMP_FIELD (a, b, family);
+	NM_CMP_FIELD (a, b, mask);
+
+	if (a->family == AF_INET)
+		NM_CMP_FIELD (a, b, addr.addr4);
+	else if (a->family == AF_INET6)
+		NM_CMP_FIELD_IN6ADDR (a, b, addr.addr6);
+
+	return 0;
+}
+
+static void
+_wireguard_peer_hash_update (const NMPWireGuardPeer *peer,
+                             NMHashState *h)
+{
+	guint i;
+
+	nm_hash_update (h, peer->public_key, sizeof (peer->public_key));
+	nm_hash_update (h, peer->preshared_key, sizeof (peer->preshared_key));
+	nm_hash_update_vals (h,
+	                     peer->persistent_keepalive_interval,
+	                     peer->allowed_ips_len,
+	                     peer->rx_bytes,
+	                     peer->tx_bytes,
+	                     peer->last_handshake_time.tv_sec,
+	                     peer->last_handshake_time.tv_nsec,
+	                     peer->endpoint_port,
+	                     peer->endpoint_family);
+
+	if (peer->endpoint_family == AF_INET)
+		nm_hash_update_val (h, peer->endpoint_addr.addr4);
+	else if (peer->endpoint_family == AF_INET6)
+		nm_hash_update_val (h, peer->endpoint_addr.addr6);
+
+	for (i = 0; i < peer->allowed_ips_len; i++)
+		_wireguard_allowed_ip_hash_update (&peer->allowed_ips[i], h);
+}
+
+static int
+_wireguard_peer_cmp (const NMPWireGuardPeer *a,
+                     const NMPWireGuardPeer *b)
+{
+	guint i;
+
+	NM_CMP_SELF (a, b);
+
+	NM_CMP_FIELD (a, b, last_handshake_time.tv_sec);
+	NM_CMP_FIELD (a, b, last_handshake_time.tv_nsec);
+	NM_CMP_FIELD (a, b, rx_bytes);
+	NM_CMP_FIELD (a, b, tx_bytes);
+	NM_CMP_FIELD (a, b, allowed_ips_len);
+	NM_CMP_FIELD (a, b, persistent_keepalive_interval);
+	NM_CMP_FIELD (a, b, endpoint_port);
+	NM_CMP_FIELD (a, b, endpoint_family);
+	NM_CMP_FIELD_MEMCMP (a, b, public_key);
+	NM_CMP_FIELD_MEMCMP (a, b, preshared_key);
+
+	if (a->endpoint_family == AF_INET)
+		NM_CMP_FIELD (a, b, endpoint_addr.addr4);
+	else if (a->endpoint_family == AF_INET6)
+		NM_CMP_FIELD_IN6ADDR (a, b, endpoint_addr.addr6);
+
+	for (i = 0; i < a->allowed_ips_len; i++) {
+		NM_CMP_RETURN (_wireguard_allowed_ip_cmp (&a->allowed_ips[i],
+		                                          &b->allowed_ips[i]));
+	}
+
+	return 0;
+}
+
+/*****************************************************************************/
+
 static const char *
 _link_get_driver (struct udev_device *udevice, const char *kind, int ifindex)
 {
@@ -456,6 +551,7 @@ _vt_cmd_obj_dispose_link (NMPObject *obj)
 		udev_device_unref (obj->_link.udev.device);
 		obj->_link.udev.device = NULL;
 	}
+	g_clear_object (&obj->_link.ext_data);
 	nmp_object_unref (obj->_link.netlink.lnk);
 }
 
@@ -466,6 +562,28 @@ _vt_cmd_obj_dispose_lnk_vlan (NMPObject *obj)
 	g_free ((gpointer) obj->_lnk_vlan.egress_qos_map);
 }
 
+static void
+_wireguard_clear (NMPObjectLnkWireGuard *lnk)
+{
+	guint i;
+
+	nm_explicit_bzero (lnk->_public.private_key,
+	                   sizeof (lnk->_public.private_key));
+	for (i = 0; i < lnk->peers_len; i++) {
+		NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &lnk->peers[i];
+
+		nm_explicit_bzero (peer->preshared_key, sizeof (peer->preshared_key));
+	}
+	g_free ((gpointer) lnk->peers);
+	g_free ((gpointer) lnk->_allowed_ips_buf);
+}
+
+static void
+_vt_cmd_obj_dispose_lnk_wireguard (NMPObject *obj)
+{
+	_wireguard_clear (&obj->_lnk_wireguard);
+}
+
 static NMPObject *
 _nmp_object_new_from_class (const NMPClass *klass)
 {
@@ -631,31 +749,35 @@ static const char *
 _vt_cmd_obj_to_string_link (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
 {
 	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
-	char buf2[sizeof (_nm_utils_to_string_buffer)];
-	char buf3[sizeof (_nm_utils_to_string_buffer)];
+	char *b = buf;
 
 	switch (to_string_mode) {
 	case NMP_OBJECT_TO_STRING_ID:
 		return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size);
 	case NMP_OBJECT_TO_STRING_ALL:
-		g_snprintf (buf, buf_size,
-		            "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; %s]",
-		            klass->obj_type_name, obj, obj->parent._ref_count,
-		            nmp_object_is_alive (obj) ? '+' : '-',
-		            nmp_object_is_visible (obj) ? '+' : '-',
-		            obj->_link.netlink.is_in_netlink ? '+' : '-',
-		            obj->_link.udev.device,
-		            nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)));
+		nm_utils_strbuf_append (&b, &buf_size,
+		                        "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; ",
+		                        klass->obj_type_name, obj, obj->parent._ref_count,
+		                        nmp_object_is_alive (obj) ? '+' : '-',
+		                        nmp_object_is_visible (obj) ? '+' : '-',
+		                        obj->_link.netlink.is_in_netlink ? '+' : '-',
+		                        obj->_link.udev.device);
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, b, buf_size);
+		nm_utils_strbuf_seek_end (&b, &buf_size);
+		if (obj->_link.netlink.lnk) {
+			nm_utils_strbuf_append_str (&b, &buf_size, "; ");
+			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_ALL, b, buf_size);
+			nm_utils_strbuf_seek_end (&b, &buf_size);
+		}
+		nm_utils_strbuf_append_c (&b, &buf_size, ']');
 		return buf;
 	case NMP_OBJECT_TO_STRING_PUBLIC:
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, b, buf_size);
 		if (obj->_link.netlink.lnk) {
-			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf2, sizeof (buf2));
-			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, buf3, sizeof (buf3));
-			g_snprintf (buf, buf_size,
-			            "%s; %s",
-			            buf2, buf3);
-		} else
-			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+			nm_utils_strbuf_seek_end (&b, &buf_size);
+			nm_utils_strbuf_append_str (&b, &buf_size, "; ");
+			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, b, buf_size);
+		}
 		return buf;
 	default:
 		g_return_val_if_reached ("ERROR");
@@ -720,6 +842,55 @@ _vt_cmd_obj_to_string_lnk_vlan (const NMPObject *obj, NMPObjectToStringMode to_s
 	}
 }
 
+static const char *
+_vt_cmd_obj_to_string_lnk_wireguard (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
+{
+	const NMPClass *klass;
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
+	char *b;
+	guint i;
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	switch (to_string_mode) {
+	case NMP_OBJECT_TO_STRING_ID:
+		g_snprintf (buf, buf_size, "%p", obj);
+		return buf;
+	case NMP_OBJECT_TO_STRING_ALL:
+		b = buf;
+
+		nm_utils_strbuf_append (&b, &buf_size,
+		                        "[%s,%p,%u,%calive,%cvisible; %s"
+		                        "%s",
+		                        klass->obj_type_name, obj, obj->parent._ref_count,
+		                        nmp_object_is_alive (obj) ? '+' : '-',
+		                        nmp_object_is_visible (obj) ? '+' : '-',
+		                        nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)),
+		                        obj->_lnk_wireguard.peers_len > 0
+		                          ? " peers {"
+		                          : "");
+
+		for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) {
+			const NMPWireGuardPeer *peer = &obj->_lnk_wireguard.peers[i];
+
+			nm_utils_strbuf_append_str (&b, &buf_size, " { ");
+			nm_platform_wireguard_peer_to_string (peer, b, buf_size);
+			nm_utils_strbuf_seek_end (&b, &buf_size);
+			nm_utils_strbuf_append_str (&b, &buf_size, " }");
+		}
+		if (obj->_lnk_wireguard.peers_len)
+			nm_utils_strbuf_append_str (&b, &buf_size, " }");
+
+		return buf;
+	case NMP_OBJECT_TO_STRING_PUBLIC:
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+
+		return buf;
+	default:
+		g_return_val_if_reached ("ERROR");
+	}
+}
+
 #define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \
 static const char * \
 _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsize buf_len) \
@@ -768,6 +939,7 @@ _vt_cmd_obj_hash_update_link (const NMPObject *obj, NMHashState *h)
 	nm_platform_link_hash_update (&obj->link, h);
 	nm_hash_update_vals (h,
 	                     obj->_link.netlink.is_in_netlink,
+	                     obj->_link.wireguard_family_id,
 	                     obj->_link.udev.device);
 	if (obj->_link.netlink.lnk)
 		nmp_object_hash_update (obj->_link.netlink.lnk, h);
@@ -787,17 +959,26 @@ _vt_cmd_obj_hash_update_lnk_vlan (const NMPObject *obj, NMHashState *h)
 	                                       h);
 }
 
+static void
+_vt_cmd_obj_hash_update_lnk_wireguard (const NMPObject *obj, NMHashState *h)
+{
+	guint i;
+
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD);
+
+	nm_platform_lnk_wireguard_hash_update (&obj->lnk_wireguard, h);
+
+	nm_hash_update_val (h, obj->_lnk_wireguard.peers_len);
+	for (i = 0; i < obj->_lnk_wireguard.peers_len; i++)
+		_wireguard_peer_hash_update (&obj->_lnk_wireguard.peers[i], h);
+}
+
 int
 nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 {
 	const NMPClass *klass1, *klass2;
 
-	if (obj1 == obj2)
-		return 0;
-	if (!obj1)
-		return -1;
-	if (!obj2)
-		return 1;
+	NM_CMP_SELF (obj1, obj2);
 
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), -1);
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), 1);
@@ -818,16 +999,11 @@ nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 static int
 _vt_cmd_obj_cmp_link (const NMPObject *obj1, const NMPObject *obj2)
 {
-	int i;
+	NM_CMP_RETURN (nm_platform_link_cmp (&obj1->link, &obj2->link));
+	NM_CMP_DIRECT (obj1->_link.netlink.is_in_netlink, obj2->_link.netlink.is_in_netlink);
+	NM_CMP_RETURN (nmp_object_cmp (obj1->_link.netlink.lnk, obj2->_link.netlink.lnk));
+	NM_CMP_DIRECT (obj1->_link.wireguard_family_id, obj2->_link.wireguard_family_id);
 
-	i = nm_platform_link_cmp (&obj1->link, &obj2->link);
-	if (i)
-		return i;
-	if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink)
-		return obj1->_link.netlink.is_in_netlink ? -1 : 1;
-	i = nmp_object_cmp (obj1->_link.netlink.lnk, obj2->_link.netlink.lnk);
-	if (i)
-		return i;
 	if (obj1->_link.udev.device != obj2->_link.udev.device) {
 		if (!obj1->_link.udev.device)
 			return -1;
@@ -840,6 +1016,7 @@ _vt_cmd_obj_cmp_link (const NMPObject *obj1, const NMPObject *obj2)
 		 * Have this check as very last. */
 		return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1;
 	}
+
 	return 0;
 }
 
@@ -865,6 +1042,21 @@ _vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2)
 	return c;
 }
 
+static int
+_vt_cmd_obj_cmp_lnk_wireguard (const NMPObject *obj1, const NMPObject *obj2)
+{
+	guint i;
+
+	NM_CMP_RETURN (nm_platform_lnk_wireguard_cmp (&obj1->lnk_wireguard, &obj2->lnk_wireguard));
+
+	NM_CMP_FIELD (obj1, obj2, _lnk_wireguard.peers_len);
+
+	for (i = 0; i < obj1->_lnk_wireguard.peers_len; i++)
+		NM_CMP_RETURN (_wireguard_peer_cmp (&obj1->_lnk_wireguard.peers[i], &obj2->_lnk_wireguard.peers[i]));
+
+	return 0;
+}
+
 /* @src is a const object, which is not entirely correct for link types, where
  * we increase the ref count for src->_link.udev.device.
  * Hence, nmp_object_copy() can violate the const promise of @src.
@@ -908,6 +1100,12 @@ _vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src)
 			nmp_object_unref (dst->_link.netlink.lnk);
 		dst->_link.netlink.lnk = src->_link.netlink.lnk;
 	}
+	if (dst->_link.ext_data != src->_link.ext_data) {
+		if (dst->_link.ext_data)
+			g_clear_object (&dst->_link.ext_data);
+		if (src->_link.ext_data)
+			dst->_link.ext_data = g_object_ref (src->_link.ext_data);
+	}
 	dst->_link = src->_link;
 }
 
@@ -925,6 +1123,41 @@ _vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src)
 	                               src->_lnk_vlan.egress_qos_map);
 }
 
+static void
+_vt_cmd_obj_copy_lnk_wireguard (NMPObject *dst, const NMPObject *src)
+{
+	guint i;
+
+	nm_assert (dst != src);
+
+	_wireguard_clear (&dst->_lnk_wireguard);
+
+	dst->_lnk_wireguard = src->_lnk_wireguard;
+
+	dst->_lnk_wireguard.peers = nm_memdup (dst->_lnk_wireguard.peers,
+	                                       sizeof (NMPWireGuardPeer) * dst->_lnk_wireguard.peers_len);
+	dst->_lnk_wireguard._allowed_ips_buf = nm_memdup (dst->_lnk_wireguard._allowed_ips_buf,
+	                                                  sizeof (NMPWireGuardAllowedIP) * dst->_lnk_wireguard._allowed_ips_buf_len);
+
+	/* all the peers' pointers point into the buffer. They need to be readjusted. */
+	for (i = 0; i < dst->_lnk_wireguard.peers_len; i++) {
+		NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &dst->_lnk_wireguard.peers[i];
+
+		if (peer->allowed_ips_len == 0) {
+			nm_assert (!peer->allowed_ips);
+			continue;
+		}
+		nm_assert (dst->_lnk_wireguard._allowed_ips_buf_len > 0);
+		nm_assert (src->_lnk_wireguard._allowed_ips_buf);
+		nm_assert (peer->allowed_ips >= src->_lnk_wireguard._allowed_ips_buf);
+		nm_assert (&peer->allowed_ips[peer->allowed_ips_len] <= &src->_lnk_wireguard._allowed_ips_buf[src->_lnk_wireguard._allowed_ips_buf_len]);
+
+		peer->allowed_ips = &dst->_lnk_wireguard._allowed_ips_buf[peer->allowed_ips - src->_lnk_wireguard._allowed_ips_buf];
+	}
+
+	nm_assert (nmp_object_equal (src, dst));
+}
+
 #define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \
 static void \
 _vt_cmd_plobj_id_copy_##type (NMPlatformObject *_dst, const NMPlatformObject *_src) \
@@ -2702,6 +2935,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_gre_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_GRETAP - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_GRETAP,
+		.sizeof_data                        = sizeof (NMPObjectLnkGre),
+		.sizeof_public                      = sizeof (NMPlatformLnkGre),
+		.obj_type_name                      = "gretap",
+		.lnk_link_type                      = NM_LINK_TYPE_GRETAP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_gre_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_gre_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp,
+	},
 	[NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_INFINIBAND,
@@ -2724,6 +2968,28 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_IP6GRE - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IP6GRE,
+		.sizeof_data                        = sizeof (NMPObjectLnkIp6Tnl),
+		.sizeof_public                      = sizeof (NMPlatformLnkIp6Tnl),
+		.obj_type_name                      = "ip6gre",
+		.lnk_link_type                      = NM_LINK_TYPE_IP6GRE,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_IP6GRETAP - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IP6GRETAP,
+		.sizeof_data                        = sizeof (NMPObjectLnkIp6Tnl),
+		.sizeof_public                      = sizeof (NMPlatformLnkIp6Tnl),
+		.obj_type_name                      = "ip6gretap",
+		.lnk_link_type                      = NM_LINK_TYPE_IP6GRETAP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
+	},
 	[NMP_OBJECT_TYPE_LNK_IPIP - 1] = {
 		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_IPIP,
@@ -2817,5 +3083,20 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vxlan_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vxlan_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_WIREGUARD - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_WIREGUARD,
+		.sizeof_data                        = sizeof (NMPObjectLnkWireGuard),
+		.sizeof_public                      = sizeof (NMPlatformLnkWireGuard),
+		.obj_type_name                      = "wireguard",
+		.lnk_link_type                      = NM_LINK_TYPE_WIREGUARD,
+		.cmd_obj_hash_update                = _vt_cmd_obj_hash_update_lnk_wireguard,
+		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_lnk_wireguard,
+		.cmd_obj_copy                       = _vt_cmd_obj_copy_lnk_wireguard,
+		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_wireguard,
+		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_wireguard,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_wireguard_to_string,
+		.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,
+	},
 };
-
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index f473f462..97be8321 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2015 - 2017 Red Hat, Inc.
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NMP_OBJECT_H__
@@ -27,6 +27,36 @@
 
 struct udev_device;
 
+/*****************************************************************************/
+
+typedef struct {
+	NMIPAddr addr;
+	guint8 family;
+	guint8 mask;
+} NMPWireGuardAllowedIP;
+
+typedef struct _NMPWireGuardPeer {
+	NMIPAddr endpoint_addr;
+	struct timespec last_handshake_time;
+	guint64 rx_bytes;
+	guint64 tx_bytes;
+	union {
+		const NMPWireGuardAllowedIP *allowed_ips;
+		guint _construct_idx_start;
+	};
+	union {
+		guint allowed_ips_len;
+		guint _construct_idx_end;
+	};
+	guint16 persistent_keepalive_interval;
+	guint16 endpoint_port;
+	guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN];
+	guint8 preshared_key[NMP_WIREGUARD_SYMMETRIC_KEY_LEN];
+	guint8 endpoint_family;
+} NMPWireGuardPeer;
+
+/*****************************************************************************/
+
 typedef enum { /*< skip >*/
 	NMP_OBJECT_TO_STRING_ID,
 	NMP_OBJECT_TO_STRING_PUBLIC,
@@ -164,6 +194,14 @@ typedef struct {
 		 */
 		struct udev_device *device;
 	} udev;
+
+	/* Auxiliary data object for Wi-Fi and WPAN */
+	GObject *ext_data;
+
+	/* FIXME: not every NMPObjectLink should pay the price for tracking
+	 * the wireguard family id. This should be tracked via ext_data, which
+	 * would be exactly the right place. */
+	int wireguard_family_id;
 } NMPObjectLink;
 
 typedef struct {
@@ -214,6 +252,14 @@ typedef struct {
 } NMPObjectLnkVxlan;
 
 typedef struct {
+	NMPlatformLnkWireGuard _public;
+	const NMPWireGuardPeer *peers;
+	const NMPWireGuardAllowedIP *_allowed_ips_buf;
+	guint peers_len;
+	guint _allowed_ips_buf_len;
+} NMPObjectLnkWireGuard;
+
+typedef struct {
 	NMPlatformIP4Address _public;
 } NMPObjectIP4Address;
 
@@ -278,6 +324,9 @@ struct _NMPObject {
 		NMPlatformLnkVxlan      lnk_vxlan;
 		NMPObjectLnkVxlan       _lnk_vxlan;
 
+		NMPlatformLnkWireGuard  lnk_wireguard;
+		NMPObjectLnkWireGuard   _lnk_wireguard;
+
 		NMPlatformIPAddress     ip_address;
 		NMPlatformIPXAddress    ipx_address;
 		NMPlatformIP4Address    ip4_address;
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 8b8c87d8..12d91812 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -60,7 +60,7 @@ test_cleanup_internal (void)
 	g_assert (ifindex > 0);
 
 	/* wait for kernel to add the IPv6 link local address... it takes a bit. */
-	NMTST_WAIT_ASSERT (100, {
+	NMTST_WAIT_ASSERT (300, {
 		gs_unref_array GArray *addrs = NULL;
 		const NMPlatformIP6Address *a;
 
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 42569d5b..3dedbd10 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1243,21 +1243,29 @@ nmtstp_link_gre_add (NMPlatform *platform,
 	const NMPlatformLink *pllink = NULL;
 	gboolean success;
 	char buffer[INET_ADDRSTRLEN];
+	NMLinkType link_type;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
 
 	external_command = nmtstp_run_command_check_external (external_command);
+	link_type = lnk->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE;
 
 	_init_platform (&platform, external_command);
 
 	if (external_command) {
 		gs_free char *dev = NULL;
+		char *obj, *type;
 
 		if (lnk->parent_ifindex)
 			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
 
-		success = !nmtstp_run_command ("ip tunnel add %s mode gre %s local %s remote %s ttl %u tos %02x %s",
+		obj = lnk->is_tap ? "link" : "tunnel";
+		type = lnk->is_tap ? "type gretap" : "mode gre";
+
+		success = !nmtstp_run_command ("ip %s add %s %s %s local %s remote %s ttl %u tos %02x %s",
+		                                obj,
 		                                name,
+		                                type,
 		                                dev ?: "",
 		                                nm_utils_inet4_ntop (lnk->local, NULL),
 		                                nm_utils_inet4_ntop (lnk->remote, buffer),
@@ -1265,11 +1273,11 @@ nmtstp_link_gre_add (NMPlatform *platform,
 		                                lnk->tos,
 		                                lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc");
 		if (success)
-			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_GRE, 100);
+			pllink = nmtstp_assert_wait_for_link (platform, name, link_type, 100);
 	} else
 		success = nm_platform_link_gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
 
-	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_GRE);
+	_assert_pllink (platform, success, pllink, name, link_type);
 
 	return pllink;
 }
@@ -1289,6 +1297,7 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 	gboolean tclass_inherit;
 
 	g_assert (nm_utils_is_valid_iface_name (name, NULL));
+	g_assert (!lnk->is_gre);
 
 	external_command = nmtstp_run_command_check_external (external_command);
 
@@ -1336,6 +1345,56 @@ nmtstp_link_ip6tnl_add (NMPlatform *platform,
 }
 
 const NMPlatformLink *
+nmtstp_link_ip6gre_add (NMPlatform *platform,
+                        gboolean external_command,
+                        const char *name,
+                        const NMPlatformLnkIp6Tnl *lnk)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+	char buffer[INET6_ADDRSTRLEN];
+	char tclass[20];
+	gboolean tclass_inherit;
+
+	g_assert (nm_utils_is_valid_iface_name (name, NULL));
+	g_assert (lnk->is_gre);
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	_init_platform (&platform, external_command);
+
+	if (external_command) {
+		gs_free char *dev = NULL;
+
+		if (lnk->parent_ifindex)
+			dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (platform, lnk->parent_ifindex));
+
+		tclass_inherit = NM_FLAGS_HAS (lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS);
+
+		success = !nmtstp_run_command ("ip link add %s type %s %s local %s remote %s ttl %u tclass %s flowlabel %x",
+		                                name,
+		                                lnk->is_tap ? "ip6gretap" : "ip6gre",
+		                                dev,
+		                                nm_utils_inet6_ntop (&lnk->local, NULL),
+		                                nm_utils_inet6_ntop (&lnk->remote, buffer),
+		                                lnk->ttl,
+		                                tclass_inherit ? "inherit" : nm_sprintf_buf (tclass, "%02x", lnk->tclass),
+		                                lnk->flow_label);
+		if (success) {
+			pllink = nmtstp_assert_wait_for_link (platform,
+			                                      name,
+			                                      lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE,
+			                                      100);
+		}
+	} else
+		success = nm_platform_link_ip6gre_add (platform, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	_assert_pllink (platform, success, pllink, name, lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE);
+
+	return pllink;
+}
+
+const NMPlatformLink *
 nmtstp_link_ipip_add (NMPlatform *platform,
                       gboolean external_command,
                       const char *name,
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index bd02b0d7..1baadfa1 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -80,7 +80,7 @@ typedef struct {
 	gulong handler_id;
 	const char *name;
 	NMPlatformSignalChangeType change_type;
-	gint received_count;
+	int received_count;
 	GMainLoop *loop;
 	int ifindex;
 	const char *ifname;
@@ -297,6 +297,10 @@ const NMPlatformLink *nmtstp_link_ip6tnl_add (NMPlatform *platform,
                                               gboolean external_command,
                                               const char *name,
                                               const NMPlatformLnkIp6Tnl *lnk);
+const NMPlatformLink *nmtstp_link_ip6gre_add (NMPlatform *platform,
+                                              gboolean external_command,
+                                              const char *name,
+                                              const NMPlatformLnkIp6Tnl *lnk);
 const NMPlatformLink *nmtstp_link_ipip_add (NMPlatform *platform,
                                             gboolean external_command,
                                             const char *name,
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index 8708c80f..eebc15d8 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2015 Red Hat, Inc.
+ * Copyright (C) 2015 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -57,7 +57,7 @@ NMTST_DEFINE ();
 int
 main (int argc, char **argv)
 {
-	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
+	nmtst_init_assert_logging (&argc, &argv, "WARN", "DEFAULT");
 
 	g_test_add_func ("/general/init_linux_platform", test_init_linux_platform);
 	g_test_add_func ("/general/link_get_all", test_link_get_all);
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index cd5880e3..057490c4 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -25,6 +25,7 @@
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
+#include "nm-utils/nm-io-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nmp-netns.h"
 #include "platform/nm-platform-utils.h"
@@ -699,6 +700,7 @@ test_software_detect (gconstpointer user_data)
 	guint i_step;
 	const gboolean ext = test_data->external_command;
 	NMPlatformLnkTun lnk_tun;
+	NMPlatformLnkGre lnk_gre = { };
 	nm_auto_close int tun_fd = -1;
 
 	nmtstp_run_command_check ("ip link add %s type dummy", PARENT_NAME);
@@ -706,7 +708,6 @@ test_software_detect (gconstpointer user_data)
 
 	switch (test_data->link_type) {
 	case NM_LINK_TYPE_GRE: {
-		NMPlatformLnkGre lnk_gre = { };
 		gboolean gracefully_skip = FALSE;
 
 		lnk_gre.local = nmtst_inet4_from_string ("192.168.233.204");
@@ -730,6 +731,31 @@ test_software_detect (gconstpointer user_data)
 		}
 		break;
 	}
+	case NM_LINK_TYPE_GRETAP: {
+		gboolean gracefully_skip = FALSE;
+
+		lnk_gre.local = nmtst_inet4_from_string ("192.168.1.133");
+		lnk_gre.remote = nmtst_inet4_from_string ("172.168.101.2");
+		lnk_gre.parent_ifindex = ifindex_parent;
+		lnk_gre.ttl = 39;
+		lnk_gre.tos = 12;
+		lnk_gre.path_mtu_discovery = FALSE;
+		lnk_gre.is_tap = TRUE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "gretap0")) {
+			/* Seems that the ip_gre module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip_gre", NULL) != 0;
+		}
+
+		if (!nmtstp_link_gre_add (NULL, ext, DEVICE_NAME, &lnk_gre)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create gretap tunnel because of missing ip_gre module (modprobe ip_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding GRETAP tunnel");
+		}
+		break;
+	}
 	case NM_LINK_TYPE_IPIP: {
 		NMPlatformLnkIpIp lnk_ipip = { };
 		gboolean gracefully_skip = FALSE;
@@ -794,6 +820,58 @@ test_software_detect (gconstpointer user_data)
 		}
 		break;
 	}
+	case NM_LINK_TYPE_IP6GRE: {
+		NMPlatformLnkIp6Tnl lnk_ip6tnl = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "ip6gre0")) {
+			/* Seems that the ip6_tunnel module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_gre", NULL) != 0;
+		}
+
+		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fd01::42");
+		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fd01::aaaa");
+		lnk_ip6tnl.parent_ifindex = ifindex_parent;
+		lnk_ip6tnl.tclass = 21;
+		lnk_ip6tnl.flow_label = 1338;
+		lnk_ip6tnl.is_gre = TRUE;
+
+		if (!nmtstp_link_ip6gre_add (NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ip6gre tunnel because of missing ip6_gre module (modprobe ip6_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IP6GRE tunnel");
+		}
+		break;
+	}
+	case NM_LINK_TYPE_IP6GRETAP: {
+		NMPlatformLnkIp6Tnl lnk_ip6tnl = { };
+		gboolean gracefully_skip = FALSE;
+
+		if (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "ip6gre0")) {
+			/* Seems that the ip6_tunnel module is not loaded... try to load it. */
+			gracefully_skip = nm_utils_modprobe (NULL, TRUE, "ip6_gre", NULL) != 0;
+		}
+
+		lnk_ip6tnl.local = *nmtst_inet6_from_string ("fe80::abcd");
+		lnk_ip6tnl.remote = *nmtst_inet6_from_string ("fc01::bbbb");
+		lnk_ip6tnl.parent_ifindex = ifindex_parent;
+		lnk_ip6tnl.ttl = 10;
+		lnk_ip6tnl.tclass = 22;
+		lnk_ip6tnl.flow_label = 1339;
+		lnk_ip6tnl.is_gre = TRUE;
+		lnk_ip6tnl.is_tap = TRUE;
+
+		if (!nmtstp_link_ip6gre_add (NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) {
+			if (gracefully_skip) {
+				g_test_skip ("Cannot create ip6gretap tunnel because of missing ip6_gre module (modprobe ip6_gre)");
+				goto out_delete_parent;
+			}
+			g_error ("Failed adding IP6GRETAP tunnel");
+		}
+		break;
+	}
 	case NM_LINK_TYPE_MACVLAN: {
 		NMPlatformLnkMacvlan lnk_macvlan = { };
 		const NMPlatformLink *dummy;
@@ -970,16 +1048,15 @@ test_software_detect (gconstpointer user_data)
 			const NMPlatformLnkGre *plnk = &lnk->lnk_gre;
 
 			g_assert (plnk == nm_platform_link_get_lnk_gre (NM_PLATFORM_GET, ifindex, NULL));
-			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
-			g_assert_cmpint (plnk->input_flags, ==, 0);
-			g_assert_cmpint (plnk->output_flags, ==, 0);
-			g_assert_cmpint (plnk->input_key, ==, 0);
-			g_assert_cmpint (plnk->output_key, ==, 0);
-			nmtst_assert_ip4_address (plnk->local, "192.168.233.204");
-			nmtst_assert_ip4_address (plnk->remote, "172.168.10.25");
-			g_assert_cmpint (plnk->ttl, ==, 174);
-			g_assert_cmpint (plnk->tos, ==, 37);
-			g_assert_cmpint (plnk->path_mtu_discovery, ==, TRUE);
+			g_assert (nm_platform_lnk_gre_cmp (plnk, &lnk_gre) == 0);
+
+			break;
+		}
+		case NM_LINK_TYPE_GRETAP: {
+			const NMPlatformLnkGre *plnk = &lnk->lnk_gre;
+
+			g_assert (plnk == nm_platform_link_get_lnk_gretap (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert (nm_platform_lnk_gre_cmp (plnk, &lnk_gre) == 0);
 			break;
 		}
 		case NM_LINK_TYPE_IP6TNL: {
@@ -1012,6 +1089,33 @@ test_software_detect (gconstpointer user_data)
 			}
 			break;
 		}
+		case NM_LINK_TYPE_IP6GRE: {
+			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ip6gre (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip6_address (&plnk->local, "fd01::42");
+			nmtst_assert_ip6_address (&plnk->remote, "fd01::aaaa");
+			g_assert_cmpint (plnk->tclass, ==, 21);
+			g_assert_cmpint (plnk->flow_label, ==, 1338);
+			g_assert_cmpint (plnk->is_gre, ==, TRUE);
+			g_assert_cmpint (plnk->is_tap, ==, FALSE);
+			break;
+		}
+		case NM_LINK_TYPE_IP6GRETAP: {
+			const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl;
+
+			g_assert (plnk == nm_platform_link_get_lnk_ip6gretap (NM_PLATFORM_GET, ifindex, NULL));
+			g_assert_cmpint (plnk->parent_ifindex, ==, ifindex_parent);
+			nmtst_assert_ip6_address (&plnk->local, "fe80::abcd");
+			nmtst_assert_ip6_address (&plnk->remote, "fc01::bbbb");
+			g_assert_cmpint (plnk->ttl, ==, 10);
+			g_assert_cmpint (plnk->tclass, ==, 22);
+			g_assert_cmpint (plnk->flow_label, ==, 1339);
+			g_assert_cmpint (plnk->is_gre, ==, TRUE);
+			g_assert_cmpint (plnk->is_tap, ==, TRUE);
+			break;
+		}
 		case NM_LINK_TYPE_IPIP: {
 			const NMPlatformLnkIpIp *plnk = &lnk->lnk_ipip;
 
@@ -1732,7 +1836,7 @@ test_create_many_links_do (guint n_devices)
 	char name[64];
 	const NMPlatformLink *pllink;
 	gs_unref_array GArray *ifindexes = g_array_sized_new (FALSE, FALSE, sizeof (int), n_devices);
-	const gint EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
+	const int EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
 
 	g_assert (EX >= -1 && EX <= 2);
 
@@ -2519,7 +2623,9 @@ test_sysctl_rename (void)
 	case 0: {
 		gs_free char *c = NULL;
 
-		if (nm_utils_file_get_contents (dirfd, "ifindex", 1*1024*1024, &c, NULL, NULL) < 0)
+		if (nm_utils_file_get_contents (dirfd, "ifindex", 1*1024*1024,
+		                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+		                                &c, NULL, NULL) < 0)
 			g_assert_not_reached();
 		g_assert_cmpint (ifindex[0], ==, (int) _nm_utils_ascii_str_to_int64 (c, 10, 0, G_MAXINT, -1));
 		break;
@@ -2583,7 +2689,9 @@ test_sysctl_netns_switch (void)
 	{
 		gs_free char *c = NULL;
 
-		if (nm_utils_file_get_contents (dirfd, "ifindex", 0, &c, NULL, NULL) < 0)
+		if (nm_utils_file_get_contents (dirfd, "ifindex", 0,
+		                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+		                                &c, NULL, NULL) < 0)
 			g_assert_not_reached();
 		g_assert_cmpint (ifindex, ==, (int) _nm_utils_ascii_str_to_int64 (c, 10, 0, G_MAXINT, -1));
 	}
@@ -2595,7 +2703,11 @@ test_sysctl_netns_switch (void)
 	{
 		gs_free char *c = NULL;
 
-		if (nm_utils_file_get_contents (-1, nm_sprintf_bufa (100, "/sys/class/net/%s/ifindex", IFNAME), 0, &c, NULL, NULL) < 0)
+		if (nm_utils_file_get_contents (-1,
+		                                nm_sprintf_bufa (100, "/sys/class/net/%s/ifindex", IFNAME),
+		                                0,
+		                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
+		                                &c, NULL, NULL) < 0)
 			ifindex_tmp = -1;
 		else
 			ifindex_tmp = _nm_utils_ascii_str_to_int64 (c, 10, 0, G_MAXINT, -2);
@@ -2610,6 +2722,102 @@ test_sysctl_netns_switch (void)
 
 /*****************************************************************************/
 
+static void
+ethtool_features_dump (const NMEthtoolFeatureStates *features)
+{
+	guint i, j;
+
+	g_assert (features);
+
+	_LOGT (">>> %u features (%u ss-features)", features->n_states, features->n_ss_features);
+
+	for (i = 0; i < features->n_states; i++) {
+		const NMEthtoolFeatureState *s = &features->states_list[i];
+
+		_LOGT (">>> feature-list[%3u]: %3d = %-32s (%3u) | %s %s %s %s",
+		       i,
+		       (int) s->info->ethtool_id,
+		       s->info->kernel_names[s->idx_kernel_name],
+		       s->idx_ss_features,
+		       s->active ? "ACT" : "act",
+		       s->available ? "AVA" : "ava",
+		       s->never_changed ? "NCH" : "nch",
+		       s->requested ? "REQ" : "req");
+	}
+	for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) {
+		_LOGT (">>> feature-idx [%3u]: %-32s = %u features",
+		       i + (guint) _NM_ETHTOOL_ID_FEATURE_FIRST,
+		       nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname,
+		       (guint) NM_PTRARRAY_LEN (features->states_indexed[i]));
+		for (j = 0; features->states_indexed[i] && features->states_indexed[i][j]; j++) {
+			const NMEthtoolFeatureState *s = features->states_indexed[i][j];
+
+			_LOGT (">>>  %3u: %-32s | %s %s %s %s",
+			       j,
+			       s->info->kernel_names[s->idx_kernel_name],
+			       s->active ? "ACT" : "act",
+			       s->available ? "AVA" : "ava",
+			       s->never_changed ? "NCH" : "nch",
+			       s->requested ? "REQ" : "req");
+		}
+	}
+}
+
+static void
+test_ethtool_features_get (void)
+{
+	gs_unref_ptrarray GPtrArray *gfree_keeper = g_ptr_array_new_with_free_func (g_free);
+	const int IFINDEX = 1;
+	guint i;
+	guint i_run;
+
+	for (i_run = 0; i_run < 5; i_run++) {
+		NMEthtoolFeatureStates *features;
+		NMTernary *requested;
+		gboolean do_set = TRUE;
+
+		requested = g_new (NMTernary, _NM_ETHTOOL_ID_FEATURE_NUM);
+		for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++)
+			requested[i] = NM_TERNARY_DEFAULT;
+		g_ptr_array_add (gfree_keeper, requested);
+
+		if (i_run == 0) {
+			requested[NM_ETHTOOL_ID_FEATURE_RX]                    = NM_TERNARY_FALSE;
+			requested[NM_ETHTOOL_ID_FEATURE_TSO]                   = NM_TERNARY_FALSE;
+			requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION]  = NM_TERNARY_FALSE;
+		} else if (i_run == 1)
+			do_set = FALSE;
+		else if (i_run == 2) {
+			requested[NM_ETHTOOL_ID_FEATURE_TSO]                   = NM_TERNARY_FALSE;
+			requested[NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION]  = NM_TERNARY_TRUE;
+		} else if (i_run == 3)
+			do_set = FALSE;
+
+		_LOGT (">>> ethtool-features-get RUN %u (do-set=%s", i_run, do_set ? "set" : "reset");
+
+		features = nmp_utils_ethtool_get_features (IFINDEX);
+		g_ptr_array_add (gfree_keeper, features);
+
+		ethtool_features_dump (features);
+
+		if (_LOGT_ENABLED ()) {
+			int ignore;
+
+			ignore = system ("ethtool -k lo");
+			(void) ignore;
+		}
+
+		if (!do_set) {
+			requested = gfree_keeper->pdata[i_run * 2 - 2];
+			features = gfree_keeper->pdata[i_run * 2 - 1];
+		}
+
+		nmp_utils_ethtool_set_features (IFINDEX, features, requested, do_set);
+	}
+}
+
+/*****************************************************************************/
+
 NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
 
 void
@@ -2641,8 +2849,11 @@ _nmtstp_setup_tests (void)
 		g_test_add_func ("/link/external", test_external);
 
 		test_software_detect_add ("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0);
+		test_software_detect_add ("/link/software/detect/gretap", NM_LINK_TYPE_GRETAP, 0);
 		test_software_detect_add ("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0);
 		test_software_detect_add ("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1);
+		test_software_detect_add ("/link/software/detect/ip6gre", NM_LINK_TYPE_IP6GRE, 0);
+		test_software_detect_add ("/link/software/detect/ip6gretap", NM_LINK_TYPE_IP6GRETAP, 0);
 		test_software_detect_add ("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0);
 		test_software_detect_add ("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0);
 		test_software_detect_add ("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0);
@@ -2668,5 +2879,7 @@ _nmtstp_setup_tests (void)
 
 		g_test_add_func ("/general/sysctl/rename", test_sysctl_rename);
 		g_test_add_func ("/general/sysctl/netns-switch", test_sysctl_netns_switch);
+
+		g_test_add_func ("/link/ethtool/features/get", test_ethtool_features_get);
 	}
 }
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index 6c2ff3f5..b3bb2bb6 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -15,14 +15,14 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  * Copyright (C) 2011 Intel Corporation. All rights reserved.
  */
 
 #include "nm-default.h"
 
-#include "wifi-utils-nl80211.h"
+#include "nm-wifi-utils-nl80211.h"
 
 #include <errno.h>
 #include <string.h>
@@ -32,7 +32,7 @@
 #include <linux/nl80211.h>
 
 #include "platform/nm-netlink.h"
-#include "wifi-utils-private.h"
+#include "nm-wifi-utils-private.h"
 #include "platform/nm-platform.h"
 #include "platform/nm-platform-utils.h"
 #include "nm-utils.h"
@@ -47,14 +47,20 @@
 	} G_STMT_END
 
 typedef struct {
-	WifiData parent;
+	NMWifiUtils parent;
 	struct nl_sock *nl_sock;
 	guint32 *freqs;
 	int id;
 	int num_freqs;
 	int phy;
 	bool can_wowlan:1;
-} WifiDataNl80211;
+} NMWifiUtilsNl80211;
+
+typedef struct {
+	NMWifiUtilsClass parent;
+} NMWifiUtilsNl80211Class;
+
+G_DEFINE_TYPE (NMWifiUtilsNl80211, nm_wifi_utils_nl80211, NM_TYPE_WIFI_UTILS)
 
 static int
 ack_handler (struct nl_msg *msg, void *arg)
@@ -97,7 +103,7 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-nl80211_alloc_msg (WifiDataNl80211 *nl80211, guint32 cmd, guint32 flags)
+nl80211_alloc_msg (NMWifiUtilsNl80211 *nl80211, guint32 cmd, guint32 flags)
 {
 	return _nl80211_alloc_msg (nl80211->id, nl80211->parent.ifindex, nl80211->phy, cmd, flags);
 }
@@ -154,7 +160,7 @@ _nl80211_send_and_recv (struct nl_sock *nl_sock,
 }
 
 static int
-nl80211_send_and_recv (WifiDataNl80211 *nl80211,
+nl80211_send_and_recv (NMWifiUtilsNl80211 *nl80211,
                        struct nl_msg *msg,
                        int (*valid_handler) (struct nl_msg *, void *),
                        void *valid_data)
@@ -164,13 +170,11 @@ nl80211_send_and_recv (WifiDataNl80211 *nl80211,
 }
 
 static void
-wifi_nl80211_deinit (WifiData *parent)
+dispose (GObject *object)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) parent;
+	NMWifiUtilsNl80211 *nl80211 = NM_WIFI_UTILS_NL80211 (object);
 
-	if (nl80211->nl_sock)
-		nl_socket_free (nl80211->nl_sock);
-	g_free (nl80211->freqs);
+	g_clear_pointer (&nl80211->freqs, g_free);
 }
 
 struct nl80211_iface_info {
@@ -207,9 +211,9 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 }
 
 static NM80211Mode
-wifi_nl80211_get_mode (WifiData *data)
+wifi_nl80211_get_mode (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_iface_info iface_info = {
 		.mode = NM_802_11_MODE_UNKNOWN,
 	};
@@ -225,9 +229,9 @@ wifi_nl80211_get_mode (WifiData *data)
 }
 
 static gboolean
-wifi_nl80211_set_mode (WifiData *data, const NM80211Mode mode)
+wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
@@ -255,9 +259,9 @@ nla_put_failure:
 }
 
 static gboolean
-wifi_nl80211_set_powersave (WifiData *data, guint32 powersave)
+wifi_nl80211_set_powersave (NMWifiUtils *data, guint32 powersave)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
@@ -271,10 +275,64 @@ nla_put_failure:
 	return FALSE;
 }
 
+static int
+nl80211_get_wake_on_wlan_handler (struct nl_msg *msg, void *arg)
+{
+	NMSettingWirelessWakeOnWLan *wowl = arg;
+	struct nlattr *attrs[NL80211_ATTR_MAX + 1];
+	struct nlattr *trig[NUM_NL80211_WOWLAN_TRIG];
+	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
+
+	nla_parse (attrs, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+	           genlmsg_attrlen(gnlh, 0), NULL);
+
+	if (!attrs[NL80211_ATTR_WOWLAN_TRIGGERS])
+		return NL_SKIP;
+
+	nla_parse (trig, MAX_NL80211_WOWLAN_TRIG,
+	           nla_data (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	           nla_len (attrs[NL80211_ATTR_WOWLAN_TRIGGERS]),
+	           NULL);
+
+	*wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE;
+	if (trig[NL80211_WOWLAN_TRIG_ANY])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY;
+	if (trig[NL80211_WOWLAN_TRIG_DISCONNECT])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT;
+	if (trig[NL80211_WOWLAN_TRIG_MAGIC_PKT])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC;
+	if (trig[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE;
+	if (trig[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST;
+	if (trig[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE;
+	if (trig[NL80211_WOWLAN_TRIG_RFKILL_RELEASE])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE;
+	if (trig[NL80211_WOWLAN_TRIG_TCP_CONNECTION])
+		*wowl |= NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP;
+
+	return NL_SKIP;
+}
+
+static NMSettingWirelessWakeOnWLan
+wifi_nl80211_get_wake_on_wlan (NMWifiUtils *data)
+{
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
+	NMSettingWirelessWakeOnWLan wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
+
+	nl80211_send_and_recv (nl80211, msg, nl80211_get_wake_on_wlan_handler, &wowl);
+
+	return wowl;
+}
+
 static gboolean
-wifi_nl80211_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
+wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nlattr *triggers;
 	int err;
@@ -282,11 +340,11 @@ wifi_nl80211_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
 	if (wowl == NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE)
 		return TRUE;
 
-	msg = nl80211_alloc_msg(nl80211, NL80211_CMD_SET_WOWLAN, 0);
+	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_SET_WOWLAN, 0);
 	if (!msg)
 		return FALSE;
 
-	triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS);
+	triggers = nla_nest_start (msg, NL80211_ATTR_WOWLAN_TRIGGERS);
 
 	if (NM_FLAGS_HAS (wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY))
 		NLA_PUT_FLAG (msg, NL80211_WOWLAN_TRIG_ANY);
@@ -306,7 +364,8 @@ wifi_nl80211_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
 	nla_nest_end(msg, triggers);
 
 	err = nl80211_send_and_recv (nl80211, msg, NULL, NULL);
-	return err ? FALSE : TRUE;
+
+	return err >= 0;
 
 nla_put_failure:
 	return FALSE;
@@ -432,7 +491,7 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_bss_info (WifiDataNl80211 *nl80211,
+nl80211_get_bss_info (NMWifiUtilsNl80211 *nl80211,
                       struct nl80211_bss_info *bss_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -445,9 +504,9 @@ nl80211_get_bss_info (WifiDataNl80211 *nl80211,
 }
 
 static guint32
-wifi_nl80211_get_freq (WifiData *data)
+wifi_nl80211_get_freq (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
 	nl80211_get_bss_info (nl80211, &bss_info);
@@ -456,9 +515,9 @@ wifi_nl80211_get_freq (WifiData *data)
 }
 
 static guint32
-wifi_nl80211_find_freq (WifiData *data, const guint32 *freqs)
+wifi_nl80211_find_freq (NMWifiUtils *data, const guint32 *freqs)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	int i;
 
 	for (i = 0; i < nl80211->num_freqs; i++) {
@@ -472,9 +531,9 @@ wifi_nl80211_find_freq (WifiData *data, const guint32 *freqs)
 }
 
 static gboolean
-wifi_nl80211_get_bssid (WifiData *data, guint8 *out_bssid)
+wifi_nl80211_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_bss_info bss_info;
 
 	nl80211_get_bss_info (nl80211, &bss_info);
@@ -556,7 +615,7 @@ nl80211_station_handler (struct nl_msg *msg, void *arg)
 }
 
 static void
-nl80211_get_ap_info (WifiDataNl80211 *nl80211,
+nl80211_get_ap_info (NMWifiUtilsNl80211 *nl80211,
                      struct nl80211_station_info *sta_info)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -586,9 +645,9 @@ nla_put_failure:
 }
 
 static guint32
-wifi_nl80211_get_rate (WifiData *data)
+wifi_nl80211_get_rate (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
 	nl80211_get_ap_info (nl80211, &sta_info);
@@ -597,9 +656,9 @@ wifi_nl80211_get_rate (WifiData *data)
 }
 
 static int
-wifi_nl80211_get_qual (WifiData *data)
+wifi_nl80211_get_qual (NMWifiUtils *data)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	struct nl80211_station_info sta_info;
 
 	nl80211_get_ap_info (nl80211, &sta_info);
@@ -607,9 +666,9 @@ wifi_nl80211_get_qual (WifiData *data)
 }
 
 static gboolean
-wifi_nl80211_indicate_addressing_running (WifiData *data, gboolean running)
+wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 {
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
+	NMWifiUtilsNl80211 *nl80211 = (NMWifiUtilsNl80211 *) data;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	int err;
 
@@ -638,44 +697,6 @@ nla_put_failure:
 	return FALSE;
 }
 
-struct nl80211_wowlan_info {
-	gboolean enabled;
-};
-
-static int
-nl80211_wowlan_handler (struct nl_msg *msg, void *arg)
-{
-	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
-	struct nl80211_wowlan_info *info = arg;
-
-	info->enabled = FALSE;
-
-	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
-		return NL_SKIP;
-
-	if (tb[NL80211_ATTR_WOWLAN_TRIGGERS])
-		info->enabled = TRUE;
-
-	return NL_SKIP;
-}
-
-static gboolean
-wifi_nl80211_get_wowlan (WifiData *data)
-{
-	WifiDataNl80211 *nl80211 = (WifiDataNl80211 *) data;
-	nm_auto_nlmsg struct nl_msg *msg = NULL;
-	struct nl80211_wowlan_info info;
-
-	if (!nl80211->can_wowlan)
-		return FALSE;
-
-	msg = nl80211_alloc_msg (nl80211, NL80211_CMD_GET_WOWLAN, 0);
-	nl80211_send_and_recv (nl80211, msg, nl80211_wowlan_handler, &info);
-	return info.enabled;
-}
-
 struct nl80211_device_info {
 	int phy;
 	guint32 *freqs;
@@ -879,48 +900,58 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 	return NL_SKIP;
 }
 
-WifiData *
-wifi_nl80211_init (int ifindex)
+static void
+nm_wifi_utils_nl80211_init (NMWifiUtilsNl80211 *self)
 {
-	static const WifiDataClass klass = {
-		.struct_size = sizeof (WifiDataNl80211),
-		.get_mode = wifi_nl80211_get_mode,
-		.set_mode = wifi_nl80211_set_mode,
-		.set_powersave = wifi_nl80211_set_powersave,
-		.set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan,
-		.get_freq = wifi_nl80211_get_freq,
-		.find_freq = wifi_nl80211_find_freq,
-		.get_bssid = wifi_nl80211_get_bssid,
-		.get_rate = wifi_nl80211_get_rate,
-		.get_qual = wifi_nl80211_get_qual,
-		.get_wowlan = wifi_nl80211_get_wowlan,
-		.indicate_addressing_running = wifi_nl80211_indicate_addressing_running,
-		.deinit = wifi_nl80211_deinit,
-	};
-	WifiDataNl80211 *nl80211;
+}
+
+static void
+nm_wifi_utils_nl80211_class_init (NMWifiUtilsNl80211Class *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	wifi_utils_class->get_mode = wifi_nl80211_get_mode;
+	wifi_utils_class->set_mode = wifi_nl80211_set_mode;
+	wifi_utils_class->set_powersave = wifi_nl80211_set_powersave;
+	wifi_utils_class->get_wake_on_wlan = wifi_nl80211_get_wake_on_wlan,
+	wifi_utils_class->set_wake_on_wlan = wifi_nl80211_set_wake_on_wlan,
+	wifi_utils_class->get_freq = wifi_nl80211_get_freq;
+	wifi_utils_class->find_freq = wifi_nl80211_find_freq;
+	wifi_utils_class->get_bssid = wifi_nl80211_get_bssid;
+	wifi_utils_class->get_rate = wifi_nl80211_get_rate;
+	wifi_utils_class->get_qual = wifi_nl80211_get_qual;
+	wifi_utils_class->indicate_addressing_running = wifi_nl80211_indicate_addressing_running;
+}
+
+NMWifiUtils *
+nm_wifi_utils_nl80211_new (int ifindex, struct nl_sock *genl)
+{
+	gs_unref_object NMWifiUtilsNl80211 *nl80211 = NULL;
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 	struct nl80211_device_info device_info = {};
 	char ifname[IFNAMSIZ];
 
+	if (!genl)
+		return NULL;
+
 	if (!nmp_utils_if_indextoname (ifindex, ifname)) {
 		_LOGW (LOGD_PLATFORM | LOGD_WIFI,
 		       "can't determine interface name for ifindex %d", ifindex);
 		nm_sprintf_buf (ifname, "if %d", ifindex);
 	}
 
-	nl80211 = wifi_data_new (&klass, ifindex);
-
-	nl80211->nl_sock = nl_socket_alloc ();
-	if (nl80211->nl_sock == NULL)
-		goto error;
+	nl80211 = g_object_new (NM_TYPE_WIFI_UTILS_NL80211, NULL);
 
-	if (nl_connect (nl80211->nl_sock, NETLINK_GENERIC))
-		goto error;
+	nl80211->parent.ifindex = ifindex;
+	nl80211->nl_sock = genl;
 
 	nl80211->id = genl_ctrl_resolve (nl80211->nl_sock, "nl80211");
 	if (nl80211->id < 0) {
 		_LOGD (LOGD_WIFI, "genl_ctrl_resolve: failed to resolve \"nl80211\"");
-		goto error;
+		return NULL;
 	}
 
 	nl80211->phy = -1;
@@ -932,42 +963,42 @@ wifi_nl80211_init (int ifindex)
 		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): NL80211_CMD_GET_WIPHY request failed",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (!device_info.success) {
 		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): NL80211_CMD_GET_WIPHY request indicated failure",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (!device_info.supported) {
 		_LOGD (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): driver does not fully support nl80211, falling back to WEXT",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (!device_info.can_scan_ssid) {
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): driver does not support SSID scans",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (device_info.num_freqs == 0 || device_info.freqs == NULL) {
 		nm_log_err (LOGD_PLATFORM | LOGD_WIFI,
 		            "(%s): driver reports no supported frequencies",
 		            ifname);
-		goto error;
+		return NULL;
 	}
 
 	if (device_info.caps == 0) {
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI,
 		       "(%s): driver doesn't report support of any encryption",
 		       ifname);
-		goto error;
+		return NULL;
 	}
 
 	nl80211->phy = device_info.phy;
@@ -979,10 +1010,5 @@ wifi_nl80211_init (int ifindex)
 	_LOGI (LOGD_PLATFORM | LOGD_WIFI,
 	       "(%s): using nl80211 for WiFi device control",
 	       ifname);
-	return (WifiData *) nl80211;
-
-error:
-	wifi_utils_unref ((WifiData *) nl80211);
-	return NULL;
+	return (NMWifiUtils *) g_steal_pointer (&nl80211);
 }
-
diff --git a/src/platform/wifi/wifi-utils-nl80211.h b/src/platform/wifi/nm-wifi-utils-nl80211.h
index aff24555..27f67697 100644
--- a/src/platform/wifi/wifi-utils-nl80211.h
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.h
@@ -16,13 +16,24 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2011 Intel Corporation. All rights reserved.
+ * Copyright (C) 2018 Red Hat, Inc.
  */
 
 #ifndef __WIFI_UTILS_NL80211_H__
 #define __WIFI_UTILS_NL80211_H__
 
-#include "wifi-utils.h"
+#include "nm-wifi-utils.h"
+#include "platform/nm-netlink.h"
 
-WifiData *wifi_nl80211_init (int ifindex);
+#define NM_TYPE_WIFI_UTILS_NL80211            (nm_wifi_utils_nl80211_get_type ())
+#define NM_WIFI_UTILS_NL80211(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211))
+#define NM_WIFI_UTILS_NL80211_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class))
+#define NM_IS_WIFI_UTILS_NL80211(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_UTILS_NL80211))
+#define NM_IS_WIFI_UTILS_NL80211_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_UTILS_NL80211))
+#define NM_WIFI_UTILS_NL80211_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_UTILS_NL80211, NMWifiUtilsNl80211Class))
+
+GType nm_wifi_utils_nl80211_get_type (void);
+
+NMWifiUtils *nm_wifi_utils_nl80211_new (int ifindex, struct nl_sock *genl);
 
 #endif  /* __WIFI_UTILS_NL80211_H__ */
diff --git a/src/platform/wifi/wifi-utils-private.h b/src/platform/wifi/nm-wifi-utils-private.h
index 627108cb..bcbc1c51 100644
--- a/src/platform/wifi/wifi-utils-private.h
+++ b/src/platform/wifi/nm-wifi-utils-private.h
@@ -15,67 +15,65 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
 #ifndef __WIFI_UTILS_PRIVATE_H__
 #define __WIFI_UTILS_PRIVATE_H__
 
 #include "nm-dbus-interface.h"
-#include "wifi-utils.h"
+#include "nm-wifi-utils.h"
 
 typedef struct {
-	gsize struct_size;
+	GObjectClass parent;
 
-	NM80211Mode (*get_mode) (WifiData *data);
+	NM80211Mode (*get_mode) (NMWifiUtils *data);
 
-	gboolean (*set_mode) (WifiData *data, const NM80211Mode mode);
+	gboolean (*set_mode) (NMWifiUtils *data, const NM80211Mode mode);
 
 	/* Set power saving mode on an interface */
-	gboolean (*set_powersave) (WifiData *data, guint32 powersave);
+	gboolean (*set_powersave) (NMWifiUtils *data, guint32 powersave);
+
+	/* Get WakeOnWLAN configuration on an interface */
+	NMSettingWirelessWakeOnWLan (*get_wake_on_wlan) (NMWifiUtils *data);
 
 	/* Set WakeOnWLAN mode on an interface */
-	gboolean (*set_wake_on_wlan) (WifiData *data, NMSettingWirelessWakeOnWLan wowl);
+	gboolean (*set_wake_on_wlan) (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl);
 
 	/* Return current frequency in MHz (really associated BSS frequency) */
-	guint32 (*get_freq) (WifiData *data);
+	guint32 (*get_freq) (NMWifiUtils *data);
 
 	/* Return first supported frequency in the zero-terminated list */
-	guint32 (*find_freq) (WifiData *data, const guint32 *freqs);
+	guint32 (*find_freq) (NMWifiUtils *data, const guint32 *freqs);
 
 	/* Return current bitrate in Kbps */
-	guint32 (*get_rate) (WifiData *data);
+	guint32 (*get_rate) (NMWifiUtils *data);
 
-	gboolean (*get_bssid) (WifiData *data, guint8 *out_bssid);
+	gboolean (*get_bssid) (NMWifiUtils *data, guint8 *out_bssid);
 
 	/* Return a signal strength percentage 0 - 100% for the current BSSID;
 	 * return -1 on errors or if not associated.
 	 */
-	int (*get_qual) (WifiData *data);
-
-	void (*deinit) (WifiData *data);
-
-	gboolean (*get_wowlan) (WifiData *data);
+	int (*get_qual) (NMWifiUtils *data);
 
 	/* OLPC Mesh-only functions */
 
-	guint32 (*get_mesh_channel) (WifiData *data);
+	guint32 (*get_mesh_channel) (NMWifiUtils *data);
 
 	/* channel == 0 means "auto channel" */
-	gboolean (*set_mesh_channel) (WifiData *data, guint32 channel);
+	gboolean (*set_mesh_channel) (NMWifiUtils *data, guint32 channel);
 
 	/* ssid == NULL means "auto SSID" */
-	gboolean (*set_mesh_ssid) (WifiData *data, const guint8 *ssid, gsize len);
+	gboolean (*set_mesh_ssid) (NMWifiUtils *data, const guint8 *ssid, gsize len);
 
-	gboolean (*indicate_addressing_running) (WifiData *data, gboolean running);
-} WifiDataClass;
+	gboolean (*indicate_addressing_running) (NMWifiUtils *data, gboolean running);
+} NMWifiUtilsClass;
+
+struct NMWifiUtils {
+	GObject parent;
 
-struct WifiData {
-	const WifiDataClass *klass;
 	int ifindex;
 	NMDeviceWifiCapabilities caps;
 };
 
-gpointer wifi_data_new (const WifiDataClass *klass, int ifindex);
-
 #endif  /* __WIFI_UTILS_PRIVATE_H__ */
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/nm-wifi-utils-wext.c
index c8744f79..e52ae5a3 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/nm-wifi-utils-wext.c
@@ -15,13 +15,13 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
 #include "nm-default.h"
 
-#include "wifi-utils-wext.h"
+#include "nm-wifi-utils-wext.h"
 
 #include <errno.h>
 #include <string.h>
@@ -38,17 +38,24 @@
 #include <sys/socket.h>
 #include <linux/wireless.h>
 
-#include "wifi-utils-private.h"
+#include "nm-wifi-utils-private.h"
 #include "nm-utils.h"
 #include "platform/nm-platform-utils.h"
+#include "nm-core-internal.h"
 
 typedef struct {
-	WifiData parent;
+	NMWifiUtils parent;
 	int fd;
 	struct iw_quality max_qual;
 	gint8 num_freqs;
 	guint32 freqs[IW_MAX_FREQUENCIES];
-} WifiDataWext;
+} NMWifiUtilsWext;
+
+typedef struct {
+	NMWifiUtilsClass parent;
+} NMWifiUtilsWextClass;
+
+G_DEFINE_TYPE (NMWifiUtilsWext, nm_wifi_utils_wext, NM_TYPE_WIFI_UTILS)
 
 /* Until a new wireless-tools comes out that has the defs and the structure,
  * need to copy them here.
@@ -94,11 +101,11 @@ iw_freq_to_uint32 (const struct iw_freq *freq)
 }
 
 static void
-wifi_wext_deinit (WifiData *parent)
+dispose (GObject *object)
 {
-	WifiDataWext *wext = (WifiDataWext *) parent;
+	NMWifiUtilsWext *wext = NM_WIFI_UTILS_WEXT (object);
 
-	nm_close (wext->fd);
+	wext->fd = nm_close (wext->fd);
 }
 
 static gboolean
@@ -118,9 +125,9 @@ get_ifname (int ifindex, char *buffer, const char *op)
 }
 
 static NM80211Mode
-wifi_wext_get_mode_ifname (WifiData *data, const char *ifname)
+wifi_wext_get_mode_ifname (NMWifiUtils *data, const char *ifname)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 
 	memset (&wrq, 0, sizeof (struct iwreq));
@@ -150,7 +157,7 @@ wifi_wext_get_mode_ifname (WifiData *data, const char *ifname)
 }
 
 static NM80211Mode
-wifi_wext_get_mode (WifiData *data)
+wifi_wext_get_mode (NMWifiUtils *data)
 {
 	char ifname[IFNAMSIZ];
 
@@ -161,9 +168,9 @@ wifi_wext_get_mode (WifiData *data)
 }
 
 static gboolean
-wifi_wext_set_mode (WifiData *data, const NM80211Mode mode)
+wifi_wext_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -203,9 +210,9 @@ wifi_wext_set_mode (WifiData *data, const NM80211Mode mode)
 }
 
 static gboolean
-wifi_wext_set_powersave (WifiData *data, guint32 powersave)
+wifi_wext_set_powersave (NMWifiUtils *data, guint32 powersave)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -232,9 +239,9 @@ wifi_wext_set_powersave (WifiData *data, guint32 powersave)
 }
 
 static guint32
-wifi_wext_get_freq (WifiData *data)
+wifi_wext_get_freq (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -254,9 +261,9 @@ wifi_wext_get_freq (WifiData *data)
 }
 
 static guint32
-wifi_wext_find_freq (WifiData *data, const guint32 *freqs)
+wifi_wext_find_freq (NMWifiUtils *data, const guint32 *freqs)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	int i;
 
 	for (i = 0; i < wext->num_freqs; i++) {
@@ -270,9 +277,9 @@ wifi_wext_find_freq (WifiData *data, const guint32 *freqs)
 }
 
 static gboolean
-wifi_wext_get_bssid (WifiData *data, guint8 *out_bssid)
+wifi_wext_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -292,9 +299,9 @@ wifi_wext_get_bssid (WifiData *data, guint8 *out_bssid)
 }
 
 static guint32
-wifi_wext_get_rate (WifiData *data)
+wifi_wext_get_rate (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	int err;
 	char ifname[IFNAMSIZ];
@@ -403,9 +410,9 @@ wext_qual_to_percent (const struct iw_quality *qual,
 }
 
 static int
-wifi_wext_get_qual (WifiData *data)
+wifi_wext_get_qual (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	struct iw_statistics stats;
 	char ifname[IFNAMSIZ];
@@ -433,13 +440,13 @@ wifi_wext_get_qual (WifiData *data)
 /* OLPC Mesh-only functions */
 
 static guint32
-wifi_wext_get_mesh_channel (WifiData *data)
+wifi_wext_get_mesh_channel (NMWifiUtils *data)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	guint32 freq;
 	int i;
 
-	freq = wifi_utils_get_freq (data);
+	freq = nm_wifi_utils_get_freq (data);
 	for (i = 0; i < wext->num_freqs; i++) {
 		if (freq == wext->freqs[i])
 			return i + 1;
@@ -448,9 +455,9 @@ wifi_wext_get_mesh_channel (WifiData *data)
 }
 
 static gboolean
-wifi_wext_set_mesh_channel (WifiData *data, guint32 channel)
+wifi_wext_set_mesh_channel (NMWifiUtils *data, guint32 channel)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char ifname[IFNAMSIZ];
 
@@ -477,9 +484,9 @@ wifi_wext_set_mesh_channel (WifiData *data, guint32 channel)
 }
 
 static gboolean
-wifi_wext_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
+wifi_wext_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len)
 {
-	WifiDataWext *wext = (WifiDataWext *) data;
+	NMWifiUtilsWext *wext = (NMWifiUtilsWext *) data;
 	struct iwreq wrq;
 	char buf[IW_ESSID_MAX_SIZE + 1];
 	char ifname[IFNAMSIZ];
@@ -500,11 +507,13 @@ wifi_wext_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
 		return TRUE;
 
 	if (errno != ENODEV) {
+		gs_free char *ssid_str = NULL;
+
 		errsv = errno;
 		_LOGE (LOGD_PLATFORM | LOGD_WIFI | LOGD_OLPC,
 		       "(%s): error setting SSID to '%s': %s",
 		       ifname,
-		       ssid ? nm_utils_escape_ssid (ssid, len) : "(null)",
+		       (ssid_str = _nm_utils_ssid_to_string_arr (ssid, len)),
 		       strerror (errsv));
 	}
 
@@ -514,7 +523,7 @@ wifi_wext_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
 /*****************************************************************************/
 
 static gboolean
-wext_can_scan_ifname (WifiDataWext *wext, const char *ifname)
+wext_can_scan_ifname (NMWifiUtilsWext *wext, const char *ifname)
 {
 	struct iwreq wrq;
 
@@ -528,7 +537,7 @@ wext_can_scan_ifname (WifiDataWext *wext, const char *ifname)
 }
 
 static gboolean
-wext_get_range_ifname (WifiDataWext *wext,
+wext_get_range_ifname (NMWifiUtilsWext *wext,
                        const char *ifname,
                        struct iw_range *range,
                        guint32 *response_len)
@@ -577,7 +586,7 @@ wext_get_range_ifname (WifiDataWext *wext,
                   NM_WIFI_DEVICE_CAP_RSN)
 
 static guint32
-wext_get_caps (WifiDataWext *wext, const char *ifname, struct iw_range *range)
+wext_get_caps (NMWifiUtilsWext *wext, const char *ifname, struct iw_range *range)
 {
 	guint32 caps = NM_WIFI_DEVICE_CAP_NONE;
 
@@ -626,25 +635,38 @@ wext_get_caps (WifiDataWext *wext, const char *ifname, struct iw_range *range)
 	return caps;
 }
 
-WifiData *
-wifi_wext_init (int ifindex, gboolean check_scan)
+/*****************************************************************************/
+
+static void
+nm_wifi_utils_wext_init (NMWifiUtilsWext *self)
+{
+}
+
+static void
+nm_wifi_utils_wext_class_init (NMWifiUtilsWextClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMWifiUtilsClass *wifi_utils_class = NM_WIFI_UTILS_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	wifi_utils_class->get_mode = wifi_wext_get_mode;
+	wifi_utils_class->set_mode = wifi_wext_set_mode;
+	wifi_utils_class->set_powersave = wifi_wext_set_powersave;
+	wifi_utils_class->get_freq = wifi_wext_get_freq;
+	wifi_utils_class->find_freq = wifi_wext_find_freq;
+	wifi_utils_class->get_bssid = wifi_wext_get_bssid;
+	wifi_utils_class->get_rate = wifi_wext_get_rate;
+	wifi_utils_class->get_qual = wifi_wext_get_qual;
+	wifi_utils_class->get_mesh_channel = wifi_wext_get_mesh_channel;
+	wifi_utils_class->set_mesh_channel = wifi_wext_set_mesh_channel;
+	wifi_utils_class->set_mesh_ssid = wifi_wext_set_mesh_ssid;
+}
+
+NMWifiUtils *
+nm_wifi_utils_wext_new (int ifindex, gboolean check_scan)
 {
-	static const WifiDataClass klass = {
-		.struct_size = sizeof (WifiDataWext),
-		.get_mode = wifi_wext_get_mode,
-		.set_mode = wifi_wext_set_mode,
-		.set_powersave = wifi_wext_set_powersave,
-		.get_freq = wifi_wext_get_freq,
-		.find_freq = wifi_wext_find_freq,
-		.get_bssid = wifi_wext_get_bssid,
-		.get_rate = wifi_wext_get_rate,
-		.get_qual = wifi_wext_get_qual,
-		.deinit = wifi_wext_deinit,
-		.get_mesh_channel = wifi_wext_get_mesh_channel,
-		.set_mesh_channel = wifi_wext_set_mesh_channel,
-		.set_mesh_ssid = wifi_wext_set_mesh_ssid,
-	};
-	WifiDataWext *wext;
+	NMWifiUtilsWext *wext;
 	struct iw_range range;
 	guint32 response_len = 0;
 	struct iw_range_with_scan_capa *scan_capa_range;
@@ -658,8 +680,9 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 		return NULL;
 	}
 
-	wext = wifi_data_new (&klass, ifindex);
+	wext = g_object_new (NM_TYPE_WIFI_UTILS_WEXT, NULL);
 
+	wext->parent.ifindex = ifindex;
 	wext->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
 	if (wext->fd < 0)
 		goto error;
@@ -731,15 +754,15 @@ wifi_wext_init (int ifindex, gboolean check_scan)
 	       "(%s): using WEXT for WiFi device control",
 	       ifname);
 
-	return (WifiData *) wext;
+	return (NMWifiUtils *) wext;
 
 error:
-	wifi_utils_unref ((WifiData *) wext);
+	g_object_unref (wext);
 	return NULL;
 }
 
 gboolean
-wifi_wext_is_wifi (const char *iface)
+nm_wifi_utils_wext_is_wifi (const char *iface)
 {
 	int fd;
 	struct iwreq iwr;
diff --git a/src/platform/wifi/wifi-utils-wext.h b/src/platform/wifi/nm-wifi-utils-wext.h
index 3ef5a173..44b11afb 100644
--- a/src/platform/wifi/wifi-utils-wext.h
+++ b/src/platform/wifi/nm-wifi-utils-wext.h
@@ -15,16 +15,25 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2011 Red Hat, Inc.
+ * Copyright (C) 2011 - 2018 Red Hat, Inc.
  */
 
 #ifndef __WIFI_UTILS_WEXT_H__
 #define __WIFI_UTILS_WEXT_H__
 
-#include "wifi-utils.h"
+#include "nm-wifi-utils.h"
 
-WifiData *wifi_wext_init (int ifindex, gboolean check_scan);
+#define NM_TYPE_WIFI_UTILS_WEXT            (nm_wifi_utils_wext_get_type ())
+#define NM_WIFI_UTILS_WEXT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWext))
+#define NM_WIFI_UTILS_WEXT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass))
+#define NM_IS_WIFI_UTILS_WEXT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_UTILS_WEXT))
+#define NM_IS_WIFI_UTILS_WEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_UTILS_WEXT))
+#define NM_WIFI_UTILS_WEXT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_UTILS_WEXT, NMWifiUtilsWextClass))
 
-gboolean wifi_wext_is_wifi (const char *iface);
+GType nm_wifi_utils_wext_get_type (void);
+
+NMWifiUtils *nm_wifi_utils_wext_new (int ifindex, gboolean check_scan);
+
+gboolean nm_wifi_utils_wext_is_wifi (const char *iface);
 
 #endif  /* __WIFI_UTILS_WEXT_H__ */
diff --git a/src/platform/wifi/nm-wifi-utils.c b/src/platform/wifi/nm-wifi-utils.c
new file mode 100644
index 00000000..25d71c6a
--- /dev/null
+++ b/src/platform/wifi/nm-wifi-utils.c
@@ -0,0 +1,240 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wifi-utils.h"
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "nm-wifi-utils-private.h"
+#include "nm-wifi-utils-nl80211.h"
+#if HAVE_WEXT
+#include "nm-wifi-utils-wext.h"
+#endif
+#include "nm-core-utils.h"
+
+#include "platform/nm-platform-utils.h"
+
+G_DEFINE_ABSTRACT_TYPE (NMWifiUtils, nm_wifi_utils, G_TYPE_OBJECT)
+
+/*****************************************************************************/
+
+static void
+nm_wifi_utils_init (NMWifiUtils *self)
+{
+}
+
+static void
+nm_wifi_utils_class_init (NMWifiUtilsClass *klass)
+{
+}
+
+NMWifiUtils *
+nm_wifi_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan)
+{
+	NMWifiUtils *ret;
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	ret = nm_wifi_utils_nl80211_new (ifindex, genl);
+
+#if HAVE_WEXT
+	if (ret == NULL)
+		ret = nm_wifi_utils_wext_new (ifindex, check_scan);
+#endif
+
+	return ret;
+}
+
+NMDeviceWifiCapabilities
+nm_wifi_utils_get_caps (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, NM_WIFI_DEVICE_CAP_NONE);
+
+	return data->caps;
+}
+
+NM80211Mode
+nm_wifi_utils_get_mode (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, NM_802_11_MODE_UNKNOWN);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_mode (data);
+}
+
+gboolean
+nm_wifi_utils_set_mode (NMWifiUtils *data, const NM80211Mode mode)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (   (mode == NM_802_11_MODE_INFRA)
+	                      || (mode == NM_802_11_MODE_AP)
+	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+
+	/* nl80211 probably doesn't need this */
+	return klass->set_mode ? klass->set_mode (data, mode) : TRUE;
+}
+
+gboolean
+nm_wifi_utils_set_powersave (NMWifiUtils *data, guint32 powersave)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	return klass->set_powersave ? klass->set_powersave (data, powersave) : TRUE;
+}
+
+NMSettingWirelessWakeOnWLan
+nm_wifi_utils_get_wake_on_wlan (NMWifiUtils *data)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+
+	return klass->get_wake_on_wlan ? klass->get_wake_on_wlan (data) : NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE;
+}
+
+gboolean
+nm_wifi_utils_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	return klass->set_wake_on_wlan ? klass->set_wake_on_wlan (data, wowl) : FALSE;
+}
+
+guint32
+nm_wifi_utils_get_freq (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_freq (data);
+}
+
+guint32
+nm_wifi_utils_find_freq (NMWifiUtils *data, const guint32 *freqs)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	g_return_val_if_fail (freqs != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->find_freq (data, freqs);
+}
+
+gboolean
+nm_wifi_utils_get_bssid (NMWifiUtils *data, guint8 *out_bssid)
+{
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (out_bssid != NULL, FALSE);
+
+	memset (out_bssid, 0, ETH_ALEN);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_bssid (data, out_bssid);
+}
+
+guint32
+nm_wifi_utils_get_rate (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_rate (data);
+}
+
+int
+nm_wifi_utils_get_qual (NMWifiUtils *data)
+{
+	g_return_val_if_fail (data != NULL, 0);
+	return NM_WIFI_UTILS_GET_CLASS (data)->get_qual (data);
+}
+
+gboolean
+nm_wifi_utils_is_wifi (int dirfd, const char *ifname)
+{
+	g_return_val_if_fail (dirfd >= 0, FALSE);
+
+	if (faccessat (dirfd, "phy80211", F_OK, 0) == 0)
+		return TRUE;
+#if HAVE_WEXT
+	if (nm_wifi_utils_wext_is_wifi (ifname))
+		return TRUE;
+#endif
+	return FALSE;
+}
+
+/* OLPC Mesh-only functions */
+
+guint32
+nm_wifi_utils_get_mesh_channel (NMWifiUtils *data)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	g_return_val_if_fail (klass->get_mesh_channel != NULL, FALSE);
+
+	return klass->get_mesh_channel (data);
+}
+
+gboolean
+nm_wifi_utils_set_mesh_channel (NMWifiUtils *data, guint32 channel)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+	g_return_val_if_fail (channel <= 13, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	g_return_val_if_fail (klass->set_mesh_channel != NULL, FALSE);
+
+	return klass->set_mesh_channel (data, channel);
+}
+
+gboolean
+nm_wifi_utils_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	g_return_val_if_fail (klass->set_mesh_ssid != NULL, FALSE);
+
+	return klass->set_mesh_ssid (data, ssid, len);
+}
+
+gboolean
+nm_wifi_utils_indicate_addressing_running (NMWifiUtils *data, gboolean running)
+{
+	NMWifiUtilsClass *klass;
+
+	g_return_val_if_fail (data != NULL, FALSE);
+
+	klass = NM_WIFI_UTILS_GET_CLASS (data);
+	return klass->indicate_addressing_running ? klass->indicate_addressing_running (data, running) : FALSE;
+}
diff --git a/src/platform/wifi/nm-wifi-utils.h b/src/platform/wifi/nm-wifi-utils.h
new file mode 100644
index 00000000..6cd178bd
--- /dev/null
+++ b/src/platform/wifi/nm-wifi-utils.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2005 - 2018 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#ifndef __WIFI_UTILS_H__
+#define __WIFI_UTILS_H__
+
+#include <net/ethernet.h>
+
+#include "nm-dbus-interface.h"
+#include "nm-setting-wireless.h"
+#include "platform/nm-netlink.h"
+
+typedef struct NMWifiUtils NMWifiUtils;
+
+#define NM_TYPE_WIFI_UTILS            (nm_wifi_utils_get_type ())
+#define NM_WIFI_UTILS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WIFI_UTILS, NMWifiUtils))
+#define NM_WIFI_UTILS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass))
+#define NM_IS_WIFI_UTILS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WIFI_UTILS))
+#define NM_IS_WIFI_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WIFI_UTILS))
+#define NM_WIFI_UTILS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WIFI_UTILS, NMWifiUtilsClass))
+
+GType nm_wifi_utils_get_type (void);
+
+gboolean nm_wifi_utils_is_wifi (int dirfd, const char *ifname);
+
+NMWifiUtils *nm_wifi_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan);
+
+NMDeviceWifiCapabilities nm_wifi_utils_get_caps (NMWifiUtils *data);
+
+NM80211Mode nm_wifi_utils_get_mode (NMWifiUtils *data);
+
+gboolean nm_wifi_utils_set_mode (NMWifiUtils *data, const NM80211Mode mode);
+
+/* Returns frequency in MHz */
+guint32 nm_wifi_utils_get_freq (NMWifiUtils *data);
+
+/* Return the first supported frequency in the zero-terminated list.
+ * Frequencies are specified in MHz. */
+guint32 nm_wifi_utils_find_freq (NMWifiUtils *data, const guint32 *freqs);
+
+/* out_bssid must be ETH_ALEN bytes */
+gboolean nm_wifi_utils_get_bssid (NMWifiUtils *data, guint8 *out_bssid);
+
+/* Returns current bitrate in Kbps */
+guint32 nm_wifi_utils_get_rate (NMWifiUtils *data);
+
+/* Returns quality 0 - 100% on succes, or -1 on error */
+int nm_wifi_utils_get_qual (NMWifiUtils *data);
+
+/* Tells the driver DHCP or SLAAC is running */
+gboolean nm_wifi_utils_indicate_addressing_running (NMWifiUtils *data, gboolean running);
+
+gboolean nm_wifi_utils_set_powersave (NMWifiUtils *data, guint32 powersave);
+
+NMSettingWirelessWakeOnWLan nm_wifi_utils_get_wake_on_wlan (NMWifiUtils *data);
+
+gboolean nm_wifi_utils_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wowl);
+
+/* OLPC Mesh-only functions */
+guint32 nm_wifi_utils_get_mesh_channel (NMWifiUtils *data);
+
+gboolean nm_wifi_utils_set_mesh_channel (NMWifiUtils *data, guint32 channel);
+
+gboolean nm_wifi_utils_set_mesh_ssid (NMWifiUtils *data, const guint8 *ssid, gsize len);
+
+#endif  /* __WIFI_UTILS_H__ */
diff --git a/src/platform/wifi/wifi-utils.c b/src/platform/wifi/wifi-utils.c
deleted file mode 100644
index b3dd92ba..00000000
--- a/src/platform/wifi/wifi-utils.c
+++ /dev/null
@@ -1,231 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#include "nm-default.h"
-
-#include "wifi-utils.h"
-
-#include <sys/stat.h>
-#include <stdio.h>
-#include <string.h>
-#include <fcntl.h>
-
-#include "wifi-utils-private.h"
-#include "wifi-utils-nl80211.h"
-#if HAVE_WEXT
-#include "wifi-utils-wext.h"
-#endif
-#include "nm-core-utils.h"
-
-#include "platform/nm-platform-utils.h"
-
-gpointer
-wifi_data_new (const WifiDataClass *klass, int ifindex)
-{
-	WifiData *data;
-
-	nm_assert (klass);
-	nm_assert (klass->struct_size > sizeof (WifiData));
-
-	data = g_malloc0 (klass->struct_size);
-	data->klass = klass;
-	data->ifindex = ifindex;
-	return data;
-}
-
-/*****************************************************************************/
-
-WifiData *
-wifi_utils_init (int ifindex, gboolean check_scan)
-{
-	WifiData *ret;
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
-	ret = wifi_nl80211_init (ifindex);
-	if (ret == NULL) {
-#if HAVE_WEXT
-		ret = wifi_wext_init (ifindex, check_scan);
-#endif
-	}
-	return ret;
-}
-
-int
-wifi_utils_get_ifindex (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, -1);
-
-	return data->ifindex;
-}
-
-NMDeviceWifiCapabilities
-wifi_utils_get_caps (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, NM_WIFI_DEVICE_CAP_NONE);
-
-	return data->caps;
-}
-
-NM80211Mode
-wifi_utils_get_mode (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, NM_802_11_MODE_UNKNOWN);
-	return data->klass->get_mode (data);
-}
-
-gboolean
-wifi_utils_set_mode (WifiData *data, const NM80211Mode mode)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (   (mode == NM_802_11_MODE_INFRA)
-	                      || (mode == NM_802_11_MODE_AP)
-	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
-
-	/* nl80211 probably doesn't need this */
-	return data->klass->set_mode ? data->klass->set_mode (data, mode) : TRUE;
-}
-
-gboolean
-wifi_utils_set_powersave (WifiData *data, guint32 powersave)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-
-	return data->klass->set_powersave ? data->klass->set_powersave (data, powersave) : TRUE;
-}
-
-gboolean
-wifi_utils_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-
-	return data->klass->set_wake_on_wlan ?
-	       data->klass->set_wake_on_wlan (data, wowl) : FALSE;
-}
-
-guint32
-wifi_utils_get_freq (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_freq (data);
-}
-
-guint32
-wifi_utils_find_freq (WifiData *data, const guint32 *freqs)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	g_return_val_if_fail (freqs != NULL, 0);
-	return data->klass->find_freq (data, freqs);
-}
-
-gboolean
-wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (out_bssid != NULL, FALSE);
-
-	memset (out_bssid, 0, ETH_ALEN);
-	return data->klass->get_bssid (data, out_bssid);
-}
-
-guint32
-wifi_utils_get_rate (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_rate (data);
-}
-
-int
-wifi_utils_get_qual (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-	return data->klass->get_qual (data);
-}
-
-gboolean
-wifi_utils_get_wowlan (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, 0);
-
-	if (!data->klass->get_wowlan)
-		return FALSE;
-	return data->klass->get_wowlan (data);
-}
-
-void
-wifi_utils_unref (WifiData *data)
-{
-	g_return_if_fail (data != NULL);
-
-	data->klass->deinit (data);
-	g_free (data);
-}
-
-gboolean
-wifi_utils_is_wifi (int dirfd, const char *ifname)
-{
-	g_return_val_if_fail (dirfd >= 0, FALSE);
-
-	if (faccessat (dirfd, "phy80211", F_OK, 0) == 0)
-		return TRUE;
-#if HAVE_WEXT
-	if (wifi_wext_is_wifi (ifname))
-		return TRUE;
-#endif
-	return FALSE;
-}
-
-/* OLPC Mesh-only functions */
-
-guint32
-wifi_utils_get_mesh_channel (WifiData *data)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->klass->get_mesh_channel != NULL, FALSE);
-	return data->klass->get_mesh_channel (data);
-}
-
-gboolean
-wifi_utils_set_mesh_channel (WifiData *data, guint32 channel)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (channel <= 13, FALSE);
-	g_return_val_if_fail (data->klass->set_mesh_channel != NULL, FALSE);
-	return data->klass->set_mesh_channel (data, channel);
-}
-
-gboolean
-wifi_utils_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	g_return_val_if_fail (data->klass->set_mesh_ssid != NULL, FALSE);
-	return data->klass->set_mesh_ssid (data, ssid, len);
-}
-
-gboolean
-wifi_utils_indicate_addressing_running (WifiData *data, gboolean running)
-{
-	g_return_val_if_fail (data != NULL, FALSE);
-	if (data->klass->indicate_addressing_running)
-		return data->klass->indicate_addressing_running (data, running);
-	return FALSE;
-}
-
diff --git a/src/platform/wifi/wifi-utils.h b/src/platform/wifi/wifi-utils.h
deleted file mode 100644
index c69561de..00000000
--- a/src/platform/wifi/wifi-utils.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Copyright (C) 2005 - 2011 Red Hat, Inc.
- * Copyright (C) 2006 - 2008 Novell, Inc.
- */
-
-#ifndef __WIFI_UTILS_H__
-#define __WIFI_UTILS_H__
-
-#include <net/ethernet.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-setting-wireless.h"
-
-typedef struct WifiData WifiData;
-
-gboolean wifi_utils_is_wifi (int dirfd, const char *ifname);
-
-WifiData *wifi_utils_init (int ifindex, gboolean check_scan);
-
-int wifi_utils_get_ifindex (WifiData *data);
-
-void wifi_utils_unref (WifiData *data);
-
-NMDeviceWifiCapabilities wifi_utils_get_caps (WifiData *data);
-
-NM80211Mode wifi_utils_get_mode (WifiData *data);
-
-gboolean wifi_utils_set_mode (WifiData *data, const NM80211Mode mode);
-
-/* Returns frequency in MHz */
-guint32 wifi_utils_get_freq (WifiData *data);
-
-/* Return the first supported frequency in the zero-terminated list.
- * Frequencies are specified in MHz. */
-guint32 wifi_utils_find_freq (WifiData *data, const guint32 *freqs);
-
-/* out_bssid must be ETH_ALEN bytes */
-gboolean wifi_utils_get_bssid (WifiData *data, guint8 *out_bssid);
-
-/* Returns current bitrate in Kbps */
-guint32 wifi_utils_get_rate (WifiData *data);
-
-/* Returns quality 0 - 100% on succes, or -1 on error */
-int wifi_utils_get_qual (WifiData *data);
-
-/* Tells the driver DHCP or SLAAC is running */
-gboolean wifi_utils_indicate_addressing_running (WifiData *data, gboolean running);
-
-/* Returns true if WoWLAN is enabled on device */
-gboolean wifi_utils_get_wowlan (WifiData *data);
-
-gboolean wifi_utils_set_powersave (WifiData *data, guint32 powersave);
-
-gboolean wifi_utils_set_wake_on_wlan (WifiData *data, NMSettingWirelessWakeOnWLan wowl);
-
-/* OLPC Mesh-only functions */
-guint32 wifi_utils_get_mesh_channel (WifiData *data);
-
-gboolean wifi_utils_set_mesh_channel (WifiData *data, guint32 channel);
-
-gboolean wifi_utils_set_mesh_ssid (WifiData *data, const guint8 *ssid, gsize len);
-
-#endif  /* __WIFI_UTILS_H__ */
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
new file mode 100644
index 00000000..0544539a
--- /dev/null
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -0,0 +1,286 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-wpan-utils.h"
+
+#include "platform/linux/nl802154.h"
+#include "platform/nm-netlink.h"
+
+#define _NMLOG_PREFIX_NAME "wpan-nl802154"
+#define _NMLOG(level, domain, ...) \
+	G_STMT_START { \
+		nm_log ((level), (domain), NULL, NULL, \
+		        "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        _NMLOG_PREFIX_NAME \
+		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+	} G_STMT_END
+
+/*****************************************************************************/
+
+struct NMWpanUtils {
+        GObject parent;
+	int ifindex;
+	struct nl_sock *nl_sock;
+	int id;
+};
+
+typedef struct {
+        GObjectClass parent;
+} NMWpanUtilsClass;
+
+G_DEFINE_TYPE (NMWpanUtils, nm_wpan_utils, G_TYPE_OBJECT)
+
+/*****************************************************************************/
+
+static int
+ack_handler (struct nl_msg *msg, void *arg)
+{
+	int *done = arg;
+	*done = 1;
+	return NL_STOP;
+}
+
+static int
+finish_handler (struct nl_msg *msg, void *arg)
+{
+	int *done = arg;
+	*done = 1;
+	return NL_SKIP;
+}
+
+static int
+error_handler (struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
+{
+	int *done = arg;
+	*done = err->error;
+	return NL_SKIP;
+}
+
+static struct nl_msg *
+_nl802154_alloc_msg (int id, int ifindex, guint32 cmd, guint32 flags)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	msg = nlmsg_alloc ();
+	genlmsg_put (msg, 0, 0, id, 0, flags, cmd, 0);
+	NLA_PUT_U32 (msg, NL802154_ATTR_IFINDEX, ifindex);
+	return g_steal_pointer (&msg);
+
+nla_put_failure:
+	return NULL;
+}
+
+static struct nl_msg *
+nl802154_alloc_msg (NMWpanUtils *self, guint32 cmd, guint32 flags)
+{
+	return _nl802154_alloc_msg (self->id, self->ifindex, cmd, flags);
+}
+
+static int
+_nl802154_send_and_recv (struct nl_sock *nl_sock,
+                         struct nl_msg *msg,
+                         int (*valid_handler) (struct nl_msg *, void *),
+                         void *valid_data)
+{
+	int err;
+	int done = 0;
+	const struct nl_cb cb = {
+		.err_cb     = error_handler,
+		.err_arg    = &done,
+		.finish_cb  = finish_handler,
+		.finish_arg = &done,
+		.ack_cb     = ack_handler,
+		.ack_arg    = &done,
+		.valid_cb   = valid_handler,
+		.valid_arg  = valid_data,
+	};
+
+	g_return_val_if_fail (msg != NULL, -ENOMEM);
+
+	err = nl_send_auto (nl_sock, msg);
+	if (err < 0)
+		return err;
+
+	/* Loop until one of our NL callbacks says we're done; on success
+	 * done will be 1, on error it will be < 0.
+	 */
+	while (!done) {
+		err = nl_recvmsgs (nl_sock, &cb);
+		if (err < 0 && err != -EAGAIN) {
+			_LOGW (LOGD_PLATFORM, "nl_recvmsgs() error: (%d) %s",
+			       err, nl_geterror (err));
+			break;
+		}
+	}
+
+	if (err >= 0 && done < 0)
+		err = done;
+	return err;
+}
+
+static int
+nl802154_send_and_recv (NMWpanUtils *self,
+                        struct nl_msg *msg,
+                        int (*valid_handler) (struct nl_msg *, void *),
+                        void *valid_data)
+{
+	return _nl802154_send_and_recv (self->nl_sock, msg,
+	                                valid_handler, valid_data);
+}
+
+struct nl802154_interface {
+	guint16 pan_id;
+	guint16 short_addr;
+
+	gboolean valid;
+};
+
+static int
+nl802154_get_interface_handler (struct nl_msg *msg, void *arg)
+{
+	struct nl802154_interface *info = arg;
+	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
+	struct nlattr *tb[NL802154_ATTR_MAX + 1] = { 0, };
+	static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX + 1] = {
+		[NL802154_ATTR_PAN_ID] =            { .type = NLA_U16 },
+		[NL802154_ATTR_SHORT_ADDR] =        { .type = NLA_U16 },
+	};
+
+	if (nla_parse (tb, NL802154_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
+	               genlmsg_attrlen (gnlh, 0), nl802154_policy) < 0)
+	return NL_SKIP;
+
+	if (tb[NL802154_ATTR_PAN_ID])
+		info->pan_id = le16toh (nla_get_u16 (tb[NL802154_ATTR_PAN_ID]));
+
+	if (tb[NL802154_ATTR_SHORT_ADDR])
+		info->short_addr = le16toh (nla_get_u16 (tb[NL802154_ATTR_SHORT_ADDR]));
+
+	info->valid = TRUE;
+
+	return NL_SKIP;
+}
+
+static void
+nl802154_get_interface (NMWpanUtils *self,
+                        struct nl802154_interface *interface)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+
+	memset (interface, 0, sizeof (*interface));
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_GET_INTERFACE, 0);
+
+	nl802154_send_and_recv (self, msg, nl802154_get_interface_handler, interface);
+}
+
+/*****************************************************************************/
+
+guint16
+nm_wpan_utils_get_pan_id (NMWpanUtils *self)
+{
+	struct nl802154_interface interface;
+
+	nl802154_get_interface (self, &interface);
+
+	return interface.pan_id;
+}
+
+gboolean
+nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int err;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_SET_PAN_ID, 0);
+	NLA_PUT_U16 (msg, NL802154_ATTR_PAN_ID, htole16 (pan_id));
+	err = nl802154_send_and_recv (self, msg, NULL, NULL);
+	return err >= 0;
+
+nla_put_failure:
+	return FALSE;
+}
+
+guint16
+nm_wpan_utils_get_short_addr (NMWpanUtils *self)
+{
+	struct nl802154_interface interface;
+
+	nl802154_get_interface (self, &interface);
+
+	return interface.short_addr;
+}
+
+gboolean
+nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr)
+{
+	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	int err;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	msg = nl802154_alloc_msg (self, NL802154_CMD_SET_SHORT_ADDR, 0);
+	NLA_PUT_U16 (msg, NL802154_ATTR_SHORT_ADDR, htole16 (short_addr));
+	err = nl802154_send_and_recv (self, msg, NULL, NULL);
+	return err >= 0;
+
+nla_put_failure:
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static void
+nm_wpan_utils_init (NMWpanUtils *self)
+{
+}
+
+static void
+nm_wpan_utils_class_init (NMWpanUtilsClass *klass)
+{
+}
+
+NMWpanUtils *
+nm_wpan_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan)
+{
+	NMWpanUtils *self;
+	int id;
+
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	if (!genl)
+		return NULL;
+
+	id = genl_ctrl_resolve (genl, "nl802154");
+	if (id < 0) {
+		_LOGD (LOGD_PLATFORM, "genl_ctrl_resolve: failed to resolve \"nl802154\"");
+		return NULL;
+	}
+
+	self = g_object_new (NM_TYPE_WPAN_UTILS, NULL);
+	self->ifindex = ifindex;
+	self->nl_sock = genl;
+	self->id = id;
+
+	return self;
+}
diff --git a/src/platform/wpan/nm-wpan-utils.h b/src/platform/wpan/nm-wpan-utils.h
new file mode 100644
index 00000000..f7d0c03e
--- /dev/null
+++ b/src/platform/wpan/nm-wpan-utils.h
@@ -0,0 +1,47 @@
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __WPAN_UTILS_H__
+#define __WPAN_UTILS_H__
+
+#include <net/ethernet.h>
+
+#include "nm-dbus-interface.h"
+#include "platform/nm-netlink.h"
+
+typedef struct NMWpanUtils NMWpanUtils;
+
+#define NM_TYPE_WPAN_UTILS            (nm_wpan_utils_get_type ())
+#define NM_WPAN_UTILS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WPAN_UTILS, NMWpanUtils))
+#define NM_WPAN_UTILS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass))
+#define NM_IS_WPAN_UTILS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_WPAN_UTILS))
+#define NM_IS_WPAN_UTILS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_WPAN_UTILS))
+#define NM_WPAN_UTILS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_WPAN_UTILS, NMWpanUtilsClass))
+
+GType nm_wpan_utils_get_type (void);
+
+NMWpanUtils *nm_wpan_utils_new (int ifindex, struct nl_sock *genl, gboolean check_scan);
+
+guint16 nm_wpan_utils_get_pan_id (NMWpanUtils *self);
+gboolean nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id);
+
+guint16 nm_wpan_utils_get_short_addr (NMWpanUtils *self);
+gboolean nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr);
+
+#endif  /* __WPAN_UTILS_H__ */
diff --git a/src/ppp/meson.build b/src/ppp/meson.build
index 38f9a0f1..20ed64ed 100644
--- a/src/ppp/meson.build
+++ b/src/ppp/meson.build
@@ -31,7 +31,6 @@ core_plugins += shared_module(
   name,
   sources: 'nm-ppp-manager.c',
   dependencies: deps,
-  c_args: '-DPPPD_PLUGIN_DIR="@0@"'.format(pppd_plugin_dir),
   link_args: [
     '-Wl,--version-script,@0@'.format(linker_script),
   ],
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index 5e893c85..fdec4763 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -695,7 +695,7 @@ nm_cmd_line_to_str (NMCmdLine *cmd)
 	char *str;
 
 	g_ptr_array_add (cmd->array, NULL);
-	str = g_strjoinv (" ", (gchar **) cmd->array->pdata);
+	str = g_strjoinv (" ", (char **) cmd->array->pdata);
 	g_ptr_array_remove_index (cmd->array, cmd->array->len - 1);
 
 	return str;
@@ -874,13 +874,13 @@ create_pppd_cmd_line (NMPPPManager *self,
 			nm_cmd_line_add_string (cmd, pppoe_service);
 		}
 	} else if (adsl) {
-		const gchar *protocol = nm_setting_adsl_get_protocol (adsl);
+		const char *protocol = nm_setting_adsl_get_protocol (adsl);
 
 		if (!strcmp (protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)) {
 			guint32 vpi = nm_setting_adsl_get_vpi (adsl);
 			guint32 vci = nm_setting_adsl_get_vci (adsl);
 			const char *encaps = nm_setting_adsl_get_encapsulation (adsl);
-			gchar *vpivci;
+			char *vpivci;
 
 			nm_cmd_line_add_string (cmd, "plugin");
 			nm_cmd_line_add_string (cmd, "pppoatm.so");
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 453136e4..953d3ce4 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -349,7 +349,7 @@ agent_register_permissions_done (NMAuthChain *chain,
 		priv->agent_version_id += 1;
 		sender = nm_secret_agent_get_dbus_owner (agent);
 		g_hash_table_insert (priv->agents, g_strdup (sender), agent);
-		_LOGD (agent, "agent registered");
+		_LOGI (agent, "agent registered");
 		g_dbus_method_invocation_return_value (context, NULL);
 
 		/* Signal an agent was registered */
@@ -642,7 +642,7 @@ req_complete_error (Request *req, GError *error)
 	req_complete (req, NULL, NULL, NULL, error);
 }
 
-static gint
+static int
 agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 {
 	NMSecretAgent *a = (NMSecretAgent *)aa;
@@ -651,12 +651,14 @@ agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 	NMSessionMonitor *sm;
 	gboolean a_active, b_active;
 	gulong a_pid, b_pid, requester;
+	guint64 a_start, b_start;
+
+	a_pid = nm_secret_agent_get_pid (a);
+	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)) {
 		requester = nm_auth_subject_get_unix_process_pid (req->subject);
-		a_pid = nm_secret_agent_get_pid (a);
-		b_pid = nm_secret_agent_get_pid (b);
 
 		if (a_pid != b_pid) {
 			if (a_pid == requester)
@@ -672,11 +674,17 @@ agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 	b_active = nm_session_monitor_session_exists (sm, nm_secret_agent_get_owner_uid (b), TRUE);
 	if (a_active && !b_active)
 		return -1;
-	else if (a_active == b_active)
-		return 0;
 	else if (!a_active && b_active)
 		return 1;
 
+	/* Prefer agents launched later (this is essentially to ease agent debugging) */
+	a_start = nm_utils_get_start_time_for_pid (a_pid, NULL, NULL);
+	b_start = nm_utils_get_start_time_for_pid (b_pid, NULL, NULL);
+	if (a_start > b_start)
+		return -1;
+	else if (a_start < b_start)
+		return 1;
+
 	return 0;
 }
 
diff --git a/src/settings/nm-secret-agent.c b/src/settings/nm-secret-agent.c
index af6d7017..836ab21d 100644
--- a/src/settings/nm-secret-agent.c
+++ b/src/settings/nm-secret-agent.c
@@ -648,10 +648,10 @@ _on_disconnected_private_connection (NMDBusManager *mgr,
 
 static void
 _on_disconnected_name_owner_changed (GDBusConnection *connection,
-                                     const gchar      *sender_name,
-                                     const gchar      *object_path,
-                                     const gchar      *interface_name,
-                                     const gchar      *signal_name,
+                                     const char       *sender_name,
+                                     const char       *object_path,
+                                     const char       *interface_name,
+                                     const char       *signal_name,
                                      GVariant         *parameters,
                                      gpointer          user_data)
 {
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index c09f6804..1b2edc83 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -49,7 +49,26 @@
 
 /*****************************************************************************/
 
-static void nm_settings_connection_connection_interface_init (NMConnectionInterface *iface);
+NMConnection **
+nm_settings_connections_array_to_connections (NMSettingsConnection *const*connections,
+                                              gssize n_connections)
+{
+	NMConnection **arr;
+	gssize i;
+
+	if (n_connections < 0)
+		n_connections = NM_PTRARRAY_LEN (connections);
+	if (n_connections == 0)
+		return NULL;
+
+	arr = g_new (NMConnection *, n_connections + 1);
+	for (i = 0; i < n_connections; i++)
+		arr[i] = nm_settings_connection_get_connection (connections[i]);
+	arr[i] = NULL;
+	return arr;
+}
+
+/*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMSettingsConnection,
 	PROP_UNSAVED,
@@ -87,6 +106,8 @@ typedef struct _NMSettingsConnectionPrivate {
 
 	CList call_ids_lst_head; /* in-progress secrets requests */
 
+	NMConnection *connection;
+
 	/* Caches secrets from on-disk connections; were they not cached any
 	 * call to nm_connection_clear_secrets() wipes them out and we'd have
 	 * to re-read them from disk which defeats the purpose of having the
@@ -115,9 +136,7 @@ typedef struct _NMSettingsConnectionPrivate {
 
 } NMSettingsConnectionPrivate;
 
-G_DEFINE_TYPE_WITH_CODE (NMSettingsConnection, nm_settings_connection, NM_TYPE_DBUS_OBJECT,
-                         G_IMPLEMENT_INTERFACE (NM_TYPE_CONNECTION, nm_settings_connection_connection_interface_init)
-                         )
+G_DEFINE_TYPE (NMSettingsConnection, nm_settings_connection, NM_TYPE_DBUS_OBJECT)
 
 #define NM_SETTINGS_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMSettingsConnection, NM_IS_SETTINGS_CONNECTION)
 
@@ -152,6 +171,16 @@ static const NMDBusInterfaceInfoExtended interface_info_settings_connection;
 
 /*****************************************************************************/
 
+NMConnection *
+nm_settings_connection_get_connection (NMSettingsConnection *self)
+{
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NULL);
+
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->connection;
+}
+
+/*****************************************************************************/
+
 gboolean
 nm_settings_connection_has_unmodified_applied_connection (NMSettingsConnection *self,
                                                           NMConnection *applied_connection,
@@ -163,7 +192,8 @@ nm_settings_connection_has_unmodified_applied_connection (NMSettingsConnection *
 	/* for convenience, we *always* ignore certain settings. */
 	compare_flags |= NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS | NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP;
 
-	return nm_connection_compare (NM_CONNECTION (self), applied_connection, compare_flags);
+	return nm_connection_compare (nm_settings_connection_get_connection (self),
+	                              applied_connection, compare_flags);
 }
 
 /*****************************************************************************/
@@ -332,8 +362,7 @@ nm_settings_connection_recheck_visibility (NMSettingsConnection *self)
 
 	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
-	g_assert (s_con);
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
 
 	/* Check every user in the ACL for a session */
 	num = nm_setting_connection_get_num_permissions (s_con);
@@ -362,9 +391,9 @@ nm_settings_connection_recheck_visibility (NMSettingsConnection *self)
 }
 
 static void
-session_changed_cb (NMSessionMonitor *self, gpointer user_data)
+session_changed_cb (NMSessionMonitor *self, NMSettingsConnection *sett_conn)
 {
-	nm_settings_connection_recheck_visibility (NM_SETTINGS_CONNECTION (user_data));
+	nm_settings_connection_recheck_visibility (sett_conn);
 }
 
 /*****************************************************************************/
@@ -390,8 +419,7 @@ nm_settings_connection_check_permission (NMSettingsConnection *self,
 	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE))
 		return FALSE;
 
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
-	g_assert (s_con);
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
 
 	/* Check every user in the ACL for a session */
 	num = nm_setting_connection_get_num_permissions (s_con);
@@ -447,7 +475,7 @@ update_system_secrets_cache (NMSettingsConnection *self)
 
 	if (priv->system_secrets)
 		g_object_unref (priv->system_secrets);
-	priv->system_secrets = nm_simple_connection_new_clone (NM_CONNECTION (self));
+	priv->system_secrets = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
 
 	/* Clear out non-system-owned and not-saved secrets */
 	nm_connection_clear_secrets_with_flags (priv->system_secrets,
@@ -463,7 +491,8 @@ update_agent_secrets_cache (NMSettingsConnection *self, NMConnection *new)
 
 	if (priv->agent_secrets)
 		g_object_unref (priv->agent_secrets);
-	priv->agent_secrets = nm_simple_connection_new_clone (new ?: NM_CONNECTION(self));
+	priv->agent_secrets = nm_simple_connection_new_clone (   new
+	                                                      ?: nm_settings_connection_get_connection (self));
 
 	/* Clear out non-system-owned secrets */
 	nm_connection_clear_secrets_with_flags (priv->agent_secrets,
@@ -472,7 +501,7 @@ update_agent_secrets_cache (NMSettingsConnection *self, NMConnection *new)
 }
 
 static void
-secrets_cleared_cb (NMSettingsConnection *self)
+secrets_cleared_cb (NMConnection *connection, NMSettingsConnection *self)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
@@ -533,7 +562,7 @@ _emit_updated (NMSettingsConnection *self, gboolean by_user)
 }
 
 static void
-connection_changed_cb (NMSettingsConnection *self, gpointer unused)
+connection_changed_cb (NMConnection *connection, NMSettingsConnection *self)
 {
 	set_persist_mode (self, NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED);
 	_emit_updated (self, FALSE);
@@ -636,7 +665,7 @@ nm_settings_connection_update (NMSettingsConnection *self,
 
 	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK) {
 		if (!klass->commit_changes (self,
-		                            new_connection ?: NM_CONNECTION (self),
+		                            new_connection ?: nm_settings_connection_get_connection (self),
 		                            commit_reason,
 		                            &reread_connection,
 		                            &logmsg_change,
@@ -655,30 +684,30 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	/* Disconnect the changed signal to ensure we don't set Unsaved when
 	 * it's not required.
 	 */
-	g_signal_handlers_block_by_func (self, G_CALLBACK (connection_changed_cb), NULL);
+	g_signal_handlers_block_by_func (priv->connection, G_CALLBACK (connection_changed_cb), self);
 
 	/* Do nothing if there's nothing to update */
 	if (   replace_connection
-	    && !nm_connection_compare (NM_CONNECTION (self),
+	    && !nm_connection_compare (nm_settings_connection_get_connection (self),
 	                               replace_connection,
 	                               NM_SETTING_COMPARE_FLAG_EXACT)) {
 		gs_unref_object NMConnection *simple = NULL;
 
 		if (log_diff_name) {
-			nm_utils_log_connection_diff (replace_connection, NM_CONNECTION (self), LOGL_DEBUG, LOGD_CORE, log_diff_name, "++ ",
+			nm_utils_log_connection_diff (replace_connection, nm_settings_connection_get_connection (self), LOGL_DEBUG, LOGD_CORE, log_diff_name, "++ ",
 			                              nm_dbus_object_get_path (NM_DBUS_OBJECT (self)));
 		}
 
 		/* Make a copy of agent-owned secrets because they won't be present in
 		 * the connection returned by plugins, as plugins return only what was
 		 * reread from the file. */
-		simple = nm_simple_connection_new_clone (NM_CONNECTION (self));
+		simple = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
 		nm_connection_clear_secrets_with_flags (simple,
 		                                        secrets_filter_cb,
 		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
 		con_agent_secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
 
-		nm_connection_replace_settings_from_connection (NM_CONNECTION (self), replace_connection);
+		nm_connection_replace_settings_from_connection (nm_settings_connection_get_connection (self), replace_connection);
 
 		replaced = TRUE;
 	}
@@ -701,12 +730,12 @@ nm_settings_connection_update (NMSettingsConnection *self,
 
 			dict = nm_connection_to_dbus (priv->agent_secrets, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
 			if (dict) {
-				(void) nm_connection_update_secrets (NM_CONNECTION (self), NULL, dict, NULL);
+				(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self), NULL, dict, NULL);
 				g_variant_unref (dict);
 			}
 		}
 		if (con_agent_secrets)
-			(void) nm_connection_update_secrets (NM_CONNECTION (self), NULL, con_agent_secrets, NULL);
+			(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self), NULL, con_agent_secrets, NULL);
 	}
 
 	nm_settings_connection_recheck_visibility (self);
@@ -724,7 +753,7 @@ nm_settings_connection_update (NMSettingsConnection *self,
 	                                  NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_DETACHED))
 		nm_settings_connection_set_filename (self, NULL);
 
-	g_signal_handlers_unblock_by_func (self, G_CALLBACK (connection_changed_cb), NULL);
+	g_signal_handlers_unblock_by_func (priv->connection, G_CALLBACK (connection_changed_cb), self);
 
 	_emit_updated (self, TRUE);
 
@@ -800,7 +829,7 @@ nm_settings_connection_delete (NMSettingsConnection *self,
 	set_visible (self, FALSE);
 
 	/* Tell agents to remove secrets for this connection */
-	for_agents = nm_simple_connection_new_clone (NM_CONNECTION (self));
+	for_agents = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
 	nm_connection_clear_secrets (for_agents);
 	nm_agent_manager_delete_secrets (priv->agent_mgr,
 	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
@@ -920,7 +949,7 @@ get_cmp_flags (NMSettingsConnection *self, /* only needed for logging */
                gboolean *agent_had_system,
                ForEachSecretFlags *cmp_flags)
 {
-	gboolean is_self = (((NMConnection *) self) == connection);
+	gboolean is_self = (nm_settings_connection_get_connection (self) == connection);
 
 	g_return_if_fail (secrets);
 
@@ -1001,7 +1030,7 @@ nm_settings_connection_new_secrets (NMSettingsConnection *self,
 		return FALSE;
 	}
 
-	if (!nm_connection_update_secrets (NM_CONNECTION (self), setting_name, secrets, error))
+	if (!nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, secrets, error))
 		return FALSE;
 
 	update_system_secrets_cache (self);
@@ -1073,7 +1102,7 @@ get_secrets_done_cb (NMAgentManager *manager,
 		goto out;
 	}
 
-	if (!nm_connection_get_setting_by_name (NM_CONNECTION (self), setting_name)) {
+	if (!nm_connection_get_setting_by_name (nm_settings_connection_get_connection (self), setting_name)) {
 		g_set_error (&local, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
 		             "Connection didn't have requested setting '%s'.",
 		             setting_name);
@@ -1083,7 +1112,7 @@ get_secrets_done_cb (NMAgentManager *manager,
 
 	get_cmp_flags (self,
 	               call_id,
-	               NM_CONNECTION (self),
+	               nm_settings_connection_get_connection (self),
 	               agent_dbus_owner,
 	               agent_has_modify,
 	               setting_name,
@@ -1100,8 +1129,8 @@ get_secrets_done_cb (NMAgentManager *manager,
 		dict = nm_connection_to_dbus (priv->system_secrets, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
 
 	/* Update the connection with our existing secrets from backing storage */
-	nm_connection_clear_secrets (NM_CONNECTION (self));
-	if (!dict || nm_connection_update_secrets (NM_CONNECTION (self), setting_name, dict, &local)) {
+	nm_connection_clear_secrets (nm_settings_connection_get_connection (self));
+	if (!dict || nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, dict, &local)) {
 		GVariant *filtered_secrets;
 
 		/* Update the connection with the agent's secrets; by this point if any
@@ -1109,8 +1138,8 @@ get_secrets_done_cb (NMAgentManager *manager,
 		 * will have been authenticated, so those secrets can replace the existing
 		 * system secrets.
 		 */
-		filtered_secrets = for_each_secret (NM_CONNECTION (self), secrets, TRUE, validate_secret_flags, &cmp_flags);
-		if (nm_connection_update_secrets (NM_CONNECTION (self), setting_name, filtered_secrets, &local)) {
+		filtered_secrets = for_each_secret (nm_settings_connection_get_connection (self), secrets, TRUE, validate_secret_flags, &cmp_flags);
+		if (nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, filtered_secrets, &local)) {
 			/* Now that all secrets are updated, copy and cache new secrets,
 			 * then save them to backing storage.
 			 */
@@ -1250,7 +1279,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NULL);
 	g_return_val_if_fail (   !applied_connection
 	                      || (   NM_IS_CONNECTION (applied_connection)
-	                          && (((NMConnection *) self) != applied_connection)), NULL);
+	                          && (nm_settings_connection_get_connection (self) != applied_connection)), NULL);
 
 	call_id = g_slice_new0 (NMSettingsConnectionCallId);
 	call_id->self = self;
@@ -1264,7 +1293,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	c_list_link_tail (&priv->call_ids_lst_head, &call_id->call_ids_lst);
 
 	/* Make sure the request actually requests something we can return */
-	if (!nm_connection_get_setting_by_name (NM_CONNECTION (self), setting_name)) {
+	if (!nm_connection_get_setting_by_name (nm_settings_connection_get_connection (self), setting_name)) {
 		g_set_error (&local, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
 		             "Connection didn't have requested setting '%s'.",
 		             setting_name);
@@ -1295,7 +1324,7 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 
 	call_id_a = nm_agent_manager_get_secrets (priv->agent_mgr,
 	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
-	                                          NM_CONNECTION (self),
+	                                          nm_settings_connection_get_connection (self),
 	                                          subject,
 	                                          existing_secrets,
 	                                          setting_name,
@@ -1465,7 +1494,7 @@ auth_start (NMSettingsConnection *self,
 	nm_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
 	nm_assert (NM_IS_AUTH_SUBJECT (subject));
 
-	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (self),
+	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (self),
 	                                          subject,
 	                                          NM_SETTINGS_ERROR,
 	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
@@ -1539,15 +1568,14 @@ get_settings_auth_cb (NMSettingsConnection *self,
 	if (error)
 		g_dbus_method_invocation_return_gerror (context, error);
 	else {
+		gs_unref_object NMConnection *dupl_con = NULL;
 		GVariant *settings;
-		NMConnection *dupl_con;
 		NMSettingConnection *s_con;
 		NMSettingWireless *s_wifi;
 		guint64 timestamp = 0;
-		char **bssids;
+		gs_free char **bssids = NULL;
 
-		dupl_con = nm_simple_connection_new_clone (NM_CONNECTION (self));
-		g_assert (dupl_con);
+		dupl_con = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
 
 		/* Timestamp is not updated in connection's 'timestamp' property,
 		 * because it would force updating the connection and in turn
@@ -1557,8 +1585,7 @@ get_settings_auth_cb (NMSettingsConnection *self,
 		 */
 		nm_settings_connection_get_timestamp (self, &timestamp);
 		if (timestamp) {
-			s_con = nm_connection_get_setting_connection (NM_CONNECTION (dupl_con));
-			g_assert (s_con);
+			s_con = nm_connection_get_setting_connection (dupl_con);
 			g_object_set (s_con, NM_SETTING_CONNECTION_TIMESTAMP, timestamp, NULL);
 		}
 		/* Seen BSSIDs are not updated in 802-11-wireless 'seen-bssids' property
@@ -1566,20 +1593,17 @@ get_settings_auth_cb (NMSettingsConnection *self,
 		 * return settings too.
 		 */
 		bssids = nm_settings_connection_get_seen_bssids (self);
-		s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (dupl_con));
+		s_wifi = nm_connection_get_setting_wireless (dupl_con);
 		if (bssids && bssids[0] && s_wifi)
 			g_object_set (s_wifi, NM_SETTING_WIRELESS_SEEN_BSSIDS, bssids, NULL);
-		g_free (bssids);
 
 		/* Secrets should *never* be returned by the GetSettings method, they
 		 * get returned by the GetSecrets method which can be better
 		 * protected against leakage of secrets to unprivileged callers.
 		 */
-		settings = nm_connection_to_dbus (NM_CONNECTION (dupl_con), NM_CONNECTION_SERIALIZE_NO_SECRETS);
-		g_assert (settings);
+		settings = nm_connection_to_dbus (dupl_con, NM_CONNECTION_SERIALIZE_NO_SECRETS);
 		g_dbus_method_invocation_return_value (context,
 		                                       g_variant_new ("(@a{sa{sv}})", settings));
-		g_object_unref (dupl_con);
 	}
 }
 
@@ -1734,7 +1758,7 @@ update_auth_cb (NMSettingsConnection *self,
 			gs_unref_hashtable GHashTable *diff = NULL;
 			gboolean same;
 
-			same = nm_connection_diff (NM_CONNECTION (self), info->new_settings,
+			same = nm_connection_diff (nm_settings_connection_get_connection (self), info->new_settings,
 			                           NM_SETTING_COMPARE_FLAG_EXACT |
 			                           NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT,
 			                           &diff);
@@ -1745,7 +1769,7 @@ update_auth_cb (NMSettingsConnection *self,
 
 	commit_reason = NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION;
 	if (   info->new_settings
-	    && !nm_streq0 (nm_connection_get_id (NM_CONNECTION (self)),
+	    && !nm_streq0 (nm_connection_get_id (nm_settings_connection_get_connection (self)),
 	                   nm_connection_get_id (info->new_settings)))
 		commit_reason |= NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED;
 
@@ -1791,7 +1815,7 @@ update_auth_cb (NMSettingsConnection *self,
 		 * as agent-owned secrets are the only ones we send back be saved.
 		 * Only send secrets to agents of the same UID that called update too.
 		 */
-		for_agent = nm_simple_connection_new_clone (NM_CONNECTION (self));
+		for_agent = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
 		nm_connection_clear_secrets_with_flags (for_agent,
 		                                        secrets_filter_cb,
 		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
@@ -1811,11 +1835,9 @@ get_update_modify_permission (NMConnection *old, NMConnection *new)
 	guint32 orig_num = 0, new_num = 0;
 
 	s_con = nm_connection_get_setting_connection (old);
-	g_assert (s_con);
 	orig_num = nm_setting_connection_get_num_permissions (s_con);
 
 	s_con = nm_connection_get_setting_connection (new);
-	g_assert (s_con);
 	new_num = nm_setting_connection_get_num_permissions (s_con);
 
 	/* If the caller is the only user in either connection's permissions, then
@@ -1848,7 +1870,7 @@ settings_connection_update (NMSettingsConnection *self,
 	 * the problem (ex a system settings plugin that can't write connections out)
 	 * instead of over D-Bus.
 	 */
-	if (!check_writable (NM_CONNECTION (self), &error))
+	if (!check_writable (nm_settings_connection_get_connection (self), &error))
 		goto error;
 
 	/* Check if the settings are valid first */
@@ -1879,7 +1901,7 @@ settings_connection_update (NMSettingsConnection *self,
 	 * that's sending the update request.  You can't make a connection
 	 * invisible to yourself.
 	 */
-	if (!nm_auth_is_subject_in_acl_set_error (tmp ?: NM_CONNECTION(self),
+	if (!nm_auth_is_subject_in_acl_set_error (tmp ?: nm_settings_connection_get_connection (self),
 	                                          subject,
 	                                          NM_SETTINGS_ERROR,
 	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
@@ -1894,8 +1916,8 @@ settings_connection_update (NMSettingsConnection *self,
 	info->flags = flags;
 	info->new_settings = tmp;
 
-	permission = get_update_modify_permission (NM_CONNECTION (self),
-	                                           tmp ?: NM_CONNECTION(self));
+	permission = get_update_modify_permission (nm_settings_connection_get_connection (self),
+	                                           tmp ?: nm_settings_connection_get_connection (self));
 	auth_start (self, context, subject, permission, update_auth_cb, info);
 	return;
 
@@ -2066,8 +2088,7 @@ get_modify_permission_basic (NMSettingsConnection *self)
 	 * we use the 'modify.own' permission instead of 'modify.system'.  If the
 	 * request affects more than just the caller, require 'modify.system'.
 	 */
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
-	nm_assert (s_con);
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
 	if (nm_setting_connection_get_num_permissions (s_con) == 1)
 		return NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
 
@@ -2087,7 +2108,7 @@ impl_settings_connection_delete (NMDBusObject *obj,
 	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 
-	if (!check_writable (NM_CONNECTION (self), &error))
+	if (!check_writable (nm_settings_connection_get_connection (self), &error))
 		goto err;
 
 	subject = _new_auth_subject (invocation, &error);
@@ -2122,7 +2143,7 @@ dbus_get_agent_secrets_cb (NMSettingsConnection *self,
 		 * secrets from backing storage and those returned from the agent
 		 * by the time we get here.
 		 */
-		dict = nm_connection_to_dbus (NM_CONNECTION (self), NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
+		dict = nm_connection_to_dbus (nm_settings_connection_get_connection (self), NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
 		if (!dict)
 			dict = g_variant_new_array (G_VARIANT_TYPE ("{sa{sv}}"), NULL, 0);
 		g_dbus_method_invocation_return_value (context, g_variant_new ("(@a{sa{sv}})", dict));
@@ -2204,7 +2225,7 @@ dbus_clear_secrets_auth_cb (NMSettingsConnection *self,
 	}
 
 	/* Clear secrets in connection and caches */
-	nm_connection_clear_secrets (NM_CONNECTION (self));
+	nm_connection_clear_secrets (nm_settings_connection_get_connection (self));
 	if (priv->system_secrets)
 		nm_connection_clear_secrets (priv->system_secrets);
 	if (priv->agent_secrets)
@@ -2213,7 +2234,7 @@ dbus_clear_secrets_auth_cb (NMSettingsConnection *self,
 	/* Tell agents to remove secrets for this connection */
 	nm_agent_manager_delete_secrets (priv->agent_mgr,
 	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
-	                                 NM_CONNECTION (self));
+	                                 nm_settings_connection_get_connection (self));
 
 	nm_settings_connection_update (self,
 	                               NULL,
@@ -2388,15 +2409,8 @@ _cmp_timestamp (NMSettingsConnection *a, NMSettingsConnection *b)
 static int
 _cmp_last_resort (NMSettingsConnection *a, NMSettingsConnection *b)
 {
-	int c;
-
-	nm_assert (NM_IS_SETTINGS_CONNECTION (a));
-	nm_assert (NM_IS_SETTINGS_CONNECTION (b));
-
-	c = g_strcmp0 (nm_connection_get_uuid (NM_CONNECTION (a)),
-	               nm_connection_get_uuid (NM_CONNECTION (b)));
-	if (c)
-		return c;
+	NM_CMP_DIRECT_STRCMP0 (nm_settings_connection_get_uuid (a),
+	                       nm_settings_connection_get_uuid (b));
 
 	/* hm, same UUID. Use their pointer value to give them a stable
 	 * order. */
@@ -2411,19 +2425,11 @@ _cmp_last_resort (NMSettingsConnection *a, NMSettingsConnection *b)
 int
 nm_settings_connection_cmp_timestamp (NMSettingsConnection *a, NMSettingsConnection *b)
 {
-	int c;
+	NM_CMP_SELF (a, b);
 
-	if (a == b)
-		return 0;
-	if (!a)
-		return 1;
-	if (!b)
-		return -1;
-
-	if ((c = _cmp_timestamp (a, b)))
-		return c;
-	if ((c = nm_utils_cmp_connection_by_autoconnect_priority (NM_CONNECTION (a), NM_CONNECTION (b))))
-		return c;
+	NM_CMP_RETURN (_cmp_timestamp (a, b));
+	NM_CMP_RETURN (nm_utils_cmp_connection_by_autoconnect_priority (nm_settings_connection_get_connection (a),
+	                                                                nm_settings_connection_get_connection (b)));
 	return _cmp_last_resort (a, b);
 }
 
@@ -2437,14 +2443,11 @@ nm_settings_connection_cmp_timestamp_p_with_data (gconstpointer pa, gconstpointe
 int
 nm_settings_connection_cmp_autoconnect_priority (NMSettingsConnection *a, NMSettingsConnection *b)
 {
-	int c;
-
 	if (a == b)
 		return 0;
-	if ((c = nm_utils_cmp_connection_by_autoconnect_priority (NM_CONNECTION (a), NM_CONNECTION (b))))
-		return c;
-	if ((c = _cmp_timestamp (a, b)))
-		return c;
+	NM_CMP_RETURN (nm_utils_cmp_connection_by_autoconnect_priority (nm_settings_connection_get_connection (a),
+	                                                                nm_settings_connection_get_connection (b)));
+	NM_CMP_RETURN (_cmp_timestamp (a, b));
 	return _cmp_last_resort (a, b);
 }
 
@@ -2727,7 +2730,7 @@ nm_settings_connection_read_and_fill_seen_bssids (NMSettingsConnection *self)
 		 * seen-bssids list from the deprecated seen-bssids property of the
 		 * wifi setting.
 		 */
-		s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (self));
+		s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (self));
 		if (s_wifi) {
 			len = nm_setting_wireless_get_num_seen_bssids (s_wifi);
 			for (i = 0; i < len; i++) {
@@ -2747,7 +2750,7 @@ _autoconnect_retries_initial (NMSettingsConnection *self)
 	NMSettingConnection *s_con;
 	int retries = -1;
 
-	s_con = nm_connection_get_setting_connection ((NMConnection *) self);
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
 	if (s_con)
 		retries = nm_setting_connection_get_autoconnect_retries (s_con);
 
@@ -2959,13 +2962,19 @@ nm_settings_connection_get_filename (NMSettingsConnection *self)
 const char *
 nm_settings_connection_get_id (NMSettingsConnection *self)
 {
-	return nm_connection_get_id (NM_CONNECTION (self));
+	return nm_connection_get_id (nm_settings_connection_get_connection (self));
 }
 
 const char *
 nm_settings_connection_get_uuid (NMSettingsConnection *self)
 {
-	return nm_connection_get_uuid (NM_CONNECTION (self));
+	return nm_connection_get_uuid (nm_settings_connection_get_connection (self));
+}
+
+const char *
+nm_settings_connection_get_connection_type (NMSettingsConnection *self)
+{
+	return nm_connection_get_connection_type (nm_settings_connection_get_connection (self));
 }
 
 /*****************************************************************************/
@@ -2995,8 +3004,10 @@ nm_settings_connection_init (NMSettingsConnection *self)
 
 	priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET;
 
-	g_signal_connect (self, NM_CONNECTION_SECRETS_CLEARED, G_CALLBACK (secrets_cleared_cb), NULL);
-	g_signal_connect (self, NM_CONNECTION_CHANGED, G_CALLBACK (connection_changed_cb), NULL);
+	priv->connection = nm_simple_connection_new ();
+
+	g_signal_connect (priv->connection, NM_CONNECTION_SECRETS_CLEARED, G_CALLBACK (secrets_cleared_cb), self);
+	g_signal_connect (priv->connection, NM_CONNECTION_CHANGED, G_CALLBACK (connection_changed_cb), self);
 }
 
 static void
@@ -3027,26 +3038,32 @@ dispose (GObject *object)
 			_get_secrets_cancel (self, call_id, TRUE);
 	}
 
-	/* Disconnect handlers.
-	 * connection_changed_cb() has to be disconnected *before* nm_connection_clear_secrets(),
-	 * because nm_connection_clear_secrets() emits NM_CONNECTION_CHANGED signal.
-	 */
-	g_signal_handlers_disconnect_by_func (self, G_CALLBACK (secrets_cleared_cb), NULL);
-	g_signal_handlers_disconnect_by_func (self, G_CALLBACK (connection_changed_cb), NULL);
+	set_visible (self, FALSE);
+
+	if (priv->connection) {
+		/* Disconnect handlers.
+		 * connection_changed_cb() has to be disconnected *before* nm_connection_clear_secrets(),
+		 * because nm_connection_clear_secrets() emits NM_CONNECTION_CHANGED signal.
+		 */
+		g_signal_handlers_disconnect_by_func (priv->connection, G_CALLBACK (secrets_cleared_cb), self);
+		g_signal_handlers_disconnect_by_func (priv->connection, G_CALLBACK (connection_changed_cb), self);
+
+		/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+		nm_connection_clear_secrets (priv->connection);
+	}
 
-	nm_connection_clear_secrets (NM_CONNECTION (self));
 	g_clear_object (&priv->system_secrets);
 	g_clear_object (&priv->agent_secrets);
 
 	g_clear_pointer (&priv->seen_bssids, g_hash_table_destroy);
 
-	set_visible (self, FALSE);
-
 	nm_clear_g_signal_handler (priv->session_monitor, &priv->session_changed_id);
 	g_clear_object (&priv->session_monitor);
 
 	g_clear_object (&priv->agent_mgr);
 
+	g_clear_object (&priv->connection);
+
 	g_clear_pointer (&priv->filename, g_free);
 
 	G_OBJECT_CLASS (nm_settings_connection_parent_class)->dispose (object);
@@ -3264,9 +3281,3 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *klass)
 	                  g_cclosure_marshal_VOID__VOID,
 	                  G_TYPE_NONE, 0);
 }
-
-static void
-nm_settings_connection_connection_interface_init (NMConnectionInterface *iface)
-{
-}
-
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index cd64f976..e796b716 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -139,6 +139,8 @@ struct _NMSettingsConnectionClass {
 
 GType nm_settings_connection_get_type (void);
 
+NMConnection *nm_settings_connection_get_connection (NMSettingsConnection *self);
+
 guint64 nm_settings_connection_get_last_secret_agent_version_id (NMSettingsConnection *self);
 
 gboolean nm_settings_connection_has_unmodified_applied_connection (NMSettingsConnection *self,
@@ -268,7 +270,13 @@ void        nm_settings_connection_set_filename (NMSettingsConnection *self,
                                                  const char *filename);
 const char *nm_settings_connection_get_filename (NMSettingsConnection *self);
 
-const char *nm_settings_connection_get_id   (NMSettingsConnection *connection);
-const char *nm_settings_connection_get_uuid (NMSettingsConnection *connection);
+const char *nm_settings_connection_get_id              (NMSettingsConnection *connection);
+const char *nm_settings_connection_get_uuid            (NMSettingsConnection *connection);
+const char *nm_settings_connection_get_connection_type (NMSettingsConnection *connection);
+
+/*****************************************************************************/
+
+NMConnection **nm_settings_connections_array_to_connections (NMSettingsConnection *const*connections,
+                                                             gssize n_connections);
 
 #endif /* __NETWORKMANAGER_SETTINGS_CONNECTION_H__ */
diff --git a/src/settings/nm-settings-plugin.c b/src/settings/nm-settings-plugin.c
index f6dd2cfb..e7275631 100644
--- a/src/settings/nm-settings-plugin.c
+++ b/src/settings/nm-settings-plugin.c
@@ -15,117 +15,94 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2007 - 2011 Red Hat, Inc.
+ * Copyright (C) 2007 - 2018 Red Hat, Inc.
  * Copyright (C) 2008 Novell, Inc.
  */
 
 #include "nm-default.h"
 
 #include "nm-settings-plugin.h"
+
 #include "nm-settings-connection.h"
 
-G_DEFINE_INTERFACE (NMSettingsPlugin, nm_settings_plugin, G_TYPE_OBJECT)
+/*****************************************************************************/
 
-static void
-nm_settings_plugin_default_init (NMSettingsPluginInterface *g_iface)
-{
-	GType iface_type = G_TYPE_FROM_INTERFACE (g_iface);
-	static gboolean initialized = FALSE;
-
-	if (initialized)
-		return;
-
-	/* Signals */
-	g_signal_new (NM_SETTINGS_PLUGIN_CONNECTION_ADDED,
-	              iface_type,
-	              G_SIGNAL_RUN_FIRST,
-	              G_STRUCT_OFFSET (NMSettingsPluginInterface, connection_added),
-	              NULL, NULL,
-	              g_cclosure_marshal_VOID__OBJECT,
-	              G_TYPE_NONE, 1,
-	              NM_TYPE_SETTINGS_CONNECTION);
-
-	g_signal_new (NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
-	              iface_type,
-	              G_SIGNAL_RUN_FIRST,
-	              G_STRUCT_OFFSET (NMSettingsPluginInterface, unmanaged_specs_changed),
-	              NULL, NULL,
-	              g_cclosure_marshal_VOID__VOID,
-	              G_TYPE_NONE, 0);
-
-	g_signal_new (NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
-	              iface_type,
-	              G_SIGNAL_RUN_FIRST,
-	              G_STRUCT_OFFSET (NMSettingsPluginInterface, unrecognized_specs_changed),
-	              NULL, NULL,
-	              g_cclosure_marshal_VOID__VOID,
-	              G_TYPE_NONE, 0);
-
-	initialized = TRUE;
-}
+enum {
+	CONNECTION_ADDED,
+	UNMANAGED_SPECS_CHANGED,
+	UNRECOGNIZED_SPECS_CHANGED,
+
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (NMSettingsPlugin, nm_settings_plugin, G_TYPE_OBJECT)
+
+/*****************************************************************************/
 
 void
-nm_settings_plugin_init (NMSettingsPlugin *config)
+nm_settings_plugin_initialize (NMSettingsPlugin *self)
 {
-	g_return_if_fail (config != NULL);
+	g_return_if_fail (NM_IS_SETTINGS_PLUGIN (self));
 
-	if (NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->init)
-		NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->init (config);
+	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->initialize)
+		NM_SETTINGS_PLUGIN_GET_CLASS (self)->initialize (self);
 }
 
 GSList *
-nm_settings_plugin_get_connections (NMSettingsPlugin *config)
+nm_settings_plugin_get_connections (NMSettingsPlugin *self)
 {
-	g_return_val_if_fail (config != NULL, NULL);
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
 
-	if (NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->get_connections)
-		return NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->get_connections (config);
+	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_connections)
+		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_connections (self);
 	return NULL;
 }
 
 gboolean
-nm_settings_plugin_load_connection (NMSettingsPlugin *config,
+nm_settings_plugin_load_connection (NMSettingsPlugin *self,
                                     const char *filename)
 {
-	g_return_val_if_fail (config != NULL, FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), FALSE);
 
-	if (NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->load_connection)
-		return NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->load_connection (config, filename);
+	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->load_connection)
+		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->load_connection (self, filename);
 	return FALSE;
 }
 
 void
-nm_settings_plugin_reload_connections (NMSettingsPlugin *config)
+nm_settings_plugin_reload_connections (NMSettingsPlugin *self)
 {
-	g_return_if_fail (config != NULL);
+	g_return_if_fail (NM_IS_SETTINGS_PLUGIN (self));
 
-	if (NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->reload_connections)
-		NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->reload_connections (config);
+	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->reload_connections)
+		NM_SETTINGS_PLUGIN_GET_CLASS (self)->reload_connections (self);
 }
 
 GSList *
-nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *config)
+nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *self)
 {
-	g_return_val_if_fail (config != NULL, NULL);
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
 
-	if (NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->get_unmanaged_specs)
-		return NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->get_unmanaged_specs (config);
+	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unmanaged_specs)
+		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unmanaged_specs (self);
 	return NULL;
 }
 
 GSList *
-nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *config)
+nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *self)
 {
-	g_return_val_if_fail (config != NULL, NULL);
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
 
-	if (NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->get_unrecognized_specs)
-		return NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->get_unrecognized_specs (config);
+	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unrecognized_specs)
+		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unrecognized_specs (self);
 	return NULL;
 }
 
 /**
  * nm_settings_plugin_add_connection:
- * @config: the #NMSettingsPlugin
+ * @self: the #NMSettingsPlugin
  * @connection: the source connection to create a plugin-specific
  * #NMSettingsConnection from
  * @save_to_disk: %TRUE to save the connection to disk immediately, %FALSE to
@@ -140,22 +117,88 @@ nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *config)
  * Returns: the new #NMSettingsConnection or %NULL
  */
 NMSettingsConnection *
-nm_settings_plugin_add_connection (NMSettingsPlugin *config,
+nm_settings_plugin_add_connection (NMSettingsPlugin *self,
                                    NMConnection *connection,
                                    gboolean save_to_disk,
                                    GError **error)
 {
-	NMSettingsPluginInterface *config_interface;
+	NMSettingsPluginClass *klass;
 
-	g_return_val_if_fail (config != NULL, NULL);
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 
-	config_interface = NM_SETTINGS_PLUGIN_GET_INTERFACE (config);
-	if (!config_interface->add_connection) {
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+	if (!klass->add_connection) {
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
 		                     "Plugin does not support adding connections");
 		return NULL;
 	}
 
-	return config_interface->add_connection (config, connection, save_to_disk, error);
+	return klass->add_connection (self, connection, save_to_disk, error);
+}
+
+/*****************************************************************************/
+
+void
+_nm_settings_plugin_emit_signal_connection_added (NMSettingsPlugin *self,
+                                                  NMSettingsConnection *sett_conn)
+{
+	nm_assert (NM_IS_SETTINGS_PLUGIN (self));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+
+	g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
+}
+
+void
+_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NMSettingsPlugin *self)
+{
+	nm_assert (NM_IS_SETTINGS_PLUGIN (self));
+
+	g_signal_emit (self, signals[UNMANAGED_SPECS_CHANGED], 0);
+}
+
+void
+_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NMSettingsPlugin *self)
+{
+	nm_assert (NM_IS_SETTINGS_PLUGIN (self));
+
+	g_signal_emit (self, signals[UNRECOGNIZED_SPECS_CHANGED], 0);
+}
+
+/*****************************************************************************/
+
+static void
+nm_settings_plugin_init (NMSettingsPlugin *self)
+{
+}
+
+static void
+nm_settings_plugin_class_init (NMSettingsPluginClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	signals[CONNECTION_ADDED] =
+	    g_signal_new (NM_SETTINGS_PLUGIN_CONNECTION_ADDED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__OBJECT,
+	                  G_TYPE_NONE, 1,
+	                  NM_TYPE_SETTINGS_CONNECTION);
+
+	signals[UNMANAGED_SPECS_CHANGED] =
+	    g_signal_new (NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
+
+	signals[UNRECOGNIZED_SPECS_CHANGED] =
+	    g_signal_new (NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL,
+	                  g_cclosure_marshal_VOID__VOID,
+	                  G_TYPE_NONE, 0);
 }
diff --git a/src/settings/nm-settings-plugin.h b/src/settings/nm-settings-plugin.h
index 6fba25e7..fdd48f2b 100644
--- a/src/settings/nm-settings-plugin.h
+++ b/src/settings/nm-settings-plugin.h
@@ -15,53 +15,52 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2007 - 2011 Red Hat, Inc.
+ * Copyright (C) 2007 - 2018 Red Hat, Inc.
  * Copyright (C) 2008 Novell, Inc.
  */
 
-#ifndef __NETWORKMANAGER_SETTINGS_PLUGIN_H__
-#define __NETWORKMANAGER_SETTINGS_PLUGIN_H__
+#ifndef __NM_SETTINGS_PLUGIN_H__
+#define __NM_SETTINGS_PLUGIN_H__
 
 #include "nm-connection.h"
 
-/* Plugin's factory function that returns a GObject that implements
- * NMSettingsPlugin.
- */
-GObject * nm_settings_plugin_factory (void);
-
 #define NM_TYPE_SETTINGS_PLUGIN               (nm_settings_plugin_get_type ())
 #define NM_SETTINGS_PLUGIN(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPlugin))
+#define NM_SETTINGS_PLUGIN_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginClass))
 #define NM_IS_SETTINGS_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTINGS_PLUGIN))
-#define NM_SETTINGS_PLUGIN_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginInterface))
+#define NM_IS_SETTINGS_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTINGS_PLUGIN))
+#define NM_SETTINGS_PLUGIN_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginClass))
 
-#define NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED "unmanaged-specs-changed"
+#define NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED    "unmanaged-specs-changed"
 #define NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED "unrecognized-specs-changed"
-#define NM_SETTINGS_PLUGIN_CONNECTION_ADDED "connection-added"
+#define NM_SETTINGS_PLUGIN_CONNECTION_ADDED           "connection-added"
 
-typedef struct _NMSettingsPlugin NMSettingsPlugin;
+typedef struct {
+	GObject parent;
+} NMSettingsPlugin;
 
 typedef struct {
-	GTypeInterface g_iface;
+	GObjectClass parent;
 
 	/* Called when the plugin is loaded to initialize it */
-	void     (*init) (NMSettingsPlugin *config);
+	void (*initialize) (NMSettingsPlugin *plugin);
 
 	/* Returns a GSList of NMSettingsConnection objects that represent
 	 * connections the plugin knows about.  The returned list is freed by the
 	 * system settings service.
 	 */
-	GSList * (*get_connections) (NMSettingsPlugin *config);
+	GSList * (*get_connections) (NMSettingsPlugin *plugin);
 
 	/* Requests that the plugin load/reload a single connection, if it
 	 * recognizes the filename. Returns success or failure.
 	 */
-	gboolean (*load_connection) (NMSettingsPlugin *config,
+	gboolean (*load_connection) (NMSettingsPlugin *plugin,
 	                             const char *filename);
 
 	/* Requests that the plugin reload all connection files from disk,
 	 * and emit signals reflecting new, changed, and removed connections.
 	 */
-	void (*reload_connections) (NMSettingsPlugin *config);
+	void (*reload_connections) (NMSettingsPlugin *plugin);
 
 	/*
 	 * Return a string list of specifications of devices which NetworkManager
@@ -72,7 +71,7 @@ typedef struct {
 	 * Each string in the list must be in one of the formats recognized by
 	 * nm_device_spec_match_list().
 	 */
-	GSList * (*get_unmanaged_specs) (NMSettingsPlugin *config);
+	GSList * (*get_unmanaged_specs) (NMSettingsPlugin *plugin);
 
 	/*
 	 * Return a string list of specifications of devices for which at least
@@ -84,7 +83,7 @@ typedef struct {
 	 * Each string in the list must be in one of the formats recognized by
 	 * nm_device_spec_match_list().
 	 */
-	GSList * (*get_unrecognized_specs) (NMSettingsPlugin *config);
+	GSList * (*get_unrecognized_specs) (NMSettingsPlugin *plugin);
 
 	/*
 	 * Initialize the plugin-specific connection and return a new
@@ -93,40 +92,42 @@ typedef struct {
 	 * storage if @save_to_disk is TRUE.  The returned object is owned by the
 	 * plugin and must be referenced by the owner if necessary.
 	 */
-	NMSettingsConnection * (*add_connection) (NMSettingsPlugin *config,
+	NMSettingsConnection * (*add_connection) (NMSettingsPlugin *plugin,
 	                                          NMConnection *connection,
 	                                          gboolean save_to_disk,
 	                                          GError **error);
+} NMSettingsPluginClass;
 
-	/* Signals */
-
-	/* Emitted when a new connection has been found by the plugin */
-	void (*connection_added)   (NMSettingsPlugin *config,
-	                            NMSettingsConnection *connection);
+GType nm_settings_plugin_get_type (void);
 
-	/* Emitted when the list of unmanaged device specifications changes */
-	void (*unmanaged_specs_changed) (NMSettingsPlugin *config);
+typedef NMSettingsPlugin *(*NMSettingsPluginFactoryFunc) (void);
 
-	/* Emitted when the list of devices with unrecognized connections changes */
-	void (*unrecognized_specs_changed) (NMSettingsPlugin *config);
-} NMSettingsPluginInterface;
+/* Plugin's factory function that returns a #NMSettingsPlugin */
+NMSettingsPlugin *nm_settings_plugin_factory (void);
 
-GType nm_settings_plugin_get_type (void);
+void nm_settings_plugin_initialize (NMSettingsPlugin *config);
 
-void nm_settings_plugin_init (NMSettingsPlugin *config);
+GSList *nm_settings_plugin_get_connections (NMSettingsPlugin *plugin);
 
-GSList *nm_settings_plugin_get_connections (NMSettingsPlugin *config);
-
-gboolean nm_settings_plugin_load_connection (NMSettingsPlugin *config,
+gboolean nm_settings_plugin_load_connection (NMSettingsPlugin *plugin,
                                              const char *filename);
-void nm_settings_plugin_reload_connections (NMSettingsPlugin *config);
+void nm_settings_plugin_reload_connections (NMSettingsPlugin *plugin);
 
-GSList *nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *config);
-GSList *nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *config);
+GSList *nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *plugin);
+GSList *nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *plugin);
 
-NMSettingsConnection *nm_settings_plugin_add_connection (NMSettingsPlugin *config,
+NMSettingsConnection *nm_settings_plugin_add_connection (NMSettingsPlugin *plugin,
                                                          NMConnection *connection,
                                                          gboolean save_to_disk,
                                                          GError **error);
 
-#endif /* __NETWORKMANAGER_SETTINGS_PLUGIN_H__ */
+/* internal API */
+
+void _nm_settings_plugin_emit_signal_connection_added (NMSettingsPlugin *plugin,
+                                                       NMSettingsConnection *sett_conn);
+
+void _nm_settings_plugin_emit_signal_unmanaged_specs_changed (NMSettingsPlugin *plugin);
+
+void _nm_settings_plugin_emit_signal_unrecognized_specs_changed (NMSettingsPlugin *plugin);
+
+#endif /* __NM_SETTINGS_PLUGIN_H__ */
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 5bb629dc..2253d56a 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -214,9 +214,9 @@ connection_ready_changed (NMSettingsConnection *conn,
 static void
 plugin_connection_added (NMSettingsPlugin *config,
                          NMSettingsConnection *connection,
-                         gpointer user_data)
+                         NMSettings *self)
 {
-	claim_connection (NM_SETTINGS (user_data), connection);
+	claim_connection (self, connection);
 }
 
 static void
@@ -237,7 +237,7 @@ load_connections (NMSettings *self)
 		// priority plugin.
 
 		for (elt = plugin_connections; elt; elt = g_slist_next (elt))
-			claim_connection (self, NM_SETTINGS_CONNECTION (elt->data));
+			claim_connection (self, elt->data);
 
 		g_slist_free (plugin_connections);
 
@@ -306,15 +306,15 @@ impl_settings_get_connection_by_uuid (NMDBusObject *obj,
                                       GVariant *parameters)
 {
 	NMSettings *self = NM_SETTINGS (obj);
-	NMSettingsConnection *connection = NULL;
+	NMSettingsConnection *sett_conn;
 	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 	const char *uuid;
 
 	g_variant_get (parameters, "(&s)", &uuid);
 
-	connection = nm_settings_get_connection_by_uuid (self, uuid);
-	if (!connection) {
+	sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
+	if (!sett_conn) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		                             "No connection with the UUID was found.");
@@ -329,7 +329,7 @@ impl_settings_get_connection_by_uuid (NMDBusObject *obj,
 		goto error;
 	}
 
-	if (!nm_auth_is_subject_in_acl_set_error (NM_CONNECTION (connection),
+	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
 	                                          subject,
 	                                          NM_SETTINGS_ERROR,
 	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
@@ -338,7 +338,7 @@ impl_settings_get_connection_by_uuid (NMDBusObject *obj,
 
 	g_dbus_method_invocation_return_value (invocation,
 	                                       g_variant_new ("(o)",
-	                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (connection))));
+	                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn))));
 	return;
 
 error:
@@ -362,6 +362,7 @@ _clear_connections_cached_list (NMSettingsPrivate *priv)
 	        0xdeaddead,
 	        sizeof (NMSettingsConnection *) * (priv->connections_len + 1));
 #endif
+
 	nm_clear_g_free (&priv->connections_cached_list);
 }
 
@@ -480,8 +481,8 @@ nm_settings_get_connection_by_path (NMSettings *self, const char *path)
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	connection = (NMSettingsConnection *) nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
-	                                                                     path);
+	connection = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                            path);
 	if (   !connection
 	    || !NM_IS_SETTINGS_CONNECTION (connection))
 		return NULL;
@@ -525,28 +526,6 @@ nm_settings_get_unmanaged_specs (NMSettings *self)
 	return priv->unmanaged_specs;
 }
 
-static NMSettingsPlugin *
-get_plugin (NMSettings *self, gboolean has_add_connection)
-{
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GSList *iter;
-
-	g_return_val_if_fail (self != NULL, NULL);
-
-	/* Do any of the plugins support the given capability? */
-	for (iter = priv->plugins; iter; iter = iter->next) {
-		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
-
-		if (!has_add_connection)
-			return plugin;
-
-		if (NM_SETTINGS_PLUGIN_GET_INTERFACE (iter->data)->add_connection != NULL)
-			return plugin;
-	}
-
-	return NULL;
-}
-
 static gboolean
 find_spec (GSList *spec_list, const char *spec)
 {
@@ -607,67 +586,49 @@ unrecognized_specs_changed (NMSettingsPlugin *config,
 	              nm_settings_plugin_get_unrecognized_specs);
 }
 
-static gboolean
-add_plugin (NMSettings *self, NMSettingsPlugin *plugin)
+static void
+add_plugin (NMSettings *self, NMSettingsPlugin *plugin, const char *path)
 {
 	NMSettingsPrivate *priv;
-	const char *path;
 
-	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
-	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (plugin), FALSE);
+	nm_assert (NM_IS_SETTINGS (self));
+	nm_assert (NM_IS_SETTINGS_PLUGIN (plugin));
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	if (g_slist_find (priv->plugins, plugin)) {
-		/* don't add duplicates. */
-		return FALSE;
-	}
+	nm_assert (!g_slist_find (priv->plugins, plugin));
 
 	priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
-	nm_settings_plugin_init (plugin);
-
-
-	path = g_object_get_qdata (G_OBJECT (plugin), plugin_module_path_quark ());
-
-	_LOGI ("Loaded settings plugin: %s (%s)", G_OBJECT_TYPE_NAME (plugin), path ?: "internal");
-
-	return TRUE;
-}
-
-static gboolean
-plugin_loaded (GSList *list, const char *path)
-{
-	GSList *iter;
 
-	g_return_val_if_fail (path != NULL, TRUE);
+	nm_settings_plugin_initialize (plugin);
 
-	for (iter = list; iter; iter = g_slist_next (iter)) {
-		const char *list_path = g_object_get_qdata (G_OBJECT (iter->data),
-		                                            plugin_module_path_quark ());
-
-		if (g_strcmp0 (path, list_path) == 0)
-			return TRUE;
-	}
-
-	return FALSE;
+	_LOGI ("Loaded settings plugin: %s (%s%s%s)",
+	       G_OBJECT_TYPE_NAME (plugin),
+	       NM_PRINT_FMT_QUOTED (path, "\"", path, "\"", "internal"));
 }
 
 static gboolean
-load_plugin (NMSettings *self, GSList **list, const char *pname, GError **error)
+add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
 {
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	gs_free char *full_name = NULL;
 	gs_free char *path = NULL;
-	gs_unref_object GObject *obj = NULL;
-	GModule *plugin;
-	GObject * (*factory_func) (void);
+	gs_unref_object NMSettingsPlugin *plugin = NULL;
+	GModule *module;
+	NMSettingsPluginFactoryFunc factory_func;
+	GSList *iter;
 	struct stat st;
 	int errsv;
 
 	full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
 	path = g_module_build_path (NMPLUGINDIR, full_name);
 
-	if (plugin_loaded (*list, path))
-		return TRUE;
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		if (nm_streq0 (path,
+		               g_object_get_qdata (iter->data,
+		                                   plugin_module_path_quark ())))
+			return TRUE;
+	}
 
 	if (stat (path, &st) != 0) {
 		errsv = errno;
@@ -687,8 +648,8 @@ load_plugin (NMSettings *self, GSList **list, const char *pname, GError **error)
 		return TRUE;
 	}
 
-	plugin = g_module_open (path, G_MODULE_BIND_LOCAL);
-	if (!plugin) {
+	module = g_module_open (path, G_MODULE_BIND_LOCAL);
+	if (!module) {
 		_LOGW ("could not load plugin '%s' from file '%s': %s",
 		     pname, path, g_module_error ());
 		return TRUE;
@@ -696,48 +657,46 @@ load_plugin (NMSettings *self, GSList **list, const char *pname, GError **error)
 
 	/* errors after this point are fatal, because we loaded the shared library already. */
 
-	if (!g_module_symbol (plugin, "nm_settings_plugin_factory", (gpointer) (&factory_func))) {
+	if (!g_module_symbol (module, "nm_settings_plugin_factory", (gpointer) (&factory_func))) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 		             "Could not find plugin '%s' factory function.",
 		             pname);
-		g_module_close (plugin);
+		g_module_close (module);
 		return FALSE;
 	}
 
 	/* after accessing the plugin we cannot unload it anymore, because the glib
 	 * types cannot be properly unregistered. */
-	g_module_make_resident (plugin);
+	g_module_make_resident (module);
 
-	obj = (*factory_func) ();
-	if (!obj || !NM_IS_SETTINGS_PLUGIN (obj)) {
+	plugin = (*factory_func) ();
+	if (!NM_IS_SETTINGS_PLUGIN (plugin)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Plugin '%s' returned invalid system config object.",
+		             "plugin '%s' returned invalid settings plugin",
 		             pname);
 		return FALSE;
 	}
 
-	g_object_set_qdata_full (obj, plugin_module_path_quark (), path, g_free);
-	path = NULL;
-	if (add_plugin (self, NM_SETTINGS_PLUGIN (obj)))
-		*list = g_slist_append (*list, g_steal_pointer (&obj));
-
+	add_plugin (self, NM_SETTINGS_PLUGIN (plugin), path);
+	g_object_set_qdata_full (G_OBJECT (plugin),
+	                         plugin_module_path_quark (),
+	                         g_steal_pointer (&path),
+	                         g_free);
 	return TRUE;
 }
 
 static void
-add_keyfile_plugin (NMSettings *self)
+add_plugin_keyfile (NMSettings *self)
 {
 	gs_unref_object NMSKeyfilePlugin *keyfile_plugin = NULL;
 
 	keyfile_plugin = nms_keyfile_plugin_new ();
-	if (!add_plugin (self, NM_SETTINGS_PLUGIN (keyfile_plugin)))
-		g_return_if_reached ();
+	add_plugin (self, NM_SETTINGS_PLUGIN (keyfile_plugin), NULL);
 }
 
 static gboolean
 load_plugins (NMSettings *self, const char **plugins, GError **error)
 {
-	GSList *list = NULL;
 	const char **iter;
 	gboolean keyfile_added = FALSE;
 	gboolean success = TRUE;
@@ -765,15 +724,15 @@ load_plugins (NMSettings *self, const char **plugins, GError **error)
 			continue;
 		}
 
-		if (!strcmp (pname, "no-ibft"))
+		if (nm_streq (pname, "no-ibft"))
 			continue;
-		if (has_no_ibft && !strcmp (pname, "ibft"))
+		if (has_no_ibft && nm_streq (pname, "ibft"))
 			continue;
 
 		/* keyfile plugin is built-in now */
-		if (strcmp (pname, "keyfile") == 0) {
+		if (nm_streq (pname, "keyfile")) {
 			if (!keyfile_added) {
-				add_keyfile_plugin (self);
+				add_plugin_keyfile (self);
 				keyfile_added = TRUE;
 			}
 			continue;
@@ -787,27 +746,25 @@ load_plugins (NMSettings *self, const char **plugins, GError **error)
 			continue;
 		}
 
-		success = load_plugin (self, &list, pname, error);
+		success = add_plugin_load_file (self, pname, error);
 		if (!success)
 			break;
 
-		if (add_ibft && !strcmp (pname, "ifcfg-rh")) {
+		if (add_ibft && nm_streq (pname, "ifcfg-rh")) {
 			/* The plugin ibft is not explicitly mentioned but we just enabled "ifcfg-rh".
 			 * Enable "ibft" by default after "ifcfg-rh". */
 			pname = "ibft";
 			add_ibft = FALSE;
 
-			success = load_plugin (self, &list, "ibft", error);
+			success = add_plugin_load_file (self, "ibft", error);
 			if (!success)
 				break;
 		}
 	}
 
 	/* If keyfile plugin was not among configured plugins, add it as the last one */
-	if (!keyfile_added)
-		add_keyfile_plugin (self);
-
-	g_slist_free_full (list, g_object_unref);
+	if (!keyfile_added && success)
+		add_plugin_keyfile (self);
 
 	return success;
 }
@@ -931,29 +888,33 @@ openconnect_migrate_hack (NMConnection *connection)
 }
 
 static void
-claim_connection (NMSettings *self, NMSettingsConnection *connection)
+claim_connection (NMSettings *self, NMSettingsConnection *sett_conn)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMSettingsPrivate *priv;
 	GError *error = NULL;
 	const char *path;
 	NMSettingsConnection *existing;
 
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (connection)));
+	g_return_if_fail (NM_IS_SETTINGS (self));
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (sett_conn)));
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
 
 	/* prevent duplicates */
-	if (!c_list_is_empty (&connection->_connections_lst)) {
-		nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
+	if (!c_list_is_empty (&sett_conn->_connections_lst)) {
+		nm_assert (c_list_contains (&priv->connections_lst_head, &sett_conn->_connections_lst));
 		return;
 	}
 
-	if (!nm_connection_normalize (NM_CONNECTION (connection), NULL, NULL, &error)) {
+	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+	if (!nm_connection_normalize (nm_settings_connection_get_connection (sett_conn), NULL, NULL, &error)) {
 		_LOGW ("plugin provided invalid connection: %s", error->message);
 		g_error_free (error);
 		return;
 	}
 
-	existing = nm_settings_get_connection_by_uuid (self, nm_settings_connection_get_uuid (connection));
+	existing = nm_settings_get_connection_by_uuid (self, nm_settings_connection_get_uuid (sett_conn));
 	if (existing) {
 		/* Cannot add duplicate connections per UUID. Just return without action and
 		 * log a warning.
@@ -966,51 +927,56 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 		 * error out. That should not happen unless the admin misconfigured the system
 		 * to create conflicting connections. */
 		_LOGW ("plugin provided duplicate connection with UUID %s",
-		       nm_settings_connection_get_uuid (connection));
+		       nm_settings_connection_get_uuid (sett_conn));
 		return;
 	}
 
 	/* Read timestamp from look-aside file and put it into the connection's data */
-	nm_settings_connection_read_and_fill_timestamp (connection);
+	nm_settings_connection_read_and_fill_timestamp (sett_conn);
 
 	/* Read seen-bssids from look-aside file and put it into the connection's data */
-	nm_settings_connection_read_and_fill_seen_bssids (connection);
+	nm_settings_connection_read_and_fill_seen_bssids (sett_conn);
 
 	/* Ensure its initial visibility is up-to-date */
-	nm_settings_connection_recheck_visibility (connection);
+	nm_settings_connection_recheck_visibility (sett_conn);
 
 	/* Evil openconnect migration hack */
-	openconnect_migrate_hack (NM_CONNECTION (connection));
+	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+	openconnect_migrate_hack (nm_settings_connection_get_connection (sett_conn));
 
 	/* This one unexports the connection, it needs to run late to give the active
 	 * connection a chance to deal with its reference to this settings connection. */
-	g_signal_connect_after (connection, NM_SETTINGS_CONNECTION_REMOVED,
+	g_signal_connect_after (sett_conn, NM_SETTINGS_CONNECTION_REMOVED,
 	                        G_CALLBACK (connection_removed), self);
-	g_signal_connect (connection, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
+	g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
 	                  G_CALLBACK (connection_updated), self);
-	g_signal_connect (connection, NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
+	g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
 	                  G_CALLBACK (connection_flags_changed),
 	                  self);
 	if (!priv->startup_complete) {
-		g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_READY,
+		g_signal_connect (sett_conn, "notify::" NM_SETTINGS_CONNECTION_READY,
 		                  G_CALLBACK (connection_ready_changed),
 		                  self);
 	}
 
 	_clear_connections_cached_list (priv);
 
-	g_object_ref (connection);
+	g_object_ref (sett_conn);
 	/* FIXME(shutdown): The NMSettings instance can't be disposed
 	 * while there is any exported connection. Ideally we should
 	 * unexport all connections on NMSettings' disposal, but for now
 	 * leak @self on termination when there are connections alive. */
 	g_object_ref (self);
 	priv->connections_len++;
-	c_list_link_tail (&priv->connections_lst_head, &connection->_connections_lst);
+	c_list_link_tail (&priv->connections_lst_head, &sett_conn->_connections_lst);
 
-	path = nm_dbus_object_export (NM_DBUS_OBJECT (connection));
+	path = nm_dbus_object_export (NM_DBUS_OBJECT (sett_conn));
 
-	nm_utils_log_connection_diff (NM_CONNECTION (connection), NULL, LOGL_DEBUG, LOGD_CORE, "new connection", "++ ",
+	nm_utils_log_connection_diff (nm_settings_connection_get_connection (sett_conn),
+	                              NULL,
+	                              LOGL_DEBUG,
+	                              LOGD_CORE,
+	                              "new connection", "++ ",
 	                              path);
 
 	/* Only emit the individual connection-added signal after connections
@@ -1021,13 +987,13 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 		                            &interface_info_settings,
 		                            &signal_info_new_connection,
 		                            "(o)",
-		                            nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)));
+		                            nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)));
 
-		g_signal_emit (self, signals[CONNECTION_ADDED], 0, connection);
+		g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
 		_notify (self, PROP_CONNECTIONS);
 	}
 
-	nm_settings_connection_added (connection);
+	nm_settings_connection_added (sett_conn);
 }
 
 static gboolean
@@ -1079,7 +1045,7 @@ nm_settings_add_connection (NMSettings *self,
 
 	/* Make sure a connection with this UUID doesn't already exist */
 	c_list_for_each_entry (candidate, &priv->connections_lst_head, _connections_lst) {
-		if (nm_streq0 (uuid, nm_connection_get_uuid (NM_CONNECTION (candidate)))) {
+		if (nm_streq0 (uuid, nm_settings_connection_get_uuid (candidate))) {
 			g_set_error_literal (error,
 			                     NM_SETTINGS_ERROR,
 			                     NM_SETTINGS_ERROR_UUID_EXISTS,
@@ -1113,8 +1079,13 @@ nm_settings_add_connection (NMSettings *self,
 
 		added = nm_settings_plugin_add_connection (plugin, connection, save_to_disk, &add_error);
 		if (added) {
-			if (secrets)
-				nm_connection_update_secrets (NM_CONNECTION (added), NULL, secrets, NULL);
+			if (secrets) {
+				/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+				nm_connection_update_secrets (nm_settings_connection_get_connection (added),
+				                              NULL,
+				                              secrets,
+				                              NULL);
+			}
 			claim_connection (self, added);
 			return added;
 		}
@@ -1132,7 +1103,7 @@ nm_settings_add_connection (NMSettings *self,
 
 static void
 send_agent_owned_secrets (NMSettings *self,
-                          NMSettingsConnection *connection,
+                          NMSettingsConnection *sett_conn,
                           NMAuthSubject *subject)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
@@ -1142,12 +1113,12 @@ send_agent_owned_secrets (NMSettings *self,
 	 * as agent-owned secrets are the only ones we send back to be saved.
 	 * Only send secrets to agents of the same UID that called update too.
 	 */
-	for_agent = nm_simple_connection_new_clone (NM_CONNECTION (connection));
+	for_agent = nm_simple_connection_new_clone (nm_settings_connection_get_connection (sett_conn));
 	nm_connection_clear_secrets_with_flags (for_agent,
 	                                        secrets_filter_cb,
 	                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
 	nm_agent_manager_save_secrets (priv->agent_mgr,
-	                               nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)),
+	                               nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)),
 	                               for_agent,
 	                               subject);
 }
@@ -1190,7 +1161,8 @@ pk_add_cb (NMAuthChain *chain,
 	} else {
 		/* Authorized */
 		connection = nm_auth_chain_get_data (chain, "connection");
-		g_assert (connection);
+		nm_assert (connection);
+
 		save_to_disk = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "save-to-disk"));
 		added = nm_settings_add_connection (self, connection, save_to_disk, &error);
 	}
@@ -1281,14 +1253,6 @@ nm_settings_add_connection_dbus (NMSettings *self,
 		goto done;
 	}
 
-	/* Do any of the plugins support adding? */
-	if (!get_plugin (self, TRUE)) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_NOT_SUPPORTED,
-		                             "None of the registered plugins support add.");
-		goto done;
-	}
-
 	if (!nm_auth_is_subject_in_acl_set_error (connection,
 	                                          subject,
 	                                          NM_SETTINGS_ERROR,
@@ -1612,20 +1576,21 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 	NMSettingWired *s_wired;
 	const char *setting_hwaddr;
 	const char *perm_hw_addr;
-	NMSettingsConnection *connection;
+	NMSettingsConnection *sett_conn;
 
 	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
 
 	perm_hw_addr = nm_device_get_permanent_hw_address (device);
 
 	/* Find a wired connection locked to the given MAC address, if any */
-	c_list_for_each_entry (connection, &priv->connections_lst_head, _connections_lst) {
+	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
+		NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
 		const char *ctype, *iface;
 
-		if (!nm_device_check_connection_compatible (device, NM_CONNECTION (connection)))
+		if (!nm_device_check_connection_compatible (device, connection, NULL))
 			continue;
 
-		s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+		s_con = nm_connection_get_setting_connection (connection);
 
 		iface = nm_setting_connection_get_interface_name (s_con);
 		if (iface && strcmp (iface, nm_device_get_iface (device)) != 0)
@@ -1636,14 +1601,15 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 		    && strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME))
 			continue;
 
-		s_wired = nm_connection_get_setting_wired (NM_CONNECTION (connection));
+		s_wired = nm_connection_get_setting_wired (connection);
 
-		if (!s_wired && !strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME)) {
+		if (   !s_wired
+		    && nm_streq (ctype, NM_SETTING_PPPOE_SETTING_NAME)) {
 			/* No wired setting; therefore the PPPoE connection applies to any device */
 			return TRUE;
 		}
 
-		g_assert (s_wired != NULL);
+		nm_assert (s_wired);
 
 		setting_hwaddr = nm_setting_wired_get_mac_address (s_wired);
 		if (setting_hwaddr) {
@@ -1687,11 +1653,11 @@ default_wired_clear_tag (NMSettings *self,
                          NMSettingsConnection *connection,
                          gboolean add_to_no_auto_default)
 {
-	g_return_if_fail (NM_IS_SETTINGS (self));
-	g_return_if_fail (NM_IS_DEVICE (device));
-	g_return_if_fail (NM_IS_CONNECTION (connection));
-	g_return_if_fail (device == g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ()));
-	g_return_if_fail (connection == g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()));
+	nm_assert (NM_IS_SETTINGS (self));
+	nm_assert (NM_IS_DEVICE (device));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (connection));
+	nm_assert (device == g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ()));
+	nm_assert (connection == g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()));
 
 	g_object_set_qdata (G_OBJECT (connection), _default_wired_device_quark (), NULL);
 	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), NULL);
@@ -1866,7 +1832,7 @@ get_property (GObject *object, guint prop_id,
 		                      : NULL);
 		break;
 	case PROP_CAN_MODIFY:
-		g_value_set_boolean (value, !!get_plugin (self, TRUE));
+		g_value_set_boolean (value, TRUE);
 		break;
 	case PROP_CONNECTIONS:
 		if (priv->connections_loaded) {
@@ -1930,6 +1896,7 @@ finalize (GObject *object)
 {
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
 
 	_clear_connections_cached_list (priv);
 
@@ -1938,7 +1905,12 @@ finalize (GObject *object)
 	g_slist_free_full (priv->unmanaged_specs, g_free);
 	g_slist_free_full (priv->unrecognized_specs, g_free);
 
-	g_slist_free_full (priv->plugins, g_object_unref);
+	while ((iter = priv->plugins)) {
+		gs_unref_object NMSettingsPlugin *plugin = iter->data;
+
+		priv->plugins = g_slist_delete_link (priv->plugins, iter);
+		g_signal_handlers_disconnect_by_data (plugin, self);
+	}
 
 	g_clear_object (&priv->agent_mgr);
 
diff --git a/src/settings/nm-settings.h b/src/settings/nm-settings.h
index 021512fd..38d8ad4e 100644
--- a/src/settings/nm-settings.h
+++ b/src/settings/nm-settings.h
@@ -85,7 +85,7 @@ void nm_settings_add_connection_dbus (NMSettings *self,
                                       NMSettingsAddCallback callback,
                                       gpointer user_data);
 
-NMSettingsConnection *const* nm_settings_get_connections (NMSettings *settings, guint *out_len);
+NMSettingsConnection *const*nm_settings_get_connections (NMSettings *settings, guint *out_len);
 
 NMSettingsConnection **nm_settings_get_connections_clone (NMSettings *self,
                                                           guint *out_len,
diff --git a/src/settings/plugins/ibft/meson.build b/src/settings/plugins/ibft/meson.build
index 1b6ff28a..c7dbe459 100644
--- a/src/settings/plugins/ibft/meson.build
+++ b/src/settings/plugins/ibft/meson.build
@@ -1,15 +1,9 @@
 name = 'nm-settings-plugin-ibft'
 
-cflags = [
-  '-DSBINDIR="@0@"'.format(nm_sbindir),
-  '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir)
-]
-
 libnms_ibft_core = static_library(
   'nms-ibft-core',
   'nms-ibft-reader.c',
   dependencies: nm_dep,
-  c_args: cflags
 )
 
 sources = files(
@@ -21,7 +15,6 @@ libnm_settings_plugin_ibft = shared_module(
   name,
   sources: sources,
   dependencies: nm_dep,
-  c_args: cflags,
   link_with: libnms_ibft_core,
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
index 77ce208f..69dd3733 100644
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ b/src/settings/plugins/ibft/nms-ibft-plugin.c
@@ -42,19 +42,15 @@ typedef struct {
 } NMSIbftPluginPrivate;
 
 struct _NMSIbftPlugin {
-	GObject parent;
+	NMSettingsPlugin parent;
 	NMSIbftPluginPrivate _priv;
 };
 
 struct _NMSIbftPluginClass {
-	GObjectClass parent;
+	NMSettingsPluginClass parent;
 };
 
-static void settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface);
-
-G_DEFINE_TYPE_EXTENDED (NMSIbftPlugin, nms_ibft_plugin, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (NM_TYPE_SETTINGS_PLUGIN,
-                                               settings_plugin_interface_init))
+G_DEFINE_TYPE (NMSIbftPlugin, nms_ibft_plugin, NM_TYPE_SETTINGS_PLUGIN);
 
 #define NMS_IBFT_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSIbftPlugin, NMS_IS_IBFT_PLUGIN)
 
@@ -84,9 +80,9 @@ read_connections (NMSIbftPlugin *self)
 		connection = nms_ibft_connection_new (iter->data, &error);
 		if (connection) {
 			nm_log_info (LOGD_SETTINGS, "ibft: read connection '%s'",
-			             nm_connection_get_id (NM_CONNECTION (connection)));
+			             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (connection)));
 			g_hash_table_insert (priv->connections,
-			                     g_strdup (nm_connection_get_uuid (NM_CONNECTION (connection))),
+			                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection))),
 			                     connection);
 		} else {
 			nm_log_warn (LOGD_SETTINGS, "ibft: failed to read iscsiadm record: %s", error->message);
@@ -121,11 +117,6 @@ get_connections (NMSettingsPlugin *config)
 /*****************************************************************************/
 
 static void
-init (NMSettingsPlugin *config)
-{
-}
-
-static void
 nms_ibft_plugin_init (NMSIbftPlugin *self)
 {
 	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
@@ -148,24 +139,20 @@ dispose (GObject *object)
 }
 
 static void
-nms_ibft_plugin_class_init (NMSIbftPluginClass *req_class)
+nms_ibft_plugin_class_init (NMSIbftPluginClass *klass)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (req_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->dispose = dispose;
-}
 
-static void
-settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface)
-{
-	plugin_iface->get_connections = get_connections;
-	plugin_iface->init = init;
+	plugin_class->get_connections = get_connections;
 }
 
 /*****************************************************************************/
 
-G_MODULE_EXPORT GObject *
+G_MODULE_EXPORT NMSettingsPlugin *
 nm_settings_plugin_factory (void)
 {
-	return G_OBJECT (g_object_ref (nms_ibft_plugin_get ()));
+	return NM_SETTINGS_PLUGIN (g_object_ref (nms_ibft_plugin_get ()));
 }
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
index cf849e28..ac5824a1 100644
--- a/src/settings/plugins/ibft/nms-ibft-reader.c
+++ b/src/settings/plugins/ibft/nms-ibft-reader.c
@@ -98,7 +98,7 @@ nms_ibft_reader_load_blocks (const char *iscsiadm_path,
 	const char *envp[1] = { NULL };
 	GSList *blocks = NULL;
 	char *out = NULL, *err = NULL;
-	gint status = 0;
+	int status = 0;
 	char **lines = NULL, **iter;
 	GPtrArray *block_lines = NULL;
 	gboolean success = FALSE;
diff --git a/src/settings/plugins/ibft/tests/meson.build b/src/settings/plugins/ibft/tests/meson.build
index 59f49c31..8b5e143a 100644
--- a/src/settings/plugins/ibft/tests/meson.build
+++ b/src/settings/plugins/ibft/tests/meson.build
@@ -6,7 +6,6 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  c_args: nm_build_cflags,
   link_with: libnms_ibft_core
 )
 
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
index 964ce22d..5d9689a5 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -3,11 +3,6 @@ install_data(
   install_dir: dbus_conf_dir
 )
 
-cflags = [
-  '-DSBINDIR="@0@"'.format(nm_sbindir),
-  '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir)
-]
-
 name = 'nmdbus-ifcfg-rh'
 
 dbus_sources = gnome.gdbus_codegen(
@@ -21,7 +16,6 @@ libnmdbus_ifcfg_rh = static_library(
   name,
   sources: dbus_sources,
   dependencies: glib_dep,
-  c_args: cflags
 )
 
 sources = files(
@@ -33,7 +27,6 @@ sources = files(
 )
 
 deps = [
-  crypto_dep,
   nm_dep
 ]
 
@@ -41,7 +34,6 @@ libnms_ifcfg_rh_core = static_library(
   'nms-ifcfg-rh-core',
   sources: sources,
   dependencies: deps,
-  c_args: cflags
 )
 
 sources = [dbus_sources] + files('nms-ifcfg-rh-connection.c')
@@ -50,7 +42,6 @@ libnm_settings_plugin_ifcfg_rh = shared_module(
   'nm-settings-plugin-ifcfg-rh',
   sources: sources,
   dependencies: deps,
-  c_args: cflags,
   link_with: [libnms_ifcfg_rh_core],
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
index 2863df64..e7a74a1a 100644
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
+++ b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
@@ -120,8 +120,8 @@ inotify_event_handler (GIOChannel *channel, GIOCondition cond, gpointer user_dat
 	struct inotify_event evt;
 
 	/* read the notifications from the watch descriptor */
-	while (g_io_channel_read_chars (channel, (gchar *) &evt, sizeof (struct inotify_event), NULL, NULL) == G_IO_STATUS_NORMAL) {
-		gchar filename[PATH_MAX + 1];
+	while (g_io_channel_read_chars (channel, (char *) &evt, sizeof (struct inotify_event), NULL, NULL) == G_IO_STATUS_NORMAL) {
+		char filename[PATH_MAX + 1];
 
 		filename[0] = '\0';
 		if (evt.len > 0) {
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
index 6979fdaa..ca319ddc 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
@@ -143,7 +143,7 @@ devtimeout_expired (gpointer user_data)
 	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
 
 	nm_log_info (LOGD_SETTINGS, "Device for connection '%s' did not appear before timeout",
-	             nm_connection_get_id (NM_CONNECTION (self)));
+	             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (self)));
 
 	g_signal_handler_disconnect (NM_PLATFORM_GET, priv->devtimeout_link_changed_handler);
 	priv->devtimeout_link_changed_handler = 0;
@@ -163,7 +163,7 @@ nm_ifcfg_connection_check_devtimeout (NMIfcfgConnection *self)
 	guint devtimeout;
 	const NMPlatformLink *pllink;
 
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (self)));
 
 	if (!nm_setting_connection_get_autoconnect (s_con))
 		return;
@@ -186,7 +186,7 @@ nm_ifcfg_connection_check_devtimeout (NMIfcfgConnection *self)
 	nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), FALSE);
 
 	nm_log_info (LOGD_SETTINGS, "Waiting %u seconds for %s to appear for connection '%s'",
-	             devtimeout, ifname, nm_connection_get_id (NM_CONNECTION (self)));
+	             devtimeout, ifname, nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (self)));
 
 	priv->devtimeout_link_changed_handler =
 	    g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED,
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 0fb77b9d..6cac8cb6 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -70,19 +70,15 @@ typedef struct {
 } SettingsPluginIfcfgPrivate;
 
 struct _SettingsPluginIfcfg {
-	GObject parent;
+	NMSettingsPlugin parent;
 	SettingsPluginIfcfgPrivate _priv;
 };
 
 struct _SettingsPluginIfcfgClass {
-	GObjectClass parent;
+	NMSettingsPluginClass parent;
 };
 
-static void settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface);
-
-G_DEFINE_TYPE_EXTENDED (SettingsPluginIfcfg, settings_plugin_ifcfg, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (NM_TYPE_SETTINGS_PLUGIN,
-                                               settings_plugin_interface_init))
+G_DEFINE_TYPE (SettingsPluginIfcfg, settings_plugin_ifcfg, NM_TYPE_SETTINGS_PLUGIN)
 
 #define SETTINGS_PLUGIN_IFCFG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfcfg, SETTINGS_IS_PLUGIN_IFCFG)
 
@@ -139,7 +135,7 @@ static void
 connection_removed_cb (NMSettingsConnection *obj, gpointer user_data)
 {
 	g_hash_table_remove (SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) user_data)->connections,
-	                     nm_connection_get_uuid (NM_CONNECTION (obj)));
+	                     nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (obj)));
 }
 
 static void
@@ -157,16 +153,16 @@ remove_connection (SettingsPluginIfcfg *self, NMIfcfgConnection *connection)
 	unrecognized = !!nm_ifcfg_connection_get_unrecognized_spec (connection);
 
 	g_object_ref (connection);
-	g_hash_table_remove (priv->connections, nm_connection_get_uuid (NM_CONNECTION (connection)));
+	g_hash_table_remove (priv->connections, nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
 	if (!unmanaged && !unrecognized)
 		nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
 	g_object_unref (connection);
 
 	/* Emit changes _after_ removing the connection */
 	if (unmanaged)
-		g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
 	if (unrecognized)
-		g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED);
+		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
 static NMIfcfgConnection *
@@ -228,7 +224,7 @@ update_connection (SettingsPluginIfcfg *self,
 		return NULL;
 	}
 
-	uuid = nm_connection_get_uuid (NM_CONNECTION (connection_new));
+	uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
 	connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);
 
 	if (   connection
@@ -285,12 +281,16 @@ update_connection (SettingsPluginIfcfg *self,
 
 		if (   !unmanaged_changed
 		    && !unrecognized_changed
-		    && nm_connection_compare (NM_CONNECTION (connection_by_uuid),
-		                              NM_CONNECTION (connection_new),
+		    && nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
+		                              nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
 		                              NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
 		                              NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
-			if (old_path && g_strcmp0 (old_path, full_path) != 0)
-				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT" without other changes", nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid)), NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
+			if (   old_path
+			    && !nm_streq0 (old_path, full_path)) {
+				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT" without other changes",
+				       nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid)),
+				       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
+			}
 		} else {
 
 			/*******************************************************
@@ -299,7 +299,7 @@ update_connection (SettingsPluginIfcfg *self,
 
 			if (source)
 				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT" from %s", NM_IFCFG_CONNECTION_LOG_ARG (connection_new), NM_IFCFG_CONNECTION_LOG_PATH (old_path));
-			else if (!g_strcmp0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
+			else if (nm_streq0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
 				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
 			else if (old_path)
 				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT, old_path, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
@@ -312,7 +312,7 @@ update_connection (SettingsPluginIfcfg *self,
 			              NULL);
 
 			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
-			                                    NM_CONNECTION (connection_new),
+			                                    nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
 			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 			                                    "ifcfg-update",
@@ -338,25 +338,27 @@ update_connection (SettingsPluginIfcfg *self,
 					 * so add it back now.
 					 */
 					g_hash_table_insert (priv->connections,
-					                     g_strdup (nm_connection_get_uuid (NM_CONNECTION (connection_by_uuid))),
+					                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_by_uuid))),
 					                     connection_by_uuid /* we took reference above and pass it on */);
 				}
 			} else {
 				if (old_unmanaged /* && !new_unmanaged */) {
 					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" and its device because NM_CONTROLLED was true.",
 					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-					g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_CONNECTION_ADDED, connection_by_uuid);
+					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
+					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
 				} else if (old_unrecognized /* && !new_unrecognized */) {
 					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" because it is now a recognized type.",
 					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-					g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_CONNECTION_ADDED, connection_by_uuid);
+					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
+					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
 				}
 			}
 
 			if (unmanaged_changed)
-				g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
+				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
 			if (unrecognized_changed)
-				g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED);
+				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
 		}
 		nm_settings_connection_set_filename (NM_SETTINGS_CONNECTION (connection_by_uuid), full_path);
 		g_object_unref (connection_new);
@@ -394,11 +396,13 @@ update_connection (SettingsPluginIfcfg *self,
 			/* Only raise the signal if we were called without source, i.e. if we read the connection from file.
 			 * Otherwise, we were called by add_connection() which does not expect the signal. */
 			if (nm_ifcfg_connection_get_unmanaged_spec (connection_new))
-				g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
+				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
 			else if (nm_ifcfg_connection_get_unrecognized_spec (connection_new))
-				g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED);
-			else
-				g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_CONNECTION_ADDED, connection_new);
+				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
+			else {
+				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
+				                                                  NM_SETTINGS_CONNECTION (connection_new));
+			}
 		}
 		return connection_new;
 	}
@@ -731,7 +735,7 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 		return;
 	}
 
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (connection));
+	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection)));
 	if (!s_con) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
@@ -998,11 +1002,6 @@ config_changed_cb (NMConfig *config,
 /*****************************************************************************/
 
 static void
-init (NMSettingsPlugin *config)
-{
-}
-
-static void
 settings_plugin_ifcfg_init (SettingsPluginIfcfg *plugin)
 {
 	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) plugin);
@@ -1059,30 +1058,26 @@ dispose (GObject *object)
 }
 
 static void
-settings_plugin_ifcfg_class_init (SettingsPluginIfcfgClass *req_class)
+settings_plugin_ifcfg_class_init (SettingsPluginIfcfgClass *klass)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (req_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->constructed = constructed;
 	object_class->dispose = dispose;
-}
 
-static void
-settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface)
-{
-	plugin_iface->get_connections = get_connections;
-	plugin_iface->add_connection = add_connection;
-	plugin_iface->load_connection = load_connection;
-	plugin_iface->reload_connections = reload_connections;
-	plugin_iface->get_unmanaged_specs = get_unmanaged_specs;
-	plugin_iface->get_unrecognized_specs = get_unrecognized_specs;
-	plugin_iface->init = init;
+	plugin_class->get_connections = get_connections;
+	plugin_class->add_connection = add_connection;
+	plugin_class->load_connection = load_connection;
+	plugin_class->reload_connections = reload_connections;
+	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+	plugin_class->get_unrecognized_specs = get_unrecognized_specs;
 }
 
 /*****************************************************************************/
 
-G_MODULE_EXPORT GObject *
+G_MODULE_EXPORT NMSettingsPlugin *
 nm_settings_plugin_factory (void)
 {
-	return G_OBJECT (g_object_ref (settings_plugin_ifcfg_get ()));
+	return NM_SETTINGS_PLUGIN (g_object_ref (settings_plugin_ifcfg_get ()));
 }
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 c1960846..09a37991 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -33,6 +33,7 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
+#include "nm-utils/nm-secret-utils.h"
 #include "nm-connection.h"
 #include "nm-dbus-interface.h"
 #include "nm-setting-connection.h"
@@ -41,6 +42,7 @@
 #include "nm-setting-ip6-config.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
+#include "nm-setting-ethtool.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-bond.h"
 #include "nm-setting-team.h"
@@ -53,6 +55,7 @@
 #include "nm-setting-generic.h"
 #include "nm-core-internal.h"
 #include "nm-utils.h"
+#include "nm-ethtool-utils.h"
 
 #include "platform/nm-platform.h"
 #include "NetworkManagerUtils.h"
@@ -77,6 +80,210 @@
 
 /*****************************************************************************/
 
+static char *
+get_full_file_path (const char *ifcfg_path, const char *file_path)
+{
+	const char *base = file_path;
+	char *p, *ret, *dirname;
+
+	g_return_val_if_fail (ifcfg_path != NULL, NULL);
+	g_return_val_if_fail (file_path != NULL, NULL);
+
+	if (file_path[0] == '/')
+		return g_strdup (file_path);
+
+	p = strrchr (file_path, '/');
+	if (p)
+		base = p + 1;
+
+	dirname = g_path_get_dirname (ifcfg_path);
+	ret = g_build_path ("/", dirname, base, NULL);
+	g_free (dirname);
+	return ret;
+}
+
+/*****************************************************************************/
+
+static NMSettingSecretFlags
+_secret_read_ifcfg_flags (shvarFile *ifcfg, const char *flags_key)
+{
+	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
+	gs_free char *val_free = NULL;
+	const char *val;
+
+	nm_assert (flags_key);
+	nm_assert (g_str_has_suffix (flags_key, "_FLAGS"));
+
+	val = svGetValueStr (ifcfg, flags_key, &val_free);
+	if (val) {
+		if (strstr (val, SECRET_FLAG_AGENT))
+			flags |= NM_SETTING_SECRET_FLAG_AGENT_OWNED;
+		if (strstr (val, SECRET_FLAG_NOT_SAVED))
+			flags |= NM_SETTING_SECRET_FLAG_NOT_SAVED;
+		if (strstr (val, SECRET_FLAG_NOT_REQUIRED))
+			flags |= NM_SETTING_SECRET_FLAG_NOT_REQUIRED;
+	}
+	return flags;
+}
+
+static void
+_secret_read_ifcfg (shvarFile *ifcfg,
+                    shvarFile *keys_ifcfg,
+                    const char *name,
+                    char **value,
+                    NMSettingSecretFlags *flags)
+{
+	char flags_key[250];
+
+	nm_sprintf_buf (flags_key, "%s_FLAGS", name);
+
+	*flags = _secret_read_ifcfg_flags (ifcfg, flags_key);
+
+	if (*flags != NM_SETTING_SECRET_FLAG_NONE)
+		*value = NULL;
+	else {
+		*value = svGetValue_cp (ifcfg, name);
+		if (!*value && keys_ifcfg)
+			*value = svGetValue_cp (keys_ifcfg, name);
+	}
+}
+
+static void
+_secret_set_from_ifcfg (gpointer setting,
+                        shvarFile *ifcfg,
+                        shvarFile *keys_ifcfg,
+                        const char *ifcfg_key,
+                        const char *property_name)
+{
+	nm_auto_free_secret char *secret = NULL;
+	NMSettingSecretFlags flags;
+	char flags_key[250];
+
+	nm_assert (NM_IS_SETTING (setting));
+
+	_secret_read_ifcfg (ifcfg, keys_ifcfg, ifcfg_key, &secret, &flags);
+
+	g_object_set (setting,
+	              property_name,
+	              secret,
+	              nm_sprintf_buf (flags_key, "%s-flags", property_name),
+	              flags,
+	              NULL);
+}
+
+static gboolean
+_secret_password_raw_to_bytes (const char *ifcfg_key,
+                               const char *password_raw,
+                               GBytes **out_bytes,
+                               GError **error)
+{
+	nm_auto_free_secret_buf NMSecretBuf *secret = NULL;
+	gsize len;
+
+	if (!password_raw) {
+		NM_SET_OUT (out_bytes, NULL);
+		return TRUE;
+	}
+
+	if (password_raw[0] == '0' && password_raw[1] == 'x')
+		password_raw += 2;
+
+	secret = nm_secret_buf_new (strlen (password_raw) / 2 + 3);
+	if (!_nm_utils_str2bin_full (password_raw, FALSE, ":", secret->bin, secret->len, &len)) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		             "Invalid hex password in %s",
+		             ifcfg_key);
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_bytes, nm_secret_buf_to_gbytes_take (g_steal_pointer (&secret), len));
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+static GBytes *
+_cert_get_cert_bytes (const char *ifcfg_path,
+                      const char *value,
+                      GError **error)
+{
+	gs_free char *path = NULL;
+
+	if (g_str_has_prefix (value, "pkcs11:"))
+		return _nm_setting_802_1x_cert_value_to_bytes (NM_SETTING_802_1X_CK_SCHEME_PKCS11, (guint8 *) value, -1, error);
+
+	path = get_full_file_path (ifcfg_path, value);
+	return _nm_setting_802_1x_cert_value_to_bytes (NM_SETTING_802_1X_CK_SCHEME_PATH, (guint8 *) path, -1, error);
+}
+
+static gboolean
+_cert_get_cert (shvarFile *ifcfg,
+                const char *ifcfg_key,
+                GBytes **out_cert,
+                NMSetting8021xCKScheme *out_scheme,
+                GError **error)
+{
+	nm_auto_free_secret char *val_free = NULL;
+	const char *val;
+	gs_unref_bytes GBytes *cert = NULL;
+	GError *local = NULL;
+	NMSetting8021xCKScheme scheme;
+
+	val = svGetValueStr (ifcfg, ifcfg_key, &val_free);
+	if (!val) {
+		NM_SET_OUT (out_cert, NULL);
+		NM_SET_OUT (out_scheme, NM_SETTING_802_1X_CK_SCHEME_UNKNOWN);
+		return TRUE;
+	}
+
+	cert = _cert_get_cert_bytes (svFileGetName (ifcfg), val, &local);
+	if (!cert)
+		goto err;
+
+	scheme = _nm_setting_802_1x_cert_get_scheme (cert, &local);
+	if (scheme == NM_SETTING_802_1X_CK_SCHEME_UNKNOWN)
+		goto err;
+
+	NM_SET_OUT (out_cert, g_steal_pointer (&cert));
+	NM_SET_OUT (out_scheme, scheme);
+	return TRUE;
+
+err:
+	g_set_error (error,
+	             NM_SETTINGS_ERROR,
+	             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+	             "invalid certificate %s: %s",
+	             ifcfg_key,
+	             local->message);
+	g_error_free (local);
+	return FALSE;
+}
+
+static gboolean
+_cert_set_from_ifcfg (gpointer setting,
+                      shvarFile *ifcfg,
+                      const char *ifcfg_key,
+                      const char *property_name,
+                      GBytes **out_cert,
+                      GError **error)
+{
+	gs_unref_bytes GBytes *cert = NULL;
+
+	if (!_cert_get_cert (ifcfg,
+	                     ifcfg_key,
+	                     &cert,
+	                     NULL,
+	                     error))
+		return FALSE;
+
+	g_object_set (setting, property_name, cert, NULL);
+
+	NM_SET_OUT (out_cert, g_steal_pointer (&cert));
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 static void
 check_if_bond_slave (shvarFile *ifcfg,
                      NMSettingConnection *s_con)
@@ -232,13 +439,16 @@ make_connection_setting (const char *file,
 	              NM_SETTING_CONNECTION_AUTOCONNECT,
 	              svGetValueBoolean (ifcfg, "ONBOOT", TRUE),
 	              NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY,
-	              (gint) svGetValueInt64 (ifcfg, "AUTOCONNECT_PRIORITY", 10,
+	              (int) svGetValueInt64 (ifcfg, "AUTOCONNECT_PRIORITY", 10,
 	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN,
 	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX,
 	                                      NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT),
 	              NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES,
-	              (gint) svGetValueInt64 (ifcfg, "AUTOCONNECT_RETRIES", 10,
+	              (int) svGetValueInt64 (ifcfg, "AUTOCONNECT_RETRIES", 10,
 	                                      -1, G_MAXINT32, -1),
+	              NM_SETTING_CONNECTION_MULTI_CONNECT,
+	              (gint) svGetValueInt64 (ifcfg, "MULTI_CONNECT", 10,
+	                                      G_MININT32, G_MAXINT32, NM_CONNECTION_MULTI_CONNECT_DEFAULT),
 	              NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES,
 	              svGetValueBoolean (ifcfg, "AUTOCONNECT_SLAVES", NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT),
 	              NM_SETTING_CONNECTION_LLDP, lldp,
@@ -249,7 +459,7 @@ make_connection_setting (const char *file,
 	if (v) {
 		gs_free const char **items = NULL;
 
-		items = nm_utils_strsplit_set (v, " ");
+		items = nm_utils_strsplit_set (v, " ", FALSE);
 		for (iter = items; iter && *iter; iter++) {
 			if (!nm_setting_connection_add_permission (s_con, "user", *iter, NULL))
 				PARSE_WARNING ("invalid USERS item '%s'", *iter);
@@ -265,7 +475,7 @@ make_connection_setting (const char *file,
 	if (v) {
 		gs_free const char **items = NULL;
 
-		items = nm_utils_strsplit_set (v, " \t");
+		items = nm_utils_strsplit_set (v, " \t", FALSE);
 		for (iter = items; iter && *iter; iter++) {
 			if (!nm_setting_connection_add_secondary (s_con, *iter))
 				PARSE_WARNING ("secondary connection UUID '%s' already added", *iter);
@@ -335,7 +545,7 @@ make_connection_setting (const char *file,
 	}
 
 	vint64 = svGetValueInt64 (ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1);
-	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (gint) vint64, NULL);
+	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (int) vint64, NULL);
 
 	i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT;
 	if (!svGetValueEnum (ifcfg, "MDNS",
@@ -344,6 +554,13 @@ make_connection_setting (const char *file,
 		PARSE_WARNING ("invalid MDNS setting");
 	g_object_set (s_con, NM_SETTING_CONNECTION_MDNS, i_val, NULL);
 
+	i_val = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+	if (!svGetValueEnum (ifcfg, "LLMNR",
+	                     nm_setting_connection_llmnr_get_type (),
+	                     &i_val, NULL))
+		PARSE_WARNING ("invalid LLMNR setting");
+	g_object_set (s_con, NM_SETTING_CONNECTION_LLMNR, i_val, NULL);
+
 	return NM_SETTING (s_con);
 }
 
@@ -674,7 +891,7 @@ parse_route_line (const char *line,
 	 * Maybe later we want to support some form of quotation here.
 	 * Which of course, would be incompatible with initscripts.
 	 */
-	words_free = nm_utils_strsplit_set (line, " \t\n");
+	words_free = nm_utils_strsplit_set (line, " \t\n", FALSE);
 
 	words = words_free ?: NM_PTRARRAY_EMPTY (const char *);
 
@@ -1110,7 +1327,7 @@ parse_dns_options (NMSettingIPConfig *ip_config, const char *value)
 	if (!nm_setting_ip_config_has_dns_options (ip_config))
 		nm_setting_ip_config_clear_dns_options (ip_config, TRUE);
 
-	options = nm_utils_strsplit_set (value, " ");
+	options = nm_utils_strsplit_set (value, " ", FALSE);
 	if (options) {
 		for (item = options; *item; item++) {
 			if (!nm_setting_ip_config_add_dns_option (ip_config, *item))
@@ -1178,7 +1395,7 @@ make_user_setting (shvarFile *ifcfg)
 	const char *key;
 	nm_auto_free_gstring GString *str = NULL;
 
-	keys = svGetKeys (ifcfg);
+	keys = svGetKeys (ifcfg, SV_KEY_TYPE_USER);
 	if (!keys)
 		return NULL;
 
@@ -1187,9 +1404,6 @@ make_user_setting (shvarFile *ifcfg)
 		const char *value;
 		gs_free char *value_to_free = NULL;
 
-		if (!g_str_has_prefix (key, "NM_USER_"))
-			continue;
-
 		value = svGetValue (ifcfg, key, &value_to_free);
 
 		if (!value)
@@ -1217,6 +1431,32 @@ make_user_setting (shvarFile *ifcfg)
 }
 
 static NMSetting *
+make_match_setting (shvarFile *ifcfg)
+{
+	NMSettingMatch *s_match = NULL;
+	gs_free const char **strv = NULL;
+	gs_free char *value = NULL;
+	const char *v;
+	gsize i;
+
+	v = svGetValueStr (ifcfg, "MATCH_INTERFACE_NAME", &value);
+	if (!v)
+		return NULL;
+
+	strv = nm_utils_strsplit_set (v, " \t", TRUE);
+	if (strv) {
+		for (i = 0; strv[i]; i++) {
+			if (!s_match)
+				s_match = (NMSettingMatch *) nm_setting_match_new ();
+			nm_setting_match_add_interface_name (s_match,
+			                                     _nm_utils_unescape_spaces ((char *) strv[i]));
+		}
+	}
+
+	return (NMSetting *) s_match;
+}
+
+static NMSetting *
 make_proxy_setting (shvarFile *ifcfg)
 {
 	NMSettingProxy *s_proxy = NULL;
@@ -1286,7 +1526,7 @@ make_ip4_setting (shvarFile *ifcfg,
 	shvarFile *route_ifcfg;
 	gboolean never_default;
 	gint64 timeout;
-	gint priority;
+	int priority;
 	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
 	const char *const *item;
 	guint32 route_table;
@@ -1483,7 +1723,7 @@ make_ip4_setting (shvarFile *ifcfg,
 		if (v) {
 			gs_free const char **searches = NULL;
 
-			searches = nm_utils_strsplit_set (v, " ");
+			searches = nm_utils_strsplit_set (v, " ", FALSE);
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
@@ -1544,7 +1784,7 @@ make_ip4_setting (shvarFile *ifcfg,
 		if (v) {
 			gs_free const char **searches = NULL;
 
-			searches = nm_utils_strsplit_set (v, " ");
+			searches = nm_utils_strsplit_set (v, " ", FALSE);
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
@@ -1561,7 +1801,7 @@ make_ip4_setting (shvarFile *ifcfg,
 		if (timeout > 0)
 			timeout *= 1000;
 	}
-	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT, (gint) timeout, NULL);
+	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DAD_TIMEOUT, (int) timeout, NULL);
 
 	return g_steal_pointer (&s_ip4);
 }
@@ -1697,7 +1937,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	guint32 i;
 	int i_val;
 	GError *local = NULL;
-	gint priority;
+	int priority;
 	gboolean never_default = FALSE;
 	gboolean ip6_privacy = FALSE, ip6_privacy_prefer_public_ip;
 	NMSettingIP6ConfigPrivacy ip6_privacy_val;
@@ -1860,7 +2100,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	                   ipv6addr_secondaries ?: "",
 	                   NULL);
 
-	list = nm_utils_strsplit_set (value, " ");
+	list = nm_utils_strsplit_set (value, " ", FALSE);
 	for (iter = list, i = 0; iter && *iter; iter++, i++) {
 		NMIPAddress *addr = NULL;
 
@@ -1953,7 +2193,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	if (v) {
 		gs_free const char **searches = NULL;
 
-		searches = nm_utils_strsplit_set (v, " ");
+		searches = nm_utils_strsplit_set (v, " ", FALSE);
 		if (searches) {
 			for (iter = searches; *iter; iter++) {
 				if (!nm_setting_ip_config_add_dns_search (s_ip6, *iter))
@@ -1982,6 +2222,68 @@ error:
 }
 
 static NMSetting *
+make_sriov_setting (shvarFile *ifcfg)
+{
+	gs_unref_hashtable GHashTable *keys = NULL;
+	gs_unref_ptrarray GPtrArray *vfs = NULL;
+	NMTernary autoprobe_drivers;
+	NMSettingSriov *s_sriov;
+	int total_vfs;
+
+	total_vfs = svGetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", 10, 0, G_MAXINT32, 0);
+	if (!total_vfs)
+		return NULL;
+
+	autoprobe_drivers = svGetValueInt64 (ifcfg,
+	                                     "SRIOV_AUTOPROBE_DRIVERS",
+	                                     10,
+	                                     NM_TERNARY_FALSE,
+	                                     NM_TERNARY_TRUE,
+	                                     NM_TERNARY_DEFAULT);
+
+	keys = svGetKeys (ifcfg, SV_KEY_TYPE_SRIOV_VF);
+	if (keys) {
+		GHashTableIter iter;
+		const char *key;
+
+		g_hash_table_iter_init (&iter, keys);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
+			gs_free_error GError *error = NULL;
+			gs_free char *value_to_free = NULL;
+			const char *value;
+			NMSriovVF *vf;
+
+			nm_assert (g_str_has_prefix (key, "SRIOV_VF"));
+
+			value = svGetValue (ifcfg, key, &value_to_free);
+			if (!value)
+				continue;
+
+			key += NM_STRLEN ("SRIOV_VF");
+
+			vf = _nm_utils_sriov_vf_from_strparts (key, value, &error);
+			if (!vf) {
+				PARSE_WARNING ("ignoring invalid SR-IOV VF '%s %s': %s",
+				               key, value, error->message);
+				continue;
+			}
+			if (!vfs)
+				vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref);
+			g_ptr_array_add (vfs, vf);
+		}
+	}
+
+	s_sriov = (NMSettingSriov *) nm_setting_sriov_new ();
+	g_object_set (s_sriov,
+	              NM_SETTING_SRIOV_TOTAL_VFS, total_vfs,
+	              NM_SETTING_SRIOV_VFS, vfs,
+	              NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, (int) autoprobe_drivers,
+	              NULL);
+
+	return (NMSetting *) s_sriov;
+}
+
+static NMSetting *
 make_tc_setting (shvarFile *ifcfg)
 {
 	NMSettingTCConfig *s_tc = NULL;
@@ -2241,7 +2543,7 @@ read_dcb_percent_array (shvarFile *ifcfg,
 		return TRUE;
 	}
 
-	split = nm_utils_strsplit_set (val, ",");
+	split = nm_utils_strsplit_set (val, ",", FALSE);
 	if (NM_PTRARRAY_LEN (split) != 8) {
 		PARSE_WARNING ("invalid %s percentage list value '%s'", prop, val);
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -2518,30 +2820,6 @@ read_wep_keys (shvarFile *ifcfg,
 	return TRUE;
 }
 
-static NMSettingSecretFlags
-read_secret_flags (shvarFile *ifcfg, const char *flags_key)
-{
-	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
-	char *val;
-
-	g_return_val_if_fail (flags_key != NULL, NM_SETTING_SECRET_FLAG_NONE);
-	g_return_val_if_fail (flags_key[0] != '\0', NM_SETTING_SECRET_FLAG_NONE);
-	g_return_val_if_fail (g_str_has_suffix (flags_key, "_FLAGS"), NM_SETTING_SECRET_FLAG_NONE);
-
-	val = svGetValueStr_cp (ifcfg, flags_key);
-	if (val) {
-		if (strstr (val, SECRET_FLAG_AGENT))
-			flags |= NM_SETTING_SECRET_FLAG_AGENT_OWNED;
-		if (strstr (val, SECRET_FLAG_NOT_SAVED))
-			flags |= NM_SETTING_SECRET_FLAG_NOT_SAVED;
-		if (strstr (val, SECRET_FLAG_NOT_REQUIRED))
-			flags |= NM_SETTING_SECRET_FLAG_NOT_REQUIRED;
-
-		g_free (val);
-	}
-	return flags;
-}
-
 static NMSetting *
 make_wep_setting (shvarFile *ifcfg,
                   const char *file,
@@ -2573,7 +2851,7 @@ make_wep_setting (shvarFile *ifcfg,
 	}
 
 	/* Read WEP key flags */
-	key_flags = read_secret_flags (ifcfg, "WEP_KEY_FLAGS");
+	key_flags = _secret_read_ifcfg_flags (ifcfg, "WEP_KEY_FLAGS");
 	g_object_set (s_wsec, NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, key_flags, NULL);
 
 	/* Read keys in the ifcfg file if they are system-owned */
@@ -2677,7 +2955,7 @@ fill_wpa_ciphers (shvarFile *ifcfg,
 	if (!p)
 		return TRUE;
 
-	list = nm_utils_strsplit_set (p, " ");
+	list = nm_utils_strsplit_set (p, " ", FALSE);
 	for (iter = list; iter && *iter; iter++, i++) {
 		/* Ad-Hoc configurations cannot have pairwise ciphers, and can only
 		 * have one group cipher.  Ignore any additional group ciphers and
@@ -2772,254 +3050,118 @@ parse_wpa_psk (shvarFile *ifcfg,
 	return g_steal_pointer (&psk);
 }
 
-static void
-read_8021x_password (shvarFile *ifcfg, shvarFile *keys_ifcfg, const char *name,
-                     char **value, NMSettingSecretFlags *flags)
-{
-	gs_free char *flags_key = NULL;
-
-	*value = NULL;
-	flags_key = g_strdup_printf ("%s_FLAGS", name);
-	*flags = read_secret_flags (ifcfg, flags_key);
-
-	if (*flags == NM_SETTING_SECRET_FLAG_NONE) {
-		*value = svGetValueStr_cp (ifcfg, name);
-		if (!*value && keys_ifcfg)
-			*value = svGetValueStr_cp (keys_ifcfg, name);
-	}
-}
-
 static gboolean
 eap_simple_reader (const char *eap_method,
                    shvarFile *ifcfg,
-                   shvarFile *keys,
+                   shvarFile *keys_ifcfg,
                    NMSetting8021x *s_8021x,
                    gboolean phase2,
                    GError **error)
 {
 	NMSettingSecretFlags flags;
-	GBytes *bytes;
-	char *value;
+	gs_free char *identity_free = NULL;
+	nm_auto_free_secret char *password_raw_str = NULL;
+	gs_unref_bytes GBytes *password_raw_bytes = NULL;
 
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_IDENTITY");
-	if (!value) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Missing IEEE_8021X_IDENTITY for EAP method '%s'.",
-		             eap_method);
-		return FALSE;
-	}
-	g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, value, NULL);
-	nm_clear_g_free (&value);
+	g_object_set (s_8021x,
+	              NM_SETTING_802_1X_IDENTITY,
+	              svGetValueStr (ifcfg, "IEEE_8021X_IDENTITY", &identity_free),
+	              NULL);
 
-	read_8021x_password (ifcfg, keys, "IEEE_8021X_PASSWORD", &value, &flags);
-	g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD_FLAGS, flags, NULL);
-	if (value) {
-		g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD, value, NULL);
-		nm_clear_g_free (&value);
-	}
+	_secret_set_from_ifcfg (s_8021x,
+	                        ifcfg,
+	                        keys_ifcfg,
+	                        "IEEE_8021X_PASSWORD",
+	                        NM_SETTING_802_1X_PASSWORD);
+
+	_secret_read_ifcfg (ifcfg, keys_ifcfg, "IEEE_8021X_PASSWORD_RAW", &password_raw_str, &flags);
+	if (!_secret_password_raw_to_bytes ("IEEE_8021X_PASSWORD_RAW",
+	                                    password_raw_str,
+	                                    &password_raw_bytes,
+	                                    error))
+		return FALSE;
 
-	read_8021x_password (ifcfg, keys, "IEEE_8021X_PASSWORD_RAW", &value, &flags);
-	g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD_RAW_FLAGS, flags, NULL);
-	if (value) {
-		bytes = nm_utils_hexstr2bin (value);
-		if (!bytes) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid hex string '%s' in IEEE_8021X_PASSWORD_RAW.",
-			             value);
-			g_free (value);
-			return FALSE;
-		}
-		g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD_RAW, bytes, NULL);
-		g_bytes_unref (bytes);
-		nm_clear_g_free (&value);
-	}
+	g_object_set (s_8021x,
+	              NM_SETTING_802_1X_PASSWORD_RAW_FLAGS,
+	              flags,
+	              NM_SETTING_802_1X_PASSWORD_RAW,
+	              password_raw_bytes,
+	              NULL);
 
 	return TRUE;
 }
 
-static char *
-get_full_file_path (const char *ifcfg_path, const char *file_path)
-{
-	const char *base = file_path;
-	char *p, *ret, *dirname;
-
-	g_return_val_if_fail (ifcfg_path != NULL, NULL);
-	g_return_val_if_fail (file_path != NULL, NULL);
-
-	if (file_path[0] == '/')
-		return g_strdup (file_path);
-
-	p = strrchr (file_path, '/');
-	if (p)
-		base = p + 1;
-
-	dirname = g_path_get_dirname (ifcfg_path);
-	ret = g_build_path ("/", dirname, base, NULL);
-	g_free (dirname);
-	return ret;
-}
-
-static char *
-get_cert_value (const char *ifcfg_path, const char *value,
-                NMSetting8021xCKScheme *out_scheme)
-{
-	if (strncmp (value, "pkcs11:", 7) == 0) {
-		*out_scheme = NM_SETTING_802_1X_CK_SCHEME_PKCS11;
-		return g_strdup (value);
-	}
-
-	*out_scheme = NM_SETTING_802_1X_CK_SCHEME_PATH;
-	return get_full_file_path (ifcfg_path, value);
-}
-
 static gboolean
 eap_tls_reader (const char *eap_method,
                 shvarFile *ifcfg,
-                shvarFile *keys,
+                shvarFile *keys_ifcfg,
                 NMSetting8021x *s_8021x,
                 gboolean phase2,
                 GError **error)
 {
-	gs_free char *ca_cert = NULL;
-	gs_free char *privkey = NULL;
-	gs_free char *privkey_password = NULL;
-	char *value;
-	char *ca_cert_password = NULL;
-	char *client_cert_password = NULL;
-	NMSetting8021xCKFormat privkey_format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
-	const char *ca_cert_key = phase2 ? "IEEE_8021X_INNER_CA_CERT" : "IEEE_8021X_CA_CERT";
-	const char *ca_cert_pw_key = phase2 ? "IEEE_8021X_INNER_CA_CERT_PASSWORD" : "IEEE_8021X_CA_CERT_PASSWORD";
-	const char *ca_cert_pw_prop = phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD : NM_SETTING_802_1X_CA_CERT_PASSWORD;
-	const char *ca_cert_pw_flags_key = phase2 ? "IEEE_8021X_INNER_CA_CERT_PASSWORD_FLAGS" : "IEEE_8021X_CA_CERT_PASSWORD_FLAGS";
-	const char *ca_cert_pw_flags_prop = phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD_FLAGS : NM_SETTING_802_1X_CA_CERT_PASSWORD_FLAGS;
-	const char *cli_cert_key = phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT";
-	const char *cli_cert_pw_key = phase2 ? "IEEE_8021X_INNER_CLIENT_CERT_PASSWORD" : "IEEE_8021X_CLIENT_CERT_PASSWORD";
-	const char *cli_cert_pw_prop = phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD;
-	const char *cli_cert_pw_flags_key = phase2 ? "IEEE_8021X_INNER_CLIENT_CERT_PASSWORD_FLAGS" : "IEEE_8021X_CLIENT_CERT_PASSWORD_FLAGS";
-	const char *cli_cert_pw_flags_prop = phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD_FLAGS : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD_FLAGS;
-	const char *pk_key = phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY";
-	const char *pk_pw_key = phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD": "IEEE_8021X_PRIVATE_KEY_PASSWORD";
-	const char *pk_pw_flags_key = phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD_FLAGS" : "IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS";
-	const char *pk_pw_flags_prop = phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS : NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS;
-	NMSettingSecretFlags flags;
-	NMSetting8021xCKScheme scheme;
+	gs_unref_bytes GBytes *privkey = NULL;
+	gs_unref_bytes GBytes *client_cert = NULL;
+	gs_free char *identity_free = NULL;
 
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_IDENTITY");
-	if (value) {
-		g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, value, NULL);
-		g_free (value);
-	}
-
-	ca_cert = svGetValueStr_cp (ifcfg, ca_cert_key);
-	if (ca_cert) {
-		gs_free char *real_cert_value = NULL;
-
-		real_cert_value = get_cert_value (svFileGetName (ifcfg), ca_cert, &scheme);
-		if (phase2) {
-			if (!nm_setting_802_1x_set_phase2_ca_cert (s_8021x, real_cert_value, scheme, NULL, error))
-				return FALSE;
-		} else {
-			if (!nm_setting_802_1x_set_ca_cert (s_8021x, real_cert_value, scheme, NULL, error))
-				return FALSE;
-		}
-
-		if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-			flags = read_secret_flags (ifcfg, ca_cert_pw_flags_key);
-			g_object_set (s_8021x, ca_cert_pw_flags_prop, flags, NULL);
-
-			if (flags == NM_SETTING_SECRET_FLAG_NONE) {
-				ca_cert_password = svGetValueStr_cp (ifcfg, ca_cert_pw_key);
-				g_object_set (s_8021x, ca_cert_pw_prop, ca_cert_password, NULL);
-			}
-		}
-	} else {
-		PARSE_WARNING ("missing %s for EAP method '%s'; this is insecure!",
-		               ca_cert_key, eap_method);
-	}
-
-	/* Read and set private key password flags */
-	flags = read_secret_flags (ifcfg, pk_pw_flags_key);
-	g_object_set (s_8021x, pk_pw_flags_prop, flags, NULL);
-
-	/* Read the private key password if it's system-owned */
-	if (flags == NM_SETTING_SECRET_FLAG_NONE) {
-		/* Private key password */
-		privkey_password = svGetValueStr_cp (ifcfg, pk_pw_key);
-		if (!privkey_password && keys) {
-			/* Try the lookaside keys file */
-			privkey_password = svGetValueStr_cp (keys, pk_pw_key);
-		}
-	}
+	g_object_set (s_8021x,
+	              NM_SETTING_802_1X_IDENTITY,
+	              svGetValueStr (ifcfg, "IEEE_8021X_IDENTITY", &identity_free),
+	              NULL);
 
-	/* The private key itself */
-	privkey = svGetValueStr_cp (ifcfg, pk_key);
+	if (!_cert_set_from_ifcfg (s_8021x,
+	                           ifcfg,
+	                           phase2 ? "IEEE_8021X_INNER_CA_CERT" : "IEEE_8021X_CA_CERT",
+	                           phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT : NM_SETTING_802_1X_CA_CERT,
+	                           NULL,
+	                           error))
+		return FALSE;
+	_secret_set_from_ifcfg (s_8021x,
+	                        ifcfg,
+	                        keys_ifcfg,
+	                        phase2 ? "IEEE_8021X_INNER_CA_CERT_PASSWORD" : "IEEE_8021X_CA_CERT_PASSWORD",
+	                        phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD : NM_SETTING_802_1X_CA_CERT_PASSWORD);
+
+	if (!_cert_set_from_ifcfg (s_8021x,
+	                           ifcfg,
+	                           phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY",
+	                           phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY : NM_SETTING_802_1X_PRIVATE_KEY,
+	                           &privkey,
+	                           error))
+		return FALSE;
+	_secret_set_from_ifcfg (s_8021x,
+	                        ifcfg,
+	                        keys_ifcfg,
+	                        phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD" : "IEEE_8021X_PRIVATE_KEY_PASSWORD",
+	                        phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD : NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
 	if (!privkey) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Missing %s for EAP method '%s'.",
-		             pk_key,
+		             phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY",
 		             eap_method);
 		return FALSE;
 	}
 
-	{
-		gs_free char *real_cert_value = NULL;
-
-		real_cert_value = get_cert_value (svFileGetName (ifcfg), privkey, &scheme);
-		if (phase2) {
-			if (!nm_setting_802_1x_set_phase2_private_key (s_8021x,
-			                                               real_cert_value,
-			                                               privkey_password,
-			                                               scheme,
-			                                               &privkey_format,
-			                                               error))
-				return FALSE;
-		} else {
-			if (!nm_setting_802_1x_set_private_key (s_8021x,
-			                                        real_cert_value,
-			                                        privkey_password,
-			                                        scheme,
-			                                        &privkey_format,
-			                                        error))
-				return FALSE;
-		}
-	}
-
-	/* Only set the client certificate if the private key is not PKCS#12 format,
-	 * as NM (due to supplicant restrictions) requires.  If the key was PKCS#12,
-	 * then nm_setting_802_1x_set_private_key() already set the client certificate
-	 * to the same value as the private key.
-	 */
-	if (privkey_format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
-		gs_free char *real_cert_value = NULL;
-		gs_free char *client_cert = NULL;
-
-		client_cert = svGetValueStr_cp (ifcfg, cli_cert_key);
-		if (!client_cert) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Missing %s for EAP method '%s'.",
-			             cli_cert_key,
-			             eap_method);
-			return FALSE;
-		}
-
-		real_cert_value = get_cert_value (svFileGetName (ifcfg), client_cert, &scheme);
-		if (phase2) {
-			if (!nm_setting_802_1x_set_phase2_client_cert (s_8021x, real_cert_value, scheme, NULL, error))
-				return FALSE;
-		} else {
-			if (!nm_setting_802_1x_set_client_cert (s_8021x, real_cert_value, scheme, NULL, error))
-				return FALSE;
-		}
-
-		if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) {
-			flags = read_secret_flags (ifcfg, cli_cert_pw_flags_key);
-			g_object_set (s_8021x, cli_cert_pw_flags_prop, flags, NULL);
-
-			if (flags == NM_SETTING_SECRET_FLAG_NONE) {
-				client_cert_password = svGetValueStr_cp (ifcfg, cli_cert_pw_key);
-				g_object_set (s_8021x, cli_cert_pw_prop, client_cert_password, NULL);
-			}
-		}
+	if (!_cert_set_from_ifcfg (s_8021x,
+	                           ifcfg,
+	                           phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT",
+	                           phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT,
+	                           &client_cert,
+	                           error))
+		return FALSE;
+	/* FIXME: writer does not actually write IEEE_8021X_CLIENT_CERT_PASSWORD and other
+	 * certificate related passwords. It should, because otherwise persisting such profiles
+	 * to ifcfg looses information. As this currently only matters for PKCS11 URIs, it seems
+	 * a seldomly used feature so that it is not fixed yet. */
+	_secret_set_from_ifcfg (s_8021x,
+	                        ifcfg,
+	                        keys_ifcfg,
+	                        phase2 ? "IEEE_8021X_INNER_CLIENT_CERT_PASSWORD" : "IEEE_8021X_CLIENT_CERT_PASSWORD",
+	                        phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD);
+	if (!client_cert) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		             "Missing certificate for EAP method '%s'.",
+		             eap_method);
+		return FALSE;
 	}
 
 	return TRUE;
@@ -3028,7 +3170,7 @@ eap_tls_reader (const char *eap_method,
 static gboolean
 eap_peap_reader (const char *eap_method,
                  shvarFile *ifcfg,
-                 shvarFile *keys,
+                 shvarFile *keys_ifcfg,
                  NMSetting8021x *s_8021x,
                  gboolean phase2,
                  GError **error)
@@ -3037,19 +3179,19 @@ eap_peap_reader (const char *eap_method,
 	const char *v;
 	gs_free const char **list = NULL;
 	const char *const *iter;
-	NMSetting8021xCKScheme scheme;
-
-	v = svGetValueStr (ifcfg, "IEEE_8021X_CA_CERT", &value);
-	if (v) {
-		gs_free char *real_cert_value = NULL;
 
-		real_cert_value = get_cert_value (svFileGetName (ifcfg), v, &scheme);
-		if (!nm_setting_802_1x_set_ca_cert (s_8021x, real_cert_value, scheme, NULL, error))
-			return FALSE;
-	} else {
-		PARSE_WARNING ("missing IEEE_8021X_CA_CERT for EAP method '%s'; this is insecure!",
-		               eap_method);
-	}
+	if (!_cert_set_from_ifcfg (s_8021x,
+	                           ifcfg,
+	                           "IEEE_8021X_CA_CERT",
+	                           NM_SETTING_802_1X_CA_CERT,
+	                           NULL,
+	                           error))
+		return FALSE;
+	_secret_set_from_ifcfg (s_8021x,
+	                        ifcfg,
+	                        keys_ifcfg,
+	                        "IEEE_8021X_CA_CERT_PASSWORD",
+	                        NM_SETTING_802_1X_CA_CERT_PASSWORD);
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "IEEE_8021X_PEAP_VERSION", &value);
@@ -3083,16 +3225,16 @@ eap_peap_reader (const char *eap_method,
 	}
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (v, " ");
+	list = nm_utils_strsplit_set (v, " ", FALSE);
 	iter = list;
 	if (iter) {
 		if (NM_IN_STRSET (*iter, "MSCHAPV2",
 		                         "MD5",
 		                         "GTC")) {
-			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
+			if (!eap_simple_reader (*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
 				return FALSE;
 		} else if (nm_streq (*iter, "TLS")) {
-			if (!eap_tls_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
+			if (!eap_tls_reader (*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
 				return FALSE;
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -3121,7 +3263,7 @@ eap_peap_reader (const char *eap_method,
 static gboolean
 eap_ttls_reader (const char *eap_method,
                  shvarFile *ifcfg,
-                 shvarFile *keys,
+                 shvarFile *keys_ifcfg,
                  NMSetting8021x *s_8021x,
                  gboolean phase2,
                  GError **error)
@@ -3131,19 +3273,19 @@ eap_ttls_reader (const char *eap_method,
 	const char *v;
 	gs_free const char **list = NULL;
 	const char *const *iter;
-	NMSetting8021xCKScheme scheme;
-
-	v = svGetValueStr (ifcfg, "IEEE_8021X_CA_CERT", &value);
-	if (v) {
-		gs_free char *real_cert_value = NULL;
 
-		real_cert_value = get_cert_value (svFileGetName (ifcfg), v, &scheme);
-		if (!nm_setting_802_1x_set_ca_cert (s_8021x, real_cert_value, scheme, NULL, error))
-			return FALSE;
-	} else {
-		PARSE_WARNING ("missing IEEE_8021X_CA_CERT for EAP method '%s'; this is insecure!",
-		               eap_method);
-	}
+	if (!_cert_set_from_ifcfg (s_8021x,
+	                           ifcfg,
+	                           "IEEE_8021X_CA_CERT",
+	                           NM_SETTING_802_1X_CA_CERT,
+	                           NULL,
+	                           error))
+		return FALSE;
+	_secret_set_from_ifcfg (s_8021x,
+	                        ifcfg,
+	                        keys_ifcfg,
+	                        "IEEE_8021X_CA_CERT_PASSWORD",
+	                        NM_SETTING_802_1X_CA_CERT_PASSWORD);
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "IEEE_8021X_ANON_IDENTITY", &value);
@@ -3161,24 +3303,24 @@ eap_ttls_reader (const char *eap_method,
 	inner_auth = g_ascii_strdown (v, -1);
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (inner_auth, " ");
+	list = nm_utils_strsplit_set (inner_auth, " ", FALSE);
 	iter = list;
 	if (iter) {
 		if (NM_IN_STRSET (*iter, "mschapv2",
 		                         "mschap",
 		                         "pap",
 		                         "chap")) {
-			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
+			if (!eap_simple_reader (*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
 				return FALSE;
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, *iter, NULL);
 		} else if (nm_streq (*iter, "eap-tls")) {
-			if (!eap_tls_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
+			if (!eap_tls_reader (*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
 				return FALSE;
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, "tls", NULL);
 		} else if (NM_IN_STRSET (*iter, "eap-mschapv2",
 		                                "eap-md5",
 		                                "eap-gtc")) {
-			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
+			if (!eap_simple_reader (*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
 				return FALSE;
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, (*iter + NM_STRLEN ("eap-")), NULL);
 		} else {
@@ -3195,7 +3337,7 @@ eap_ttls_reader (const char *eap_method,
 static gboolean
 eap_fast_reader (const char *eap_method,
                  shvarFile *ifcfg,
-                 shvarFile *keys,
+                 shvarFile *keys_ifcfg,
                  NMSetting8021x *s_8021x,
                  gboolean phase2,
                  GError **error)
@@ -3222,7 +3364,7 @@ eap_fast_reader (const char *eap_method,
 	if (fast_provisioning) {
 		gs_free const char **list1 = NULL;
 
-		list1 = nm_utils_strsplit_set (fast_provisioning, " \t");
+		list1 = nm_utils_strsplit_set (fast_provisioning, " \t", FALSE);
 		for (iter = list1; iter && *iter; iter++) {
 			if (strcmp (*iter, "allow-unauth") == 0)
 				allow_unauth = TRUE;
@@ -3256,12 +3398,12 @@ eap_fast_reader (const char *eap_method,
 	}
 
 	/* Handle options for the inner auth method */
-	list = nm_utils_strsplit_set (inner_auth, " ");
+	list = nm_utils_strsplit_set (inner_auth, " ", FALSE);
 	iter = list;
 	if (iter) {
 		if (   !strcmp (*iter, "MSCHAPV2")
 		    || !strcmp (*iter, "GTC")) {
-			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
+			if (!eap_simple_reader (*iter, ifcfg, keys_ifcfg, s_8021x, TRUE, error))
 				goto done;
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -3296,7 +3438,7 @@ typedef struct {
 	const char *method;
 	gboolean (*reader) (const char *eap_method,
 	                    shvarFile *ifcfg,
-	                    shvarFile *keys,
+	                    shvarFile *keys_ifcfg,
 	                    NMSetting8021x *s_8021x,
 	                    gboolean phase2,
 	                    GError **error);
@@ -3336,7 +3478,7 @@ read_8021x_list_value (shvarFile *ifcfg,
 	if (!v)
 		return;
 
-	strv = nm_utils_strsplit_set (v, " \t");
+	strv = nm_utils_strsplit_set (v, " \t", FALSE);
 	if (strv)
 		g_object_set (setting, prop_name, strv, NULL);
 }
@@ -3348,7 +3490,7 @@ fill_8021x (shvarFile *ifcfg,
             gboolean wifi,
             GError **error)
 {
-	nm_auto_shvar_file_close shvarFile *keys = NULL;
+	nm_auto_shvar_file_close shvarFile *keys_ifcfg = NULL;
 	gs_unref_object NMSetting8021x *s_8021x = NULL;
 	gs_free char *value = NULL;
 	const char *v;
@@ -3365,12 +3507,12 @@ fill_8021x (shvarFile *ifcfg,
 		return NULL;
 	}
 
-	list = nm_utils_strsplit_set (v, " ");
+	list = nm_utils_strsplit_set (v, " ", FALSE);
 
 	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
 
-	/* Read in the lookaside keys file, if present */
-	keys = utils_get_keys_ifcfg (file, FALSE);
+	/* Read in the lookaside keys_ifcfg file, if present */
+	keys_ifcfg = utils_get_keys_ifcfg (file, FALSE);
 
 	/* Validate and handle each EAP method */
 	for (iter = list; iter && *iter; iter++) {
@@ -3394,7 +3536,7 @@ fill_8021x (shvarFile *ifcfg,
 			}
 
 			/* Parse EAP method specific options */
-			if (!(*eap->reader)(lower, ifcfg, keys, s_8021x, FALSE, error))
+			if (!(*eap->reader)(lower, ifcfg, keys_ifcfg, s_8021x, FALSE, error))
 				return NULL;
 
 			nm_setting_802_1x_add_eap_method (s_8021x, lower);
@@ -3444,7 +3586,7 @@ next:
 	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_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, (gint) timeout, NULL);
+	g_object_set (s_8021x, NM_SETTING_802_1X_AUTH_TIMEOUT, (int) timeout, NULL);
 
 	return g_steal_pointer (&s_8021x);
 }
@@ -3510,7 +3652,7 @@ make_wpa_setting (shvarFile *ifcfg,
 	if (wpa_psk) {
 		NMSettingSecretFlags psk_flags;
 
-		psk_flags = read_secret_flags (ifcfg, "WPA_PSK_FLAGS");
+		psk_flags = _secret_read_ifcfg_flags (ifcfg, "WPA_PSK_FLAGS");
 		g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS, psk_flags, NULL);
 
 		/* Read PSK if it's system-owned */
@@ -3597,7 +3739,7 @@ make_leap_setting (shvarFile *ifcfg,
 		return NULL; /* Not LEAP */
 	nm_clear_g_free (&value);
 
-	flags = read_secret_flags (ifcfg, "IEEE_8021X_PASSWORD_FLAGS");
+	flags = _secret_read_ifcfg_flags (ifcfg, "IEEE_8021X_PASSWORD_FLAGS");
 	g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, flags, NULL);
 
 	/* Read LEAP password if it's system-owned */
@@ -3674,7 +3816,7 @@ transform_hwaddr_blacklist (const char *blacklist)
 	const char **strv;
 	gsize i, j;
 
-	strv = nm_utils_strsplit_set (blacklist, " \t");
+	strv = nm_utils_strsplit_set (blacklist, " \t", FALSE);
 	if (!strv)
 		return NULL;
 	for (i = 0, j = 0; strv[j]; j++) {
@@ -3923,7 +4065,7 @@ wireless_connection_from_ifcfg (const char *file,
 	NMSetting8021x *s_8021x = NULL;
 	GBytes *ssid;
 	NMSetting *security_setting = NULL;
-	char *printable_ssid = NULL;
+	gs_free char *ssid_utf8 = NULL;
 	const char *mode;
 	gboolean adhoc = FALSE;
 	GError *local = NULL;
@@ -3943,12 +4085,6 @@ wireless_connection_from_ifcfg (const char *file,
 	nm_connection_add_setting (connection, wireless_setting);
 
 	ssid = nm_setting_wireless_get_ssid (NM_SETTING_WIRELESS (wireless_setting));
-	if (ssid) {
-		printable_ssid = nm_utils_ssid_to_utf8 (g_bytes_get_data (ssid, NULL),
-		                                        g_bytes_get_size (ssid));
-	} else
-		printable_ssid = g_strdup ("unmanaged");
-
 	mode = nm_setting_wireless_get_mode (NM_SETTING_WIRELESS (wireless_setting));
 	if (mode && !strcmp (mode, "adhoc"))
 		adhoc = TRUE;
@@ -3956,7 +4092,6 @@ wireless_connection_from_ifcfg (const char *file,
 	/* Wireless security */
 	security_setting = make_wireless_security_setting (ifcfg, file, ssid, adhoc, &s_8021x, &local);
 	if (local) {
-		g_free (printable_ssid);
 		g_object_unref (connection);
 		g_propagate_error (error, local);
 		return NULL;
@@ -3967,11 +4102,16 @@ wireless_connection_from_ifcfg (const char *file,
 			nm_connection_add_setting (connection, NM_SETTING (s_8021x));
 	}
 
+	if (ssid)
+		ssid_utf8 = _nm_utils_ssid_to_utf8 (ssid);
+
 	/* Connection */
-	con_setting = make_connection_setting (file, ifcfg,
+	con_setting = make_connection_setting (file,
+	                                       ifcfg,
 	                                       NM_SETTING_WIRELESS_SETTING_NAME,
-	                                       printable_ssid, NULL);
-	g_free (printable_ssid);
+	                                       nm_str_not_empty (ssid_utf8) ?: "unmanaged",
+	                                       NULL);
+
 	if (!con_setting) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Failed to create connection setting.");
@@ -3984,199 +4124,253 @@ wireless_connection_from_ifcfg (const char *file,
 }
 
 static void
-parse_ethtool_option_autoneg (const char *value, gboolean *out_autoneg)
+parse_ethtool_option (const char *value,
+                      NMSettingWiredWakeOnLan *out_flags,
+                      char **out_password,
+                      gboolean *out_autoneg,
+                      guint32 *out_speed,
+                      const char **out_duplex,
+                      NMSettingEthtool **out_s_ethtool)
 {
-	if (!value) {
-		PARSE_WARNING ("Auto-negotiation option missing");
+	gs_free const char **words = NULL;
+	guint i;
+
+	words = nm_utils_strsplit_set (value, NULL, FALSE);
+	if (!words)
 		return;
-	}
 
-	if (g_str_equal (value, "off"))
-		*out_autoneg = FALSE;
-	else if (g_str_equal (value, "on"))
-		*out_autoneg = TRUE;
-	else
-		PARSE_WARNING ("Auto-negotiation unknown value: %s", value);
-}
+	if (words[0] && words[0][0] == '-') {
+		/* /sbin/ethtool $opts */
+		if (NM_IN_STRSET (words[0], "-K", "--features", "--offload")) {
+			if (!words[1]) {
+				/* first argument must be the interface name. This is invalid. */
+				return;
+			}
 
-static void
-parse_ethtool_option_speed (const char *value, guint32 *out_speed)
-{
-	if (!value) {
-		PARSE_WARNING ("Speed option missing");
-		return;
-	}
+			if (!*out_s_ethtool)
+				*out_s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ());
 
-	*out_speed =  _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, 0);
-	if (errno)
-		PARSE_WARNING ("Speed value '%s' is invalid", value);
-}
+			for (i = 2; words[i]; ) {
+				const char *opt = words[i];
+				const char *opt_val = words[++i];
+				const NMEthtoolData *d = NULL;
+				NMTernary onoff = NM_TERNARY_DEFAULT;
 
-static void
-parse_ethtool_option_duplex (const char *value, const char **out_duplex)
-{
-	if (!value) {
-		PARSE_WARNING ("Duplex option missing");
-		return;
-	}
+				if (nm_streq0 (opt_val, "on"))
+					onoff = NM_TERNARY_TRUE;
+				else if (nm_streq0 (opt_val, "off"))
+					onoff = NM_TERNARY_FALSE;
 
-	if (g_str_equal (value, "half"))
-		*out_duplex = "half";
-	else if (g_str_equal (value, "full"))
-		*out_duplex = "full";
-	else
-		PARSE_WARNING ("Duplex unknown value: %s", value);
+				d = nms_ifcfg_rh_utils_get_ethtool_by_name (opt);
 
-}
+				if (!d) {
+					if (onoff != NM_TERNARY_DEFAULT) {
+						/* the next value is just the on/off argument. Skip it too. */
+						i++;
+					}
 
-static void
-parse_ethtool_option_wol (const char *value, NMSettingWiredWakeOnLan *out_flags)
-{
-	NMSettingWiredWakeOnLan wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE;
+					/* silently ignore unsupported offloading features. */
+					continue;
+				}
 
-	if (!value) {
-		PARSE_WARNING ("Wake-on-LAN options missing");
+				i++;
+
+				if (onoff == NM_TERNARY_DEFAULT) {
+					PARSE_WARNING ("Expects on/off argument for feature '%s'", opt);
+					continue;
+				}
+
+				nm_setting_ethtool_set_feature (*out_s_ethtool,
+				                                d->optname,
+				                                onoff);
+			}
+		}
 		return;
 	}
 
-	for (; *value; value++) {
-		switch (*value) {
-		case 'p':
-			wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_PHY;
-			break;
-		case 'u':
-			wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST;
-			break;
-		case 'm':
-			wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST;
-			break;
-		case 'b':
-			wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST;
-			break;
-		case 'a':
-			wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_ARP;
-			break;
-		case 'g':
-			wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC;
-			break;
-		case 's':
-			break;
-		case 'd':
-			wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE;
-			break;
-		default:
-			PARSE_WARNING ("unrecognized Wake-on-LAN option '%c'", *value);
+	/* /sbin/ethtool -s ${REALDEVICE} $opts */
+	for (i = 0; words[i]; ) {
+		const char *opt = words[i];
+		const char *opt_val = words[++i];
+
+		if (nm_streq (opt, "autoneg")) {
+			if (!opt_val) {
+				PARSE_WARNING ("Auto-negotiation option missing");
+				break;
+			}
+			i++;
+
+			if (nm_streq (opt_val, "off"))
+				*out_autoneg = FALSE;
+			else if (nm_streq (opt_val, "on"))
+				*out_autoneg = TRUE;
+			else
+				PARSE_WARNING ("Auto-negotiation unknown value: %s", opt_val);
+			continue;
 		}
-	}
 
-	*out_flags = wol_flags;
-}
+		if (nm_streq (opt, "speed")) {
+			guint32 speed;
 
-static void parse_ethtool_option_sopass (const char *value, char **out_password)
-{
-	if (!value) {
-		PARSE_WARNING ("Wake-on-LAN password missing");
-		return;
-	}
+			if (!opt_val) {
+				PARSE_WARNING ("Speed option missing");
+				break;
+			}
+			i++;
 
-	g_clear_pointer (out_password, g_free);
-	if (!nm_utils_hwaddr_valid (value, ETH_ALEN)) {
-		PARSE_WARNING ("Wake-on-LAN password '%s' is invalid", value);
-		return;
-	}
+			speed =  _nm_utils_ascii_str_to_int64 (opt_val, 10, 0, G_MAXUINT32, 0);
+			if (errno == 0)
+				*out_speed = speed;
+			else
+				PARSE_WARNING ("Speed value '%s' is invalid", opt_val);
+			continue;
+		}
 
-	*out_password = g_strdup (value);
-}
+		if (nm_streq (opt, "duplex")) {
+			if (!opt_val) {
+				PARSE_WARNING ("Duplex option missing");
+				break;
+			}
+			i++;
 
-static void
-parse_ethtool_option (const char *value,
-                      NMSettingWiredWakeOnLan *out_flags,
-                      char **out_password,
-                      gboolean *out_autoneg,
-                      guint32 *out_speed,
-                      const char **out_duplex)
-{
-	gs_free const char **words = NULL;
-	const char *const *iter;
-	const char *opt_val, *opt;
+			if (nm_streq (opt_val, "half"))
+				*out_duplex = "half";
+			else if (nm_streq (opt_val, "full"))
+				*out_duplex = "full";
+			else
+				PARSE_WARNING ("Duplex unknown value: %s", opt_val);
+			continue;
+		}
 
-	words = nm_utils_strsplit_set (value, "\t ");
-	if (!words)
-		return;
+		if (nm_streq (opt, "wol")) {
+			NMSettingWiredWakeOnLan wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE;
 
-	iter = words;
-
-	while (iter[0]) {
-		opt = iter++[0];
-		opt_val = iter[0];
-
-		if (nm_streq (opt, "autoneg"))
-			parse_ethtool_option_autoneg (opt_val, out_autoneg);
-		else if (nm_streq (opt, "speed"))
-			parse_ethtool_option_speed (opt_val, out_speed);
-		else if (nm_streq (opt, "duplex"))
-			parse_ethtool_option_duplex (opt_val, out_duplex);
-		else if (nm_streq (opt, "wol"))
-			parse_ethtool_option_wol (opt_val, out_flags);
-		else if (nm_streq (opt, "sopass"))
-			parse_ethtool_option_sopass (opt_val, out_password);
-		else {
-			/* Silently skip unknown options */
+			if (!opt_val) {
+				PARSE_WARNING ("Wake-on-LAN options missing");
+				break;
+			}
+			i++;
+
+			for (; *opt_val; opt_val++) {
+				switch (*opt_val) {
+				case 'p':
+					wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_PHY;
+					break;
+				case 'u':
+					wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST;
+					break;
+				case 'm':
+					wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST;
+					break;
+				case 'b':
+					wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST;
+					break;
+				case 'a':
+					wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_ARP;
+					break;
+				case 'g':
+					wol_flags |= NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC;
+					break;
+				case 's':
+					break;
+				case 'd':
+					wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_NONE;
+					break;
+				default:
+					PARSE_WARNING ("unrecognized Wake-on-LAN option '%c'", *opt_val);
+				}
+			}
+
+			*out_flags = wol_flags;
 			continue;
 		}
 
-		if (iter[0])
-			iter++;
+		if (nm_streq (opt, "sopass")) {
+			if (!opt_val) {
+				PARSE_WARNING ("Wake-on-LAN password missing");
+				break;
+			}
+			i++;
+
+			if (nm_utils_hwaddr_valid (opt_val, ETH_ALEN)) {
+				g_clear_pointer (out_password, g_free);
+				*out_password = g_strdup (opt_val);
+			} else
+				PARSE_WARNING ("Wake-on-LAN password '%s' is invalid", opt_val);
+			continue;
+		}
+
+		/* Silently skip unknown options */
 	}
 }
 
 static void
-parse_ethtool_options (shvarFile *ifcfg, NMSettingWired *s_wired, const char *value)
+parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 {
+	NMSettingWired *s_wired;
+	gs_unref_object NMSettingEthtool *s_ethtool = NULL;
 	NMSettingWiredWakeOnLan wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT;
-	gs_free char *wol_password = NULL, *wol_value = NULL;
-	gboolean ignore_wol_password = FALSE, autoneg = FALSE;
+	gs_free char *ethtool_opts_free = NULL;
+	const char *ethtool_opts;
+	gs_free char *wol_password = NULL;
+	gs_free char *wol_value_free = NULL;
+	const char *tmp;
+	gboolean autoneg = FALSE;
 	guint32 speed = 0;
 	const char *duplex = NULL;
 
-	if (value) {
-		gs_free const char **opts = NULL;
-		const char *const *iter;
-
+	ethtool_opts = svGetValue (ifcfg, "ETHTOOL_OPTS", &ethtool_opts_free);
+	if (ethtool_opts) {
 		/* WAKE_ON_LAN_IGNORE is inferred from a specified but empty ETHTOOL_OPTS */
-		if (!value[0])
+		if (!ethtool_opts[0])
 			wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
-
-		opts = nm_utils_strsplit_set (value, ";");
-		for (iter = opts; iter && iter[0]; iter++) {
-			/* in case of repeated wol_passwords, parse_ethtool_option()
-			 * will do the right thing and clear wol_password before resetting. */
-			parse_ethtool_option (iter[0], &wol_flags, &wol_password, &autoneg, &speed, &duplex);
+		else {
+			gs_free const char **opts = NULL;
+			const char *const *iter;
+
+			opts = nm_utils_strsplit_set (ethtool_opts, ";", FALSE);
+			for (iter = opts; iter && iter[0]; iter++) {
+				/* in case of repeated wol_passwords, parse_ethtool_option()
+				 * will do the right thing and clear wol_password before resetting. */
+				parse_ethtool_option (iter[0],
+				                      &wol_flags,
+				                      &wol_password,
+				                      &autoneg,
+				                      &speed,
+				                      &duplex,
+				                      &s_ethtool);
+			}
 		}
 	}
 
 	/* ETHTOOL_WAKE_ON_LAN = ignore overrides WoL settings in ETHTOOL_OPTS */
-	wol_value = svGetValueStr_cp (ifcfg, "ETHTOOL_WAKE_ON_LAN");
-	if (wol_value) {
-		if (strcmp (wol_value, "ignore") == 0)
-			wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
-		else
-			PARSE_WARNING ("invalid ETHTOOL_WAKE_ON_LAN value '%s'", wol_value);
-	}
+	tmp = svGetValueStr (ifcfg, "ETHTOOL_WAKE_ON_LAN", &wol_value_free);
+	if (nm_streq0 (tmp, "ignore"))
+		wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
+	else if (tmp)
+		PARSE_WARNING ("invalid ETHTOOL_WAKE_ON_LAN value '%s'", tmp);
 
 	if (   wol_password
 	    && !NM_FLAGS_HAS (wol_flags, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) {
 		PARSE_WARNING ("Wake-on-LAN password not expected");
-		ignore_wol_password = TRUE;
+		nm_clear_g_free (&wol_password);
 	}
 
-	g_object_set (s_wired,
-	              NM_SETTING_WIRED_WAKE_ON_LAN, wol_flags,
-	              NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, ignore_wol_password ? NULL : wol_password,
-	              NM_SETTING_WIRED_AUTO_NEGOTIATE, autoneg,
-	              NM_SETTING_WIRED_SPEED, speed,
-	              NM_SETTING_WIRED_DUPLEX, duplex,
-	              NULL);
+	s_wired = nm_connection_get_setting_wired (connection);
+	if (s_wired) {
+		g_object_set (s_wired,
+		              NM_SETTING_WIRED_WAKE_ON_LAN, wol_flags,
+		              NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, wol_password,
+		              NM_SETTING_WIRED_AUTO_NEGOTIATE, autoneg,
+		              NM_SETTING_WIRED_SPEED, speed,
+		              NM_SETTING_WIRED_DUPLEX, duplex,
+		              NULL);
+	}
+
+	if (s_ethtool) {
+		nm_connection_add_setting (connection,
+		                           NM_SETTING (g_steal_pointer (&s_ethtool)));
+	}
 }
 
 static NMSetting *
@@ -4230,7 +4424,7 @@ make_wired_setting (shvarFile *ifcfg,
 			gs_free const char **chans = NULL;
 			guint32 num_chans;
 
-			chans = nm_utils_strsplit_set (value, ",");
+			chans = nm_utils_strsplit_set (value, ",", FALSE);
 			num_chans = NM_PTRARRAY_LEN (chans);
 			if (num_chans < 2 || num_chans > 3) {
 				PARSE_WARNING ("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
@@ -4318,10 +4512,6 @@ make_wired_setting (shvarFile *ifcfg,
 		nm_clear_g_free (&value);
 	}
 
-	parse_ethtool_options (ifcfg, s_wired,
-	                       svGetValue (ifcfg, "ETHTOOL_OPTS", &value));
-	nm_clear_g_free (&value);
-
 	return (NMSetting *) g_steal_pointer (&s_wired);
 }
 
@@ -4557,7 +4747,7 @@ make_bond_setting (shvarFile *ifcfg,
 		gs_free const char **items = NULL;
 		const char *const *iter;
 
-		items = nm_utils_strsplit_set (v, " ");
+		items = nm_utils_strsplit_set (v, " ", FALSE);
 		for (iter = items; iter && *iter; iter++) {
 			gs_strfreev char **keys = NULL;
 			const char *key, *val;
@@ -4835,7 +5025,7 @@ handle_bridging_opts (NMSetting *setting,
 	gs_free const char **items = NULL;
 	const char *const *iter;
 
-	items = nm_utils_strsplit_set (value, " ");
+	items = nm_utils_strsplit_set (value, " ", FALSE);
 	for (iter = items; iter && *iter; iter++) {
 		gs_strfreev char **keys = NULL;
 		const char *key, *val;
@@ -5055,7 +5245,7 @@ parse_prio_map_list (NMSettingVlan *s_vlan,
 	v = svGetValueStr (ifcfg, key, &value);
 	if (!v)
 		return;
-	list = nm_utils_strsplit_set (v, ",");
+	list = nm_utils_strsplit_set (v, ",", FALSE);
 
 	for (iter = list; iter && *iter; iter++) {
 		if (!strchr (*iter, ':'))
@@ -5077,7 +5267,7 @@ make_vlan_setting (shvarFile *ifcfg,
 	const char *v = NULL;
 	int vlan_id = -1;
 	guint32 vlan_flags = 0;
-	gint gvrp, reorder_hdr;
+	int gvrp, reorder_hdr;
 
 	v = svGetValueStr (ifcfg, "VLAN_ID", &value);
 	if (v) {
@@ -5160,7 +5350,7 @@ make_vlan_setting (shvarFile *ifcfg,
 		gs_free const char **strv = NULL;
 		const char *const *ptr;
 
-		strv = nm_utils_strsplit_set (v, ", ");
+		strv = nm_utils_strsplit_set (v, ", ", FALSE);
 		for (ptr = strv; ptr && *ptr; ptr++) {
 			if (nm_streq (*ptr, "GVRP") && gvrp == -1)
 				vlan_flags |= NM_VLAN_FLAG_GVRP;
@@ -5302,7 +5492,7 @@ check_dns_search_domains (shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6)
 			gs_free const char **searches = NULL;
 			const char *const *item;
 
-			searches = nm_utils_strsplit_set (v, " ");
+			searches = nm_utils_strsplit_set (v, " ", FALSE);
 			if (searches) {
 				for (item = searches; *item; item++) {
 					if (!nm_setting_ip_config_add_dns_search (NM_SETTING_IP_CONFIG (s_ip6), *item))
@@ -5321,12 +5511,13 @@ connection_from_file_full (const char *filename,
                            GError **error,
                            gboolean *out_ignore_error)
 {
-	nm_auto_shvar_file_close shvarFile *parsed = NULL;
+	nm_auto_shvar_file_close shvarFile *main_ifcfg = NULL;
 	nm_auto_shvar_file_close shvarFile *network_ifcfg = NULL;
 	gs_unref_object NMConnection *connection = NULL;
 	gs_free char *type = NULL;
 	char *devtype, *bootproto;
 	NMSetting *s_ip4, *s_ip6, *s_tc, *s_proxy, *s_port, *s_dcb = NULL, *s_user;
+	NMSetting *s_sriov, *s_match;
 	const char *ifcfg_name = NULL;
 	gboolean has_ip4_defroute = FALSE;
 	gboolean has_complex_routes_v4;
@@ -5348,14 +5539,14 @@ connection_from_file_full (const char *filename,
 		return NULL;
 	}
 
-	parsed = svOpenFile (filename, error);
-	if (!parsed)
+	main_ifcfg = svOpenFile (filename, error);
+	if (!main_ifcfg)
 		return NULL;
 
 	network_ifcfg = svOpenFile (network_file, NULL);
 
-	if (!svGetValueBoolean (parsed, "NM_CONTROLLED", TRUE)) {
-		connection = create_unhandled_connection (filename, parsed, "unmanaged", out_unhandled);
+	if (!svGetValueBoolean (main_ifcfg, "NM_CONTROLLED", TRUE)) {
+		connection = create_unhandled_connection (filename, main_ifcfg, "unmanaged", out_unhandled);
 		if (!connection) {
 			NM_SET_OUT (out_ignore_error, TRUE);
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
@@ -5365,7 +5556,7 @@ connection_from_file_full (const char *filename,
 	}
 
 	/* iBFT is handled by the iBFT settings plugin */
-	bootproto = svGetValueStr_cp (parsed, "BOOTPROTO");
+	bootproto = svGetValueStr_cp (main_ifcfg, "BOOTPROTO");
 	if (bootproto && !g_ascii_strcasecmp (bootproto, "ibft")) {
 		NM_SET_OUT (out_ignore_error, TRUE);
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -5375,19 +5566,19 @@ connection_from_file_full (const char *filename,
 	}
 	g_free (bootproto);
 
-	devtype = svGetValueStr_cp (parsed, "DEVICETYPE");
+	devtype = svGetValueStr_cp (main_ifcfg, "DEVICETYPE");
 	if (devtype) {
 		if (!strcasecmp (devtype, TYPE_TEAM))
 			type = g_strdup (TYPE_TEAM);
 		else if (!strcasecmp (devtype, TYPE_TEAM_PORT)) {
 			gs_free char *device = NULL;
 
-			type = svGetValueStr_cp (parsed, "TYPE");
-			device = svGetValueStr_cp (parsed, "DEVICE");
+			type = svGetValueStr_cp (main_ifcfg, "TYPE");
+			device = svGetValueStr_cp (main_ifcfg, "DEVICE");
 
 			if (type) {
 				/* nothing to do */
-			} else if (device && is_vlan_device (device, parsed))
+			} else if (device && is_vlan_device (device, main_ifcfg))
 				type = g_strdup (TYPE_VLAN);
 			else
 				type = g_strdup (TYPE_ETHERNET);
@@ -5400,26 +5591,26 @@ connection_from_file_full (const char *filename,
 		/* Team and TeamPort types are also accepted by the mere
 		 * presence of TEAM_CONFIG/TEAM_MASTER. They don't require
 		 * DEVICETYPE. */
-		t = svGetValueStr_cp (parsed, "TEAM_CONFIG");
+		t = svGetValueStr_cp (main_ifcfg, "TEAM_CONFIG");
 		if (t)
 			type = g_strdup (TYPE_TEAM);
 	}
 
 	if (!type)
-		type = svGetValueStr_cp (parsed, "TYPE");
+		type = svGetValueStr_cp (main_ifcfg, "TYPE");
 
 	if (!type) {
 		gs_free char *tmp = NULL;
 		char *device;
 
-		if ((tmp = svGetValueStr_cp (parsed, "IPV6TUNNELIPV4"))) {
+		if ((tmp = svGetValueStr_cp (main_ifcfg, "IPV6TUNNELIPV4"))) {
 			NM_SET_OUT (out_ignore_error, TRUE);
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Ignoring unsupported connection due to IPV6TUNNELIPV4");
 			return NULL;
 		}
 
-		device = svGetValueStr_cp (parsed, "DEVICE");
+		device = svGetValueStr_cp (main_ifcfg, "DEVICE");
 		if (!device) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "File '%s' had neither TYPE nor DEVICE keys.", filename);
@@ -5435,11 +5626,11 @@ connection_from_file_full (const char *filename,
 		}
 
 		if (!test_type) {
-			if (is_bond_device (device, parsed))
+			if (is_bond_device (device, main_ifcfg))
 				type = g_strdup (TYPE_BOND);
-			else if (is_vlan_device (device, parsed))
+			else if (is_vlan_device (device, main_ifcfg))
 				type = g_strdup (TYPE_VLAN);
-			else if (is_wifi_device (device, parsed))
+			else if (is_wifi_device (device, main_ifcfg))
 				type = g_strdup (TYPE_WIRELESS);
 			else {
 				gs_free char *p_path = NULL;
@@ -5505,14 +5696,14 @@ connection_from_file_full (const char *filename,
 	if (nm_streq0 (type, TYPE_ETHERNET)) {
 		gs_free char *bond_options = NULL;
 
-		if (svGetValueStr (parsed, "BONDING_OPTS", &bond_options)) {
+		if (svGetValueStr (main_ifcfg, "BONDING_OPTS", &bond_options)) {
 			/* initscripts consider these as bond masters */
 			g_free (type);
 			type = g_strdup (TYPE_BOND);
 		}
 	}
 
-	if (svGetValueBoolean (parsed, "BONDING_MASTER", FALSE) &&
+	if (svGetValueBoolean (main_ifcfg, "BONDING_MASTER", FALSE) &&
 	    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");
@@ -5521,21 +5712,21 @@ connection_from_file_full (const char *filename,
 
 	/* Construct the connection */
 	if (!strcasecmp (type, TYPE_ETHERNET))
-		connection = wired_connection_from_ifcfg (filename, parsed, error);
+		connection = wired_connection_from_ifcfg (filename, main_ifcfg, error);
 	else if (!strcasecmp (type, TYPE_WIRELESS))
-		connection = wireless_connection_from_ifcfg (filename, parsed, error);
+		connection = wireless_connection_from_ifcfg (filename, main_ifcfg, error);
 	else if (!strcasecmp (type, TYPE_INFINIBAND))
-		connection = infiniband_connection_from_ifcfg (filename, parsed, error);
+		connection = infiniband_connection_from_ifcfg (filename, main_ifcfg, error);
 	else if (!strcasecmp (type, TYPE_BOND))
-		connection = bond_connection_from_ifcfg (filename, parsed, error);
+		connection = bond_connection_from_ifcfg (filename, main_ifcfg, error);
 	else if (!strcasecmp (type, TYPE_TEAM))
-		connection = team_connection_from_ifcfg (filename, parsed, error);
+		connection = team_connection_from_ifcfg (filename, main_ifcfg, error);
 	else if (!strcasecmp (type, TYPE_VLAN))
-		connection = vlan_connection_from_ifcfg (filename, parsed, error);
+		connection = vlan_connection_from_ifcfg (filename, main_ifcfg, error);
 	else if (!strcasecmp (type, TYPE_BRIDGE))
-		connection = bridge_connection_from_ifcfg (filename, parsed, error);
+		connection = bridge_connection_from_ifcfg (filename, main_ifcfg, error);
 	else {
-		connection = create_unhandled_connection (filename, parsed, "unrecognized", out_unhandled);
+		connection = create_unhandled_connection (filename, main_ifcfg, "unrecognized", out_unhandled);
 		if (!connection) {
 			PARSE_WARNING ("connection type was unrecognized but device was not uniquely identified; device may be managed");
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -5547,6 +5738,8 @@ connection_from_file_full (const char *filename,
 	if (!connection)
 		return NULL;
 
+	parse_ethtool_options (main_ifcfg, connection);
+
 	has_complex_routes_v4 = utils_has_complex_routes (filename, AF_INET);
 	has_complex_routes_v6 = utils_has_complex_routes (filename, AF_INET6);
 
@@ -5559,7 +5752,7 @@ connection_from_file_full (const char *filename,
 			PARSE_WARNING ("'rule-' and 'rule6-' files are present; you will need to use a dispatcher script to apply these routes");
 	}
 
-	s_ip6 = make_ip6_setting (parsed,
+	s_ip6 = make_ip6_setting (main_ifcfg,
 	                          network_ifcfg,
 	                          !has_complex_routes_v4 && !has_complex_routes_v6,
 	                          error);
@@ -5568,7 +5761,7 @@ connection_from_file_full (const char *filename,
 	else
 		nm_connection_add_setting (connection, s_ip6);
 
-	s_ip4 = make_ip4_setting (parsed,
+	s_ip4 = make_ip4_setting (main_ifcfg,
 	                          network_ifcfg,
 	                          !has_complex_routes_v4 && !has_complex_routes_v6,
 	                          &has_ip4_defroute,
@@ -5582,7 +5775,11 @@ connection_from_file_full (const char *filename,
 		nm_connection_add_setting (connection, s_ip4);
 	}
 
-	s_tc = make_tc_setting (parsed);
+	s_sriov = make_sriov_setting (main_ifcfg);
+	if (s_sriov)
+		nm_connection_add_setting (connection, s_sriov);
+
+	s_tc = make_tc_setting (main_ifcfg);
 	if (s_tc)
 		nm_connection_add_setting (connection, s_tc);
 
@@ -5590,27 +5787,31 @@ connection_from_file_full (const char *filename,
 	 * config fails for some reason, we read DOMAIN and put the
 	 * values into IPv6 config instead of IPv4.
 	 */
-	check_dns_search_domains (parsed, s_ip4, s_ip6);
+	check_dns_search_domains (main_ifcfg, s_ip4, s_ip6);
 
-	s_proxy = make_proxy_setting (parsed);
+	s_proxy = make_proxy_setting (main_ifcfg);
 	if (s_proxy)
 		nm_connection_add_setting (connection, s_proxy);
 
-	s_user = make_user_setting (parsed);
+	s_user = make_user_setting (main_ifcfg);
 	if (s_user)
 		nm_connection_add_setting (connection, s_user);
 
+	s_match = make_match_setting (main_ifcfg);
+	if (s_match)
+		nm_connection_add_setting (connection, s_match);
+
 	/* Bridge port? */
-	s_port = make_bridge_port_setting (parsed);
+	s_port = make_bridge_port_setting (main_ifcfg);
 	if (s_port)
 		nm_connection_add_setting (connection, s_port);
 
 	/* Team port? */
-	s_port = make_team_port_setting (parsed);
+	s_port = make_team_port_setting (main_ifcfg);
 	if (s_port)
 		nm_connection_add_setting (connection, s_port);
 
-	if (!make_dcb_setting (parsed, &s_dcb, error))
+	if (!make_dcb_setting (main_ifcfg, &s_dcb, error))
 		return NULL;
 	if (s_dcb)
 		nm_connection_add_setting (connection, s_dcb);
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 862e640e..49096d26 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -470,3 +470,104 @@ nms_ifcfg_rh_utils_user_key_decode (const char *name, GString *str_buffer)
 
 	return TRUE;
 }
+
+/*****************************************************************************/
+
+const char *const _nm_ethtool_ifcfg_names[] = {
+#define ETHT_NAME(eid, ename) \
+[eid - _NM_ETHTOOL_ID_FEATURE_FIRST] = ""ename""
+	/* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_ESP_HW_OFFLOAD,               "esp-hw-offload"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_ESP_TX_CSUM_HW_OFFLOAD,       "esp-tx-csum-hw-offload"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_FCOE_MTU,                     "fcoe-mtu"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_GRO,                          "gro"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_GSO,                          "gso"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_HIGHDMA,                      "highdma"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_HW_TC_OFFLOAD,                "hw-tc-offload"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_L2_FWD_OFFLOAD,               "l2-fwd-offload"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_LOOPBACK,                     "loopback"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_LRO,                          "lro"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_NTUPLE,                       "ntuple"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX,                           "rx"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RXHASH,                       "rxhash"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RXVLAN,                       "rxvlan"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX_ALL,                       "rx-all"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX_FCS,                       "rx-fcs"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX_GRO_HW,                    "rx-gro-hw"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX_UDP_TUNNEL_PORT_OFFLOAD,   "rx-udp_tunnel-port-offload"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX_VLAN_FILTER,               "rx-vlan-filter"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_FILTER,          "rx-vlan-stag-filter"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_RX_VLAN_STAG_HW_PARSE,        "rx-vlan-stag-hw-parse"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_SG,                           "sg"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TLS_HW_RECORD,                "tls-hw-record"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TLS_HW_TX_OFFLOAD,            "tls-hw-tx-offload"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TSO,                          "tso"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX,                           "tx"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TXVLAN,                       "txvlan"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_FCOE_CRC,         "tx-checksum-fcoe-crc"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV4,             "tx-checksum-ipv4"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IPV6,             "tx-checksum-ipv6"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_IP_GENERIC,       "tx-checksum-ip-generic"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_CHECKSUM_SCTP,             "tx-checksum-sctp"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_ESP_SEGMENTATION,          "tx-esp-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_FCOE_SEGMENTATION,         "tx-fcoe-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_GRE_CSUM_SEGMENTATION,     "tx-gre-csum-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_GRE_SEGMENTATION,          "tx-gre-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_GSO_PARTIAL,               "tx-gso-partial"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_GSO_ROBUST,                "tx-gso-robust"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_IPXIP4_SEGMENTATION,       "tx-ipxip4-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_IPXIP6_SEGMENTATION,       "tx-ipxip6-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_NOCACHE_COPY,              "tx-nocache-copy"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER,            "tx-scatter-gather"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_SCATTER_GATHER_FRAGLIST,   "tx-scatter-gather-fraglist"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_SCTP_SEGMENTATION,         "tx-sctp-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION,         "tx-tcp6-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_TCP_ECN_SEGMENTATION,      "tx-tcp-ecn-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_TCP_MANGLEID_SEGMENTATION, "tx-tcp-mangleid-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_TCP_SEGMENTATION,          "tx-tcp-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_UDP_SEGMENTATION,          "tx-udp-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_CSUM_SEGMENTATION, "tx-udp_tnl-csum-segmentation"),
+	ETHT_NAME (NM_ETHTOOL_ID_FEATURE_TX_UDP_TNL_SEGMENTATION,      "tx-udp_tnl-segmentation"),
+	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];
+	}
+
+	/* Option not found. 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
+	 * for ETH_SS_FEATURES).
+	 *
+	 * 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];
+	}
+
+	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 3756af7c..84c22094 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -22,14 +22,15 @@
 #define _UTILS_H_
 
 #include "nm-connection.h"
+#include "nm-ethtool-utils.h"
 
 #include "shvar.h"
 
 #define NM_IFCFG_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
 #define NM_IFCFG_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
-#define NM_IFCFG_CONNECTION_LOG_ARG(con)    NM_IFCFG_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con))
+#define NM_IFCFG_CONNECTION_LOG_ARG(con)    NM_IFCFG_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con))
 #define NM_IFCFG_CONNECTION_LOG_FMTD        "%s (%s,\"%s\",%p)"
-#define NM_IFCFG_CONNECTION_LOG_ARGD(con)   NM_IFCFG_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con)), (con)
+#define NM_IFCFG_CONNECTION_LOG_ARGD(con)   NM_IFCFG_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con)), (con)
 
 char *utils_cert_path (const char *parent, const char *suffix, const char *extension);
 
@@ -80,4 +81,20 @@ _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)); \
 	})
 
+/*****************************************************************************/
+
+extern const char *const _nm_ethtool_ifcfg_names[_NM_ETHTOOL_ID_FEATURE_NUM];
+
+static inline const char *
+nms_ifcfg_rh_utils_get_ethtool_name (NMEthtoolID ethtool_id)
+{
+	nm_assert (ethtool_id >= _NM_ETHTOOL_ID_FEATURE_FIRST && ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST);
+	nm_assert ((ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST) < G_N_ELEMENTS (_nm_ethtool_ifcfg_names));
+	nm_assert (_nm_ethtool_ifcfg_names[ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST]);
+
+	return _nm_ethtool_ifcfg_names[ethtool_id - _NM_ETHTOOL_ID_FEATURE_FIRST];
+}
+
+const NMEthtoolData *nms_ifcfg_rh_utils_get_ethtool_by_name (const char *name);
+
 #endif  /* _UTILS_H_ */
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 fdb2e10a..e1a2289e 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -33,10 +33,12 @@
 #include <stdio.h>
 
 #include "nm-utils/nm-enum-utils.h"
+#include "nm-utils/nm-io-utils.h"
 #include "nm-manager.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
+#include "nm-setting-ethtool.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-proxy.h"
 #include "nm-setting-ip4-config.h"
@@ -50,6 +52,7 @@
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-meta-setting.h"
+#include "nm-ethtool-utils.h"
 
 #include "nms-ifcfg-rh-common.h"
 #include "nms-ifcfg-rh-reader.h"
@@ -809,7 +812,7 @@ write_wireless_setting (NMConnection *connection,
 	const char *device_mac, *cloned_mac;
 	guint32 mtu, chan, i;
 	gboolean adhoc = FALSE, hex_ssid = FALSE;
-	const char * const *macaddr_blacklist;
+	const char *const*macaddr_blacklist;
 
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	if (!s_wireless) {
@@ -1041,16 +1044,9 @@ static gboolean
 write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
 	NMSettingWired *s_wired;
-	const char *device_mac, *cloned_mac;
-	char *tmp;
-	const char *nettype, *portname, *ctcprot, *s390_key, *s390_val, *duplex;
-	guint32 mtu, num_opts, speed, i;
-	const char *const *s390_subchannels;
-	GString *str = NULL;
-	const char * const *macaddr_blacklist;
-	gboolean auto_negotiate;
-	NMSettingWiredWakeOnLan wol;
-	const char *wol_password;
+	const char *const*s390_subchannels;
+	guint32 mtu, num_opts, i;
+	const char *const*macaddr_blacklist;
 
 	s_wired = nm_connection_get_setting_wired (connection);
 	if (!s_wired) {
@@ -1059,144 +1055,211 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 		return FALSE;
 	}
 
-	device_mac = nm_setting_wired_get_mac_address (s_wired);
-	svSetValueStr (ifcfg, "HWADDR", device_mac);
+	svSetValueStr (ifcfg, "HWADDR",
+	               nm_setting_wired_get_mac_address (s_wired));
 
-	cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
-	svSetValueStr (ifcfg, "MACADDR", cloned_mac);
+	svSetValueStr (ifcfg, "MACADDR",
+	               nm_setting_wired_get_cloned_mac_address (s_wired));
 
 	svSetValueStr (ifcfg, "GENERATE_MAC_ADDRESS_MASK",
 	               nm_setting_wired_get_generate_mac_address_mask (s_wired));
 
-	svUnsetValue (ifcfg, "HWADDR_BLACKLIST");
 	macaddr_blacklist = nm_setting_wired_get_mac_address_blacklist (s_wired);
 	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);
-	}
+	} else
+		svUnsetValue (ifcfg, "HWADDR_BLACKLIST");
 
 	mtu = nm_setting_wired_get_mtu (s_wired);
 	svSetValueInt64_cond (ifcfg, "MTU", mtu != 0, mtu);
 
-	svUnsetValue (ifcfg, "SUBCHANNELS");
 	s390_subchannels = nm_setting_wired_get_s390_subchannels (s_wired);
-	if (s390_subchannels) {
-		int len = g_strv_length ((char **)s390_subchannels);
 
-		tmp = NULL;
+	{
+		gs_free char *tmp = NULL;
+		gsize len = NM_PTRARRAY_LEN (s390_subchannels);
+
 		if (len == 2) {
-			tmp = g_strdup_printf ("%s,%s", s390_subchannels[0], s390_subchannels[1]);
+			tmp = g_strdup_printf ("%s,%s",
+			                       s390_subchannels[0],
+			                       s390_subchannels[1]);
 		} else if (len == 3) {
-			tmp = g_strdup_printf ("%s,%s,%s", s390_subchannels[0], s390_subchannels[1],
+			tmp = g_strdup_printf ("%s,%s,%s",
+			                       s390_subchannels[0],
+			                       s390_subchannels[1],
 			                       s390_subchannels[2]);
 		}
+
 		svSetValueStr (ifcfg, "SUBCHANNELS", tmp);
-		g_free (tmp);
 	}
 
-	svUnsetValue (ifcfg, "NETTYPE");
-	nettype = nm_setting_wired_get_s390_nettype (s_wired);
-	if (nettype)
-		svSetValueStr (ifcfg, "NETTYPE", nettype);
+	svSetValueStr (ifcfg, "NETTYPE",
+	               nm_setting_wired_get_s390_nettype (s_wired));
 
-	svUnsetValue (ifcfg, "PORTNAME");
-	portname = nm_setting_wired_get_s390_option_by_key (s_wired, "portname");
-	if (portname)
-		svSetValueStr (ifcfg, "PORTNAME", portname);
+	svSetValueStr (ifcfg, "PORTNAME",
+	               nm_setting_wired_get_s390_option_by_key (s_wired, "portname"));
 
-	svUnsetValue (ifcfg, "CTCPROT");
-	ctcprot = nm_setting_wired_get_s390_option_by_key (s_wired, "ctcprot");
-	if (ctcprot)
-		svSetValueStr (ifcfg, "CTCPROT", ctcprot);
+	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) {
-		str = g_string_sized_new (30);
+		nm_auto_free_gstring GString *tmp = NULL;
+
 		for (i = 0; i < num_opts; i++) {
+			const char *s390_key, *s390_val;
+
 			nm_setting_wired_get_s390_option (s_wired, i, &s390_key, &s390_val);
 
 			/* portname is handled separately */
 			if (!strcmp (s390_key, "portname") || !strcmp (s390_key, "ctcprot"))
 				continue;
 
-			if (str->len)
-				g_string_append_c (str, ' ');
-			g_string_append_printf (str, "%s=%s", s390_key, s390_val);
+			if (!tmp)
+				tmp = g_string_sized_new (30);
+			else
+				g_string_append_c (tmp, ' ');
+			g_string_append_printf (tmp, "%s=%s", s390_key, s390_val);
 		}
-		if (str->len)
-			svSetValueStr (ifcfg, "OPTIONS", str->str);
-		g_string_free (str, TRUE);
+		if (tmp)
+			svSetValueStr (ifcfg, "OPTIONS", tmp->str);
 	}
 
-	/* Stuff ETHTOOL_OPT with required options */
-	str = NULL;
-	auto_negotiate = nm_setting_wired_get_auto_negotiate (s_wired);
-	speed = nm_setting_wired_get_speed (s_wired);
-	duplex = nm_setting_wired_get_duplex (s_wired);
+	svSetValueStr (ifcfg, "TYPE", TYPE_ETHERNET);
 
-	/* autoneg off + speed 0 + duplex NULL, means we want NM
-	 * to skip link configuration which is default. So write
-	 * down link config only if we have auto-negotiate true or
-	 * a valid value for one among speed and duplex.
-	 */
-	if (auto_negotiate) {
-		str = g_string_sized_new (64);
-		g_string_printf (str, "autoneg on");
-	} else if (speed || duplex) {
-		str = g_string_sized_new (64);
-		g_string_printf (str, "autoneg off");
+	return TRUE;
+}
+
+static gboolean
+write_ethtool_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+	NMSettingWired *s_wired;
+	NMSettingEthtool *s_ethtool;
+	const char *duplex;
+	guint32 speed;
+	GString *str = NULL;
+	gboolean auto_negotiate;
+	NMSettingWiredWakeOnLan wol;
+	const char *wol_password;
+
+	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");
+		return TRUE;
 	}
-	if (speed)
-		g_string_append_printf (str, " speed %u", speed);
-	if (duplex)
-		g_string_append_printf (str, " duplex %s", duplex);
 
-	wol = nm_setting_wired_get_wake_on_lan (s_wired);
-	wol_password = nm_setting_wired_get_wake_on_lan_password (s_wired);
+	if (s_wired) {
+		auto_negotiate = nm_setting_wired_get_auto_negotiate (s_wired);
+		speed = nm_setting_wired_get_speed (s_wired);
+		duplex = nm_setting_wired_get_duplex (s_wired);
+
+		/* autoneg off + speed 0 + duplex NULL, means we want NM
+		 * to skip link configuration which is default. So write
+		 * down link config only if we have auto-negotiate true or
+		 * a valid value for one among speed and duplex.
+		 */
+		if (auto_negotiate) {
+			str = g_string_sized_new (64);
+			g_string_printf (str, "autoneg on");
+		} else if (speed || duplex) {
+			str = g_string_sized_new (64);
+			g_string_printf (str, "autoneg off");
+		}
+		if (speed)
+			g_string_append_printf (str, " speed %u", speed);
+		if (duplex)
+			g_string_append_printf (str, " duplex %s", duplex);
+
+		wol = nm_setting_wired_get_wake_on_lan (s_wired);
+		wol_password = nm_setting_wired_get_wake_on_lan_password (s_wired);
+
+		svSetValue (ifcfg, "ETHTOOL_WAKE_ON_LAN",
+		              wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE
+		            ? "ignore"
+		            : NULL);
+		if (!NM_IN_SET (wol, NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE,
+		                     NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT)) {
+			if (!str)
+				str = g_string_sized_new (30);
+			else
+				g_string_append (str, " ");
+
+			g_string_append (str, "wol ");
+
+			if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
+				g_string_append (str, "p");
+			if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
+				g_string_append (str, "u");
+			if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST))
+				g_string_append (str, "m");
+			if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST))
+				g_string_append (str, "b");
+			if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_ARP))
+				g_string_append (str, "a");
+			if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
+				g_string_append (str, "g");
+
+			if (!NM_FLAGS_ANY (wol, NM_SETTING_WIRED_WAKE_ON_LAN_ALL))
+				g_string_append (str, "d");
+
+			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;
+		NMSettingConnection *s_con;
+		const char *iface = NULL;
+
+		s_con = nm_connection_get_setting_connection (connection);
+		if (s_con) {
+			iface = nm_setting_connection_get_interface_name (s_con);
+			if (   iface
+			    && (   !iface[0]
+			        || !NM_STRCHAR_ALL (iface, ch,    (ch >= 'a' && ch <= 'z')
+			                                       || (ch >= 'A' && ch <= 'Z')
+			                                       || (ch >= '0' && ch <= '9')
+			                                       || NM_IN_SET (ch, '_'))))
+				iface = NULL;
+		}
 
-	if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)
-		svSetValue (ifcfg, "ETHTOOL_WAKE_ON_LAN", "ignore");
-	else if (wol == NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT) {
-		if (!str)
-			svUnsetValue (ifcfg, "ETHTOOL_OPTS");
-	} else {
 		if (!str)
 			str = g_string_sized_new (30);
 		else
-			g_string_append (str, " ");
+			g_string_append (str, " ; ");
+		g_string_append (str, "-K ");
+		g_string_append (str, iface ?: "net0");
 
-		g_string_append (str, "wol ");
+		for (ethtool_id = _NM_ETHTOOL_ID_FEATURE_FIRST; ethtool_id <= _NM_ETHTOOL_ID_FEATURE_LAST; ethtool_id++) {
+			const NMEthtoolData *ed = nm_ethtool_data[ethtool_id];
+			NMTernary val;
 
-		if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
-			g_string_append (str, "p");
-		if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
-			g_string_append (str, "u");
-		if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST))
-			g_string_append (str, "m");
-		if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST))
-			g_string_append (str, "b");
-		if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_ARP))
-			g_string_append (str, "a");
-		if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
-			g_string_append (str, "g");
+			nm_assert (nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
 
-		if (!NM_FLAGS_ANY (wol, NM_SETTING_WIRED_WAKE_ON_LAN_ALL))
-			g_string_append (str, "d");
+			val = nm_setting_ethtool_get_feature (s_ethtool, ed->optname);
+			if (val == NM_TERNARY_DEFAULT)
+				continue;
 
-		if (wol_password && NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC))
-			g_string_append_printf (str, "s sopass %s", wol_password);
+			g_string_append_c (str, ' ');
+			g_string_append (str, nms_ifcfg_rh_utils_get_ethtool_name (ethtool_id));
+			g_string_append (str, val == NM_TERNARY_TRUE ? " on" : " off");
+		}
 	}
+
 	if (str) {
 		svSetValueStr (ifcfg, "ETHTOOL_OPTS", str->str);
 		g_string_free (str, TRUE);
-	}
-	/* End ETHTOOL_OPT stuffing */
-
-	svSetValueStr (ifcfg, "TYPE", TYPE_ETHERNET);
+	} else
+		svUnsetValue (ifcfg, "ETHTOOL_OPTS");
 
 	return TRUE;
 }
@@ -1555,7 +1618,7 @@ static void
 write_dcb_app (shvarFile *ifcfg,
                const char *tag,
                NMSettingDcbFlags flags,
-               gint priority)
+               int priority)
 {
 	char prop[NM_STRLEN ("DCB_xxxxxxxxxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyy")];
 
@@ -1725,8 +1788,9 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	guint32 n, i;
 	GString *str;
 	const char *master, *master_iface = NULL, *type;
-	gint vint;
+	int vint;
 	NMSettingConnectionMdns mdns;
+	NMSettingConnectionLlmnr llmnr;
 	guint32 vuint32;
 	const char *tmp;
 
@@ -1746,6 +1810,11 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	                      vint != -1,
 	                      vint);
 
+	vint = nm_setting_connection_get_multi_connect (s_con);
+	svSetValueInt64_cond (ifcfg, "MULTI_CONNECT",
+	                      vint != NM_CONNECTION_MULTI_CONNECT_DEFAULT,
+	                      vint);
+
 	/* Only save the value for master connections */
 	type = nm_setting_connection_get_connection_type (s_con);
 	if (_nm_connection_type_is_master (type)) {
@@ -1892,6 +1961,13 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 		                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 *
@@ -2135,6 +2211,45 @@ write_user_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	return TRUE;
 }
 
+static void
+write_sriov_setting (NMConnection *connection, shvarFile *ifcfg)
+{
+	NMSettingSriov *s_sriov;
+	guint i, num = 0;
+	NMTernary b;
+	NMSriovVF *vf;
+	char key[32];
+	char *str;
+
+	svUnsetAll (ifcfg, SV_KEY_TYPE_SRIOV_VF);
+
+	s_sriov = nm_connection_get_setting_sriov (connection);
+	if (s_sriov)
+		num = nm_setting_sriov_get_total_vfs (s_sriov);
+	if (num == 0) {
+		svUnsetValue (ifcfg, "SRIOV_TOTAL_VFS");
+		svUnsetValue (ifcfg, "SRIOV_AUTOPROBE_DRIVERS");
+		return;
+	}
+
+	svSetValueInt64 (ifcfg, "SRIOV_TOTAL_VFS", num);
+
+	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++) {
+		vf = nm_setting_sriov_get_vf (s_sriov, i);
+		nm_sprintf_buf (key, "SRIOV_VF%u", nm_sriov_vf_get_index (vf));
+		str = nm_utils_sriov_vf_to_str (vf, TRUE, NULL);
+		svSetValueStr (ifcfg, key, str);
+		g_free (str);
+	}
+}
+
 static gboolean
 write_tc_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
@@ -2179,6 +2294,38 @@ write_tc_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	return TRUE;
 }
 
+static gboolean
+write_match_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+{
+	NMSettingMatch *s_match;
+	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;
+
+	num = nm_setting_match_get_num_interface_names (s_match);
+	for (i = 0; i < num; i++) {
+		gs_free char *to_free = NULL;
+		const char *name;
+
+		if (i == 0)
+			str = g_string_new ("");
+		else
+			g_string_append_c (str, ' ');
+		name = nm_setting_match_get_interface_name (s_match, i);
+		g_string_append (str, _nm_utils_escape_spaces (name, &to_free));
+	}
+
+	if (str)
+		svSetValueStr (ifcfg, "MATCH_INTERFACE_NAME", str->str);
+
+	return TRUE;
+}
+
 static void
 write_res_options (shvarFile *ifcfg, NMSettingIPConfig *s_ip, const char *var)
 {
@@ -2212,11 +2359,11 @@ write_ip4_setting (NMConnection *connection,
 	const char *value;
 	char *tmp;
 	char tag[64];
-	gint j;
+	int j;
 	guint i, num, n;
 	gint64 route_metric;
 	NMIPRouteTableSyncMode route_table;
-	gint priority;
+	int priority;
 	int timeout;
 	GString *searches;
 	const char *method = NULL;
@@ -2548,7 +2695,7 @@ write_ip6_setting (NMConnection *connection,
 	NMSettingIPConfig *s_ip4;
 	const char *value;
 	guint i, num, num4;
-	gint priority;
+	int priority;
 	NMIPAddress *addr;
 	const char *dns;
 	gint64 route_metric;
@@ -2918,9 +3065,17 @@ do_write_construct (NMConnection *connection,
 	if (!write_proxy_setting (connection, ifcfg, error))
 		return FALSE;
 
+	if (!write_ethtool_setting (connection, ifcfg, error))
+		return FALSE;
+
 	if (!write_user_setting (connection, ifcfg, error))
 		return FALSE;
 
+	if (!write_match_setting (connection, ifcfg, error))
+		return FALSE;
+
+	write_sriov_setting (connection, ifcfg);
+
 	if (!write_tc_setting (connection, ifcfg, error))
 		return FALSE;
 
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 9120b870..fe82fbdd 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -39,6 +39,7 @@
 #include "nm-core-internal.h"
 #include "nm-core-utils.h"
 #include "nm-utils/nm-enum-utils.h"
+#include "nm-utils/nm-io-utils.h"
 #include "c-list/src/c-list.h"
 
 /*****************************************************************************/
@@ -90,8 +91,8 @@ struct _shvarFile {
  *
  * Returns: the parsed boolean value or @fallback.
  */
-gint
-svParseBoolean (const char *value, gint fallback)
+int
+svParseBoolean (const char *value, int fallback)
 {
 	if (!value)
 		return fallback;
@@ -148,8 +149,8 @@ static char *
 _escape_ansic (const char *source)
 {
 	const char *p;
-	gchar *dest;
-	gchar *q;
+	char *dest;
+	char *q;
 
 	nm_assert (source);
 
@@ -821,6 +822,7 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 	if (nm_utils_fd_get_contents (closefd ? nm_steal_fd (&fd) : fd,
 	                              closefd,
 	                              10 * 1024 * 1024,
+	                              NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
 	                              &arena,
 	                              NULL,
 	                              &local) < 0) {
@@ -870,8 +872,61 @@ svCreateFile (const char *name)
 
 /*****************************************************************************/
 
+static gboolean
+_is_all_digits (const char *str)
+{
+	return    str[0]
+	       && NM_STRCHAR_ALL (str, ch, g_ascii_isdigit (ch));
+}
+
+#define IS_NUMBERED_TAG(key, tab_name) \
+	({ \
+		const char *_key = (key); \
+		\
+		(   (strncmp (_key, tab_name, NM_STRLEN (tab_name)) == 0) \
+		 && _is_all_digits (&_key[NM_STRLEN (tab_name)])); \
+	})
+
+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"))
+			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"))
+			return TRUE;
+	}
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_USER)) {
+		if (g_str_has_prefix (key, "NM_USER_"))
+			return TRUE;
+	}
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_TC)) {
+		if (   IS_NUMBERED_TAG (key, "QDISC")
+		    || IS_NUMBERED_TAG (key, "FILTER"))
+			return TRUE;
+	}
+	if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_SRIOV_VF)) {
+		if (IS_NUMBERED_TAG (key, "SRIOV_VF"))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
 GHashTable *
-svGetKeys (shvarFile *s)
+svGetKeys (shvarFile *s, SvKeyType match_key_type)
 {
 	GHashTable *keys = NULL;
 	CList *current;
@@ -881,7 +936,9 @@ svGetKeys (shvarFile *s)
 
 	c_list_for_each (current, &s->lst_head) {
 		line = c_list_entry (current, shvarLine, lst);
-		if (line->key && line->line) {
+		if (   line->key
+		    && line->line
+		    && _svKeyMatchesType (line->key, match_key_type)) {
 			/* we don't clone the keys. The keys are only valid
 			 * until @s gets modified. */
 			if (!keys)
@@ -1044,8 +1101,8 @@ svGetValueStr_cp (shvarFile *s, const char *key)
  *
  * Returns: the parsed boolean value or @fallback.
  */
-gint
-svGetValueBoolean (shvarFile *s, const char *key, gint fallback)
+int
+svGetValueBoolean (shvarFile *s, const char *key, int fallback)
 {
 	gs_free char *to_free = NULL;
 	const char *value;
@@ -1120,21 +1177,6 @@ svGetValueEnum (shvarFile *s, const char *key,
 
 /*****************************************************************************/
 
-static gboolean
-_is_all_digits (const char *str)
-{
-	return    str[0]
-	       && NM_STRCHAR_ALL (str, ch, g_ascii_isdigit (ch));
-}
-
-#define IS_NUMBERED_TAG(key, tab_name) \
-	({ \
-		const char *_key = (key); \
-		\
-		(   (strncmp (_key, tab_name, NM_STRLEN (tab_name)) == 0) \
-		 && _is_all_digits (&_key[NM_STRLEN (tab_name)])); \
-	})
-
 gboolean
 svUnsetAll (shvarFile *s, SvKeyType match_key_type)
 {
@@ -1150,38 +1192,11 @@ svUnsetAll (shvarFile *s, SvKeyType match_key_type)
 		if (!line->key)
 			continue;
 
-		if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ANY))
-			goto do_clear;
-		if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_ROUTE_SVFORMAT)) {
-			if (   IS_NUMBERED_TAG (line->key, "ADDRESS")
-			    || IS_NUMBERED_TAG (line->key, "NETMASK")
-			    || IS_NUMBERED_TAG (line->key, "GATEWAY")
-			    || IS_NUMBERED_TAG (line->key, "METRIC")
-			    || IS_NUMBERED_TAG (line->key, "OPTIONS"))
-				goto do_clear;
-		}
-		if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_IP4_ADDRESS)) {
-			if (   IS_NUMBERED_TAG (line->key, "IPADDR")
-			    || IS_NUMBERED_TAG (line->key, "PREFIX")
-			    || IS_NUMBERED_TAG (line->key, "NETMASK")
-			    || IS_NUMBERED_TAG (line->key, "GATEWAY"))
-				goto do_clear;
-		}
-		if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_USER)) {
-			if (g_str_has_prefix (line->key, "NM_USER_"))
-				goto do_clear;
-		}
-		if (NM_FLAGS_HAS (match_key_type, SV_KEY_TYPE_TC)) {
-			if (   IS_NUMBERED_TAG (line->key, "QDISC")
-			    || IS_NUMBERED_TAG (line->key, "FILTER"))
-				goto do_clear;
-		}
-
-		continue;
-do_clear:
-		if (nm_clear_g_free (&line->line)) {
-			ASSERT_shvarLine (line);
-			changed = TRUE;
+		if (_svKeyMatchesType (line->key, match_key_type)) {
+			if (nm_clear_g_free (&line->line)) {
+				ASSERT_shvarLine (line);
+				changed = TRUE;
+			}
 		}
 	}
 
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index dbc4d950..622bb474 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -33,6 +33,15 @@
 
 typedef struct _shvarFile shvarFile;
 
+typedef enum {
+	SV_KEY_TYPE_ANY                     = (1LL << 0),
+	SV_KEY_TYPE_ROUTE_SVFORMAT          = (1LL << 1),
+	SV_KEY_TYPE_IP4_ADDRESS             = (1LL << 2),
+	SV_KEY_TYPE_TC                      = (1LL << 3),
+	SV_KEY_TYPE_USER                    = (1LL << 4),
+	SV_KEY_TYPE_SRIOV_VF                = (1LL << 5),
+} SvKeyType;
+
 const char *svFileGetName (const shvarFile *s);
 
 void _nmtst_svFileSetName (shvarFile *s, const char *fileName);
@@ -56,15 +65,15 @@ char *svGetValue_cp (shvarFile *s, const char *key);
 const char *svGetValueStr (shvarFile *s, const char *key, char **to_free);
 char *svGetValueStr_cp (shvarFile *s, const char *key);
 
-gint svParseBoolean (const char *value, gint def);
+int svParseBoolean (const char *value, int def);
 
-GHashTable *svGetKeys (shvarFile *s);
+GHashTable *svGetKeys (shvarFile *s, SvKeyType match_key_type);
 
 /* return TRUE if <key> resolves to any truth value (e.g. "yes", "y", "true")
  * return FALSE if <key> resolves to any non-truth value (e.g. "no", "n", "false")
  * return <def> otherwise
  */
-gint svGetValueBoolean (shvarFile *s, const char *key, gint def);
+int svGetValueBoolean (shvarFile *s, const char *key, int def);
 
 gint64 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback);
 
@@ -85,15 +94,6 @@ gboolean svSetValueInt64_cond (shvarFile *s, const char *key, gboolean do_set, g
 gboolean svSetValueEnum (shvarFile *s, const char *key, GType gtype, int value);
 
 gboolean svUnsetValue (shvarFile *s, const char *key);
-
-typedef enum {
-	SV_KEY_TYPE_ANY                     = (1LL << 0),
-	SV_KEY_TYPE_ROUTE_SVFORMAT          = (1LL << 1),
-	SV_KEY_TYPE_IP4_ADDRESS             = (1LL << 2),
-	SV_KEY_TYPE_TC                      = (1LL << 3),
-	SV_KEY_TYPE_USER                    = (1LL << 4),
-} SvKeyType;
-
 gboolean svUnsetAll (shvarFile *s, SvKeyType match_key_type);
 
 /* Write the current contents iff modified.  Returns FALSE on error
diff --git a/src/settings/plugins/ifcfg-rh/tests/meson.build b/src/settings/plugins/ifcfg-rh/tests/meson.build
index 3d72d892..0593f12d 100644
--- a/src/settings/plugins/ifcfg-rh/tests/meson.build
+++ b/src/settings/plugins/ifcfg-rh/tests/meson.build
@@ -6,7 +6,6 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  c_args: nm_build_cflags,
   link_with: libnms_ifcfg_rh_core
 )
 
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
index 8f421cfb..214f5da5 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Auto-Negotiate.cexpected
@@ -1,7 +1,7 @@
-ETHTOOL_OPTS="autoneg off speed 10 duplex half"
 TYPE=Ethernet
 PROXY_METHOD=none
 BROWSER_ONLY=no
+ETHTOOL_OPTS="autoneg off speed 10 duplex half"
 BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
index 398a3017..de66dcdd 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Wake-on-LAN.cexpected
@@ -1,7 +1,7 @@
-ETHTOOL_OPTS="wol umgs sopass 00:00:00:11:22:33"
 TYPE=Ethernet
 PROXY_METHOD=none
 BROWSER_ONLY=no
+ETHTOOL_OPTS="wol umgs sopass 00:00:00:11:22:33"
 BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
new file mode 100644
index 00000000..f0cd36cb
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
@@ -0,0 +1,11 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+MATCH_INTERFACE_NAME="ens* eth\\ 1? !veth*"
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write Wired with Match setting"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov
new file mode 100644
index 00000000..142f56e4
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov
@@ -0,0 +1,19 @@
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:55
+BOOTPROTO=none
+ONBOOT=yes
+DNS1=4.2.2.1
+DNS2=4.2.2.2
+IPADDR=192.168.1.5
+PREFIX=24
+NETMASK=255.255.255.0
+GATEWAY=192.168.1.1
+IPV6INIT=no
+NAME=ethernet-sriov
+UUID=acc703b8-e751-44ce-b456-1550bdf2057e
+SRIOV_TOTAL_VFS=16
+SRIOV_AUTOPROBE_DRIVERS=0
+SRIOV_VF15="max-tx-rate=200 mac=01:23:45:67:89:ab vlans=2"
+SRIOV_VF12="trust=false min-tx-rate=100  vlans=1.200.ad"
+SRIOV_VF3="mac=55:44:33:22:11:00 spoof-check=true"
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
new file mode 100644
index 00000000..c882c479
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
@@ -0,0 +1,18 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+SRIOV_TOTAL_VFS=64
+SRIOV_AUTOPROBE_DRIVERS=1
+SRIOV_VF2="mac=55:55:55:55:55:55 vlans=3.10.ad;10"
+SRIOV_VF19=spoof-check=true
+BOOTPROTO=none
+IPADDR=1.1.1.3
+PREFIX=24
+GATEWAY=1.1.1.1
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=no
+NAME="Test Write SR-IOV config"
+UUID=${UUID}
+DEVICE=eth0
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected
new file mode 100644
index 00000000..42608576
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test_write_wired_auto_negotiate_on.cexpected
@@ -0,0 +1,15 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+ETHTOOL_OPTS="autoneg on ; -K net0 rxvlan off tx on"
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6INIT=yes
+IPV6_AUTOCONF=yes
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
+NAME="Test Write Wired Auto-Negotiate"
+UUID=${UUID}
+ONBOOT=yes
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 1331391d..70693861 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -43,12 +43,14 @@
 #include "nm-setting-pppoe.h"
 #include "nm-setting-ppp.h"
 #include "nm-setting-vpn.h"
+#include "nm-setting-ethtool.h"
 #include "nm-setting-gsm.h"
 #include "nm-setting-cdma.h"
 #include "nm-setting-serial.h"
 #include "nm-setting-vlan.h"
 #include "nm-setting-dcb.h"
 #include "nm-core-internal.h"
+#include "nm-ethtool-utils.h"
 
 #include "NetworkManagerUtils.h"
 
@@ -185,7 +187,8 @@ _assert_expected_content (NMConnection *connection, const char *filename, const
 
 	if (   len_expectd != len_written
 	    || memcmp (content_expectd, content_written, len_expectd) != 0) {
-		if (g_getenv ("NMTST_IFCFG_RH_UPDATE_EXPECTED")) {
+		if (   g_getenv ("NMTST_IFCFG_RH_UPDATE_EXPECTED")
+		    || nm_streq0 (g_getenv ("NM_TEST_REGENERATE"), "1")) {
 			if (uuid) {
 				gs_free char *search = g_strdup_printf ("UUID=%s\n", uuid);
 				const char *s;
@@ -1898,10 +1901,8 @@ test_read_write_802_1X_subj_matches (void)
 	gs_unref_object NMConnection *reread = NULL;
 	NMSetting8021x *s_8021x;
 
-	NMTST_EXPECT_NM_WARN ("*missing IEEE_8021X_CA_CERT*peap*");
 	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-wired-802-1X-subj-matches",
 	                                    NULL, TYPE_ETHERNET, NULL);
-	g_test_assert_expected_messages ();
 
 	/* ===== 802.1x SETTING ===== */
 	s_8021x = nm_connection_get_setting_802_1x (connection);
@@ -1919,16 +1920,12 @@ test_read_write_802_1X_subj_matches (void)
 	g_assert_cmpstr (nm_setting_802_1x_get_phase2_altsubject_match (s_8021x, 0), ==, "x.yourdomain.tld");
 	g_assert_cmpstr (nm_setting_802_1x_get_phase2_altsubject_match (s_8021x, 1), ==, "y.yourdomain.tld");
 
-	NMTST_EXPECT_NM_WARN ("*missing IEEE_8021X_CA_CERT for EAP method 'peap'; this is insecure!");
 	_writer_new_connec_exp (connection,
 	                        TEST_SCRATCH_DIR,
 	                        TEST_IFCFG_DIR"/ifcfg-System_test-wired-802-1X-subj-matches.cexpected",
 	                        &testfile);
-	g_test_assert_expected_messages ();
 
-	NMTST_EXPECT_NM_WARN ("*missing IEEE_8021X_CA_CERT for EAP method 'peap'; this is insecure!");
 	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
-	g_test_assert_expected_messages ();
 
 	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
 
@@ -3117,8 +3114,7 @@ test_read_wifi_wpa_psk_hex (void)
 
 	ssid = nm_setting_wireless_get_ssid (s_wireless);
 	g_assert (ssid);
-	g_assert_cmpint (g_bytes_get_size (ssid), ==, strlen (expected_ssid));
-	g_assert (memcmp (g_bytes_get_data (ssid, NULL), expected_ssid, strlen (expected_ssid)) == 0);
+	g_assert (nm_utils_gbytes_equal_mem (ssid, expected_ssid, strlen (expected_ssid)));
 
 	/* ===== WIRELESS SECURITY SETTING ===== */
 
@@ -3725,6 +3721,7 @@ test_write_wired_auto_negotiate_on (void)
 	gs_unref_object NMConnection *connection = NULL;
 	gs_unref_object NMConnection *reread = NULL;
 	NMSettingWired *s_wired;
+	NMSettingEthtool *s_ethtool;
 	char *val;
 	shvarFile *f;
 
@@ -3734,8 +3731,14 @@ test_write_wired_auto_negotiate_on (void)
 	              NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE,
 	              NULL);
 
-	_writer_new_connection (connection,
+	s_ethtool = NM_SETTING_ETHTOOL (nm_setting_ethtool_new ());
+	nm_setting_ethtool_set_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX, NM_TERNARY_TRUE);
+	nm_setting_ethtool_set_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN, NM_TERNARY_FALSE);
+	nm_connection_add_setting (connection, NM_SETTING (s_ethtool));
+
+	_writer_new_connec_exp (connection,
 	                        TEST_SCRATCH_DIR,
+	                        TEST_IFCFG_DIR"/ifcfg-test_write_wired_auto_negotiate_on.cexpected",
 	                        &testfile);
 
 	f = _svOpenFile (testfile);
@@ -3749,7 +3752,15 @@ test_write_wired_auto_negotiate_on (void)
 
 	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
 
+	nmtst_assert_connection_verifies_without_normalization (reread);
+
 	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+
+	s_ethtool = NM_SETTING_ETHTOOL (nm_connection_get_setting (reread, NM_TYPE_SETTING_ETHTOOL));
+	g_assert (s_ethtool);
+	g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX), ==, NM_TERNARY_TRUE);
+	g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN), ==, NM_TERNARY_FALSE);
+	g_assert_cmpint (nm_setting_ethtool_get_feature (s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TXVLAN), ==, NM_TERNARY_DEFAULT);
 }
 
 static void
@@ -4417,6 +4428,67 @@ test_write_wired_dhcp (void)
 }
 
 static void
+test_write_wired_match (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWired *s_wired;
+	NMSettingMatch *s_match;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+
+	connection = nm_simple_connection_new ();
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Write Wired with Match setting",
+	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME,
+	              NULL);
+
+	/* Wired setting */
+	s_wired = (NMSettingWired *) nm_setting_wired_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+	              NULL);
+
+	/* IP6 setting */
+	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+
+	g_object_set (s_ip6,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	              NULL);
+
+	/* Match setting */
+	s_match = (NMSettingMatch *) nm_setting_match_new ();
+	nm_setting_match_add_interface_name (s_match, "ens*");
+	nm_setting_match_add_interface_name (s_match, "eth 1?");
+	nm_setting_match_add_interface_name (s_match, "!veth*");
+	nm_connection_add_setting (connection, NM_SETTING (s_match));
+
+	nmtst_assert_connection_verifies (connection);
+	_writer_new_connec_exp (connection,
+	                        TEST_SCRATCH_DIR,
+	                        TEST_IFCFG_DIR"/ifcfg-Test_Write_Wired_match.cexpected",
+	                        &testfile);
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
+static void
 test_write_wired_dhcp_plus_ip (void)
 {
 	nmtst_auto_unlinkfile char *testfile = NULL;
@@ -9632,6 +9704,153 @@ test_utils_ignore (void)
 	do_test_utils_ignored ("ignored-augtmp", "ifcfg-FooBar" AUGTMP_TAG, TRUE);
 }
 
+/*****************************************************************************/
+
+static void
+test_sriov_read (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingSriov *s_sriov;
+	NMSriovVF *vf;
+	GVariant *variant;
+	GError *error = NULL;
+	char *str;
+
+	connection = _connection_from_file (TEST_IFCFG_DIR "/ifcfg-test-sriov",
+	                                    NULL, TYPE_ETHERNET,NULL);
+
+	g_assert_cmpstr (nm_connection_get_interface_name (connection), ==, "eth0");
+
+	s_sriov = nm_connection_get_setting_sriov (connection);
+	g_assert (s_sriov);
+
+	g_assert_cmpint (nm_setting_sriov_get_total_vfs (s_sriov), ==, 16);
+	g_assert_cmpint (nm_setting_sriov_get_num_vfs (s_sriov), ==, 3);
+	g_assert_cmpint (nm_setting_sriov_get_autoprobe_drivers (s_sriov), ==, NM_TERNARY_FALSE);
+
+	/* VF 3 */
+	vf = nm_setting_sriov_get_vf (s_sriov, 0);
+	g_assert (vf);
+	g_assert_cmpint (nm_sriov_vf_get_index (vf), ==, 3);
+
+	variant = nm_sriov_vf_get_attribute (vf, NM_SRIOV_VF_ATTRIBUTE_MAC);
+	g_assert (variant);
+	g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING));
+	g_assert_cmpstr (g_variant_get_string (variant, NULL), ==, "55:44:33:22:11:00");
+
+	variant = nm_sriov_vf_get_attribute (vf, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK);
+	g_assert (variant);
+	g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN));
+	g_assert_cmpint (g_variant_get_boolean (variant), ==, TRUE);
+
+	/* VF 12 */
+	vf = nm_setting_sriov_get_vf (s_sriov, 1);
+	str = nm_utils_sriov_vf_to_str (vf, FALSE, &error);
+	g_assert_no_error (error);
+	g_assert_cmpstr (str, ==, "12 min-tx-rate=100 trust=false vlans=1.200.ad");
+	g_free (str);
+
+	/* VF 15 */
+	vf = nm_setting_sriov_get_vf (s_sriov, 2);
+	str = nm_utils_sriov_vf_to_str (vf, FALSE, &error);
+	g_assert_no_error (error);
+	g_assert_cmpstr (str, ==, "15 mac=01:23:45:67:89:ab max-tx-rate=200 vlans=2");
+	g_free (str);
+}
+
+static void
+test_sriov_write (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+	NMSettingWired *s_wired;
+	NMSettingSriov *s_sriov;
+	NMSriovVF *vf;
+	gs_unref_ptrarray GPtrArray *vfs = NULL;
+	NMIPAddress *addr;
+	GError *error = NULL;
+
+	connection = nm_simple_connection_new ();
+
+	/* Connection setting */
+	s_con = (NMSettingConnection *) nm_setting_connection_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
+
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_ID, "Test Write SR-IOV config",
+	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_INTERFACE_NAME, "eth0",
+	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRED_SETTING_NAME,
+	              NULL);
+
+	/* Wired setting */
+	s_wired = (NMSettingWired *) nm_setting_wired_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
+
+	/* IP4 setting */
+	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+
+	g_object_set (s_ip4,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+	              NM_SETTING_IP_CONFIG_GATEWAY, "1.1.1.1",
+	              NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE,
+	              NULL);
+
+	addr = nm_ip_address_new (AF_INET, "1.1.1.3", 24, &error);
+	g_assert_no_error (error);
+	nm_setting_ip_config_add_address (s_ip4, addr);
+	nm_ip_address_unref (addr);
+
+	/* IP6 setting */
+	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+
+	g_object_set (s_ip6,
+	              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	              NULL);
+
+	/* SRIOV setting */
+	s_sriov = (NMSettingSriov *) nm_setting_sriov_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_sriov));
+
+	vfs = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_sriov_vf_unref);
+
+	vf = nm_utils_sriov_vf_from_str ("2 mac=55:55:55:55:55:55 vlans=3.10.ad;10", &error);
+	nmtst_assert_success (vf, error);
+	g_ptr_array_add (vfs, vf);
+
+	vf = nm_utils_sriov_vf_from_str ("19 spoof-check=true", &error);
+	nmtst_assert_success (vf, error);
+	g_ptr_array_add (vfs, vf);
+
+	g_object_set (s_sriov,
+	              NM_SETTING_SRIOV_TOTAL_VFS, 64,
+	              NM_SETTING_SRIOV_VFS, vfs,
+	              NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, NM_TERNARY_TRUE,
+	              NULL);
+
+	nm_connection_add_setting (connection, nm_setting_proxy_new ());
+
+	nmtst_assert_connection_verifies_without_normalization (connection);
+
+	_writer_new_connec_exp (connection,
+	                        TEST_SCRATCH_DIR,
+	                        TEST_IFCFG_DIR "/ifcfg-test-sriov-write.cexpected",
+	                        &testfile);
+
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
+/*****************************************************************************/
+
 static void
 test_tc_read (void)
 {
@@ -9907,6 +10126,7 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "wired/write/dhcp", test_write_wired_dhcp);
 	g_test_add_func (TPATH "wired/write-dhcp-plus-ip", test_write_wired_dhcp_plus_ip);
 	g_test_add_func (TPATH "wired/write/dhcp-8021x-peap-mschapv2", test_write_wired_dhcp_8021x_peap_mschapv2);
+	g_test_add_func (TPATH "wired/write/match", test_write_wired_match);
 
 #define _add_test_write_wired_8021x_tls(testpath, scheme, flags) \
 	nmtst_add_test_func (testpath, test_write_wired_8021x_tls, GINT_TO_POINTER (scheme), GINT_TO_POINTER (flags))
@@ -10033,6 +10253,9 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "utils/path", test_utils_path);
 	g_test_add_func (TPATH "utils/ignore", test_utils_ignore);
 
+	g_test_add_func (TPATH "sriov/read", test_sriov_read);
+	g_test_add_func (TPATH "sriov/write", test_sriov_write);
+
 	g_test_add_func (TPATH "tc/read", test_tc_read);
 	g_test_add_func (TPATH "tc/write", test_tc_write);
 
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/settings/plugins/ifupdown/meson.build
index fd028f4d..826c7458 100644
--- a/src/settings/plugins/ifupdown/meson.build
+++ b/src/settings/plugins/ifupdown/meson.build
@@ -8,13 +8,10 @@ deps = [
   nm_dep
 ]
 
-cflags = '-DSYSCONFDIR="@0@"'.format(nm_sysconfdir)
-
 libnms_ifupdown_core = static_library(
   'nms-ifupdown-core',
   sources: sources,
   dependencies: deps,
-  c_args: cflags
 )
 
 sources = files(
@@ -26,7 +23,6 @@ libnm_settings_plugin_ifupdown = shared_module(
   'nm-settings-plugin-ifupdown',
   sources: sources,
   dependencies: deps,
-  c_args: cflags,
   link_with: libnms_ifupdown_core,
   link_args: ldflags_linker_script_settings,
   link_depends: linker_script_settings,
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
index c3d231fe..1b817044 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
@@ -49,10 +49,20 @@ G_DEFINE_TYPE (NMIfupdownConnection, nm_ifupdown_connection, NM_TYPE_SETTINGS_CO
 
 /*****************************************************************************/
 
+#define _NMLOG_PREFIX_NAME      "ifupdown"
+#define _NMLOG_DOMAIN           LOGD_SETTINGS
+#define _NMLOG(level, ...) \
+    nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
+            "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+            _NMLOG_PREFIX_NAME": " \
+            _NM_UTILS_MACRO_REST (__VA_ARGS__))
+
+/*****************************************************************************/
+
 static gboolean
 supports_secrets (NMSettingsConnection *connection, const char *setting_name)
 {
-	nm_log_info (LOGD_SETTINGS, "supports_secrets() for setting_name: '%s'", setting_name);
+	_LOGI ("supports_secrets() for setting_name: '%s'", setting_name);
 
 	return (strcmp (setting_name, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME) == 0);
 }
@@ -64,26 +74,27 @@ nm_ifupdown_connection_init (NMIfupdownConnection *connection)
 {
 }
 
-NMIfupdownConnection*
+NMIfupdownConnection *
 nm_ifupdown_connection_new (if_block *block)
 {
-	GObject *object;
+	NMIfupdownConnection *connection;
 	GError *error = NULL;
 
 	g_return_val_if_fail (block != NULL, NULL);
 
-	object = g_object_new (NM_TYPE_IFUPDOWN_CONNECTION, NULL);
+	connection = g_object_new (NM_TYPE_IFUPDOWN_CONNECTION, NULL);
 
-	if (!ifupdown_update_connection_from_if_block (NM_CONNECTION (object), block, &error)) {
-		nm_log_warn (LOGD_SETTINGS, "%s.%d - invalid connection read from /etc/network/interfaces: %s",
-		             __FILE__,
-		             __LINE__,
-		             error->message);
-		g_object_unref (object);
+	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+	if (!ifupdown_update_connection_from_if_block (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection)),
+	                                               block,
+	                                               &error)) {
+		_LOGW ("invalid connection read from /etc/network/interfaces: %s",
+		       error->message);
+		g_object_unref (connection);
 		return NULL;
 	}
 
-	return (NMIfupdownConnection *) object;
+	return connection;
 }
 
 static void
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index 94a65ecb..d926829f 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -32,78 +32,90 @@
 
 #include "nm-utils.h"
 
-if_block* first;
-if_block* last;
+/*****************************************************************************/
 
-if_data* last_data;
+static void _ifparser_source (if_parser *parser, const char *path, const char *en_dir, int quiet, int dir);
 
-void add_block(const char *type, const char* name)
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME      "ifupdown"
+#define _NMLOG_DOMAIN           LOGD_SETTINGS
+#define _NMLOG(level, ...) \
+    nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
+            "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+            _NMLOG_PREFIX_NAME": " \
+            _NM_UTILS_MACRO_REST (__VA_ARGS__))
+
+/*****************************************************************************/
+
+static void
+add_block (if_parser *parser, const char *type, const char* name)
 {
-	if_block *ret = g_slice_new0 (struct _if_block);
-	ret->name = g_strdup(name);
-	ret->type = g_strdup(type);
-	if (first == NULL)
-		first = last = ret;
-	else
-	{
-		last->next = ret;
-		last = ret;
-	}
-	last_data = NULL;
+	if_block *ifb;
+	gsize l_type, l_name;
+
+	l_type = strlen (type) + 1;
+	l_name = strlen (name) + 1;
+
+	ifb = g_malloc (sizeof (if_block) + l_type + l_name);
+	memcpy ((char *) ifb->name, name, l_name);
+	ifb->type = &ifb->name[l_name];
+	memcpy ((char *) ifb->type, type, l_type);
+	c_list_init (&ifb->data_lst_head);
+	c_list_link_tail (&parser->block_lst_head, &ifb->block_lst);
 }
 
-void add_data(const char *key,const char *data)
+static void
+add_data (if_parser *parser, const char *key, const char *data)
 {
-	if_data *ret;
+	if_block *last_block;
+	if_data *ifd;
 	char *idx;
+	gsize l_key, l_data;
+
+	last_block = c_list_last_entry (&parser->block_lst_head, if_block, block_lst);
 
 	/* Check if there is a block where we can attach our data */
-	if (first == NULL)
+	if (!last_block)
 		return;
 
-	ret = g_slice_new0 (struct _if_data);
-	ret->key = g_strdup(key);
+	l_key = strlen (key) + 1;
+	l_data = strlen (data) + 1;
+
+	ifd = g_malloc (sizeof (if_data) + l_key + l_data);
+	memcpy ((char *) ifd->key, key, l_key);
+	ifd->data = &ifd->key[l_key];
+	memcpy ((char *) ifd->data, data, l_data);
 
 	/* Normalize keys. Convert '_' to '-', as ifupdown accepts both variants.
 	 * When querying keys via ifparser_getkey(), use '-'. */
-	while ((idx = strrchr(ret->key, '_'))) {
-		*idx = '-';
-	}
-	ret->data = g_strdup(data);
+	idx = (char *) ifd->key;
+	while ((idx = strchr (idx, '_')))
+		*(idx++) = '-';
 
-	if (last->info == NULL)
-	{
-		last->info = ret;
-		last_data = ret;
-	}
-	else
-	{
-		last_data->next = ret;
-		last_data = last_data->next;
-	}
+	c_list_link_tail (&last_block->data_lst_head, &ifd->data_lst);
 }
 
 /* join values in src with spaces into dst;  dst needs to be large enough */
-static char *join_values_with_spaces(char *dst, char **src)
+static char *
+join_values_with_spaces (char *dst, char **src)
 {
 	if (dst != NULL) {
 		*dst = '\0';
 		if (src != NULL && *src != NULL) {
-			strcat(dst, *src);
+			strcat (dst, *src);
 
 			for (src++; *src != NULL; src++) {
-				strcat(dst, " ");
-				strcat(dst, *src);
+				strcat (dst, " ");
+				strcat (dst, *src);
 			}
 		}
 	}
-	return(dst);
+	return (dst);
 }
 
-static void _ifparser_source (const char *path, const char *en_dir, int quiet, int dir);
-
 static void
-_recursive_ifparser (const char *eni_file, int quiet)
+_recursive_ifparser (if_parser *parser, const char *eni_file, int quiet)
 {
 	FILE *inp;
 	char line[255];
@@ -114,36 +126,35 @@ _recursive_ifparser (const char *eni_file, int quiet)
 	/* Check if interfaces file exists and open it */
 	if (!g_file_test (eni_file, G_FILE_TEST_EXISTS)) {
 		if (!quiet)
-			nm_log_warn (LOGD_SETTINGS, "interfaces file %s doesn't exist\n", eni_file);
+			_LOGW ("interfaces file %s doesn't exist\n", eni_file);
 		return;
 	}
 	inp = fopen (eni_file, "re");
 	if (inp == NULL) {
 		if (!quiet)
-			nm_log_warn (LOGD_SETTINGS, "Can't open %s\n", eni_file);
+			_LOGW ("Can't open %s\n", eni_file);
 		return;
 	}
 	if (!quiet)
-		nm_log_info (LOGD_SETTINGS, "      interface-parser: parsing file %s\n", eni_file);
+		_LOGI ("      interface-parser: parsing file %s\n", eni_file);
 
-	while (!feof(inp))
-	{
+	while (!feof (inp)) {
 		char *token[128]; /* 255 chars can only be split into 127 tokens */
 		char value[255];  /* large enough to join previously split tokens */
 		char *safeptr;
 		int toknum;
 		int len = 0;
 
-		char *ptr = fgets(line+offs, 255-offs, inp);
+		char *ptr = fgets (line+offs, 255-offs, inp);
 		if (ptr == NULL)
 			break;
 
-		len = strlen(line);
+		len = strlen (line);
 		/* skip over-long lines */
-		if (!feof(inp) && len > 0 &&  line[len-1] != '\n') {
+		if (!feof (inp) && len > 0 &&  line[len-1] != '\n') {
 			if (!skip_long_line) {
 				if (!quiet)
-					nm_log_warn (LOGD_SETTINGS, "Skipping over-long-line '%s...'\n", line);
+					_LOGW ("Skipping over-long-line '%s...'\n", line);
 			}
 			skip_long_line = 1;
 			continue;
@@ -170,9 +181,9 @@ _recursive_ifparser (const char *eni_file, int quiet)
 
 #define SPACES " \t"
 		/* tokenize input; */
-		for (toknum = 0, token[toknum] = strtok_r(line, SPACES, &safeptr);
+		for (toknum = 0, token[toknum] = strtok_r (line, SPACES, &safeptr);
 		     token[toknum] != NULL;
-		     toknum++, token[toknum] = strtok_r(NULL, SPACES, &safeptr))
+		     toknum++, token[toknum] = strtok_r (NULL, SPACES, &safeptr))
 			;
 
 		/* ignore comments and empty lines */
@@ -181,8 +192,8 @@ _recursive_ifparser (const char *eni_file, int quiet)
 
 		if (toknum < 2) {
 			if (!quiet) {
-				nm_log_warn (LOGD_SETTINGS, "Can't parse interface line '%s'\n",
-				             join_values_with_spaces(value, token));
+				_LOGW ("Can't parse interface line '%s'\n",
+				       join_values_with_spaces (value, token));
 			}
 			skip_to_block = 1;
 			continue;
@@ -193,71 +204,71 @@ _recursive_ifparser (const char *eni_file, int quiet)
 		 * Create a block for each of them except source and source-directory.  */
 
 		/* iface stanza takes at least 3 parameters */
-		if (strcmp(token[0], "iface") == 0) {
+		if (nm_streq (token[0], "iface")) {
 			if (toknum < 4) {
 				if (!quiet) {
-					nm_log_warn (LOGD_SETTINGS, "Can't parse iface line '%s'\n",
-					             join_values_with_spaces(value, token));
+					_LOGW ("Can't parse iface line '%s'\n",
+					       join_values_with_spaces (value, token));
 				}
 				continue;
 			}
-			add_block(token[0], token[1]);
+			add_block (parser, token[0], token[1]);
 			skip_to_block = 0;
-			add_data(token[2], join_values_with_spaces(value, token + 3));
+			add_data (parser, token[2], join_values_with_spaces (value, token + 3));
 		}
 		/* auto and allow-auto stanzas are equivalent,
 		 * both can take multiple interfaces as parameters: add one block for each */
-		else if (strcmp(token[0], "auto") == 0 ||
-			 strcmp(token[0], "allow-auto") == 0) {
+		else if (NM_IN_STRSET (token[0], "auto", "allow-auto")) {
 			int i;
+
 			for (i = 1; i < toknum; i++)
-				add_block("auto", token[i]);
+				add_block (parser, "auto", token[i]);
 			skip_to_block = 0;
 		}
-		else if (strcmp(token[0], "mapping") == 0) {
-			add_block(token[0], join_values_with_spaces(value, token + 1));
+		else if (nm_streq (token[0], "mapping")) {
+			add_block (parser, token[0], join_values_with_spaces (value, token + 1));
 			skip_to_block = 0;
 		}
 		/* allow-* can take multiple interfaces as parameters: add one block for each */
-		else if (strncmp(token[0],"allow-",6) == 0) {
+		else if (g_str_has_prefix (token[0], "allow-")) {
 			int i;
 			for (i = 1; i < toknum; i++)
-				add_block(token[0], token[i]);
+				add_block (parser, token[0], token[i]);
 			skip_to_block = 0;
 		}
 		/* source and source-directory stanzas take one or more paths as parameters */
-		else if (strcmp (token[0], "source") == 0 || strcmp (token[0], "source-directory") == 0) {
+		else if (NM_IN_STRSET (token[0], "source", "source-directory")) {
 			int i;
 			char *en_dir;
 
 			skip_to_block = 0;
 			en_dir = g_path_get_dirname (eni_file);
 			for (i = 1; i < toknum; ++i) {
-				if (strcmp (token[0], "source-directory") == 0)
-					_ifparser_source (token[i], en_dir, quiet, TRUE);
+				if (nm_streq (token[0], "source-directory"))
+					_ifparser_source (parser, token[i], en_dir, quiet, TRUE);
 				else
-					_ifparser_source (token[i], en_dir, quiet, FALSE);
+					_ifparser_source (parser, token[i], en_dir, quiet, FALSE);
 			}
 			g_free (en_dir);
 		}
 		else {
 			if (skip_to_block) {
 				if (!quiet) {
-					nm_log_warn (LOGD_SETTINGS, "ignoring out-of-block data '%s'\n",
-					             join_values_with_spaces(value, token));
+					_LOGW ("ignoring out-of-block data '%s'\n",
+					       join_values_with_spaces (value, token));
 				}
 			} else
-				add_data(token[0], join_values_with_spaces(value, token + 1));
+				add_data (parser, token[0], join_values_with_spaces (value, token + 1));
 		}
 	}
-	fclose(inp);
+	fclose (inp);
 
 	if (!quiet)
-		nm_log_info (LOGD_SETTINGS, "      interface-parser: finished parsing file %s\n", eni_file);
+		_LOGI ("      interface-parser: finished parsing file %s\n", eni_file);
 }
 
 static void
-_ifparser_source (const char *path, const char *en_dir, int quiet, int dir)
+_ifparser_source (if_parser *parser, const char *path, const char *en_dir, int quiet, int dir)
 {
 	char *abs_path;
 	const char *item;
@@ -272,132 +283,128 @@ _ifparser_source (const char *path, const char *en_dir, int quiet, int dir)
 		abs_path = g_build_filename (en_dir, path, NULL);
 
 	if (!quiet)
-		nm_log_info (LOGD_SETTINGS, "      interface-parser: source line includes interfaces file(s) %s\n", abs_path);
+		_LOGI ("      interface-parser: source line includes interfaces file(s) %s\n", abs_path);
 
 	/* ifupdown uses WRDE_NOCMD for wordexp. */
 	if (wordexp (abs_path, &we, WRDE_NOCMD)) {
 		if (!quiet)
-			nm_log_warn (LOGD_SETTINGS, "word expansion for %s failed\n", abs_path);
+			_LOGW ("word expansion for %s failed\n", abs_path);
 	} else {
 		for (i = 0; i < we.we_wordc; i++) {
 			if (dir) {
 				source_dir = g_dir_open (we.we_wordv[i], 0, &error);
 				if (!source_dir) {
 					if (!quiet) {
-						nm_log_warn (LOGD_SETTINGS, "Failed to open directory %s: %s",
-						             we.we_wordv[i], error->message);
+						_LOGW ("Failed to open directory %s: %s",
+						       we.we_wordv[i], error->message);
 					}
 					g_clear_error (&error);
 				} else {
 					while ((item = g_dir_read_name (source_dir)))
-						_ifparser_source (item, we.we_wordv[i], quiet, FALSE);
+						_ifparser_source (parser, item, we.we_wordv[i], quiet, FALSE);
 					g_dir_close (source_dir);
 				}
 			} else
-				_recursive_ifparser (we.we_wordv[i], quiet);
+				_recursive_ifparser (parser, we.we_wordv[i], quiet);
 		}
 		wordfree (&we);
 	}
 	g_free (abs_path);
 }
 
-void ifparser_init (const char *eni_file, int quiet)
+if_parser *
+ifparser_parse (const char *eni_file, int quiet)
 {
-	first = last = NULL;
-	_recursive_ifparser (eni_file, quiet);
+	if_parser *parser;
+
+	parser = g_slice_new (if_parser);
+	c_list_init (&parser->block_lst_head);
+	_recursive_ifparser (parser, eni_file, quiet);
+	return parser;
 }
 
-void _destroy_data(if_data *ifd)
+static void
+_destroy_data (if_data *ifd)
 {
-	if (ifd == NULL)
-		return;
-	_destroy_data(ifd->next);
-	g_free(ifd->key);
-	g_free(ifd->data);
-	g_slice_free(struct _if_data, ifd);
-	return;
+	c_list_unlink_stale (&ifd->data_lst);
+	g_free (ifd);
 }
 
-void _destroy_block(if_block* ifb)
+static void
+_destroy_block (if_block* ifb)
 {
-	if (ifb == NULL)
-		return;
-	_destroy_block(ifb->next);
-	_destroy_data(ifb->info);
-	g_free(ifb->name);
-	g_free(ifb->type);
-	g_slice_free(struct _if_block, ifb);
-	return;
+	if_data *ifd;
+
+	while ((ifd = c_list_first_entry (&ifb->data_lst_head, if_data, data_lst)))
+		_destroy_data (ifd);
+	c_list_unlink_stale (&ifb->block_lst);
+	g_free (ifb);
 }
 
-void ifparser_destroy(void)
+void
+ifparser_destroy (if_parser *parser)
 {
-	_destroy_block(first);
-	first = last = NULL;
+	if_block *ifb;
+
+	while ((ifb = c_list_first_entry (&parser->block_lst_head, if_block, block_lst)))
+		_destroy_block (ifb);
+	g_slice_free (if_parser, parser);
 }
 
-if_block *ifparser_getfirst(void)
+if_block *
+ifparser_getfirst (if_parser *parser)
 {
-	return first;
+	return c_list_first_entry (&parser->block_lst_head, if_block, block_lst);
 }
 
-int ifparser_get_num_blocks(void)
+guint
+ifparser_get_num_blocks (if_parser *parser)
 {
-	int i = 0;
-	if_block *iter = first;
-
-	while (iter) {
-		i++;
-		iter = iter->next;
-	}
-	return i;
+	return c_list_length (&parser->block_lst_head);
 }
 
-if_block *ifparser_getif(const char* iface)
+if_block *
+ifparser_getif (if_parser *parser, const char* iface)
 {
-	if_block *curr = first;
-	while(curr!=NULL)
-	{
-		if (strcmp(curr->type,"iface")==0 && strcmp(curr->name,iface)==0)
-			return curr;
-		curr = curr->next;
+	if_block *ifb;
+
+	c_list_for_each_entry (ifb, &parser->block_lst_head, block_lst) {
+		if (   nm_streq (ifb->type, "iface")
+		    && nm_streq (ifb->name, iface))
+			return ifb;
 	}
 	return NULL;
 }
 
-const char *ifparser_getkey(if_block* iface, const char *key)
+static if_data *
+ifparser_findkey (if_block* iface, const char *key)
 {
-	if_data *curr = iface->info;
-	while(curr!=NULL)
-	{
-		if (strcmp(curr->key,key)==0)
-			return curr->data;
-		curr = curr->next;
+	if_data *ifd;
+
+	c_list_for_each_entry (ifd, &iface->data_lst_head, data_lst) {
+		if (nm_streq (ifd->key, key))
+			return ifd;
 	}
 	return NULL;
 }
 
-gboolean
-ifparser_haskey(if_block* iface, const char *key)
+const char *
+ifparser_getkey (if_block* iface, const char *key)
 {
-	if_data *curr = iface->info;
+	if_data *ifd;
 
-	while (curr != NULL) {
-		if (strcmp (curr->key, key) == 0)
-			return TRUE;
-		curr = curr->next;
-	}
-	return FALSE;
+	ifd = ifparser_findkey (iface, key);
+	return ifd ? ifd->data : NULL;
 }
 
-int ifparser_get_num_info(if_block* iface)
+gboolean
+ifparser_haskey (if_block* iface, const char *key)
 {
-	int i = 0;
-	if_data *iter = iface->info;
+	return !!ifparser_findkey (iface, key);
+}
 
-	while (iter) {
-		i++;
-		iter = iter->next;
-	}
-	return i;
+guint
+ifparser_get_num_info (if_block* iface)
+{
+	return c_list_length (&iface->data_lst_head);
 }
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
index 7e6c8e34..f367f626 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
@@ -23,33 +23,37 @@
 #ifndef _INTERFACE_PARSER_H
 #define _INTERFACE_PARSER_H
 
-typedef struct _if_data
-{
-	char *key;
-	char *data;
-	struct _if_data *next;
+#include "c-list/src/c-list.h"
+
+typedef struct {
+	CList data_lst;
+	const char *data;
+	const char key[];
 } if_data;
 
-typedef struct _if_block
-{
-	char *type;
-	char *name;
-	if_data *info;
-	struct _if_block *next;
+typedef struct {
+	CList block_lst;
+	CList data_lst_head;
+	const char *type;
+	const char name[];
 } if_block;
 
-void ifparser_init(const char *eni_file, int quiet);
-void ifparser_destroy(void);
+typedef struct {
+	CList block_lst_head;
+} if_parser;
+
+if_parser *ifparser_parse (const char *eni_file, int quiet);
+
+void ifparser_destroy (if_parser *parser);
+NM_AUTO_DEFINE_FCN0 (if_parser *, _nm_auto_ifparser, ifparser_destroy);
+#define nm_auto_ifparser nm_auto(_nm_auto_ifparser)
+
+if_block *ifparser_getif (if_parser *parser, const char* iface);
+if_block *ifparser_getfirst (if_parser *parser);
+const char *ifparser_getkey (if_block* iface, const char *key);
+gboolean ifparser_haskey (if_block* iface, const char *key);
 
-if_block *ifparser_getif(const char* iface);
-if_block *ifparser_getfirst(void);
-const char *ifparser_getkey(if_block* iface, const char *key);
-gboolean ifparser_haskey(if_block* iface, const char *key);
-int ifparser_get_num_blocks(void);
-int ifparser_get_num_info(if_block* iface);
+guint ifparser_get_num_blocks (if_parser *parser);
+guint ifparser_get_num_info (if_block* iface);
 
-void add_block(const char *type, const char* name);
-void add_data(const char *key,const char *data);
-void _destroy_data(if_data *ifd);
-void _destroy_block(if_block* ifb);
 #endif
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index 884c6591..369fa70d 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -37,42 +37,64 @@
 #include "nms-ifupdown-plugin.h"
 #include "nms-ifupdown-parser.h"
 
-static const gchar*
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_NAME      "ifupdown"
+#define _NMLOG_DOMAIN           LOGD_SETTINGS
+#define _NMLOG(level, ...) \
+    nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
+            "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+            _NMLOG_PREFIX_NAME": " \
+            _NM_UTILS_MACRO_REST (__VA_ARGS__))
+
+/*****************************************************************************/
+
+#define _str_has_prefix(val, prefix, require_suffix) \
+	({ \
+		const char *_val = (val); \
+		\
+		   (strncmp (_val, ""prefix"", NM_STRLEN (prefix)) == 0) \
+		&& (   !(require_suffix) \
+		    || _val[NM_STRLEN (prefix)] != '\0'); \
+	})
+
+static const char*
 _ifupdownplugin_guess_connection_type (if_block *block)
 {
-	if_data *curr = block->info;
-	const gchar* ret_type = NULL;
-	const gchar* value = ifparser_getkey(block, "inet");
-	if(value && !strcmp("ppp", value)) {
-		ret_type = NM_SETTING_PPP_SETTING_NAME;
-	}
+	const char *ret_type = NULL;
 
-	while(!ret_type && curr) {
-		if(!strncmp("wireless-", curr->key, strlen("wireless-")) ||
-		   !strncmp("wpa-", curr->key, strlen("wpa-"))) {
-			ret_type = NM_SETTING_WIRELESS_SETTING_NAME;
+	if(nm_streq0 (ifparser_getkey (block, "inet"), "ppp"))
+		ret_type = NM_SETTING_PPP_SETTING_NAME;
+	else {
+		if_data *ifb;
+
+		c_list_for_each_entry (ifb, &block->data_lst_head, data_lst) {
+			if (   _str_has_prefix (ifb->key, "wireless-", FALSE)
+			    || _str_has_prefix (ifb->key, "wpa-", FALSE)) {
+				ret_type = NM_SETTING_WIRELESS_SETTING_NAME;
+				break;
+			}
 		}
-		curr = curr->next;
+		if(!ret_type)
+			ret_type = NM_SETTING_WIRED_SETTING_NAME;
 	}
 
-	if(!ret_type)
-		ret_type = NM_SETTING_WIRED_SETTING_NAME;
-
-	nm_log_info (LOGD_SETTINGS, "guessed connection type (%s) = %s", block->name, ret_type);
+	_LOGI ("guessed connection type (%s) = %s", block->name, ret_type);
 	return ret_type;
 }
 
 struct _Mapping {
-	const gchar *domain;
+	const char *domain;
 	const gpointer target;
 };
 
 static gpointer
-map_by_mapping(struct _Mapping *mapping, const gchar *key)
+map_by_mapping (struct _Mapping *mapping, const char *key)
 {
 	struct _Mapping *curr = mapping;
-	while(curr->domain) {
-		if(!strcmp(curr->domain, key))
+
+	while (curr->domain) {
+		if (nm_streq (curr->domain, key))
 			return curr->target;
 		curr++;
 	}
@@ -80,14 +102,11 @@ map_by_mapping(struct _Mapping *mapping, const gchar *key)
 }
 
 static void
-update_wireless_setting_from_if_block(NMConnection *connection,
-							   if_block *block)
+update_wireless_setting_from_if_block (NMConnection *connection,
+                                       if_block *block)
 {
-	gint wpa_l= strlen("wpa-");
-	gint wireless_l= strlen("wireless-");
-
-	if_data *curr = block->info;
-	const gchar* value = ifparser_getkey (block, "inet");
+	if_data *curr;
+	const char *value = ifparser_getkey (block, "inet");
 	struct _Mapping mapping[] = {
 		{"ssid", "ssid"},
 		{"essid", "ssid"},
@@ -97,27 +116,26 @@ update_wireless_setting_from_if_block(NMConnection *connection,
 
 	NMSettingWireless *wireless_setting = NULL;
 
-	if(value && !strcmp("ppp", value)) {
+	if (nm_streq0 (value, "ppp"))
 		return;
-	}
 
-	nm_log_info (LOGD_SETTINGS, "update wireless settings (%s).", block->name);
-	wireless_setting = NM_SETTING_WIRELESS(nm_setting_wireless_new());
+	_LOGI ("update wireless settings (%s).", block->name);
+	wireless_setting = NM_SETTING_WIRELESS (nm_setting_wireless_new ());
+
+	c_list_for_each_entry (curr, &block->data_lst_head, data_lst) {
+		if (_str_has_prefix (curr->key, "wireless-", TRUE)) {
+			const char* newkey = map_by_mapping (mapping, curr->key + NM_STRLEN ("wireless-"));
 
-	while(curr) {
-		if(strlen(curr->key) > wireless_l &&
-		   !strncmp("wireless-", curr->key, wireless_l)) {
-			const gchar* newkey = map_by_mapping(mapping, curr->key+wireless_l);
-			nm_log_info (LOGD_SETTINGS, "wireless setting key: %s='%s'", newkey, curr->data);
-			if(newkey && !strcmp("ssid", newkey)) {
+			_LOGI ("wireless setting key: %s='%s'", newkey, curr->data);
+			if (nm_streq0 (newkey, "ssid")) {
 				GBytes *ssid;
-				gint len = strlen(curr->data);
+				int len = strlen (curr->data);
 
 				ssid = g_bytes_new (curr->data, len);
 				g_object_set (wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL);
 				g_bytes_unref (ssid);
-				nm_log_info (LOGD_SETTINGS, "setting wireless ssid = %d", len);
-			} else if(newkey && !strcmp("mode", newkey)) {
+				_LOGI ("setting wireless ssid = %d", len);
+			} else if (nm_streq0 (newkey, "mode")) {
 				if (!g_ascii_strcasecmp (curr->data, "Managed") || !g_ascii_strcasecmp (curr->data, "Auto"))
 					g_object_set (wireless_setting, NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA, NULL);
 				else if (!g_ascii_strcasecmp (curr->data, "Ad-Hoc"))
@@ -125,39 +143,37 @@ update_wireless_setting_from_if_block(NMConnection *connection,
 				else if (!g_ascii_strcasecmp (curr->data, "Master"))
 					g_object_set (wireless_setting, NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_AP, NULL);
 				else
-					nm_log_warn (LOGD_SETTINGS, "Invalid mode '%s' (not 'Ad-Hoc', 'Ap', 'Managed', or 'Auto')", curr->data);
+					_LOGW ("Invalid mode '%s' (not 'Ad-Hoc', 'Ap', 'Managed', or 'Auto')", curr->data);
 			} else {
-				g_object_set(wireless_setting,
-					   newkey, curr->data,
-					   NULL);
+				g_object_set (wireless_setting,
+				              newkey, curr->data,
+				              NULL);
 			}
-		} else if(strlen(curr->key) > wpa_l &&
-				!strncmp("wpa-", curr->key, wpa_l)) {
-			const gchar* newkey = map_by_mapping(mapping, curr->key+wpa_l);
+		} else if (_str_has_prefix (curr->key, "wpa-", TRUE)) {
+			const char* newkey = map_by_mapping (mapping, curr->key + NM_STRLEN ("wpa-"));
 
-			if(newkey && !strcmp("ssid", newkey)) {
+			if (nm_streq0 (newkey, "ssid")) {
 				GBytes *ssid;
-				gint len = strlen(curr->data);
+				int len = strlen (curr->data);
 
 				ssid = g_bytes_new (curr->data, len);
 				g_object_set (wireless_setting, NM_SETTING_WIRELESS_SSID, ssid, NULL);
 				g_bytes_unref (ssid);
-				nm_log_info (LOGD_SETTINGS, "setting wpa ssid = %d", len);
-			} else if(newkey) {
+				_LOGI ("setting wpa ssid = %d", len);
+			} else if (newkey) {
 
-				g_object_set(wireless_setting,
-						   newkey, curr->data,
-						   NULL);
-				nm_log_info (LOGD_SETTINGS, "setting wpa newkey(%s)=data(%s)", newkey, curr->data);
+				g_object_set (wireless_setting,
+				              newkey, curr->data,
+				              NULL);
+				_LOGI ("setting wpa newkey(%s)=data(%s)", newkey, curr->data);
 			}
 		}
-		curr = curr->next;
 	}
-	nm_connection_add_setting(connection, (NMSetting*) wireless_setting);
+	nm_connection_add_setting (connection, (NMSetting*) wireless_setting);
 }
 
-typedef gchar* (*IfupdownStrDupeFunc) (gpointer value, gpointer data);
-typedef gpointer (*IfupdownStrToTypeFunc) (const gchar* value);
+typedef char* (*IfupdownStrDupeFunc) (gconstpointer value, gpointer data);
+typedef gpointer (*IfupdownStrToTypeFunc) (const char* value);
 
 static char*
 normalize_dupe_wireless_key (gpointer value, gpointer data) {
@@ -177,7 +193,7 @@ normalize_dupe_wireless_key (gpointer value, gpointer data) {
 		result_cur += next - delim;
 		delim = next + 1;
 	}
-	if (*delim && strlen (valuec) > GPOINTER_TO_UINT(delim - valuec)) {
+	if (*delim && strlen (valuec) > GPOINTER_TO_UINT (delim - valuec)) {
 		strncpy (result_cur, delim, endc - delim);
 		result_cur += endc - delim;
 	}
@@ -187,12 +203,12 @@ normalize_dupe_wireless_key (gpointer value, gpointer data) {
 
 static char*
 normalize_dupe (gpointer value, gpointer data) {
-	return g_strdup(value);
+	return g_strdup (value);
 }
 
 static char*
 normalize_tolower (gpointer value, gpointer data) {
-	return g_ascii_strdown(value, -1);
+	return g_ascii_strdown (value, -1);
 }
 
 static char *normalize_psk (gpointer value, gpointer data)
@@ -203,25 +219,25 @@ static char *normalize_psk (gpointer value, gpointer data)
 }
 
 static gpointer
-string_to_gpointerint(const gchar* data)
+string_to_gpointerint (const char* data)
 {
-	gint result = (gint) strtol (data, NULL, 10);
-	return GINT_TO_POINTER(result);
+	int result = (int) strtol (data, NULL, 10);
+	return GINT_TO_POINTER (result);
 }
 
 static gpointer
-string_to_glist_of_strings(const gchar* data)
+string_to_glist_of_strings (const char* data)
 {
 	GSList *ret = NULL;
-	gchar *string = (gchar*) data;
-	while(string) {
-		gchar* next = NULL;
-		if( (next = strchr(string, ' '))  ||
-		    (next = strchr(string, '\t')) ||
-		    (next = strchr(string, '\0')) ) {
-
-			gchar *part = g_strndup(string, (next - string));
-			ret = g_slist_append(ret, part);
+	char *string = (char*) data;
+	while (string) {
+		char* next = NULL;
+		if ( (next = strchr (string, ' '))  ||
+		     (next = strchr (string, '\t')) ||
+		     (next = strchr (string, '\0')) ) {
+
+			char *part = g_strndup (string, (next - string));
+			ret = g_slist_append (ret, part);
 			if (*next)
 				string = next+1;
 			else
@@ -234,19 +250,17 @@ string_to_glist_of_strings(const gchar* data)
 }
 
 static void
-slist_free_all(gpointer slist)
+slist_free_all (gpointer slist)
 {
 	g_slist_free_full ((GSList *) slist, g_free);
 }
 
 static void
-update_wireless_security_setting_from_if_block(NMConnection *connection,
-									  if_block *block)
+update_wireless_security_setting_from_if_block (NMConnection *connection,
+                                                if_block *block)
 {
-	gint wpa_l= strlen("wpa-");
-	gint wireless_l= strlen("wireless-");
-	if_data *curr = block->info;
-	const gchar* value = ifparser_getkey (block, "inet");
+	if_data *curr;
+	const char* value = ifparser_getkey (block, "inet");
 	struct _Mapping mapping[] = {
 		{"psk", "psk"},
 		{"identity", "leap-username"},
@@ -302,101 +316,93 @@ update_wireless_security_setting_from_if_block(NMConnection *connection,
 	NMSettingWireless *s_wireless;
 	gboolean security = FALSE;
 
-	if(value && !strcmp("ppp", value)) {
+	if (nm_streq0 (value, "ppp"))
 		return;
-	}
-
-	s_wireless = nm_connection_get_setting_wireless(connection);
-	g_return_if_fail(s_wireless);
 
-	nm_log_info (LOGD_SETTINGS, "update wireless security settings (%s).", block->name);
-	wireless_security_setting =
-		NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new());
+	s_wireless = nm_connection_get_setting_wireless (connection);
+	g_return_if_fail (s_wireless);
 
-	while(curr) {
-		if(strlen(curr->key) > wireless_l &&
-		   !strncmp("wireless-", curr->key, wireless_l)) {
+	_LOGI ("update wireless security settings (%s).", block->name);
+	wireless_security_setting = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ());
 
-			gchar *property_value = NULL;
+	c_list_for_each_entry (curr, &block->data_lst_head, data_lst) {
+		if (_str_has_prefix (curr->key, "wireless-", TRUE)) {
+			const char *key = curr->key + NM_STRLEN ("wireless-");
+			char *property_value = NULL;
 			gpointer typed_property_value = NULL;
-			const gchar* newkey = map_by_mapping(mapping, curr->key+wireless_l);
-			IfupdownStrDupeFunc dupe_func = map_by_mapping (dupe_mapping, curr->key+wireless_l);
-			IfupdownStrToTypeFunc type_map_func = map_by_mapping (type_mapping, curr->key+wireless_l);
-			GFreeFunc free_func = map_by_mapping (free_type_mapping, curr->key+wireless_l);
-			if(!newkey || !dupe_func)
+			const char* newkey = map_by_mapping (mapping, key);
+			IfupdownStrDupeFunc dupe_func = map_by_mapping (dupe_mapping, key);
+			IfupdownStrToTypeFunc type_map_func = map_by_mapping (type_mapping, key);
+			GFreeFunc free_func = map_by_mapping (free_type_mapping, key);
+			if (!newkey || !dupe_func)
 				goto next;
 
 			property_value = (*dupe_func) (curr->data, connection);
-			nm_log_info (LOGD_SETTINGS, "setting wireless security key: %s=%s",
-			             newkey, property_value);
+			_LOGI ("setting wireless security key: %s=%s",
+			       newkey, property_value);
 
 			if (type_map_func) {
 				errno = 0;
 				typed_property_value = (*type_map_func) (property_value);
-				if(errno)
+				if (errno)
 					goto wireless_next;
 			}
 
-			g_object_set(wireless_security_setting,
-					   newkey, typed_property_value ?: property_value,
-					   NULL);
+			g_object_set (wireless_security_setting,
+			              newkey, typed_property_value ?: property_value,
+			              NULL);
 			security = TRUE;
 
-		wireless_next:
-			g_free(property_value);
+wireless_next:
+			g_free (property_value);
 			if (typed_property_value && free_func)
 				(*free_func) (typed_property_value);
 
-		} else if(strlen(curr->key) > wpa_l &&
-				!strncmp("wpa-", curr->key, wpa_l)) {
-
-			gchar *property_value = NULL;
+		} else if (_str_has_prefix (curr->key, "wpa-", TRUE)) {
+			const char *key = curr->key + NM_STRLEN ("wpa-");
+			char *property_value = NULL;
 			gpointer typed_property_value = NULL;
-			const gchar* newkey = map_by_mapping(mapping, curr->key+wpa_l);
-			IfupdownStrDupeFunc dupe_func = map_by_mapping (dupe_mapping, curr->key+wpa_l);
-			IfupdownStrToTypeFunc type_map_func = map_by_mapping (type_mapping, curr->key+wpa_l);
-			GFreeFunc free_func = map_by_mapping (free_type_mapping, curr->key+wpa_l);
-			if(!newkey || !dupe_func)
+			const char* newkey = map_by_mapping (mapping, key);
+			IfupdownStrDupeFunc dupe_func = map_by_mapping (dupe_mapping, key);
+			IfupdownStrToTypeFunc type_map_func = map_by_mapping (type_mapping, key);
+			GFreeFunc free_func = map_by_mapping (free_type_mapping, key);
+			if (!newkey || !dupe_func)
 				goto next;
 
 			property_value = (*dupe_func) (curr->data, connection);
-			nm_log_info (LOGD_SETTINGS, "setting wpa security key: %s=%s",
-			             newkey,
-#ifdef DEBUG_SECRETS
-			             property_value
-#else /* DEBUG_SECRETS */
-			             !strcmp("key", newkey) ||
-			             !strcmp("leap-password", newkey) ||
-			             !strcmp("pin", newkey) ||
-			             !strcmp("psk", newkey) ||
-			             !strcmp("wep-key0", newkey) ||
-			             !strcmp("wep-key1", newkey) ||
-			             !strcmp("wep-key2", newkey) ||
-			             !strcmp("wep-key3", newkey) ||
-			             NULL ?
-			             "<omitted>" : property_value
-#endif /* DEBUG_SECRETS */
-			             );
+			_LOGI ("setting wpa security key: %s=%s",
+			       newkey,
+			       NM_IN_STRSET (newkey, "key",
+			                             "leap-password",
+			                             "pin",
+			                             "psk",
+			                             "wep-key0",
+			                             "wep-key1",
+			                             "wep-key2",
+			                             "wep-key3")
+			         ? "<omitted>"
+			         : property_value
+			       );
 
 			if (type_map_func) {
 				errno = 0;
 				typed_property_value = (*type_map_func) (property_value);
-				if(errno)
+				if (errno)
 					goto wpa_next;
 			}
 
-			g_object_set(wireless_security_setting,
-					   newkey, typed_property_value ?: property_value,
-					   NULL);
+			g_object_set (wireless_security_setting,
+			              newkey, typed_property_value ?: property_value,
+			              NULL);
 			security = TRUE;
 
-		wpa_next:
-			g_free(property_value);
+wpa_next:
+			g_free (property_value);
 			if (free_func && typed_property_value)
 				(*free_func) (typed_property_value);
 		}
-	next:
-		curr = curr->next;
+next:
+		;
 	}
 
 	if (security)
@@ -404,51 +410,52 @@ update_wireless_security_setting_from_if_block(NMConnection *connection,
 }
 
 static void
-update_wired_setting_from_if_block(NMConnection *connection,
-							if_block *block)
+update_wired_setting_from_if_block (NMConnection *connection,
+                                    if_block *block)
 {
 	NMSettingWired *s_wired = NULL;
-	s_wired = NM_SETTING_WIRED(nm_setting_wired_new());
-	nm_connection_add_setting(connection, NM_SETTING(s_wired));
+	s_wired = NM_SETTING_WIRED (nm_setting_wired_new ());
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
 }
 
 static void
 ifupdown_ip4_add_dns (NMSettingIPConfig *s_ip4, const char *dns)
 {
 	guint32 addr;
-	char **list, **iter;
+	gs_strfreev char **list = NULL;
+	char **iter;
 
 	if (dns == NULL)
 		return;
 
 	list = g_strsplit_set (dns, " \t", -1);
 	for (iter = list; iter && *iter; iter++) {
-		g_strstrip (*iter);
-		if (g_ascii_isspace (*iter[0]))
+		if ((*iter)[0] == '\0')
 			continue;
 		if (!inet_pton (AF_INET, *iter, &addr)) {
-			nm_log_warn (LOGD_SETTINGS, "    ignoring invalid nameserver '%s'", *iter);
+			_LOGW ("    ignoring invalid nameserver '%s'", *iter);
 			continue;
 		}
 
 		if (!nm_setting_ip_config_add_dns (s_ip4, *iter))
-			nm_log_warn (LOGD_SETTINGS, "    duplicate DNS domain '%s'", *iter);
+			_LOGW ("    duplicate DNS domain '%s'", *iter);
 	}
-	g_strfreev (list);
 }
 
 static gboolean
-update_ip4_setting_from_if_block(NMConnection *connection,
-						   if_block *block,
-						   GError **error)
+update_ip4_setting_from_if_block (NMConnection *connection,
+                                  if_block *block,
+                                  GError **error)
 {
 
-	NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new());
-	const char *type = ifparser_getkey(block, "inet");
-	gboolean is_static = type && !strcmp("static", type);
+	gs_unref_object NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
+	const char *type = ifparser_getkey (block, "inet");
 
-	if (!is_static) {
-		g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
+	if (!nm_streq0 (type, "static")) {
+		g_object_set (s_ip4,
+		              NM_SETTING_IP_CONFIG_METHOD,
+		              NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+		              NULL);
 	} else {
 		guint32 tmp_mask;
 		NMIPAddress *addr;
@@ -458,7 +465,6 @@ update_ip4_setting_from_if_block(NMConnection *connection,
 		const char *nameserver_v;
 		const char *nameservers_v;
 		const char *search_v;
-		char **list, **iter;
 		guint32 netmask_int = 32;
 
 		/* Address */
@@ -466,7 +472,7 @@ update_ip4_setting_from_if_block(NMConnection *connection,
 		if (!address_v) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Missing IPv4 address");
-			goto error;
+			return FALSE;
 		}
 
 		/* mask/prefix */
@@ -476,8 +482,8 @@ update_ip4_setting_from_if_block(NMConnection *connection,
 				netmask_int = atoi (netmask_v);
 			} else if (!inet_pton (AF_INET, netmask_v, &tmp_mask)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-						   "Invalid IPv4 netmask '%s'", netmask_v);
-				goto error;
+				             "Invalid IPv4 netmask '%s'", netmask_v);
+				return FALSE;
 			} else {
 				netmask_int = nm_utils_ip4_netmask_to_prefix (tmp_mask);
 			}
@@ -486,13 +492,13 @@ update_ip4_setting_from_if_block(NMConnection *connection,
 		/* Add the new address to the setting */
 		addr = nm_ip_address_new (AF_INET, address_v, netmask_int, error);
 		if (!addr)
-			goto error;
+			return FALSE;
 
 		if (nm_setting_ip_config_add_address (s_ip4, addr)) {
-			nm_log_info (LOGD_SETTINGS, "addresses count: %d",
-			             nm_setting_ip_config_get_num_addresses (s_ip4));
+			_LOGI ("addresses count: %d",
+			       nm_setting_ip_config_get_num_addresses (s_ip4));
 		} else {
-			nm_log_info (LOGD_SETTINGS, "ignoring duplicate IP4 address");
+			_LOGI ("ignoring duplicate IP4 address");
 		}
 		nm_ip_address_unref (addr);
 
@@ -502,7 +508,7 @@ update_ip4_setting_from_if_block(NMConnection *connection,
 			if (!nm_utils_ipaddr_valid (AF_INET, gateway_v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				             "Invalid IPv4 gateway '%s'", gateway_v);
-				goto error;
+				return FALSE;
 			}
 			if (!nm_setting_ip_config_get_gateway (s_ip4))
 				g_object_set (s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL);
@@ -515,70 +521,67 @@ update_ip4_setting_from_if_block(NMConnection *connection,
 		ifupdown_ip4_add_dns (s_ip4, nameservers_v);
 
 		if (!nm_setting_ip_config_get_num_dns (s_ip4))
-			nm_log_info (LOGD_SETTINGS, "No dns-nameserver configured in /etc/network/interfaces");
+			_LOGI ("No dns-nameserver configured in /etc/network/interfaces");
 
 		/* DNS searches */
 		search_v = ifparser_getkey (block, "dns-search");
 		if (search_v) {
+			gs_strfreev char **list = NULL;
+			char **iter;
+
 			list = g_strsplit_set (search_v, " \t", -1);
 			for (iter = list; iter && *iter; iter++) {
-				g_strstrip (*iter);
-				if (g_ascii_isspace (*iter[0]))
+				if ((*iter)[0] == '\0')
 					continue;
 				if (!nm_setting_ip_config_add_dns_search (s_ip4, *iter))
-					nm_log_warn (LOGD_SETTINGS, "    duplicate DNS domain '%s'", *iter);
+					_LOGW ("    duplicate DNS domain '%s'", *iter);
 			}
-			g_strfreev (list);
 		}
 
 		g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
 	}
 
-	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
+	nm_connection_add_setting (connection, NM_SETTING (g_steal_pointer (&s_ip4)));
 	return TRUE;
-
-error:
-	g_object_unref (s_ip4);
-	return FALSE;
 }
 
 static void
 ifupdown_ip6_add_dns (NMSettingIPConfig *s_ip6, const char *dns)
 {
 	struct in6_addr addr;
-	char **list, **iter;
+	gs_strfreev char **list = NULL;
+	char **iter;
 
 	if (dns == NULL)
 		return;
 
 	list = g_strsplit_set (dns, " \t", -1);
 	for (iter = list; iter && *iter; iter++) {
-		g_strstrip (*iter);
-		if (g_ascii_isspace (*iter[0]))
+		if ((*iter)[0] == '\0')
 			continue;
 		if (!inet_pton (AF_INET6, *iter, &addr)) {
-			nm_log_warn (LOGD_SETTINGS, "    ignoring invalid nameserver '%s'", *iter);
+			_LOGW ("    ignoring invalid nameserver '%s'", *iter);
 			continue;
 		}
 
 		if (!nm_setting_ip_config_add_dns (s_ip6, *iter))
-			nm_log_warn (LOGD_SETTINGS, "    duplicate DNS domain '%s'", *iter);
+			_LOGW ("    duplicate DNS domain '%s'", *iter);
 	}
-	g_strfreev (list);
 }
 
 static gboolean
-update_ip6_setting_from_if_block(NMConnection *connection,
-						   if_block *block,
-						   GError **error)
+update_ip6_setting_from_if_block (NMConnection *connection,
+                                  if_block *block,
+                                  GError **error)
 {
-	NMSettingIPConfig *s_ip6 = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new());
-	const char *type = ifparser_getkey(block, "inet6");
-	gboolean is_static = type && (!strcmp("static", type) ||
-							!strcmp("v4tunnel", type));
+	gs_unref_object NMSettingIPConfig *s_ip6 = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new ());
+	const char *type = ifparser_getkey (block, "inet6");
 
-	if (!is_static) {
-		g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
+	if (!NM_IN_STRSET (type, "static", "v4tunnel")) {
+		g_object_set (s_ip6,
+		              NM_SETTING_IP_CONFIG_METHOD,
+		              NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+		              NULL);
 	} else {
 		NMIPAddress *addr;
 		const char *address_v;
@@ -588,31 +591,30 @@ update_ip6_setting_from_if_block(NMConnection *connection,
 		const char *nameservers_v;
 		const char *search_v;
 		int prefix_int = 128;
-		char **list, **iter;
 
 		/* Address */
-		address_v = ifparser_getkey(block, "address");
+		address_v = ifparser_getkey (block, "address");
 		if (!address_v) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Missing IPv6 address");
-			goto error;
+			return FALSE;
 		}
 
 		/* Prefix */
-		prefix_v = ifparser_getkey(block, "netmask");
+		prefix_v = ifparser_getkey (block, "netmask");
 		if (prefix_v)
 			prefix_int = g_ascii_strtoll (prefix_v, NULL, 10);
 
 		/* Add the new address to the setting */
 		addr = nm_ip_address_new (AF_INET6, address_v, prefix_int, error);
 		if (!addr)
-			goto error;
+			return FALSE;
 
 		if (nm_setting_ip_config_add_address (s_ip6, addr)) {
-			nm_log_info (LOGD_SETTINGS, "addresses count: %d",
+			_LOGI ("addresses count: %d",
 			             nm_setting_ip_config_get_num_addresses (s_ip6));
 		} else {
-			nm_log_info (LOGD_SETTINGS, "ignoring duplicate IP6 address");
+			_LOGI ("ignoring duplicate IP6 address");
 		}
 		nm_ip_address_unref (addr);
 
@@ -622,33 +624,34 @@ update_ip6_setting_from_if_block(NMConnection *connection,
 			if (!nm_utils_ipaddr_valid (AF_INET6, gateway_v)) {
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 				             "Invalid IPv6 gateway '%s'", gateway_v);
-				goto error;
+				return FALSE;
 			}
 			if (!nm_setting_ip_config_get_gateway (s_ip6))
 				g_object_set (s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, gateway_v, NULL);
 		}
 
-		nameserver_v = ifparser_getkey(block, "dns-nameserver");
+		nameserver_v = ifparser_getkey (block, "dns-nameserver");
 		ifupdown_ip6_add_dns (s_ip6, nameserver_v);
 
-		nameservers_v = ifparser_getkey(block, "dns-nameservers");
+		nameservers_v = ifparser_getkey (block, "dns-nameservers");
 		ifupdown_ip6_add_dns (s_ip6, nameservers_v);
 
 		if (!nm_setting_ip_config_get_num_dns (s_ip6))
-			nm_log_info (LOGD_SETTINGS, "No dns-nameserver configured in /etc/network/interfaces");
+			_LOGI ("No dns-nameserver configured in /etc/network/interfaces");
 
 		/* DNS searches */
 		search_v = ifparser_getkey (block, "dns-search");
 		if (search_v) {
+			gs_strfreev char **list = NULL;
+			char **iter;
+
 			list = g_strsplit_set (search_v, " \t", -1);
 			for (iter = list; iter && *iter; iter++) {
-				g_strstrip (*iter);
-				if (isblank (*iter[0]))
+				if ((*iter)[0] == '\0')
 					continue;
 				if (!nm_setting_ip_config_add_dns_search (s_ip6, *iter))
-					nm_log_warn (LOGD_SETTINGS, "    duplicate DNS domain '%s'", *iter);
+					_LOGW ("    duplicate DNS domain '%s'", *iter);
 			}
-			g_strfreev (list);
 		}
 
 		g_object_set (s_ip6,
@@ -656,12 +659,8 @@ update_ip6_setting_from_if_block(NMConnection *connection,
 		              NULL);
 	}
 
-	nm_connection_add_setting (connection, NM_SETTING (s_ip6));
+	nm_connection_add_setting (connection, NM_SETTING (g_steal_pointer (&s_ip6)));
 	return TRUE;
-
-error:
-	g_object_unref (s_ip6);
-	return FALSE;
 }
 
 gboolean
@@ -669,25 +668,22 @@ ifupdown_update_connection_from_if_block (NMConnection *connection,
                                           if_block *block,
                                           GError **error)
 {
-	const char *type = NULL;
-	char *idstr = NULL;
-	char *uuid_base = NULL;
-	char *uuid = NULL;
+	const char *type;
+	gs_free char *idstr = NULL;
+	gs_free char *uuid = NULL;
 	NMSettingConnection *s_con;
 	gboolean success = FALSE;
 
 	s_con = nm_connection_get_setting_connection (connection);
-	if(!s_con) {
-		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new());
-		g_assert (s_con);
+	if (!s_con) {
+		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
 		nm_connection_add_setting (connection, NM_SETTING (s_con));
 	}
 
 	type = _ifupdownplugin_guess_connection_type (block);
 	idstr = g_strconcat ("Ifupdown (", block->name, ")", NULL);
-	uuid_base = idstr;
 
-	uuid = nm_utils_uuid_generate_from_string (uuid_base, -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
+	uuid = nm_utils_uuid_generate_from_string (idstr, -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
 	g_object_set (s_con,
 	              NM_SETTING_CONNECTION_TYPE, type,
 	              NM_SETTING_CONNECTION_INTERFACE_NAME, block->name,
@@ -696,19 +692,18 @@ ifupdown_update_connection_from_if_block (NMConnection *connection,
 	              NM_SETTING_CONNECTION_READ_ONLY, TRUE,
 	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
 	              NULL);
-	g_free (uuid);
 
-	nm_log_info (LOGD_SETTINGS, "update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s",
-	             block->name, type, idstr, nm_setting_connection_get_uuid (s_con));
+	_LOGI ("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s",
+	       block->name, type, idstr, nm_setting_connection_get_uuid (s_con));
 
-	if (!strcmp (NM_SETTING_WIRED_SETTING_NAME, type))
+	if (nm_streq (type, NM_SETTING_WIRED_SETTING_NAME))
 		update_wired_setting_from_if_block (connection, block);
-	else if (!strcmp (NM_SETTING_WIRELESS_SETTING_NAME, type)) {
+	else if (nm_streq (type, NM_SETTING_WIRELESS_SETTING_NAME)) {
 		update_wireless_setting_from_if_block (connection, block);
 		update_wireless_security_setting_from_if_block (connection, block);
 	}
 
-	if (ifparser_haskey(block, "inet6"))
+	if (ifparser_haskey (block, "inet6"))
 		success = update_ip6_setting_from_if_block (connection, block, error);
 	else
 		success = update_ip4_setting_from_if_block (connection, block, error);
@@ -716,6 +711,5 @@ ifupdown_update_connection_from_if_block (NMConnection *connection,
 	if (success == TRUE)
 		success = nm_connection_verify (connection, error);
 
-	g_free (idstr);
 	return success;
 }
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 8bd72d02..88c651b8 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -29,7 +29,6 @@
 #include <string.h>
 #include <arpa/inet.h>
 #include <gmodule.h>
-#include <libudev.h>
 
 #include "nm-setting-connection.h"
 #include "nm-dbus-interface.h"
@@ -42,7 +41,6 @@
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-config.h"
-#include "nm-utils/nm-udev-utils.h"
 
 #include "nms-ifupdown-interface-parser.h"
 #include "nms-ifupdown-connection.h"
@@ -52,43 +50,27 @@
 
 #define IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT TRUE
 
-/* #define ALWAYS_UNMANAGE TRUE */
-#ifndef ALWAYS_UNMANAGE
-#define ALWAYS_UNMANAGE FALSE
-#endif
-
 /*****************************************************************************/
 
 typedef struct {
-	NMUdevClient *udev_client;
-
-	GHashTable *connections;  /* /e/n/i block name :: NMIfupdownConnection */
-
-	/* Stores all blocks/interfaces read from /e/n/i regardless of whether
-	 * there is an NMIfupdownConnection for block.
+	/* Stores an entry for blocks/interfaces read from /e/n/i and (if exists)
+	 * the NMIfupdownConnection associated with the block.
 	 */
 	GHashTable *eni_ifaces;
 
-	/* Stores any network interfaces the kernel knows about */
-	GHashTable *kernel_ifaces;
-
-	gboolean unmanage_well_known;
+	bool ifupdown_managed;
 } SettingsPluginIfupdownPrivate;
 
 struct _SettingsPluginIfupdown {
-	GObject parent;
+	NMSettingsPlugin parent;
 	SettingsPluginIfupdownPrivate _priv;
 };
 
 struct _SettingsPluginIfupdownClass {
-	GObjectClass parent;
+	NMSettingsPluginClass parent;
 };
 
-static void settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface);
-
-G_DEFINE_TYPE_EXTENDED (SettingsPluginIfupdown, settings_plugin_ifupdown, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (NM_TYPE_SETTINGS_PLUGIN,
-                                               settings_plugin_interface_init))
+G_DEFINE_TYPE (SettingsPluginIfupdown, settings_plugin_ifupdown, NM_TYPE_SETTINGS_PLUGIN)
 
 #define SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfupdown, SETTINGS_IS_PLUGIN_IFUPDOWN)
 
@@ -99,169 +81,41 @@ NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfupdown, settings_plugin_ifupdown_get
 
 /*****************************************************************************/
 
-static void
-bind_device_to_connection (SettingsPluginIfupdown *self,
-                           struct udev_device *device,
-                           NMIfupdownConnection *exported)
-{
-	NMSettingWired *s_wired;
-	NMSettingWireless *s_wifi;
-	const char *iface, *address;
-
-	iface = udev_device_get_sysname (device);
-	if (!iface) {
-		nm_log_warn (LOGD_SETTINGS, "failed to get ifname for device.");
-		return;
-	}
-
-	address = udev_device_get_sysattr_value (device, "address");
-	if (!address || !address[0]) {
-		nm_log_warn (LOGD_SETTINGS, "failed to get MAC address for %s", iface);
-		return;
-	}
-
-	if (!nm_utils_hwaddr_valid (address, ETH_ALEN)) {
-		nm_log_warn (LOGD_SETTINGS, "failed to parse MAC address '%s' for %s",
-		             address, iface);
-		return;
-	}
-
-	s_wired = nm_connection_get_setting_wired (NM_CONNECTION (exported));
-	s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (exported));
-	if (s_wired) {
-		nm_log_info (LOGD_SETTINGS, "locking wired connection setting");
-		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, address, NULL);
-	} else if (s_wifi) {
-		nm_log_info (LOGD_SETTINGS, "locking wireless connection setting");
-		g_object_set (s_wifi, NM_SETTING_WIRELESS_MAC_ADDRESS, address, NULL);
-	}
-
-	nm_settings_connection_update (NM_SETTINGS_CONNECTION (exported),
-	                               NULL,
-	                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-	                               NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                               "ifupdown-new",
-	                               NULL);
-}
-
-static void
-udev_device_added (SettingsPluginIfupdown *self, struct udev_device *device)
-{
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	const char *iface, *path;
-	NMIfupdownConnection *exported;
-
-	iface = udev_device_get_sysname (device);
-	path = udev_device_get_syspath (device);
-	if (!iface || !path)
-		return;
-
-	nm_log_info (LOGD_SETTINGS, "devices added (path: %s, iface: %s)", path, iface);
-
-	/* if we have a configured connection for this particular iface
-	 * we want to either unmanage the device or lock it
-	 */
-	exported = g_hash_table_lookup (priv->connections, iface);
-	if (!exported && !g_hash_table_lookup (priv->eni_ifaces, iface)) {
-		nm_log_info (LOGD_SETTINGS, "device added (path: %s, iface: %s): no ifupdown configuration found.",
-		             path, iface);
-		return;
-	}
-
-	g_hash_table_insert (priv->kernel_ifaces, g_strdup (iface), udev_device_ref (device));
-
-	if (exported)
-		bind_device_to_connection (self, device, exported);
-
-	if (ALWAYS_UNMANAGE || priv->unmanage_well_known)
-		g_signal_emit_by_name (G_OBJECT (self), NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
-}
-
-static void
-udev_device_removed (SettingsPluginIfupdown *self, struct udev_device *device)
-{
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	const char *iface, *path;
-
-	iface = udev_device_get_sysname (device);
-	path = udev_device_get_syspath (device);
-	if (!iface || !path)
-		return;
-
-	nm_log_info (LOGD_SETTINGS, "devices removed (path: %s, iface: %s)", path, iface);
-
-	if (!g_hash_table_remove (priv->kernel_ifaces, iface))
-		return;
-
-	if (ALWAYS_UNMANAGE || priv->unmanage_well_known)
-		g_signal_emit_by_name (G_OBJECT (self), NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
-}
-
-static void
-udev_device_changed (SettingsPluginIfupdown *self, struct udev_device *device)
-{
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	const char *iface, *path;
-
-	iface = udev_device_get_sysname (device);
-	path = udev_device_get_syspath (device);
-	if (!iface || !path)
-		return;
-
-	nm_log_info (LOGD_SETTINGS, "device changed (path: %s, iface: %s)", path, iface);
-
-	if (!g_hash_table_lookup (priv->kernel_ifaces, iface))
-		return;
-
-	if (ALWAYS_UNMANAGE || priv->unmanage_well_known)
-		g_signal_emit_by_name (G_OBJECT (self), NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
-}
-
-static void
-handle_uevent (NMUdevClient *client,
-               struct udev_device *device,
-               gpointer user_data)
-{
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (user_data);
-	const char *subsys;
-	const char *action;
-
-	action = udev_device_get_action (device);
-
-	g_return_if_fail (action != NULL);
+#define _NMLOG_PREFIX_NAME      "ifupdown"
+#define _NMLOG_DOMAIN           LOGD_SETTINGS
+#define _NMLOG(level, ...) \
+    nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
+            "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+            _NMLOG_PREFIX_NAME": " \
+            _NM_UTILS_MACRO_REST (__VA_ARGS__))
 
-	/* A bit paranoid */
-	subsys = udev_device_get_subsystem (device);
-	g_return_if_fail (nm_streq0 (subsys, "net"));
-
-	if (!strcmp (action, "add"))
-		udev_device_added (self, device);
-	else if (!strcmp (action, "remove"))
-		udev_device_removed (self, device);
-	else if (!strcmp (action, "change"))
-		udev_device_changed (self, device);
-}
+/*****************************************************************************/
 
 /* Returns the plugins currently known list of connections.  The returned
  * list is freed by the system settings service.
  */
 static GSList*
-get_connections (NMSettingsPlugin *config)
+get_connections (NMSettingsPlugin *plugin)
 {
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE ((SettingsPluginIfupdown *) config);
-	GSList *connections;
-
-	nm_log_info (LOGD_SETTINGS, "(%d) ... get_connections.", GPOINTER_TO_UINT(config));
+	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
+	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
+	GSList *list = NULL;
+	GHashTableIter iter;
+	void *value;
 
-	if(priv->unmanage_well_known) {
-		nm_log_info (LOGD_SETTINGS, "(%d) ... get_connections (managed=false): return empty list.", GPOINTER_TO_UINT(config));
+	if (!priv->ifupdown_managed) {
+		_LOGD ("get_connections: not connections due to managed=false");
 		return NULL;
 	}
 
-	connections = _nm_utils_hash_values_to_slist (priv->connections);
+	g_hash_table_iter_init (&iter, priv->eni_ifaces);
+	while (g_hash_table_iter_next (&iter, NULL, &value)) {
+		if (value)
+			list = g_slist_prepend (list, value);
+	}
 
-	nm_log_info (LOGD_SETTINGS, "(%d) connections count: %d", GPOINTER_TO_UINT(config), g_slist_length(connections));
-	return connections;
+	_LOGD ("get_connections: %u connections", g_slist_length (list));
+	return list;
 }
 
 /*
@@ -270,202 +124,178 @@ get_connections (NMSettingsPlugin *config)
  * each element must be allocated using g_malloc() or its variants.
  */
 static GSList*
-get_unmanaged_specs (NMSettingsPlugin *config)
+get_unmanaged_specs (NMSettingsPlugin *plugin)
 {
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE ((SettingsPluginIfupdown *) config);
+	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
+	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
 	GSList *specs = NULL;
 	GHashTableIter iter;
-	struct udev_device *device;
 	const char *iface;
 
-	if (!ALWAYS_UNMANAGE && !priv->unmanage_well_known)
+	if (priv->ifupdown_managed)
 		return NULL;
 
-	nm_log_info (LOGD_SETTINGS, "get unmanaged devices count: %d",
-	             g_hash_table_size (priv->kernel_ifaces));
-
-	g_hash_table_iter_init (&iter, priv->kernel_ifaces);
-	while (g_hash_table_iter_next (&iter, (gpointer) &iface, (gpointer) &device)) {
-		const char *address;
+	_LOGD ("unmanaged-specs: unmanaged devices count %u",
+	       g_hash_table_size (priv->eni_ifaces));
 
-		address = udev_device_get_sysattr_value (device, "address");
-		if (address)
-			specs = g_slist_append (specs, g_strdup_printf ("mac:%s", address));
-		else
-			specs = g_slist_append (specs, g_strdup_printf ("interface-name:%s", iface));
-	}
+	g_hash_table_iter_init (&iter, priv->eni_ifaces);
+	while (g_hash_table_iter_next (&iter, (gpointer) &iface, NULL))
+		specs = g_slist_append (specs, g_strdup_printf ("interface-name:=%s", iface));
 	return specs;
 }
 
 /*****************************************************************************/
 
 static void
-_udev_device_unref (gpointer ptr)
+initialize (NMSettingsPlugin *plugin)
 {
-	udev_device_unref (ptr);
-}
-
-static void
-init (NMSettingsPlugin *config)
-{
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (config);
+	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
 	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	GHashTable *auto_ifaces;
-	if_block *block = NULL;
-	struct udev_enumerate *enumerate;
-	struct udev_list_entry *keys;
+	gs_unref_hashtable GHashTable *auto_ifaces = NULL;
+	nm_auto_ifparser if_parser *parser = NULL;
+	if_block *block;
 	GHashTableIter con_iter;
 	const char *block_name;
-	NMIfupdownConnection *connection;
-
-	auto_ifaces = g_hash_table_new (nm_str_hash, g_str_equal);
-
-	if(!priv->connections)
-		priv->connections = g_hash_table_new (nm_str_hash, g_str_equal);
-
-	if(!priv->kernel_ifaces)
-		priv->kernel_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _udev_device_unref);
+	NMIfupdownConnection *conn;
 
-	if(!priv->eni_ifaces)
-		priv->eni_ifaces = g_hash_table_new (nm_str_hash, g_str_equal);
+	parser = ifparser_parse (ENI_INTERFACES_FILE, 0);
 
-	nm_log_info (LOGD_SETTINGS, "init!");
+	c_list_for_each_entry (block, &parser->block_lst_head, block_lst) {
 
-	priv->udev_client = nm_udev_client_new ((const char *[]) { "net", NULL },
-	                                        handle_uevent, self);
-
-	/* Read in all the interfaces */
-	ifparser_init (ENI_INTERFACES_FILE, 0);
-	block = ifparser_getfirst ();
-	while (block) {
-		if(!strcmp ("auto", block->type) || !strcmp ("allow-hotplug", block->type))
-			g_hash_table_insert (auto_ifaces, block->name, GUINT_TO_POINTER (1));
-		else if (!strcmp ("iface", block->type)) {
-			NMIfupdownConnection *exported;
+		if (NM_IN_STRSET (block->type, "auto", "allow-hotplug")) {
+			if (!auto_ifaces)
+				auto_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+			g_hash_table_add (auto_ifaces, g_strdup (block->name));
+			continue;
+		}
 
+		if (nm_streq (block->type, "iface")) {
 			/* Bridge configuration */
-			if(!strncmp ("br", block->name, 2)) {
+			if (g_str_has_prefix (block_name, "br")) {
 				/* Try to find bridge ports */
 				const char *ports = ifparser_getkey (block, "bridge-ports");
+
 				if (ports) {
-					int i;
+					guint i;
 					int state = 0;
-					char **port_ifaces;
+					gs_strfreev char **port_ifaces = NULL;
 
-					nm_log_info (LOGD_SETTINGS, "found bridge ports %s for %s", ports, block->name);
+					_LOGD ("parse: found bridge ports %s for %s", ports, block->name);
 
 					port_ifaces = g_strsplit_set (ports, " \t", -1);
-					for (i = 0; i < g_strv_length (port_ifaces); i++) {
-						char *token = port_ifaces[i];
+					for (i = 0; port_ifaces[i]; i++) {
+						const char *token = port_ifaces[i];
+
 						/* Skip crazy stuff like regex or all */
-						if (!strcmp ("all", token)) {
+						if (nm_streq (token, "all"))
 							continue;
-						}
+
 						/* Small SM to skip everything inside regex */
-						if (!strcmp ("regex", token)) {
+						if (nm_streq (token, "regex")) {
 							state++;
 							continue;
 						}
-						if (!strcmp ("noregex", token)) {
+						if (nm_streq (token, "noregex")) {
 							state--;
 							continue;
 						}
+						if (nm_streq (token, "none"))
+							continue;
 						if (state == 0 && strlen (token) > 0) {
-							nm_log_info (LOGD_SETTINGS, "adding bridge port %s to eni_ifaces", token);
-							g_hash_table_insert (priv->eni_ifaces, g_strdup (token), "known");
+							conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
+							if (!conn) {
+								_LOGD ("parse: adding bridge port \"%s\"", token);
+								g_hash_table_insert (priv->eni_ifaces, g_strdup (token), NULL);
+							} else {
+								_LOGD ("parse: adding bridge port \"%s\" (have connection %s)", token,
+								       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
+							}
 						}
 					}
-					g_strfreev (port_ifaces);
 				}
-				goto next;
+				continue;
 			}
 
 			/* Skip loopback configuration */
-			if(!strcmp ("lo", block->name)) {
-				goto next;
-			}
+			if (nm_streq (block->name, "lo"))
+				continue;
 
 			/* Remove any connection for this block that was previously found */
-			exported = g_hash_table_lookup (priv->connections, block->name);
-			if (exported) {
-				nm_log_info (LOGD_SETTINGS, "deleting %s from connections", block->name);
-				nm_settings_connection_delete (NM_SETTINGS_CONNECTION (exported), NULL);
-				g_hash_table_remove (priv->connections, block->name);
+			conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
+			if (conn) {
+				_LOGD ("parse: replace connection \"%s\" (%s)",
+				       block->name,
+				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
+				nm_settings_connection_delete (NM_SETTINGS_CONNECTION (conn), NULL);
+				g_hash_table_remove (priv->eni_ifaces, block->name);
 			}
 
 			/* add the new connection */
-			exported = nm_ifupdown_connection_new (block);
-			if (exported) {
-				nm_log_info (LOGD_SETTINGS, "adding %s to connections", block->name);
-				g_hash_table_insert (priv->connections, block->name, exported);
+			conn = nm_ifupdown_connection_new (block);
+			if (conn) {
+				_LOGD ("parse: adding connection \"%s\" (%s)", block->name,
+				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
+			} else
+				_LOGD ("parse: adding place holder for connection \"%s\"", block->name);
+			g_hash_table_insert (priv->eni_ifaces, g_strdup (block->name), conn);
+			continue;
+		}
+
+		if (nm_streq (block->type, "mapping")) {
+			conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
+			if (!conn) {
+				_LOGD ("parse: adding mapping \"%s\"", block->name);
+				g_hash_table_insert (priv->eni_ifaces, g_strdup (block->name), NULL);
+			} else {
+				_LOGD ("parse: adding mapping \"%s\" (have connection %s)", block->name,
+				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
 			}
-			nm_log_info (LOGD_SETTINGS, "adding iface %s to eni_ifaces", block->name);
-			g_hash_table_insert (priv->eni_ifaces, block->name, "known");
-		} else if (!strcmp ("mapping", block->type)) {
-			g_hash_table_insert (priv->eni_ifaces, block->name, "known");
-			nm_log_info (LOGD_SETTINGS, "adding mapping %s to eni_ifaces", block->name);
+			continue;
 		}
-	next:
-		block = block->next;
 	}
 
 	/* Make 'auto' interfaces autoconnect=TRUE */
-	g_hash_table_iter_init (&con_iter, priv->connections);
-	while (g_hash_table_iter_next (&con_iter, (gpointer) &block_name, (gpointer) &connection)) {
+	g_hash_table_iter_init (&con_iter, priv->eni_ifaces);
+	while (g_hash_table_iter_next (&con_iter, (gpointer) &block_name, (gpointer) &conn)) {
 		NMSettingConnection *setting;
 
-		if (g_hash_table_lookup (auto_ifaces, block_name)) {
-			setting = nm_connection_get_setting_connection (NM_CONNECTION (connection));
-			g_object_set (setting, NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, NULL);
-			nm_log_info (LOGD_SETTINGS, "autoconnect");
-		}
+		if (   !conn
+		    || !auto_ifaces
+		    || !g_hash_table_contains (auto_ifaces, block_name))
+			continue;
+
+		/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
+		setting = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (conn)));
+		g_object_set (setting, NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, NULL);
 	}
-	g_hash_table_destroy (auto_ifaces);
 
 	/* Check the config file to find out whether to manage interfaces */
-	priv->unmanage_well_known = !nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
-	                                                               NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
-	                                                               NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
-	                                                               !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT);
-	nm_log_info (LOGD_SETTINGS, "management mode: %s", priv->unmanage_well_known ? "unmanaged" : "managed");
-
-	/* Add well-known interfaces */
-	enumerate = nm_udev_client_enumerate_new (priv->udev_client);
-	udev_enumerate_scan_devices (enumerate);
-	keys = udev_enumerate_get_list_entry (enumerate);
-	for (; keys; keys = udev_list_entry_get_next (keys)) {
-		struct udev_device *udevice;
-
-		udevice = udev_device_new_from_syspath (udev_enumerate_get_udev (enumerate),
-		                                        udev_list_entry_get_name (keys));
-		if (udevice) {
-			udev_device_added (self, udevice);
-			udev_device_unref (udevice);
-		}
-	}
-	udev_enumerate_unref (enumerate);
+	priv->ifupdown_managed = nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
+	                                                           NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
+	                                                           NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
+	                                                           !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT);
+	_LOGI ("management mode: %s", priv->ifupdown_managed ? "managed" : "unmanaged");
 
 	/* Now if we're running in managed mode, let NM know there are new connections */
-	if (!priv->unmanage_well_known) {
-		GList *con_list = g_hash_table_get_values (priv->connections);
-		GList *cl_iter;
-
-		for (cl_iter = con_list; cl_iter; cl_iter = g_list_next (cl_iter)) {
-			g_signal_emit_by_name (self,
-			                       NM_SETTINGS_PLUGIN_CONNECTION_ADDED,
-			                       NM_SETTINGS_CONNECTION (cl_iter->data));
+	if (priv->ifupdown_managed) {
+		GHashTableIter iter;
+
+		g_hash_table_iter_init (&iter, priv->eni_ifaces);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) conn)) {
+			_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
+			                                                  NM_SETTINGS_CONNECTION (conn));
 		}
-		g_list_free (con_list);
 	}
-
-	nm_log_info (LOGD_SETTINGS, "end _init.");
 }
 
 /*****************************************************************************/
 
 static void
-settings_plugin_ifupdown_init (SettingsPluginIfupdown *plugin)
+settings_plugin_ifupdown_init (SettingsPluginIfupdown *self)
 {
+	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
+
+	priv->eni_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 }
 
 static void
@@ -474,35 +304,28 @@ dispose (GObject *object)
 	SettingsPluginIfupdown *plugin = SETTINGS_PLUGIN_IFUPDOWN (object);
 	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (plugin);
 
-	g_clear_pointer (&priv->kernel_ifaces, g_hash_table_destroy);
 	g_clear_pointer (&priv->eni_ifaces, g_hash_table_destroy);
 
-	priv->udev_client = nm_udev_client_unref (priv->udev_client);
-
 	G_OBJECT_CLASS (settings_plugin_ifupdown_parent_class)->dispose (object);
 }
 
 static void
-settings_plugin_ifupdown_class_init (SettingsPluginIfupdownClass *req_class)
+settings_plugin_ifupdown_class_init (SettingsPluginIfupdownClass *klass)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (req_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->dispose = dispose;
-}
 
-static void
-settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface)
-{
-	plugin_iface->init = init;
-	plugin_iface->get_connections = get_connections;
-	plugin_iface->get_unmanaged_specs = get_unmanaged_specs;
+	plugin_class->initialize          = initialize;
+	plugin_class->get_connections     = get_connections;
+	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
 }
 
 /*****************************************************************************/
 
-G_MODULE_EXPORT GObject *
+G_MODULE_EXPORT NMSettingsPlugin *
 nm_settings_plugin_factory (void)
 {
-	return G_OBJECT (g_object_ref (settings_plugin_ifupdown_get ()));
+	return NM_SETTINGS_PLUGIN (g_object_ref (settings_plugin_ifupdown_get ()));
 }
-
diff --git a/src/settings/plugins/ifupdown/tests/meson.build b/src/settings/plugins/ifupdown/tests/meson.build
index ee3b6a34..5a2383d9 100644
--- a/src/settings/plugins/ifupdown/tests/meson.build
+++ b/src/settings/plugins/ifupdown/tests/meson.build
@@ -4,7 +4,6 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  c_args: nm_build_cflags,
   link_with: libnms_ifupdown_core
 )
 
diff --git a/src/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
index 18bad65e..e96350c0 100644
--- a/src/settings/plugins/ifupdown/tests/test-ifupdown.c
+++ b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -130,16 +130,15 @@ expected_free (Expected *e)
 }
 
 static void
-compare_expected_to_ifparser (Expected *e)
+compare_expected_to_ifparser (if_parser *parser, Expected *e)
 {
 	if_block *n;
 	GSList *biter, *kiter;
 
-	g_assert_cmpint (g_slist_length (e->blocks), ==, ifparser_get_num_blocks ());
+	g_assert_cmpint (g_slist_length (e->blocks), ==, ifparser_get_num_blocks (parser));
 
-	for (n = ifparser_getfirst (), biter = e->blocks;
-	     n && biter;
-	     n = n->next, biter = g_slist_next (biter)) {
+	biter = e->blocks;
+	c_list_for_each_entry (n, &parser->block_lst_head, block_lst) {
 		if_data *m;
 		ExpectedBlock *b = biter->data;
 
@@ -150,26 +149,31 @@ compare_expected_to_ifparser (Expected *e)
 
 		g_assert_cmpint (g_slist_length (b->keys), ==, ifparser_get_num_info (n));
 
-		for (m = n->info, kiter = b->keys;
-		     m && kiter;
-		     m = m->next, kiter = g_slist_next (kiter)) {
+		kiter = b->keys;
+		c_list_for_each_entry (m, &n->data_lst_head, data_lst) {
 			ExpectedKey *k = kiter->data;
 
 			g_assert (k->key && m->key);
 			g_assert_cmpstr (k->key, ==, m->key);
 			g_assert (k->data && m->data);
 			g_assert_cmpstr (k->data, ==, m->data);
+
+			kiter = g_slist_next (kiter);
 		}
+		g_assert (!kiter);
+
+		biter = g_slist_next (biter);
 	}
+	g_assert (!biter);
 }
 
 static void
-dump_blocks (void)
+dump_blocks (if_parser *parser)
 {
 	if_block *n;
 
 	g_message ("\n***************************************************");
-	for (n = ifparser_getfirst (); n != NULL; n = n->next) {
+	c_list_for_each_entry (n, &parser->block_lst_head, block_lst) {
 		if_data *m;
 
 		// each block start with its type & name
@@ -178,8 +182,8 @@ dump_blocks (void)
 
 		// each key-value pair within a block is indented & separated by a tab
 		// (single quotes used to show typ & name baoundaries)
-		for (m = n->info; m != NULL; m = m->next)
-			   g_print("\t'%s'\t'%s'\n", m->key, m->data);
+		c_list_for_each_entry (m, &n->data_lst_head, data_lst)
+			g_print("\t'%s'\t'%s'\n", m->key, m->data);
 
 		// blocks are separated by an empty line
 		g_print("\n");
@@ -187,21 +191,24 @@ dump_blocks (void)
 	g_message ("##################################################\n");
 }
 
-static void
-init_ifparser_with_file (const char *path, const char *file)
+static if_parser *
+init_ifparser_with_file (const char *file)
 {
-	char *tmp;
+	if_parser *parser;
+	gs_free char *tmp = NULL;
 
-	tmp = g_strdup_printf ("%s/%s", path, file);
-	ifparser_init (tmp, 1);
-	g_free (tmp);
+	tmp = g_strdup_printf ("%s/%s", TEST_DIR, file);
+	parser = ifparser_parse (tmp, 1);
+	g_assert (parser);
+	return parser;
 }
 
 static void
-test1_ignore_line_before_first_block (const char *path)
+test1_ignore_line_before_first_block (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test1");
 
 	e = expected_new ();
 	b = expected_block_new ("auto", "eth0");
@@ -210,35 +217,33 @@ test1_ignore_line_before_first_block (const char *path)
 	expected_add_block (e, b);
 	expected_block_add_key (b, expected_key_new ("inet", "dhcp"));
 
-	init_ifparser_with_file (path, "test1");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test2_wrapped_line (const char *path)
+test2_wrapped_line (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test2");
 
 	e = expected_new ();
 	b = expected_block_new ("auto", "lo");
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test2");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test3_wrapped_multiline_multiarg (const char *path)
+test3_wrapped_multiline_multiarg (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test3");
 
 	e = expected_new ();
 	b = expected_block_new ("allow-hotplug", "eth0");
@@ -248,35 +253,33 @@ test3_wrapped_multiline_multiarg (const char *path)
 	b = expected_block_new ("allow-hotplug", "bnep0");
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test3");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test4_allow_auto_is_auto (const char *path)
+test4_allow_auto_is_auto (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test4");
 
 	e = expected_new ();
 	b = expected_block_new ("auto", "eth0");
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test4");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test5_allow_auto_multiarg (const char *path)
+test5_allow_auto_multiarg (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test5");
 
 	e = expected_new ();
 	b = expected_block_new ("allow-hotplug", "eth0");
@@ -284,52 +287,50 @@ test5_allow_auto_multiarg (const char *path)
 	b = expected_block_new ("allow-hotplug", "wlan0");
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test5");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test6_mixed_whitespace (const char *path)
+test6_mixed_whitespace (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test6");
 
 	e = expected_new ();
 	b = expected_block_new ("iface", "lo");
 	expected_block_add_key (b, expected_key_new ("inet", "loopback"));
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test6");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test7_long_line (const char *path)
+test7_long_line (void)
 {
-	init_ifparser_with_file (path, "test7");
-	g_assert_cmpint (ifparser_get_num_blocks (), ==, 0);
-	ifparser_destroy ();
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test7");
+
+	g_assert_cmpint (ifparser_get_num_blocks (parser), ==, 0);
 }
 
 static void
-test8_long_line_wrapped (const char *path)
+test8_long_line_wrapped (void)
 {
-	init_ifparser_with_file (path, "test8");
-	g_assert_cmpint (ifparser_get_num_blocks (), ==, 0);
-	ifparser_destroy ();
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test8");
+
+	g_assert_cmpint (ifparser_get_num_blocks (parser), ==, 0);
 }
 
 static void
-test9_wrapped_lines_in_block (const char *path)
+test9_wrapped_lines_in_block (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test9");
 
 	e = expected_new ();
 	b = expected_block_new ("iface", "eth0");
@@ -340,18 +341,17 @@ test9_wrapped_lines_in_block (const char *path)
 	expected_block_add_key (b, expected_key_new ("broadcast", "10.250.2.63"));
 	expected_block_add_key (b, expected_key_new ("gateway", "10.250.2.50"));
 
-	init_ifparser_with_file (path, "test9");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test11_complex_wrap (const char *path)
+test11_complex_wrap (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test11");
 
 	e = expected_new ();
 	b = expected_block_new ("iface", "pppoe");
@@ -359,18 +359,17 @@ test11_complex_wrap (const char *path)
 	expected_block_add_key (b, expected_key_new ("inet", "manual"));
 	expected_block_add_key (b, expected_key_new ("pre-up", "/sbin/ifconfig eth0 up"));
 
-	init_ifparser_with_file (path, "test11");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test12_complex_wrap_split_word (const char *path)
+test12_complex_wrap_split_word (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test12");
 
 	e = expected_new ();
 	b = expected_block_new ("iface", "pppoe");
@@ -378,36 +377,34 @@ test12_complex_wrap_split_word (const char *path)
 	expected_block_add_key (b, expected_key_new ("inet", "manual"));
 	expected_block_add_key (b, expected_key_new ("up", "ifup ppp0=dsl"));
 
-	init_ifparser_with_file (path, "test12");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test13_more_mixed_whitespace (const char *path)
+test13_more_mixed_whitespace (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test13");
 
 	e = expected_new ();
 	b = expected_block_new ("iface", "dsl");
 	expected_block_add_key (b, expected_key_new ("inet", "ppp"));
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test13");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test14_mixed_whitespace_block_start (const char *path)
+test14_mixed_whitespace_block_start (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test14");
 
 	e = expected_new ();
 	b = expected_block_new ("iface", "wlan0");
@@ -420,47 +417,43 @@ test14_mixed_whitespace_block_start (const char *path)
 	expected_block_add_key (b, expected_key_new ("inet", "dhcp"));
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test14");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test15_trailing_space (const char *path)
+test15_trailing_space (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test15");
 
 	e = expected_new ();
 	b = expected_block_new ("iface", "bnep0");
 	expected_block_add_key (b, expected_key_new ("inet", "static"));
 	expected_add_block (e, b);
 
-	init_ifparser_with_file (path, "test15");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test16_missing_newline (const char *path)
+test16_missing_newline (void)
 {
 	Expected *e;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test16");
 
 	e = expected_new ();
 	expected_add_block (e, expected_block_new ("mapping", "eth0"));
 
-	init_ifparser_with_file (path, "test16");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 static void
-test17_read_static_ipv4 (const char *path)
+test17_read_static_ipv4 (void)
 {
 	NMConnection *connection;
 	NMSettingConnection *s_con;
@@ -470,13 +463,13 @@ test17_read_static_ipv4 (const char *path)
 	gboolean success;
 	NMIPAddress *ip4_addr;
 	if_block *block = NULL;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test17-wired-static-verify-ip4");
 
-	init_ifparser_with_file (path, "test17-wired-static-verify-ip4");
-	block = ifparser_getfirst ();
+	block = ifparser_getfirst (parser);
 	connection = nm_simple_connection_new();
 	g_assert (connection);
 
-	ifupdown_update_connection_from_if_block(connection, block, &error);
+	ifupdown_update_connection_from_if_block (connection, block, &error);
 	g_assert_no_error (error);
 
 	success = nm_connection_verify (connection, &error);
@@ -511,12 +504,11 @@ test17_read_static_ipv4 (const char *path)
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip4, 0), ==, "example.com");
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip4, 1), ==, "foo.example.com");
 
-	ifparser_destroy ();
 	g_object_unref (connection);
 }
 
 static void
-test18_read_static_ipv6 (const char *path)
+test18_read_static_ipv6 (void)
 {
 	NMConnection *connection;
 	NMSettingConnection *s_con;
@@ -526,12 +518,12 @@ test18_read_static_ipv6 (const char *path)
 	gboolean success;
 	NMIPAddress *ip6_addr;
 	if_block *block = NULL;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test18-wired-static-verify-ip6");
 
-	init_ifparser_with_file (path, "test18-wired-static-verify-ip6");
-	block = ifparser_getfirst ();
+	block = ifparser_getfirst (parser);
 	connection = nm_simple_connection_new();
 	g_assert (connection);
-	ifupdown_update_connection_from_if_block(connection, block, &error);
+	ifupdown_update_connection_from_if_block (connection, block, &error);
 	g_assert_no_error (error);
 
 	success = nm_connection_verify (connection, &error);
@@ -566,12 +558,11 @@ test18_read_static_ipv6 (const char *path)
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip6, 0), ==, "example.com");
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip6, 1), ==, "foo.example.com");
 
-	ifparser_destroy ();
 	g_object_unref (connection);
 }
 
 static void
-test19_read_static_ipv4_plen (const char *path)
+test19_read_static_ipv4_plen (void)
 {
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4;
@@ -579,12 +570,12 @@ test19_read_static_ipv4_plen (const char *path)
 	NMIPAddress *ip4_addr;
 	if_block *block = NULL;
 	gboolean success;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test19-wired-static-verify-ip4-plen");
 
-	init_ifparser_with_file (path, "test19-wired-static-verify-ip4-plen");
-	block = ifparser_getfirst ();
+	block = ifparser_getfirst (parser);
 	connection = nm_simple_connection_new();
 	g_assert (connection);
-	ifupdown_update_connection_from_if_block(connection, block, &error);
+	ifupdown_update_connection_from_if_block (connection, block, &error);
 	g_assert_no_error (error);
 
 	success = nm_connection_verify (connection, &error);
@@ -601,15 +592,15 @@ test19_read_static_ipv4_plen (const char *path)
 	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "10.0.0.3");
 	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 8);
 
-	ifparser_destroy ();
 	g_object_unref (connection);
 }
 
 static void
-test20_source_stanza (const char *path)
+test20_source_stanza (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test20-source-stanza");
 
 	e = expected_new ();
 
@@ -625,18 +616,17 @@ test20_source_stanza (const char *path)
 	expected_add_block (e, b);
 	expected_block_add_key (b, expected_key_new ("inet", "dhcp"));
 
-	init_ifparser_with_file (path, "test20-source-stanza");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
 static void
-test21_source_dir_stanza (const char *path)
+test21_source_dir_stanza (void)
 {
 	Expected *e;
 	ExpectedBlock *b;
+	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test21-source-dir-stanza");
 
 	e = expected_new ();
 
@@ -646,10 +636,8 @@ test21_source_dir_stanza (const char *path)
 	expected_add_block (e, b);
 	expected_block_add_key (b, expected_key_new ("inet", "dhcp"));
 
-	init_ifparser_with_file (path, "test21-source-dir-stanza");
-	compare_expected_to_ifparser (e);
+	compare_expected_to_ifparser (parser, e);
 
-	ifparser_destroy ();
 	expected_free (e);
 }
 
@@ -660,49 +648,28 @@ main (int argc, char **argv)
 {
 	nmtst_init_assert_logging (&argc, &argv, "WARN", "DEFAULT");
 
-	if (0)
-		dump_blocks ();
-
-	g_test_add_data_func ("/ifupdate/ignore_line_before_first_block", TEST_DIR,
-	                      (GTestDataFunc) test1_ignore_line_before_first_block);
-	g_test_add_data_func ("/ifupdate/wrapped_line", TEST_DIR,
-	                      (GTestDataFunc) test2_wrapped_line);
-	g_test_add_data_func ("/ifupdate/wrapped_multiline_multiarg", TEST_DIR,
-	                      (GTestDataFunc) test3_wrapped_multiline_multiarg);
-	g_test_add_data_func ("/ifupdate/allow_auto_is_auto", TEST_DIR,
-	                      (GTestDataFunc) test4_allow_auto_is_auto);
-	g_test_add_data_func ("/ifupdate/allow_auto_multiarg", TEST_DIR,
-	                      (GTestDataFunc) test5_allow_auto_multiarg);
-	g_test_add_data_func ("/ifupdate/mixed_whitespace", TEST_DIR,
-	                      (GTestDataFunc) test6_mixed_whitespace);
-	g_test_add_data_func ("/ifupdate/long_line", TEST_DIR,
-	                      (GTestDataFunc) test7_long_line);
-	g_test_add_data_func ("/ifupdate/long_line_wrapped", TEST_DIR,
-	                      (GTestDataFunc) test8_long_line_wrapped);
-	g_test_add_data_func ("/ifupdate/wrapped_lines_in_block", TEST_DIR,
-	                      (GTestDataFunc) test9_wrapped_lines_in_block);
-	g_test_add_data_func ("/ifupdate/complex_wrap", TEST_DIR,
-	                      (GTestDataFunc) test11_complex_wrap);
-	g_test_add_data_func ("/ifupdate/complex_wrap_split_word", TEST_DIR,
-	                      (GTestDataFunc) test12_complex_wrap_split_word);
-	g_test_add_data_func ("/ifupdate/more_mixed_whitespace", TEST_DIR,
-	                      (GTestDataFunc) test13_more_mixed_whitespace);
-	g_test_add_data_func ("/ifupdate/mixed_whitespace_block_start", TEST_DIR,
-	                      (GTestDataFunc) test14_mixed_whitespace_block_start);
-	g_test_add_data_func ("/ifupdate/trailing_space", TEST_DIR,
-	                      (GTestDataFunc) test15_trailing_space);
-	g_test_add_data_func ("/ifupdate/missing_newline", TEST_DIR,
-	                      (GTestDataFunc) test16_missing_newline);
-	g_test_add_data_func ("/ifupdate/read_static_ipv4", TEST_DIR,
-	                      (GTestDataFunc) test17_read_static_ipv4);
-	g_test_add_data_func ("/ifupdate/read_static_ipv6", TEST_DIR,
-	                      (GTestDataFunc) test18_read_static_ipv6);
-	g_test_add_data_func ("/ifupdate/read_static_ipv4_plen", TEST_DIR,
-	                      (GTestDataFunc) test19_read_static_ipv4_plen);
-	g_test_add_data_func ("/ifupdate/source_stanza", TEST_DIR,
-	                      (GTestDataFunc) test20_source_stanza);
-	g_test_add_data_func ("/ifupdate/source_dir_stanza", TEST_DIR,
-	                      (GTestDataFunc) test21_source_dir_stanza);
+	(void) dump_blocks;
+
+	g_test_add_func ("/ifupdate/ignore_line_before_first_block", test1_ignore_line_before_first_block);
+	g_test_add_func ("/ifupdate/wrapped_line",                   test2_wrapped_line);
+	g_test_add_func ("/ifupdate/wrapped_multiline_multiarg",     test3_wrapped_multiline_multiarg);
+	g_test_add_func ("/ifupdate/allow_auto_is_auto",             test4_allow_auto_is_auto);
+	g_test_add_func ("/ifupdate/allow_auto_multiarg",            test5_allow_auto_multiarg);
+	g_test_add_func ("/ifupdate/mixed_whitespace",               test6_mixed_whitespace);
+	g_test_add_func ("/ifupdate/long_line",                      test7_long_line);
+	g_test_add_func ("/ifupdate/long_line_wrapped",              test8_long_line_wrapped);
+	g_test_add_func ("/ifupdate/wrapped_lines_in_block",         test9_wrapped_lines_in_block);
+	g_test_add_func ("/ifupdate/complex_wrap",                   test11_complex_wrap);
+	g_test_add_func ("/ifupdate/complex_wrap_split_word",        test12_complex_wrap_split_word);
+	g_test_add_func ("/ifupdate/more_mixed_whitespace",          test13_more_mixed_whitespace);
+	g_test_add_func ("/ifupdate/mixed_whitespace_block_start",   test14_mixed_whitespace_block_start);
+	g_test_add_func ("/ifupdate/trailing_space",                 test15_trailing_space);
+	g_test_add_func ("/ifupdate/missing_newline",                test16_missing_newline);
+	g_test_add_func ("/ifupdate/read_static_ipv4",               test17_read_static_ipv4);
+	g_test_add_func ("/ifupdate/read_static_ipv6",               test18_read_static_ipv6);
+	g_test_add_func ("/ifupdate/read_static_ipv4_plen",          test19_read_static_ipv4_plen);
+	g_test_add_func ("/ifupdate/source_stanza",                  test20_source_stanza);
+	g_test_add_func ("/ifupdate/source_dir_stanza",              test21_source_dir_stanza);
 
 	return g_test_run ();
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
index 5f72a9fa..64e94b26 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-connection.c
@@ -75,9 +75,7 @@ commit_changes (NMSettingsConnection *connection,
 	                                    error))
 		return FALSE;
 
-	/* Update the filename if it changed */
-	if (   path
-	    && g_strcmp0 (path, nm_settings_connection_get_filename (connection)) != 0) {
+	if (!nm_streq0 (path, nm_settings_connection_get_filename (connection))) {
 		gs_free char *old_path = g_strdup (nm_settings_connection_get_filename (connection));
 
 		nm_settings_connection_set_filename (connection, path);
@@ -142,7 +140,7 @@ nms_keyfile_connection_new (NMConnection *source,
 		if (!tmp)
 			return NULL;
 
-		uuid = nm_connection_get_uuid (NM_CONNECTION (tmp));
+		uuid = nm_connection_get_uuid (tmp);
 		if (!uuid) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Connection in file %s had no UUID", full_path);
@@ -154,9 +152,9 @@ nms_keyfile_connection_new (NMConnection *source,
 		update_unsaved = FALSE;
 	}
 
-	object = (GObject *) g_object_new (NMS_TYPE_KEYFILE_CONNECTION,
-	                                   NM_SETTINGS_CONNECTION_FILENAME, full_path,
-	                                   NULL);
+	object = g_object_new (NMS_TYPE_KEYFILE_CONNECTION,
+	                       NM_SETTINGS_CONNECTION_FILENAME, full_path,
+	                       NULL);
 
 	/* Update our settings with what was read from the file */
 	if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 3723db94..89b89467 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -56,19 +56,15 @@ typedef struct {
 } NMSKeyfilePluginPrivate;
 
 struct _NMSKeyfilePlugin {
-	GObject parent;
+	NMSettingsPlugin parent;
 	NMSKeyfilePluginPrivate _priv;
 };
 
 struct _NMSKeyfilePluginClass {
-	GObjectClass parent;
+	NMSettingsPluginClass parent;
 };
 
-static void settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface);
-
-G_DEFINE_TYPE_EXTENDED (NMSKeyfilePlugin, nms_keyfile_plugin, G_TYPE_OBJECT, 0,
-                        G_IMPLEMENT_INTERFACE (NM_TYPE_SETTINGS_PLUGIN,
-                                               settings_plugin_interface_init))
+G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
 #define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN)
 
@@ -85,10 +81,10 @@ G_DEFINE_TYPE_EXTENDED (NMSKeyfilePlugin, nms_keyfile_plugin, G_TYPE_OBJECT, 0,
 /*****************************************************************************/
 
 static void
-connection_removed_cb (NMSettingsConnection *obj, gpointer user_data)
+connection_removed_cb (NMSettingsConnection *sett_conn, NMSKeyfilePlugin *self)
 {
-	g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) user_data)->connections,
-	                     nm_connection_get_uuid (NM_CONNECTION (obj)));
+	g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
+	                     nm_settings_connection_get_uuid (sett_conn));
 }
 
 /* Monitoring */
@@ -106,7 +102,7 @@ remove_connection (NMSKeyfilePlugin *self, NMSKeyfileConnection *connection)
 	g_object_ref (connection);
 	g_signal_handlers_disconnect_by_func (connection, connection_removed_cb, self);
 	removed = g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
-	                               nm_connection_get_uuid (NM_CONNECTION (connection)));
+	                               nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
 	nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
 	g_object_unref (connection);
 
@@ -197,7 +193,7 @@ update_connection (NMSKeyfilePlugin *self,
 		return NULL;
 	}
 
-	uuid = nm_connection_get_uuid (NM_CONNECTION (connection_new));
+	uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
 	connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);
 
 	if (   connection
@@ -240,8 +236,8 @@ update_connection (NMSKeyfilePlugin *self,
 
 		old_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid));
 
-		if (nm_connection_compare (NM_CONNECTION (connection_by_uuid),
-		                           NM_CONNECTION (connection_new),
+		if (nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
+		                           nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
 		                           NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
 		                           NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
 			/* Nothing to do... except updating the path. */
@@ -259,7 +255,7 @@ update_connection (NMSKeyfilePlugin *self,
 				_LOGI ("update and persist "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
 
 			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
-			                                    NM_CONNECTION (connection_new),
+			                                    nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
 			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 			                                    "keyfile-update",
@@ -287,8 +283,10 @@ update_connection (NMSKeyfilePlugin *self,
 		if (!source) {
 			/* Only raise the signal if we were called without source, i.e. if we read the connection from file.
 			 * Otherwise, we were called by add_connection() which does not expect the signal. */
-			g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_CONNECTION_ADDED, connection_new);
+			_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
+			                                                  NM_SETTINGS_CONNECTION (connection_new));
 		}
+
 		return connection_new;
 	}
 }
@@ -341,13 +339,14 @@ config_changed_cb (NMConfig *config,
                    NMConfigData *old_data,
                    NMSKeyfilePlugin *self)
 {
-	gs_free char *old_value = NULL, *new_value = NULL;
+	gs_free char *old_value = NULL;
+	gs_free char *new_value = NULL;
 
 	old_value = nm_config_data_get_value (old_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
 	new_value = nm_config_data_get_value (config_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
 
-	if (g_strcmp0 (old_value, new_value) != 0)
-		g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED);
+	if (!nm_streq0 (old_value, new_value))
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
 static void
@@ -616,20 +615,17 @@ dispose (GObject *object)
 }
 
 static void
-nms_keyfile_plugin_class_init (NMSKeyfilePluginClass *req_class)
+nms_keyfile_plugin_class_init (NMSKeyfilePluginClass *klass)
 {
-	GObjectClass *object_class = G_OBJECT_CLASS (req_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->constructed = constructed;
-	object_class->dispose = dispose;
-}
+	object_class->dispose     = dispose;
 
-static void
-settings_plugin_interface_init (NMSettingsPluginInterface *plugin_iface)
-{
-	plugin_iface->get_connections = get_connections;
-	plugin_iface->load_connection = load_connection;
-	plugin_iface->reload_connections = reload_connections;
-	plugin_iface->add_connection = add_connection;
-	plugin_iface->get_unmanaged_specs = get_unmanaged_specs;
+	plugin_class->get_connections     = get_connections;
+	plugin_class->load_connection     = load_connection;
+	plugin_class->reload_connections  = reload_connections;
+	plugin_class->add_connection      = add_connection;
+	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index cb4b8379..71578686 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -115,7 +115,7 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
 NMConnection *
 nms_keyfile_reader_from_file (const char *filename, GError **error)
 {
-	GKeyFile *key_file;
+	gs_unref_keyfile GKeyFile *key_file = NULL;
 	struct stat statbuf;
 	NMConnection *connection = NULL;
 	GError *verify_error = NULL;
@@ -144,11 +144,11 @@ nms_keyfile_reader_from_file (const char *filename, GError **error)
 
 	key_file = g_key_file_new ();
 	if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, error))
-		goto out;
+		return NULL;
 
 	connection = nms_keyfile_reader_from_keyfile (key_file, filename, TRUE, error);
 	if (!connection)
-		goto out;
+		return NULL;
 
 	/* Normalize and verify the connection */
 	if (!nm_connection_normalize (connection, NULL, NULL, &verify_error)) {
@@ -160,8 +160,6 @@ nms_keyfile_reader_from_file (const char *filename, GError **error)
 		connection = NULL;
 	}
 
-out:
-	g_key_file_free (key_file);
 	return connection;
 }
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.h b/src/settings/plugins/keyfile/nms-keyfile-utils.h
index cd3f42b9..d5ddb8d5 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -25,9 +25,9 @@
 
 #define NMS_KEYFILE_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
 #define NMS_KEYFILE_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
-#define NMS_KEYFILE_CONNECTION_LOG_ARG(con)    NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con))
+#define NMS_KEYFILE_CONNECTION_LOG_ARG(con)    NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con))
 #define NMS_KEYFILE_CONNECTION_LOG_FMTD        "%s (%s,\"%s\",%p)"
-#define NMS_KEYFILE_CONNECTION_LOG_ARGD(con)   NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_connection_get_uuid ((NMConnection *) (con)), nm_connection_get_id ((NMConnection *) (con)), (con)
+#define NMS_KEYFILE_CONNECTION_LOG_ARGD(con)   NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con)), (con)
 
 gboolean nms_keyfile_utils_should_ignore_file (const char *filename);
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 270a217e..194b97d4 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -34,6 +34,8 @@
 #include "nms-keyfile-utils.h"
 #include "nms-keyfile-reader.h"
 
+#include "nm-utils/nm-io-utils.h"
+
 /*****************************************************************************/
 
 typedef struct {
@@ -127,7 +129,7 @@ cert_writer (NMConnection *connection,
 		new_path = g_strdup_printf ("%s/%s-%s.%s", info->keyfile_dir, nm_connection_get_uuid (connection),
 		                            cert_data->vtable->file_suffix, ext);
 
-		success = nm_utils_file_set_contents (new_path, (const gchar *) blob_data,
+		success = nm_utils_file_set_contents (new_path, (const char *) blob_data,
 		                                      blob_len, 0600, &local);
 		if (success) {
 			/* Write the path value to the keyfile.
@@ -297,11 +299,6 @@ _internal_write_connection (NMConnection *connection,
 		return FALSE;
 	}
 
-	if (out_path && g_strcmp0 (existing_path, path)) {
-		*out_path = path;  /* pass path out to caller */
-		path = NULL;
-	}
-
 	if (out_reread || out_reread_same)
 	{
 		gs_unref_object NMConnection *reread = NULL;
@@ -333,6 +330,8 @@ _internal_write_connection (NMConnection *connection,
 		NM_SET_OUT (out_reread_same, reread_same);
 	}
 
+	NM_SET_OUT (out_path, g_steal_pointer (&path));
+
 	return TRUE;
 }
 
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 33aaa264..8b94b256 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -6,7 +6,6 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  c_args: nm_build_cflags,
 )
 
 test(
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index a3045f7d..b4c6b1e2 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -960,7 +960,7 @@ test_write_intlist_ssid (void)
 	unsigned char tmpssid[] = { 65, 49, 50, 51, 0, 50, 50 };
 	gs_free_error GError *error = NULL;
 	gs_unref_keyfile GKeyFile *keyfile = NULL;
-	gint *intlist;
+	int *intlist;
 	gsize len = 0, i;
 
 	connection = nm_simple_connection_new ();
@@ -1018,27 +1018,20 @@ test_read_intlike_ssid (void)
 	gs_free_error GError *error = NULL;
 	gboolean success;
 	GBytes *ssid;
-	const guint8 *ssid_data;
-	gsize ssid_len;
 	const char *expected_ssid = "101";
 
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR "/Test_Intlike_SSID", &error);
-	g_assert_no_error (error);
-	g_assert (connection);
+	nmtst_assert_success (connection, error);
 
 	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
-	/* SSID */
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
 
 	ssid = nm_setting_wireless_get_ssid (s_wifi);
-	g_assert (ssid != NULL);
-	ssid_data = g_bytes_get_data (ssid, &ssid_len);
-	g_assert_cmpint (ssid_len, ==, strlen (expected_ssid));
-	g_assert_cmpint (memcmp (ssid_data, expected_ssid, strlen (expected_ssid)), ==, 0);
+	g_assert (ssid);
+	g_assert (nm_utils_gbytes_equal_mem (ssid, expected_ssid, strlen (expected_ssid)));
 }
 
 static void
@@ -1049,27 +1042,20 @@ test_read_intlike_ssid_2 (void)
 	gs_free_error GError *error = NULL;
 	gboolean success;
 	GBytes *ssid;
-	const guint8 *ssid_data;
-	gsize ssid_len;
 	const char *expected_ssid = "11;12;13;";
 
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR "/Test_Intlike_SSID_2", &error);
-	g_assert_no_error (error);
-	g_assert (connection);
+	nmtst_assert_success (connection, error);
 
 	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
-	/* SSID */
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
 
 	ssid = nm_setting_wireless_get_ssid (s_wifi);
-	g_assert (ssid != NULL);
-	ssid_data = g_bytes_get_data (ssid, &ssid_len);
-	g_assert_cmpint (ssid_len, ==, strlen (expected_ssid));
-	g_assert_cmpint (memcmp (ssid_data, expected_ssid, strlen (expected_ssid)), ==, 0);
+	g_assert (ssid);
+	g_assert (nm_utils_gbytes_equal_mem (ssid, expected_ssid, strlen (expected_ssid)));
 }
 
 static void
@@ -1787,7 +1773,8 @@ test_write_wired_8021x_tls_connection_blob (void)
 	const char *uuid;
 	gboolean reread_same = FALSE;
 	gs_free_error GError *error = NULL;
-	GBytes *password_raw = NULL;
+	GBytes *password_raw;
+
 #define PASSWORD_RAW "password-raw\0test"
 
 	connection = create_wired_tls_connection (NM_SETTING_802_1X_CK_SCHEME_BLOB);
@@ -1846,8 +1833,7 @@ test_write_wired_8021x_tls_connection_blob (void)
 
 	password_raw = nm_setting_802_1x_get_password_raw (s_8021x);
 	g_assert (password_raw);
-	g_assert (g_bytes_get_size (password_raw) == NM_STRLEN (PASSWORD_RAW));
-	g_assert (!memcmp (g_bytes_get_data (password_raw, NULL), PASSWORD_RAW, NM_STRLEN (PASSWORD_RAW)));
+	g_assert (nm_utils_gbytes_equal_mem (password_raw, PASSWORD_RAW, NM_STRLEN (PASSWORD_RAW)));
 
 	unlink (testfile);
 
@@ -2186,7 +2172,7 @@ test_write_new_wired_group_name (void)
 	gs_free char *testfile = NULL;
 	gs_free_error GError *error = NULL;
 	char *s;
-	gint mtu;
+	int mtu;
 
 	connection = nm_simple_connection_new ();
 	g_assert (connection);
@@ -2234,32 +2220,25 @@ test_read_new_wireless_group_names (void)
 	NMSettingWireless *s_wifi;
 	NMSettingWirelessSecurity *s_wsec;
 	GBytes *ssid;
-	const guint8 *ssid_data;
-	gsize ssid_len;
 	const char *expected_ssid = "foobar";
 	gs_free_error GError *error = NULL;
 	gboolean success;
 
 	connection = nms_keyfile_reader_from_file (TEST_KEYFILES_DIR"/Test_New_Wireless_Group_Names", &error);
-	g_assert_no_error (error);
-	g_assert (connection);
+	nmtst_assert_success (connection, error);
+
 	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	nmtst_assert_success (success, error);
 
-	/* Wifi setting */
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
 
 	ssid = nm_setting_wireless_get_ssid (s_wifi);
 	g_assert (ssid);
-	ssid_data = g_bytes_get_data (ssid, &ssid_len);
-	g_assert_cmpint (ssid_len, ==, strlen (expected_ssid));
-	g_assert_cmpint (memcmp (ssid_data, expected_ssid, ssid_len), ==, 0);
+	g_assert (nm_utils_gbytes_equal_mem (ssid, expected_ssid, strlen (expected_ssid)));
 
 	g_assert_cmpstr (nm_setting_wireless_get_mode (s_wifi), ==, NM_SETTING_WIRELESS_MODE_INFRA);
 
-	/* Wifi security setting */
 	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), ==, "wpa-psk");
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 80db5baa..41d510e8 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -89,23 +89,17 @@ config_option_free (ConfigOption *opt)
 }
 
 static void
-blob_free (GByteArray *array)
-{
-	g_byte_array_free (array, TRUE);
-}
-
-static void
 nm_supplicant_config_init (NMSupplicantConfig * self)
 {
 	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 
 	priv->config = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                      (GDestroyNotify) g_free,
+	                                      g_free,
 	                                      (GDestroyNotify) config_option_free);
 
 	priv->blobs = g_hash_table_new_full (nm_str_hash, g_str_equal,
-	                                     (GDestroyNotify) g_free,
-	                                     (GDestroyNotify) blob_free);
+	                                     g_free,
+	                                     (GDestroyNotify) g_bytes_unref);
 
 	priv->ap_scan = 1;
 	priv->dispose_has_run = FALSE;
@@ -198,7 +192,6 @@ nm_supplicant_config_add_blob (NMSupplicantConfig *self,
 	ConfigOption *old_opt;
 	ConfigOption *opt;
 	OptType type;
-	GByteArray *blob;
 	const guint8 *data;
 	gsize data_len;
 
@@ -226,9 +219,6 @@ nm_supplicant_config_add_blob (NMSupplicantConfig *self,
 		return FALSE;
 	}
 
-	blob = g_byte_array_sized_new (data_len);
-	g_byte_array_append (blob, data, data_len);
-
 	opt = g_slice_new0 (ConfigOption);
 	opt->value = g_strdup_printf ("blob://%s", blobid);
 	opt->len = strlen (opt->value);
@@ -237,7 +227,9 @@ 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);
-	g_hash_table_insert (priv->blobs, g_strdup (blobid), blob);
+	g_hash_table_insert (priv->blobs,
+	                     g_strdup (blobid),
+	                     g_bytes_ref (value));
 
 	return TRUE;
 }
@@ -949,7 +941,7 @@ add_pkcs11_uri_with_pin (NMSupplicantConfig *self,
                          const NMSettingSecretFlags pin_flags,
                          GError **error)
 {
-	gs_strfreev gchar **split = NULL;
+	gs_strfreev char **split = NULL;
 	gs_free char *tmp = NULL;
 	gs_free char *tmp_log = NULL;
 	gs_free char *pin_qattr = NULL;
@@ -1009,6 +1001,7 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
 	guint32 frag, hdrs;
 	gs_free char *frag_str = NULL;
 	NMSetting8021xAuthFlags phase1_auth_flags;
+	nm_auto_free_gstring GString *eap_str = NULL;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE);
 	g_return_val_if_fail (setting != NULL, FALSE);
@@ -1045,20 +1038,38 @@ nm_supplicant_config_add_setting_8021x (NMSupplicantConfig *self,
 		priv->ap_scan = 0;
 	}
 
-	if (!ADD_STRING_LIST_VAL (self, setting, 802_1x, eap_method, eap_methods, "eap", ' ', TRUE, NULL, error))
-		return FALSE;
-
-	/* Check EAP method for special handling: PEAP + GTC, FAST */
+	/* Build the "eap" option string while we check for EAP methods needing
+	 * special handling: PEAP + GTC, FAST, external */
+	eap_str = g_string_new (NULL);
 	num_eap = nm_setting_802_1x_get_num_eap_methods (setting);
 	for (i = 0; i < num_eap; i++) {
 		const char *method = nm_setting_802_1x_get_eap_method (setting, i);
 
-		if (method && (strcasecmp (method, "fast") == 0)) {
+		if (nm_streq (method, "fast")) {
 			fast = TRUE;
 			priv->fast_required = TRUE;
 		}
+
+		if (nm_streq (method, "external")) {
+			if (num_eap == 1) {
+				g_set_error (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+				             "Connection settings managed externally to NM, connection"
+				             " cannot be used with wpa_supplicant");
+				return FALSE;
+			}
+			continue;
+		}
+
+		if (eap_str->len)
+			g_string_append_c (eap_str, ' ');
+		g_string_append (eap_str, method);
 	}
 
+	g_string_ascii_up (eap_str);
+	if (   eap_str->len
+	    && !nm_supplicant_config_add_option (self, "eap", eap_str->str, -1, NULL, error))
+		return FALSE;
+
 	/* Adjust the fragment size according to MTU, but do not set it higher than 1280-14
 	 * for better compatibility */
 	hdrs = 14; /* EAPOL + EAP-TLS */
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 71e6a35a..5237acb2 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -111,7 +111,7 @@ typedef struct {
 	NMSupplicantInterfaceState state;
 	int            disconnect_reason;
 
-	gboolean       scanning:1;
+	bool           scanning:1;
 
 	bool           scan_done_pending:1;
 	bool           scan_done_success:1;
@@ -1579,7 +1579,7 @@ assoc_add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 	GHashTable *blobs;
 	GHashTableIter iter;
 	const char *blob_name;
-	GByteArray *blob_data;
+	GBytes *blob_data;
 
 	assoc_data = add_network_data->assoc_data;
 	if (assoc_data)
@@ -1637,8 +1637,7 @@ assoc_add_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 			                   "AddBlob",
 			                   g_variant_new ("(s@ay)",
 			                                  blob_name,
-			                                  g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-			                                                             blob_data->data, blob_data->len, 1)),
+			                                  nm_utils_gbytes_to_variant_ay (blob_data)),
 			                   G_DBUS_CALL_FLAGS_NONE,
 			                   -1,
 			                   priv->assoc_data->cancellable,
@@ -1790,7 +1789,9 @@ scan_request_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 }
 
 void
-nm_supplicant_interface_request_scan (NMSupplicantInterface *self, const GPtrArray *ssids)
+nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
+                                      GBytes *const*ssids,
+                                      guint ssids_len)
 {
 	NMSupplicantInterfacePrivate *priv;
 	GVariantBuilder builder;
@@ -1804,15 +1805,14 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self, const GPtrArr
 	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));
-	if (ssids) {
+	if (ssids_len > 0) {
 		GVariantBuilder ssids_builder;
 
 		g_variant_builder_init (&ssids_builder, G_VARIANT_TYPE_BYTESTRING_ARRAY);
-		for (i = 0; i < ssids->len; i++) {
-			GByteArray *ssid = g_ptr_array_index (ssids, i);
+		for (i = 0; i < ssids_len; i++) {
+			nm_assert (ssids[i]);
 			g_variant_builder_add (&ssids_builder, "@ay",
-			                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-			                                                  ssid->data, ssid->len, 1));
+			                       nm_utils_gbytes_to_variant_ay (ssids[i]));
 		}
 		g_variant_builder_add (&builder, "{sv}", "SSIDs", g_variant_builder_end (&ssids_builder));
 	}
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index 31272b3c..0365fdcd 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -100,7 +100,9 @@ void nm_supplicant_interface_disconnect (NMSupplicantInterface * iface);
 
 const char *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface);
 
-void nm_supplicant_interface_request_scan (NMSupplicantInterface * self, const GPtrArray *ssids);
+void nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
+                                           GBytes *const*ssids,
+                                           guint ssids_len);
 
 NMSupplicantInterfaceState nm_supplicant_interface_get_state (NMSupplicantInterface * self);
 
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index 317afff9..8a0cadc7 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -220,7 +220,7 @@ validate_type_keyword (const struct Opt * opt,
                        const guint32 len)
 {
 	char **allowed;
-	gchar **candidates = NULL;
+	char **candidates = NULL;
 	char **candidate;
 	gboolean found = FALSE;
 
diff --git a/src/supplicant/tests/meson.build b/src/supplicant/tests/meson.build
index b8bad7f3..5e4cbdbe 100644
--- a/src/supplicant/tests/meson.build
+++ b/src/supplicant/tests/meson.build
@@ -4,7 +4,6 @@ exe = executable(
   test_unit,
   test_unit + '.c',
   dependencies: test_nm_dep,
-  c_args: nm_build_cflags,
 )
 
 test(
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
index 0c7d0aec..870721b0 100644
--- a/src/systemd/meson.build
+++ b/src/systemd/meson.build
@@ -2,6 +2,7 @@ sources = files(
   'sd-adapt/nm-sd-adapt.c',
   'src/basic/alloc-util.c',
   'src/basic/escape.c',
+  'src/basic/env-util.c',
   'src/basic/ether-addr-util.c',
   'src/basic/extract-word.c',
   'src/basic/fd-util.c',
diff --git a/src/systemd/nm-sd.c b/src/systemd/nm-sd.c
index e05b345b..cbef91d1 100644
--- a/src/systemd/nm-sd.c
+++ b/src/systemd/nm-sd.c
@@ -35,7 +35,7 @@ typedef struct SDEventSource {
 } SDEventSource;
 
 static gboolean
-event_prepare (GSource *source, gint *timeout_)
+event_prepare (GSource *source, int *timeout_)
 {
 	return sd_event_prepare (((SDEventSource *) source)->event) > 0;
 }
diff --git a/src/systemd/sd-adapt/libudev.h b/src/systemd/sd-adapt/libudev.h
deleted file mode 100644
index 8c49f075..00000000
--- a/src/systemd/sd-adapt/libudev.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-
-/* dummy header */
-
-struct udev_device;
diff --git a/src/systemd/sd-adapt/nm-sd-adapt.h b/src/systemd/sd-adapt/nm-sd-adapt.h
index 380cbc60..e163eeda 100644
--- a/src/systemd/sd-adapt/nm-sd-adapt.h
+++ b/src/systemd/sd-adapt/nm-sd-adapt.h
@@ -26,10 +26,6 @@
 #include <sys/resource.h>
 #include <time.h>
 
-#ifndef CLOCK_BOOTTIME
-#define CLOCK_BOOTTIME 7
-#endif
-
 #if defined(HAVE_DECL_REALLOCARRAY) && HAVE_DECL_REALLOCARRAY == 1
 #define HAVE_REALLOCARRAY 1
 #else
@@ -180,15 +176,6 @@ sd_notify (int unset_environment, const char *state)
 #define MAX_HANDLE_SZ 128
 #endif
 
-/*
- * Some toolchains (E.G. uClibc 0.9.33 and earlier) don't export
- * CLOCK_BOOTTIME even though the kernel supports it, so provide a
- * local definition
- */
-#ifndef CLOCK_BOOTTIME
-#define CLOCK_BOOTTIME 7
-#endif
-
 #include "sd-id128.h"
 #include "sparse-endian.h"
 #include "async.h"
diff --git a/src/systemd/sd-adapt/env-util.h b/src/systemd/sd-adapt/sd-device.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/env-util.h
+++ b/src/systemd/sd-adapt/sd-device.h
diff --git a/src/systemd/sd-adapt/udev-util.h b/src/systemd/sd-adapt/udev-util.h
deleted file mode 100644
index 637892c2..00000000
--- a/src/systemd/sd-adapt/udev-util.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#pragma once
-
-/* dummy header */
diff --git a/src/systemd/sd-adapt/udev.h b/src/systemd/sd-adapt/udev.h
deleted file mode 100644
index 419abcbf..00000000
--- a/src/systemd/sd-adapt/udev.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-/* dummy header */
-
-#include "libudev.h"
-#include "strv.h"
diff --git a/src/systemd/src/basic/env-util.c b/src/systemd/src/basic/env-util.c
new file mode 100644
index 00000000..52d12a8a
--- /dev/null
+++ b/src/systemd/src/basic/env-util.c
@@ -0,0 +1,791 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-sd-adapt.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "env-util.h"
+#include "escape.h"
+#include "extract-word.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+
+#if 0 /* NM_IGNORED */
+#define VALID_CHARS_ENV_NAME                    \
+        DIGITS LETTERS                          \
+        "_"
+
+#ifndef ARG_MAX
+#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX))
+#endif
+
+static bool env_name_is_valid_n(const char *e, size_t n) {
+        const char *p;
+
+        if (!e)
+                return false;
+
+        if (n <= 0)
+                return false;
+
+        if (e[0] >= '0' && e[0] <= '9')
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, an individual assignment hence cannot be
+         * either. Discounting the equal sign and trailing NUL this
+         * hence leaves ARG_MAX-2 as longest possible variable
+         * name. */
+        if (n > ARG_MAX - 2)
+                return false;
+
+        for (p = e; p < e + n; p++)
+                if (!strchr(VALID_CHARS_ENV_NAME, *p))
+                        return false;
+
+        return true;
+}
+
+bool env_name_is_valid(const char *e) {
+        if (!e)
+                return false;
+
+        return env_name_is_valid_n(e, strlen(e));
+}
+
+bool env_value_is_valid(const char *e) {
+        if (!e)
+                return false;
+
+        if (!utf8_is_valid(e))
+                return false;
+
+        /* bash allows tabs and newlines in environment variables, and so
+         * should we */
+        if (string_has_cc(e, "\t\n"))
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, an individual assignment hence cannot be
+         * either. Discounting the shortest possible variable name of
+         * length 1, the equal sign and trailing NUL this hence leaves
+         * ARG_MAX-3 as longest possible variable value. */
+        if (strlen(e) > ARG_MAX - 3)
+                return false;
+
+        return true;
+}
+
+bool env_assignment_is_valid(const char *e) {
+        const char *eq;
+
+        eq = strchr(e, '=');
+        if (!eq)
+                return false;
+
+        if (!env_name_is_valid_n(e, eq - e))
+                return false;
+
+        if (!env_value_is_valid(eq + 1))
+                return false;
+
+        /* POSIX says the overall size of the environment block cannot
+         * be > ARG_MAX, hence the individual variable assignments
+         * cannot be either, but let's leave room for one trailing NUL
+         * byte. */
+        if (strlen(e) > ARG_MAX - 1)
+                return false;
+
+        return true;
+}
+
+bool strv_env_is_valid(char **e) {
+        char **p, **q;
+
+        STRV_FOREACH(p, e) {
+                size_t k;
+
+                if (!env_assignment_is_valid(*p))
+                        return false;
+
+                /* Check if there are duplicate assginments */
+                k = strcspn(*p, "=");
+                STRV_FOREACH(q, p + 1)
+                        if (strneq(*p, *q, k) && (*q)[k] == '=')
+                                return false;
+        }
+
+        return true;
+}
+
+bool strv_env_name_is_valid(char **l) {
+        char **p, **q;
+
+        STRV_FOREACH(p, l) {
+                if (!env_name_is_valid(*p))
+                        return false;
+
+                STRV_FOREACH(q, p + 1)
+                        if (streq(*p, *q))
+                                return false;
+        }
+
+        return true;
+}
+
+bool strv_env_name_or_assignment_is_valid(char **l) {
+        char **p, **q;
+
+        STRV_FOREACH(p, l) {
+                if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p))
+                        return false;
+
+                STRV_FOREACH(q, p + 1)
+                        if (streq(*p, *q))
+                                return false;
+        }
+
+        return true;
+}
+
+static int env_append(char **r, char ***k, char **a) {
+        assert(r);
+        assert(k);
+
+        if (!a)
+                return 0;
+
+        /* Add the entries of a to *k unless they already exist in *r
+         * in which case they are overridden instead. This assumes
+         * there is enough space in the r array. */
+
+        for (; *a; a++) {
+                char **j;
+                size_t n;
+
+                n = strcspn(*a, "=");
+
+                if ((*a)[n] == '=')
+                        n++;
+
+                for (j = r; j < *k; j++)
+                        if (strneq(*j, *a, n))
+                                break;
+
+                if (j >= *k)
+                        (*k)++;
+                else
+                        free(*j);
+
+                *j = strdup(*a);
+                if (!*j)
+                        return -ENOMEM;
+        }
+
+        return 0;
+}
+
+char **strv_env_merge(size_t n_lists, ...) {
+        size_t n = 0;
+        char **l, **k, **r;
+        va_list ap;
+        size_t i;
+
+        /* Merges an arbitrary number of environment sets */
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                n += strv_length(l);
+        }
+        va_end(ap);
+
+        r = new(char*, n+1);
+        if (!r)
+                return NULL;
+
+        k = r;
+
+        va_start(ap, n_lists);
+        for (i = 0; i < n_lists; i++) {
+                l = va_arg(ap, char**);
+                if (env_append(r, &k, l) < 0)
+                        goto fail;
+        }
+        va_end(ap);
+
+        *k = NULL;
+
+        return r;
+
+fail:
+        va_end(ap);
+        strv_free(r);
+
+        return NULL;
+}
+
+static bool env_match(const char *t, const char *pattern) {
+        assert(t);
+        assert(pattern);
+
+        /* pattern a matches string a
+         *         a matches a=
+         *         a matches a=b
+         *         a= matches a=
+         *         a=b matches a=b
+         *         a= does not match a
+         *         a=b does not match a=
+         *         a=b does not match a
+         *         a=b does not match a=c */
+
+        if (streq(t, pattern))
+                return true;
+
+        if (!strchr(pattern, '=')) {
+                size_t l = strlen(pattern);
+
+                return strneq(t, pattern, l) && t[l] == '=';
+        }
+
+        return false;
+}
+
+static bool env_entry_has_name(const char *entry, const char *name) {
+        const char *t;
+
+        assert(entry);
+        assert(name);
+
+        t = startswith(entry, name);
+        if (!t)
+                return false;
+
+        return *t == '=';
+}
+
+char **strv_env_delete(char **x, size_t n_lists, ...) {
+        size_t n, i = 0;
+        char **k, **r;
+        va_list ap;
+
+        /* Deletes every entry from x that is mentioned in the other
+         * string lists */
+
+        n = strv_length(x);
+
+        r = new(char*, n+1);
+        if (!r)
+                return NULL;
+
+        STRV_FOREACH(k, x) {
+                size_t v;
+
+                va_start(ap, n_lists);
+                for (v = 0; v < n_lists; v++) {
+                        char **l, **j;
+
+                        l = va_arg(ap, char**);
+                        STRV_FOREACH(j, l)
+                                if (env_match(*k, *j))
+                                        goto skip;
+                }
+                va_end(ap);
+
+                r[i] = strdup(*k);
+                if (!r[i]) {
+                        strv_free(r);
+                        return NULL;
+                }
+
+                i++;
+                continue;
+
+        skip:
+                va_end(ap);
+        }
+
+        r[i] = NULL;
+
+        assert(i <= n);
+
+        return r;
+}
+
+char **strv_env_unset(char **l, const char *p) {
+
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        assert(p);
+
+        /* Drops every occurrence of the env var setting p in the
+         * string list. Edits in-place. */
+
+        for (f = t = l; *f; f++) {
+
+                if (env_match(*f, p)) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+char **strv_env_unset_many(char **l, ...) {
+
+        char **f, **t;
+
+        if (!l)
+                return NULL;
+
+        /* Like strv_env_unset() but applies many at once. Edits in-place. */
+
+        for (f = t = l; *f; f++) {
+                bool found = false;
+                const char *p;
+                va_list ap;
+
+                va_start(ap, l);
+
+                while ((p = va_arg(ap, const char*))) {
+                        if (env_match(*f, p)) {
+                                found = true;
+                                break;
+                        }
+                }
+
+                va_end(ap);
+
+                if (found) {
+                        free(*f);
+                        continue;
+                }
+
+                *(t++) = *f;
+        }
+
+        *t = NULL;
+        return l;
+}
+
+int strv_env_replace(char ***l, char *p) {
+        char **f;
+        const char *t, *name;
+
+        assert(p);
+
+        /* Replace first occurrence of the env var or add a new one in the
+         * string list. Drop other occurences. Edits in-place. Does not copy p.
+         * p must be a valid key=value assignment.
+         */
+
+        t = strchr(p, '=');
+        assert(t);
+
+        name = strndupa(p, t - p);
+
+        for (f = *l; f && *f; f++)
+                if (env_entry_has_name(*f, name)) {
+                        free_and_replace(*f, p);
+                        strv_env_unset(f + 1, *f);
+                        return 0;
+                }
+
+        /* We didn't find a match, we need to append p or create a new strv */
+        if (strv_push(l, p) < 0)
+                return -ENOMEM;
+        return 1;
+}
+
+char **strv_env_set(char **x, const char *p) {
+
+        char **k;
+        _cleanup_strv_free_ char **r = NULL;
+        char* m[2] = { (char*) p, NULL };
+
+        /* Overrides the env var setting of p, returns a new copy */
+
+        r = new(char*, strv_length(x)+2);
+        if (!r)
+                return NULL;
+
+        k = r;
+        if (env_append(r, &k, x) < 0)
+                return NULL;
+
+        if (env_append(r, &k, m) < 0)
+                return NULL;
+
+        *k = NULL;
+
+        return TAKE_PTR(r);
+}
+
+char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
+        char **i;
+
+        assert(name);
+
+        if (k <= 0)
+                return NULL;
+
+        STRV_FOREACH_BACKWARDS(i, l)
+                if (strneq(*i, name, k) &&
+                    (*i)[k] == '=')
+                        return *i + k + 1;
+
+        if (flags & REPLACE_ENV_USE_ENVIRONMENT) {
+                const char *t;
+
+                t = strndupa(name, k);
+                return getenv(t);
+        };
+
+        return NULL;
+}
+
+char *strv_env_get(char **l, const char *name) {
+        assert(name);
+
+        return strv_env_get_n(l, name, strlen(name), 0);
+}
+
+char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
+        char **p, **q;
+        int k = 0;
+
+        STRV_FOREACH(p, e) {
+                size_t n;
+                bool duplicate = false;
+
+                if (!env_assignment_is_valid(*p)) {
+                        if (invalid_callback)
+                                invalid_callback(*p, userdata);
+                        free(*p);
+                        continue;
+                }
+
+                n = strcspn(*p, "=");
+                STRV_FOREACH(q, p + 1)
+                        if (strneq(*p, *q, n) && (*q)[n] == '=') {
+                                duplicate = true;
+                                break;
+                        }
+
+                if (duplicate) {
+                        free(*p);
+                        continue;
+                }
+
+                e[k++] = *p;
+        }
+
+        if (e)
+                e[k] = NULL;
+
+        return e;
+}
+
+char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
+        enum {
+                WORD,
+                CURLY,
+                VARIABLE,
+                VARIABLE_RAW,
+                TEST,
+                DEFAULT_VALUE,
+                ALTERNATE_VALUE,
+        } state = WORD;
+
+        const char *e, *word = format, *test_value;
+        char *k;
+        _cleanup_free_ char *r = NULL;
+        size_t i, len;
+        int nest = 0;
+
+        assert(format);
+
+        for (e = format, i = 0; *e && i < n; e ++, i ++)
+                switch (state) {
+
+                case WORD:
+                        if (*e == '$')
+                                state = CURLY;
+                        break;
+
+                case CURLY:
+                        if (*e == '{') {
+                                k = strnappend(r, word, e-word-1);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e-1;
+                                state = VARIABLE;
+                                nest++;
+                        } else if (*e == '$') {
+                                k = strnappend(r, word, e-word);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e+1;
+                                state = WORD;
+
+                        } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_CHARS_ENV_NAME, *e)) {
+                                k = strnappend(r, word, e-word-1);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e-1;
+                                state = VARIABLE_RAW;
+
+                        } else
+                                state = WORD;
+                        break;
+
+                case VARIABLE:
+                        if (*e == '}') {
+                                const char *t;
+
+                                t = strv_env_get_n(env, word+2, e-word-2, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e+1;
+                                state = WORD;
+                        } else if (*e == ':') {
+                                if (!(flags & REPLACE_ENV_ALLOW_EXTENDED))
+                                        /* Treat this as unsupported syntax, i.e. do no replacement */
+                                        state = WORD;
+                                else {
+                                        len = e-word-2;
+                                        state = TEST;
+                                }
+                        }
+                        break;
+
+                case TEST:
+                        if (*e == '-')
+                                state = DEFAULT_VALUE;
+                        else if (*e == '+')
+                                state = ALTERNATE_VALUE;
+                        else {
+                                state = WORD;
+                                break;
+                        }
+
+                        test_value = e+1;
+                        break;
+
+                case DEFAULT_VALUE: /* fall through */
+                case ALTERNATE_VALUE:
+                        assert(flags & REPLACE_ENV_ALLOW_EXTENDED);
+
+                        if (*e == '{') {
+                                nest++;
+                                break;
+                        }
+
+                        if (*e != '}')
+                                break;
+
+                        nest--;
+                        if (nest == 0) {
+                                const char *t;
+                                _cleanup_free_ char *v = NULL;
+
+                                t = strv_env_get_n(env, word+2, len, flags);
+
+                                if (t && state == ALTERNATE_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+                                else if (!t && state == DEFAULT_VALUE)
+                                        t = v = replace_env_n(test_value, e-test_value, env, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e+1;
+                                state = WORD;
+                        }
+                        break;
+
+                case VARIABLE_RAW:
+                        assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
+
+                        if (!strchr(VALID_CHARS_ENV_NAME, *e)) {
+                                const char *t;
+
+                                t = strv_env_get_n(env, word+1, e-word-1, flags);
+
+                                k = strappend(r, t);
+                                if (!k)
+                                        return NULL;
+
+                                free_and_replace(r, k);
+
+                                word = e--;
+                                i--;
+                                state = WORD;
+                        }
+                        break;
+                }
+
+        if (state == VARIABLE_RAW) {
+                const char *t;
+
+                assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
+
+                t = strv_env_get_n(env, word+1, e-word-1, flags);
+                return strappend(r, t);
+        } else
+                return strnappend(r, word, e-word);
+}
+
+char **replace_env_argv(char **argv, char **env) {
+        char **ret, **i;
+        size_t k = 0, l = 0;
+
+        l = strv_length(argv);
+
+        ret = new(char*, l+1);
+        if (!ret)
+                return NULL;
+
+        STRV_FOREACH(i, argv) {
+
+                /* If $FOO appears as single word, replace it by the split up variable */
+                if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
+                        char *e;
+                        char **w, **m = NULL;
+                        size_t q;
+
+                        e = strv_env_get(env, *i+1);
+                        if (e) {
+                                int r;
+
+                                r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES);
+                                if (r < 0) {
+                                        ret[k] = NULL;
+                                        strv_free(ret);
+                                        return NULL;
+                                }
+                        } else
+                                m = NULL;
+
+                        q = strv_length(m);
+                        l = l + q - 1;
+
+                        w = reallocarray(ret, l + 1, sizeof(char *));
+                        if (!w) {
+                                ret[k] = NULL;
+                                strv_free(ret);
+                                strv_free(m);
+                                return NULL;
+                        }
+
+                        ret = w;
+                        if (m) {
+                                memcpy(ret + k, m, q * sizeof(char*));
+                                free(m);
+                        }
+
+                        k += q;
+                        continue;
+                }
+
+                /* If ${FOO} appears as part of a word, replace it by the variable as-is */
+                ret[k] = replace_env(*i, env, 0);
+                if (!ret[k]) {
+                        strv_free(ret);
+                        return NULL;
+                }
+                k++;
+        }
+
+        ret[k] = NULL;
+        return ret;
+}
+#endif /* NM_IGNORED */
+
+int getenv_bool(const char *p) {
+        const char *e;
+
+        e = getenv(p);
+        if (!e)
+                return -ENXIO;
+
+        return parse_boolean(e);
+}
+
+#if 0 /* NM_IGNORED */
+int getenv_bool_secure(const char *p) {
+        const char *e;
+
+        e = secure_getenv(p);
+        if (!e)
+                return -ENXIO;
+
+        return parse_boolean(e);
+}
+
+int serialize_environment(FILE *f, char **environment) {
+        char **e;
+
+        STRV_FOREACH(e, environment) {
+                _cleanup_free_ char *ce;
+
+                ce = cescape(*e);
+                if (!ce)
+                        return -ENOMEM;
+
+                fprintf(f, "env=%s\n", ce);
+        }
+
+        /* caller should call ferror() */
+
+        return 0;
+}
+
+int deserialize_environment(char ***environment, const char *line) {
+        char *uce;
+        int r;
+
+        assert(line);
+        assert(environment);
+
+        assert(startswith(line, "env="));
+        r = cunescape(line + 4, 0, &uce);
+        if (r < 0)
+                return r;
+
+        return strv_env_replace(environment, uce);
+}
+#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/env-util.h b/src/systemd/src/basic/env-util.h
new file mode 100644
index 00000000..174433ea
--- /dev/null
+++ b/src/systemd/src/basic/env-util.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#include "macro.h"
+#include "string.h"
+
+bool env_name_is_valid(const char *e);
+bool env_value_is_valid(const char *e);
+bool env_assignment_is_valid(const char *e);
+
+enum {
+        REPLACE_ENV_USE_ENVIRONMENT = 1u,
+        REPLACE_ENV_ALLOW_BRACELESS = 2u,
+        REPLACE_ENV_ALLOW_EXTENDED  = 4u,
+};
+
+char *replace_env_n(const char *format, size_t n, char **env, unsigned flags);
+char **replace_env_argv(char **argv, char **env);
+
+static inline char *replace_env(const char *format, char **env, unsigned flags) {
+        return replace_env_n(format, strlen(format), env, flags);
+}
+
+bool strv_env_is_valid(char **e);
+#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL)
+char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata);
+
+bool strv_env_name_is_valid(char **l);
+bool strv_env_name_or_assignment_is_valid(char **l);
+
+char **strv_env_merge(size_t n_lists, ...);
+char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */
+
+char **strv_env_set(char **x, const char *p); /* New copy ... */
+char **strv_env_unset(char **l, const char *p); /* In place ... */
+char **strv_env_unset_many(char **l, ...) _sentinel_;
+int strv_env_replace(char ***l, char *p); /* In place ... */
+
+char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
+char *strv_env_get(char **x, const char *n) _pure_;
+
+int getenv_bool(const char *p);
+int getenv_bool_secure(const char *p);
+
+int serialize_environment(FILE *f, char **environment);
+int deserialize_environment(char ***environment, const char *line);
diff --git a/src/systemd/src/basic/ether-addr-util.c b/src/systemd/src/basic/ether-addr-util.c
index 6f946c3e..ed92bc60 100644
--- a/src/systemd/src/basic/ether-addr-util.c
+++ b/src/systemd/src/basic/ether-addr-util.c
@@ -1,7 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2014 Tom Gundersen
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/ether-addr-util.h b/src/systemd/src/basic/ether-addr-util.h
index f02cefea..3be03700 100644
--- a/src/systemd/src/basic/ether-addr-util.h
+++ b/src/systemd/src/basic/ether-addr-util.h
@@ -1,10 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  Copyright © 2014 Tom Gundersen
-***/
-
 #include <net/ethernet.h>
 #include <stdbool.h>
 
diff --git a/src/systemd/src/basic/fd-util.h b/src/systemd/src/basic/fd-util.h
index 8adc959d..00303a7e 100644
--- a/src/systemd/src/basic/fd-util.h
+++ b/src/systemd/src/basic/fd-util.h
@@ -78,8 +78,12 @@ int acquire_data_fd(const void *data, size_t size, unsigned flags);
 int fd_duplicate_data_fd(int fd);
 
 /* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
-#define ERRNO_IS_DISCONNECT(r) \
-        IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH)
+/* The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases.
+ * See the icmp_err_convert[] in net/ipv4/icmp.c in the kernel sources */
+#define ERRNO_IS_DISCONNECT(r)                                          \
+        IN_SET(r,                                                       \
+               ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, \
+               ENETUNREACH, EHOSTUNREACH, ENOPROTOOPT, EHOSTDOWN, ENONET)
 
 /* Resource exhaustion, could be our fault or general system trouble */
 #define ERRNO_IS_RESOURCE(r) \
diff --git a/src/systemd/src/basic/fileio.c b/src/systemd/src/basic/fileio.c
index 2d3aef2c..3978c3a7 100644
--- a/src/systemd/src/basic/fileio.c
+++ b/src/systemd/src/basic/fileio.c
@@ -1235,9 +1235,13 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
         const char *fn;
         char *t;
 
-        assert(p);
         assert(ret);
 
+        if (isempty(p))
+                return -EINVAL;
+        if (path_equal(p, "/"))
+                return -EINVAL;
+
         /*
          * Turns this:
          *         /foo/bar/waldo
@@ -1269,9 +1273,13 @@ int tempfn_random(const char *p, const char *extra, char **ret) {
         uint64_t u;
         unsigned i;
 
-        assert(p);
         assert(ret);
 
+        if (isempty(p))
+                return -EINVAL;
+        if (path_equal(p, "/"))
+                return -EINVAL;
+
         /*
          * Turns this:
          *         /foo/bar/waldo
@@ -1330,7 +1338,10 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
         if (!t)
                 return -ENOMEM;
 
-        x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
+        if (isempty(p))
+                x = stpcpy(stpcpy(t, ".#"), extra);
+        else
+                x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
 
         u = random_u64();
         for (i = 0; i < 16; i++) {
@@ -1417,7 +1428,8 @@ int open_tmpfile_unlinkable(const char *directory, int flags) {
                 r = tmp_dir(&directory);
                 if (r < 0)
                         return r;
-        }
+        } else if (isempty(directory))
+                return -EINVAL;
 
         /* Returns an unlinked temporary file that cannot be linked into the file system anymore */
 
@@ -1452,22 +1464,14 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
          * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in
          * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
 
-        {
-                _cleanup_free_ char *dn = NULL;
-
-                dn = dirname_malloc(target);
-                if (!dn)
-                        return -ENOMEM;
-
-                fd = open(dn, O_TMPFILE|flags, 0640);
-                if (fd >= 0) {
-                        *ret_path = NULL;
-                        return fd;
-                }
-
-                log_debug_errno(errno, "Failed to use O_TMPFILE on %s: %m", dn);
+        fd = open_parent(target, O_TMPFILE|flags, 0640);
+        if (fd >= 0) {
+                *ret_path = NULL;
+                return fd;
         }
 
+        log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target);
+
         r = tempfn_random(target, NULL, &tmp);
         if (r < 0)
                 return r;
diff --git a/src/systemd/src/basic/fs-util.c b/src/systemd/src/basic/fs-util.c
index e3ae9916..8c19e9c7 100644
--- a/src/systemd/src/basic/fs-util.c
+++ b/src/systemd/src/basic/fs-util.c
@@ -442,6 +442,31 @@ int mkfifo_atomic(const char *path, mode_t mode) {
         return 0;
 }
 
+int mkfifoat_atomic(int dirfd, const char *path, mode_t mode) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(path);
+
+        if (path_is_absolute(path))
+                return mkfifo_atomic(path, mode);
+
+        /* We're only interested in the (random) filename.  */
+        r = tempfn_random_child("", NULL, &t);
+        if (r < 0)
+                return r;
+
+        if (mkfifoat(dirfd, t, mode) < 0)
+                return -errno;
+
+        if (renameat(dirfd, t, dirfd, path) < 0) {
+                unlink_noerrno(t);
+                return -errno;
+        }
+
+        return 0;
+}
+
 int get_files_in_directory(const char *path, char ***list) {
         _cleanup_closedir_ DIR *d = NULL;
         struct dirent *de;
@@ -679,7 +704,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN|CHASE_STEP)) == CHASE_OPEN) {
                 /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set
                  * and doesn't care about any of the other special features we provide either. */
-                r = open(path, O_PATH|O_CLOEXEC);
+                r = open(path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
                 if (r < 0)
                         return -errno;
 
@@ -834,7 +859,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                     fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
                         return -EREMOTE;
 
-                if (S_ISLNK(st.st_mode)) {
+                if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
                         char *joined;
 
                         _cleanup_free_ char *destination = NULL;
@@ -1165,7 +1190,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) {
 }
 
 int fsync_directory_of_file(int fd) {
-        _cleanup_free_ char *path = NULL, *dn = NULL;
+        _cleanup_free_ char *path = NULL;
         _cleanup_close_ int dfd = -1;
         int r;
 
@@ -1191,17 +1216,41 @@ int fsync_directory_of_file(int fd) {
         if (!path_is_absolute(path))
                 return -EINVAL;
 
-        dn = dirname_malloc(path);
-        if (!dn)
-                return -ENOMEM;
-
-        dfd = open(dn, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+        dfd = open_parent(path, O_CLOEXEC, 0);
         if (dfd < 0)
-                return -errno;
+                return dfd;
 
         if (fsync(dfd) < 0)
                 return -errno;
 
         return 0;
 }
+
+int open_parent(const char *path, int flags, mode_t mode) {
+        _cleanup_free_ char *parent = NULL;
+        int fd;
+
+        if (isempty(path))
+                return -EINVAL;
+        if (path_equal(path, "/")) /* requesting the parent of the root dir is fishy, let's prohibit that */
+                return -EINVAL;
+
+        parent = dirname_malloc(path);
+        if (!parent)
+                return -ENOMEM;
+
+        /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
+         * O_TMPFILE file, because in that case we are actually create a regular file below the parent directory. */
+
+        if ((flags & O_PATH) == O_PATH)
+                flags |= O_DIRECTORY;
+        else if ((flags & O_TMPFILE) != O_TMPFILE)
+                flags |= O_DIRECTORY|O_RDONLY;
+
+        fd = open(parent, flags, mode);
+        if (fd < 0)
+                return -errno;
+
+        return fd;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fs-util.h b/src/systemd/src/basic/fs-util.h
index 28566773..4b656258 100644
--- a/src/systemd/src/basic/fs-util.h
+++ b/src/systemd/src/basic/fs-util.h
@@ -42,6 +42,7 @@ int symlink_idempotent(const char *from, const char *to);
 int symlink_atomic(const char *from, const char *to);
 int mknod_atomic(const char *path, mode_t mode, dev_t dev);
 int mkfifo_atomic(const char *path, mode_t mode);
+int mkfifoat_atomic(int dir_fd, const char *path, mode_t mode);
 
 int get_files_in_directory(const char *path, char ***list);
 
@@ -72,6 +73,7 @@ enum {
         CHASE_OPEN        = 1 << 4, /* If set, return an O_PATH object to the final component */
         CHASE_TRAIL_SLASH = 1 << 5, /* If set, any trailing slash will be preserved */
         CHASE_STEP        = 1 << 6, /* If set, just execute a single step of the normalization */
+        CHASE_NOFOLLOW    = 1 << 7, /* Only valid with CHASE_OPEN: when the path's right-most component refers to symlink return O_PATH fd of the symlink, rather than following it. */
 };
 
 /* How many iterations to execute before returning -ELOOP */
@@ -103,3 +105,5 @@ void unlink_tempfilep(char (*p)[]);
 int unlinkat_deallocate(int fd, const char *name, int flags);
 
 int fsync_directory_of_file(int fd);
+
+int open_parent(const char *path, int flags, mode_t mode);
diff --git a/src/systemd/src/basic/hash-funcs.c b/src/systemd/src/basic/hash-funcs.c
index 7ed35cb1..fc2c4f78 100644
--- a/src/systemd/src/basic/hash-funcs.c
+++ b/src/systemd/src/basic/hash-funcs.c
@@ -1,7 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2014 Michal Schmidt
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -78,7 +75,7 @@ void trivial_hash_func(const void *p, struct siphash *state) {
 }
 
 int trivial_compare_func(const void *a, const void *b) {
-        return a < b ? -1 : (a > b ? 1 : 0);
+        return CMP(a, b);
 }
 
 const struct hash_ops trivial_hash_ops = {
@@ -94,7 +91,7 @@ int uint64_compare_func(const void *_a, const void *_b) {
         uint64_t a, b;
         a = *(const uint64_t*) _a;
         b = *(const uint64_t*) _b;
-        return a < b ? -1 : (a > b ? 1 : 0);
+        return CMP(a, b);
 }
 
 const struct hash_ops uint64_hash_ops = {
@@ -112,7 +109,7 @@ int devt_compare_func(const void *_a, const void *_b) {
         dev_t a, b;
         a = *(const dev_t*) _a;
         b = *(const dev_t*) _b;
-        return a < b ? -1 : (a > b ? 1 : 0);
+        return CMP(a, b);
 }
 
 const struct hash_ops devt_hash_ops = {
diff --git a/src/systemd/src/basic/hash-funcs.h b/src/systemd/src/basic/hash-funcs.h
index 3715c56e..fa45cfe2 100644
--- a/src/systemd/src/basic/hash-funcs.h
+++ b/src/systemd/src/basic/hash-funcs.h
@@ -1,10 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  Copyright © 2014 Michal Schmidt
-***/
-
 #include "macro.h"
 #include "siphash24.h"
 
diff --git a/src/systemd/src/basic/hashmap.c b/src/systemd/src/basic/hashmap.c
index c91f8bd2..cfddeafe 100644
--- a/src/systemd/src/basic/hashmap.c
+++ b/src/systemd/src/basic/hashmap.c
@@ -1,7 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2014 Michal Schmidt
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -11,8 +8,9 @@
 #include <string.h>
 
 #include "alloc-util.h"
-#include "hashmap.h"
+#include "env-util.h"
 #include "fileio.h"
+#include "hashmap.h"
 #include "macro.h"
 #include "mempool.h"
 #include "process-util.h"
@@ -771,20 +769,31 @@ static void reset_direct_storage(HashmapBase *h) {
         memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets);
 }
 
+static bool use_pool(void) {
+        static int b = -1;
+
+        if (!is_main_thread())
+                return false;
+
+        if (b < 0)
+                b = getenv_bool("SYSTEMD_MEMPOOL") != 0;
+
+        return b;
+}
+
 static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) {
         HashmapBase *h;
         const struct hashmap_type_info *hi = &hashmap_type_info[type];
-        bool use_pool;
+        bool up;
 
-        use_pool = is_main_thread();
-
-        h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
+        up = use_pool();
 
+        h = up ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
         if (!h)
                 return NULL;
 
         h->type = type;
-        h->from_pool = use_pool;
+        h->from_pool = up;
         h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops;
 
         if (type == HASHMAP_TYPE_ORDERED) {
@@ -862,9 +871,11 @@ static void hashmap_free_no_clear(HashmapBase *h) {
         assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
 #endif
 
-        if (h->from_pool)
+        if (h->from_pool) {
+                /* Ensure that the object didn't get migrated between threads. */
+                assert_se(is_main_thread());
                 mempool_free_tile(hashmap_type_info[h->type].mempool, h);
-        else
+        } else
                 free(h);
 }
 
diff --git a/src/systemd/src/basic/hashmap.h b/src/systemd/src/basic/hashmap.h
index d1d1b9c8..274afb39 100644
--- a/src/systemd/src/basic/hashmap.h
+++ b/src/systemd/src/basic/hashmap.h
@@ -1,10 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  Copyright © 2014 Michal Schmidt
-***/
-
 #include <limits.h>
 #include <stdbool.h>
 #include <stddef.h>
diff --git a/src/systemd/src/basic/in-addr-util.c b/src/systemd/src/basic/in-addr-util.c
index d945567a..19d0db25 100644
--- a/src/systemd/src/basic/in-addr-util.c
+++ b/src/systemd/src/basic/in-addr-util.c
@@ -576,4 +576,27 @@ int in_addr_prefix_from_string_auto(
         return 0;
 
 }
+
+void in_addr_data_hash_func(const void *p, struct siphash *state) {
+        const struct in_addr_data *a = p;
+
+        siphash24_compress(&a->family, sizeof(a->family), state);
+        siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
+}
+
+int in_addr_data_compare_func(const void *a, const void *b) {
+        const struct in_addr_data *x = a, *y = b;
+        int r;
+
+        r = CMP(x->family, y->family);
+        if (r != 0)
+                return r;
+
+        return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
+}
+
+const struct hash_ops in_addr_data_hash_ops = {
+        .hash = in_addr_data_hash_func,
+        .compare = in_addr_data_compare_func,
+};
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/in-addr-util.h b/src/systemd/src/basic/in-addr-util.h
index 956c00a8..e4be30dc 100644
--- a/src/systemd/src/basic/in-addr-util.h
+++ b/src/systemd/src/basic/in-addr-util.h
@@ -5,6 +5,7 @@
 #include <stddef.h>
 #include <sys/socket.h>
 
+#include "hash-funcs.h"
 #include "macro.h"
 #include "util.h"
 
@@ -53,3 +54,7 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) {
 }
 
 #define IN_ADDR_NULL ((union in_addr_union) {})
+
+void in_addr_data_hash_func(const void *p, struct siphash *state);
+int in_addr_data_compare_func(const void *a, const void *b);
+extern const struct hash_ops in_addr_data_hash_ops;
diff --git a/src/systemd/src/basic/macro.h b/src/systemd/src/basic/macro.h
index cdb54f82..22193fe3 100644
--- a/src/systemd/src/basic/macro.h
+++ b/src/systemd/src/basic/macro.h
@@ -7,7 +7,7 @@
 #include <sys/sysmacros.h>
 #include <sys/types.h>
 
-#define _printf_(a,b) __attribute__ ((format (printf, a, b)))
+#define _printf_(a, b) __attribute__ ((format (printf, a, b)))
 #ifdef __clang__
 #  define _alloc_(...)
 #else
@@ -22,8 +22,8 @@
 #define _packed_ __attribute__ ((packed))
 #define _malloc_ __attribute__ ((malloc))
 #define _weak_ __attribute__ ((weak))
-#define _likely_(x) (__builtin_expect(!!(x),1))
-#define _unlikely_(x) (__builtin_expect(!!(x),0))
+#define _likely_(x) (__builtin_expect(!!(x), 1))
+#define _unlikely_(x) (__builtin_expect(!!(x), 0))
 #define _public_ __attribute__ ((visibility("default")))
 #define _hidden_ __attribute__ ((visibility("hidden")))
 #define _weakref_(x) __attribute__((weakref(#x)))
@@ -155,10 +155,10 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
 #  define VOID_0 ((void*)0)
 #endif
 
-#define ELEMENTSOF(x)                                                    \
-        __extension__ (__builtin_choose_expr(                            \
+#define ELEMENTSOF(x)                                                   \
+        (__builtin_choose_expr(                                         \
                 !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \
-                sizeof(x)/sizeof((x)[0]),                                \
+                sizeof(x)/sizeof((x)[0]),                               \
                 VOID_0))
 
 /*
@@ -176,23 +176,23 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
  */
 #define container_of(ptr, type, member) __container_of(UNIQ, (ptr), type, member)
 #define __container_of(uniq, ptr, type, member)                         \
-        __extension__ ({                                                \
+        ({                                                              \
                 const typeof( ((type*)0)->member ) *UNIQ_T(A, uniq) = (ptr); \
-                (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type,member) ); \
+                (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type, member) ); \
         })
 
 #undef MAX
 #define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b))
 #define __MAX(aq, a, bq, b)                             \
-        __extension__ ({                                \
+        ({                                              \
                 const typeof(a) UNIQ_T(A, aq) = (a);    \
                 const typeof(b) UNIQ_T(B, bq) = (b);    \
-                UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \
+                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
         })
 
 /* evaluates to (void) if _A or _B are not constant or of different types */
 #define CONST_MAX(_A, _B) \
-        __extension__ (__builtin_choose_expr(                           \
+        (__builtin_choose_expr(                                         \
                 __builtin_constant_p(_A) &&                             \
                 __builtin_constant_p(_B) &&                             \
                 __builtin_types_compatible_p(typeof(_A), typeof(_B)),   \
@@ -202,47 +202,56 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
 /* takes two types and returns the size of the larger one */
 #define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; }))
 
-#define MAX3(x,y,z)                                     \
-        __extension__ ({                                \
-                        const typeof(x) _c = MAX(x,y);  \
-                        MAX(_c, z);                     \
-                })
+#define MAX3(x, y, z)                                   \
+        ({                                              \
+                const typeof(x) _c = MAX(x, y);         \
+                MAX(_c, z);                             \
+        })
 
 #undef MIN
 #define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b))
 #define __MIN(aq, a, bq, b)                             \
-        __extension__ ({                                \
+        ({                                              \
                 const typeof(a) UNIQ_T(A, aq) = (a);    \
                 const typeof(b) UNIQ_T(B, bq) = (b);    \
-                UNIQ_T(A,aq) < UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \
+                UNIQ_T(A, aq) < UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \
         })
 
-#define MIN3(x,y,z)                                     \
-        __extension__ ({                                \
-                        const typeof(x) _c = MIN(x,y);  \
-                        MIN(_c, z);                     \
-                })
+#define MIN3(x, y, z)                                   \
+        ({                                              \
+                const typeof(x) _c = MIN(x, y);         \
+                MIN(_c, z);                             \
+        })
 
 #define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b))
 #define __LESS_BY(aq, a, bq, b)                         \
-        __extension__ ({                                \
+        ({                                              \
+                const typeof(a) UNIQ_T(A, aq) = (a);    \
+                const typeof(b) UNIQ_T(B, bq) = (b);    \
+                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) - UNIQ_T(B, bq) : 0; \
+        })
+
+#define CMP(a, b) __CMP(UNIQ, (a), UNIQ, (b))
+#define __CMP(aq, a, bq, b)                             \
+        ({                                              \
                 const typeof(a) UNIQ_T(A, aq) = (a);    \
                 const typeof(b) UNIQ_T(B, bq) = (b);    \
-                UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) - UNIQ_T(B,bq) : 0; \
+                UNIQ_T(A, aq) < UNIQ_T(B, bq) ? -1 :    \
+                UNIQ_T(A, aq) > UNIQ_T(B, bq) ? 1 : 0;  \
         })
 
 #undef CLAMP
 #define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high))
 #define __CLAMP(xq, x, lowq, low, highq, high)                          \
-        __extension__ ({                                                \
-                const typeof(x) UNIQ_T(X,xq) = (x);                     \
-                const typeof(low) UNIQ_T(LOW,lowq) = (low);             \
-                const typeof(high) UNIQ_T(HIGH,highq) = (high);         \
-                        UNIQ_T(X,xq) > UNIQ_T(HIGH,highq) ?             \
-                                UNIQ_T(HIGH,highq) :                    \
-                                UNIQ_T(X,xq) < UNIQ_T(LOW,lowq) ?       \
-                                        UNIQ_T(LOW,lowq) :              \
-                                        UNIQ_T(X,xq);                   \
+        ({                                                              \
+                const typeof(x) UNIQ_T(X, xq) = (x);                    \
+                const typeof(low) UNIQ_T(LOW, lowq) = (low);            \
+                const typeof(high) UNIQ_T(HIGH, highq) = (high);        \
+                        UNIQ_T(X, xq) > UNIQ_T(HIGH, highq) ?           \
+                                UNIQ_T(HIGH, highq) :                   \
+                                UNIQ_T(X, xq) < UNIQ_T(LOW, lowq) ?     \
+                                        UNIQ_T(LOW, lowq) :             \
+                                        UNIQ_T(X, xq);                  \
         })
 
 /* [(x + y - 1) / y] suffers from an integer overflow, even though the
@@ -250,18 +259,54 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
  * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the
  * quotient and the remainder, so both should be equally fast. */
 #define DIV_ROUND_UP(_x, _y)                                            \
-        __extension__ ({                                                \
+        ({                                                              \
                 const typeof(_x) __x = (_x);                            \
                 const typeof(_y) __y = (_y);                            \
                 (__x / __y + !!(__x % __y));                            \
         })
 
+#ifdef __COVERITY__
+
+/* Use special definitions of assertion macros in order to prevent
+ * false positives of ASSERT_SIDE_EFFECT on Coverity static analyzer
+ * for uses of assert_se() and assert_return().
+ *
+ * These definitions make expression go through a (trivial) function
+ * call to ensure they are not discarded. Also use ! or !! to ensure
+ * the boolean expressions are seen as such.
+ *
+ * This technique has been described and recommended in:
+ * https://community.synopsys.com/s/question/0D534000046Yuzb/suppressing-assertsideeffect-for-functions-that-allow-for-sideeffects
+ */
+
+extern void __coverity_panic__(void);
+
+static inline int __coverity_check__(int condition) {
+        return condition;
+}
+
+#define assert_message_se(expr, message)                                \
+        do {                                                            \
+                if (__coverity_check__(!(expr)))                        \
+                        __coverity_panic__();                           \
+        } while (false)
+
+#define assert_log(expr, message) __coverity_check__(!!(expr))
+
+#else  /* ! __COVERITY__ */
+
 #define assert_message_se(expr, message)                                \
         do {                                                            \
                 if (_unlikely_(!(expr)))                                \
                         log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
         } while (false)
 
+#define assert_log(expr, message) ((_likely_(expr))                     \
+        ? (true)                                                        \
+        : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false))
+
+#endif  /* __COVERITY__ */
+
 #define assert_se(expr) assert_message_se(expr, #expr)
 
 /* We override the glibc assert() here. */
@@ -294,10 +339,6 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
         REENABLE_WARNING
 #endif
 
-#define assert_log(expr, message) ((_likely_(expr))                     \
-        ? (true)                                                        \
-        : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false))
-
 #define assert_return(expr, r)                                          \
         do {                                                            \
                 if (!assert_log(expr, #expr))                           \
@@ -419,7 +460,7 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
  * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
  * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769
  */
-#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
+#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
 #define thread_local _Thread_local
 #else
 #define thread_local __thread
diff --git a/src/systemd/src/basic/mempool.c b/src/systemd/src/basic/mempool.c
index a0154263..dd1b1e5f 100644
--- a/src/systemd/src/basic/mempool.c
+++ b/src/systemd/src/basic/mempool.c
@@ -1,7 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2014 Michal Schmidt
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/basic/mempool.h b/src/systemd/src/basic/mempool.h
index 291415de..4098535c 100644
--- a/src/systemd/src/basic/mempool.h
+++ b/src/systemd/src/basic/mempool.h
@@ -1,10 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  Copyright © 2014 Michal Schmidt
-***/
-
 #include <stddef.h>
 
 struct pool;
diff --git a/src/systemd/src/basic/parse-util.c b/src/systemd/src/basic/parse-util.c
index 7d6bcb5a..b0ccce28 100644
--- a/src/systemd/src/basic/parse-util.c
+++ b/src/systemd/src/basic/parse-util.c
@@ -642,6 +642,8 @@ int parse_permille_unbounded(const char *p) {
                 r = safe_atoi(n, &v);
                 if (r < 0)
                         return r;
+                if (v < 0)
+                        return -ERANGE;
         } else {
                 pc = endswith(p, "%");
                 if (!pc)
@@ -662,15 +664,14 @@ int parse_permille_unbounded(const char *p) {
                 r = safe_atoi(n, &v);
                 if (r < 0)
                         return r;
+                if (v < 0)
+                        return -ERANGE;
                 if (v > (INT_MAX - q) / 10)
                         return -ERANGE;
 
                 v = v * 10 + q;
         }
 
-        if (v < 0)
-                return -ERANGE;
-
         return v;
 }
 
diff --git a/src/systemd/src/basic/path-util.c b/src/systemd/src/basic/path-util.c
index 69bdb894..5202eae3 100644
--- a/src/systemd/src/basic/path-util.c
+++ b/src/systemd/src/basic/path-util.c
@@ -113,10 +113,7 @@ int path_make_absolute_cwd(const char *p, char **ret) {
                 if (r < 0)
                         return r;
 
-                if (endswith(cwd, "/"))
-                        c = strjoin(cwd, p);
-                else
-                        c = strjoin(cwd, "/", p);
+                c = path_join(NULL, cwd, p);
         }
         if (!c)
                 return -ENOMEM;
@@ -426,6 +423,7 @@ char* path_startswith(const char *path, const char *prefix) {
                 prefix += b;
         }
 }
+#endif /* NM_IGNORED */
 
 int path_compare(const char *a, const char *b) {
         int d;
@@ -477,6 +475,7 @@ bool path_equal(const char *a, const char *b) {
         return path_compare(a, b) == 0;
 }
 
+#if 0 /* NM_IGNORED */
 bool path_equal_or_files_same(const char *a, const char *b, int flags) {
         return path_equal(a, b) || files_same(a, b, flags) > 0;
 }
diff --git a/src/systemd/src/basic/path-util.h b/src/systemd/src/basic/path-util.h
index 519060cf..72f3ce36 100644
--- a/src/systemd/src/basic/path-util.h
+++ b/src/systemd/src/basic/path-util.h
@@ -60,10 +60,10 @@ static inline bool path_equal_ptr(const char *a, const char *b) {
 /* Note: the search terminates on the first NULL item. */
 #define PATH_IN_SET(p, ...)                                     \
         ({                                                      \
-                char **s;                                       \
+                char **_s;                                      \
                 bool _found = false;                            \
-                STRV_FOREACH(s, STRV_MAKE(__VA_ARGS__))         \
-                        if (path_equal(p, *s)) {                \
+                STRV_FOREACH(_s, STRV_MAKE(__VA_ARGS__))        \
+                        if (path_equal(p, *_s)) {               \
                                _found = true;                   \
                                break;                           \
                         }                                       \
diff --git a/src/systemd/src/basic/process-util.c b/src/systemd/src/basic/process-util.c
index 10aa0e56..1412f036 100644
--- a/src/systemd/src/basic/process-util.c
+++ b/src/systemd/src/basic/process-util.c
@@ -1115,12 +1115,7 @@ int pid_compare_func(const void *a, const void *b) {
         const pid_t *p = a, *q = b;
 
         /* Suitable for usage in qsort() */
-
-        if (*p < *q)
-                return -1;
-        if (*p > *q)
-                return 1;
-        return 0;
+        return CMP(*p, *q);
 }
 
 int ioprio_parse_priority(const char *s, int *ret) {
@@ -1161,7 +1156,7 @@ void reset_cached_pid(void) {
 /* We use glibc __register_atfork() + __dso_handle directly here, as they are not included in the glibc
  * headers. __register_atfork() is mostly equivalent to pthread_atfork(), but doesn't require us to link against
  * libpthread, as it is part of glibc anyway. */
-extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void), void * __dso_handle);
+extern int __register_atfork(void (*prepare) (void), void (*parent) (void), void (*child) (void), void *dso_handle);
 extern void* __dso_handle __attribute__ ((__weak__));
 
 pid_t getpid_cached(void) {
diff --git a/src/systemd/src/basic/random-util.c b/src/systemd/src/basic/random-util.c
index 01a8488f..5c07e067 100644
--- a/src/systemd/src/basic/random-util.c
+++ b/src/systemd/src/basic/random-util.c
@@ -2,6 +2,10 @@
 
 #include "nm-sd-adapt.h"
 
+#ifdef __x86_64__
+#include <cpuid.h>
+#endif
+
 #include <elf.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -28,6 +32,41 @@
 #include "random-util.h"
 #include "time-util.h"
 
+
+int rdrand64(uint64_t *ret) {
+
+#ifdef __x86_64__
+        static int have_rdrand = -1;
+        unsigned char err;
+
+        if (have_rdrand < 0) {
+                uint32_t eax, ebx, ecx, edx;
+
+                /* Check if RDRAND is supported by the CPU */
+                if (__get_cpuid(1, &eax, &ebx, &ecx, &edx) == 0) {
+                        have_rdrand = false;
+                        return -EOPNOTSUPP;
+                }
+
+                have_rdrand = !!(ecx & (1U << 30));
+        }
+
+        if (have_rdrand == 0)
+                return -EOPNOTSUPP;
+
+        asm volatile("rdrand %0;"
+                     "setc %1"
+                     : "=r" (*ret),
+                       "=qm" (err));
+        if (!err)
+                return -EAGAIN;
+
+        return 0;
+#else
+        return -EOPNOTSUPP;
+#endif
+}
+
 int acquire_random_bytes(void *p, size_t n, bool high_quality_required) {
         static int have_syscall = -1;
 
@@ -77,8 +116,26 @@ int acquire_random_bytes(void *p, size_t n, bool high_quality_required) {
                          * a best-effort basis. */
                         have_syscall = true;
 
-                        if (!high_quality_required)
-                                return -ENODATA;
+                        if (!high_quality_required) {
+                                uint64_t u;
+                                size_t k;
+
+                                /* Try x86-64' RDRAND intrinsic if we have it. We only use it if high quality
+                                 * randomness is not required, as we don't trust it (who does?). Note that we only do a
+                                 * single iteration of RDRAND here, even though the Intel docs suggest calling this in
+                                 * a tight loop of 10 invocatins or so. That's because we don't really care about the
+                                 * quality here. */
+
+                                if (rdrand64(&u) < 0)
+                                        return -ENODATA;
+
+                                k = MIN(n, sizeof(u));
+                                memcpy(p, &u, k);
+
+                                /* We only get 64bit out of RDRAND, the rest let's fill up with pseudo-random crap. */
+                                pseudorandom_bytes((uint8_t*) p + k, n - k);
+                                return 0;
+                        }
                 } else
                         return -errno;
         }
diff --git a/src/systemd/src/basic/random-util.h b/src/systemd/src/basic/random-util.h
index 9a103f0e..affcc9ac 100644
--- a/src/systemd/src/basic/random-util.h
+++ b/src/systemd/src/basic/random-util.h
@@ -21,3 +21,5 @@ static inline uint32_t random_u32(void) {
         random_bytes(&u, sizeof(u));
         return u;
 }
+
+int rdrand64(uint64_t *ret);
diff --git a/src/systemd/src/basic/socket-util.c b/src/systemd/src/basic/socket-util.c
index a71db5a7..acc22901 100644
--- a/src/systemd/src/basic/socket-util.c
+++ b/src/systemd/src/basic/socket-util.c
@@ -53,7 +53,8 @@ static const char* const socket_address_type_table[] = {
 DEFINE_STRING_TABLE_LOOKUP(socket_address_type, int);
 
 int socket_address_parse(SocketAddress *a, const char *s) {
-        char *e, *n;
+        _cleanup_free_ char *n = NULL;
+        char *e;
         int r;
 
         assert(a);
@@ -71,7 +72,9 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                 if (!e)
                         return -EINVAL;
 
-                n = strndupa(s+1, e-s-1);
+                n = strndup(s+1, e-s-1);
+                if (!n)
+                        return -ENOMEM;
 
                 errno = 0;
                 if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0)
@@ -128,7 +131,10 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                 if (r < 0)
                         return r;
 
-                n = strndupa(cid_start, e - cid_start);
+                n = strndup(cid_start, e - cid_start);
+                if (!n)
+                        return -ENOMEM;
+
                 if (!isempty(n)) {
                         r = safe_atou(n, &a->sockaddr.vm.svm_cid);
                         if (r < 0)
@@ -149,7 +155,9 @@ int socket_address_parse(SocketAddress *a, const char *s) {
                         if (r < 0)
                                 return r;
 
-                        n = strndupa(s, e-s);
+                        n = strndup(s, e-s);
+                        if (!n)
+                                return -ENOMEM;
 
                         /* IPv4 in w.x.y.z:p notation? */
                         r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr);
@@ -1006,9 +1014,10 @@ int getpeergroups(int fd, gid_t **ret) {
         return (int) n;
 }
 
-int send_one_fd_sa(
+ssize_t send_one_fd_iov_sa(
                 int transport_fd,
                 int fd,
+                struct iovec *iov, size_t iovlen,
                 const struct sockaddr *sa, socklen_t len,
                 int flags) {
 
@@ -1019,28 +1028,58 @@ int send_one_fd_sa(
         struct msghdr mh = {
                 .msg_name = (struct sockaddr*) sa,
                 .msg_namelen = len,
-                .msg_control = &control,
-                .msg_controllen = sizeof(control),
+                .msg_iov = iov,
+                .msg_iovlen = iovlen,
         };
-        struct cmsghdr *cmsg;
+        ssize_t k;
 
         assert(transport_fd >= 0);
-        assert(fd >= 0);
 
-        cmsg = CMSG_FIRSTHDR(&mh);
-        cmsg->cmsg_level = SOL_SOCKET;
-        cmsg->cmsg_type = SCM_RIGHTS;
-        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
-        memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+        /*
+         * We need either an FD or data to send.
+         * If there's nothing, return an error.
+         */
+        if (fd < 0 && !iov)
+                return -EINVAL;
 
-        mh.msg_controllen = CMSG_SPACE(sizeof(int));
-        if (sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags) < 0)
-                return -errno;
+        if (fd >= 0) {
+                struct cmsghdr *cmsg;
 
-        return 0;
+                mh.msg_control = &control;
+                mh.msg_controllen = sizeof(control);
+
+                cmsg = CMSG_FIRSTHDR(&mh);
+                cmsg->cmsg_level = SOL_SOCKET;
+                cmsg->cmsg_type = SCM_RIGHTS;
+                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+                memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+                mh.msg_controllen = CMSG_SPACE(sizeof(int));
+        }
+        k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags);
+        if (k < 0)
+                return (ssize_t) -errno;
+
+        return k;
 }
 
-int receive_one_fd(int transport_fd, int flags) {
+int send_one_fd_sa(
+                int transport_fd,
+                int fd,
+                const struct sockaddr *sa, socklen_t len,
+                int flags) {
+
+        assert(fd >= 0);
+
+        return (int) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, sa, len, flags);
+}
+
+ssize_t receive_one_fd_iov(
+                int transport_fd,
+                struct iovec *iov, size_t iovlen,
+                int flags,
+                int *ret_fd) {
+
         union {
                 struct cmsghdr cmsghdr;
                 uint8_t buf[CMSG_SPACE(sizeof(int))];
@@ -1048,10 +1087,14 @@ int receive_one_fd(int transport_fd, int flags) {
         struct msghdr mh = {
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
+                .msg_iov = iov,
+                .msg_iovlen = iovlen,
         };
         struct cmsghdr *cmsg, *found = NULL;
+        ssize_t k;
 
         assert(transport_fd >= 0);
+        assert(ret_fd);
 
         /*
          * Receive a single FD via @transport_fd. We don't care for
@@ -1061,8 +1104,9 @@ int receive_one_fd(int transport_fd, int flags) {
          * combination with send_one_fd().
          */
 
-        if (recvmsg(transport_fd, &mh, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC | flags) < 0)
-                return -errno;
+        k = recvmsg(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags);
+        if (k < 0)
+                return (ssize_t) -errno;
 
         CMSG_FOREACH(cmsg, &mh) {
                 if (cmsg->cmsg_level == SOL_SOCKET &&
@@ -1074,12 +1118,33 @@ int receive_one_fd(int transport_fd, int flags) {
                 }
         }
 
-        if (!found) {
+        if (!found)
                 cmsg_close_all(&mh);
+
+        /* If didn't receive an FD or any data, return an error. */
+        if (k == 0 && !found)
                 return -EIO;
-        }
 
-        return *(int*) CMSG_DATA(found);
+        if (found)
+                *ret_fd = *(int*) CMSG_DATA(found);
+        else
+                *ret_fd = -1;
+
+        return k;
+}
+
+int receive_one_fd(int transport_fd, int flags) {
+        int fd;
+        ssize_t k;
+
+        k = receive_one_fd_iov(transport_fd, NULL, 0, flags, &fd);
+        if (k == 0)
+                return fd;
+
+        /* k must be negative, since receive_one_fd_iov() only returns
+         * a positive value if data was received through the iov. */
+        assert(k < 0);
+        return (int) k;
 }
 #endif /* NM_IGNORED */
 
diff --git a/src/systemd/src/basic/socket-util.h b/src/systemd/src/basic/socket-util.h
index e2db795a..d7b814ae 100644
--- a/src/systemd/src/basic/socket-util.h
+++ b/src/systemd/src/basic/socket-util.h
@@ -132,11 +132,19 @@ int getpeercred(int fd, struct ucred *ucred);
 int getpeersec(int fd, char **ret);
 int getpeergroups(int fd, gid_t **ret);
 
+ssize_t send_one_fd_iov_sa(
+                int transport_fd,
+                int fd,
+                struct iovec *iov, size_t iovlen,
+                const struct sockaddr *sa, socklen_t len,
+                int flags);
 int send_one_fd_sa(int transport_fd,
                    int fd,
                    const struct sockaddr *sa, socklen_t len,
                    int flags);
-#define send_one_fd(transport_fd, fd, flags) send_one_fd_sa(transport_fd, fd, NULL, 0, flags)
+#define send_one_fd_iov(transport_fd, fd, iov, iovlen, flags) send_one_fd_iov_sa(transport_fd, fd, iov, iovlen, NULL, 0, flags)
+#define send_one_fd(transport_fd, fd, flags) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, NULL, 0, flags)
+ssize_t receive_one_fd_iov(int transport_fd, struct iovec *iov, size_t iovlen, int flags, int *ret_fd);
 int receive_one_fd(int transport_fd, int flags);
 
 ssize_t next_datagram_size_fd(int fd);
diff --git a/src/systemd/src/basic/stat-util.c b/src/systemd/src/basic/stat-util.c
index 9892d8bc..d645ce30 100644
--- a/src/systemd/src/basic/stat-util.c
+++ b/src/systemd/src/basic/stat-util.c
@@ -48,6 +48,17 @@ int is_dir(const char* path, bool follow) {
         return !!S_ISDIR(st.st_mode);
 }
 
+int is_dir_fd(int fd) {
+        struct stat st;
+        int r;
+
+        r = fstat(fd, &st);
+        if (r < 0)
+                return -errno;
+
+        return !!S_ISDIR(st.st_mode);
+}
+
 int is_device_node(const char *path) {
         struct stat info;
 
diff --git a/src/systemd/src/basic/stat-util.h b/src/systemd/src/basic/stat-util.h
index f8014ed3..1a725f1d 100644
--- a/src/systemd/src/basic/stat-util.h
+++ b/src/systemd/src/basic/stat-util.h
@@ -12,6 +12,7 @@
 
 int is_symlink(const char *path);
 int is_dir(const char *path, bool follow);
+int is_dir_fd(int fd);
 int is_device_node(const char *path);
 
 int dir_is_empty(const char *path);
diff --git a/src/systemd/src/basic/strv.c b/src/systemd/src/basic/strv.c
index 9da8cece..6f80b317 100644
--- a/src/systemd/src/basic/strv.c
+++ b/src/systemd/src/basic/strv.c
@@ -255,6 +255,10 @@ char **strv_split(const char *s, const char *separator) {
 
         assert(s);
 
+        s += strspn(s, separator);
+        if (isempty(s))
+                return new0(char*, 1);
+
         n = 0;
         FOREACH_WORD_SEPARATOR(word, l, s, separator, state)
                 n++;
diff --git a/src/systemd/src/basic/time-util.c b/src/systemd/src/basic/time-util.c
index aafa0375..c0f53a47 100644
--- a/src/systemd/src/basic/time-util.c
+++ b/src/systemd/src/basic/time-util.c
@@ -2,6 +2,7 @@
 
 #include "nm-sd-adapt.h"
 
+#include <ctype.h>
 #include <errno.h>
 #include <limits.h>
 #include <stdlib.h>
@@ -1001,10 +1002,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
         }
 
         for (;;) {
+                usec_t multiplier = default_unit, k;
                 long long l, z = 0;
-                char *e;
                 unsigned n = 0;
-                usec_t multiplier = default_unit, k;
+                char *e;
 
                 p += strspn(p, WHITESPACE);
 
@@ -1015,6 +1016,9 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                         break;
                 }
 
+                if (*p == '-') /* Don't allow "-0" */
+                        return -ERANGE;
+
                 errno = 0;
                 l = strtoll(p, &e, 10);
                 if (errno > 0)
@@ -1025,14 +1029,16 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                 if (*e == '.') {
                         char *b = e + 1;
 
+                        /* Don't allow "0.-0", "3.+1" or "3. 1" */
+                        if (*b == '-' || *b == '+' || isspace(*b))
+                                return -EINVAL;
+
                         errno = 0;
                         z = strtoll(b, &e, 10);
                         if (errno > 0)
                                 return -errno;
-
                         if (z < 0)
                                 return -ERANGE;
-
                         if (e == b)
                                 return -EINVAL;
 
@@ -1149,26 +1155,28 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                         break;
                 }
 
+                if (*p == '-')
+                        return -ERANGE;
+
                 errno = 0;
                 l = strtoll(p, &e, 10);
-
                 if (errno > 0)
                         return -errno;
-
                 if (l < 0)
                         return -ERANGE;
 
                 if (*e == '.') {
                         char *b = e + 1;
 
+                        if (*b == '-' || *b == '+' || isspace(*b))
+                                return -EINVAL;
+
                         errno = 0;
                         z = strtoll(b, &e, 10);
                         if (errno > 0)
                                 return -errno;
-
                         if (z < 0)
                                 return -ERANGE;
-
                         if (e == b)
                                 return -EINVAL;
 
diff --git a/src/systemd/src/basic/utf8.c b/src/systemd/src/basic/utf8.c
index 80de43c5..7238bca7 100644
--- a/src/systemd/src/basic/utf8.c
+++ b/src/systemd/src/basic/utf8.c
@@ -1,7 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2008-2011 Kay Sievers
-***/
 
 /* Parts of this file are based on the GLIB utf8 validation functions. The
  * original license text follows. */
diff --git a/src/systemd/src/basic/util.c b/src/systemd/src/basic/util.c
index ba7474f0..2c6101ea 100644
--- a/src/systemd/src/basic/util.c
+++ b/src/systemd/src/basic/util.c
@@ -82,31 +82,6 @@ bool display_is_local(const char *display) {
                 display[1] <= '9';
 }
 
-int socket_from_display(const char *display, char **path) {
-        size_t k;
-        char *f, *c;
-
-        assert(display);
-        assert(path);
-
-        if (!display_is_local(display))
-                return -EINVAL;
-
-        k = strspn(display+1, "0123456789");
-
-        f = new(char, STRLEN("/tmp/.X11-unix/X") + k + 1);
-        if (!f)
-                return -ENOMEM;
-
-        c = stpcpy(f, "/tmp/.X11-unix/X");
-        memcpy(c, display+1, k);
-        c[k] = 0;
-
-        *path = f;
-
-        return 0;
-}
-
 bool kexec_loaded(void) {
        _cleanup_free_ char *s = NULL;
 
@@ -260,6 +235,11 @@ int container_get_leader(const char *machine, pid_t *pid) {
         assert(machine);
         assert(pid);
 
+        if (streq(machine, ".host")) {
+                *pid = 1;
+                return 0;
+        }
+
         if (!machine_name_is_valid(machine))
                 return -EINVAL;
 
diff --git a/src/systemd/src/basic/util.h b/src/systemd/src/basic/util.h
index 9699d228..308933ac 100644
--- a/src/systemd/src/basic/util.h
+++ b/src/systemd/src/basic/util.h
@@ -50,7 +50,6 @@ static inline const char* enable_disable(bool b) {
 bool plymouth_running(void);
 
 bool display_is_local(const char *display) _pure_;
-int socket_from_display(const char *display, char **path);
 
 #define NULSTR_FOREACH(i, l)                                    \
         for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1)
@@ -113,9 +112,7 @@ static inline void qsort_r_safe(void *base, size_t nmemb, size_t size, int (*com
         qsort_r(base, nmemb, size, compar, userdata);
 }
 
-/**
- * Normal memcpy requires src to be nonnull. We do nothing if n is 0.
- */
+/* Normal memcpy requires src to be nonnull. We do nothing if n is 0. */
 static inline void memcpy_safe(void *dst, const void *src, size_t n) {
         if (n == 0)
                 return;
@@ -123,6 +120,15 @@ static inline void memcpy_safe(void *dst, const void *src, size_t n) {
         memcpy(dst, src, n);
 }
 
+/* Normal memcmp requires s1 and s2 to be nonnull. We do nothing if n is 0. */
+static inline int memcmp_safe(const void *s1, const void *s2, size_t n) {
+        if (n == 0)
+                return 0;
+        assert(s1);
+        assert(s2);
+        return memcmp(s1, s2, n);
+}
+
 int on_ac_power(void);
 
 #define memzero(x,l) (memset((x), 0, (l)))
diff --git a/src/systemd/src/libsystemd-network/arp-util.c b/src/systemd/src/libsystemd-network/arp-util.c
index 1b711c95..4fbecb54 100644
--- a/src/systemd/src/libsystemd-network/arp-util.c
+++ b/src/systemd/src/libsystemd-network/arp-util.c
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   Copyright © 2014 Axis Communications AB. All rights reserved.
-  Copyright © 2015 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index 75357367..f18713e8 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -1,11 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2015 Tom Gundersen <teg@jklmen>
-***/
 
 #include "nm-sd-adapt.h"
 
-#include "libudev.h"
+#include <linux/if_infiniband.h>
+#include <net/if_arp.h>
+
+#include "sd-device.h"
 #include "sd-id128.h"
 
 #include "dhcp-identifier.h"
@@ -13,7 +13,6 @@
 #include "network-internal.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
-#include "udev-util.h"
 #include "virt.h"
 
 #if 0 /* NM_IGNORED */
@@ -21,8 +20,10 @@
 #include <net/if.h>
 #endif /* NM_IGNORED */
 
-#define SYSTEMD_PEN 43793
-#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
+#define SYSTEMD_PEN    43793
+#define HASH_KEY       SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
+#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
+#define USEC_2000       ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */
 
 int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len) {
         struct duid d;
@@ -55,6 +56,58 @@ int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len) {
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
+int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
+        uint16_t time_from_2000y;
+
+        assert(duid);
+        assert(len);
+        assert(addr);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(addr_len == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+        else
+                return -EINVAL;
+
+        if (t < USEC_2000)
+                time_from_2000y = 0;
+        else
+                time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff);
+
+        unaligned_write_be16(&duid->type, DUID_TYPE_LLT);
+        unaligned_write_be16(&duid->llt.htype, arp_type);
+        unaligned_write_be32(&duid->llt.time, time_from_2000y);
+        memcpy(duid->llt.haddr, addr, addr_len);
+
+        *len = sizeof(duid->type) + sizeof(duid->llt.htype) + sizeof(duid->llt.time) + addr_len;
+
+        return 0;
+}
+
+int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len) {
+        assert(duid);
+        assert(len);
+        assert(addr);
+
+        if (arp_type == ARPHRD_ETHER)
+                assert_return(addr_len == ETH_ALEN, -EINVAL);
+        else if (arp_type == ARPHRD_INFINIBAND)
+                assert_return(addr_len == INFINIBAND_ALEN, -EINVAL);
+        else
+                return -EINVAL;
+
+        unaligned_write_be16(&duid->type, DUID_TYPE_LL);
+        unaligned_write_be16(&duid->ll.htype, arp_type);
+        memcpy(duid->ll.haddr, addr, addr_len);
+
+        *len = sizeof(duid->type) + sizeof(duid->ll.htype) + addr_len;
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
 int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) {
         sd_id128_t machine_id;
         uint64_t hash;
@@ -73,18 +126,39 @@ int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) {
         *len = sizeof(duid->type) + sizeof(duid->en);
 
         /* a bit of snake-oil perhaps, but no need to expose the machine-id
-           directly; duid->en.id might not be aligned, so we need to copy */
+         * directly; duid->en.id might not be aligned, so we need to copy */
         hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes));
         memcpy(duid->en.id, &hash, sizeof(duid->en.id));
 
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
+int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len) {
+        sd_id128_t machine_id;
+        int r;
+
+        assert(duid);
+        assert(len);
+
+        r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id);
+        if (r < 0)
+                return r;
+
+        unaligned_write_be16(&duid->type, DUID_TYPE_UUID);
+        memcpy(&duid->raw.data, &machine_id, sizeof(machine_id));
+
+        *len = sizeof(duid->type) + sizeof(machine_id);
+
+        return 0;
+}
+#endif
+
 int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id) {
 #if 0 /* NM_IGNORED */
-        /* name is a pointer to memory in the udev_device struct, so must
-           have the same scope */
-        _cleanup_(udev_device_unrefp) struct udev_device *device = NULL;
+        /* name is a pointer to memory in the sd_device struct, so must
+         * have the same scope */
+        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
 #else /* NM_IGNORED */
         char name_buf[IF_NAMESIZE];
 #endif /* NM_IGNORED */
@@ -94,17 +168,15 @@ int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_i
 #if 0 /* NM_IGNORED */
         if (detect_container() <= 0) {
                 /* not in a container, udev will be around */
-                _cleanup_(udev_unrefp) struct udev *udev;
                 char ifindex_str[2 + DECIMAL_STR_MAX(int)];
-
-                udev = udev_new();
-                if (!udev)
-                        return -ENOMEM;
+                int initialized, r;
 
                 sprintf(ifindex_str, "n%d", ifindex);
-                device = udev_device_new_from_device_id(udev, ifindex_str);
-                if (device) {
-                        if (udev_device_get_is_initialized(device) <= 0)
+                if (sd_device_new_from_device_id(&device, ifindex_str) >= 0) {
+                        r = sd_device_get_is_initialized(device, &initialized);
+                        if (r < 0)
+                                return r;
+                        if (!initialized)
                                 /* not yet ready */
                                 return -EBUSY;
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.h b/src/systemd/src/libsystemd-network/dhcp-identifier.h
index 42d4956d..64315d3a 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.h
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.h
@@ -1,14 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  Copyright © 2015 Tom Gundersen <teg@jklmen>
-***/
-
 #include "sd-id128.h"
 
 #include "macro.h"
 #include "sparse-endian.h"
+#include "time-util.h"
 #include "unaligned.h"
 
 typedef enum DUIDType {
@@ -31,18 +28,18 @@ struct duid {
         union {
                 struct {
                         /* DUID_TYPE_LLT */
-                        uint16_t htype;
-                        uint32_t time;
+                        be16_t htype;
+                        be32_t time;
                         uint8_t haddr[0];
                 } _packed_ llt;
                 struct {
                         /* DUID_TYPE_EN */
-                        uint32_t pen;
+                        be32_t pen;
                         uint8_t id[8];
                 } _packed_ en;
                 struct {
                         /* DUID_TYPE_LL */
-                        int16_t htype;
+                        be16_t htype;
                         uint8_t haddr[0];
                 } _packed_ ll;
                 struct {
@@ -56,5 +53,8 @@ struct duid {
 } _packed_;
 
 int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len);
+int dhcp_identifier_set_duid_llt(struct duid *duid, usec_t t, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
+int dhcp_identifier_set_duid_ll(struct duid *duid, const uint8_t *addr, size_t addr_len, uint16_t arp_type, size_t *len);
 int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len);
+int dhcp_identifier_set_duid_uuid(struct duid *duid, size_t *len);
 int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id);
diff --git a/src/systemd/src/libsystemd-network/dhcp-internal.h b/src/systemd/src/libsystemd-network/dhcp-internal.h
index 257a3c2e..a0f9c229 100644
--- a/src/systemd/src/libsystemd-network/dhcp-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-internal.h
@@ -3,7 +3,6 @@
 
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
-  Copyright © 2014 Tom Gundersen
 ***/
 
 #include <linux/if_packet.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index fabac183..9d245a90 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -3,7 +3,6 @@
 
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
-  Copyright © 2014 Tom Gundersen
 ***/
 
 #include <stdint.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/systemd/src/libsystemd-network/dhcp-packet.c
index 214aed8e..8f8acb0d 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/systemd/src/libsystemd-network/dhcp-packet.c
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
-  Copyright © 2014 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
index dc85c437..ff0b0f00 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-lease-internal.h
@@ -2,7 +2,6 @@
 #pragma once
 
 /***
-  Copyright © 2014 Tom Gundersen
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
diff --git a/src/systemd/src/libsystemd-network/lldp-internal.h b/src/systemd/src/libsystemd-network/lldp-internal.h
index e5650988..328d51f8 100644
--- a/src/systemd/src/libsystemd-network/lldp-internal.h
+++ b/src/systemd/src/libsystemd-network/lldp-internal.h
@@ -1,11 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  Copyright © 2014 Tom Gundersen
-  Copyright © 2014 Susant Sahani
-***/
-
 #include "sd-event.h"
 #include "sd-lldp.h"
 
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/systemd/src/libsystemd-network/lldp-neighbor.c
index c28c9956..cd9358a4 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -28,22 +28,15 @@ static int lldp_neighbor_id_compare_func(const void *a, const void *b) {
         if (r != 0)
                 return r;
 
-        if (x->chassis_id_size < y->chassis_id_size)
-                return -1;
-
-        if (x->chassis_id_size > y->chassis_id_size)
-                return 1;
+        r = CMP(x->chassis_id_size, y->chassis_id_size);
+        if (r != 0)
+                return r;
 
         r = memcmp(x->port_id, y->port_id, MIN(x->port_id_size, y->port_id_size));
         if (r != 0)
                 return r;
 
-        if (x->port_id_size < y->port_id_size)
-                return -1;
-        if (x->port_id_size > y->port_id_size)
-                return 1;
-
-        return 0;
+        return CMP(x->port_id_size, y->port_id_size);
 }
 
 const struct hash_ops lldp_neighbor_id_hash_ops = {
@@ -54,13 +47,7 @@ const struct hash_ops lldp_neighbor_id_hash_ops = {
 int lldp_neighbor_prioq_compare_func(const void *a, const void *b) {
         const sd_lldp_neighbor *x = a, *y = b;
 
-        if (x->until < y->until)
-                return -1;
-
-        if (x->until > y->until)
-                return 1;
-
-        return 0;
+        return CMP(x->until, y->until);
 }
 
 _public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) {
diff --git a/src/systemd/src/libsystemd-network/lldp-network.c b/src/systemd/src/libsystemd-network/lldp-network.c
index bc6c20d1..db779aaa 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.c
+++ b/src/systemd/src/libsystemd-network/lldp-network.c
@@ -1,8 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2014 Tom Gundersen
-  Copyright © 2014 Susant Sahani
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/libsystemd-network/lldp-network.h b/src/systemd/src/libsystemd-network/lldp-network.h
index 1d773acc..e4ed2898 100644
--- a/src/systemd/src/libsystemd-network/lldp-network.h
+++ b/src/systemd/src/libsystemd-network/lldp-network.h
@@ -1,11 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-  Copyright © 2014 Tom Gundersen
-  Copyright © 2014 Susant Sahani
-***/
-
 #include "sd-event.h"
 
 int lldp_network_bind_raw_socket(int ifindex);
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index 308762ee..57a59030 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -1,7 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
- Copyright © 2013 Tom Gundersen <teg@jklm.no>
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -29,24 +26,22 @@
 #include "util.h"
 
 #if 0 /* NM_IGNORED */
-const char *net_get_name(struct udev_device *device) {
+const char *net_get_name(sd_device *device) {
         const char *name, *field;
 
         assert(device);
 
         /* fetch some persistent data unique (on this machine) to this device */
-        FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT", "ID_NET_NAME_PATH", "ID_NET_NAME_MAC") {
-                name = udev_device_get_property_value(device, field);
-                if (name)
+        FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT", "ID_NET_NAME_PATH", "ID_NET_NAME_MAC")
+                if (sd_device_get_property_value(device, field, &name) >= 0)
                         return name;
-        }
 
         return NULL;
 }
 
 #define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
 
-int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result) {
+int net_get_unique_predictable_data(sd_device *device, uint64_t *result) {
         size_t l, sz = 0;
         const char *name = NULL;
         int r;
@@ -130,7 +125,7 @@ bool net_match_config(Set *match_mac,
         if (match_arch && condition_test(match_arch) <= 0)
                 return false;
 
-        if (match_mac && dev_mac && !set_contains(match_mac, dev_mac))
+        if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
                 return false;
 
         if (!net_condition_test_strv(match_paths, dev_path))
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index ef69e701..06d61184 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -1,18 +1,14 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
- Copyright © 2013 Tom Gundersen <teg@jklm.no>
-***/
-
 #include <stdbool.h>
 
+#include "sd-device.h"
 #include "sd-dhcp-lease.h"
 
 #include "condition.h"
 #include "conf-parser.h"
 #include "set.h"
-#include "udev.h"
 
 #define LINK_BRIDGE_PORT_PRIORITY_INVALID 128
 #define LINK_BRIDGE_PORT_PRIORITY_MAX 63
@@ -42,10 +38,10 @@ CONFIG_PARSER_PROTOTYPE(config_parse_ifnames);
 CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
 CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
 CONFIG_PARSER_PROTOTYPE(config_parse_bridge_port_priority);
-#endif /* NM_IGNORED */
 
-int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result);
-const char *net_get_name(struct udev_device *device);
+int net_get_unique_predictable_data(sd_device *device, uint64_t *result);
+const char *net_get_name(sd_device *device);
+#endif /* NM_IGNORED */
 
 void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size);
 int deserialize_in_addrs(struct in_addr **addresses, const char *string);
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index c2f81e1c..42707f10 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -345,13 +345,14 @@ int sd_dhcp_client_set_client_id(
  * without further modification. Otherwise, if duid_type is supported, DUID
  * is set based on that type. Otherwise, an error is returned.
  */
-static int dhcp_client_set_iaid_duid(
+static int dhcp_client_set_iaid_duid_internal(
                 sd_dhcp_client *client,
                 uint32_t iaid,
                 bool append_iaid,
                 uint16_t duid_type,
                 const void *duid,
-                size_t duid_len) {
+                size_t duid_len,
+                usec_t llt_time) {
 
         DHCP_CLIENT_DONT_DESTROY(client);
         int r;
@@ -385,18 +386,43 @@ static int dhcp_client_set_iaid_duid(
                 client->client_id.ns.duid.type = htobe16(duid_type);
                 memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len);
                 len = sizeof(client->client_id.ns.duid.type) + duid_len;
-        } else if (duid_type == DUID_TYPE_EN) {
-                r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
-                if (r < 0)
-                        return r;
         } else
-                return -EOPNOTSUPP;
+                switch (duid_type) {
+                case DUID_TYPE_LLT:
+                        if (!client->mac_addr || client->mac_addr_len == 0)
+                                return -EOPNOTSUPP;
+
+                        r = dhcp_identifier_set_duid_llt(&client->client_id.ns.duid, llt_time, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+                        if (r < 0)
+                                return r;
+                        break;
+                case DUID_TYPE_EN:
+                        r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len);
+                        if (r < 0)
+                                return r;
+                        break;
+                case DUID_TYPE_LL:
+                        if (!client->mac_addr || client->mac_addr_len == 0)
+                                return -EOPNOTSUPP;
+
+                        r = dhcp_identifier_set_duid_ll(&client->client_id.ns.duid, client->mac_addr, client->mac_addr_len, client->arp_type, &len);
+                        if (r < 0)
+                                return r;
+                        break;
+                case DUID_TYPE_UUID:
+                        r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len);
+                        if (r < 0)
+                                return r;
+                        break;
+                default:
+                        return -EINVAL;
+                }
 
         client->client_id_len = sizeof(client->client_id.type) + len +
                                 (append_iaid ? sizeof(client->client_id.ns.iaid) : 0);
 
         if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) {
-                log_dhcp_client(client, "Configured IAID+DUID, restarting.");
+                log_dhcp_client(client, "Configured %sDUID, restarting.", append_iaid ? "IAID+" : "");
                 client_stop(client, SD_DHCP_CLIENT_EVENT_STOP);
                 sd_dhcp_client_start(client);
         }
@@ -410,7 +436,14 @@ int sd_dhcp_client_set_iaid_duid(
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len) {
-        return dhcp_client_set_iaid_duid(client, iaid, true, duid_type, duid, duid_len);
+        return dhcp_client_set_iaid_duid_internal(client, iaid, true, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_iaid_duid_llt(
+                sd_dhcp_client *client,
+                uint32_t iaid,
+                usec_t llt_time) {
+        return dhcp_client_set_iaid_duid_internal(client, iaid, true, DUID_TYPE_LLT, NULL, 0, llt_time);
 }
 
 int sd_dhcp_client_set_duid(
@@ -418,7 +451,13 @@ int sd_dhcp_client_set_duid(
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len) {
-        return dhcp_client_set_iaid_duid(client, 0, false, duid_type, duid, duid_len);
+        return dhcp_client_set_iaid_duid_internal(client, 0, false, duid_type, duid, duid_len, 0);
+}
+
+int sd_dhcp_client_set_duid_llt(
+                sd_dhcp_client *client,
+                usec_t llt_time) {
+        return dhcp_client_set_iaid_duid_internal(client, 0, false, DUID_TYPE_LLT, NULL, 0, llt_time);
 }
 #endif /* NM_IGNORED */
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index 33a0796a..d2402595 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
-  Copyright © 2014 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
@@ -28,6 +27,7 @@
 #include "parse-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
+#include "strv.h"
 #include "unaligned.h"
 
 int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) {
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index ca03f580..882a7eb8 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -184,13 +184,14 @@ static int client_ensure_duid(sd_dhcp6_client *client) {
  * without further modification. Otherwise, if duid_type is supported, DUID
  * is set based on that type. Otherwise, an error is returned.
  */
-int sd_dhcp6_client_set_duid(
+static int dhcp6_client_set_duid_internal(
                 sd_dhcp6_client *client,
                 uint16_t duid_type,
                 const void *duid,
-                size_t duid_len) {
-
+                size_t duid_len,
+                usec_t llt_time) {
         int r;
+
         assert_return(client, -EINVAL);
         assert_return(duid_len == 0 || duid != NULL, -EINVAL);
         assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
@@ -199,23 +200,64 @@ int sd_dhcp6_client_set_duid(
                 r = dhcp_validate_duid_len(duid_type, duid_len);
                 if (r < 0)
                         return r;
-        }
 
-        if (duid != NULL) {
                 client->duid.type = htobe16(duid_type);
                 memcpy(&client->duid.raw.data, duid, duid_len);
                 client->duid_len = sizeof(client->duid.type) + duid_len;
-        } else if (duid_type == DUID_TYPE_EN) {
-                r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
-                if (r < 0)
-                        return r;
         } else
-                return -EOPNOTSUPP;
+#if 0 /* NM_IGNORED */
+                switch (duid_type) {
+                case DUID_TYPE_LLT:
+                        if (!client->mac_addr || client->mac_addr_len == 0)
+                                return -EOPNOTSUPP;
+
+                        r = dhcp_identifier_set_duid_llt(&client->duid, 0, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
+                        if (r < 0)
+                                return r;
+                        break;
+                case DUID_TYPE_EN:
+                        r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len);
+                        if (r < 0)
+                                return r;
+                        break;
+                case DUID_TYPE_LL:
+                        if (!client->mac_addr || client->mac_addr_len == 0)
+                                return -EOPNOTSUPP;
+
+                        r = dhcp_identifier_set_duid_ll(&client->duid, client->mac_addr, client->mac_addr_len, client->arp_type, &client->duid_len);
+                        if (r < 0)
+                                return r;
+                        break;
+                case DUID_TYPE_UUID:
+                        r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len);
+                        if (r < 0)
+                                return r;
+                        break;
+                default:
+                        return -EINVAL;
+                }
+#else /* NM_IGNORED */
+                g_return_val_if_reached (-EINVAL);
+#endif /* NM_IGNORED */
 
         return 0;
 }
 
+int sd_dhcp6_client_set_duid(
+                sd_dhcp6_client *client,
+                uint16_t duid_type,
+                const void *duid,
+                size_t duid_len) {
+        return dhcp6_client_set_duid_internal(client, duid_type, duid, duid_len, 0);
+}
+
 #if 0 /* NM_IGNORED */
+int sd_dhcp6_client_set_duid_llt(
+                sd_dhcp6_client *client,
+                usec_t llt_time) {
+        return dhcp6_client_set_duid_internal(client, DUID_TYPE_LLT, NULL, 0, llt_time);
+}
+
 int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) {
         assert_return(client, -EINVAL);
         assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY);
@@ -1148,9 +1190,8 @@ static int client_receive_message(
                 return 0;
         }
 
-        if (r >= 0)
-                log_dhcp6_client(client, "Recv %s",
-                                 dhcp6_message_type_to_string(message->type));
+        log_dhcp6_client(client, "Recv %s",
+                         dhcp6_message_type_to_string(message->type));
 
         return 0;
 }
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index 1c8346d4..cca0b500 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  Copyright © 2014 Tom Gundersen
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 ***/
 
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index 3766c1f3..a39a865a 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   Copyright © 2014 Axis Communications AB. All rights reserved.
-  Copyright © 2015 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index ca1e0bdb..69fa60e2 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   Copyright © 2014 Axis Communications AB. All rights reserved.
-  Copyright © 2015 Tom Gundersen
 ***/
 
 #include "nm-sd-adapt.h"
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/systemd/src/libsystemd-network/sd-lldp.c
index ea1dda4d..74ba8236 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/systemd/src/libsystemd-network/sd-lldp.c
@@ -1,8 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-  Copyright © 2014 Tom Gundersen
-  Copyright © 2014 Susant Sahani
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index f4d5dbe4..2af3572d 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -1,6 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-***/
 
 #include "nm-sd-adapt.h"
 
@@ -94,7 +92,7 @@ struct sd_event_source {
         char *description;
 
         EventSourceType type:5;
-        int enabled:3;
+        signed int enabled:3;
         bool pending:1;
         bool dispatching:1;
         bool floating:1;
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index 03a38699..d4d668e8 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -1,6 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.h b/src/systemd/src/libsystemd/sd-id128/id128-util.h
index f0b4eca5..44f159c0 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.h
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.h
@@ -1,9 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
-***/
-
 #include <stdbool.h>
 
 #include "sd-id128.h"
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index 8fe8ff43..483b2fe0 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -1,6 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
-***/
 
 #include "nm-sd-adapt.h"
 
diff --git a/src/systemd/src/shared/dns-domain.c b/src/systemd/src/shared/dns-domain.c
index ab034043..048075f2 100644
--- a/src/systemd/src/shared/dns-domain.c
+++ b/src/systemd/src/shared/dns-domain.c
@@ -1,6 +1,4 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
-/***
- ***/
 
 #include "nm-sd-adapt.h"
 
@@ -514,7 +512,7 @@ int dns_name_compare_func(const void *a, const void *b) {
                 r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
                 q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
                 if (r < 0 || q < 0)
-                        return r - q;
+                        return CMP(r, q);
 
                 r = ascii_strcasecmp_nn(la, r, lb, q);
                 if (r != 0)
diff --git a/src/systemd/src/shared/dns-domain.h b/src/systemd/src/shared/dns-domain.h
index 8781eec6..95f4069c 100644
--- a/src/systemd/src/shared/dns-domain.h
+++ b/src/systemd/src/shared/dns-domain.h
@@ -1,9 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-/***
- ***/
-
 #include <errno.h>
 #include <stdbool.h>
 #include <stddef.h>
diff --git a/src/systemd/src/systemd/_sd-common.h b/src/systemd/src/systemd/_sd-common.h
index 7b54d179..4742b2e7 100644
--- a/src/systemd/src/systemd/_sd-common.h
+++ b/src/systemd/src/systemd/_sd-common.h
@@ -3,7 +3,6 @@
 #define foosdcommonhfoo
 
 /***
-
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1 of the License, or
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index 931b0e89..e3885520 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -131,11 +131,18 @@ int sd_dhcp_client_set_iaid_duid(
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len);
+int sd_dhcp_client_set_iaid_duid_llt(
+                sd_dhcp_client *client,
+                uint32_t iaid,
+                uint64_t llt_time);
 int sd_dhcp_client_set_duid(
                 sd_dhcp_client *client,
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len);
+int sd_dhcp_client_set_duid_llt(
+                sd_dhcp_client *client,
+                uint64_t llt_time);
 int sd_dhcp_client_get_client_id(
                 sd_dhcp_client *client,
                 uint8_t *type,
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index 16c05661..2a60145f 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -4,8 +4,6 @@
 
 /***
   Copyright © 2013 Intel Corporation. All rights reserved.
-  Copyright © 2014 Tom Gundersen
-
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1 of the License, or
diff --git a/src/systemd/src/systemd/sd-dhcp6-client.h b/src/systemd/src/systemd/sd-dhcp6-client.h
index 4f3b2d9e..fa36dca9 100644
--- a/src/systemd/src/systemd/sd-dhcp6-client.h
+++ b/src/systemd/src/systemd/sd-dhcp6-client.h
@@ -102,6 +102,9 @@ int sd_dhcp6_client_set_duid(
                 uint16_t duid_type,
                 const void *duid,
                 size_t duid_len);
+int sd_dhcp6_client_set_duid_llt(
+                sd_dhcp6_client *client,
+                uint64_t llt_time);
 int sd_dhcp6_client_set_iaid(
                 sd_dhcp6_client *client,
                 uint32_t iaid);
diff --git a/src/systemd/src/systemd/sd-dhcp6-lease.h b/src/systemd/src/systemd/sd-dhcp6-lease.h
index a673de5e..33a32a6d 100644
--- a/src/systemd/src/systemd/sd-dhcp6-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp6-lease.h
@@ -3,7 +3,6 @@
 #define foosddhcp6leasehfoo
 
 /***
-  Copyright © 2014 Tom Gundersen
   Copyright © 2014-2015 Intel Corporation. All rights reserved.
 
   systemd is free software; you can redistribute it and/or modify it
diff --git a/src/systemd/src/systemd/sd-event.h b/src/systemd/src/systemd/sd-event.h
index 7fcae4ac..eb35b834 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/systemd/src/systemd/sd-event.h
@@ -3,7 +3,6 @@
 #define foosdeventhfoo
 
 /***
-
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1 of the License, or
diff --git a/src/systemd/src/systemd/sd-id128.h b/src/systemd/src/systemd/sd-id128.h
index b24fd06f..143a0ffb 100644
--- a/src/systemd/src/systemd/sd-id128.h
+++ b/src/systemd/src/systemd/sd-id128.h
@@ -3,7 +3,6 @@
 #define foosdid128hfoo
 
 /***
-
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1 of the License, or
diff --git a/src/systemd/src/systemd/sd-ipv4acd.h b/src/systemd/src/systemd/sd-ipv4acd.h
index 259db263..039ed3c7 100644
--- a/src/systemd/src/systemd/sd-ipv4acd.h
+++ b/src/systemd/src/systemd/sd-ipv4acd.h
@@ -4,8 +4,6 @@
 
 /***
   Copyright © 2014 Axis Communications AB. All rights reserved.
-  Copyright © 2015 Tom Gundersen
-
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1 of the License, or
diff --git a/src/systemd/src/systemd/sd-lldp.h b/src/systemd/src/systemd/sd-lldp.h
index 11a2119a..d650794c 100644
--- a/src/systemd/src/systemd/sd-lldp.h
+++ b/src/systemd/src/systemd/sd-lldp.h
@@ -3,9 +3,6 @@
 #define foosdlldphfoo
 
 /***
-  Copyright © 2014 Tom Gundersen
-  Copyright © 2014 Susant Sahani
-
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1 of the License, or
diff --git a/src/tests/config/meson.build b/src/tests/config/meson.build
index f542c453..fd6c89b7 100644
--- a/src/tests/config/meson.build
+++ b/src/tests/config/meson.build
@@ -11,7 +11,6 @@ exe = executable(
   test_unit,
   sources,
   dependencies: test_nm_dep,
-  c_args: nm_build_cflags,
 )
 
 test(
diff --git a/src/tests/test-general-with-expect.c b/src/tests/test-general-with-expect.c
index a78b5311..ba8e3ce4 100644
--- a/src/tests/test-general-with-expect.c
+++ b/src/tests/test-general-with-expect.c
@@ -32,10 +32,6 @@
 
 #include "nm-test-utils-core.h"
 
-#ifndef CLOCK_BOOTTIME
-#define CLOCK_BOOTTIME 7
-#endif
-
 /*****************************************************************************/
 
 static void
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index 4db05ac8..1e7329d1 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -907,6 +907,79 @@ test_connection_match_ip6_routes (void)
 	g_assert (matched == copy);
 }
 
+#define do_test_wildcard_match(str, result, ...) \
+	g_assert (nm_wildcard_match_check (str, \
+	                                  (const char *const[]) { __VA_ARGS__ }, \
+	                                  NM_NARG (__VA_ARGS__)) \
+	          == result);
+
+static void
+test_wildcard_match (void)
+{
+	do_test_wildcard_match ("foobar", TRUE);
+
+	do_test_wildcard_match ("foo",    TRUE,  "foo", "bar", "baz");
+	do_test_wildcard_match ("bar",    TRUE,  "foo", "bar", "baz");
+	do_test_wildcard_match ("baz",    TRUE,  "foo", "bar", "baz");
+	do_test_wildcard_match ("aaa",    FALSE, "foo", "bar", "baz");
+	do_test_wildcard_match ("",       FALSE, "foo", "bar", "baz");
+
+	do_test_wildcard_match ("ens1",   TRUE,  "ens1*");
+	do_test_wildcard_match ("ens10",  TRUE,  "ens1*");
+	do_test_wildcard_match ("ens11",  TRUE,  "ens1*");
+	do_test_wildcard_match ("ens12",  TRUE,  "ens1*");
+	do_test_wildcard_match ("eth0",   FALSE, "ens1*");
+	do_test_wildcard_match ("ens",    FALSE, "ens1*");
+
+	do_test_wildcard_match ("ens1*",  TRUE,  "ens1\\*");
+	do_test_wildcard_match ("ens1" ,  FALSE, "ens1\\*");
+	do_test_wildcard_match ("ens10",  FALSE, "ens1\\*");
+
+	do_test_wildcard_match ("abcd",   TRUE,   "ab??");
+	do_test_wildcard_match ("ab",     FALSE,  "ab??");
+
+	do_test_wildcard_match ("ab??",   TRUE,  "ab\\?\\?");
+	do_test_wildcard_match ("abcd",   FALSE, "ab\\?\\?");
+
+	do_test_wildcard_match ("ens10",  TRUE,  "ens1*", "!ens11");
+	do_test_wildcard_match ("ens11",  FALSE, "ens1*", "!ens11");
+	do_test_wildcard_match ("ens12",  TRUE,  "ens1*", "!ens11");
+
+	do_test_wildcard_match ("a",      FALSE, "!a", "!b");
+	do_test_wildcard_match ("b",      FALSE, "!a", "!b");
+	do_test_wildcard_match ("c",      TRUE,  "!a", "!b");
+	do_test_wildcard_match ("!a",     TRUE,  "!a", "!b");
+
+	do_test_wildcard_match ("!net",   TRUE,  "\\!net");
+	do_test_wildcard_match ("net",    FALSE, "\\!net");
+	do_test_wildcard_match ("ens10",  FALSE, "\\!net");
+	do_test_wildcard_match ("\\!net", FALSE, "\\!net");
+
+	do_test_wildcard_match ("eth0",   FALSE, "*eth?", "!veth*", "!*0");
+	do_test_wildcard_match ("eth1",   TRUE,  "*eth?", "!veth*", "!*0");
+	do_test_wildcard_match ("myeth0", FALSE, "*eth?", "!veth*", "!*0");
+	do_test_wildcard_match ("myeth2", TRUE,  "*eth?", "!veth*", "!*0");
+	do_test_wildcard_match ("veth0",  FALSE, "*eth?", "!veth*", "!*0");
+	do_test_wildcard_match ("veth1",  FALSE, "*eth?", "!veth*", "!*0");
+	do_test_wildcard_match ("dummy1", FALSE, "*eth?", "!veth*", "!*0");
+
+	do_test_wildcard_match ("a",      TRUE,  "!!a");
+	do_test_wildcard_match ("b",      TRUE,  "!!a");
+	do_test_wildcard_match ("!a",     FALSE, "!!a");
+
+	do_test_wildcard_match ("\\",     TRUE,  "\\\\");
+	do_test_wildcard_match ("\\\\",   FALSE, "\\\\");
+	do_test_wildcard_match ("",       FALSE, "\\\\");
+
+	do_test_wildcard_match ("name",   FALSE, "name[123]");
+	do_test_wildcard_match ("name1",  TRUE,  "name[123]");
+	do_test_wildcard_match ("name2",  TRUE,  "name[123]");
+	do_test_wildcard_match ("name3",  TRUE,  "name[123]");
+	do_test_wildcard_match ("name4",  FALSE, "name[123]");
+
+	do_test_wildcard_match ("[a]",    TRUE,  "\\[a\\]");
+}
+
 static NMConnection *
 _create_connection_autoconnect (const char *id, gboolean autoconnect, int autoconnect_priority)
 {
@@ -1205,7 +1278,7 @@ test_match_spec_device (void)
 /*****************************************************************************/
 
 static void
-_do_test_match_spec_config (const char *file, gint line, const char *spec_str, guint version, guint v_maj, guint v_min, guint v_mic, NMMatchSpecMatchType expected)
+_do_test_match_spec_config (const char *file, int line, const char *spec_str, guint version, guint v_maj, guint v_min, guint v_mic, NMMatchSpecMatchType expected)
 {
 	GSList *specs;
 	NMMatchSpecMatchType match_result;
@@ -1372,7 +1445,7 @@ test_nm_utils_strbuf_append (void)
 			t_buf = buf;
 			t_len = buf_len;
 
-			test_mode = nmtst_get_rand_int () % 4;
+			test_mode = nmtst_get_rand_int () % 5;
 
 			switch (test_mode) {
 			case 0:
@@ -1393,6 +1466,45 @@ test_nm_utils_strbuf_append (void)
 			case 3:
 				nm_utils_strbuf_append (&t_buf, &t_len, "%s", str);
 				break;
+			case 4:
+				g_snprintf (t_buf, t_len, "%s", str);
+				if (   t_len > 0
+				    && strlen (str) >= buf_len
+				    && (nmtst_get_rand_int () % 2)) {
+					/* the string was truncated by g_snprintf(). That means, at the last position in the
+					 * buffer is now NUL.
+					 * Replace the NUL by the actual character, and check that nm_utils_strbuf_seek_end()
+					 * does the right thing: NUL terminate the buffer and seek past the end of the buffer. */
+					g_assert_cmpmem (t_buf, t_len - 1, str, t_len - 1);
+					g_assert (t_buf[t_len - 1] == '\0');
+					g_assert (str[t_len - 1] != '\0');
+					t_buf[t_len - 1] = str[t_len - 1];
+					nm_utils_strbuf_seek_end (&t_buf, &t_len);
+					g_assert (t_len == 0);
+					g_assert (t_buf == &buf[buf_len]);
+					g_assert (t_buf[-1] == '\0');
+				} else {
+					nm_utils_strbuf_seek_end (&t_buf, &t_len);
+					if (   buf_len > 0
+					    && strlen (str) + 1 > buf_len) {
+						/* the buffer was truncated by g_snprintf() above.
+						 *
+						 * But nm_utils_strbuf_seek_end() does not recognize that and returns
+						 * a remaining length of 1.
+						 *
+						 * Note that other nm_utils_strbuf_append*() functions recognize
+						 * truncation, and properly set the remaining length to zero.
+						 * As the assertions below check for the behavior of nm_utils_strbuf_append*(),
+						 * we assert here that nm_utils_strbuf_seek_end() behaved as expected, and then
+						 * adjust t_buf/t_len according to the "is-truncated" case. */
+						g_assert (t_len == 1);
+						g_assert (t_buf == &buf[buf_len - 1]);
+						g_assert (t_buf[0] == '\0');
+						t_len = 0;
+						t_buf++;
+					}
+				}
+				break;
 			}
 
 			/* Assert that the source-buffer is unmodified. */
@@ -1765,6 +1877,8 @@ main (int argc, char **argv)
 	g_test_add_func ("/general/connection-match/routes/ip4/2", test_connection_match_ip4_routes2);
 	g_test_add_func ("/general/connection-match/routes/ip6", test_connection_match_ip6_routes);
 
+	g_test_add_func ("/general/wildcard-match", test_wildcard_match);
+
 	g_test_add_func ("/general/connection-sort/autoconnect-priority", test_connection_sort_autoconnect_priority);
 
 	g_test_add_func ("/general/match-spec/device", test_match_spec_device);
diff --git a/src/tests/test-wired-defname.c b/src/tests/test-wired-defname.c
index a966ed21..97a6c319 100644
--- a/src/tests/test-wired-defname.c
+++ b/src/tests/test-wired-defname.c
@@ -44,15 +44,14 @@ _new_connection (const char *id)
 static char *
 _get_default_wired_name (GSList *list)
 {
-	gs_free NMConnection **v = NULL;
-	guint l, i;
-
-	l = g_slist_length (list);
-	v = g_new0 (NMConnection *, l + 1);
-	for (i = 0; list; list = list->next, i++)
-		v[i] = NM_CONNECTION (list->data);
-	g_assert (i == l);
-	return nm_device_ethernet_utils_get_default_wired_name (v);
+	gs_unref_hashtable GHashTable *existing_ids = NULL;
+
+	if (list) {
+		existing_ids = g_hash_table_new (nm_str_hash, g_str_equal);
+		for (; list; list = list->next)
+			g_hash_table_add (existing_ids, (char *) nm_connection_get_id (list->data));
+	}
+	return nm_device_ethernet_utils_get_default_wired_name (existing_ids);
 }
 
 /*****************************************************************************/
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index ad752c4c..d1aaa7c9 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -193,7 +193,7 @@ static void _set_vpn_state (NMVpnConnection *self,
 #define __NMLOG_prefix_buf_len 128
 
 static const char *
-__LOG_create_prefix (char *buf, NMVpnConnection *self, NMConnection *con)
+__LOG_create_prefix (char *buf, NMVpnConnection *self, NMSettingsConnection *con)
 {
 	NMVpnConnectionPrivate *priv;
 	const char *id;
@@ -202,7 +202,7 @@ __LOG_create_prefix (char *buf, NMVpnConnection *self, NMConnection *con)
 		return _NMLOG_PREFIX_NAME;
 
 	priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
-	id = con ? nm_connection_get_id (con) : NULL;
+	id = con ? nm_settings_connection_get_id (con) : NULL;
 
 	g_snprintf (buf, __NMLOG_prefix_buf_len,
 	            "%s["
@@ -214,7 +214,7 @@ __LOG_create_prefix (char *buf, NMVpnConnection *self, NMConnection *con)
 	            "]",
 	            _NMLOG_PREFIX_NAME,
 	            self,
-	            con ? "," : "--", con ? (nm_connection_get_uuid (con) ?: "??") : "",
+	            con ? "," : "--", con ? (nm_settings_connection_get_uuid (con) ?: "??") : "",
 	            con ? "," : "", NM_PRINT_FMT_QUOTED (id, "\"", id, "\"", con ? "??" : ""),
 	            priv->ip_ifindex,
 	            NM_PRINT_FMT_QUOTED (priv->ip_iface, ":(", priv->ip_iface, ")", "")
@@ -225,17 +225,17 @@ __LOG_create_prefix (char *buf, NMVpnConnection *self, NMConnection *con)
 
 #define _NMLOG(level, ...) \
     G_STMT_START { \
-        const NMLogLevel __level = (level); \
-        NMConnection *__con = (self) ? (NMConnection *) _get_settings_connection (self, TRUE) : NULL; \
+        const NMLogLevel _level = (level); \
+        NMSettingsConnection *_con = (self) ? _get_settings_connection (self, TRUE) : NULL; \
         \
-        if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \
+        if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \
             char __prefix[__NMLOG_prefix_buf_len]; \
             \
-            _nm_log (__level, _NMLOG_DOMAIN, 0, \
+            _nm_log (_level, _NMLOG_DOMAIN, 0, \
                      (self) ? NM_VPN_CONNECTION_GET_PRIVATE (self)->ip_iface : NULL, \
-                     (__con) ? nm_connection_get_uuid (__con) : NULL, \
+                     (_con) ? nm_settings_connection_get_uuid (_con) : NULL, \
                      "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-                     __LOG_create_prefix (__prefix, (self), __con) \
+                     __LOG_create_prefix (__prefix, (self), _con) \
                      _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
         } \
     } G_STMT_END
@@ -1619,6 +1619,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 	nm_ip4_config_merge_setting (config,
 	                             s_ip,
 	                             nm_setting_connection_get_mdns (s_con),
+	                             nm_setting_connection_get_llmnr (s_con),
 	                             route_table,
 	                             route_metric);
 
@@ -1876,13 +1877,10 @@ connect_success (NMVpnConnection *self)
 	 * It is a configured value or 60 seconds */
 	timeout = nm_setting_vpn_get_timeout (s_vpn);
 	if (timeout == 0) {
-		char *value;
-
-		value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-		                                              "vpn.timeout", NULL);
-		timeout = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, 60);
-		timeout = timeout == 0 ? 60 : timeout;
-		g_free (value);
+		timeout = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+		                                                       "vpn.timeout",
+		                                                       NULL,
+		                                                       1, G_MAXUINT32, 60);
 	}
 	priv->connect_timeout = g_timeout_add_seconds (timeout, connect_timeout_cb, self);
 
diff --git a/src/vpn/nm-vpn-connection.h b/src/vpn/nm-vpn-connection.h
index 3046634d..e409cc31 100644
--- a/src/vpn/nm-vpn-connection.h
+++ b/src/vpn/nm-vpn-connection.h
@@ -59,7 +59,7 @@ void                 nm_vpn_connection_activate        (NMVpnConnection *self,
                                                         NMVpnPluginInfo *plugin_info);
 NMVpnConnectionState nm_vpn_connection_get_vpn_state   (NMVpnConnection *self);
 const char *         nm_vpn_connection_get_banner      (NMVpnConnection *self);
-const gchar *        nm_vpn_connection_get_service     (NMVpnConnection *self);
+const char *        nm_vpn_connection_get_service     (NMVpnConnection *self);
 
 gboolean             nm_vpn_connection_deactivate      (NMVpnConnection *self,
                                                         NMActiveConnectionStateReason reason,