about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
committerMichael Biebl <biebl@debian.org>2017-11-07 00:14:39 +0100
commit90e8691111889a7b5f3c812f5a41f15a8a058913 (patch)
treef101a879eca27c34a9bfa5f3da52266b22539a36 /src
parentbdb6eeb0670658255c2a4c3c501c0a27fa8cfe55 (diff)
New upstream version 1.9.90 upstream/1.9.90
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c133
-rw-r--r--src/devices/adsl/nm-device-adsl.c17
-rw-r--r--src/devices/bluetooth/nm-bluez-common.h26
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c58
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h3
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c37
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c131
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.h6
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c154
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c4
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c299
-rw-r--r--src/devices/bluetooth/nm-device-bt.c96
-rw-r--r--src/devices/bluetooth/nm-device-bt.h6
-rw-r--r--src/devices/nm-device-bond.c25
-rw-r--r--src/devices/nm-device-bridge.c123
-rw-r--r--src/devices/nm-device-bridge.h2
-rw-r--r--src/devices/nm-device-dummy.c2
-rw-r--r--src/devices/nm-device-ethernet.c129
-rw-r--r--src/devices/nm-device-factory.c149
-rw-r--r--src/devices/nm-device-factory.h28
-rw-r--r--src/devices/nm-device-infiniband.c10
-rw-r--r--src/devices/nm-device-ip-tunnel.c60
-rw-r--r--src/devices/nm-device-macsec.c39
-rw-r--r--src/devices/nm-device-macvlan.c73
-rw-r--r--src/devices/nm-device-ppp.c350
-rw-r--r--src/devices/nm-device-ppp.h30
-rw-r--r--src/devices/nm-device-private.h14
-rw-r--r--src/devices/nm-device-tun.c2
-rw-r--r--src/devices/nm-device-veth.c6
-rw-r--r--src/devices/nm-device-vlan.c105
-rw-r--r--src/devices/nm-device-vxlan.c42
-rw-r--r--src/devices/nm-device.c3107
-rw-r--r--src/devices/nm-device.h54
-rw-r--r--src/devices/nm-lldp-listener.c16
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.c156
-rw-r--r--src/devices/ovs/nm-device-ovs-bridge.h35
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c191
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.h35
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c201
-rw-r--r--src/devices/ovs/nm-device-ovs-port.h35
-rw-r--r--src/devices/ovs/nm-ovs-factory.c195
-rw-r--r--src/devices/ovs/nm-ovsdb.c1607
-rw-r--r--src/devices/ovs/nm-ovsdb.h50
-rw-r--r--src/devices/team/nm-device-team.c65
-rw-r--r--src/devices/tests/test-arping.c7
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c23
-rw-r--r--src/devices/wifi/nm-device-wifi.c516
-rw-r--r--src/devices/wifi/nm-device-wifi.h2
-rw-r--r--src/devices/wifi/nm-wifi-ap.c368
-rw-r--r--src/devices/wifi/nm-wifi-ap.h3
-rw-r--r--src/devices/wifi/nm-wifi-utils.c4
-rw-r--r--src/devices/wifi/tests/test-general.c32
-rw-r--r--src/devices/wwan/libnm-wwan.ver4
-rw-r--r--src/devices/wwan/nm-device-modem.c50
-rw-r--r--src/devices/wwan/nm-modem-broadband.c103
-rw-r--r--src/devices/wwan/nm-modem-manager.c813
-rw-r--r--src/devices/wwan/nm-modem-manager.h13
-rw-r--r--src/devices/wwan/nm-modem-ofono.c578
-rw-r--r--src/devices/wwan/nm-modem.c300
-rw-r--r--src/devices/wwan/nm-modem.h21
-rw-r--r--src/devices/wwan/nm-wwan-factory.c4
-rw-r--r--src/dhcp/nm-dhcp-client-logging.h21
-rw-r--r--src/dhcp/nm-dhcp-client.c166
-rw-r--r--src/dhcp/nm-dhcp-client.h29
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c60
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h9
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c104
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c272
-rw-r--r--src/dhcp/nm-dhcp-listener.c67
-rw-r--r--src/dhcp/nm-dhcp-manager.c55
-rw-r--r--src/dhcp/nm-dhcp-manager.h16
-rw-r--r--src/dhcp/nm-dhcp-systemd.c271
-rw-r--r--src/dhcp/nm-dhcp-utils.c88
-rw-r--r--src/dhcp/nm-dhcp-utils.h10
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c84
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c140
-rw-r--r--src/dns/nm-dns-dnsmasq.c32
-rw-r--r--src/dns/nm-dns-manager.c88
-rw-r--r--src/dns/nm-dns-systemd-resolved.c123
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c4
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.c83
-rw-r--r--src/dnsmasq/tests/test-dnsmasq-utils.c184
-rw-r--r--src/main.c19
-rw-r--r--src/ndisc/nm-fake-ndisc.c8
-rw-r--r--src/ndisc/nm-fake-ndisc.h4
-rw-r--r--src/ndisc/nm-lndp-ndisc.c47
-rw-r--r--src/ndisc/nm-ndisc-private.h2
-rw-r--r--src/ndisc/nm-ndisc.c358
-rw-r--r--src/ndisc/nm-ndisc.h26
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c105
-rw-r--r--src/nm-active-connection.c119
-rw-r--r--src/nm-active-connection.h14
-rw-r--r--src/nm-auth-utils.c3
-rw-r--r--src/nm-bus-manager.c16
-rw-r--r--src/nm-checkpoint-manager.c2
-rw-r--r--src/nm-checkpoint.c6
-rw-r--r--src/nm-config-data.c83
-rw-r--r--src/nm-config-data.h4
-rw-r--r--src/nm-config.c41
-rw-r--r--src/nm-config.h3
-rw-r--r--src/nm-connectivity.c19
-rw-r--r--src/nm-core-utils.c604
-rw-r--r--src/nm-core-utils.h239
-rw-r--r--src/nm-default-route-manager.c1611
-rw-r--r--src/nm-default-route-manager.h67
-rw-r--r--src/nm-dispatcher.c45
-rw-r--r--src/nm-exported-object.c4
-rw-r--r--src/nm-firewall-manager.c39
-rw-r--r--src/nm-hostname-manager.c664
-rw-r--r--src/nm-hostname-manager.h63
-rw-r--r--src/nm-iface-helper.c178
-rw-r--r--src/nm-ip4-config.c2364
-rw-r--r--src/nm-ip4-config.h299
-rw-r--r--src/nm-ip6-config.c2099
-rw-r--r--src/nm-ip6-config.h233
-rw-r--r--src/nm-logging.h12
-rw-r--r--src/nm-manager.c373
-rw-r--r--src/nm-manager.h10
-rw-r--r--src/nm-multi-index.c473
-rw-r--r--src/nm-multi-index.h105
-rw-r--r--src/nm-netns.c22
-rw-r--r--src/nm-netns.h4
-rw-r--r--src/nm-pacrunner-manager.c202
-rw-r--r--src/nm-policy.c714
-rw-r--r--src/nm-route-manager.c1321
-rw-r--r--src/nm-route-manager.h49
-rw-r--r--src/nm-session-monitor.c6
-rw-r--r--src/nm-test-utils-core.h76
-rw-r--r--src/nm-types.h30
-rw-r--r--src/platform/nm-fake-platform.c1437
-rw-r--r--src/platform/nm-linux-platform.c2379
-rw-r--r--src/platform/nm-linux-platform.h6
-rw-r--r--src/platform/nm-platform-private.h42
-rw-r--r--src/platform/nm-platform.c2926
-rw-r--r--src/platform/nm-platform.h469
-rw-r--r--src/platform/nmp-netns.c16
-rw-r--r--src/platform/nmp-netns.h3
-rw-r--r--src/platform/nmp-object.c2461
-rw-r--r--src/platform/nmp-object.h559
-rw-r--r--src/platform/tests/monitor.c2
-rw-r--r--src/platform/tests/test-address.c87
-rw-r--r--src/platform/tests/test-cleanup.c32
-rw-r--r--src/platform/tests/test-common.c445
-rw-r--r--src/platform/tests/test-common.h174
-rw-r--r--src/platform/tests/test-general.c2
-rw-r--r--src/platform/tests/test-link.c31
-rw-r--r--src/platform/tests/test-nmp-object.c494
-rw-r--r--src/platform/tests/test-route.c541
-rw-r--r--src/platform/wifi/wifi-utils-nl80211.c38
-rw-r--r--src/platform/wifi/wifi-utils-wext.c5
-rw-r--r--src/ppp/nm-ppp-manager-call.c16
-rw-r--r--src/ppp/nm-ppp-manager-call.h7
-rw-r--r--src/ppp/nm-ppp-manager.c134
-rw-r--r--src/ppp/nm-ppp-manager.h2
-rw-r--r--src/ppp/nm-ppp-plugin-api.h6
-rw-r--r--src/settings/nm-agent-manager.c190
-rw-r--r--src/settings/nm-inotify-helper.c3
-rw-r--r--src/settings/nm-secret-agent.c43
-rw-r--r--src/settings/nm-settings-connection.c606
-rw-r--r--src/settings/nm-settings-connection.h81
-rw-r--r--src/settings/nm-settings-plugin.c14
-rw-r--r--src/settings/nm-settings.c524
-rw-r--r--src/settings/nm-settings.h10
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c2
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf11
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c104
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c18
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c2282
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h13
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c25
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h26
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c1268
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h30
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c323
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h33
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main2
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw13
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c258
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-connection-parser.c2
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-connection.c76
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-net-parser.c6
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-plugin.c45
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c9
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c15
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c74
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c4
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c3
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile.c6
-rw-r--r--src/supplicant/nm-supplicant-config.c96
-rw-r--r--src/supplicant/nm-supplicant-config.h7
-rw-r--r--src/supplicant/nm-supplicant-interface.c388
-rw-r--r--src/supplicant/nm-supplicant-interface.h18
-rw-r--r--src/supplicant/nm-supplicant-manager.c18
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c5
-rw-r--r--src/supplicant/tests/test-supplicant-config.c585
-rw-r--r--src/systemd/sd-adapt/architecture.h (renamed from src/systemd/sd-adapt/process-util.h)0
-rw-r--r--src/systemd/sd-adapt/btrfs-util.h3
-rw-r--r--src/systemd/sd-adapt/ioprio.h3
-rw-r--r--src/systemd/sd-adapt/nm-sd-adapt.h33
-rw-r--r--src/systemd/sd-adapt/raw-clone.h3
-rw-r--r--src/systemd/sd-adapt/udev.h1
-rw-r--r--src/systemd/src/basic/alloc-util.c27
-rw-r--r--src/systemd/src/basic/alloc-util.h10
-rw-r--r--src/systemd/src/basic/escape.c42
-rw-r--r--src/systemd/src/basic/escape.h23
-rw-r--r--src/systemd/src/basic/extract-word.c9
-rw-r--r--src/systemd/src/basic/extract-word.h2
-rw-r--r--src/systemd/src/basic/fd-util.c3
-rw-r--r--src/systemd/src/basic/fileio.c207
-rw-r--r--src/systemd/src/basic/fileio.h22
-rw-r--r--src/systemd/src/basic/fs-util.c19
-rw-r--r--src/systemd/src/basic/fs-util.h1
-rw-r--r--src/systemd/src/basic/hashmap.c26
-rw-r--r--src/systemd/src/basic/hashmap.h4
-rw-r--r--src/systemd/src/basic/hexdecoct.c3
-rw-r--r--src/systemd/src/basic/hostname-util.c13
-rw-r--r--src/systemd/src/basic/in-addr-util.c150
-rw-r--r--src/systemd/src/basic/in-addr-util.h16
-rw-r--r--src/systemd/src/basic/io-util.h13
-rw-r--r--src/systemd/src/basic/log.h133
-rw-r--r--src/systemd/src/basic/macro.h1
-rw-r--r--src/systemd/src/basic/parse-util.c21
-rw-r--r--src/systemd/src/basic/parse-util.h1
-rw-r--r--src/systemd/src/basic/path-util.c29
-rw-r--r--src/systemd/src/basic/path-util.h16
-rw-r--r--src/systemd/src/basic/process-util.c1093
-rw-r--r--src/systemd/src/basic/process-util.h141
-rw-r--r--src/systemd/src/basic/random-util.c147
-rw-r--r--src/systemd/src/basic/random-util.h4
-rw-r--r--src/systemd/src/basic/set.h2
-rw-r--r--src/systemd/src/basic/siphash24.c202
-rw-r--r--src/systemd/src/basic/siphash24.h23
-rw-r--r--src/systemd/src/basic/socket-util.c31
-rw-r--r--src/systemd/src/basic/socket-util.h20
-rw-r--r--src/systemd/src/basic/string-util.c31
-rw-r--r--src/systemd/src/basic/string-util.h13
-rw-r--r--src/systemd/src/basic/strv.c6
-rw-r--r--src/systemd/src/basic/time-util.c267
-rw-r--r--src/systemd/src/basic/time-util.h19
-rw-r--r--src/systemd/src/basic/unaligned.h129
-rw-r--r--src/systemd/src/basic/utf8.c2
-rw-r--r--src/systemd/src/basic/util.c152
-rw-r--r--src/systemd/src/basic/util.h3
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-lease-internal.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c16
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-packet.c4
-rw-r--r--src/systemd/src/libsystemd-network/lldp-neighbor.c7
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c39
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h7
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c105
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c151
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-lldp.c2
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c21
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c2
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c4
-rw-r--r--src/systemd/src/shared/dns-domain.c83
-rw-r--r--src/systemd/src/shared/dns-domain.h4
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h17
-rw-r--r--src/systemd/src/systemd/sd-dhcp-lease.h1
-rw-r--r--src/tests/config/test-config.c36
-rw-r--r--src/tests/test-general-with-expect.c358
-rw-r--r--src/tests/test-general.c28
-rw-r--r--src/tests/test-ip4-config.c114
-rw-r--r--src/tests/test-ip6-config.c151
-rw-r--r--src/tests/test-resolvconf-capture.c66
-rw-r--r--src/tests/test-route-manager.c933
-rw-r--r--src/tests/test-systemd.c2
-rw-r--r--src/vpn/nm-vpn-connection.c389
-rw-r--r--src/vpn/nm-vpn-manager.c2
275 files changed, 29690 insertions, 20972 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index ecb1d8ae..596c5bb3 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -175,37 +175,78 @@ nm_utils_get_ip_config_method (NMConnection *connection,
 	if (ip_setting_type == NM_TYPE_SETTING_IP4_CONFIG) {
 		g_return_val_if_fail (s_con != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
-		if (nm_setting_connection_get_master (s_con))
+		s_ip4 = nm_connection_get_setting_ip4_config (connection);
+		if (!s_ip4)
 			return NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-		else {
-			s_ip4 = nm_connection_get_setting_ip4_config (connection);
-			if (!s_ip4)
-				return NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-			method = nm_setting_ip_config_get_method (s_ip4);
-			g_return_val_if_fail (method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-
-			return method;
-		}
+		method = nm_setting_ip_config_get_method (s_ip4);
+		g_return_val_if_fail (method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+		return method;
 
 	} else if (ip_setting_type == NM_TYPE_SETTING_IP6_CONFIG) {
 		g_return_val_if_fail (s_con != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 
-		if (nm_setting_connection_get_master (s_con))
+		s_ip6 = nm_connection_get_setting_ip6_config (connection);
+		if (!s_ip6)
 			return NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-		else {
-			s_ip6 = nm_connection_get_setting_ip6_config (connection);
-			if (!s_ip6)
-				return NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
-			method = nm_setting_ip_config_get_method (s_ip6);
-			g_return_val_if_fail (method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
-
-			return method;
-		}
+		method = nm_setting_ip_config_get_method (s_ip6);
+		g_return_val_if_fail (method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+
+		return method;
 
 	} else
 		g_assert_not_reached ();
 }
 
+gboolean
+nm_utils_connection_has_default_route (NMConnection *connection,
+                                       int addr_family,
+                                       gboolean *out_is_never_default)
+{
+	const char *method;
+	NMSettingIPConfig *s_ip;
+	gboolean is_never_default = FALSE;
+	gboolean has_default_route = FALSE;
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), FALSE);
+
+	if (!connection)
+		goto out;
+
+	if (addr_family == AF_INET)
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+	else
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+	if (!s_ip)
+		goto out;
+	if (nm_setting_ip_config_get_never_default (s_ip)) {
+		is_never_default = TRUE;
+		goto out;
+	}
+
+	if (addr_family == AF_INET) {
+		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
+		if (NM_IN_STRSET (method, NULL,
+		                          NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
+		                          NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
+			goto out;
+	} else {
+		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
+		if (NM_IN_STRSET (method, NULL,
+		                          NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+		                          NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
+			goto out;
+	}
+
+	has_default_route = TRUE;
+out:
+	NM_SET_OUT (out_is_never_default, is_never_default);
+	return has_default_route;
+}
+
+/*****************************************************************************/
+
 void
 nm_utils_complete_generic (NMPlatform *platform,
                            NMConnection *connection,
@@ -250,7 +291,7 @@ nm_utils_complete_generic (NMPlatform *platform,
 	}
 
 	/* Normalize */
-	parameters = g_hash_table_new (g_str_hash, g_str_equal);
+	parameters = g_hash_table_new (nm_str_hash, g_str_equal);
 	g_hash_table_insert (parameters, NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD,
 	                     default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
 	nm_connection_normalize (connection, parameters, NULL, NULL);
@@ -392,12 +433,14 @@ check_ip_routes (NMConnection *orig,
                  gint64 default_metric,
                  gboolean v4)
 {
-	gs_free NMIPRoute **routes1 = NULL, **routes2 = NULL;
+	gs_free NMIPRoute **routes1 = NULL;
+	NMIPRoute **routes2;
 	NMSettingIPConfig *s_ip1, *s_ip2;
 	gint64 m;
 	const char *s_name;
 	GHashTable *props;
-	guint i, num;
+	guint i, i1, i2, num1, num2;
+	const guint8 PLEN = v4 ? 32 : 128;
 
 	s_name = v4 ? NM_SETTING_IP4_CONFIG_SETTING_NAME :
 	              NM_SETTING_IP6_CONFIG_SETTING_NAME;
@@ -414,27 +457,49 @@ check_ip_routes (NMConnection *orig,
 	if (!s_ip1 || !s_ip2)
 		return FALSE;
 
-	num = nm_setting_ip_config_get_num_routes (s_ip1);
-	if (num != nm_setting_ip_config_get_num_routes (s_ip2))
-		return FALSE;
+	num1 = nm_setting_ip_config_get_num_routes (s_ip1);
+	num2 = nm_setting_ip_config_get_num_routes (s_ip2);
 
-	routes1 = g_new (NMIPRoute *, num);
-	routes2 = g_new (NMIPRoute *, num);
+	routes1 = g_new (NMIPRoute *, (gsize) num1 + num2);
+	routes2 = &routes1[num1];
 
-	for (i = 0; i < num; i++) {
+	for (i = 0; i < num1; i++)
 		routes1[i] = nm_setting_ip_config_get_route (s_ip1, i);
+	for (i = 0; i < num2; i++)
 		routes2[i] = nm_setting_ip_config_get_route (s_ip2, i);
-	}
 
 	m = nm_setting_ip_config_get_route_metric (s_ip2);
 	if (m != -1)
 		default_metric = m;
 
-	g_qsort_with_data (routes1, num, sizeof (NMIPRoute *), route_ptr_compare, &default_metric);
-	g_qsort_with_data (routes2, num, sizeof (NMIPRoute *), route_ptr_compare, &default_metric);
+	g_qsort_with_data (routes1, num1, sizeof (NMIPRoute *), route_ptr_compare, &default_metric);
+	g_qsort_with_data (routes2, num2, sizeof (NMIPRoute *), route_ptr_compare, &default_metric);
+
+	for (i1 = 0, i2 = 0; i2 < num2; i1++) {
+		if (i1 >= num1)
+			return FALSE;
+		if (route_compare (routes1[i1], routes2[i2], default_metric) == 0) {
+			i2++;
+			continue;
+		}
+
+		/* if @orig (@routes1) contains /32 routes that are missing in @candidate,
+		 * we accept that.
+		 *
+		 * A /32 may have been added automatically, as a direct-route to the gateway.
+		 * The generated connection (@orig) would contain that route, so we shall ignore
+		 * it.
+		 *
+		 * Likeweise for /128 for IPv6. */
+		if (nm_ip_route_get_prefix (routes1[i1]) == PLEN)
+			continue;
+
+		return FALSE;
+	}
 
-	for (i = 0; i < num; i++) {
-		if (route_compare (routes1[i], routes2[i], default_metric))
+	/* check that @orig has no left-over (except host routes that we ignore). */
+	for (; i1 < num1; i1++) {
+		if (nm_ip_route_get_prefix (routes1[i1]) != PLEN)
 			return FALSE;
 	}
 
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index fe622bdf..e9bd41ae 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -227,7 +227,7 @@ br2684_assign_vcc (NMDeviceAdsl *self, NMSettingAdsl *s_adsl)
 	return TRUE;
 
 error:
-	close (priv->brfd);
+	nm_close (priv->brfd);
 	priv->brfd = -1;
 	return FALSE;
 }
@@ -474,6 +474,15 @@ act_stage3_ip4_config_start (NMDevice *device,
 	}
 
 	priv->ppp_manager = nm_ppp_manager_create (ppp_iface, &err);
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     nm_device_get_route_table (device, AF_INET, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET),
+		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET6));
+	}
+
 	if (   !priv->ppp_manager
 	    || !nm_ppp_manager_start (priv->ppp_manager, req,
 	                              nm_setting_adsl_get_username (s_adsl),
@@ -510,10 +519,8 @@ adsl_cleanup (NMDeviceAdsl *self)
 
 	g_signal_handlers_disconnect_by_func (nm_device_get_platform (NM_DEVICE (self)), G_CALLBACK (link_changed_cb), self);
 
-	if (priv->brfd >= 0) {
-		close (priv->brfd);
-		priv->brfd = -1;
-	}
+	nm_close (priv->brfd);
+	priv->brfd = -1;
 
 	nm_clear_g_source (&priv->nas_update_id);
 
diff --git a/src/devices/bluetooth/nm-bluez-common.h b/src/devices/bluetooth/nm-bluez-common.h
index 6e97c3f5..d72bea81 100644
--- a/src/devices/bluetooth/nm-bluez-common.h
+++ b/src/devices/bluetooth/nm-bluez-common.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 Red Hat, Inc.
+ * Copyright (C) 2017 Red Hat, Inc.
  */
 
 #ifndef __NETWORKMANAGER_BLUEZ_COMMON_H__
@@ -24,21 +24,23 @@
 #define BLUETOOTH_CONNECT_DUN "dun"
 #define BLUETOOTH_CONNECT_NAP "nap"
 
-#define BLUEZ_SERVICE           "org.bluez"
+#define NM_BLUEZ_SERVICE           "org.bluez"
 
-#define BLUEZ_MANAGER_PATH      "/"
-#define OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager"
+#define NM_BLUEZ_MANAGER_PATH      "/"
+#define NM_OBJECT_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager"
 
-#define BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1"
-#define BLUEZ5_DEVICE_INTERFACE  "org.bluez.Device1"
-#define BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1"
+#define NM_BLUEZ5_ADAPTER_INTERFACE "org.bluez.Adapter1"
+#define NM_BLUEZ5_DEVICE_INTERFACE  "org.bluez.Device1"
+#define NM_BLUEZ5_NETWORK_INTERFACE "org.bluez.Network1"
+#define NM_BLUEZ5_NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer1"
 
-#define BLUEZ4_MANAGER_INTERFACE "org.bluez.Manager"
-#define BLUEZ4_ADAPTER_INTERFACE "org.bluez.Adapter"
-#define BLUEZ4_DEVICE_INTERFACE  "org.bluez.Device"
-#define BLUEZ4_SERIAL_INTERFACE  "org.bluez.Serial"
-#define BLUEZ4_NETWORK_INTERFACE "org.bluez.Network"
+#define NM_BLUEZ4_MANAGER_INTERFACE "org.bluez.Manager"
+#define NM_BLUEZ4_ADAPTER_INTERFACE "org.bluez.Adapter"
+#define NM_BLUEZ4_DEVICE_INTERFACE  "org.bluez.Device"
+#define NM_BLUEZ4_SERIAL_INTERFACE  "org.bluez.Serial"
+#define NM_BLUEZ4_NETWORK_INTERFACE "org.bluez.Network"
 
 #define NM_BLUEZ_MANAGER_BDADDR_ADDED "bdaddr-added"
+#define NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED "network-server-added"
 
 #endif  /* NM_BLUEZ_COMMON_H */
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index 41ef74ca..182527d9 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -84,6 +84,7 @@ typedef struct {
 	char *name;
 	guint32 capabilities;
 	gboolean connected;
+	gboolean paired;
 
 	char *b4_iface;
 #if WITH_BLUEZ5_DUN
@@ -278,10 +279,11 @@ check_emit_usable (NMBluezDevice *self)
 	/* only expect the supported capabilities set. */
 	nm_assert ((priv->capabilities & ~(NM_BT_CAPABILITY_NAP | NM_BT_CAPABILITY_DUN)) == NM_BT_CAPABILITY_NONE );
 
-	new_usable = (priv->initialized && priv->capabilities && priv->name &&
-	              ((priv->bluez_version == 4) ||
-	               (priv->bluez_version == 5 && priv->adapter5 && priv->adapter_powered) ) &&
-	              priv->dbus_connection && priv->address && priv->adapter_address);
+	new_usable = (   priv->initialized && priv->capabilities
+	              && priv->name && priv->paired
+	              && (   (priv->bluez_version == 4)
+	                  || (priv->bluez_version == 5 && priv->adapter5 && priv->adapter_powered))
+	              && priv->dbus_connection && priv->address && priv->adapter_address);
 
 	if (!new_usable)
 		goto END;
@@ -343,6 +345,10 @@ connection_compatible (NMBluezDevice *self, NMConnection *connection)
 		return FALSE;
 
 	bt_type = nm_setting_bluetooth_get_connection_type (s_bt);
+
+	if (nm_streq (bt_type, NM_SETTING_BLUETOOTH_TYPE_NAP))
+		return FALSE;
+
 	if (   g_str_equal (bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN)
 	    && !(priv->capabilities & NM_BT_CAPABILITY_DUN))
 		return FALSE;
@@ -465,7 +471,7 @@ nm_bluez_device_disconnect (NMBluezDevice *self)
 			if (!priv->b4_iface)
 				goto out;
 			args = g_variant_new ("(s)", priv->b4_iface),
-			dbus_iface = BLUEZ4_SERIAL_INTERFACE;
+			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
 		} else if (priv->bluez_version == 5) {
 #if WITH_BLUEZ5_DUN
 			nm_bluez5_dun_cleanup (priv->b5_dun_context);
@@ -475,16 +481,16 @@ nm_bluez_device_disconnect (NMBluezDevice *self)
 		}
 	} else if (priv->connection_bt_type == NM_BT_CAPABILITY_NAP) {
 		if (priv->bluez_version == 4)
-			dbus_iface = BLUEZ4_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
 		else if (priv->bluez_version == 5)
-			dbus_iface = BLUEZ5_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
 		else
 			g_assert_not_reached ();
 	} else
 		g_assert_not_reached ();
 
 	g_dbus_connection_call (priv->dbus_connection,
-	                        BLUEZ_SERVICE,
+	                        NM_BLUEZ_SERVICE,
 	                        priv->path,
 	                        dbus_iface,
 	                        "Disconnect",
@@ -577,13 +583,13 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 	if (connection_bt_type == NM_BT_CAPABILITY_NAP) {
 		connect_type = BLUETOOTH_CONNECT_NAP;
 		if (priv->bluez_version == 4)
-			dbus_iface = BLUEZ4_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ4_NETWORK_INTERFACE;
 		else if (priv->bluez_version == 5)
-			dbus_iface = BLUEZ5_NETWORK_INTERFACE;
+			dbus_iface = NM_BLUEZ5_NETWORK_INTERFACE;
 	} else if (connection_bt_type == NM_BT_CAPABILITY_DUN) {
 		connect_type = BLUETOOTH_CONNECT_DUN;
 		if (priv->bluez_version == 4)
-			dbus_iface = BLUEZ4_SERIAL_INTERFACE;
+			dbus_iface = NM_BLUEZ4_SERIAL_INTERFACE;
 		else if (priv->bluez_version == 5) {
 #if WITH_BLUEZ5_DUN
 			if (priv->b5_dun_context == NULL)
@@ -602,7 +608,7 @@ nm_bluez_device_connect_async (NMBluezDevice *self,
 		g_assert_not_reached ();
 
 	g_dbus_connection_call (priv->dbus_connection,
-	                        BLUEZ_SERVICE,
+	                        NM_BLUEZ_SERVICE,
 	                        priv->path,
 	                        dbus_iface,
 	                        "Connect",
@@ -795,6 +801,17 @@ _take_variant_property_connected (NMBluezDevice *self, GVariant *v)
 		g_variant_unref (v);
 }
 
+static void
+_take_variant_property_paired (NMBluezDevice *self, GVariant *v)
+{
+	NMBluezDevicePrivate *priv = NM_BLUEZ_DEVICE_GET_PRIVATE (self);
+
+	if (VARIANT_IS_OF_TYPE_BOOLEAN (v))
+		priv->paired = g_variant_get_boolean (v);
+
+	if (v)
+		g_variant_unref (v);
+}
 
 static void
 adapter5_on_properties_changed (GDBusProxy *proxy,
@@ -864,6 +881,8 @@ _take_one_variant_property (NMBluezDevice *self, const char *property, GVariant
 			_take_variant_property_address (self, v);
 		else if (!g_strcmp0 (property, "Connected"))
 			_take_variant_property_connected (self, v);
+		else if (!g_strcmp0 (property, "Paired"))
+			_take_variant_property_paired (self, v);
 		else if (!g_strcmp0 (property, "Name"))
 			_take_variant_property_name (self, v);
 		else if (!g_strcmp0 (property, "UUIDs"))
@@ -963,6 +982,7 @@ query_properties (NMBluezDevice *self)
 		g_object_freeze_notify (G_OBJECT (self));
 		_take_variant_property_address   (self, g_dbus_proxy_get_cached_property (priv->proxy, "Address"));
 		_take_variant_property_connected (self, g_dbus_proxy_get_cached_property (priv->proxy, "Connected"));
+		_take_variant_property_paired    (self, g_dbus_proxy_get_cached_property (priv->proxy, "Paired"));
 		_take_variant_property_name      (self, g_dbus_proxy_get_cached_property (priv->proxy, "Name"));
 		_take_variant_property_uuids     (self, g_dbus_proxy_get_cached_property (priv->proxy, "UUIDs"));
 		g_object_thaw_notify (G_OBJECT (self));
@@ -972,9 +992,9 @@ query_properties (NMBluezDevice *self)
 			g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 			                          G_DBUS_PROXY_FLAGS_NONE,
 			                          NULL,
-			                          BLUEZ_SERVICE,
+			                          NM_BLUEZ_SERVICE,
 			                          g_variant_get_string (v, NULL),
-			                          BLUEZ5_ADAPTER_INTERFACE,
+			                          NM_BLUEZ5_ADAPTER_INTERFACE,
 			                          NULL,
 			                          (GAsyncReadyCallback) adapter5_on_acquired,
 			                          g_object_ref (self));
@@ -1134,17 +1154,17 @@ nm_bluez_device_new (const char *path,
 
 	switch (priv->bluez_version) {
 	case 4:
-		interface_name = BLUEZ4_DEVICE_INTERFACE;
+		interface_name = NM_BLUEZ4_DEVICE_INTERFACE;
 		break;
 	case 5:
-		interface_name = BLUEZ5_DEVICE_INTERFACE;
+		interface_name = NM_BLUEZ5_DEVICE_INTERFACE;
 		break;
 	}
 
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_NONE,
 	                          NULL,
-	                          BLUEZ_SERVICE,
+	                          NM_BLUEZ_SERVICE,
 	                          priv->path,
 	                          interface_name,
 	                          NULL,
@@ -1199,7 +1219,7 @@ 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, NULL);
+		nm_settings_connection_delete (NM_SETTINGS_CONNECTION (to_delete), NULL);
 		g_object_unref (to_delete);
 	}
 
@@ -1274,7 +1294,7 @@ nm_bluez_device_class_init (NMBluezDeviceClass *config_class)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[INITIALIZED] = g_signal_new ("initialized",
+	signals[INITIALIZED] = g_signal_new (NM_BLUEZ_DEVICE_INITIALIZED,
 	                                     G_OBJECT_CLASS_TYPE (object_class),
 	                                     G_SIGNAL_RUN_LAST,
 	                                     0,
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
index e56d5d24..f8a1872f 100644
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ b/src/devices/bluetooth/nm-bluez-device.h
@@ -39,6 +39,7 @@
 #define NM_BLUEZ_DEVICE_CONNECTED    "connected"
 
 /* Signals */
+#define NM_BLUEZ_DEVICE_INITIALIZED  "initialized"
 #define NM_BLUEZ_DEVICE_REMOVED      "removed"
 
 typedef struct _NMBluezDevice NMBluezDevice;
@@ -61,8 +62,6 @@ const char *nm_bluez_device_get_address (NMBluezDevice *self);
 
 const char *nm_bluez_device_get_name (NMBluezDevice *self);
 
-guint32 nm_bluez_device_get_class (NMBluezDevice *self);
-
 guint32 nm_bluez_device_get_capabilities (NMBluezDevice *self);
 
 gboolean nm_bluez_device_get_connected (NMBluezDevice *self);
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 2f0afa16..96e80245 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -26,6 +26,7 @@
 #include <gmodule.h>
 
 #include "devices/nm-device-factory.h"
+#include "devices/nm-device-bridge.h"
 #include "nm-setting-bluetooth.h"
 #include "settings/nm-settings.h"
 #include "nm-bluez4-manager.h"
@@ -146,7 +147,7 @@ cleanup_checking (NMBluezManager *self, gboolean do_unwatch_name)
 
 
 static void
-manager_bdaddr_added_cb (NMBluez4Manager *bluez_mgr,
+manager_bdaddr_added_cb (GObject *manager,
                          NMBluezDevice *bt_device,
                          const char *bdaddr,
                          const char *name,
@@ -180,6 +181,13 @@ manager_bdaddr_added_cb (NMBluez4Manager *bluez_mgr,
 }
 
 static void
+manager_network_server_added_cb (GObject *manager,
+                                 gpointer user_data)
+{
+	nm_device_factory_emit_component_added (NM_DEVICE_FACTORY (user_data), NULL);
+}
+
+static void
 setup_version_number (NMBluezManager *self, int bluez_version)
 {
 	NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE (self);
@@ -228,6 +236,10 @@ setup_bluez5 (NMBluezManager *self)
 	                  NM_BLUEZ_MANAGER_BDADDR_ADDED,
 	                  G_CALLBACK (manager_bdaddr_added_cb),
 	                  self);
+	g_signal_connect (manager,
+	                  NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
+	                  G_CALLBACK (manager_network_server_added_cb),
+	                  self);
 
 	nm_bluez5_manager_query_devices (manager);
 }
@@ -264,7 +276,7 @@ check_bluez_and_try_setup_final_step (NMBluezManager *self, int bluez_version, c
 		cleanup_checking (self, FALSE);
 		if (!priv->watch_name_id) {
 			priv->watch_name_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM,
-			                                        BLUEZ_SERVICE,
+			                                        NM_BLUEZ_SERVICE,
 			                                        G_BUS_NAME_WATCHER_FLAGS_NONE,
 			                                        watch_name_on_appeared,
 			                                        NULL,
@@ -317,7 +329,7 @@ check_bluez_and_try_setup_do_introspect (GObject *source_object,
 	/* might not be the best approach to detect the version, but it's good enough in practice. */
 	if (strstr (xml_data, "org.freedesktop.DBus.ObjectManager"))
 		bluez_version = 5;
-	else if (strstr (xml_data, BLUEZ4_MANAGER_INTERFACE))
+	else if (strstr (xml_data, NM_BLUEZ4_MANAGER_INTERFACE))
 		bluez_version = 4;
 	else
 		reason = "unexpected introspect result";
@@ -380,7 +392,7 @@ check_bluez_and_try_setup (NMBluezManager *self)
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 	                          NULL,
-	                          BLUEZ_SERVICE,
+	                          NM_BLUEZ_SERVICE,
 	                          "/",
 	                          DBUS_INTERFACE_INTROSPECTABLE,
 	                          priv->async_cancellable,
@@ -406,6 +418,20 @@ create_device (NMDeviceFactory *factory,
 	return NULL;
 }
 
+static gboolean
+match_connection (NMDeviceFactory *factory,
+                  NMConnection *connection)
+{
+	const char *type = nm_connection_get_connection_type (connection);
+
+	nm_assert (nm_streq (type, NM_SETTING_BLUETOOTH_SETTING_NAME));
+
+	if (_nm_connection_get_setting_bluetooth_for_nap (connection))
+		return FALSE;    /* handled by the bridge factory */
+
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -427,7 +453,7 @@ dispose (GObject *object)
 		g_clear_object (&priv->manager4);
 	}
 	if (priv->manager5) {
-		g_signal_handlers_disconnect_by_func (priv->manager5, manager_bdaddr_added_cb, self);
+		g_signal_handlers_disconnect_by_data (priv->manager5, self);
 		g_clear_object (&priv->manager5);
 	}
 
@@ -450,5 +476,6 @@ nm_bluez_manager_class_init (NMBluezManagerClass *klass)
 
 	factory_class->get_supported_types = get_supported_types;
 	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
 	factory_class->start = start;
 }
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index c0c1be30..0f19f998 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include "nm-dbus-interface.h"
+#include "nm-utils/nm-hash-utils.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
 #include "nm-core-internal.h"
@@ -49,6 +50,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 typedef struct {
 	char *path;
 	GDBusProxy *proxy;
+	GCancellable *proxy_cancellable;
 	gboolean initialized;
 
 	char *address;
@@ -73,6 +75,11 @@ G_DEFINE_TYPE (NMBluez4Adapter, nm_bluez4_adapter, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_BT
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-adapter", __VA_ARGS__)
+
+/*****************************************************************************/
+
 static void device_do_remove (NMBluez4Adapter *self, NMBluezDevice *device);
 
 /*****************************************************************************/
@@ -119,8 +126,8 @@ nm_bluez4_adapter_get_devices (NMBluez4Adapter *self)
 static void
 emit_device_removed (NMBluez4Adapter *self, NMBluezDevice *device)
 {
-	nm_log_dbg (LOGD_BT, "(%s): bluez device now unusable",
-	            nm_bluez_device_get_path (device));
+	_LOGD ("(%s): bluez device now unusable",
+	       nm_bluez_device_get_path (device));
 	g_signal_emit (self, signals[DEVICE_REMOVED], 0, device);
 }
 
@@ -130,9 +137,9 @@ device_usable (NMBluezDevice *device, GParamSpec *pspec, gpointer user_data)
 	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
 
 	if (nm_bluez_device_get_usable (device)) {
-		nm_log_dbg (LOGD_BT, "(%s): bluez device now usable (device address is %s)",
-		            nm_bluez_device_get_path (device),
-		            nm_bluez_device_get_address (device));
+		_LOGD ("(%s): bluez device now usable (device address is %s)",
+		       nm_bluez_device_get_path (device),
+		       nm_bluez_device_get_address (device));
 		g_signal_emit (self, signals[DEVICE_ADDED], 0, device);
 	} else
 		emit_device_removed (self, device);
@@ -143,9 +150,9 @@ device_initialized (NMBluezDevice *device, gboolean success, gpointer user_data)
 {
 	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device %s",
-	            nm_bluez_device_get_path (device),
-	            success ? "initialized" : "failed to initialize");
+	_LOGD ("(%s): bluez device %s",
+	       nm_bluez_device_get_path (device),
+	       success ? "initialized" : "failed to initialize");
 	if (!success)
 		device_do_remove (self, device);
 }
@@ -174,11 +181,11 @@ device_created (GDBusProxy *proxy, const char *path, gpointer user_data)
 	NMBluezDevice *device;
 
 	device = nm_bluez_device_new (path, priv->address, priv->settings, 4);
-	g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self);
+	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
+	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
 	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
 
-	nm_log_dbg (LOGD_BT, "(%s): new bluez device found", path);
+	_LOGD ("(%s): new bluez device found", path);
 }
 
 static void
@@ -188,7 +195,7 @@ device_removed (GDBusProxy *proxy, const char *path, gpointer user_data)
 	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device removed", path);
+	_LOGD ("(%s): bluez device removed", path);
 
 	device = g_hash_table_lookup (priv->devices, path);
 	if (device)
@@ -198,19 +205,28 @@ device_removed (GDBusProxy *proxy, const char *path, gpointer user_data)
 static void
 get_properties_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
 {
-	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (user_data);
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
-	GError *err = NULL;
+	NMBluez4Adapter *self;
+	NMBluez4AdapterPrivate *priv;
+	gs_free_error GError *error = NULL;
 	GVariant *ret, *properties;
 	char **devices;
 	int i;
 
 	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(a{sv})"), &err);
+	                                  G_VARIANT_TYPE ("(a{sv})"), &error);
+
+	if (   !ret
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_BLUEZ4_ADAPTER (user_data);
+	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->proxy_cancellable);
+
 	if (!ret) {
-		g_dbus_error_strip_remote_error (err);
-		nm_log_warn (LOGD_BT, "bluez error getting adapter properties: %s", err->message);
-		g_error_free (err);
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("bluez error getting adapter properties: %s", error->message);
 		goto done;
 	}
 
@@ -233,15 +249,43 @@ done:
 }
 
 static void
-query_properties (NMBluez4Adapter *self)
+_proxy_new_cb (GObject *source_object,
+               GAsyncResult *result,
+               gpointer user_data)
 {
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
+	NMBluez4Adapter *self;
+	NMBluez4AdapterPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
+		g_clear_object (&priv->proxy_cancellable);
+		g_signal_emit (self, signals[INITIALIZED], 0, priv->initialized);
+		return;
+	}
+
+	priv->proxy = proxy;
+
+	_nm_dbus_signal_connect (priv->proxy, "DeviceCreated", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (device_created), self);
+	_nm_dbus_signal_connect (priv->proxy, "DeviceRemoved", G_VARIANT_TYPE ("(o)"),
+	                         G_CALLBACK (device_removed), self);
 
 	g_dbus_proxy_call (priv->proxy, "GetProperties",
 	                   NULL,
 	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   NULL,
-	                   get_properties_cb, self);
+	                   priv->proxy_cancellable,
+	                   get_properties_cb,
+	                   self);
 }
 
 /*****************************************************************************/
@@ -297,7 +341,7 @@ nm_bluez4_adapter_init (NMBluez4Adapter *self)
 {
 	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 
-	priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal,
+	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                       NULL, NULL);
 }
 
@@ -316,19 +360,17 @@ nm_bluez4_adapter_new (const char *path, NMSettings *settings)
 
 	priv->settings = g_object_ref (settings);
 
-	priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-	                                             G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                                             NULL,
-	                                             BLUEZ_SERVICE,
-	                                             priv->path,
-	                                             BLUEZ4_ADAPTER_INTERFACE,
-	                                             NULL, NULL);
-	_nm_dbus_signal_connect (priv->proxy, "DeviceCreated", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_created), self);
-	_nm_dbus_signal_connect (priv->proxy, "DeviceRemoved", G_VARIANT_TYPE ("(o)"),
-	                         G_CALLBACK (device_removed), self);
+	priv->proxy_cancellable = g_cancellable_new ();
 
-	query_properties (self);
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+	                          NULL,
+	                          NM_BLUEZ_SERVICE,
+	                          priv->path,
+	                          NM_BLUEZ4_ADAPTER_INTERFACE,
+	                          priv->proxy_cancellable,
+	                          _proxy_new_cb,
+	                          self);
 	return self;
 }
 
@@ -339,21 +381,28 @@ dispose (GObject *object)
 	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
+	nm_clear_g_cancellable (&priv->proxy_cancellable);
+
 	while ((device = g_hash_table_find (priv->devices, _find_all, NULL)))
 		device_do_remove (self, device);
 
+	if (priv->proxy) {
+		g_signal_handlers_disconnect_by_data (priv->proxy, self);
+		g_clear_object (&priv->proxy);
+	}
+
 	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->dispose (object);
 }
 
 static void
 finalize (GObject *object)
 {
-	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE ((NMBluez4Adapter *) object);
+	NMBluez4Adapter *self = NM_BLUEZ4_ADAPTER (object);
+	NMBluez4AdapterPrivate *priv = NM_BLUEZ4_ADAPTER_GET_PRIVATE (self);
 
 	g_hash_table_destroy (priv->devices);
 	g_free (priv->address);
 	g_free (priv->path);
-	g_object_unref (priv->proxy);
 
 	G_OBJECT_CLASS (nm_bluez4_adapter_parent_class)->finalize (object);
 
@@ -384,7 +433,7 @@ nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	signals[INITIALIZED] = g_signal_new ("initialized",
+	signals[INITIALIZED] = g_signal_new (NM_BLUEZ4_ADAPTER_INITIALIZED,
 	                                     G_OBJECT_CLASS_TYPE (object_class),
 	                                     G_SIGNAL_RUN_LAST,
 	                                     0,
@@ -392,7 +441,7 @@ nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
 	                                     g_cclosure_marshal_VOID__BOOLEAN,
 	                                     G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
 
-	signals[DEVICE_ADDED] = g_signal_new ("device-added",
+	signals[DEVICE_ADDED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
 	                                      G_OBJECT_CLASS_TYPE (object_class),
 	                                      G_SIGNAL_RUN_LAST,
 	                                      0,
@@ -400,7 +449,7 @@ nm_bluez4_adapter_class_init (NMBluez4AdapterClass *config_class)
 	                                      g_cclosure_marshal_VOID__OBJECT,
 	                                      G_TYPE_NONE, 1, G_TYPE_OBJECT);
 
-	signals[DEVICE_REMOVED] = g_signal_new ("device-removed",
+	signals[DEVICE_REMOVED] = g_signal_new (NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
 	                                        G_OBJECT_CLASS_TYPE (object_class),
 	                                        G_SIGNAL_RUN_LAST,
 	                                        0,
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.h b/src/devices/bluetooth/nm-bluez4-adapter.h
index e240ec2c..0aa4ff91 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.h
+++ b/src/devices/bluetooth/nm-bluez4-adapter.h
@@ -30,9 +30,15 @@
 #define NM_IS_BLUEZ4_ADAPTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_BLUEZ4_ADAPTER))
 #define NM_BLUEZ4_ADAPTER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_BLUEZ4_ADAPTER, NMBluez4AdapterClass))
 
+/* Properties */
 #define NM_BLUEZ4_ADAPTER_PATH    "path"
 #define NM_BLUEZ4_ADAPTER_ADDRESS "address"
 
+/* Signals */
+#define NM_BLUEZ4_ADAPTER_INITIALIZED      "initialized"
+#define NM_BLUEZ4_ADAPTER_DEVICE_ADDED     "device-added"
+#define NM_BLUEZ4_ADAPTER_DEVICE_REMOVED   "device-removed"
+
 typedef struct _NMBluez4Adapter NMBluez4Adapter;
 typedef struct _NMBluez4AdapterClass NMBluez4AdapterClass;
 
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
index a9079a2f..1fe02f18 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ b/src/devices/bluetooth/nm-bluez4-manager.c
@@ -47,6 +47,7 @@ typedef struct {
 	NMSettings *settings;
 
 	GDBusProxy *proxy;
+	GCancellable *proxy_cancellable;
 
 	NMBluez4Adapter *adapter;
 } NMBluez4ManagerPrivate;
@@ -66,6 +67,11 @@ G_DEFINE_TYPE (NMBluez4Manager, nm_bluez4_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_BT
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez4-manager", __VA_ARGS__)
+
+/*****************************************************************************/
+
 static void
 emit_bdaddr_added (NMBluez4Manager *self, NMBluezDevice *device)
 {
@@ -119,8 +125,10 @@ adapter_initialized (NMBluez4Adapter *adapter, gboolean success, gpointer user_d
 			emit_bdaddr_added (self, NM_BLUEZ_DEVICE (iter->data));
 		g_slist_free (devices);
 
-		g_signal_connect (adapter, "device-added", G_CALLBACK (device_added), self);
-		g_signal_connect (adapter, "device-removed", G_CALLBACK (device_removed), self);
+		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_ADDED,
+		                  G_CALLBACK (device_added), self);
+		g_signal_connect (adapter, NM_BLUEZ4_ADAPTER_DEVICE_REMOVED,
+		                  G_CALLBACK (device_removed), self);
 	} else {
 		g_object_unref (priv->adapter);
 		priv->adapter = NULL;
@@ -169,49 +177,70 @@ default_adapter_changed (GDBusProxy *proxy, const char *path, NMBluez4Manager *s
 	/* Add the new default adapter */
 	if (path) {
 		priv->adapter = nm_bluez4_adapter_new (path, priv->settings);
-		g_signal_connect (priv->adapter, "initialized", G_CALLBACK (adapter_initialized), self);
+		g_signal_connect (priv->adapter, NM_BLUEZ4_ADAPTER_INITIALIZED,
+		                  G_CALLBACK (adapter_initialized), self);
 	}
 }
 
 static void
 default_adapter_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
 {
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	GVariant *ret;
-	GError *err = NULL;
+	NMBluez4Manager *self;
+	NMBluez4ManagerPrivate *priv;
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	const char *default_adapter;
 
 	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(o)"), &err);
-	if (ret) {
-		const char *default_adapter;
+	                                  G_VARIANT_TYPE ("(o)"), &error);
+	if (   !ret
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
 
-		g_variant_get (ret, "(&o)", &default_adapter);
-		default_adapter_changed (priv->proxy, default_adapter, self);
-		g_variant_unref (ret);
-	} else {
+	self = NM_BLUEZ4_MANAGER (user_data);
+	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->proxy_cancellable);
+
+	if (!ret) {
 		/* Ignore "No such adapter" errors; just means bluetooth isn't active */
-		if (   !_nm_dbus_error_has_name (err, "org.bluez.Error.NoSuchAdapter")
-		    && !_nm_dbus_error_has_name (err, "org.freedesktop.systemd1.LoadFailed")
-		    && !g_error_matches (err, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
-			g_dbus_error_strip_remote_error (err);
-			nm_log_warn (LOGD_BT, "bluez error getting default adapter: %s",
-			             err->message);
+		if (   !_nm_dbus_error_has_name (error, "org.bluez.Error.NoSuchAdapter")
+		    && !_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")
+		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
+			g_dbus_error_strip_remote_error (error);
+			_LOGW ("bluez error getting default adapter: %s",
+			       error->message);
 		}
-		g_error_free (err);
+		return;
 	}
+
+	g_variant_get (ret, "(&o)", &default_adapter);
+	default_adapter_changed (priv->proxy, default_adapter, self);
 }
 
 static void
-query_default_adapter (NMBluez4Manager *self)
+name_owner_changed (NMBluez4Manager *self)
 {
 	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+	gs_free char *owner = NULL;
+
+	nm_clear_g_cancellable (&priv->proxy_cancellable);
+
+	owner = g_dbus_proxy_get_name_owner (priv->proxy);
+	if (!owner) {
+		/* Throwing away the adapter removes all devices too */
+		g_clear_object (&priv->adapter);
+		return;
+	}
+
+	priv->proxy_cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_call (priv->proxy, "DefaultAdapter",
 	                   NULL,
 	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   NULL,
-	                   default_adapter_cb, self);
+	                   priv->proxy_cancellable,
+	                   default_adapter_cb,
+	                   self);
 }
 
 static void
@@ -219,34 +248,35 @@ name_owner_changed_cb (GObject *object,
                        GParamSpec *pspec,
                        gpointer user_data)
 {
-	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (user_data);
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
-	char *owner;
-
-	owner = g_dbus_proxy_get_name_owner (priv->proxy);
-	if (owner) {
-		query_default_adapter (self);
-		g_free (owner);
-	} else {
-		/* Throwing away the adapter removes all devices too */
-		g_clear_object (&priv->adapter);
-	}
+	name_owner_changed (user_data);
 }
 
-/*****************************************************************************/
-
 static void
-nm_bluez4_manager_init (NMBluez4Manager *self)
+_proxy_new_cb (GObject *source_object,
+               GAsyncResult *result,
+               gpointer user_data)
 {
-	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+	NMBluez4Manager *self;
+	NMBluez4ManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("bluez error creating D-Bus proxy: %s", error->message);
+		g_clear_object (&priv->proxy_cancellable);
+		return;
+	}
+
+	priv->proxy = proxy;
 
-	priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-	                                             G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-	                                             NULL,
-	                                             BLUEZ_SERVICE,
-	                                             BLUEZ_MANAGER_PATH,
-	                                             BLUEZ4_MANAGER_INTERFACE,
-	                                             NULL, NULL);
 	_nm_dbus_signal_connect (priv->proxy, "AdapterRemoved", G_VARIANT_TYPE ("(o)"),
 	                         G_CALLBACK (adapter_removed), self);
 	_nm_dbus_signal_connect (priv->proxy, "DefaultAdapterChanged", G_VARIANT_TYPE ("(o)"),
@@ -254,7 +284,27 @@ nm_bluez4_manager_init (NMBluez4Manager *self)
 	g_signal_connect (priv->proxy, "notify::g-name-owner",
 	                  G_CALLBACK (name_owner_changed_cb), self);
 
-	query_default_adapter (self);
+	name_owner_changed (self);
+}
+
+/*****************************************************************************/
+
+static void
+nm_bluez4_manager_init (NMBluez4Manager *self)
+{
+	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
+
+	priv->proxy_cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+	                          NULL,
+	                          NM_BLUEZ_SERVICE,
+	                          NM_BLUEZ_MANAGER_PATH,
+	                          NM_BLUEZ4_MANAGER_INTERFACE,
+	                          priv->proxy_cancellable,
+	                          _proxy_new_cb,
+	                          self);
 }
 
 NMBluez4Manager *
@@ -275,7 +325,13 @@ dispose (GObject *object)
 	NMBluez4Manager *self = NM_BLUEZ4_MANAGER (object);
 	NMBluez4ManagerPrivate *priv = NM_BLUEZ4_MANAGER_GET_PRIVATE (self);
 
-	g_clear_object (&priv->proxy);
+	nm_clear_g_cancellable (&priv->proxy_cancellable);
+
+	if (priv->proxy) {
+		g_signal_handlers_disconnect_by_data (priv->proxy, self);
+		g_clear_object (&priv->proxy);
+	}
+
 	g_clear_object (&priv->adapter);
 
 	G_OBJECT_CLASS (nm_bluez4_manager_parent_class)->dispose (object);
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index aba3a0dd..ca09b276 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -386,11 +386,11 @@ nm_bluez5_dun_cleanup (NMBluez5DunContext *context)
 			ioctl (context->rfcomm_fd, RFCOMMRELEASEDEV, &req);
 			context->rfcomm_id = -1;
 		}
-		close (context->rfcomm_fd);
+		nm_close (context->rfcomm_fd);
 		context->rfcomm_fd = -1;
 	}
 
-	close (context->rfcomm_tty_fd);
+	nm_close (context->rfcomm_tty_fd);
 	context->rfcomm_tty_fd = -1;
 }
 
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index 88759301..8c93f2a5 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -16,7 +16,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2007 - 2008 Novell, Inc.
- * Copyright (C) 2007 - 2013 Red Hat, Inc.
+ * Copyright (C) 2007 - 2017 Red Hat, Inc.
  * Copyright (C) 2013 Intel Corporation.
  */
 
@@ -30,14 +30,17 @@
 
 #include "nm-core-internal.h"
 
+#include "nm-utils/c-list.h"
 #include "nm-bluez-device.h"
 #include "nm-bluez-common.h"
+#include "devices/nm-device-bridge.h"
 #include "settings/nm-settings.h"
 
 /*****************************************************************************/
 
 enum {
 	BDADDR_ADDED,
+	NETWORK_SERVER_ADDED,
 	LAST_SIGNAL,
 };
 
@@ -49,10 +52,13 @@ typedef struct {
 	GDBusProxy *proxy;
 
 	GHashTable *devices;
+
+	CList network_servers;
 } NMBluez5ManagerPrivate;
 
 struct _NMBluez5Manager {
 	GObject parent;
+	NMBtVTableNetworkServer network_server_vtable;
 	NMBluez5ManagerPrivate _priv;
 };
 
@@ -64,6 +70,15 @@ G_DEFINE_TYPE (NMBluez5Manager, nm_bluez5_manager, G_TYPE_OBJECT)
 
 #define NM_BLUEZ5_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMBluez5Manager, NM_IS_BLUEZ5_MANAGER)
 
+#define NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE(self) (&(self)->network_server_vtable)
+#define NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER(vtable) \
+	NM_BLUEZ5_MANAGER(((char *)(vtable)) - offsetof (struct _NMBluez5Manager, network_server_vtable))
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_BT
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "bluez5", __VA_ARGS__)
+
 /*****************************************************************************/
 
 static void device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *self);
@@ -71,6 +86,182 @@ static void device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Man
 
 /*****************************************************************************/
 
+typedef struct {
+	char *path;
+	char *addr;
+	NMDevice *device;
+	CList lst_ns;
+} NetworkServer;
+
+static NetworkServer *
+_find_network_server (NMBluez5Manager *self, const char *path, NMDevice *device)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server;
+
+	nm_assert (path || NM_IS_DEVICE (device));
+
+	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
+		if (path && !nm_streq (network_server->path, path))
+			continue;
+		if (device && network_server->device != device)
+			continue;
+		return network_server;
+	}
+	return NULL;
+}
+
+static NetworkServer *
+_find_network_server_for_addr (NMBluez5Manager *self, const char *addr)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server;
+
+	c_list_for_each_entry (network_server, &priv->network_servers, lst_ns) {
+		/* The address lookups need a server not assigned to a device
+		 * and tolerate an empty address as a wildcard for "any". */
+		if (   !network_server->device
+		    && (!addr || nm_streq (network_server->addr, addr)))
+			return network_server;
+	}
+	return NULL;
+}
+
+static void
+_network_server_unregister (NMBluez5Manager *self, NetworkServer *network_server)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+
+	if (!network_server->device) {
+		/* Not connected. */
+		return;
+	}
+
+	_LOGI ("NAP: unregistering %s from %s",
+	       nm_device_get_iface (network_server->device),
+	       network_server->addr);
+
+	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
+	                        NM_BLUEZ_SERVICE,
+	                        network_server->path,
+	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+	                        "Unregister",
+	                        g_variant_new ("(s)", BLUETOOTH_CONNECT_NAP),
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1, NULL, NULL, NULL);
+
+	g_clear_object (&network_server->device);
+}
+
+static void
+_network_server_free (NMBluez5Manager *self, NetworkServer *network_server)
+{
+	_network_server_unregister (self, network_server);
+	c_list_unlink (&network_server->lst_ns);
+	g_free (network_server->path);
+	g_free (network_server->addr);
+	g_slice_free (NetworkServer, network_server);
+}
+
+static gboolean
+network_server_is_available (const NMBtVTableNetworkServer *vtable,
+                             const char *addr)
+{
+	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
+
+	return !!_find_network_server_for_addr (self, addr);
+}
+
+static gboolean
+network_server_register_bridge (const NMBtVTableNetworkServer *vtable,
+                                const char *addr,
+                                NMDevice *device)
+{
+	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server = _find_network_server_for_addr (self, addr);
+
+	nm_assert (NM_IS_DEVICE (device));
+	nm_assert (!_find_network_server (self, NULL, device));
+
+	if (!network_server) {
+		/* The device checked that a network server is available, before
+		 * starting the activation, but for some reason it no longer is.
+		 * Indicate that the activation should not proceed. */
+		_LOGI ("NAP: %s is not available for %s", addr, nm_device_get_iface (device));
+		return FALSE;
+	}
+
+	_LOGI ("NAP: registering %s on %s", nm_device_get_iface (device), network_server->addr);
+
+	g_dbus_connection_call (g_dbus_proxy_get_connection (priv->proxy),
+	                        NM_BLUEZ_SERVICE,
+	                        network_server->path,
+	                        NM_BLUEZ5_NETWORK_SERVER_INTERFACE,
+	                        "Register",
+	                        g_variant_new ("(ss)", BLUETOOTH_CONNECT_NAP, nm_device_get_iface (device)),
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1, NULL, NULL, NULL);
+
+	network_server->device = g_object_ref (device);
+
+	return TRUE;
+}
+
+static gboolean
+network_server_unregister_bridge (const NMBtVTableNetworkServer *vtable,
+                                  NMDevice *device)
+{
+	NMBluez5Manager *self = NETWORK_SERVER_VTABLE_GET_NM_BLUEZ5_MANAGER (vtable);
+	NetworkServer *network_server = _find_network_server (self, NULL, device);
+
+	if (network_server)
+		_network_server_unregister (self, network_server);
+
+	return TRUE;
+}
+
+static void
+network_server_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
+{
+	NetworkServer *network_server;
+
+	network_server = _find_network_server (self, path, NULL);
+	if (!network_server)
+		return;
+
+	if (network_server->device) {
+		nm_device_queue_state (network_server->device, NM_DEVICE_STATE_DISCONNECTED,
+		                       NM_DEVICE_STATE_REASON_BT_FAILED);
+	}
+	_LOGI ("NAP: removed interface %s", network_server->addr);
+	_network_server_free (self, network_server);
+}
+
+static void
+network_server_added (GDBusProxy *proxy, const gchar *path, const char *addr, NMBluez5Manager *self)
+{
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NetworkServer *network_server;
+
+	/* If BlueZ messes up and announces a single network server twice,
+	 * make sure we get rid of the older instance first. */
+	network_server_removed (proxy, path, self);
+
+	network_server = g_slice_new0 (NetworkServer);
+	network_server->path = g_strdup (path);
+	network_server->addr = g_strdup (addr);
+	c_list_link_before (&priv->network_servers, &network_server->lst_ns);
+
+	_LOGI ("NAP: added interface %s", addr);
+
+	g_signal_emit (self, signals[NETWORK_SERVER_ADDED], 0);
+}
+
+/*****************************************************************************/
+
 static void
 emit_bdaddr_added (NMBluez5Manager *self, NMBluezDevice *device)
 {
@@ -125,14 +316,14 @@ device_usable (NMBluezDevice *device, GParamSpec *pspec, NMBluez5Manager *self)
 {
 	gboolean usable = nm_bluez_device_get_usable (device);
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device now %s",
-	            nm_bluez_device_get_path (device),
-	            usable ? "usable" : "unusable");
+	_LOGD ("(%s): bluez device now %s",
+	       nm_bluez_device_get_path (device),
+	       usable ? "usable" : "unusable");
 
 	if (usable) {
-		nm_log_dbg (LOGD_BT, "(%s): bluez device address %s",
-				    nm_bluez_device_get_path (device),
-				    nm_bluez_device_get_address (device));
+		_LOGD ("(%s): bluez device address %s",
+		       nm_bluez_device_get_path (device),
+		       nm_bluez_device_get_address (device));
 		emit_bdaddr_added (self, device);
 	} else
 		g_signal_emit_by_name (device, NM_BLUEZ_DEVICE_REMOVED);
@@ -143,9 +334,9 @@ device_initialized (NMBluezDevice *device, gboolean success, NMBluez5Manager *se
 {
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device %s",
-	            nm_bluez_device_get_path (device),
-	            success ? "initialized" : "failed to initialize");
+	_LOGD ("(%s): bluez device %s",
+	       nm_bluez_device_get_path (device),
+	       success ? "initialized" : "failed to initialize");
 	if (!success)
 		g_hash_table_remove (priv->devices, nm_bluez_device_get_path (device));
 }
@@ -157,11 +348,11 @@ device_added (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
 	NMBluezDevice *device;
 
 	device = nm_bluez_device_new (path, NULL, priv->settings, 5);
-	g_signal_connect (device, "initialized", G_CALLBACK (device_initialized), self);
-	g_signal_connect (device, "notify::usable", G_CALLBACK (device_usable), self);
+	g_signal_connect (device, NM_BLUEZ_DEVICE_INITIALIZED, G_CALLBACK (device_initialized), self);
+	g_signal_connect (device, "notify::" NM_BLUEZ_DEVICE_USABLE, G_CALLBACK (device_usable), self);
 	g_hash_table_insert (priv->devices, (gpointer) nm_bluez_device_get_path (device), device);
 
-	nm_log_dbg (LOGD_BT, "(%s): new bluez device found", path);
+	_LOGD ("(%s): new bluez device found", path);
 }
 
 static void
@@ -170,7 +361,7 @@ device_removed (GDBusProxy *proxy, const gchar *path, NMBluez5Manager *self)
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 	NMBluezDevice *device;
 
-	nm_log_dbg (LOGD_BT, "(%s): bluez device removed", path);
+	_LOGD ("(%s): bluez device removed", path);
 
 	device = g_hash_table_lookup (priv->devices, path);
 	if (device) {
@@ -186,8 +377,16 @@ object_manager_interfaces_added (GDBusProxy      *proxy,
                                  GVariant        *dict,
                                  NMBluez5Manager *self)
 {
-	if (g_variant_lookup (dict, BLUEZ5_DEVICE_INTERFACE, "a{sv}", NULL))
+	if (g_variant_lookup (dict, NM_BLUEZ5_DEVICE_INTERFACE, "a{sv}", NULL))
 		device_added (proxy, path, self);
+	if (g_variant_lookup (dict, NM_BLUEZ5_NETWORK_SERVER_INTERFACE, "a{sv}", NULL)) {
+		gs_unref_variant GVariant *adapter = g_variant_lookup_value (dict, NM_BLUEZ5_ADAPTER_INTERFACE, G_VARIANT_TYPE_DICTIONARY);
+		const char *address;
+
+		if (   adapter
+		    && g_variant_lookup (adapter, "Address", "&s", &address))
+			network_server_added (proxy, path, address, self);
+	}
 }
 
 static void
@@ -196,8 +395,10 @@ object_manager_interfaces_removed (GDBusProxy       *proxy,
                                    const char      **ifaces,
                                    NMBluez5Manager  *self)
 {
-	if (ifaces && g_strv_contains (ifaces, BLUEZ5_DEVICE_INTERFACE))
+	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_DEVICE_INTERFACE))
 		device_removed (proxy, path, self);
+	if (ifaces && g_strv_contains (ifaces, NM_BLUEZ5_NETWORK_SERVER_INTERFACE))
+		network_server_removed (proxy, path, self);
 }
 
 static void
@@ -215,20 +416,17 @@ get_managed_objects_cb (GDBusProxy *proxy,
 	                                      &error);
 	if (!variant) {
 		if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
-			nm_log_warn (LOGD_BT, "Couldn't get managed objects: not running Bluez5?");
+			_LOGW ("Couldn't get managed objects: not running Bluez5?");
 		else {
 			g_dbus_error_strip_remote_error (error);
-			nm_log_warn (LOGD_BT, "Couldn't get managed objects: %s", error->message);
+			_LOGW ("Couldn't get managed objects: %s", error->message);
 		}
 		g_clear_error (&error);
 		return;
 	}
 	g_variant_iter_init (&i, g_variant_get_child_value (variant, 0));
 	while ((g_variant_iter_next (&i, "{&o*}", &path, &ifaces))) {
-		if (g_variant_lookup_value (ifaces, BLUEZ5_DEVICE_INTERFACE,
-		                            G_VARIANT_TYPE_DICTIONARY)) {
-			device_added (proxy, path, self);
-		}
+		object_manager_interfaces_added (proxy, path, ifaces, self);
 		g_variant_unref (ifaces);
 	}
 
@@ -248,7 +446,7 @@ on_proxy_acquired (GObject *object,
 	priv->proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
 
 	if (!priv->proxy) {
-		nm_log_warn (LOGD_BT, "Couldn't acquire object manager proxy: %s", error->message);
+		_LOGW ("Couldn't acquire object manager proxy: %s", error->message);
 		g_clear_error (&error);
 		return;
 	}
@@ -281,9 +479,9 @@ bluez_connect (NMBluez5Manager *self)
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_NONE,
 	                          NULL,
-	                          BLUEZ_SERVICE,
-	                          BLUEZ_MANAGER_PATH,
-	                          OBJECT_MANAGER_INTERFACE,
+	                          NM_BLUEZ_SERVICE,
+	                          NM_BLUEZ_MANAGER_PATH,
+	                          NM_OBJECT_MANAGER_INTERFACE,
 	                          NULL,
 	                          (GAsyncReadyCallback) on_proxy_acquired,
 	                          self);
@@ -306,33 +504,26 @@ name_owner_changed_cb (GObject *object,
 	}
 }
 
-static void
-bluez_cleanup (NMBluez5Manager *self, gboolean do_signal)
-{
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
-
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_func (priv->proxy, G_CALLBACK (name_owner_changed_cb), self);
-		g_clear_object (&priv->proxy);
-	}
-
-	if (do_signal)
-		remove_all_devices (self);
-	else
-		g_hash_table_remove_all (priv->devices);
-}
-
 /*****************************************************************************/
 
 static void
 nm_bluez5_manager_init (NMBluez5Manager *self)
 {
 	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	NMBtVTableNetworkServer *network_server_vtable = NM_BLUEZ5_MANAGER_GET_NETWORK_SERVER_VTABLE (self);
 
 	bluez_connect (self);
 
-	priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal,
+	priv->devices = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                       NULL, g_object_unref);
+
+	c_list_init (&priv->network_servers);
+
+	nm_assert (!nm_bt_vtable_network_server);
+	network_server_vtable->is_available = network_server_is_available;
+	network_server_vtable->register_bridge = network_server_register_bridge;
+	network_server_vtable->unregister_bridge = network_server_unregister_bridge;
+	nm_bt_vtable_network_server = network_server_vtable;
 }
 
 NMBluez5Manager *
@@ -351,8 +542,18 @@ static void
 dispose (GObject *object)
 {
 	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
+	CList *iter, *safe;
 
-	bluez_cleanup (self, FALSE);
+	c_list_for_each_safe (iter, safe, &priv->network_servers)
+		_network_server_free (self, c_list_entry (iter, NetworkServer, lst_ns));
+
+	if (priv->proxy) {
+		g_signal_handlers_disconnect_by_func (priv->proxy, G_CALLBACK (name_owner_changed_cb), self);
+		g_clear_object (&priv->proxy);
+	}
+
+	g_hash_table_remove_all (priv->devices);
 
 	G_OBJECT_CLASS (nm_bluez5_manager_parent_class)->dispose (object);
 }
@@ -360,7 +561,8 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE ((NMBluez5Manager *) object);
+	NMBluez5Manager *self = NM_BLUEZ5_MANAGER (object);
+	NMBluez5ManagerPrivate *priv = NM_BLUEZ5_MANAGER_GET_PRIVATE (self);
 
 	g_hash_table_destroy (priv->devices);
 
@@ -384,4 +586,11 @@ nm_bluez5_manager_class_init (NMBluez5ManagerClass *klass)
 	                  0, NULL, NULL, NULL,
 	                  G_TYPE_NONE, 5, G_TYPE_OBJECT, G_TYPE_STRING,
 	                  G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
+
+	signals[NETWORK_SERVER_ADDED] =
+	    g_signal_new (NM_BLUEZ_MANAGER_NETWORK_SERVER_ADDED,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_FIRST,
+	                  0, NULL, NULL, NULL,
+	                  G_TYPE_NONE, 0);
 }
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index 4ee71489..0d46be8f 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -40,15 +40,14 @@
 #include "nm-bt-error.h"
 #include "platform/nm-platform.h"
 
+#include "devices/wwan/nm-modem-manager.h"
+#include "devices/wwan/nm-modem.h"
+
 #include "introspection/org.freedesktop.NetworkManager.Device.Bluetooth.h"
 
 #include "devices/nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceBt);
 
-#define MM_DBUS_SERVICE   "org.freedesktop.ModemManager1"
-#define MM_DBUS_PATH      "/org/freedesktop/ModemManager1"
-#define MM_DBUS_INTERFACE "org.freedesktop.ModemManager1"
-
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
@@ -65,7 +64,8 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
-	GDBusProxy *mm_proxy;
+	NMModemManager *modem_manager;
+
 	gboolean mm_running;
 
 	NMBluezDevice *bt_device;
@@ -647,7 +647,7 @@ component_added (NMDevice *device, GObject *component)
 	NMDeviceState state;
 	NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
 
-	if (!NM_IS_MODEM (component))
+	if (!component || !NM_IS_MODEM (component))
 		return FALSE;
 	modem = NM_MODEM (component);
 
@@ -967,9 +967,12 @@ is_available (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static void
-set_mm_running (NMDeviceBt *self, gboolean running)
+set_mm_running (NMDeviceBt *self)
 {
 	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
+	gboolean running;
+
+	running = (nm_modem_manager_name_owner_get (priv->modem_manager) != NULL);
 
 	if (priv->mm_running != running) {
 		_LOGD (LOGD_BT, "ModemManager now %s",
@@ -983,18 +986,11 @@ set_mm_running (NMDeviceBt *self, gboolean running)
 }
 
 static void
-mm_name_owner_changed (GObject *object,
-                       GParamSpec *pspec,
-                       NMDeviceBt *self)
+mm_name_owner_changed_cb (GObject *object,
+                          GParamSpec *pspec,
+                          gpointer user_data)
 {
-	char *owner;
-
-	owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (object));
-	if (owner) {
-		set_mm_running (self, TRUE);
-		g_free (owner);
-	} else
-		set_mm_running (self, FALSE);
+	set_mm_running (user_data);
 }
 
 /*****************************************************************************/
@@ -1039,7 +1035,8 @@ set_property (GObject *object, guint prop_id,
 	case PROP_BT_DEVICE:
 		/* construct-only */
 		priv->bt_device = g_value_dup_object (value);
-		g_signal_connect (priv->bt_device, "removed", G_CALLBACK (bluez_device_removed), object);
+		if (!priv->bt_device)
+			g_return_if_reached ();
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1052,46 +1049,42 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_device_bt_init (NMDeviceBt *self)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
-	GError *error = NULL;
-
-	priv->mm_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-	                                                G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
-	                                                    G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
-	                                                    G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-	                                                NULL,
-	                                                MM_DBUS_SERVICE,
-	                                                MM_DBUS_PATH,
-	                                                MM_DBUS_INTERFACE,
-	                                                NULL, &error);
-	if (priv->mm_proxy) {
-		g_signal_connect (priv->mm_proxy, "notify::g-name-owner",
-		                  G_CALLBACK (mm_name_owner_changed),
-		                  self);
-		mm_name_owner_changed (G_OBJECT (priv->mm_proxy), NULL, self);
-	} else {
-		_LOGW (LOGD_MB, "Could not create proxy for '%s': %s",
-		       MM_DBUS_SERVICE, error->message);
-		g_clear_error (&error);
-	}
 }
 
 static void
 constructed (GObject *object)
 {
-	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE ((NMDeviceBt *) object);
+	NMDeviceBt *self = NM_DEVICE_BT (object);
+	NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (self);
 	const char *my_hwaddr;
 
 	G_OBJECT_CLASS (nm_device_bt_parent_class)->constructed (object);
 
+	priv->modem_manager = g_object_ref (nm_modem_manager_get ());
+
+	nm_modem_manager_name_owner_ref (priv->modem_manager);
+
+	g_signal_connect (priv->modem_manager,
+	                  "notify::"NM_MODEM_MANAGER_NAME_OWNER,
+	                  G_CALLBACK (mm_name_owner_changed_cb),
+	                  self);
+
+	if (priv->bt_device) {
+		/* Watch for BT device property changes */
+		g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED,
+		                  G_CALLBACK (bluez_connected_changed),
+		                  object);
+		g_signal_connect (priv->bt_device, NM_BLUEZ_DEVICE_REMOVED,
+		                  G_CALLBACK (bluez_device_removed), object);
+	}
+
 	my_hwaddr = nm_device_get_hw_address (NM_DEVICE (object));
-	g_assert (my_hwaddr);
-	priv->bdaddr = g_strdup (my_hwaddr);
+	if (my_hwaddr)
+		priv->bdaddr = g_strdup (my_hwaddr);
+	else
+		g_warn_if_reached ();
 
-	/* Watch for BT device property changes */
-	g_signal_connect (priv->bt_device, "notify::" NM_BLUEZ_DEVICE_CONNECTED,
-	                  G_CALLBACK (bluez_connected_changed),
-	                  object);
+	set_mm_running (self);
 }
 
 NMDevice *
@@ -1129,9 +1122,10 @@ dispose (GObject *object)
 
 	g_signal_handlers_disconnect_matched (priv->bt_device, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, object);
 
-	if (priv->mm_proxy) {
-		g_signal_handlers_disconnect_by_func (priv->mm_proxy, G_CALLBACK (mm_name_owner_changed), object);
-		g_clear_object (&priv->mm_proxy);
+	if (priv->modem_manager) {
+		g_signal_handlers_disconnect_by_func (priv->modem_manager, G_CALLBACK (mm_name_owner_changed_cb), object);
+		nm_modem_manager_name_owner_unref (priv->modem_manager);
+		g_clear_object (&priv->modem_manager);
 	}
 
 	modem_cleanup (NM_DEVICE_BT (object));
diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h
index 9bcf6ca8..b90dbd2a 100644
--- a/src/devices/bluetooth/nm-device-bt.h
+++ b/src/devices/bluetooth/nm-device-bt.h
@@ -24,8 +24,6 @@
 #include "devices/nm-device.h"
 #include "nm-bluez-device.h"
 
-#include "devices/wwan/nm-modem.h"
-
 #define NM_TYPE_DEVICE_BT                   (nm_device_bt_get_type ())
 #define NM_DEVICE_BT(obj)                   (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_BT, NMDeviceBt))
 #define NM_DEVICE_BT_CLASS(klass)           (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_BT, NMDeviceBtClass))
@@ -52,8 +50,10 @@ NMDevice *nm_device_bt_new (NMBluezDevice *bt_device,
 
 guint32 nm_device_bt_get_capabilities (NMDeviceBt *device);
 
+struct _NMModem;
+
 gboolean nm_device_bt_modem_added (NMDeviceBt *device,
-                                   NMModem *modem,
+                                   struct _NMModem *modem,
                                    const char *driver);
 
 #endif /* __NETWORKMANAGER_DEVICE_BT_H__ */
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index d9c2ca64..910dd0bf 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -58,24 +58,6 @@ get_generic_capabilities (NMDevice *dev)
 }
 
 static gboolean
-is_available (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags)
-{
-	return TRUE;
-}
-
-static gboolean
-check_connection_available (NMDevice *device,
-                            NMConnection *connection,
-                            NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
-{
-	/* Connections are always available because the carrier state is determined
-	 * by the slave carrier states, not the bonds's state.
-	 */
-	return TRUE;
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMSettingBond *s_bond;
@@ -501,7 +483,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create bond interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 	return TRUE;
@@ -622,6 +604,7 @@ reapply_connection (NMDevice *device, NMConnection *con_old, NMConnection *con_n
 static void
 nm_device_bond_init (NMDeviceBond * self)
 {
+	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
 static void
@@ -631,10 +614,9 @@ nm_device_bond_class_init (NMDeviceBondClass *klass)
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BOND_SETTING_NAME, NM_LINK_TYPE_BOND)
 
+	parent_class->is_master = TRUE;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-	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->update_connection = update_connection;
@@ -671,7 +653,6 @@ create_device (NMDeviceFactory *factory,
 	                                  NM_DEVICE_TYPE_DESC, "Bond",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BOND,
 	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_BOND,
-	                                  NM_DEVICE_IS_MASTER, TRUE,
 	                                  NULL);
 }
 
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 01c4eb22..74689aef 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -49,6 +49,10 @@ G_DEFINE_TYPE (NMDeviceBridge, nm_device_bridge, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
+const NMBtVTableNetworkServer *nm_bt_vtable_network_server = NULL;
+
+/*****************************************************************************/
+
 static NMDeviceCapabilities
 get_generic_capabilities (NMDevice *dev)
 {
@@ -56,20 +60,23 @@ get_generic_capabilities (NMDevice *dev)
 }
 
 static gboolean
-is_available (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags)
-{
-	return TRUE;
-}
-
-static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
                             NMDeviceCheckConAvailableFlags flags,
                             const char *specific_object)
 {
-	/* Connections are always available because the carrier state is determined
-	 * by the bridge port carrier states, not the bridge's state.
-	 */
+	NMSettingBluetooth *s_bt;
+
+	if (!NM_DEVICE_CLASS (nm_device_bridge_parent_class)->check_connection_available (device, connection, flags, specific_object))
+		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));
+	}
+
 	return TRUE;
 }
 
@@ -83,9 +90,17 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		return FALSE;
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
-	if (!s_bridge || !nm_connection_is_type (connection, NM_SETTING_BRIDGE_SETTING_NAME))
+	if (!s_bridge)
 		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
+			return FALSE;
+	}
+
 	mac_address = nm_setting_bridge_get_mac_address (s_bridge);
 	if (mac_address && nm_device_is_real (device)) {
 		const char *hw_addr;
@@ -141,6 +156,7 @@ static const Option master_options[] = {
 	{ NM_SETTING_BRIDGE_HELLO_TIME, "hello_time", TRUE, TRUE },
 	{ NM_SETTING_BRIDGE_MAX_AGE, "max_age", TRUE, TRUE },
 	{ NM_SETTING_BRIDGE_AGEING_TIME, "ageing_time", TRUE, TRUE },
+	{ NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "group_fwd_mask", TRUE, FALSE },
 	{ NM_SETTING_BRIDGE_MULTICAST_SNOOPING, "multicast_snooping", FALSE, FALSE },
 	{ NULL, NULL }
 };
@@ -324,6 +340,40 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMConnection *connection;
+	NMSettingBluetooth *s_bt;
+
+	connection = nm_device_get_applied_connection (device);
+
+	s_bt = _nm_connection_get_setting_bluetooth_for_nap (connection);
+	if (s_bt) {
+		if (   !nm_bt_vtable_network_server
+		    || !nm_bt_vtable_network_server->register_bridge (nm_bt_vtable_network_server,
+		                                                      nm_setting_bluetooth_get_bdaddr (s_bt),
+		                                                      device)) {
+			/* The HCI we could use is no longer present. */
+			*out_failure_reason = NM_DEVICE_STATE_REASON_REMOVED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	if (nm_bt_vtable_network_server) {
+		/* always call unregister. It does nothing if the device
+		 * isn't registered as a hotspot bridge. */
+		nm_bt_vtable_network_server->unregister_bridge (nm_bt_vtable_network_server,
+		                                                device);
+	}
+}
+
 static gboolean
 enslave_slave (NMDevice *device,
                NMDevice *slave,
@@ -384,20 +434,32 @@ create_and_realize (NMDevice *device,
 	NMSettingBridge *s_bridge;
 	const char *iface = nm_device_get_iface (device);
 	const char *hwaddr;
+	gs_free char *hwaddr_cloned = NULL;
 	guint8 mac_address[NM_UTILS_HWADDR_LEN_MAX];
 	NMPlatformError plerr;
 
-	g_assert (iface);
+	nm_assert (iface);
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
-	g_assert (s_bridge);
+	nm_assert (s_bridge);
+
 	hwaddr = nm_setting_bridge_get_mac_address (s_bridge);
+	if (   !hwaddr
+	    && nm_device_hw_addr_get_cloned (device, connection, FALSE,
+	                                     &hwaddr_cloned, NULL, NULL)) {
+		/* The cloned MAC address might by dynamic, for example with stable-id="${RANDOM}".
+		 * It's a bit odd that we first create the device with one dynamic address,
+		 * and later on may reset it to another. That is, because we don't cache
+		 * the dynamic address in @device, like we do during nm_device_hw_addr_set_cloned(). */
+		hwaddr = hwaddr_cloned;
+	}
+
 	if (hwaddr) {
 		if (!nm_utils_hwaddr_aton (hwaddr, mac_address, ETH_ALEN)) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 			             "Invalid hardware address '%s'",
 			             hwaddr);
-			return FALSE;
+			g_return_val_if_reached (FALSE);
 		}
 	}
 
@@ -411,7 +473,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create bridge interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -423,6 +485,7 @@ create_and_realize (NMDevice *device,
 static void
 nm_device_bridge_init (NMDeviceBridge * self)
 {
+	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
 static void
@@ -432,8 +495,8 @@ nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 
 	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NM_SETTING_BRIDGE_SETTING_NAME, NM_LINK_TYPE_BRIDGE)
 
+	parent_class->is_master = TRUE;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-	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;
@@ -443,6 +506,8 @@ nm_device_bridge_class_init (NMDeviceBridgeClass *klass)
 
 	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;
@@ -470,12 +535,36 @@ create_device (NMDeviceFactory *factory,
 	                                  NM_DEVICE_TYPE_DESC, "Bridge",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_BRIDGE,
 	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_BRIDGE,
-	                                  NM_DEVICE_IS_MASTER, TRUE,
 	                                  NULL);
 }
 
+static gboolean
+match_connection (NMDeviceFactory *factory,
+                  NMConnection *connection)
+{
+	const char *type = nm_connection_get_connection_type (connection);
+
+	if (nm_streq (type, NM_SETTING_BRIDGE_SETTING_NAME))
+		return TRUE;
+
+	nm_assert (nm_streq (type, NM_SETTING_BLUETOOTH_SETTING_NAME));
+
+	if (!_nm_connection_get_setting_bluetooth_for_nap (connection))
+		return FALSE;
+
+	if (!g_type_from_name ("NMBluezManager")) {
+		/* bluetooth NAP connections are handled by bridge factory. However,
+		 * it needs help from the bluetooth plugin, so if the plugin is not loaded,
+		 * we claim not to support it. */
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (BRIDGE, Bridge, bridge,
 	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_BRIDGE)
-	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_BRIDGE_SETTING_NAME),
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_BRIDGE_SETTING_NAME, NM_SETTING_BLUETOOTH_SETTING_NAME),
 	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
 );
diff --git a/src/devices/nm-device-bridge.h b/src/devices/nm-device-bridge.h
index f0fa1f4b..44b4ed72 100644
--- a/src/devices/nm-device-bridge.h
+++ b/src/devices/nm-device-bridge.h
@@ -35,4 +35,6 @@ typedef struct _NMDeviceBridgeClass NMDeviceBridgeClass;
 
 GType nm_device_bridge_get_type (void);
 
+extern const NMBtVTableNetworkServer *nm_bt_vtable_network_server;
+
 #endif /* __NETWORKMANAGER_DEVICE_BRIDGE_H__ */
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index dce4f7bc..085c44e6 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -112,7 +112,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create dummy interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 8a04d401..7807100f 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -108,14 +108,14 @@ typedef struct _NMDeviceEthernetPrivate {
 
 	/* PPPoE */
 	NMPPPManager *ppp_manager;
-	NMIP4Config  *pending_ip4_config;
 	gint32        last_pppoe_time;
 	guint         pppoe_wait_id;
 
 	/* DCB */
 	DcbWait       dcb_wait;
 	guint         dcb_timeout_id;
-	gulong        dcb_carrier_id;
+
+	bool          dcb_handle_carrier_changes:1;
 } NMDeviceEthernetPrivate;
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceEthernet,
@@ -255,22 +255,6 @@ _update_s390_subchannels (NMDeviceEthernet *self)
 }
 
 static void
-reset_8021x_autoconnect_retries (NMDevice *device)
-{
-	NMActRequest *req;
-	NMSettingsConnection *connection;
-
-	req = nm_device_get_act_request (device);
-	if (   req
-	    && nm_device_get_applied_setting (device, NM_TYPE_SETTING_802_1X)) {
-		connection = nm_act_request_get_settings_connection (req);
-		g_return_if_fail (connection);
-		/* Reset autoconnect retries on success, failure, or when deactivating */
-		nm_settings_connection_reset_autoconnect_retries (connection);
-	}
-}
-
-static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -278,12 +262,6 @@ device_state_changed (NMDevice *device,
 {
 	if (new_state > NM_DEVICE_STATE_ACTIVATED)
 		wired_secrets_cancel (NM_DEVICE_ETHERNET (device));
-
-	if (NM_IN_SET (new_state,
-	               NM_DEVICE_STATE_ACTIVATED,
-	               NM_DEVICE_STATE_FAILED,
-	               NM_DEVICE_STATE_DISCONNECTED))
-		reset_8021x_autoconnect_retries (device);
 }
 
 static void
@@ -294,7 +272,7 @@ nm_device_ethernet_init (NMDeviceEthernet *self)
 	priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_DEVICE_ETHERNET, NMDeviceEthernetPrivate);
 	self->_priv = priv;
 
-	priv->s390_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	priv->s390_options = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 }
 
 static NMDeviceCapabilities
@@ -680,25 +658,20 @@ handle_auth_or_fail (NMDeviceEthernet *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
+	NMDeviceEthernetPrivate *priv;
 	const char *setting_name;
 	NMConnection *applied_connection;
-	NMSettingsConnection *settings_connection;
-	int tries_left;
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-	settings_connection = nm_act_request_get_settings_connection (req);
+	priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 
-	tries_left = nm_settings_connection_get_autoconnect_retries (settings_connection);
-	if (tries_left == 0)
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
-	if (tries_left > 0)
-		nm_settings_connection_set_autoconnect_retries (settings_connection, tries_left - 1);
-
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
 	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
 
+	applied_connection = nm_act_request_get_applied_connection (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (setting_name) {
 		wired_secrets_get_secrets (self, setting_name,
@@ -1000,6 +973,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 static NMActStageReturn
 pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_failure_reason)
 {
+	NMDevice *device = NM_DEVICE (self);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
 	NMSettingPppoe *s_pppoe;
 	NMActRequest *req;
@@ -1011,9 +985,17 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_
 	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
 	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
 
-	priv->ppp_manager = nm_ppp_manager_create (nm_device_get_iface (NM_DEVICE (self)),
+	priv->ppp_manager = nm_ppp_manager_create (nm_device_get_iface (device),
 	                                           &err);
 
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     nm_device_get_route_table (device, AF_INET, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET),
+		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET6));
+	}
+
 	if (   !priv->ppp_manager
 	    || !nm_ppp_manager_start (priv->ppp_manager, req,
 	                              nm_setting_pppoe_get_username (s_pppoe),
@@ -1133,7 +1115,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 			_LOGD (LOGD_DCB, "dcb_state() enabling DCB");
 			nm_clear_g_source (&priv->dcb_timeout_id);
 			if (!dcb_enable (device)) {
-				nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+				priv->dcb_handle_carrier_changes = FALSE;
 				nm_device_state_changed (device,
 				                         NM_DEVICE_STATE_FAILED,
 				                         NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
@@ -1157,7 +1139,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 			_LOGD (LOGD_DCB, "dcb_state() preconfig up configuring DCB");
 			nm_clear_g_source (&priv->dcb_timeout_id);
 			if (!dcb_configure (device)) {
-				nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+				priv->dcb_handle_carrier_changes = FALSE;
 				nm_device_state_changed (device,
 				                         NM_DEVICE_STATE_FAILED,
 				                         NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED);
@@ -1180,7 +1162,7 @@ dcb_state (NMDevice *device, gboolean timeout)
 		if (timeout || carrier) {
 			_LOGD (LOGD_DCB, "dcb_state() postconfig up starting IP");
 			nm_clear_g_source (&priv->dcb_timeout_id);
-			nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+			priv->dcb_handle_carrier_changes = FALSE;
 			priv->dcb_wait = DCB_WAIT_UNKNOWN;
 			nm_device_activate_schedule_stage3_ip_config_start (device);
 		}
@@ -1190,20 +1172,6 @@ dcb_state (NMDevice *device, gboolean timeout)
 	}
 }
 
-static void
-dcb_carrier_changed (NMDevice *device, GParamSpec *pspec, gpointer unused)
-{
-	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
-	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
-
-	g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
-
-	if (priv->dcb_timeout_id) {
-		_LOGD (LOGD_DCB, "carrier_changed() calling dcb_state()");
-		dcb_state (device, FALSE);
-	}
-}
-
 /*****************************************************************************/
 
 static gboolean
@@ -1262,7 +1230,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	g_return_val_if_fail (s_con, NM_ACT_STAGE_RETURN_FAILURE);
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
-	nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+	priv->dcb_handle_carrier_changes = FALSE;
 
 	/* 802.1x has to run before any IP configuration since the 802.1x auth
 	 * process opens the port up for normal traffic.
@@ -1296,13 +1264,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 			priv->dcb_timeout_id = g_timeout_add_seconds (4, dcb_carrier_timeout, device);
 		}
 
-		/* Watch carrier independently of NMDeviceClass::carrier_changed so
-		 * we get instant notifications of disconnection that aren't deferred.
-		 */
-		priv->dcb_carrier_id = g_signal_connect (device,
-		                                         "notify::" NM_DEVICE_CARRIER,
-		                                         G_CALLBACK (dcb_carrier_changed),
-		                                         NULL);
+		priv->dcb_handle_carrier_changes = TRUE;
 		ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
@@ -1367,16 +1329,8 @@ deactivate (NMDevice *device)
 	NMSettingDcb *s_dcb;
 	GError *error = NULL;
 
-	/* Clear wired secrets tries when deactivating */
-	reset_8021x_autoconnect_retries (device);
-
 	nm_clear_g_source (&priv->pppoe_wait_id);
 
-	if (priv->pending_ip4_config) {
-		g_object_unref (priv->pending_ip4_config);
-		priv->pending_ip4_config = NULL;
-	}
-
 	if (priv->ppp_manager) {
 		nm_ppp_manager_stop_sync (priv->ppp_manager);
 		g_clear_object (&priv->ppp_manager);
@@ -1386,7 +1340,7 @@ deactivate (NMDevice *device)
 
 	priv->dcb_wait = DCB_WAIT_UNKNOWN;
 	nm_clear_g_source (&priv->dcb_timeout_id);
-	nm_clear_g_signal_handler (device, &priv->dcb_carrier_id);
+	priv->dcb_handle_carrier_changes = FALSE;
 
 	/* Tear down DCB/FCoE if it was enabled */
 	s_dcb = (NMSettingDcb *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_DCB);
@@ -1579,7 +1533,7 @@ update_connection (NMDevice *device, NMConnection *connection)
 }
 
 static void
-get_link_speed (NMDevice *device)
+link_speed_update (NMDevice *device)
 {
 	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
 	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
@@ -1591,16 +1545,28 @@ get_link_speed (NMDevice *device)
 		return;
 
 	priv->speed = speed;
-	_notify (self, PROP_SPEED);
-
 	_LOGD (LOGD_PLATFORM | LOGD_ETHER, "speed is now %d Mb/s", speed);
+	_notify (self, PROP_SPEED);
 }
 
 static void
 carrier_changed_notify (NMDevice *device, gboolean carrier)
 {
+	NMDeviceEthernet *self = NM_DEVICE_ETHERNET (device);
+	NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self);
+
+	if (priv->dcb_handle_carrier_changes) {
+		nm_assert (nm_device_get_state (device) == NM_DEVICE_STATE_CONFIG);
+
+		if (priv->dcb_timeout_id) {
+			_LOGD (LOGD_DCB, "carrier_changed() calling dcb_state()");
+			dcb_state (device, FALSE);
+		}
+	}
+
 	if (carrier)
-		get_link_speed (device);
+		link_speed_update (device);
+
 	NM_DEVICE_CLASS (nm_device_ethernet_parent_class)->carrier_changed_notify (device, carrier);
 }
 
@@ -1682,7 +1648,6 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->pppoe_wait_id);
 
 	nm_clear_g_source (&priv->dcb_timeout_id);
-	nm_clear_g_signal_handler (self, &priv->dcb_carrier_id);
 
 	G_OBJECT_CLASS (nm_device_ethernet_parent_class)->dispose (object);
 }
@@ -1810,8 +1775,24 @@ create_device (NMDeviceFactory *factory,
 	                                  NULL);
 }
 
+static gboolean
+match_connection (NMDeviceFactory *factory, NMConnection *connection)
+{
+	const char *type = nm_connection_get_connection_type (connection);
+	NMSettingPppoe *s_pppoe;
+
+	if (nm_streq (type, NM_SETTING_WIRED_SETTING_NAME))
+		return TRUE;
+
+	nm_assert (nm_streq (type, NM_SETTING_PPPOE_SETTING_NAME));
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+
+	return !nm_setting_pppoe_get_parent (s_pppoe);
+}
+
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (ETHERNET, Ethernet, ethernet,
 	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_ETHERNET)
 	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_PPPOE_SETTING_NAME),
 	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
 );
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index f512b8b2..97f011c5 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -30,6 +30,8 @@
 
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-setting-bluetooth.h"
 
 #define PLUGIN_PREFIX "libnm-device-plugin-"
 
@@ -55,13 +57,12 @@ nm_device_factory_emit_component_added (NMDeviceFactory *factory, GObject *compo
 	gboolean consumed = FALSE;
 
 	g_return_val_if_fail (NM_IS_DEVICE_FACTORY (factory), FALSE);
-	g_return_val_if_fail (G_IS_OBJECT (component), FALSE);
 
 	g_signal_emit (factory, signals[COMPONENT_ADDED], 0, component, &consumed);
 	return consumed;
 }
 
-void
+static void
 nm_device_factory_get_supported_types (NMDeviceFactory *factory,
                                        const NMLinkType **out_link_types,
                                        const char *const**out_setting_types)
@@ -91,56 +92,26 @@ nm_device_factory_create_device (NMDeviceFactory *factory,
                                  GError **error)
 {
 	NMDeviceFactoryClass *klass;
-	const NMLinkType *link_types = NULL;
-	const char *const*setting_types = NULL;
-	int i;
 	NMDevice *device;
 	gboolean ignore = FALSE;
 
 	g_return_val_if_fail (factory, NULL);
 	g_return_val_if_fail (iface && *iface, NULL);
-	g_return_val_if_fail (plink || connection, NULL);
-	g_return_val_if_fail (!plink || !connection, NULL);
-
-	nm_device_factory_get_supported_types (factory, &link_types, &setting_types);
-
-	NM_SET_OUT (out_ignore, FALSE);
-
 	if (plink) {
+		g_return_val_if_fail (!connection, NULL);
 		g_return_val_if_fail (strcmp (iface, plink->name) == 0, NULL);
-
-		for (i = 0; link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
-			if (plink->type == link_types[i])
-				break;
-		}
-
-		if (link_types[i] == NM_LINK_TYPE_UNKNOWN) {
-			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
-			             "Device factory %s does not support link type %s (%d)",
-			             G_OBJECT_TYPE_NAME (factory),
-			             plink->kind, plink->type);
-			return NULL;
-		}
-	} else if (connection) {
-		for (i = 0; setting_types && setting_types[i]; i++) {
-			if (nm_connection_is_type (connection, setting_types[i]))
-				break;
-		}
-
-		if (!setting_types[i]) {
-			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
-			             "Device factory %s does not support connection type %s",
-			             G_OBJECT_TYPE_NAME (factory),
-			             nm_connection_get_connection_type (connection));
-			return NULL;
-		}
-	}
+		nm_assert (factory == nm_device_factory_manager_find_factory_for_link_type (plink->type));
+	} else if (connection)
+		nm_assert (factory == nm_device_factory_manager_find_factory_for_connection (connection));
+	else
+		g_return_val_if_reached (NULL);
 
 	klass = NM_DEVICE_FACTORY_GET_CLASS (factory);
 	if (!klass->create_device) {
 		g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
 		             "Device factory %s cannot manage new devices",
 		             G_OBJECT_TYPE_NAME (factory));
+		NM_SET_OUT (out_ignore, FALSE);
 		return NULL;
 	}
 
@@ -253,51 +224,35 @@ _cleanup (void)
 	g_clear_pointer (&factories_by_setting, g_hash_table_unref);
 }
 
-static NMDeviceFactory *
-find_factory (const NMLinkType *needle_link_types,
-              const char *const*needle_setting_types)
-{
-	NMDeviceFactory *found;
-	guint i;
-
-	g_return_val_if_fail (factories_by_link, NULL);
-	g_return_val_if_fail (factories_by_setting, NULL);
-
-	/* NMLinkType search */
-	for (i = 0; needle_link_types && needle_link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
-		found = g_hash_table_lookup (factories_by_link, GUINT_TO_POINTER (needle_link_types[i]));
-		if (found)
-			return found;
-	}
-
-	/* NMSetting name search */
-	for (i = 0; needle_setting_types && needle_setting_types[i]; i++) {
-		found = g_hash_table_lookup (factories_by_setting, needle_setting_types[i]);
-		if (found)
-			return found;
-	}
-
-	return NULL;
-}
-
 NMDeviceFactory *
 nm_device_factory_manager_find_factory_for_link_type (NMLinkType link_type)
 {
-	const NMLinkType ltypes[2] = { link_type, NM_LINK_TYPE_NONE };
+	g_return_val_if_fail (factories_by_link, NULL);
 
-	if (link_type == NM_LINK_TYPE_UNKNOWN)
-		return NULL;
-	g_return_val_if_fail (link_type > NM_LINK_TYPE_UNKNOWN, NULL);
-	return find_factory (ltypes, NULL);
+	return g_hash_table_lookup (factories_by_link, GUINT_TO_POINTER (link_type));
 }
 
 NMDeviceFactory *
 nm_device_factory_manager_find_factory_for_connection (NMConnection *connection)
 {
-	const char *const stypes[2] = { nm_connection_get_connection_type (connection), NULL };
+	NMDeviceFactoryClass *klass;
+	NMDeviceFactory *factory;
+	const char *type;
+	GSList *list;
 
-	g_assert (stypes[0]);
-	return find_factory (NULL, stypes);
+	g_return_val_if_fail (factories_by_setting, NULL);
+
+	type = nm_connection_get_connection_type (connection);
+	list = g_hash_table_lookup (factories_by_setting, type);
+
+	for (; list; list = g_slist_next (list)) {
+		factory = list->data;
+		klass = NM_DEVICE_FACTORY_GET_CLASS (factory);
+		if (!klass->match_connection || klass->match_connection (factory, connection))
+			return factory;
+	}
+
+	return NULL;
 }
 
 void
@@ -318,9 +273,11 @@ nm_device_factory_manager_for_each_factory (NMDeviceFactoryManagerFactoryFunc ca
 
 	if (factories_by_setting) {
 		g_hash_table_iter_init (&iter, factories_by_setting);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &factory)) {
-			if (!g_slist_find (list, factory))
-				list = g_slist_prepend (list, factory);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &list_iter)) {
+			for (; list_iter; list_iter = g_slist_next (list_iter)) {
+				if (!g_slist_find (list, list_iter->data))
+					list = g_slist_prepend (list, list_iter->data);
+			}
 		}
 	}
 
@@ -332,36 +289,33 @@ nm_device_factory_manager_for_each_factory (NMDeviceFactoryManagerFactoryFunc ca
 
 static gboolean
 _add_factory (NMDeviceFactory *factory,
-              gboolean check_duplicates,
               const char *path,
               NMDeviceFactoryManagerFactoryFunc callback,
               gpointer user_data)
 {
-	NMDeviceFactory *found = NULL;
 	const NMLinkType *link_types = NULL;
 	const char *const*setting_types = NULL;
+	GSList *list, *list2;
 	int i;
 
 	g_return_val_if_fail (factories_by_link, FALSE);
 	g_return_val_if_fail (factories_by_setting, FALSE);
 
 	nm_device_factory_get_supported_types (factory, &link_types, &setting_types);
-	if (check_duplicates) {
-		found = find_factory (link_types, setting_types);
-		if (found) {
-			nm_log_warn (LOGD_PLATFORM, "Loading device plugin failed: multiple plugins "
-			             "for same type (using '%s' instead of '%s')",
-			             (char *) g_object_get_qdata (G_OBJECT (found), plugin_path_quark ()),
-			             path);
-			return FALSE;
-		}
-	}
 
 	g_object_set_qdata_full (G_OBJECT (factory), plugin_path_quark (), g_strdup (path), g_free);
 	for (i = 0; link_types && link_types[i] > NM_LINK_TYPE_UNKNOWN; i++)
 		g_hash_table_insert (factories_by_link, GUINT_TO_POINTER (link_types[i]), g_object_ref (factory));
-	for (i = 0; setting_types && setting_types[i]; i++)
-		g_hash_table_insert (factories_by_setting, (char *) setting_types[i], g_object_ref (factory));
+	for (i = 0; setting_types && setting_types[i]; i++) {
+		list = g_hash_table_lookup (factories_by_setting, (char *) setting_types[i]);
+		if (list) {
+			list2 = g_slist_append (list, g_object_ref (factory));
+			nm_assert (list == list2);
+		} else {
+			list = g_slist_append (list, g_object_ref (factory));
+			g_hash_table_insert (factories_by_setting, (char *) setting_types[i], list);
+		}
+	}
 
 	callback (factory, user_data);
 
@@ -377,7 +331,13 @@ _load_internal_factory (GType factory_gtype,
 	NMDeviceFactory *factory;
 
 	factory = (NMDeviceFactory *) g_object_new (factory_gtype, NULL);
-	_add_factory (factory, FALSE, "internal", callback, user_data);
+	_add_factory (factory, "internal", callback, user_data);
+}
+
+static void
+factories_list_unref (GSList *list)
+{
+	g_slist_free_full (list, g_object_unref);
 }
 
 void
@@ -392,7 +352,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 	g_return_if_fail (factories_by_setting == NULL);
 
 	factories_by_link = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
-	factories_by_setting = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+	factories_by_setting = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, (GDestroyNotify) factories_list_unref);
 
 #define _ADD_INTERNAL(get_type_fcn) \
 	G_STMT_START { \
@@ -409,6 +369,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 	_ADD_INTERNAL (nm_ip_tunnel_device_factory_get_type);
 	_ADD_INTERNAL (nm_macsec_device_factory_get_type);
 	_ADD_INTERNAL (nm_macvlan_device_factory_get_type);
+	_ADD_INTERNAL (nm_ppp_device_factory_get_type);
 	_ADD_INTERNAL (nm_tun_device_factory_get_type);
 	_ADD_INTERNAL (nm_veth_device_factory_get_type);
 	_ADD_INTERNAL (nm_vlan_device_factory_get_type);
@@ -452,7 +413,7 @@ nm_device_factory_manager_load_factories (NMDeviceFactoryManagerFactoryFunc call
 		}
 		g_clear_error (&error);
 
-		_add_factory (factory, TRUE, g_module_name (plugin), callback, user_data);
+		_add_factory (factory, g_module_name (plugin), callback, user_data);
 
 		g_object_unref (factory);
 	}
diff --git a/src/devices/nm-device-factory.h b/src/devices/nm-device-factory.h
index ff105da7..33b596e6 100644
--- a/src/devices/nm-device-factory.h
+++ b/src/devices/nm-device-factory.h
@@ -72,11 +72,19 @@ typedef struct {
 	void (*start)                 (NMDeviceFactory *factory);
 
 	/**
+	 * match_connection:
+	 * @connection: the #NMConnection
+	 *
+	 * Check if the factory supports the given connection.
+	 */
+	gboolean (*match_connection)  (NMDeviceFactory *factory, NMConnection *connection);
+
+	/**
 	 * get_connection_parent:
 	 * @factory: the #NMDeviceFactory
 	 * @connection: the #NMConnection to return the parent name for, if supported
 	 *
-	 * Given a connection, returns the a parent interface name, parent connection
+	 * Given a connection, returns the parent interface name, parent connection
 	 * UUID, or parent device permanent hardware address for @connection.
 	 *
 	 * Returns: the parent interface name, parent connection UUID, parent
@@ -140,11 +148,15 @@ typedef struct {
 	 * @factory: the #NMDeviceFactory
 	 * @component: a new component which existing devices may wish to claim
 	 *
-	 * The factory emits this signal when it finds a new component.  For example,
-	 * the WWAN factory may indicate that a new modem is available, which an
-	 * existing Bluetooth device may wish to claim.  If no device claims the
-	 * component, the plugin is allowed to create a new #NMDevice instance for
-	 * that component and emit the "device-added" signal.
+	 * The factory emits this signal when an appearance of some component
+	 * native to it could be interesting to some of the already existing devices.
+	 * The devices then indicate if they took interest in claiming the component.
+	 *
+	 * For example, the WWAN factory may indicate that a new modem is available,
+	 * which an existing Bluetooth device may wish to claim. It emits a signal
+	 * passing the modem instance around to see if any device claims it.
+	 * If no device claims the component, the plugin is allowed to create a new
+	 * #NMDevice instance for that component and emit the "device-added" signal.
 	 *
 	 * Returns: %TRUE if the component was claimed by a device, %FALSE if not
 	 */
@@ -174,10 +186,6 @@ typedef NMDeviceFactory * (*NMDeviceFactoryCreateFunc) (GError **error);
 
 /*****************************************************************************/
 
-void       nm_device_factory_get_supported_types (NMDeviceFactory *factory,
-                                                  const NMLinkType **out_link_types,
-                                                  const char *const**out_setting_types);
-
 const char *nm_device_factory_get_connection_parent (NMDeviceFactory *factory,
                                                      NMConnection *connection);
 
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 7e041270..09ad2855 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -269,13 +269,13 @@ create_and_realize (NMDevice *device,
 	}
 
 	if (!parent) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "InfiniBand partitions can not be created without a parent interface");
 		return FALSE;
 	}
 
 	if (!NM_IS_DEVICE_INFINIBAND (parent)) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "Parent interface %s must be an InfiniBand interface",
 		             nm_device_get_iface (parent));
 		return FALSE;
@@ -283,7 +283,7 @@ create_and_realize (NMDevice *device,
 
 	priv->parent_ifindex = nm_device_get_ifindex (parent);
 	if (priv->parent_ifindex <= 0) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "failed to get InfiniBand parent %s ifindex",
 		             nm_device_get_iface (parent));
 		return FALSE;
@@ -295,7 +295,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create InfiniBand P_Key interface '%s' for '%s': %s",
 		             nm_device_get_iface (device),
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -324,7 +324,7 @@ unrealize (NMDevice *device, GError **error)
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to remove InfiniBand P_Key interface '%s': %s",
 		             nm_device_get_iface (device),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 2f505ef4..af3cfe4c 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -442,40 +442,6 @@ update_connection (NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-match_parent (NMDevice *dev_parent, const char *setting_parent)
-{
-	g_return_val_if_fail (setting_parent, FALSE);
-
-	if (!dev_parent)
-		return FALSE;
-
-	if (nm_utils_is_uuid (setting_parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-		parent_req = nm_device_get_act_request (dev_parent);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (setting_parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (setting_parent, nm_device_get_ip_iface (dev_parent)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceIPTunnel *self = NM_DEVICE_IP_TUNNEL (device);
@@ -496,10 +462,8 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 	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) {
-			if (!match_parent (nm_device_parent_get_device (device), parent))
-				return FALSE;
-		}
+		if (parent && !nm_device_match_parent (device, parent))
+			return FALSE;
 
 		if (!address_equal_pp (priv->addr_family,
 		                       nm_setting_ip_tunnel_get_local (s_ip_tunnel),
@@ -647,7 +611,7 @@ create_and_realize (NMDevice *device,
 			             "Failed to create GRE interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string (plerr));
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
@@ -670,10 +634,10 @@ create_and_realize (NMDevice *device,
 		plerr = nm_platform_link_sit_add (nm_device_get_platform (device), iface, &lnk_sit, out_plink);
 		if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
 			g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
-					"Failed to create SIT interface '%s' for '%s': %s",
-					iface,
-					nm_connection_get_id (connection),
-					nm_platform_error_to_string (plerr));
+			             "Failed to create SIT interface '%s' for '%s': %s",
+			             iface,
+			             nm_connection_get_id (connection),
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
@@ -696,10 +660,10 @@ create_and_realize (NMDevice *device,
 		plerr = nm_platform_link_ipip_add (nm_device_get_platform (device), iface, &lnk_ipip, 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",
-					iface,
-					nm_connection_get_id (connection),
-					nm_platform_error_to_string (plerr));
+			             "Failed to create IPIP interface '%s' for '%s': %s",
+			             iface,
+			             nm_connection_get_id (connection),
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
@@ -728,7 +692,7 @@ create_and_realize (NMDevice *device,
 			             "Failed to create IPIP interface '%s' for '%s': %s",
 			             iface,
 			             nm_connection_get_id (connection),
-			             nm_platform_error_to_string (plerr));
+			             nm_platform_error_to_string_a (plerr));
 			return FALSE;
 		}
 		break;
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 8add3f6f..95587278 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -477,25 +477,20 @@ handle_auth_or_fail (NMDeviceMacsec *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
+	NMDeviceMacsecPrivate *priv;
 	const char *setting_name;
-	int tries_left;
 	NMConnection *applied_connection;
-	NMSettingsConnection *settings_connection;
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-	settings_connection = nm_act_request_get_settings_connection (req);
+	priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
 
-	tries_left = nm_settings_connection_get_autoconnect_retries (settings_connection);
-	if (tries_left == 0)
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
-	if (tries_left > 0)
-		nm_settings_connection_set_autoconnect_retries (settings_connection, tries_left - 1);
-
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
 	nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
 
+	applied_connection = nm_act_request_get_applied_connection (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (setting_name) {
 		macsec_secrets_get_secrets (self, setting_name,
@@ -692,7 +687,7 @@ create_and_realize (NMDevice *device,
 	g_assert (s_macsec);
 
 	if (!parent) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "MACsec devices can not be created without a parent interface");
 		return FALSE;
 	}
@@ -720,7 +715,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create macsec interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -739,21 +734,6 @@ link_changed (NMDevice *device,
 
 
 static void
-reset_autoconnect_retries (NMDevice *device)
-{
-	NMActRequest *req;
-	NMSettingsConnection *connection;
-
-	req = nm_device_get_act_request (device);
-	if (req) {
-		connection = nm_act_request_get_settings_connection (req);
-		g_return_if_fail (connection);
-		/* Reset autoconnect retries on success, failure, or when deactivating */
-		nm_settings_connection_reset_autoconnect_retries (connection);
-	}
-}
-
-static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -761,11 +741,6 @@ device_state_changed (NMDevice *device,
 {
 	if (new_state > NM_DEVICE_STATE_ACTIVATED)
 		macsec_secrets_cancel (NM_DEVICE_MACSEC (device));
-
-	if (   new_state == NM_DEVICE_STATE_ACTIVATED
-	    || new_state == NM_DEVICE_STATE_FAILED
-	    || new_state == NM_DEVICE_STATE_DISCONNECTED)
-		reset_autoconnect_retries (device);
 }
 
 /******************************************************************/
@@ -822,7 +797,7 @@ get_property (GObject *object, guint prop_id,
 }
 
 static void
-nm_device_macsec_init (NMDeviceMacsec * self)
+nm_device_macsec_init (NMDeviceMacsec *self)
 {
 }
 
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index cea2b984..2a461543 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -234,7 +234,7 @@ create_and_realize (NMDevice *device,
 	parent_ifindex = parent ? nm_device_get_ifindex (parent) : 0;
 
 	if (parent_ifindex <= 0) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "MACVLAN devices can not be created without a parent interface");
 		g_return_val_if_fail (!parent, FALSE);
 		return FALSE;
@@ -258,7 +258,7 @@ create_and_realize (NMDevice *device,
 		             lnk.tap ? "macvtap" : "macvlan",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -286,69 +286,6 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 
 /*****************************************************************************/
 
-
-static gboolean
-match_parent (NMDeviceMacvlan *self, const char *parent)
-{
-	NMDevice *parent_device;
-
-	g_return_val_if_fail (parent != NULL, FALSE);
-
-	parent_device = nm_device_parent_get_device (NM_DEVICE (self));
-	if (!parent_device)
-		return FALSE;
-
-	if (nm_utils_is_uuid (parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-
-		parent_req = nm_device_get_act_request (parent_device);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (parent, nm_device_get_ip_iface (parent_device)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
-match_hwaddr (NMDevice *device, NMConnection *connection, gboolean fail_if_no_hwaddr)
-{
-	NMSettingWired *s_wired;
-	NMDevice *parent_device;
-	const char *setting_mac;
-	const char *parent_mac;
-
-	s_wired = nm_connection_get_setting_wired (connection);
-	if (!s_wired)
-		return !fail_if_no_hwaddr;
-
-	setting_mac = nm_setting_wired_get_mac_address (s_wired);
-	if (!setting_mac)
-		return !fail_if_no_hwaddr;
-
-	parent_device = nm_device_parent_get_device (device);
-	if (!parent_device)
-		return !fail_if_no_hwaddr;
-
-	parent_mac = nm_device_get_permanent_hw_address (parent_device);
-	return parent_mac && nm_utils_hwaddr_matches (setting_mac, -1, parent_mac, -1);
-}
-
 static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
@@ -378,11 +315,11 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_macvlan_get_parent (s_macvlan);
 		if (parent) {
-			if (!match_parent (NM_DEVICE_MACVLAN (device), parent))
+			if (!nm_device_match_parent (device, parent))
 				return FALSE;
 		} else {
 			/* Parent could be a MAC address in an NMSettingWired */
-			if (!match_hwaddr (device, connection, TRUE))
+			if (!nm_device_match_hwaddr (device, connection, TRUE))
 				return FALSE;
 		}
 	}
@@ -419,7 +356,7 @@ complete_connection (NMDevice *device,
 	 * settings, then there's not enough information to complete the setting.
 	 */
 	if (   !nm_setting_macvlan_get_parent (s_macvlan)
-	    && !match_hwaddr (device, connection, TRUE)) {
+	    && !nm_device_match_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;
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
new file mode 100644
index 00000000..8b3968d5
--- /dev/null
+++ b/src/devices/nm-device-ppp.c
@@ -0,0 +1,350 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ppp.h"
+
+#include "nm-act-request.h"
+#include "nm-device-factory.h"
+#include "nm-device-private.h"
+#include "nm-manager.h"
+#include "nm-setting-pppoe.h"
+#include "platform/nm-platform.h"
+#include "ppp/nm-ppp-manager.h"
+#include "ppp/nm-ppp-manager-call.h"
+#include "ppp/nm-ppp-status.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.Ppp.h"
+
+#include "nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDevicePpp);
+
+/*****************************************************************************/
+
+typedef struct _NMDevicePppPrivate {
+	NMPPPManager *ppp_manager;
+	NMIP4Config  *pending_ip4_config;
+	char         *pending_ifname;
+} NMDevicePppPrivate;
+
+struct _NMDevicePpp {
+	NMDevice parent;
+	NMDevicePppPrivate _priv;
+};
+
+struct _NMDevicePppClass {
+	NMDeviceClass parent;
+};
+
+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)
+{
+	return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static void
+ppp_state_changed (NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+
+	switch (status) {
+	case NM_PPP_STATUS_DISCONNECT:
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_DISCONNECT);
+		break;
+	case NM_PPP_STATUS_DEAD:
+		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
+		break;
+	case NM_PPP_STATUS_RUNNING:
+		nm_device_activate_schedule_stage3_ip_config_start (device);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+ppp_ip4_config (NMPPPManager *ppp_manager,
+                const char *iface,
+                NMIP4Config *config,
+                gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	gboolean renamed;
+
+	_LOGT (LOGD_DEVICE | LOGD_PPP, "received IPv4 config from pppd");
+
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+		if (nm_device_activate_ip4_state_in_conf (device)) {
+			if (!nm_device_take_over_link (device, iface, &renamed)) {
+				nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
+				                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+				return;
+			}
+			if (renamed)
+				nm_manager_remove_device (nm_manager_get (), iface, NM_DEVICE_TYPE_PPP);
+
+			nm_device_activate_schedule_ip4_config_result (device, config);
+			return;
+		}
+	} else {
+		if (priv->pending_ip4_config)
+			g_object_unref (priv->pending_ip4_config);
+		priv->pending_ip4_config = g_object_ref (config);
+		g_free (priv->pending_ifname);
+		priv->pending_ifname = g_strdup (iface);
+	}
+}
+
+static NMActStageReturn
+act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	NMSettingPppoe *s_pppoe;
+	NMActRequest *req;
+	GError *error = NULL;
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
+
+	s_pppoe = (NMSettingPppoe *) nm_device_get_applied_setting ((NMDevice *) self, NM_TYPE_SETTING_PPPOE);
+	g_return_val_if_fail (s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
+
+	g_clear_object (&priv->pending_ip4_config);
+	nm_clear_g_free (&priv->pending_ifname);
+
+	priv->ppp_manager = nm_ppp_manager_create (nm_setting_pppoe_get_parent (s_pppoe), &error);
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     nm_device_get_route_table (device, AF_INET, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET),
+		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_metric (device, AF_INET6));
+	}
+
+	if (   !priv->ppp_manager
+	    || !nm_ppp_manager_start (priv->ppp_manager, req,
+	                              nm_setting_pppoe_get_username (s_pppoe),
+	                              30, 0, &error)) {
+		_LOGW (LOGD_DEVICE | LOGD_PPP, "PPPoE failed to start: %s", error->message);
+		g_error_free (error);
+
+		g_clear_object (&priv->ppp_manager);
+
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
+	                  G_CALLBACK (ppp_state_changed),
+	                  self);
+	g_signal_connect (priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
+	                  G_CALLBACK (ppp_ip4_config),
+	                  self);
+
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+static NMActStageReturn
+act_stage3_ip4_config_start (NMDevice *device,
+                             NMIP4Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+	gboolean renamed;
+
+	if (priv->pending_ip4_config) {
+		if  (!nm_device_take_over_link (device, priv->pending_ifname, &renamed))
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		if (renamed)
+			nm_manager_remove_device (nm_manager_get (), priv->pending_ifname, NM_DEVICE_TYPE_PPP);
+		if (out_config)
+			*out_config = g_steal_pointer (&priv->pending_ip4_config);
+		else
+			g_clear_object (&priv->pending_ip4_config);
+		return NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	/* Wait IPCP termination */
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	int parent_ifindex;
+
+	if (!parent) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+		             "PPP devices can not be created without a parent interface");
+		return FALSE;
+	}
+
+	parent_ifindex = nm_device_get_ifindex (parent);
+	g_warn_if_fail (parent_ifindex > 0);
+
+	nm_device_parent_set_ifindex (device, parent_ifindex);
+
+	/* The interface is created later */
+
+	return TRUE;
+}
+
+static void
+deactivate (NMDevice *device)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_stop_sync (priv->ppp_manager);
+		g_clear_object (&priv->ppp_manager);
+	}
+}
+
+static void
+nm_device_ppp_init (NMDevicePpp *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDevicePpp *self = NM_DEVICE_PPP (object);
+	NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE (self);
+
+	g_clear_object (&priv->pending_ip4_config);
+	nm_clear_g_free (&priv->pending_ifname);
+
+	G_OBJECT_CLASS (nm_device_ppp_parent_class)->dispose (object);
+}
+
+static void
+nm_device_ppp_class_init (NMDevicePppClass *klass)
+{
+	GObjectClass *object_class = G_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)
+
+	object_class->dispose = dispose;
+
+	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;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_PPP_SKELETON,
+	                                        NULL);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_PPP_DEVICE_FACTORY (nm_ppp_device_factory_get_type ())
+#define NM_PPP_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_PPP_DEVICE_FACTORY, NMPppDeviceFactory))
+
+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_PPP,
+	                                  NM_DEVICE_IFACE, iface,
+	                                  NM_DEVICE_TYPE_DESC, "Ppp",
+	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_PPP,
+	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_PPP,
+	                                  NULL);
+}
+
+static gboolean
+match_connection (NMDeviceFactory *factory, NMConnection *connection)
+{
+	NMSettingPppoe *s_pppoe;
+
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+	nm_assert (s_pppoe);
+
+	return !!nm_setting_pppoe_get_parent (s_pppoe);
+}
+
+static const char *
+get_connection_parent (NMDeviceFactory *factory, NMConnection *connection)
+{
+	NMSettingPppoe *s_pppoe;
+
+	nm_assert (nm_connection_is_type (connection, NM_SETTING_PPPOE_SETTING_NAME));
+
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+	nm_assert (s_pppoe);
+
+	return nm_setting_pppoe_get_parent (s_pppoe);
+}
+
+static char *
+get_connection_iface (NMDeviceFactory *factory,
+                      NMConnection *connection,
+                      const char *parent_iface)
+{
+	nm_assert (nm_connection_is_type (connection, NM_SETTING_PPPOE_SETTING_NAME));
+
+	if (!parent_iface)
+		return NULL;
+
+	return g_strdup (nm_connection_get_interface_name (connection));
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL (PPP, Ppp, ppp,
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_PPP)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_PPPOE_SETTING_NAME),
+	factory_class->get_connection_parent = get_connection_parent;
+	factory_class->get_connection_iface = get_connection_iface;
+	factory_class->create_device = create_device;
+	factory_class->match_connection = match_connection;
+);
diff --git a/src/devices/nm-device-ppp.h b/src/devices/nm-device-ppp.h
new file mode 100644
index 00000000..aaa18b9b
--- /dev/null
+++ b/src/devices/nm-device-ppp.h
@@ -0,0 +1,30 @@
+/* -*- 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_PPP_H__
+#define __NETWORKMANAGER_DEVICE_PPP_H__
+
+#define NM_TYPE_DEVICE_PPP              (nm_device_ppp_get_type ())
+#define NM_DEVICE_PPP(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_PPP, NMDevicePpp))
+#define NM_DEVICE_PPP_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_PPP, NMDevicePppClass))
+#define NM_IS_DEVICE_PPP(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_PPP))
+#define NM_IS_DEVICE_PPP_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_PPP))
+#define NM_DEVICE_PPP_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_PPP, NMDevicePppClass))
+
+typedef struct _NMDevicePpp NMDevicePpp;
+typedef struct _NMDevicePppClass NMDevicePppClass;
+
+GType nm_device_ppp_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_PPP_H__ */
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 9eccafdc..f1486c54 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -57,6 +57,8 @@ gboolean nm_device_bring_up (NMDevice *self, gboolean wait, gboolean *no_firmwar
 
 void nm_device_take_down (NMDevice *self, gboolean block);
 
+gboolean nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed);
+
 gboolean nm_device_hw_addr_set (NMDevice *device,
                                 const char *addr,
                                 const char *detail,
@@ -83,7 +85,6 @@ gboolean nm_device_activate_ip6_state_in_conf (NMDevice *device);
 gboolean nm_device_activate_ip6_state_in_wait (NMDevice *device);
 gboolean nm_device_activate_ip6_state_done (NMDevice *device);
 
-void nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout);
 void nm_device_set_dhcp_anycast_address (NMDevice *device, const char *addr);
 
 gboolean nm_device_dhcp4_renew (NMDevice *device, gboolean release);
@@ -91,10 +92,6 @@ gboolean nm_device_dhcp6_renew (NMDevice *device, gboolean release);
 
 void nm_device_recheck_available_connections (NMDevice *device);
 
-gboolean nm_device_get_enslaved (NMDevice *device);
-
-NMDevice *nm_device_master_get_slave_by_ifindex (NMDevice *dev, int ifindex);
-
 void nm_device_master_check_slave_physical_port (NMDevice *self, NMDevice *slave,
                                                  NMLogDomain log_domain);
 
@@ -121,6 +118,8 @@ gint64 nm_device_get_configured_mtu_from_connection_default (NMDevice *self,
 
 guint32 nm_device_get_configured_mtu_for_wired (NMDevice *self, gboolean *out_is_user_config);
 
+void nm_device_commit_mtu (NMDevice *self);
+
 /*****************************************************************************/
 
 #define NM_DEVICE_CLASS_DECLARE_TYPES(klass, conn_type, ...) \
@@ -135,4 +134,9 @@ gboolean _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setti
 #define nm_device_hash_check_invalid_keys(hash, setting_name, error, ...) \
 	_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);
+
 #endif	/* NM_DEVICE_PRIVATE_H */
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index b4af4416..a7d7c0bf 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -244,7 +244,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create TUN/TAP interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index 11916c59..a8c4bcc8 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -37,12 +37,8 @@ _LOG_DECLARE_SELF(NMDeviceVeth);
 
 /*****************************************************************************/
 
-typedef struct {
-} NMDeviceVethPrivate;
-
 struct _NMDeviceVeth {
 	NMDeviceEthernet parent;
-	NMDeviceVethPrivate _priv;
 };
 
 struct _NMDeviceVethClass {
@@ -57,8 +53,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceVeth,
 
 G_DEFINE_TYPE (NMDeviceVeth, nm_device_veth, NM_TYPE_DEVICE_ETHERNET)
 
-#define NM_DEVICE_VETH_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDeviceVeth, NM_IS_DEVICE_VETH)
-
 /*****************************************************************************/
 
 static void
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index a74da8f2..e30dae74 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -51,6 +51,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceVlan,
 typedef struct {
 	gulong parent_state_id;
 	gulong parent_hwaddr_id;
+	gulong parent_mtu_id;
 	guint vlan_id;
 } NMDeviceVlanPrivate;
 
@@ -86,6 +87,17 @@ parent_state_changed (NMDevice *parent,
 }
 
 static void
+parent_mtu_maybe_changed (NMDevice *parent,
+                          GParamSpec *pspec,
+                          gpointer user_data)
+{
+	/* the MTU of a VLAN device is limited by the parent's MTU.
+	 *
+	 * When the parent's MTU changes, try to re-set the MTU. */
+	nm_device_commit_mtu (user_data);
+}
+
+static void
 parent_hwaddr_maybe_changed (NMDevice *parent,
                              GParamSpec *pspec,
                              gpointer user_data)
@@ -143,6 +155,7 @@ parent_changed_notify (NMDevice *device,
 	 *  parent_changed_notify(). */
 	nm_clear_g_signal_handler (old_parent, &priv->parent_state_id);
 	nm_clear_g_signal_handler (old_parent, &priv->parent_hwaddr_id);
+	nm_clear_g_signal_handler (old_parent, &priv->parent_mtu_id);
 
 	if (new_parent) {
 		priv->parent_state_id = g_signal_connect (new_parent,
@@ -154,6 +167,10 @@ parent_changed_notify (NMDevice *device,
 		                                           G_CALLBACK (parent_hwaddr_maybe_changed), device);
 		parent_hwaddr_maybe_changed (new_parent, NULL, self);
 
+		priv->parent_mtu_id = g_signal_connect (new_parent, "notify::" NM_DEVICE_MTU,
+		                                        G_CALLBACK (parent_mtu_maybe_changed), device);
+		parent_mtu_maybe_changed (new_parent, NULL, self);
+
 		/* Set parent-dependent unmanaged flag */
 		nm_device_set_unmanaged_by_flags (device,
 		                                  NM_UNMANAGED_PARENT,
@@ -231,11 +248,20 @@ create_and_realize (NMDevice *device,
 	g_assert (s_vlan);
 
 	if (!parent) {
-		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
 		             "VLAN devices can not be created without a parent interface");
 		return FALSE;
 	}
 
+	parent_ifindex = nm_device_get_ifindex (parent);
+	if (parent_ifindex <= 0) {
+		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+		             "cannot retrieve ifindex of interface %s (%s)",
+		             nm_device_get_iface (parent),
+		             nm_device_get_type_desc (parent));
+		return FALSE;
+	}
+
 	if (!nm_device_supports_vlans (parent)) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 		             "no support for VLANs on interface %s of type %s",
@@ -244,9 +270,6 @@ create_and_realize (NMDevice *device,
 		return FALSE;
 	}
 
-	parent_ifindex = nm_device_get_ifindex (parent);
-	g_warn_if_fail (parent_ifindex > 0);
-
 	vlan_id = nm_setting_vlan_get_id (s_vlan);
 
 	plerr = nm_platform_link_vlan_add (nm_device_get_platform (device),
@@ -260,7 +283,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create VLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -309,68 +332,6 @@ is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 /*****************************************************************************/
 
 static gboolean
-match_parent (NMDeviceVlan *self, const char *parent)
-{
-	NMDevice *parent_device;
-
-	g_return_val_if_fail (parent != NULL, FALSE);
-
-	parent_device = nm_device_parent_get_device (NM_DEVICE (self));
-	if (!parent_device)
-		return FALSE;
-
-	if (nm_utils_is_uuid (parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-
-		parent_req = nm_device_get_act_request (parent_device);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (parent, nm_device_get_ip_iface (parent_device)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
-match_hwaddr (NMDevice *device, NMConnection *connection, gboolean fail_if_no_hwaddr)
-{
-	NMSettingWired *s_wired;
-	NMDevice *parent_device;
-	const char *setting_mac;
-	const char *parent_mac;
-
-	s_wired = nm_connection_get_setting_wired (connection);
-	if (!s_wired)
-		return !fail_if_no_hwaddr;
-
-	setting_mac = nm_setting_wired_get_mac_address (s_wired);
-	if (!setting_mac)
-		return !fail_if_no_hwaddr;
-
-	parent_device = nm_device_parent_get_device (device);
-	if (!parent_device)
-		return !fail_if_no_hwaddr;
-
-	parent_mac = nm_device_get_permanent_hw_address (parent_device);
-	return parent_mac && nm_utils_hwaddr_matches (setting_mac, -1, parent_mac, -1);
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE ((NMDeviceVlan *) device);
@@ -392,11 +353,11 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		/* Check parent interface; could be an interface name or a UUID */
 		parent = nm_setting_vlan_get_parent (s_vlan);
 		if (parent) {
-			if (!match_parent (NM_DEVICE_VLAN (device), parent))
+			if (!nm_device_match_parent (device, parent))
 				return FALSE;
 		} else {
 			/* Parent could be a MAC address in an NMSettingWired */
-			if (!match_hwaddr (device, connection, TRUE))
+			if (!nm_device_match_hwaddr (device, connection, TRUE))
 				return FALSE;
 		}
 	}
@@ -445,7 +406,7 @@ complete_connection (NMDevice *device,
 	 * settings, then there's not enough information to complete the setting.
 	 */
 	if (   !nm_setting_vlan_get_parent (s_vlan)
-	    && !match_hwaddr (device, connection, TRUE)) {
+	    && !nm_device_match_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;
@@ -538,8 +499,10 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	/* Change MAC address to parent's one if needed */
 	parent_device = nm_device_parent_get_device (device);
-	if (parent_device)
+	if (parent_device) {
 		parent_hwaddr_maybe_changed (parent_device, NULL, device);
+		parent_mtu_maybe_changed (parent_device, NULL, device);
+	}
 
 	s_vlan = (NMSettingVlan *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_VLAN);
 	if (s_vlan) {
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index d0b88874..d9efe840 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -223,7 +223,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create VXLAN interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -231,43 +231,6 @@ create_and_realize (NMDevice *device,
 }
 
 static gboolean
-match_parent (NMDeviceVxlan *self, const char *parent)
-{
-	NMDevice *parent_device;
-
-	g_return_val_if_fail (parent != NULL, FALSE);
-
-	parent_device = nm_device_parent_get_device (NM_DEVICE (self));
-	if (!parent_device)
-		return FALSE;
-
-	if (nm_utils_is_uuid (parent)) {
-		NMActRequest *parent_req;
-		NMConnection *parent_connection;
-
-		/* If the parent is a UUID, the connection matches if our parent
-		 * device has that connection activated.
-		 */
-		parent_req = nm_device_get_act_request (parent_device);
-		if (!parent_req)
-			return FALSE;
-
-		parent_connection = nm_active_connection_get_applied_connection (NM_ACTIVE_CONNECTION (parent_req));
-		if (!parent_connection)
-			return FALSE;
-
-		if (g_strcmp0 (parent, nm_connection_get_uuid (parent_connection)) != 0)
-			return FALSE;
-	} else {
-		/* interface name */
-		if (g_strcmp0 (parent, nm_device_get_ip_iface (parent_device)) != 0)
-			return FALSE;
-	}
-
-	return TRUE;
-}
-
-static gboolean
 address_matches (const char *str, in_addr_t addr4, struct in6_addr *addr6)
 {
 	in_addr_t new_addr4 = 0;
@@ -302,8 +265,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 
 	if (nm_device_is_real (device)) {
 		parent = nm_setting_vxlan_get_parent (s_vxlan);
-		if (   parent
-		    && !match_parent (NM_DEVICE_VXLAN (device), parent))
+		if (parent && !nm_device_match_parent (device, parent))
 			return FALSE;
 
 		if (priv->props.id != nm_setting_vxlan_get_id (s_vxlan))
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index bacbfb33..e979b875 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -34,12 +34,16 @@
 #include <arpa/inet.h>
 #include <fcntl.h>
 #include <linux/if_addr.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
 
 #include "nm-common-macros.h"
 #include "nm-device-private.h"
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
 #include "dhcp/nm-dhcp-manager.h"
@@ -59,16 +63,16 @@
 #include "nm-netns.h"
 #include "nm-dispatcher.h"
 #include "nm-config.h"
+#include "nm-utils/c-list.h"
 #include "dns/nm-dns-manager.h"
 #include "nm-core-internal.h"
-#include "nm-default-route-manager.h"
-#include "nm-route-manager.h"
 #include "systemd/nm-sd.h"
 #include "nm-lldp-listener.h"
 #include "nm-audit-manager.h"
 #include "nm-arping-manager.h"
 #include "nm-connectivity.h"
 #include "nm-dbus-interface.h"
+#include "nm-device-vlan.h"
 
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF (NMDevice);
@@ -82,6 +86,13 @@ _LOG_DECLARE_SELF (NMDevice);
 #define DHCP_NUM_TRIES_MAX     3
 #define DEFAULT_AUTOCONNECT    TRUE
 
+#define CARRIER_WAIT_TIME_MS 5000
+#define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
+
+#define NM_DEVICE_AUTH_RETRIES_UNSET    -1
+#define NM_DEVICE_AUTH_RETRIES_INFINITY -2
+#define NM_DEVICE_AUTH_RETRIES_DEFAULT  3
+
 /*****************************************************************************/
 
 typedef void (*ActivationHandleFunc) (NMDevice *self);
@@ -106,6 +117,7 @@ typedef enum {
 } IpState;
 
 typedef struct {
+	CList lst_slave;
 	NMDevice *slave;
 	gulong watch_id;
 	bool slave_is_enslaved;
@@ -186,7 +198,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDevice,
 	PROP_IFINDEX,
 	PROP_AVAILABLE_CONNECTIONS,
 	PROP_PHYSICAL_PORT_ID,
-	PROP_IS_MASTER,
 	PROP_MASTER,
 	PROP_PARENT,
 	PROP_HW_ADDRESS,
@@ -231,6 +242,8 @@ typedef struct _NMDevicePrivate {
 
 	int parent_ifindex;
 
+	int auth_retries;
+
 	union {
 		const guint8 hw_addr_len; /* read-only */
 		guint8 hw_addr_len_;
@@ -312,6 +325,17 @@ typedef struct _NMDevicePrivate {
 	guint32 mtu_initial;
 	guint32 ip6_mtu_initial;
 
+	guint32         v4_route_table;
+	guint32         v6_route_table;
+
+	/* when carrier goes away, we give a grace period of CARRIER_WAIT_TIME_MS
+	 * until taking action.
+	 *
+	 * When changing MTU, the device might take longer then that. So, whenever
+	 * NM changes the MTU it sets @carrier_wait_until_ms to CARRIER_WAIT_TIME_AFTER_MTU_MS
+	 * in the future. This is used to extend the grace period in this particular case. */
+	gint64          carrier_wait_until_ms;
+
 	bool            carrier:1;
 	bool            ignore_carrier:1;
 
@@ -322,10 +346,15 @@ typedef struct _NMDevicePrivate {
 	bool            v4_commit_first_time:1;
 	bool            v6_commit_first_time:1;
 
+	bool            default_route_metric_penalty_ip4_has:1;
+	bool            default_route_metric_penalty_ip6_has:1;
+
 	NMDeviceSysIfaceState sys_iface_state:2;
 
+	bool            v4_route_table_initalized:1;
+	bool            v6_route_table_initalized:1;
+
 	/* Generic DHCP stuff */
-	guint32         dhcp_timeout;
 	char *          dhcp_anycast_address;
 
 	char *          current_stable_id;
@@ -346,14 +375,7 @@ typedef struct _NMDevicePrivate {
 	NMIP4Config *   ext_ip4_config; /* Stuff added outside NM */
 	NMIP4Config *   wwan_ip4_config; /* WWAN configuration */
 	GSList *        vpn4_configs;   /* VPNs which use this device */
-	struct {
-		bool v4_has;
-		bool v4_is_assumed;
-		bool v6_has;
-		bool v6_is_assumed;
-		NMPlatformIP4Route v4;
-		NMPlatformIP6Route v6;
-	} default_route;
+
 	bool v4_has_shadowed_routes;
 	const char *ip4_rp_filter;
 
@@ -390,6 +412,7 @@ typedef struct _NMDevicePrivate {
 	/* IPv4LL stuff */
 	sd_ipv4ll *    ipv4ll;
 	guint          ipv4ll_timeout;
+	guint          rt6_temporary_not_available_id;
 
 	/* IPv4 DAD stuff */
 	struct {
@@ -411,6 +434,8 @@ typedef struct _NMDevicePrivate {
 	bool           nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
 	NMIP6Config *  dad6_ip6_config;
 
+	GHashTable *   rt6_temporary_not_available;
+
 	NMNDisc *      ndisc;
 	gulong         ndisc_changed_id;
 	gulong         ndisc_timeout_id;
@@ -452,8 +477,7 @@ typedef struct _NMDevicePrivate {
 	gulong          master_ready_id;
 
 	/* slave management */
-	bool            is_master;
-	GSList *        slaves;    /* list of SlaveInfo */
+	CList           slaves;    /* list of SlaveInfo */
 
 	NMMetered       metered;
 
@@ -485,19 +509,18 @@ G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_EXPORTED_OBJECT)
 
 static void nm_device_set_proxy_config (NMDevice *self, const char *pac_url);
 
+static gboolean update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolean intersect_configs);
+
 static gboolean nm_device_set_ip4_config (NMDevice *self,
                                           NMIP4Config *config,
-                                          guint32 default_route_metric,
                                           gboolean commit,
-                                          gboolean routes_full_sync);
+                                          GPtrArray *ip4_dev_route_blacklist);
 static gboolean ip4_config_merge_and_apply (NMDevice *self,
-                                            NMIP4Config *config,
                                             gboolean commit);
 
 static gboolean nm_device_set_ip6_config (NMDevice *self,
                                           NMIP6Config *config,
-                                          gboolean commit,
-                                          gboolean routes_full_sync);
+                                          gboolean commit);
 static gboolean ip6_config_merge_and_apply (NMDevice *self,
                                             gboolean commit);
 
@@ -514,7 +537,7 @@ static void nm_device_set_autoconnect_both (NMDevice *self, gboolean autoconnect
 static void nm_device_set_autoconnect_full (NMDevice *self, int autoconnect_intern, int autoconnect_user);
 
 static const char *_activation_func_to_string (ActivationHandleFunc func);
-static void activation_source_handle_cb (NMDevice *self, int family);
+static void activation_source_handle_cb (NMDevice *self, int addr_family);
 
 static void _set_state_full (NMDevice *self,
                              NMDeviceState state,
@@ -525,7 +548,7 @@ 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 gboolean ip_config_valid (NMDeviceState state);
-static NMActStageReturn dhcp4_start (NMDevice *self, NMConnection *connection);
+static NMActStageReturn dhcp4_start (NMDevice *self);
 static gboolean dhcp6_start (NMDevice *self, gboolean wait_for_ll);
 static void nm_device_start_ip_check (NMDevice *self);
 static void realize_start_setup (NMDevice *self,
@@ -534,8 +557,9 @@ static void realize_start_setup (NMDevice *self,
                                  const char *assume_state_connection_uuid,
                                  gboolean set_nm_owned,
                                  NMUnmanFlagOp unmanaged_user_explicit);
+static void _set_mtu (NMDevice *self, guint32 mtu);
 static void _commit_mtu (NMDevice *self, const NMIP4Config *config);
-static void dhcp_schedule_restart (NMDevice *self, int family, const char *reason);
+static void dhcp_schedule_restart (NMDevice *self, int addr_family, const char *reason);
 static void _cancel_activation (NMDevice *self);
 
 /*****************************************************************************/
@@ -628,6 +652,7 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_reason_to_string, NMDeviceStateReason,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_NEW_ACTIVATION,                 "new-activation"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PARENT_CHANGED,                 "parent-changed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,         "parent-managed-changed"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_OVSDB_FAILED,                   "ovsdb-failed"),
 );
 
 #define reason_to_string(reason) \
@@ -647,12 +672,32 @@ nm_device_get_netns (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->netns;
 }
 
+NMDedupMultiIndex *
+nm_device_get_multi_index (NMDevice *self)
+{
+	return nm_netns_get_multi_idx (nm_device_get_netns (self));
+}
+
 NMPlatform *
 nm_device_get_platform (NMDevice *self)
 {
 	return nm_netns_get_platform (nm_device_get_netns (self));
 }
 
+static NMIP4Config *
+_ip4_config_new (NMDevice *self)
+{
+	return nm_ip4_config_new (nm_device_get_multi_index (self),
+	                          nm_device_get_ip_ifindex (self));
+}
+
+static NMIP6Config *
+_ip6_config_new (NMDevice *self)
+{
+	return nm_ip6_config_new (nm_device_get_multi_index (self),
+	                          nm_device_get_ip_ifindex (self));
+}
+
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_sys_iface_state_to_str, NMDeviceSysIfaceState,
@@ -714,6 +759,25 @@ nm_device_sys_iface_state_set (NMDevice *self,
 	nm_assert (priv->sys_iface_state == sys_iface_state);
 }
 
+static void
+_active_connection_set_state_flags_full (NMDevice *self,
+                                         NMActivationStateFlags flags,
+                                         NMActivationStateFlags mask)
+{
+	NMActiveConnection *ac;
+
+	ac = NM_ACTIVE_CONNECTION (nm_device_get_act_request (self));
+	if (ac)
+		nm_active_connection_set_state_flags_full (ac, flags, mask);
+}
+
+static void
+_active_connection_set_state_flags (NMDevice *self,
+                                    NMActivationStateFlags flags)
+{
+	_active_connection_set_state_flags_full (self, flags, flags);
+}
+
 /*****************************************************************************/
 
 void
@@ -796,26 +860,35 @@ nm_device_ipv4_sysctl_set (NMDevice *self, const char *property, const char *val
 	NMPlatform *platform = nm_device_get_platform (self);
 	gs_free char *value_to_free = NULL;
 	const char *value_to_set;
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	if (!nm_device_get_ip_ifindex (self))
+		return FALSE;
 
 	if (value) {
 		value_to_set = value;
 	} else {
 		/* Set to a default value when we've got a NULL @value. */
 		value_to_free = nm_platform_sysctl_get (platform,
-		                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip4_property_path ("default", property)));
+		                                        NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, "default", property)));
 		value_to_set = value_to_free;
 	}
 
 	return nm_platform_sysctl_set (platform,
-	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip4_property_path (nm_device_get_ip_iface (self), property)),
+	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
 	                               value_to_set);
 }
 
 static guint32
 nm_device_ipv4_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
 {
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	if (!nm_device_get_ip_ifindex (self))
+		return fallback;
+
 	return nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip4_property_path (nm_device_get_ip_iface (self), property)),
+	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, buf, nm_device_get_ip_iface (self), property)),
 	                                           10,
 	                                           0,
 	                                           G_MAXUINT32,
@@ -825,14 +898,24 @@ nm_device_ipv4_sysctl_get_uint32 (NMDevice *self, const char *property, guint32
 gboolean
 nm_device_ipv6_sysctl_set (NMDevice *self, const char *property, const char *value)
 {
-	return nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property)), value);
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	if (!nm_device_get_ip_ifindex (self))
+		return FALSE;
+
+	return nm_platform_sysctl_set (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)), value);
 }
 
 static guint32
 nm_device_ipv6_sysctl_get_uint32 (NMDevice *self, const char *property, guint32 fallback)
 {
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+	if (!nm_device_get_ip_ifindex (self))
+		return fallback;
+
 	return nm_platform_sysctl_get_int_checked (nm_device_get_platform (self),
-	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), property)),
+	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), property)),
 	                                           10,
 	                                           0,
 	                                           G_MAXUINT32,
@@ -949,14 +1032,29 @@ _set_ip_state (NMDevice *self, int addr_family, IpState new_state)
 	IpState *p;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert_addr_family (addr_family);
 
-	p = addr_family == AF_INET ? &priv->ip4_state_ : &priv->ip6_state_;
+	p =   (addr_family == AF_INET)
+	    ? &priv->ip4_state_
+        : &priv->ip6_state_;
 
 	if (*p != new_state) {
-		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)", addr_family == AF_INET ? '4' : '6',
-		       (int) new_state, _ip_state_to_string (new_state));
+		_LOGT (LOGD_DEVICE, "ip%c-state: set to %d (%s)",
+		       nm_utils_addr_family_to_char (addr_family),
+		       (int) new_state,
+		       _ip_state_to_string (new_state));
 		*p = new_state;
+
+		if (new_state == IP_DONE) {
+			/* we only set the IPx_READY flag once we reach IP_DONE state. We don't
+			 * ever clear it, even if we later enter IP_FAIL state.
+			 *
+			 * This is not documented/guaranteed behavior, but seems to make sense for now. */
+			_active_connection_set_state_flags (self,
+			                                    addr_family == AF_INET
+			                                      ? NM_ACTIVATION_STATE_FLAG_IP4_READY
+			                                      : NM_ACTIVATION_STATE_FLAG_IP6_READY);
+		}
 	}
 }
 
@@ -978,6 +1076,48 @@ nm_device_get_iface (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->iface;
 }
 
+gboolean
+nm_device_take_over_link (NMDevice *self, const char *ifname, gboolean *renamed)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const NMPlatformLink *plink;
+	NMPlatform *platform;
+	gboolean up, success = TRUE;
+	int ifindex;
+
+	g_return_val_if_fail (priv->ifindex <= 0, FALSE);
+	g_return_val_if_fail (ifname, FALSE);
+
+	NM_SET_OUT (renamed, FALSE);
+
+	platform = nm_device_get_platform (self);
+	plink = nm_platform_link_get_by_ifname (platform, ifname);
+	if (!plink)
+		return FALSE;
+
+	ifindex = plink->ifindex;
+
+	if (!nm_streq (ifname, nm_device_get_iface (self))) {
+		up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
+
+		/* Rename the link to the device ifname */
+		if (up)
+			nm_platform_link_set_down (platform, ifindex);
+		success = nm_platform_link_set_name (platform, ifindex, nm_device_get_iface (self));
+		if (up)
+			nm_platform_link_set_up (platform, ifindex, NULL);
+
+		NM_SET_OUT (renamed, success);
+	}
+
+	if (success) {
+		priv->ifindex = ifindex;
+		_notify (self, PROP_IFINDEX);
+	}
+
+	return success;
+}
+
 int
 nm_device_get_ifindex (NMDevice *self)
 {
@@ -1091,7 +1231,8 @@ nm_device_set_ip_iface (NMDevice *self, const char *iface)
 	}
 
 	if (priv->ip_ifindex > 0) {
-		if (nm_platform_check_support_user_ipv6ll (nm_device_get_platform (self)))
+		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
+		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
 			nm_platform_link_set_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ip_ifindex, TRUE);
 
 		if (!nm_platform_link_is_up (nm_device_get_platform (self), priv->ip_ifindex))
@@ -1478,23 +1619,14 @@ nm_device_get_metered (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->metered;
 }
 
-/**
- * nm_device_get_priority():
- * @self: the #NMDevice
- *
- * Returns: the device's routing priority.  Lower numbers means a "better"
- *  device, eg higher priority.
- */
-int
-nm_device_get_priority (NMDevice *self)
+static guint32
+_get_route_metric_default (NMDevice *self)
 {
-	g_return_val_if_fail (NM_IS_DEVICE (self), 1000);
-
 	/* Device 'priority' is used for the default route-metric and is based on
 	 * the device type. The settings ipv4.route-metric and ipv6.route-metric
 	 * can overwrite this default.
 	 *
-	 * Currently for both IPv4 and IPv6 we use the same default values.
+	 * For both IPv4 and IPv6 we use the same default values.
 	 *
 	 * The route-metric is used for the metric of the routes of device.
 	 * This also applies to the default route. Therefore it affects also
@@ -1532,6 +1664,8 @@ nm_device_get_priority (NMDevice *self)
 		return 425;
 	case NM_DEVICE_TYPE_TUN:
 		return 450;
+	case NM_DEVICE_TYPE_PPP:
+		return 460;
 	case NM_DEVICE_TYPE_VXLAN:
 		return 500;
 	case NM_DEVICE_TYPE_DUMMY:
@@ -1546,6 +1680,10 @@ nm_device_get_priority (NMDevice *self)
 		return 700;
 	case NM_DEVICE_TYPE_BT:
 		return 750;
+	case NM_DEVICE_TYPE_OVS_BRIDGE:
+	case NM_DEVICE_TYPE_OVS_INTERFACE:
+	case NM_DEVICE_TYPE_OVS_PORT:
+		return 800;
 	case NM_DEVICE_TYPE_GENERIC:
 		return 950;
 	case NM_DEVICE_TYPE_UNKNOWN:
@@ -1558,29 +1696,40 @@ nm_device_get_priority (NMDevice *self)
 	return 11000;
 }
 
-static guint32
-route_metric_with_penalty (NMDevice *self, guint32 metric)
+static gboolean
+default_route_metric_penalty_detect (NMDevice *self)
 {
 #if WITH_CONCHECK
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const guint32 PENALTY = 20000;
-
-	/* Beware: for IPv6, a metric of 0 effectively means 1024.
-	 * Only pass a normalized IPv6 metric (nm_utils_ip6_route_metric_normalize). */
 
+	/* currently we don't differentiate between IPv4 and IPv6 when detecting
+	 * connectivity. */
 	if (   priv->connectivity_state != NM_CONNECTIVITY_FULL
-	    && nm_connectivity_check_enabled (nm_connectivity_get ())) {
-		if (metric >= G_MAXUINT32 - PENALTY)
-			return G_MAXUINT32;
-		return metric + PENALTY;
+		&& nm_connectivity_check_enabled (nm_connectivity_get ())) {
+		return TRUE;
 	}
 #endif
-	return metric;
+
+	return FALSE;
 }
 
 static guint32
-_get_ipx_route_metric (NMDevice *self,
-                       gboolean is_v4)
+default_route_metric_penalty_get (NMDevice *self, int addr_family)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert_addr_family (addr_family);
+
+	if (  addr_family == AF_INET
+	    ? priv->default_route_metric_penalty_ip4_has
+	    : priv->default_route_metric_penalty_ip6_has)
+		return 20000;
+	return 0;
+}
+
+guint32
+nm_device_get_route_metric (NMDevice *self,
+                            int addr_family)
 {
 	char *value;
 	gint64 route_metric;
@@ -1588,10 +1737,11 @@ _get_ipx_route_metric (NMDevice *self,
 	NMConnection *connection;
 
 	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);
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection) {
-		s_ip = is_v4
+		s_ip = addr_family == AF_INET
 		       ? nm_connection_get_setting_ip4_config (connection)
 		       : nm_connection_get_setting_ip6_config (connection);
 
@@ -1610,7 +1760,7 @@ _get_ipx_route_metric (NMDevice *self,
 	 * 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,
-	                                               is_v4 ? "ipv4.route-metric" : "ipv6.route-metric", self);
+	                                               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);
@@ -1618,81 +1768,96 @@ _get_ipx_route_metric (NMDevice *self,
 		if (route_metric >= 0)
 			goto out;
 	}
-	route_metric = nm_device_get_priority (self);
+	route_metric = _get_route_metric_default (self);
 out:
-	if (!is_v4)
-		route_metric = nm_utils_ip6_route_metric_normalize (route_metric);
-	return route_metric;
+	return nm_utils_ip_route_metric_normalize (addr_family, route_metric);
 }
 
 guint32
-nm_device_get_ip4_route_metric (NMDevice *self)
+nm_device_get_route_table (NMDevice *self,
+                           int addr_family,
+                           gboolean fallback_main)
 {
-	return _get_ipx_route_metric (self, TRUE);
-}
+	NMDevicePrivate *priv;
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	guint32 route_table = 0;
 
-guint32
-nm_device_get_ip6_route_metric (NMDevice *self)
-{
-	return _get_ipx_route_metric (self, FALSE);
-}
+	nm_assert_addr_family (addr_family);
 
-static void
-_update_default_route (NMDevice *self, int addr_family, gboolean has, gboolean is_assumed)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	bool *p_has, *p_is_assumed;
+	g_return_val_if_fail (NM_IS_DEVICE (self), RT_TABLE_MAIN);
 
-	nm_assert (NM_IN_SET (addr_family, 0, AF_INET, AF_INET6));
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
+	/* the route table setting affects how we sync routes. We shall
+	 * not change it while the device is active, hence, cache it. */
 	if (addr_family == AF_INET) {
-		p_has = &priv->default_route.v4_has;
-		p_is_assumed = &priv->default_route.v4_is_assumed;
+		if (priv->v4_route_table_initalized)
+			return priv->v4_route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
 	} else {
-		p_has = &priv->default_route.v6_has;
-		p_is_assumed = &priv->default_route.v6_is_assumed;
+		if (priv->v6_route_table_initalized)
+			return priv->v6_route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
 	}
 
-	if (*p_has == has && *p_is_assumed == is_assumed)
-		return;
+	connection = nm_device_get_applied_connection (self);
+	if (connection) {
+		if (addr_family == AF_INET)
+			s_ip = nm_connection_get_setting_ip4_config (connection);
+		else
+			s_ip = nm_connection_get_setting_ip6_config (connection);
 
-	*p_has = has;
-	*p_is_assumed = is_assumed;
+		if (s_ip)
+			route_table = nm_setting_ip_config_get_route_table (s_ip);
 
-	if (addr_family == AF_INET)
-		nm_default_route_manager_ip4_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-	else
-		nm_default_route_manager_ip6_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-}
-
-const NMPlatformIP4Route *
-nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed)
-{
-	NMDevicePrivate *priv;
+		/* we only lookup the global default if we also have an applied
+		 * connection. Otherwise, the connection is not active, and the
+		 * connection default doesn't matter. */
+		if (route_table == 0) {
+			gs_free char *value = NULL;
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+			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);
+		}
+	}
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
+	if (addr_family == AF_INET) {
+		priv->v4_route_table_initalized = TRUE;
+		priv->v4_route_table = route_table;
+	} else {
+		priv->v6_route_table_initalized = TRUE;
+		priv->v6_route_table = route_table;
+	}
 
-	if (out_is_assumed)
-		*out_is_assumed = priv->default_route.v4_is_assumed;
+	_LOGT (LOGD_DEVICE,
+	       "ipv%c.route-table = %u%s",
+	       addr_family == AF_INET ? '4' : '6',
+	       (guint) (route_table ?: RT_TABLE_MAIN),
+	       route_table ? "" : " (policy routing not enabled)");
 
-	return priv->default_route.v4_has ? &priv->default_route.v4 : NULL;
+	return route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
 }
 
-const NMPlatformIP6Route *
-nm_device_get_ip6_default_route (NMDevice *self, gboolean *out_is_assumed)
+const NMPObject *
+nm_device_get_best_default_route (NMDevice *self,
+                                  int addr_family)
 {
-	NMDevicePrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
-
-	priv = NM_DEVICE_GET_PRIVATE (self);
-
-	if (out_is_assumed)
-		*out_is_assumed = priv->default_route.v6_is_assumed;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	return priv->default_route.v6_has ? &priv->default_route.v6 : NULL;
+	switch (addr_family) {
+	case AF_INET:
+		return priv->ip4_config ? nm_ip4_config_best_default_route_get (priv->ip4_config) : NULL;
+	case AF_INET6:
+		return priv->ip6_config ? nm_ip6_config_best_default_route_get (priv->ip6_config) : NULL;
+	case AF_UNSPEC:
+		return    (priv->ip4_config ? nm_ip4_config_best_default_route_get (priv->ip4_config) : NULL)
+		       ?: (priv->ip6_config ? nm_ip6_config_best_default_route_get (priv->ip6_config) : NULL);
+	default:
+		g_return_val_if_reached (NULL);
+	}
 }
 
 const char *
@@ -1809,7 +1974,7 @@ update_connectivity_state (NMDevice *self, NMConnectivityState state)
 	/* If the connectivity check is disabled, make an optimistic guess. */
 	if (state == NM_CONNECTIVITY_UNKNOWN) {
 		if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
-			if (priv->default_route.v4_has || priv->default_route.v6_has)
+			if (nm_device_get_best_default_route (self, AF_UNSPEC))
 				state = NM_CONNECTIVITY_FULL;
 			else
 				state = NM_CONNECTIVITY_LIMITED;
@@ -1829,12 +1994,12 @@ update_connectivity_state (NMDevice *self, NMConnectivityState state)
 
 		if (   priv->state == NM_DEVICE_STATE_ACTIVATED
 		    && !nm_device_sys_iface_state_is_external (self)) {
-			if (   priv->default_route.v4_has
-			    && !ip4_config_merge_and_apply (self, NULL, TRUE))
-				_LOGW (LOGD_IP4, "Failed to update IPv4 default route metric");
-			if (   priv->default_route.v6_has
+			if (   nm_device_get_best_default_route (self, AF_INET)
+			    && !ip4_config_merge_and_apply (self, TRUE))
+				_LOGW (LOGD_IP4, "Failed to update IPv4 route metric");
+			if (   nm_device_get_best_default_route (self, AF_INET6)
 			    && !ip6_config_merge_and_apply (self, TRUE))
-				_LOGW (LOGD_IP6, "Failed to update IPv6 default route metric");
+				_LOGW (LOGD_IP6, "Failed to update IPv6 route metric");
 		}
 	}
 }
@@ -1948,7 +2113,7 @@ concheck_periodic_update (NMDevice *self)
 	gboolean check_enable;
 
 	check_enable =    (priv->state == NM_DEVICE_STATE_ACTIVATED)
-	               && (priv->default_route.v4_has || priv->default_route.v6_has);
+	               && nm_device_get_best_default_route (self, AF_UNSPEC);
 
 	if (check_enable && !priv->concheck_periodic_id) {
 		/* We just gained a default route. Enable periodic checking. */
@@ -1976,11 +2141,11 @@ static SlaveInfo *
 find_slave_info (NMDevice *self, NMDevice *slave)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	CList *iter;
 	SlaveInfo *info;
-	GSList *iter;
 
-	for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
-		info = iter->data;
+	c_list_for_each (iter, &priv->slaves) {
+		info = c_list_entry (iter, SlaveInfo, lst_slave);
 		if (info->slave == slave)
 			return info;
 	}
@@ -2100,13 +2265,19 @@ nm_device_master_release_one_slave (NMDevice *self, NMDevice *slave, gboolean co
 	 * Transfers ownership from slave_priv->master.  */
 	self_free = self;
 
-	priv->slaves = g_slist_remove (priv->slaves, info);
+	c_list_unlink_init (&info->lst_slave);
 	slave_priv->master = NULL;
 
 	g_signal_handler_disconnect (slave, info->watch_id);
 	g_object_unref (slave);
 	g_slice_free (SlaveInfo, info);
 
+	if (c_list_is_empty (&priv->slaves)) {
+		_active_connection_set_state_flags_full (self,
+		                                         0,
+		                                         NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES);
+	}
+
 	/* Ensure the device's hardware address is up-to-date; it often changes
 	 * when slaves change.
 	 */
@@ -2140,7 +2311,8 @@ is_unmanaged_external_down (NMDevice *self, gboolean consider_can)
 	/* Manage externally-created software interfaces only when they are IFF_UP */
 	if (   priv->ifindex <= 0
 	    || !priv->up
-	    || !(priv->slaves || nm_platform_link_can_assume (nm_device_get_platform (self), priv->ifindex)))
+	    || !(   !c_list_is_empty (&priv->slaves)
+	         || nm_platform_link_can_assume (nm_device_get_platform (self), priv->ifindex)))
 		return NM_UNMAN_FLAG_OP_SET_UNMANAGED;
 
 	return NM_UNMAN_FLAG_OP_SET_MANAGED;
@@ -2235,8 +2407,6 @@ carrier_changed (NMDevice *self, gboolean carrier)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	NM_DEVICE_GET_CLASS (self)->carrier_changed_notify (self, carrier);
-
 	if (priv->state <= NM_DEVICE_STATE_UNMANAGED)
 		return;
 
@@ -2246,21 +2416,22 @@ carrier_changed (NMDevice *self, gboolean carrier)
 	if (priv->ignore_carrier && !carrier)
 		return;
 
-	if (priv->is_master) {
-		/* Bridge/bond/team carrier does not affect its own activation,
-		 * but when carrier comes on, if there are slaves waiting,
-		 * it will restart them.
-		 */
-		if (!carrier)
+	if (nm_device_is_master (self)) {
+		if (carrier) {
+			/* Force master to retry getting ip addresses when carrier
+			* is restored. */
+			if (priv->state == NM_DEVICE_STATE_ACTIVATED)
+				nm_device_update_dynamic_ip_setup (self);
+			else {
+				if (nm_device_activate_ip4_state_in_wait (self))
+					nm_device_activate_stage3_ip4_start (self);
+				if (nm_device_activate_ip6_state_in_wait (self))
+					nm_device_activate_stage3_ip6_start (self);
+			}
 			return;
-
-		if (nm_device_activate_ip4_state_in_wait (self))
-			nm_device_activate_stage3_ip4_start (self);
-		if (nm_device_activate_ip6_state_in_wait (self))
-			nm_device_activate_stage3_ip6_start (self);
-
-		return;
-	} else if (nm_device_get_enslaved (self) && !carrier) {
+		}
+		/* fall-through and change state of device */
+	} else if (priv->is_enslaved && !carrier) {
 		/* Slaves don't deactivate when they lose carrier; for
 		 * bonds/teams in particular that would be actively
 		 * counterproductive.
@@ -2306,7 +2477,7 @@ carrier_disconnected_action_cb (gpointer user_data)
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_LOGD (LOGD_DEVICE, "link disconnected (calling deferred action) (id=%u)", priv->carrier_defer_id);
+	_LOGD (LOGD_DEVICE, "carrier: link disconnected (calling deferred action) (id=%u)", priv->carrier_defer_id);
 
 	priv->carrier_defer_id = 0;
 	carrier_changed (self, FALSE);
@@ -2320,7 +2491,7 @@ carrier_disconnected_action_cancel (NMDevice *self)
 	guint id = priv->carrier_defer_id;
 
 	if (nm_clear_g_source (&priv->carrier_defer_id)) {
-		_LOGD (LOGD_DEVICE, "link disconnected (canceling deferred action) (id=%u)",
+		_LOGD (LOGD_DEVICE, "carrier: link disconnected (canceling deferred action) (id=%u)",
 		       id);
 	}
 }
@@ -2338,8 +2509,9 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 	_notify (self, PROP_CARRIER);
 
 	if (priv->carrier) {
-		_LOGI (LOGD_DEVICE, "link connected");
+		_LOGI (LOGD_DEVICE, "carrier: link connected");
 		carrier_disconnected_action_cancel (self);
+		NM_DEVICE_GET_CLASS (self)->carrier_changed_notify (self, carrier);
 		carrier_changed (self, TRUE);
 
 		if (priv->carrier_wait_id) {
@@ -2349,14 +2521,15 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 	} else {
 		if (priv->carrier_wait_id)
 			nm_device_add_pending_action (self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
+		NM_DEVICE_GET_CLASS (self)->carrier_changed_notify (self, carrier);
 		if (   state <= NM_DEVICE_STATE_DISCONNECTED
 		    && !priv->queued_act_request) {
-			_LOGD (LOGD_DEVICE, "link disconnected");
+			_LOGD (LOGD_DEVICE, "carrier: link disconnected");
 			carrier_changed (self, FALSE);
 		} else {
 			priv->carrier_defer_id = g_timeout_add_seconds (LINK_DISCONNECT_DELAY,
 			                                                carrier_disconnected_action_cb, self);
-			_LOGD (LOGD_DEVICE, "link disconnected (deferring action for %d seconds) (id=%u)",
+			_LOGD (LOGD_DEVICE, "carrier: link disconnected (deferring action for %d seconds) (id=%u)",
 			       LINK_DISCONNECT_DELAY, priv->carrier_defer_id);
 		}
 	}
@@ -2388,12 +2561,27 @@ static void
 device_recheck_slave_status (NMDevice *self, const NMPlatformLink *plink)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDevice *master;
+	nm_auto_nmpobj const NMPObject *plink_master_keep_alive = NULL;
+	const NMPlatformLink *plink_master;
 
 	g_return_if_fail (plink);
 
 	if (plink->master <= 0)
 		return;
 
+	master = nm_manager_get_device_by_ifindex (nm_manager_get (), plink->master);
+	plink_master = nm_platform_link_get (nm_device_get_platform (self), plink->master);
+	plink_master_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink_master));
+
+	if (   master == NULL
+	    && plink_master
+	    && g_strcmp0 (plink_master->name, "ovs-system") == 0
+	    && plink_master->type == NM_LINK_TYPE_OPENVSWITCH) {
+		_LOGD (LOGD_DEVICE, "the device claimed by openvswitch");
+		return;
+	}
+
 	if (priv->master) {
 		if (   plink->master > 0
 		    && plink->master == nm_device_get_ifindex (priv->master)) {
@@ -2405,20 +2593,16 @@ device_recheck_slave_status (NMDevice *self, const NMPlatformLink *plink)
 
 		nm_device_master_release_one_slave (priv->master, self, FALSE, NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
 	}
-	if (plink->master > 0) {
-		NMDevice *master;
 
-		master = nm_manager_get_device_by_ifindex (nm_manager_get (), plink->master);
-		if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave)
-			nm_device_master_add_slave (master, self, FALSE);
-		else if (master) {
-			_LOGI (LOGD_DEVICE, "enslaved to non-master-type device %s; ignoring",
-			       nm_device_get_iface (master));
-		} else {
-			_LOGW (LOGD_DEVICE, "enslaved to unknown device %d %s",
-			       plink->master,
-			       nm_platform_link_get_name (nm_device_get_platform (self), plink->master));
-		}
+	if (master && NM_DEVICE_GET_CLASS (master)->enslave_slave)
+		nm_device_master_add_slave (master, self, FALSE);
+	else if (master) {
+		_LOGI (LOGD_DEVICE, "enslaved to non-master-type device %s; ignoring",
+		       nm_device_get_iface (master));
+	} else {
+		_LOGW (LOGD_DEVICE, "enslaved to unknown device %d (%s%s%s)",
+		       plink->master,
+		       NM_PRINT_FMT_QUOTED (plink_master, "\"", plink_master->name, "\"", "??"));
 	}
 }
 
@@ -2429,16 +2613,19 @@ ndisc_set_router_config (NMNDisc *ndisc, NMDevice *self)
 	gint32 now;
 	GArray *addresses, *dns_servers, *dns_domains;
 	guint len, i;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter ipconf_iter;
 
 	if (nm_ndisc_get_node_type (ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
 		return;
 
 	now = nm_utils_get_monotonic_timestamp_s ();
 
-	len = nm_ip6_config_get_num_addresses (priv->ip6_config);
-	addresses = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscAddress), len);
-	for (i = 0; i < len; i++) {
-		const NMPlatformIP6Address *addr = nm_ip6_config_get_address (priv->ip6_config, i);
+	head_entry = nm_ip6_config_lookup_addresses (priv->ip6_config);
+	addresses = g_array_sized_new (FALSE, TRUE, sizeof (NMNDiscAddress),
+	                               head_entry ? head_entry->len : 0);
+	nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
+		const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS (ipconf_iter.current->obj);
 		NMNDiscAddress *ndisc_addr;
 
 		if (IN6_IS_ADDR_LINKLOCAL (&addr->address))
@@ -2497,8 +2684,7 @@ device_link_changed (NMDevice *self)
 	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	gboolean ip_ifname_changed = FALSE;
-	const char *udi;
-	NMPlatformLink info;
+	nm_auto_nmpobj const NMPObject *pllink_keep_alive = NULL;
 	const NMPlatformLink *pllink;
 	int ifindex;
 	gboolean was_up;
@@ -2512,40 +2698,20 @@ device_link_changed (NMDevice *self)
 	if (!pllink)
 		return G_SOURCE_REMOVE;
 
-	info = *pllink;
+	pllink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (pllink));
 
-	udi = nm_platform_link_get_udi (nm_device_get_platform (self), info.ifindex);
-	if (udi && !nm_streq0 (udi, priv->udi)) {
-		/* Update UDI to what udev gives us */
-		g_free (priv->udi);
-		priv->udi = g_strdup (udi);
-		_notify (self, PROP_UDI);
-	}
-
-	if (!nm_streq0 (info.driver, priv->driver)) {
-		g_free (priv->driver);
-		priv->driver = g_strdup (info.driver);
-		_notify (self, PROP_DRIVER);
-	}
-
-	if (priv->mtu != info.mtu) {
-		priv->mtu = info.mtu;
-		_notify (self, PROP_MTU);
-	}
-
-	if (ifindex == nm_device_get_ip_ifindex (self))
-		_stats_update_counters_from_pllink (self, &info);
+	nm_device_update_from_platform_link (self, pllink);
 
 	had_hw_addr = (priv->hw_addr != NULL);
 	nm_device_update_hw_address (self);
 	got_hw_addr = (!had_hw_addr && priv->hw_addr);
 	nm_device_update_permanent_hw_address (self, FALSE);
 
-	if (info.name[0] && strcmp (priv->iface, info.name) != 0) {
+	if (pllink->name[0] && strcmp (priv->iface, pllink->name) != 0) {
 		_LOGI (LOGD_DEVICE, "interface index %d renamed iface from '%s' to '%s'",
-		       priv->ifindex, priv->iface, info.name);
+		       priv->ifindex, priv->iface, pllink->name);
 		g_free (priv->iface);
-		priv->iface = g_strdup (info.name);
+		priv->iface = g_strdup (pllink->name);
 
 		/* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */
 		ip_ifname_changed = !priv->ip_iface;
@@ -2568,8 +2734,8 @@ device_link_changed (NMDevice *self)
 		nm_device_emit_recheck_auto_activate (self);
 	}
 
-	if (priv->ndisc && info.inet6_token.id) {
-		if (nm_ndisc_set_iid (priv->ndisc, info.inet6_token))
+	if (priv->ndisc && pllink->inet6_token.id) {
+		if (nm_ndisc_set_iid (priv->ndisc, pllink->inet6_token))
 			_LOGD (LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
 	}
 
@@ -2578,20 +2744,21 @@ device_link_changed (NMDevice *self)
 	    && !nm_device_has_capability (self, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
 		nm_device_set_carrier (self, pllink->connected);
 
-	klass->link_changed (self, &info);
+	klass->link_changed (self, pllink);
 
 	/* Update DHCP, etc, if needed */
 	if (ip_ifname_changed)
 		nm_device_update_dynamic_ip_setup (self);
 
 	was_up = priv->up;
-	priv->up = NM_FLAGS_HAS (info.n_ifi_flags, IFF_UP);
+	priv->up = NM_FLAGS_HAS (pllink->n_ifi_flags, IFF_UP);
 
-	if (   info.initialized
+	if (   pllink->initialized
 	    && nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 		NMDeviceStateReason reason;
 
 		nm_device_set_unmanaged_by_user_udev (self);
+		nm_device_set_unmanaged_by_user_conf (self);
 
 		reason = NM_DEVICE_STATE_REASON_NOW_MANAGED;
 
@@ -2616,13 +2783,13 @@ device_link_changed (NMDevice *self)
 
 	set_unmanaged_external_down (self, FALSE);
 
-	device_recheck_slave_status (self, &info);
+	device_recheck_slave_status (self, pllink);
 
 	if (priv->up && !was_up) {
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
 		if (priv->ip4_state == IP_DONE) {
-			if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+			if (!ip4_config_merge_and_apply (self, TRUE))
 				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 		}
 		if (priv->ip6_state == IP_DONE) {
@@ -2703,6 +2870,103 @@ link_changed_cb (NMPlatform *platform,
 	}
 }
 
+/*****************************************************************************/
+
+typedef struct {
+	in_addr_t network;
+	guint8 plen;
+} IP4RPFilterData;
+
+static guint
+_v4_has_shadowed_routes_detect_hash (const IP4RPFilterData *d)
+{
+	NMHashState h;
+
+	nm_hash_init (&h, 1105201169u);
+	nm_hash_update_vals (&h,
+	                     d->network,
+	                     d->plen);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_v4_has_shadowed_routes_detect_equal (const IP4RPFilterData *d1, const IP4RPFilterData *d2)
+{
+	return d1->network == d2->network && d1->plen == d2->plen;
+}
+
+static gboolean
+_v4_has_shadowed_routes_detect (NMDevice *self)
+{
+	NMPlatform *platform;
+	int ifindex;
+	NMPLookup lookup;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *o;
+	guint data_len;
+	gs_unref_hashtable GHashTable *data_hash = NULL;
+	gs_free IP4RPFilterData *data_arr = NULL;
+
+	ifindex = nm_device_get_ip_ifindex (self);
+	if (ifindex <= 0)
+		return FALSE;
+
+	platform = nm_device_get_platform (self);
+
+	head_entry = nm_platform_lookup (platform,
+	                                 nmp_lookup_init_addrroute (&lookup,
+	                                                            NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                            ifindex));
+	if (!head_entry)
+		return FALSE;
+
+	/* first, create a lookup index @data_hash for all network/plen pairs. */
+	data_len = 0;
+	data_arr = g_new (IP4RPFilterData, head_entry->len);
+	data_hash = g_hash_table_new ((GHashFunc) _v4_has_shadowed_routes_detect_hash,
+	                              (GEqualFunc) _v4_has_shadowed_routes_detect_equal);
+
+	nmp_cache_iter_for_each (&iter, head_entry, &o) {
+		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
+		IP4RPFilterData *d;
+
+		nm_assert (r->ifindex == ifindex);
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    || r->table_coerced)
+			continue;
+
+		d = &data_arr[data_len++];
+		d->network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
+		d->plen = r->plen;
+		g_hash_table_add (data_hash, d);
+	}
+
+	/* then, search if there is any route on another interface with the same
+	 * network/plen destination. If yes, we consider this a multihoming
+	 * setup. */
+	head_entry = nm_platform_lookup (platform,
+	                                 nmp_lookup_init_obj_type (&lookup,
+	                                                           NMP_OBJECT_TYPE_IP4_ROUTE));
+	nmp_cache_iter_for_each (&iter, head_entry, &o) {
+		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (o);
+		IP4RPFilterData d;
+
+		if (   r->ifindex == ifindex
+		    || NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    || r->table_coerced)
+			continue;
+
+		d.network = nm_utils_ip4_address_clear_host_address (r->network, r->plen);
+		d.plen = r->plen;
+		if (g_hash_table_contains (data_hash, &d))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
 static void
 ip4_rp_filter_update (NMDevice *self)
 {
@@ -2710,7 +2974,7 @@ ip4_rp_filter_update (NMDevice *self)
 	const char *ip4_rp_filter;
 
 	if (   priv->v4_has_shadowed_routes
-	    || priv->default_route.v4_has) {
+	    || nm_device_get_best_default_route (self, AF_INET)) {
 		if (nm_device_ipv4_sysctl_get_uint32 (self, "rp_filter", 0) != 1) {
 			/* Don't touch the rp_filter if it's not strict. */
 			return;
@@ -2729,20 +2993,6 @@ ip4_rp_filter_update (NMDevice *self)
 }
 
 static void
-ip4_routes_changed_changed_cb (NMRouteManager *route_manager, NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ifindex = nm_device_get_ip_ifindex (self);
-
-	if (nm_device_sys_iface_state_is_external_or_assume (self))
-		return;
-
-	priv->v4_has_shadowed_routes = nm_route_manager_ip4_routes_shadowed (route_manager,
-	                                                                     ifindex);
-	ip4_rp_filter_update (self);
-}
-
-static void
 link_changed (NMDevice *self, const NMPlatformLink *pllink)
 {
 	/* stub implementation of virtual function to allow subclasses to chain up. */
@@ -2822,7 +3072,9 @@ nm_device_realize_start (NMDevice *self,
                          gboolean *out_compatible,
                          GError **error)
 {
-	NMPlatformLink plink_copy;
+	nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
+
+	nm_assert (!plink || NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (plink)) == NMP_OBJECT_TYPE_LINK);
 
 	NM_SET_OUT (out_compatible, TRUE);
 
@@ -2836,13 +3088,12 @@ nm_device_realize_start (NMDevice *self,
 
 		if (!link_type_compatible (self, plink->type, out_compatible, error))
 			return FALSE;
-	}
 
-	if (plink) {
-		plink_copy = *plink;
-		plink = &plink_copy;
+		plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
 	}
-	realize_start_setup (self, plink,
+
+	realize_start_setup (self,
+	                     plink,
 	                     assume_state_guess_assume,
 	                     assume_state_connection_uuid,
 	                     set_nm_owned,
@@ -2868,8 +3119,8 @@ nm_device_create_and_realize (NMDevice *self,
                               NMDevice *parent,
                               GError **error)
 {
+	nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMPlatformLink plink_copy;
 	const NMPlatformLink *plink = NULL;
 
 	/* Must be set before device is realized */
@@ -2881,11 +3132,14 @@ nm_device_create_and_realize (NMDevice *self,
 	if (NM_DEVICE_GET_CLASS (self)->create_and_realize) {
 		if (!NM_DEVICE_GET_CLASS (self)->create_and_realize (self, connection, parent, &plink, error))
 			return FALSE;
-		plink_copy = *plink;
-		plink = &plink_copy;
+		if (plink) {
+			nm_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (plink)) == NMP_OBJECT_TYPE_LINK);
+			plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
+		}
 	}
 
-	realize_start_setup (self, plink,
+	realize_start_setup (self,
+	                     plink,
 	                     FALSE, /* assume_state_guess_assume */
 	                     NULL,  /* assume_state_connection_uuid */
 	                     FALSE, NM_UNMAN_FLAG_OP_FORGET);
@@ -2899,38 +3153,54 @@ nm_device_create_and_realize (NMDevice *self,
 	return TRUE;
 }
 
-static void
-update_device_from_platform_link (NMDevice *self, const NMPlatformLink *plink)
+void
+nm_device_update_from_platform_link (NMDevice *self, const NMPlatformLink *plink)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	const char *udi;
+	const char *str;
+	int ifindex;
+	guint32 mtu;
 
-	g_return_if_fail (plink != NULL);
+	g_return_if_fail (plink == NULL || link_type_compatible (self, plink->type, NULL, NULL));
 
-	udi = nm_platform_link_get_udi (nm_device_get_platform (self), plink->ifindex);
-	if (udi && !nm_streq0 (udi, priv->udi)) {
+	str = plink ? nm_platform_link_get_udi (nm_device_get_platform (self), plink->ifindex) : NULL;
+	if (g_strcmp0 (str, priv->udi)) {
 		g_free (priv->udi);
-		priv->udi = g_strdup (udi);
+		priv->udi = g_strdup (str);
 		_notify (self, PROP_UDI);
 	}
 
-	if (!g_strcmp0 (plink->name, priv->iface)) {
+	str = plink ? plink->name : NULL;
+	if (str && g_strcmp0 (str, priv->iface)) {
 		g_free (priv->iface);
-		priv->iface = g_strdup (plink->name);
+		priv->iface = g_strdup (str);
 		_notify (self, PROP_IFACE);
 	}
 
-	if (priv->ifindex != plink->ifindex) {
-		priv->ifindex = plink->ifindex;
-		_notify (self, PROP_IFINDEX);
-	}
-
-	priv->up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
-	if (plink->driver && g_strcmp0 (plink->driver, priv->driver) != 0) {
+	str = plink ? plink->driver : NULL;
+	if (g_strcmp0 (str, priv->driver) != 0) {
 		g_free (priv->driver);
-		priv->driver = g_strdup (plink->driver);
+		priv->driver = g_strdup (str);
 		_notify (self, PROP_DRIVER);
 	}
+
+	if (plink) {
+		priv->up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
+		if (plink->ifindex == nm_device_get_ip_ifindex (self))
+			_stats_update_counters_from_pllink (self, plink);
+	} else {
+		priv->up = FALSE;
+	}
+
+	mtu = plink ? plink->mtu : 0;
+	_set_mtu (self, mtu);
+
+	ifindex = plink ? plink->ifindex : 0;
+	if (priv->ifindex != ifindex) {
+		priv->ifindex = ifindex;
+		_notify (self, PROP_IFINDEX);
+		NM_DEVICE_GET_CLASS (self)->link_changed (self, plink);
+	}
 }
 
 static void
@@ -2943,7 +3213,7 @@ device_init_sriov_num_vfs (NMDevice *self)
 	if (   priv->ifindex > 0
 	    && nm_device_has_capability (self, NM_DEVICE_CAP_SRIOV)) {
 		value = nm_config_data_get_device_config (NM_CONFIG_GET_DATA,
-		                                          "sriov-num-vfs",
+		                                          NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
 		                                          self,
 		                                          NULL);
 		num_vfs = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXINT32, -1);
@@ -3010,7 +3280,10 @@ realize_start_setup (NMDevice *self,
 	NMDeviceCapabilities capabilities = 0;
 	NMConfig *config;
 	guint real_rate;
-	guint32 mtu;
+
+	/* plink is a NMPlatformLink type, however, we require it to come from the platform
+	 * cache (where else would it come from?). */
+	nm_assert (!plink || NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST (plink)) == NMP_OBJECT_TYPE_LINK);
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
@@ -3035,20 +3308,14 @@ realize_start_setup (NMDevice *self,
 	priv->mtu_initial = 0;
 	priv->ip6_mtu_initial = 0;
 	priv->ip6_mtu = 0;
-	if (priv->mtu) {
-		priv->mtu = 0;
-		_notify (self, PROP_MTU);
-	}
+	_set_mtu (self, 0);
 
 	_assume_state_set (self, assume_state_guess_assume, assume_state_connection_uuid);
 
 	nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
 
-	if (plink) {
-		g_return_if_fail (link_type_compatible (self, plink->type, NULL, NULL));
-		update_device_from_platform_link (self, plink);
-		_stats_update_counters_from_pllink (self, plink);
-	}
+	if (plink)
+		nm_device_update_from_platform_link (self, plink);
 
 	if (priv->ifindex > 0) {
 		priv->physical_port_id = nm_platform_link_get_physical_port_id (nm_device_get_platform (self), priv->ifindex);
@@ -3059,11 +3326,9 @@ realize_start_setup (NMDevice *self,
 		if (nm_platform_link_is_software (nm_device_get_platform (self), priv->ifindex))
 			capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
 
-		mtu = nm_platform_link_get_mtu (nm_device_get_platform (self), priv->ifindex);
-		if (priv->mtu != mtu) {
-			priv->mtu = mtu;
-			_notify (self, PROP_MTU);
-		}
+		_set_mtu (self,
+		          nm_platform_link_get_mtu (nm_device_get_platform (self),
+		                                    priv->ifindex));
 
 		nm_platform_link_get_driver_info (nm_device_get_platform (self),
 		                                  priv->ifindex,
@@ -3075,7 +3340,8 @@ realize_start_setup (NMDevice *self,
 		if (priv->firmware_version)
 			_notify (self, PROP_FIRMWARE_VERSION);
 
-		if (nm_platform_check_support_user_ipv6ll (nm_device_get_platform (self)))
+		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
+		                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
 			priv->nm_ipv6ll = nm_platform_link_get_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ifindex);
 
 		if (nm_platform_link_supports_sriov (nm_device_get_platform (self), priv->ifindex))
@@ -3147,6 +3413,7 @@ realize_start_setup (NMDevice *self,
 	nm_device_set_unmanaged_flags (self, NM_UNMANAGED_LOOPBACK, priv->ifindex == 1);
 
 	nm_device_set_unmanaged_by_user_udev (self);
+	nm_device_set_unmanaged_by_user_conf (self);
 
 	nm_device_set_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT,
 	                               plink && !plink->initialized);
@@ -3257,7 +3524,6 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 	g_return_val_if_fail (priv->iface != NULL, FALSE);
 	g_return_val_if_fail (priv->real, FALSE);
 
-	g_object_freeze_notify (G_OBJECT (self));
 
 	ifindex = nm_device_get_ifindex (self);
 
@@ -3274,6 +3540,7 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 		}
 	}
 
+	g_object_freeze_notify (G_OBJECT (self));
 	NM_DEVICE_GET_CLASS (self)->unrealize_notify (self);
 
 	_parent_set_ifindex (self, 0, FALSE);
@@ -3286,10 +3553,7 @@ nm_device_unrealize (NMDevice *self, gboolean remove_resources, GError **error)
 	if (nm_clear_g_free (&priv->ip_iface))
 		_notify (self, PROP_IP_IFACE);
 
-	if (priv->mtu != 0) {
-		priv->mtu = 0;
-		_notify (self, PROP_MTU);
-	}
+	_set_mtu (self, 0);
 
 	if (priv->driver_version) {
 		g_clear_pointer (&priv->driver_version, g_free);
@@ -3373,13 +3637,25 @@ gboolean
 nm_device_notify_component_added (NMDevice *self, GObject *component)
 {
 	NMDeviceClass *klass;
+	NMDevicePrivate *priv;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
-	g_return_val_if_fail (G_IS_OBJECT (component), FALSE);
 
+	priv = NM_DEVICE_GET_PRIVATE (self);
 	klass = NM_DEVICE_GET_CLASS (self);
+
+	if (priv->state == NM_DEVICE_STATE_DISCONNECTED) {
+		/* A device could have stayed disconnected because it would
+		 * want to register with a network server that now become
+		 * available. */
+		nm_device_recheck_available_connections (self);
+		if (g_hash_table_size (priv->available_connections) > 0)
+			nm_device_emit_recheck_auto_activate (self);
+	}
+
 	if (klass->component_added)
 		return klass->component_added (self, component);
+
 	return FALSE;
 }
 
@@ -3453,7 +3729,8 @@ slave_state_changed (NMDevice *slave,
 		                                    configure,
 		                                    reason);
 		/* Bridge/bond/team interfaces are left up until manually deactivated */
-		if (priv->slaves == NULL && priv->state == NM_DEVICE_STATE_ACTIVATED)
+		if (   c_list_is_empty (&priv->slaves)
+		    && priv->state == NM_DEVICE_STATE_ACTIVATED)
 			_LOGD (LOGD_DEVICE, "last slave removed; remaining activated");
 	}
 }
@@ -3504,9 +3781,12 @@ nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure)
 		info->watch_id = g_signal_connect (slave,
 		                                   NM_DEVICE_STATE_CHANGED,
 		                                   G_CALLBACK (slave_state_changed), self);
-		priv->slaves = g_slist_append (priv->slaves, info);
+		c_list_link_tail (&priv->slaves, &info->lst_slave);
 		slave_priv->master = g_object_ref (self);
 
+		_active_connection_set_state_flags (self,
+		                                    NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES);
+
 		/* no need to emit
 		 *
 		 *   _notify (slave, PROP_MASTER);
@@ -3527,46 +3807,6 @@ nm_device_master_add_slave (NMDevice *self, NMDevice *slave, gboolean configure)
 }
 
 /**
- * nm_device_master_get_slaves:
- * @self: the master device
- *
- * Returns: any slaves of which @self is the master.  Caller owns returned list.
- */
-static GSList *
-nm_device_master_get_slaves (NMDevice *self)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	GSList *slaves = NULL, *iter;
-
-	for (iter = priv->slaves; iter; iter = g_slist_next (iter))
-		slaves = g_slist_prepend (slaves, ((SlaveInfo *) iter->data)->slave);
-
-	return slaves;
-}
-
-/**
- * nm_device_master_get_slave_by_ifindex:
- * @self: the master device
- * @ifindex: the slave's interface index
- *
- * Returns: the slave with the given @ifindex of which @self is the master,
- *   or %NULL if no device with @ifindex is a slave of @self.
- */
-NMDevice *
-nm_device_master_get_slave_by_ifindex (NMDevice *self, int ifindex)
-{
-	GSList *iter;
-
-	for (iter = NM_DEVICE_GET_PRIVATE (self)->slaves; iter; iter = g_slist_next (iter)) {
-		SlaveInfo *info = iter->data;
-
-		if (nm_device_get_ip_ifindex (info->slave) == ifindex)
-			return info->slave;
-	}
-	return NULL;
-}
-
-/**
  * nm_device_master_check_slave_physical_port:
  * @self: the master device
  * @slave: a slave device
@@ -3582,14 +3822,14 @@ nm_device_master_check_slave_physical_port (NMDevice *self, NMDevice *slave,
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const char *slave_physical_port_id, *existing_physical_port_id;
 	SlaveInfo *info;
-	GSList *iter;
+	CList *iter;
 
 	slave_physical_port_id = nm_device_get_physical_port_id (slave);
 	if (!slave_physical_port_id)
 		return;
 
-	for (iter = priv->slaves; iter; iter = iter->next) {
-		info = iter->data;
+	c_list_for_each (iter, &priv->slaves) {
+		info = c_list_entry (iter, SlaveInfo, lst_slave);
 		if (info->slave == slave)
 			continue;
 
@@ -3615,6 +3855,7 @@ nm_device_master_release_slaves (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMDeviceStateReason reason;
 	gboolean configure = TRUE;
+	CList *iter, *safe;
 
 	/* Don't release the slaves if this connection doesn't belong to NM. */
 	if (nm_device_sys_iface_state_is_external (self))
@@ -3627,8 +3868,8 @@ nm_device_master_release_slaves (NMDevice *self)
 	if (!nm_platform_link_get (nm_device_get_platform (self), priv->ifindex))
 		configure = FALSE;
 
-	while (priv->slaves) {
-		SlaveInfo *info = priv->slaves->data;
+	c_list_for_each_safe (iter, safe, &priv->slaves) {
+		SlaveInfo *info = c_list_entry (iter, SlaveInfo, lst_slave);
 
 		nm_device_master_release_one_slave (self, info->slave, configure, reason);
 	}
@@ -3643,7 +3884,9 @@ nm_device_master_release_slaves (NMDevice *self)
 gboolean
 nm_device_is_master (NMDevice *self)
 {
-	return NM_DEVICE_GET_PRIVATE (self)->is_master;
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	return NM_DEVICE_GET_CLASS (self)->is_master;
 }
 
 /**
@@ -3669,6 +3912,113 @@ nm_device_get_master (NMDevice *self)
 	return NULL;
 }
 
+static gboolean
+get_ip_config_may_fail (NMDevice *self, int addr_family)
+{
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip = NULL;
+
+	connection = nm_device_get_applied_connection (self);
+
+	/* Fail the connection if the failed IP method is required to complete */
+	switch (addr_family) {
+	case AF_INET:
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+		break;
+	case AF_INET6:
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
+
+	return !s_ip || nm_setting_ip_config_get_may_fail (s_ip);
+}
+
+/*
+ * check_ip_state
+ *
+ * Transition the device from IP_CONFIG to the next state according to the
+ * outcome of IPv4 and IPv6 configuration. @may_fail indicates that we are
+ * called just after the initial configuration and thus IPv4/IPv6 are allowed to
+ * fail if the ipvx.may-fail properties say so, because the IP methods couldn't
+ * even be started.
+ */
+static void
+check_ip_state (NMDevice *self, gboolean may_fail)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean ip4_disabled = FALSE, ip6_ignore = FALSE;
+	NMSettingIPConfig *s_ip4, *s_ip6;
+	NMDeviceState state;
+
+	if (nm_device_get_state (self) != NM_DEVICE_STATE_IP_CONFIG)
+		return;
+
+	/* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
+	 * master to enslave us. */
+	if (   nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request))
+	    && !priv->is_enslaved)
+		return;
+
+	s_ip4 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
+	if (s_ip4 && nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
+	                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+		ip4_disabled = TRUE;
+
+	s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
+	if (s_ip6 && nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
+	                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
+		ip6_ignore = TRUE;
+
+	if (   priv->ip4_state == IP_DONE
+	    && priv->ip6_state == IP_DONE) {
+		/* Both method completed (or disabled), proceed with activation */
+		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
+		return;
+	}
+
+	if (   (priv->ip4_state == IP_FAIL || (ip4_disabled && priv->ip4_state == IP_DONE))
+	    && (priv->ip6_state == IP_FAIL || (ip6_ignore && priv->ip6_state == IP_DONE))) {
+		/* Either both methods failed, or only one failed and the other is
+		 * disabled */
+		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
+			/* We have assumed configuration, but couldn't redo it. No problem,
+			 * move to check state. */
+			_set_ip_state (self, AF_INET, IP_DONE);
+			_set_ip_state (self, AF_INET6, IP_DONE);
+			state = NM_DEVICE_STATE_IP_CHECK;
+		} else if (   may_fail
+		           && get_ip_config_may_fail (self, AF_INET)
+		           && get_ip_config_may_fail (self, AF_INET6)) {
+			/* Couldn't start either IPv6 and IPv4 autoconfiguration,
+			 * but both are allowed to fail. */
+			state = NM_DEVICE_STATE_SECONDARIES;
+		} else {
+			/* Autoconfiguration attempted without success. */
+			state = NM_DEVICE_STATE_FAILED;
+		}
+
+		nm_device_state_changed (self,
+		                         state,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		return;
+	}
+
+	/* If a method is still pending but required, wait */
+	if (priv->ip4_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET))
+		return;
+	if (priv->ip6_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET6))
+		return;
+
+	/* If at least a method has completed, proceed with activation */
+	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
+	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
+		return;
+	}
+}
+
 /**
  * nm_device_slave_notify_enslave:
  * @self: the slave device
@@ -3705,10 +4055,8 @@ nm_device_slave_notify_enslave (NMDevice *self, gboolean success)
 	}
 
 	if (activating) {
-		_set_ip_state (self, AF_INET, IP_DONE);
-		_set_ip_state (self, AF_INET6, IP_DONE);
 		if (success)
-			nm_device_queue_state (self, NM_DEVICE_STATE_SECONDARIES, NM_DEVICE_STATE_REASON_NONE);
+			check_ip_state (self, FALSE);
 		else
 			nm_device_queue_state (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN);
 	} else
@@ -3767,19 +4115,6 @@ nm_device_slave_notify_release (NMDevice *self, NMDeviceStateReason reason)
 }
 
 /**
- * nm_device_get_enslaved:
- * @self: the #NMDevice
- *
- * Returns: %TRUE if the device is enslaved to a master device (eg bridge or
- * bond or team), %FALSE if not
- */
-gboolean
-nm_device_get_enslaved (NMDevice *self)
-{
-	return NM_DEVICE_GET_PRIVATE (self)->is_enslaved;
-}
-
-/**
  * nm_device_removed:
  * @self: the #NMDevice
  * @unconfigure_ip_config: whether to clear the IP config objects
@@ -3805,17 +4140,8 @@ nm_device_removed (NMDevice *self, gboolean unconfigure_ip_config)
 	if (!unconfigure_ip_config)
 		return;
 
-	/* Clean up IP configs; this does not actually deconfigure the
-	 * interface, it just clears the configuration to which policy
-	 * is reacting via NM_DEVICE_IP4_CONFIG_CHANGED/NM_DEVICE_IP6_CONFIG_CHANGED
-	 * signal. As NMPolicy registered the NMIPxConfig instances in NMDnsManager,
-	 * these would be leaked otherwise. */
-	_update_default_route (self, AF_INET,  priv->default_route.v4_has, TRUE);
-	_update_default_route (self, AF_INET6, priv->default_route.v6_has, TRUE);
-	_update_default_route (self, AF_INET,  FALSE, TRUE);
-	_update_default_route (self, AF_INET6, FALSE, TRUE);
-	nm_device_set_ip4_config (self, NULL, 0, FALSE, FALSE);
-	nm_device_set_ip6_config (self, NULL, FALSE, FALSE);
+	nm_device_set_ip4_config (self, NULL, FALSE, NULL);
+	nm_device_set_ip6_config (self, NULL, FALSE);
 }
 
 static gboolean
@@ -3823,12 +4149,17 @@ is_available (NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->carrier || priv->ignore_carrier)
+	if (   priv->carrier
+	    || priv->ignore_carrier)
 		return TRUE;
 
 	if (NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER))
 		return TRUE;
 
+	/* master types are always available even without carrier. */
+	if (nm_device_is_master (self))
+		return TRUE;
+
 	return FALSE;
 }
 
@@ -3863,6 +4194,13 @@ nm_device_is_available (NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
 }
 
 gboolean
+nm_device_ignore_carrier_by_default (NMDevice *self)
+{
+	/* master types ignore-carrier by default. */
+	return nm_device_is_master (self);
+}
+
+gboolean
 nm_device_get_enabled (NMDevice *self)
 {
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
@@ -4057,7 +4395,8 @@ device_has_config (NMDevice *self)
 		return TRUE;
 
 	/* Master-slave relationship is also a configuration */
-	if (priv->slaves || nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) > 0)
+	if (   !c_list_is_empty (&priv->slaves)
+	    || nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) > 0)
 		return TRUE;
 
 	return FALSE;
@@ -4179,6 +4518,8 @@ nm_device_generate_connection (NMDevice *self,
 		s_ip6 = nm_ip6_config_create_setting (priv->ip6_config);
 		nm_connection_add_setting (connection, s_ip6);
 
+		nm_connection_add_setting (connection, nm_setting_proxy_new ());
+
 		pllink = nm_platform_link_get (nm_device_get_platform (self), priv->ifindex);
 		if (pllink && pllink->inet6_token.id) {
 			g_object_set (s_ip6,
@@ -4206,7 +4547,7 @@ nm_device_generate_connection (NMDevice *self,
 	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0
 	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
-	    && !priv->slaves) {
+	    && c_list_is_empty (&priv->slaves)) {
 		NM_SET_OUT (out_maybe_later, TRUE);
 		g_set_error_literal (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
 		                     "ignoring generated connection (no IP and not in master-slave relationship)");
@@ -4219,7 +4560,7 @@ nm_device_generate_connection (NMDevice *self,
 	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
 	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0
 	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
-	    && !priv->slaves
+	    && c_list_is_empty (&priv->slaves)
 	    && !nm_config_data_get_assume_ipv6ll_only (NM_CONFIG_GET_DATA, self)) {
 		_LOGD (LOGD_DEVICE, "ignoring generated connection (IPv6LL-only and not in master-slave relationship)");
 		NM_SET_OUT (out_maybe_later, TRUE);
@@ -4261,6 +4602,65 @@ nm_device_complete_connection (NMDevice *self,
 	return success;
 }
 
+gboolean
+nm_device_match_parent (NMDevice *self, const char *parent)
+{
+	NMDevice *parent_device;
+
+	g_return_val_if_fail (parent, FALSE);
+
+	parent_device = nm_device_parent_get_device (self);
+	if (!parent_device)
+		return FALSE;
+
+	if (nm_utils_is_uuid (parent)) {
+		NMConnection *connection;
+
+		/* If the parent is a UUID, the connection matches when there is
+		 * no connection active on the device or when a connection with
+		 * that UUID is active.
+		 */
+		connection = nm_device_get_applied_connection (self);
+		if (!connection)
+			return TRUE;
+
+		if (!nm_streq0 (parent, nm_connection_get_uuid (connection)))
+			return FALSE;
+	} else {
+		/* Interface name */
+		if (!nm_streq0 (parent, nm_device_get_ip_iface (parent_device)))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_device_match_hwaddr (NMDevice *device,
+                        NMConnection *connection,
+                        gboolean fail_if_no_hwaddr)
+{
+	NMSettingWired *s_wired;
+	NMDevice *parent_device;
+	const char *setting_mac;
+	const char *parent_mac;
+
+	s_wired = nm_connection_get_setting_wired (connection);
+	if (!s_wired)
+		return !fail_if_no_hwaddr;
+
+	setting_mac = nm_setting_wired_get_mac_address (s_wired);
+	if (!setting_mac)
+		return !fail_if_no_hwaddr;
+
+	parent_device = nm_device_parent_get_device (device);
+	if (!parent_device)
+		return !fail_if_no_hwaddr;
+
+	parent_mac = nm_device_get_permanent_hw_address (parent_device);
+	return parent_mac && nm_utils_hwaddr_matches (setting_mac, -1, parent_mac, -1);
+}
+
 static gboolean
 check_connection_compatible (NMDevice *self, NMConnection *connection)
 {
@@ -4317,7 +4717,7 @@ nm_device_check_slave_connection_compatible (NMDevice *self, NMConnection *slave
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (!priv->is_master)
+	if (!nm_device_is_master (self))
 		return FALSE;
 
 	/* All masters should have connection type set */
@@ -4506,44 +4906,49 @@ activation_source_handle_cb6 (gpointer user_data)
 
 static ActivationHandleData *
 activation_source_get_by_family (NMDevice *self,
-                                 int family,
+                                 int addr_family,
                                  GSourceFunc *out_idle_func)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (family == AF_INET6) {
+	switch (addr_family) {
+	case AF_INET6:
 		NM_SET_OUT (out_idle_func, activation_source_handle_cb6);
 		return &priv->act_handle6;
-	} else {
+	case AF_INET:
 		NM_SET_OUT (out_idle_func, activation_source_handle_cb4);
-		g_return_val_if_fail (family == AF_INET, &priv->act_handle4);
 		return &priv->act_handle4;
 	}
+	g_return_val_if_reached (NULL);
 }
 
 static void
-activation_source_clear (NMDevice *self, int family)
+activation_source_clear (NMDevice *self,
+                         int addr_family)
 {
 	ActivationHandleData *act_data;
 
-	act_data = activation_source_get_by_family (self, family, NULL);
+	act_data = activation_source_get_by_family (self, addr_family, NULL);
 
 	if (act_data->id) {
-		_LOGD (LOGD_DEVICE, "activation-stage: clear %s,%d (id %u)",
-		       _activation_func_to_string (act_data->func), family, act_data->id);
+		_LOGD (LOGD_DEVICE, "activation-stage: clear %s,v%c (id %u)",
+		       _activation_func_to_string (act_data->func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       act_data->id);
 		nm_clear_g_source (&act_data->id);
 		act_data->func = NULL;
 	}
 }
 
 static void
-activation_source_handle_cb (NMDevice *self, int family)
+activation_source_handle_cb (NMDevice *self,
+                             int addr_family)
 {
 	ActivationHandleData *act_data, a;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
-	act_data = activation_source_get_by_family (self, family, NULL);
+	act_data = activation_source_get_by_family (self, addr_family, NULL);
 
 	g_return_if_fail (act_data->id);
 	g_return_if_fail (act_data->func);
@@ -4553,23 +4958,27 @@ activation_source_handle_cb (NMDevice *self, int family)
 	act_data->func = NULL;
 	act_data->id = 0;
 
-	_LOGD (LOGD_DEVICE, "activation-stage: invoke %s,%d (id %u)",
-	       _activation_func_to_string (a.func), family, a.id);
+	_LOGD (LOGD_DEVICE, "activation-stage: invoke %s,v%c (id %u)",
+	       _activation_func_to_string (a.func),
+	       nm_utils_addr_family_to_char (addr_family),
+	       a.id);
 
 	a.func (self);
 
-	_LOGD (LOGD_DEVICE, "activation-stage: complete %s,%d (id %u)",
-	       _activation_func_to_string (a.func), family, a.id);
+	_LOGD (LOGD_DEVICE, "activation-stage: complete %s,v%c (id %u)",
+	       _activation_func_to_string (a.func),
+	       nm_utils_addr_family_to_char (addr_family),
+	       a.id);
 }
 
 static void
-activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int family)
+activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int addr_family)
 {
 	ActivationHandleData *act_data;
 	GSourceFunc source_func;
 	guint new_id = 0;
 
-	act_data = activation_source_get_by_family (self, family, &source_func);
+	act_data = activation_source_get_by_family (self, addr_family, &source_func);
 
 	if (act_data->id && act_data->func == func) {
 		/* Don't bother rescheduling the same function that's about to
@@ -4577,22 +4986,28 @@ activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int famil
 		 * streams of associate events before NM has had a chance to process
 		 * the first one.
 		 */
-		_LOGD (LOGD_DEVICE, "activation-stage: already scheduled %s,%d (id %u)",
-		       _activation_func_to_string (func), family, act_data->id);
+		_LOGD (LOGD_DEVICE, "activation-stage: already scheduled %s,v%c (id %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       act_data->id);
 		return;
 	}
 
 	new_id = g_idle_add (source_func, self);
 
 	if (act_data->id) {
-		_LOGW (LOGD_DEVICE, "activation-stage: schedule %s,%d which replaces %s,%d (id %u -> %u)",
-		       _activation_func_to_string (func), family,
-		       _activation_func_to_string (act_data->func), family,
+		_LOGW (LOGD_DEVICE, "activation-stage: schedule %s,v%c which replaces %s,v%c (id %u -> %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       _activation_func_to_string (act_data->func),
+		       nm_utils_addr_family_to_char (addr_family),
 		       act_data->id, new_id);
 		nm_clear_g_source (&act_data->id);
 	} else {
-		_LOGD (LOGD_DEVICE, "activation-stage: schedule %s,%d (id %u)",
-		       _activation_func_to_string (func), family, new_id);
+		_LOGD (LOGD_DEVICE, "activation-stage: schedule %s,v%c (id %u)",
+		       _activation_func_to_string (func),
+		       nm_utils_addr_family_to_char (addr_family),
+		       new_id);
 	}
 
 	act_data->func = func;
@@ -4600,42 +5015,18 @@ activation_source_schedule (NMDevice *self, ActivationHandleFunc func, int famil
 }
 
 static gboolean
-activation_source_is_scheduled (NMDevice *self, ActivationHandleFunc func, int family)
+activation_source_is_scheduled (NMDevice *self,
+                                ActivationHandleFunc func,
+                                int addr_family)
 {
 	ActivationHandleData *act_data;
 
-	act_data = activation_source_get_by_family (self, family, NULL);
+	act_data = activation_source_get_by_family (self, addr_family, NULL);
 	return act_data->func == func;
 }
 
 /*****************************************************************************/
 
-static gboolean
-get_ip_config_may_fail (NMDevice *self, int family)
-{
-	NMConnection *connection;
-	NMSettingIPConfig *s_ip = NULL;
-
-	g_return_val_if_fail (self != NULL, TRUE);
-
-	connection = nm_device_get_applied_connection (self);
-	g_assert (connection);
-
-	/* Fail the connection if the failed IP method is required to complete */
-	switch (family) {
-	case AF_INET:
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-		break;
-	case AF_INET6:
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-		break;
-	default:
-		g_assert_not_reached ();
-	}
-
-	return !s_ip || nm_setting_ip_config_get_may_fail (s_ip);
-}
-
 static void
 master_ready (NMDevice *self,
               NMActiveConnection *active)
@@ -4836,7 +5227,7 @@ activate_stage2_device_config (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret;
 	gboolean no_firmware = FALSE;
-	GSList *iter;
+	CList *iter;
 
 	nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
@@ -4863,8 +5254,8 @@ activate_stage2_device_config (NMDevice *self)
 	}
 
 	/* If we have slaves that aren't yet enslaved, do that now */
-	for (iter = priv->slaves; iter; iter = g_slist_next (iter)) {
-		SlaveInfo *info = iter->data;
+	c_list_for_each (iter, &priv->slaves) {
+		SlaveInfo *info = c_list_entry (iter, SlaveInfo, lst_slave);
 		NMDeviceState slave_state = nm_device_get_state (info->slave);
 
 		if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
@@ -4930,97 +5321,21 @@ nm_device_activate_schedule_stage2_device_config (NMDevice *self)
 	activation_source_schedule (self, activate_stage2_device_config, AF_INET);
 }
 
-/*
- * check_ip_state
- *
- * Transition the device from IP_CONFIG to the next state according to the
- * outcome of IPv4 and IPv6 configuration. @may_fail indicates that we are
- * called just after the initial configuration and thus IPv4/IPv6 are allowed to
- * fail if the ipvx.may-fail properties say so, because the IP methods couldn't
- * even be started.
- */
-static void
-check_ip_state (NMDevice *self, gboolean may_fail)
-{
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean ip4_disabled = FALSE, ip6_ignore = FALSE;
-	NMSettingIPConfig *s_ip4, *s_ip6;
-	NMDeviceState state;
-
-	if (nm_device_get_state (self) != NM_DEVICE_STATE_IP_CONFIG)
-		return;
-
-	s_ip4 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP4_CONFIG);
-	if (s_ip4 && nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
-	                        NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-		ip4_disabled = TRUE;
-
-	s_ip6 = (NMSettingIPConfig *) nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
-	if (s_ip6 && nm_streq0 (nm_setting_ip_config_get_method (s_ip6),
-	                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
-		ip6_ignore = TRUE;
-
-	if (   priv->ip4_state == IP_DONE
-	    && priv->ip6_state == IP_DONE) {
-		/* Both method completed (or disabled), proceed with activation */
-		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
-		return;
-	}
-
-	if (   (priv->ip4_state == IP_FAIL || (ip4_disabled && priv->ip4_state == IP_DONE))
-	    && (priv->ip6_state == IP_FAIL || (ip6_ignore && priv->ip6_state == IP_DONE))) {
-		/* Either both methods failed, or only one failed and the other is
-		 * disabled */
-		if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-			/* We have assumed configuration, but couldn't redo it. No problem,
-			 * move to check state. */
-			_set_ip_state (self, AF_INET, IP_DONE);
-			_set_ip_state (self, AF_INET6, IP_DONE);
-			state = NM_DEVICE_STATE_IP_CHECK;
-		} else if (   may_fail
-		           && get_ip_config_may_fail (self, AF_INET)
-		           && get_ip_config_may_fail (self, AF_INET6)) {
-			/* Couldn't start either IPv6 and IPv4 autoconfiguration,
-			 * but both are allowed to fail. */
-			state = NM_DEVICE_STATE_SECONDARIES;
-		} else {
-			/* Autoconfiguration attempted without success. */
-			state = NM_DEVICE_STATE_FAILED;
-		}
-
-		nm_device_state_changed (self,
-		                         state,
-		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-		return;
-	}
-
-	/* If a method is still pending but required, wait */
-	if (priv->ip4_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET))
-		return;
-	if (priv->ip6_state != IP_DONE && !get_ip_config_may_fail (self, AF_INET6))
-		return;
-
-	/* If at least a method has completed, proceed with activation */
-	if (   (priv->ip4_state == IP_DONE && !ip4_disabled)
-	    || (priv->ip6_state == IP_DONE && !ip6_ignore)) {
-		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
-		return;
-	}
-}
-
 void
-nm_device_ip_method_failed (NMDevice *self, int family, NMDeviceStateReason reason)
+nm_device_ip_method_failed (NMDevice *self,
+                            int addr_family,
+                            NMDeviceStateReason reason)
 {
 	NMDevicePrivate *priv;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
-	g_return_if_fail (family == AF_INET || family == AF_INET6);
+	g_return_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
-	_set_ip_state (self, family, IP_FAIL);
+	_set_ip_state (self, addr_family, IP_FAIL);
 
-	if (get_ip_config_may_fail (self, family))
+	if (get_ip_config_may_fail (self, addr_family))
 		check_ip_state (self, FALSE);
 	else
 		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
@@ -5077,7 +5392,7 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 	NMIP4Config *empty;
 
 	if (success) {
-		empty = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+		empty = _ip4_config_new (self);
 		nm_device_activate_schedule_ip4_config_result (self, empty);
 		g_object_unref (empty);
 	} else {
@@ -5091,17 +5406,17 @@ arping_manager_probe_terminated (NMArpingManager *arping_manager, ArpingData *da
 {
 	NMDevice *self;
 	NMDevicePrivate *priv;
+	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP4Address *address;
 	gboolean result, success = TRUE;
-	int i, j;
+	int i;
 
 	g_assert (data);
 	self = data->device;
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	for (i = 0; data->configs && data->configs[i]; i++) {
-		for (j = 0; j < nm_ip4_config_get_num_addresses (data->configs[i]); j++) {
-			address = nm_ip4_config_get_address (data->configs[i], j);
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
 			result = nm_arping_manager_check_address (arping_manager, address->address);
 			success &= result;
 
@@ -5135,13 +5450,14 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMArpingManager *arping_manager;
 	const NMPlatformIP4Address *address;
+	NMDedupMultiIter ipconf_iter;
 	ArpingData *data;
 	guint timeout;
 	gboolean ret, addr_found;
 	const guint8 *hw_addr;
 	size_t hw_addr_len = 0;
 	GError *error = NULL;
-	guint i, j;
+	guint i;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 	g_return_if_fail (configs);
@@ -5187,10 +5503,8 @@ ipv4_dad_start (NMDevice *self, NMIP4Config **configs, ArpingCallback cb)
 	data->device = self;
 
 	for (i = 0; configs[i]; i++) {
-		for (j = 0; j < nm_ip4_config_get_num_addresses (configs[i]); j++) {
-			address = nm_ip4_config_get_address (configs[i], j);
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
 			nm_arping_manager_add_address (arping_manager, address->address);
-		}
 	}
 
 	g_signal_connect_data (arping_manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
@@ -5234,7 +5548,7 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	NMPlatformIP4Address address;
 	NMPlatformIP4Route route;
 
-	config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+	config = _ip4_config_new (self);
 	g_assert (config);
 
 	memset (&address, 0, sizeof (address));
@@ -5247,8 +5561,9 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	route.network = htonl (0xE0000000L);
 	route.plen = 4;
 	route.rt_source = NM_IP_CONFIG_SOURCE_IP4LL;
-	route.metric = nm_device_get_ip4_route_metric (self);
-	nm_ip4_config_add_route (config, &route);
+	route.table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (self, AF_INET, TRUE));
+	route.metric = nm_device_get_route_metric (self, AF_INET);
+	nm_ip4_config_add_route (config, &route, NULL);
 
 	return config;
 }
@@ -5304,7 +5619,9 @@ nm_device_handle_ipv4ll_event (sd_ipv4ll *ll, int event, void *data)
 			nm_clear_g_source (&priv->ipv4ll_timeout);
 			nm_device_activate_schedule_ip4_config_result (self, config);
 		} else if (priv->ip4_state == IP_DONE) {
-			if (!ip4_config_merge_and_apply (self, config, TRUE)) {
+			g_clear_object (&priv->dev_ip4_config);
+			priv->dev_ip4_config = g_object_ref (config);
+			if (!ip4_config_merge_and_apply (self, TRUE)) {
 				_LOGE (LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
 				nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
 			}
@@ -5399,57 +5716,10 @@ ipv4ll_start (NMDevice *self)
 
 /*****************************************************************************/
 
-static gboolean
-_device_get_default_route_from_platform (NMDevice *self, int addr_family, NMPlatformIPRoute *out_route)
-{
-	gboolean success = FALSE;
-	int ifindex = nm_device_get_ip_ifindex (self);
-	GArray *routes;
-
-	if (addr_family == AF_INET)
-		routes = nm_platform_ip4_route_get_all (nm_device_get_platform (self), ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-	else
-		routes = nm_platform_ip6_route_get_all (nm_device_get_platform (self), ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-
-	if (routes) {
-		guint route_metric = G_MAXUINT32, m;
-		const NMPlatformIPRoute *route = NULL, *r;
-		guint i;
-
-		/* if there are several default routes, find the one with the best metric */
-		for (i = 0; i < routes->len; i++) {
-			if (addr_family == AF_INET) {
-				r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, i);
-				m = r->metric;
-			} else {
-				r = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, i);
-				m = nm_utils_ip6_route_metric_normalize (r->metric);
-			}
-			if (!route || m < route_metric) {
-				route = r;
-				route_metric = m;
-			}
-		}
-
-		if (route) {
-			if (addr_family == AF_INET)
-				*((NMPlatformIP4Route *) out_route) = *((NMPlatformIP4Route *) route);
-			else
-				*((NMPlatformIP6Route *) out_route) = *((NMPlatformIP6Route *) route);
-			success = TRUE;
-		}
-		g_array_free (routes, TRUE);
-	}
-	return success;
-}
-
-/*****************************************************************************/
-
 static void
 ensure_con_ip4_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	NMConnection *connection;
 
 	if (priv->con_ip4_config)
@@ -5459,10 +5729,11 @@ ensure_con_ip4_config (NMDevice *self)
 	if (!connection)
 		return;
 
-	priv->con_ip4_config = nm_ip4_config_new (ip_ifindex);
+	priv->con_ip4_config = _ip4_config_new (self);
 	nm_ip4_config_merge_setting (priv->con_ip4_config,
 	                             nm_connection_get_setting_ip4_config (connection),
-	                             nm_device_get_ip4_route_metric (self));
+	                             nm_device_get_route_table (self, AF_INET, TRUE),
+	                             nm_device_get_route_metric (self, AF_INET));
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 		/* For assumed connections ignore all addresses and routes. */
@@ -5475,7 +5746,6 @@ static void
 ensure_con_ip6_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	NMConnection *connection;
 
 	if (priv->con_ip6_config)
@@ -5485,10 +5755,11 @@ ensure_con_ip6_config (NMDevice *self)
 	if (!connection)
 		return;
 
-	priv->con_ip6_config = nm_ip6_config_new (ip_ifindex);
+	priv->con_ip6_config = _ip6_config_new (self);
 	nm_ip6_config_merge_setting (priv->con_ip6_config,
 	                             nm_connection_get_setting_ip6_config (connection),
-	                             nm_device_get_ip6_route_metric (self));
+	                             nm_device_get_route_table (self, AF_INET6, TRUE),
+	                             nm_device_get_route_metric (self, AF_INET6));
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
 		/* For assumed connections ignore all addresses and routes. */
@@ -5527,38 +5798,19 @@ dhcp4_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	}
 }
 
-static void
-_ip4_config_merge_default (gpointer value, gpointer user_data)
-{
-	NMIP4Config *src = (NMIP4Config *) value;
-	NMIP4Config *dst = (NMIP4Config *) user_data;
-
-	nm_ip4_config_merge (dst, src, NM_IP_CONFIG_MERGE_DEFAULT);
-}
-
 static gboolean
 ip4_config_merge_and_apply (NMDevice *self,
-                            NMIP4Config *config,
                             gboolean commit)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMConnection *connection;
 	gboolean success;
 	NMIP4Config *composite;
-	gboolean has_direct_route;
-	const guint32 default_route_metric = nm_device_get_ip4_route_metric (self);
-	guint32 gateway;
-	gboolean connection_has_default_route, connection_is_never_default;
-	gboolean routes_full_sync;
 	gboolean ignore_auto_routes = FALSE;
 	gboolean ignore_auto_dns = FALSE;
-	gboolean auto_method = FALSE;
-
-	/* Merge all the configs into the composite config */
-	if (config) {
-		g_clear_object (&priv->dev_ip4_config);
-		priv->dev_ip4_config = g_object_ref (config);
-	}
+	gboolean ignore_default_routes = FALSE;
+	GSList *iter;
+	gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	connection = nm_device_get_applied_connection (self);
@@ -5569,35 +5821,38 @@ ip4_config_merge_and_apply (NMDevice *self,
 			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip4);
 			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip4);
 
-			if (nm_streq0 (nm_setting_ip_config_get_method (s_ip4),
-			               NM_SETTING_IP4_CONFIG_METHOD_AUTO))
-				auto_method = TRUE;
+			/* if the connection has an explicit gateway, we also ignore
+			 * the default routes from other sources. */
+			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip4)
+			                        || nm_setting_ip_config_get_gateway (s_ip4);
 		}
 	}
 
-	composite = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+	composite = _ip4_config_new (self);
 	init_ip4_config_dns_priority (self, composite);
 
 	if (commit) {
+		if (priv->queued_ip4_config_id)
+			update_ext_ip_config (self, AF_INET, FALSE, FALSE);
 		ensure_con_ip4_config (self);
-		if (priv->queued_ip4_config_id) {
-			g_clear_object (&priv->ext_ip4_config);
-			priv->ext_ip4_config = nm_ip4_config_capture (nm_device_get_platform (self),
-			                                              nm_device_get_ip_ifindex (self),
-			                                              FALSE);
-		}
 	}
 
+	if (commit)
+		priv->default_route_metric_penalty_ip4_has = default_route_metric_penalty_detect (self);
+
 	if (priv->dev_ip4_config) {
 		nm_ip4_config_merge (composite, priv->dev_ip4_config,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
+		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+		                     default_route_metric_penalty_get (self, AF_INET));
 	}
 
-	g_slist_foreach (priv->vpn4_configs, _ip4_config_merge_default, composite);
+	for (iter = priv->vpn4_configs; iter; iter = iter->next)
+		nm_ip4_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	if (priv->ext_ip4_config)
-		nm_ip4_config_merge (composite, priv->ext_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
+		nm_ip4_config_merge (composite, priv->ext_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
@@ -5605,107 +5860,23 @@ ip4_config_merge_and_apply (NMDevice *self,
 	if (priv->wwan_ip4_config) {
 		nm_ip4_config_merge (composite, priv->wwan_ip4_config,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
+		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+		                     default_route_metric_penalty_get (self, AF_INET));
 	}
 
 	/* Merge user overrides into the composite config. For assumed connections,
 	 * con_ip4_config is empty. */
-	if (priv->con_ip4_config)
-		nm_ip4_config_merge (composite, priv->con_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
-
-	/* Add the default route.
-	 *
-	 * We keep track of the default route of a device in a private field.
-	 * NMDevice needs to know the default route at this point, because the gateway
-	 * might require a direct route (see below).
-	 *
-	 * But also, we don't want to add the default route to priv->ip4_config,
-	 * because the default route from the setting might not be the same that
-	 * NMDefaultRouteManager eventually configures (because the it might
-	 * tweak the effective metric).
-	 */
-
-	/* unless we come to a different conclusion below, we have no default route and
-	 * the route is assumed. */
-	priv->default_route.v4_has = FALSE;
-	priv->default_route.v4_is_assumed = TRUE;
-
-	if (!commit) {
-		/* during a non-commit event, we always pickup whatever is configured. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
-
-	/* a generated-assumed connection detects the default route from the platform,
-	 * but if the IP method is automatic we need to update the default route to
-	 * maintain connectivity.
-	 */
-	if (nm_device_sys_iface_state_is_external (self) && !auto_method)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	/* At this point, we treat assumed and non-assumed connections alike.
-	 * For assumed connections we do that because we still manage RA and DHCP
-	 * leases for them, so we must extend/update the default route on commits.
-	 */
-
-	connection_has_default_route
-	    = nm_default_route_manager_ip4_connection_has_default_route (nm_netns_get_default_route_manager (priv->netns),
-	                                                                 connection, &connection_is_never_default);
-
-	if (   !priv->v4_commit_first_time
-	    && connection_is_never_default) {
-		/* If the connection is explicitly configured as never-default, we enforce the (absence of the)
-		 * default-route only once. That allows the user to configure a connection as never-default,
-		 * but he can add default routes externally (via a dispatcher script) and NM will not interfere. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
-
-	/* we are about to commit (for a non-assumed connection). Enforce whatever we have
-	 * configured. */
-	priv->default_route.v4_is_assumed = FALSE;
-
-	if (!connection_has_default_route)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	if (!nm_ip4_config_get_num_addresses (composite)) {
-		/* without addresses we can have no default route. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
-
-	gateway = nm_ip4_config_get_gateway (composite);
-	if (   !nm_ip4_config_has_gateway (composite)
-	    && nm_device_get_device_type (self) != NM_DEVICE_TYPE_MODEM)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	has_direct_route = (   gateway == 0
-	                    || nm_ip4_config_destination_is_direct (composite, gateway, 32)
-	                    || nm_ip4_config_get_direct_route_for_host (composite, gateway));
-
-	priv->default_route.v4_has = TRUE;
-	memset (&priv->default_route.v4, 0, sizeof (priv->default_route.v4));
-	priv->default_route.v4.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	priv->default_route.v4.gateway = gateway;
-	priv->default_route.v4.metric = route_metric_with_penalty (self, default_route_metric);
-	priv->default_route.v4.mss = nm_ip4_config_get_mss (composite);
-
-	if (!has_direct_route) {
-		NMPlatformIP4Route r = priv->default_route.v4;
-
-		/* add a direct route to the gateway */
-		r.network = gateway;
-		r.plen = 32;
-		r.gateway = 0;
-		nm_ip4_config_add_route (composite, &r);
+	if (priv->con_ip4_config) {
+		nm_ip4_config_merge (composite, priv->con_ip4_config, NM_IP_CONFIG_MERGE_DEFAULT,
+		                     default_route_metric_penalty_get (self, AF_INET));
 	}
 
-END_ADD_DEFAULT_ROUTE:
-
-	if (priv->default_route.v4_is_assumed) {
-		/* If above does not explicitly assign a default route, we always pick up the
-		 * default route based on what is currently configured.
-		 * That means that even managed connections with never-default, can
-		 * get a default route (if configured externally).
-		 */
-		priv->default_route.v4_has = _device_get_default_route_from_platform (self, AF_INET, (NMPlatformIPRoute *) &priv->default_route.v4);
+	if (commit) {
+		nm_ip4_config_add_dependent_routes (composite,
+		                                    nm_device_get_route_table (self, AF_INET, TRUE),
+		                                    nm_device_get_route_metric (self, AF_INET),
+		                                    &ip4_dev_route_blacklist);
 	}
 
 	if (commit) {
@@ -5713,11 +5884,7 @@ END_ADD_DEFAULT_ROUTE:
 			NM_DEVICE_GET_CLASS (self)->ip4_config_pre_commit (self, composite);
 	}
 
-	routes_full_sync =    commit
-	                   && priv->v4_commit_first_time
-	                   && !nm_device_sys_iface_state_is_external_or_assume (self);
-
-	success = nm_device_set_ip4_config (self, composite, default_route_metric, commit, routes_full_sync);
+	success = nm_device_set_ip4_config (self, composite, commit, ip4_dev_route_blacklist);
 	g_object_unref (composite);
 
 	if (commit)
@@ -5728,9 +5895,14 @@ END_ADD_DEFAULT_ROUTE:
 static gboolean
 dhcp4_lease_change (NMDevice *self, NMIP4Config *config)
 {
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
 	g_return_val_if_fail (config, FALSE);
 
-	if (!ip4_config_merge_and_apply (self, config, TRUE)) {
+	g_clear_object (&priv->dev_ip4_config);
+	priv->dev_ip4_config = g_object_ref (config);
+
+	if (!ip4_config_merge_and_apply (self, TRUE)) {
 		_LOGW (LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
 		return FALSE;
 	}
@@ -5750,15 +5922,13 @@ dhcp4_restart_cb (gpointer user_data)
 {
 	NMDevice *self = user_data;
 	NMDevicePrivate *priv;
-	NMConnection *connection;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 	priv->dhcp4.restart_id = 0;
-	connection = nm_device_get_applied_connection (self);
 
-	if (dhcp4_start (self, connection) == NM_ACT_STAGE_RETURN_FAILURE)
+	if (dhcp4_start (self) == NM_ACT_STAGE_RETURN_FAILURE)
 		dhcp_schedule_restart (self, AF_INET, NULL);
 
 	return FALSE;
@@ -5825,7 +5995,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 	NMIP4Config *manual, **configs;
 	NMConnection *connection;
 
-	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == FALSE);
+	g_return_if_fail (nm_dhcp_client_get_addr_family (client) == AF_INET);
 	g_return_if_fail (!ip4_config || NM_IS_IP4_CONFIG (ip4_config));
 
 	_LOGD (LOGD_DHCP4, "new DHCPv4 client state %d", state);
@@ -5850,10 +6020,11 @@ dhcp4_state_changed (NMDhcpClient *client,
 			connection = nm_device_get_applied_connection (self);
 			g_assert (connection);
 
-			manual = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+			manual = _ip4_config_new (self);
 			nm_ip4_config_merge_setting (manual,
 			                             nm_connection_get_setting_ip4_config (connection),
-			                             nm_device_get_ip4_route_metric (self));
+			                             nm_device_get_route_table (self, AF_INET, TRUE),
+			                             nm_device_get_route_metric (self, AF_INET));
 
 			configs = g_new0 (NMIP4Config *, 3);
 			configs[0] = manual;
@@ -5885,36 +6056,60 @@ dhcp4_state_changed (NMDhcpClient *client,
 }
 
 static int
-dhcp4_get_timeout (NMDevice *self, NMSettingIP4Config *s_ip4)
+get_dhcp_timeout (NMDevice *self, int addr_family)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gs_free char *value = NULL;
-	int timeout;
+	NMDeviceClass *klass;
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	guint32 timeout;
 
-	timeout = nm_setting_ip_config_get_dhcp_timeout (NM_SETTING_IP_CONFIG (s_ip4));
-	if (timeout)
-		return timeout;
+	nm_assert (NM_IS_DEVICE (self));
+	nm_assert_addr_family (addr_family);
 
-	value = nm_config_data_get_connection_default (NM_CONFIG_GET_DATA,
-	                                               "ipv4.dhcp-timeout",
-	                                               self);
-	timeout = _nm_utils_ascii_str_to_int64 (value, 10,
-	                                        0, G_MAXINT32, 0);
+	connection = nm_device_get_applied_connection (self);
+
+	if (addr_family == AF_INET)
+		s_ip = nm_connection_get_setting_ip4_config (connection);
+	else
+		s_ip = nm_connection_get_setting_ip6_config (connection);
+
+	timeout = nm_setting_ip_config_get_dhcp_timeout (s_ip);
 	if (timeout)
 		return timeout;
 
-	return priv->dhcp_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;
+	}
+
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (klass->get_dhcp_timeout)
+		timeout = klass->get_dhcp_timeout (self, addr_family);
+
+	return timeout ?: NM_DHCP_TIMEOUT_DEFAULT;
 }
 
 static NMActStageReturn
-dhcp4_start (NMDevice *self,
-             NMConnection *connection)
+dhcp4_start (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip4;
 	const guint8 *hw_addr;
 	size_t hw_addr_len = 0;
 	GByteArray *tmp = NULL;
+	NMConnection *connection;
+
+	connection = nm_device_get_applied_connection (self);
+	g_return_val_if_fail (connection, FALSE);
 
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 
@@ -5931,16 +6126,18 @@ dhcp4_start (NMDevice *self,
 	/* Begin DHCP on the interface */
 	g_warn_if_fail (priv->dhcp4.client == NULL);
 	priv->dhcp4.client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
+	                                                nm_netns_get_multi_idx (nm_device_get_netns (self)),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
 	                                                tmp,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_ip4_route_metric (self),
+	                                                nm_device_get_route_table (self, AF_INET, TRUE),
+	                                                nm_device_get_route_metric (self, AF_INET),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip4),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip4),
 	                                                nm_setting_ip4_config_get_dhcp_fqdn (NM_SETTING_IP4_CONFIG (s_ip4)),
 	                                                nm_setting_ip4_config_get_dhcp_client_id (NM_SETTING_IP4_CONFIG (s_ip4)),
-	                                                dhcp4_get_timeout (self, NM_SETTING_IP4_CONFIG (s_ip4)),
+	                                                get_dhcp_timeout (self, AF_INET),
 	                                                priv->dhcp_anycast_address,
 	                                                NULL);
 
@@ -5968,7 +6165,6 @@ gboolean
 nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMConnection *connection;
 
 	g_return_val_if_fail (priv->dhcp4.client != NULL, FALSE);
 
@@ -5977,11 +6173,8 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 	/* Terminate old DHCP instance and release the old lease */
 	dhcp4_cleanup (self, CLEANUP_TYPE_DECONFIGURE, release);
 
-	connection = nm_device_get_applied_connection (self);
-	g_return_val_if_fail (connection, FALSE);
-
 	/* Start DHCP again on the interface */
-	return dhcp4_start (self, connection) != NM_ACT_STAGE_RETURN_FAILURE;
+	return dhcp4_start (self) != NM_ACT_STAGE_RETURN_FAILURE;
 }
 
 /*****************************************************************************/
@@ -5989,66 +6182,63 @@ nm_device_dhcp4_renew (NMDevice *self, gboolean release)
 static GHashTable *shared_ips = NULL;
 
 static void
-release_shared_ip (gpointer data)
+shared_ip_release (gpointer data)
 {
 	g_hash_table_remove (shared_ips, data);
+	if (!g_hash_table_size (shared_ips))
+		g_clear_pointer (&shared_ips, g_hash_table_unref);
 }
 
-static gboolean
-reserve_shared_ip (NMDevice *self, NMSettingIPConfig *s_ip4, NMPlatformIP4Address *address)
+static NMIP4Config *
+shared4_new_config (NMDevice *self, NMConnection *connection)
 {
-	if (G_UNLIKELY (shared_ips == NULL))
-		shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
+	NMIP4Config *config = NULL;
+	gboolean is_generated = FALSE;
+	NMSettingIPConfig *s_ip4;
+	NMPlatformIP4Address address = {
+		.addr_source = NM_IP_CONFIG_SOURCE_SHARED,
+	};
 
-	memset (address, 0, sizeof (*address));
+	g_return_val_if_fail (self, NULL);
+	g_return_val_if_fail (connection, NULL);
 
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	if (s_ip4 && nm_setting_ip_config_get_num_addresses (s_ip4)) {
 		/* Use the first user-supplied address */
 		NMIPAddress *user = nm_setting_ip_config_get_address (s_ip4, 0);
 		in_addr_t a;
 
-		g_assert (user);
 		nm_ip_address_get_address_binary (user, &a);
-		nm_platform_ip4_address_set_addr (address, a, nm_ip_address_get_prefix (user));
+		nm_platform_ip4_address_set_addr (&address, a, nm_ip_address_get_prefix (user));
 	} else {
 		/* Find an unused address in the 10.42.x.x range */
 		guint32 start = (guint32) ntohl (0x0a2a0001); /* 10.42.0.1 */
 		guint32 count = 0;
 
-		while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
-			count += ntohl (0x100);
-			if (count > ntohl (0xFE00)) {
-				_LOGE (LOGD_SHARING, "ran out of shared IP addresses!");
-				return FALSE;
+		if (G_UNLIKELY (!shared_ips))
+			shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
+		else {
+			while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
+				count += ntohl (0x100);
+				if (count > ntohl (0xFE00)) {
+					_LOGE (LOGD_SHARING, "ran out of shared IP addresses!");
+					return FALSE;
+				}
 			}
 		}
-		nm_platform_ip4_address_set_addr (address, start + count, 24);
-		g_hash_table_add (shared_ips, GUINT_TO_POINTER (address->address));
+		nm_platform_ip4_address_set_addr (&address, start + count, 24);
+		g_hash_table_add (shared_ips, GUINT_TO_POINTER (address.address));
+		is_generated = TRUE;
 	}
 
-	return TRUE;
-}
-
-static NMIP4Config *
-shared4_new_config (NMDevice *self, NMConnection *connection)
-{
-	NMIP4Config *config = NULL;
-	NMPlatformIP4Address address;
-
-	g_return_val_if_fail (self != NULL, NULL);
-
-	if (!reserve_shared_ip (self, nm_connection_get_setting_ip4_config (connection), &address))
-		return NULL;
-
-	config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
-	address.addr_source = NM_IP_CONFIG_SOURCE_SHARED;
+	config = _ip4_config_new (self);
 	nm_ip4_config_add_address (config, &address);
-
-	/* Remove the address lock when the object gets disposed */
-	g_object_set_qdata_full (G_OBJECT (config), NM_CACHED_QUARK ("shared-ip"),
-	                        GUINT_TO_POINTER (address.address),
-	                        release_shared_ip);
-
+	if (is_generated) {
+		/* Remove the address lock when the object gets disposed */
+		g_object_set_qdata_full (G_OBJECT (config), NM_CACHED_QUARK ("shared-ip"),
+		                         GUINT_TO_POINTER (address.address),
+		                         shared_ip_release);
+	}
 	return config;
 }
 
@@ -6092,9 +6282,16 @@ static gboolean
 connection_requires_carrier (NMConnection *connection)
 {
 	NMSettingIPConfig *s_ip4, *s_ip6;
+	NMSettingConnection *s_con;
 	gboolean ip4_carrier_wanted, ip6_carrier_wanted;
 	gboolean ip4_used = FALSE, ip6_used = FALSE;
 
+	/* We can progress to IP_CONFIG now, so that we're enslaved.
+	 * That may actually cause carrier to go up and thus continue acivation. */
+	s_con = nm_connection_get_setting_connection (connection);
+	if (nm_setting_connection_get_master (s_con))
+		return FALSE;
+
 	ip4_carrier_wanted = connection_ip4_method_requires_carrier (connection, &ip4_used);
 	if (ip4_carrier_wanted) {
 		/* If IPv4 wants a carrier and cannot fail, the whole connection
@@ -6128,16 +6325,19 @@ connection_requires_carrier (NMConnection *connection)
 }
 
 static gboolean
-have_any_ready_slaves (NMDevice *self, const GSList *slaves)
+have_any_ready_slaves (NMDevice *self)
 {
-	const GSList *iter;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	SlaveInfo *info;
+	CList *iter;
 
 	/* Any enslaved slave is "ready" in the generic case as it's
 	 * at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2
 	 * properties set up.
 	 */
-	for (iter = slaves; iter; iter = g_slist_next (iter)) {
-		if (nm_device_get_enslaved (iter->data))
+	c_list_for_each (iter, &priv->slaves) {
+		info = c_list_entry (iter, SlaveInfo, lst_slave);
+		if (NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
 			return TRUE;
 	}
 	return FALSE;
@@ -6161,29 +6361,23 @@ act_stage3_ip4_config_start (NMDevice *self,
 	NMConnection *connection;
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	const char *method;
-	GSList *slaves;
-	gboolean ready_slaves;
 
 	connection = nm_device_get_applied_connection (self);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	if (   connection_ip4_method_requires_carrier (connection, NULL)
-	    && priv->is_master
+	    && nm_device_is_master (self)
 	    && !priv->carrier) {
 		_LOGI (LOGD_IP4 | LOGD_DEVICE,
 		       "IPv4 config waiting until carrier is on");
 		return NM_ACT_STAGE_RETURN_IP_WAIT;
 	}
 
-	if (priv->is_master && ip4_requires_slaves (connection)) {
+	if (nm_device_is_master (self) && ip4_requires_slaves (connection)) {
 		/* If the master has no ready slaves, and depends on slaves for
 		 * a successful IPv4 attempt, then postpone IPv4 addressing.
 		 */
-		slaves = nm_device_master_get_slaves (self);
-		ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves);
-		g_slist_free (slaves);
-
-		if (ready_slaves == FALSE) {
+		if (!have_any_ready_slaves (self)) {
 			_LOGI (LOGD_DEVICE | LOGD_IP4,
 			       "IPv4 config waiting until slaves are ready");
 			return NM_ACT_STAGE_RETURN_IP_WAIT;
@@ -6195,7 +6389,7 @@ act_stage3_ip4_config_start (NMDevice *self,
 
 	/* Start IPv4 addressing based on the method requested */
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0) {
-		ret = dhcp4_start (self, connection);
+		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) {
@@ -6205,10 +6399,11 @@ act_stage3_ip4_config_start (NMDevice *self,
 	} else if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL) == 0) {
 		NMIP4Config **configs, *config;
 
-		config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+		config = _ip4_config_new (self);
 		nm_ip4_config_merge_setting (config,
 		                             nm_connection_get_setting_ip4_config (connection),
-		                             nm_device_get_ip4_route_metric (self));
+		                             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;
@@ -6266,15 +6461,6 @@ dhcp6_cleanup (NMDevice *self, CleanupType cleanup_type, gboolean release)
 	}
 }
 
-static void
-_ip6_config_merge_default (gpointer value, gpointer user_data)
-{
-	NMIP6Config *src = (NMIP6Config *) value;
-	NMIP6Config *dst = (NMIP6Config *) user_data;
-
-	nm_ip6_config_merge (dst, src, NM_IP_CONFIG_MERGE_DEFAULT);
-}
-
 static gboolean
 ip6_config_merge_and_apply (NMDevice *self,
                             gboolean commit)
@@ -6283,14 +6469,11 @@ ip6_config_merge_and_apply (NMDevice *self,
 	NMConnection *connection;
 	gboolean success;
 	NMIP6Config *composite;
-	gboolean has_direct_route;
-	const struct in6_addr *gateway;
-	gboolean connection_has_default_route, connection_is_never_default;
-	gboolean routes_full_sync;
 	gboolean ignore_auto_routes = FALSE;
 	gboolean ignore_auto_dns = FALSE;
-	gboolean auto_method = FALSE;
+	gboolean ignore_default_routes = FALSE;
 	const char *token = NULL;
+	GSList *iter;
 
 	/* Apply ignore-auto-routes and ignore-auto-dns settings */
 	connection = nm_device_get_applied_connection (self);
@@ -6303,17 +6486,17 @@ ip6_config_merge_and_apply (NMDevice *self,
 			ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes (s_ip6);
 			ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns (s_ip6);
 
+			/* if the connection has an explicit gateway, we also ignore
+			 * the default routes from other sources. */
+			ignore_default_routes =    nm_setting_ip_config_get_never_default (s_ip6)
+			                        || nm_setting_ip_config_get_gateway (s_ip6);
+
 			if (nm_setting_ip6_config_get_addr_gen_mode (ip6) == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
 				token = nm_setting_ip6_config_get_token (ip6);
-
-			if (NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
-			                  NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-			                  NM_SETTING_IP6_CONFIG_METHOD_DHCP))
-				auto_method = TRUE;
 		}
 	}
 
-	composite = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+	composite = _ip6_config_new (self);
 	nm_ip6_config_set_privacy (composite,
 	                           priv->ndisc ?
 	                           priv->ndisc_use_tempaddr :
@@ -6321,35 +6504,35 @@ ip6_config_merge_and_apply (NMDevice *self,
 	init_ip6_config_dns_priority (self, composite);
 
 	if (commit) {
+		if (priv->queued_ip6_config_id)
+			update_ext_ip_config (self, AF_INET6, FALSE, FALSE);
 		ensure_con_ip6_config (self);
-		if (priv->queued_ip6_config_id) {
-			g_clear_object (&priv->ext_ip6_config);
-			g_clear_object (&priv->ext_ip6_config_captured);
-			priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_platform (self),
-			                                                       nm_device_get_ip_ifindex (self),
-			                                                       FALSE,
-			                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
-			if (priv->ext_ip6_config_captured)
-				priv->ext_ip6_config = nm_ip6_config_new_cloned (priv->ext_ip6_config_captured);
-		}
 	}
 
+	if (commit)
+		priv->default_route_metric_penalty_ip6_has = default_route_metric_penalty_detect (self);
+
 	/* Merge all the IP configs into the composite config */
 	if (priv->ac_ip6_config) {
 		nm_ip6_config_merge (composite, priv->ac_ip6_config,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
+		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+		                     default_route_metric_penalty_get (self, AF_INET6));
 	}
 	if (priv->dhcp6.ip6_config) {
 		nm_ip6_config_merge (composite, priv->dhcp6.ip6_config,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
+		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+		                     default_route_metric_penalty_get (self, AF_INET6));
 	}
 
-	g_slist_foreach (priv->vpn6_configs, _ip6_config_merge_default, composite);
+	for (iter = priv->vpn6_configs; iter; iter = iter->next)
+		nm_ip6_config_merge (composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	if (priv->ext_ip6_config)
-		nm_ip6_config_merge (composite, priv->ext_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT);
+		nm_ip6_config_merge (composite, priv->ext_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
 
 	/* Merge WWAN config *last* to ensure modem-given settings overwrite
 	 * any external stuff set by pppd or other scripts.
@@ -6357,108 +6540,34 @@ ip6_config_merge_and_apply (NMDevice *self,
 	if (priv->wwan_ip6_config) {
 		nm_ip6_config_merge (composite, priv->wwan_ip6_config,
 		                       (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0));
-	}
-
-	/* Merge user overrides into the composite config. For assumed connections,
-	 * con_ip6_config is empty. */
-	if (priv->con_ip6_config)
-		nm_ip6_config_merge (composite, priv->con_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT);
-
-	/* Add the default route.
-	 *
-	 * We keep track of the default route of a device in a private field.
-	 * NMDevice needs to know the default route at this point, because the gateway
-	 * might require a direct route (see below).
-	 *
-	 * But also, we don't want to add the default route to priv->ip6_config,
-	 * because the default route from the setting might not be the same that
-	 * NMDefaultRouteManager eventually configures (because the it might
-	 * tweak the effective metric).
-	 */
-
-	/* unless we come to a different conclusion below, we have no default route and
-	 * the route is assumed. */
-	priv->default_route.v6_has = FALSE;
-	priv->default_route.v6_is_assumed = TRUE;
-
-	if (!commit) {
-		/* during a non-commit event, we always pickup whatever is configured. */
-		goto END_ADD_DEFAULT_ROUTE;
+		                     | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
+		                     | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
+		                     default_route_metric_penalty_get (self, AF_INET6));
 	}
 
-	/* a generated-assumed connection detects the default route from the platform,
-	 * but if the IP method is automatic we need to update the default route to
-	 * maintain connectivity.
-	 */
-	if (nm_device_sys_iface_state_is_external (self) && !auto_method)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	/* At this point, we treat assumed and non-assumed connections alike.
-	 * For assumed connections we do that because we still manage RA and DHCP
-	 * leases for them, so we must extend/update the default route on commits.
-	 */
-
-	connection_has_default_route
-	    = nm_default_route_manager_ip6_connection_has_default_route (nm_netns_get_default_route_manager (priv->netns),
-	                                                                 connection, &connection_is_never_default);
-
-	if (   !priv->v6_commit_first_time
-	    && connection_is_never_default) {
-		/* If the connection is explicitly configured as never-default, we enforce the (absence of the)
-		 * default-route only once. That allows the user to configure a connection as never-default,
-		 * but he can add default routes externally (via a dispatcher script) and NM will not interfere. */
-		goto END_ADD_DEFAULT_ROUTE;
-	}
+	if (priv->rt6_temporary_not_available) {
+		const NMPObject *o;
+		GHashTableIter hiter;
 
-	/* we are about to commit (for a non-assumed connection). Enforce whatever we have
-	 * configured. */
-	priv->default_route.v6_is_assumed = FALSE;
-
-	if (!connection_has_default_route)
-		goto END_ADD_DEFAULT_ROUTE;
-
-	if (!nm_ip6_config_get_num_addresses (composite)) {
-		/* without addresses we can have no default route. */
-		goto END_ADD_DEFAULT_ROUTE;
+		g_hash_table_iter_init (&hiter, priv->rt6_temporary_not_available);
+		while (g_hash_table_iter_next (&hiter, (gpointer *) &o, NULL)) {
+			nm_ip6_config_add_route (composite,
+			                         NMP_OBJECT_CAST_IP6_ROUTE (o),
+			                         NULL);
+		}
 	}
 
-	gateway = nm_ip6_config_get_gateway (composite);
-	if (!gateway)
-		goto END_ADD_DEFAULT_ROUTE;
-
-
-	has_direct_route = nm_ip6_config_get_direct_route_for_host (composite, gateway) != NULL;
-
-
-
-	priv->default_route.v6_has = TRUE;
-	memset (&priv->default_route.v6, 0, sizeof (priv->default_route.v6));
-	priv->default_route.v6.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	priv->default_route.v6.gateway = *gateway;
-	priv->default_route.v6.metric = route_metric_with_penalty (self,
-	                                                           nm_device_get_ip6_route_metric (self));
-	priv->default_route.v6.mss = nm_ip6_config_get_mss (composite);
-
-	if (!has_direct_route) {
-		NMPlatformIP6Route r = priv->default_route.v6;
-
-		/* add a direct route to the gateway */
-		r.network = *gateway;
-		r.plen = 128;
-		r.gateway = in6addr_any;
-		nm_ip6_config_add_route (composite, &r);
+	/* Merge user overrides into the composite config. For assumed connections,
+	 * con_ip6_config is empty. */
+	if (priv->con_ip6_config) {
+		nm_ip6_config_merge (composite, priv->con_ip6_config, NM_IP_CONFIG_MERGE_DEFAULT,
+		                     default_route_metric_penalty_get (self, AF_INET6));
 	}
 
-END_ADD_DEFAULT_ROUTE:
-
-	if (priv->default_route.v6_is_assumed) {
-		/* If above does not explicitly assign a default route, we always pick up the
-		 * default route based on what is currently configured.
-		 * That means that even managed connections with never-default, can
-		 * get a default route (if configured externally).
-		 */
-		priv->default_route.v6_has = _device_get_default_route_from_platform (self, AF_INET6, (NMPlatformIPRoute *) &priv->default_route.v6);
+	if (commit) {
+		nm_ip6_config_add_dependent_routes (composite,
+		                                    nm_device_get_route_table (self, AF_INET6, TRUE),
+		                                    nm_device_get_route_metric (self, AF_INET6));
 	}
 
 	/* Allow setting MTU etc */
@@ -6472,11 +6581,7 @@ END_ADD_DEFAULT_ROUTE:
 		}
 	}
 
-	routes_full_sync =    commit
-	                   && priv->v6_commit_first_time
-	                   && !nm_device_sys_iface_state_is_external_or_assume (self);
-
-	success = nm_device_set_ip6_config (self, composite, commit, routes_full_sync);
+	success = nm_device_set_ip6_config (self, composite, commit);
 	g_object_unref (composite);
 	if (commit)
 		priv->v6_commit_first_time = FALSE;
@@ -6533,28 +6638,30 @@ dhcp6_restart_cb (gpointer user_data)
 }
 
 static void
-dhcp_schedule_restart (NMDevice *self, int family, const char *reason)
+dhcp_schedule_restart (NMDevice *self,
+                       int addr_family,
+                       const char *reason)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean inet4;
 	guint tries_left;
-	gs_free char *tries_str = NULL;
+	char tries_str[255];
 
-	g_return_if_fail (family == AF_INET || family == AF_INET6);
-	inet4 = family == AF_INET;
+	nm_assert_addr_family (addr_family);
 
-	tries_left = inet4 ? priv->dhcp4.num_tries_left : priv->dhcp6.num_tries_left;
-	if (tries_left != DHCP_NUM_TRIES_MAX)
-		tries_str = g_strdup_printf (", %u tries left", tries_left + 1);
+	tries_left =   (addr_family == AF_INET)
+	             ? priv->dhcp4.num_tries_left
+	             : priv->dhcp6.num_tries_left;
 
-	_LOGI (inet4 ? LOGD_DHCP4 : LOGD_DHCP6,
+	_LOGI ((addr_family == AF_INET) ? LOGD_DHCP4 : LOGD_DHCP6,
 	       "scheduling DHCPv%c restart in %u seconds%s%s%s%s",
-	       inet4 ? '4' : '6',
+	       nm_utils_addr_family_to_char (addr_family),
 	       DHCP_RESTART_TIMEOUT,
-	       tries_str ? tries_str : "",
+	       (tries_left != DHCP_NUM_TRIES_MAX)
+	         ? nm_sprintf_buf (tries_str, ", %u tries left", tries_left + 1)
+	         : "",
 	       NM_PRINT_FMT_QUOTED (reason, " (reason: ", reason, ")", ""));
 
-	if (inet4) {
+	if (addr_family == AF_INET) {
 		priv->dhcp4.restart_id = g_timeout_add_seconds (DHCP_RESTART_TIMEOUT,
 		                                                dhcp4_restart_cb, self);
 	} else {
@@ -6631,9 +6738,8 @@ dhcp6_state_changed (NMDhcpClient *client,
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint i;
 
-	g_return_if_fail (nm_dhcp_client_get_ipv6 (client) == TRUE);
+	g_return_if_fail (nm_dhcp_client_get_addr_family (client) == AF_INET6);
 	g_return_if_fail (!ip6_config || NM_IS_IP6_CONFIG (ip6_config));
 
 	_LOGD (LOGD_DHCP6, "new DHCPv6 client state %d", state);
@@ -6648,10 +6754,11 @@ dhcp6_state_changed (NMDhcpClient *client,
 		    && event_id
 		    && priv->dhcp6.event_id
 		    && !strcmp (event_id, priv->dhcp6.event_id)) {
-			for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) {
-				nm_ip6_config_add_address (priv->dhcp6.ip6_config,
-				                           nm_ip6_config_get_address (ip6_config, i));
-			}
+			NMDedupMultiIter ipconf_iter;
+			const NMPlatformIP6Address *a;
+
+			nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6_config, &a)
+				nm_ip6_config_add_address (priv->dhcp6.ip6_config, a);
 		} else {
 			g_clear_object (&priv->dhcp6.ip6_config);
 			g_clear_pointer (&priv->dhcp6.event_id, g_free);
@@ -6741,15 +6848,17 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	}
 
 	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
+	                                                nm_device_get_multi_index (self),
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
 	                                                tmp,
 	                                                &ll_addr->address,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_ip6_route_metric (self),
+	                                                nm_device_get_route_table (self, AF_INET6, TRUE),
+	                                                nm_device_get_route_metric (self, AF_INET6),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
-	                                                priv->dhcp_timeout,
+	                                                get_dhcp_timeout (self, AF_INET6),
 	                                                priv->dhcp_anycast_address,
 	                                                (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
 	                                                nm_setting_ip6_config_get_ip6_privacy (NM_SETTING_IP6_CONFIG (s_ip6)),
@@ -6769,7 +6878,7 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	}
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self))
-		priv->dhcp4.was_active = TRUE;
+		priv->dhcp6.was_active = TRUE;
 
 	return !!priv->dhcp6.client;
 }
@@ -6869,7 +6978,7 @@ nm_device_use_ip6_subnet (NMDevice *self, const NMPlatformIP6Address *subnet)
 	NMPlatformIP6Address address = *subnet;
 
 	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+		priv->ac_ip6_config = _ip6_config_new (self);
 
 	/* Assign a ::1 address in the subnet for us. */
 	address.address.s6_addr32[3] |= htonl (1);
@@ -6899,7 +7008,7 @@ nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device)
 		nm_ip6_config_reset_nameservers (priv->ac_ip6_config);
 		nm_ip6_config_reset_searches (priv->ac_ip6_config);
 	} else
-		priv->ac_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+		priv->ac_ip6_config = _ip6_config_new (self);
 
 	if (from_device)
 		from_config = nm_device_get_ip6_config (from_device);
@@ -6990,7 +7099,6 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ip_ifindex = nm_device_get_ip_ifindex (self);
 	struct in6_addr lladdr;
-	guint i, n;
 	NMConnection *connection;
 	NMSettingIP6Config *s_ip6 = NULL;
 	GError *error = NULL;
@@ -6999,11 +7107,10 @@ check_and_add_ipv6ll_addr (NMDevice *self)
 		return;
 
 	if (priv->ip6_config) {
-		n = nm_ip6_config_get_num_addresses (priv->ip6_config);
-		for (i = 0; i < n; i++) {
-			const NMPlatformIP6Address *addr;
+		NMDedupMultiIter ipconf_iter;
+		const NMPlatformIP6Address *addr;
 
-			addr = nm_ip6_config_get_address (priv->ip6_config, i);
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, priv->ip6_config, &addr) {
 			if (   IN6_IS_ADDR_LINKLOCAL (&addr->address)
 			    && !(addr->n_ifa_flags & IFA_F_DADFAILED)) {
 				/* Already have an LL address, nothing to do */
@@ -7152,6 +7259,26 @@ nm_device_get_configured_mtu_for_wired (NMDevice *self, gboolean *out_is_user_co
 /*****************************************************************************/
 
 static void
+_set_mtu (NMDevice *self, guint32 mtu)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->mtu == mtu)
+		return;
+
+	priv->mtu = mtu;
+	_notify (self, PROP_MTU);
+
+	if (priv->master) {
+		/* changing the MTU of a slave, might require the master to reset
+		 * it's MTU. Note that the master usually cannot set a MTU larger
+		 * then the slave's. Hence, when the slave increases the MTU,
+		 * master might want to retry setting the MTU. */
+		nm_device_commit_mtu (priv->master);
+	}
+}
+
+static void
 _commit_mtu (NMDevice *self, const NMIP4Config *config)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -7170,8 +7297,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		return;
 
 	if (nm_device_sys_iface_state_is_external_or_assume (self)) {
-		/* for assumed connections we don't tamper with the MTU. This is
-		 * a bug and supposed to be fixed by the unmanaged/assumed rework. */
+		/* for assumed connections we don't tamper with the MTU. */
 		return;
 	}
 
@@ -7271,6 +7397,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	})
 	if (   (mtu_desired && mtu_desired != mtu_plat)
 	    || (ip6_mtu && ip6_mtu != _IP6_MTU_SYS ())) {
+		gboolean anticipated_failure = FALSE;
 
 		if (!priv->mtu_initial && !priv->ip6_mtu_initial) {
 			/* before touching any of the MTU paramters, record the
@@ -7279,100 +7406,102 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 			priv->ip6_mtu_initial = _IP6_MTU_SYS ();
 		}
 
-		if (mtu_desired && mtu_desired != mtu_plat)
-			nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired);
+		if (mtu_desired && mtu_desired != mtu_plat) {
+			if (nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, mtu_desired) == NM_PLATFORM_ERROR_CANT_SET_MTU) {
+				anticipated_failure = TRUE;
+				_LOGW (LOGD_DEVICE, "mtu: failure to set MTU. %s",
+				       NM_IS_DEVICE_VLAN (self)
+				         ? "Is the parent's MTU size large enough?"
+				         : (!c_list_is_empty (&priv->slaves)
+				              ? "Are the MTU sizes of the slaves large enough?"
+				              : "Did you configure the MTU correctly?"));
+			}
+			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+		}
 
 		if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS ()) {
-			nm_device_ipv6_sysctl_set (self, "mtu",
-			                           nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu));
+			if (!nm_device_ipv6_sysctl_set (self, "mtu",
+			                                nm_sprintf_buf (sbuf, "%u", (unsigned) ip6_mtu))) {
+				int errsv = errno;
+
+				_NMLOG (anticipated_failure && errsv == EINVAL ? LOGL_DEBUG : LOGL_WARN,
+				        LOGD_DEVICE,
+				        "mtu: failure to set IPv6 MTU%s",
+				        anticipated_failure && errsv == EINVAL
+				           ? ": Is the underlying MTU value successfully set?"
+				           : "");
+			}
+			priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
 		}
 	}
 #undef _IP6_MTU_SYS
 }
 
+void
+nm_device_commit_mtu (NMDevice *self)
+{
+	NMDeviceState state;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	state = nm_device_get_state (self);
+	if (   state >= NM_DEVICE_STATE_CONFIG
+	    && state < NM_DEVICE_STATE_DEACTIVATING) {
+		_LOGT (LOGD_DEVICE, "mtu: commit-mtu...");
+		_commit_mtu (self, NM_DEVICE_GET_PRIVATE (self)->ip4_config);
+	} else
+		_LOGT (LOGD_DEVICE, "mtu: commit-mtu... skip due to state %s", nm_device_state_to_str (state));
+}
+
 static void
 ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, NMDevice *self)
 {
 	NMNDiscConfigMap changed = changed_int;
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int i;
-	int system_support;
-	guint32 ifa_flags = 0x00;
-
-	/*
-	 * Check, whether kernel is recent enough to help user space handling RA.
-	 * If it's not supported, we have no ipv6-privacy and must add autoconf
-	 * addresses as /128. The reason for the /128 is to prevent the kernel
-	 * from adding a prefix route for this address.
-	 **/
-	system_support = nm_platform_check_support_kernel_extended_ifa_flags (nm_device_get_platform (self));
-
-	if (system_support)
-		ifa_flags = IFA_F_NOPREFIXROUTE;
-	if (   priv->ndisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR
-	    || priv->ndisc_use_tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
-	{
-		/* without system_support, this flag will be ignored. Still set it, doesn't seem to do any harm. */
-		ifa_flags |= IFA_F_MANAGETEMPADDR;
-	}
+	guint i;
 
 	g_return_if_fail (priv->act_request);
 
 	if (!priv->ac_ip6_config)
-		priv->ac_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
-
-	if (changed & NM_NDISC_CONFIG_GATEWAYS) {
-		/* Use the first gateway as ordered in neighbor discovery cache. */
-		if (rdata->gateways_n)
-			nm_ip6_config_set_gateway (priv->ac_ip6_config, &rdata->gateways[0].address);
-		else
-			nm_ip6_config_set_gateway (priv->ac_ip6_config, NULL);
-	}
+		priv->ac_ip6_config = _ip6_config_new (self);
 
 	if (changed & NM_NDISC_CONFIG_ADDRESSES) {
-		/* Rebuild address list from neighbor discovery cache. */
-		nm_ip6_config_reset_addresses (priv->ac_ip6_config);
-
-		/* ndisc->addresses contains at most max_addresses entries.
-		 * This is different from what the kernel does, which
-		 * also counts static and temporary addresses when checking
-		 * max_addresses.
-		 **/
-		for (i = 0; i < rdata->addresses_n; i++) {
-			const NMNDiscAddress *discovered_address = &rdata->addresses[i];
-			NMPlatformIP6Address address;
-
-			memset (&address, 0, sizeof (address));
-			address.address = discovered_address->address;
-			address.plen = system_support ? 64 : 128;
-			address.timestamp = discovered_address->timestamp;
-			address.lifetime = discovered_address->lifetime;
-			address.preferred = discovered_address->preferred;
-			if (address.preferred > address.lifetime)
-				address.preferred = address.lifetime;
-			address.addr_source = NM_IP_CONFIG_SOURCE_NDISC;
-			address.n_ifa_flags = ifa_flags;
+		guint8 plen;
+		guint32 ifa_flags;
+
+		/* Check, whether kernel is recent enough to help user space handling RA.
+		 * If it's not supported, we have no ipv6-privacy and must add autoconf
+		 * addresses as /128. The reason for the /128 is to prevent the kernel
+		 * from adding a prefix route for this address. */
+		ifa_flags = 0;
+		if (nm_platform_check_kernel_support (nm_device_get_platform (self),
+		                                      NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+			ifa_flags |= IFA_F_NOPREFIXROUTE;
+			if (NM_IN_SET (priv->ndisc_use_tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+			                                         NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
+				ifa_flags |= IFA_F_MANAGETEMPADDR;
+			plen = 64;
+		} else
+			plen = 128;
 
-			nm_ip6_config_add_address (priv->ac_ip6_config, &address);
-		}
+		nm_ip6_config_reset_addresses_ndisc (priv->ac_ip6_config,
+		                                     rdata->addresses,
+		                                     rdata->addresses_n,
+		                                     plen,
+		                                     ifa_flags);
 	}
 
-	if (changed & NM_NDISC_CONFIG_ROUTES) {
-		/* Rebuild route list from neighbor discovery cache. */
-		nm_ip6_config_reset_routes (priv->ac_ip6_config);
-
-		for (i = 0; i < rdata->routes_n; i++) {
-			const NMNDiscRoute *discovered_route = &rdata->routes[i];
-			const NMPlatformIP6Route route = {
-				.network    = discovered_route->network,
-				.plen       = discovered_route->plen,
-				.gateway    = discovered_route->gateway,
-				.rt_source  = NM_IP_CONFIG_SOURCE_NDISC,
-				.metric     = nm_device_get_ip6_route_metric (self),
-			};
-
-			nm_ip6_config_add_route (priv->ac_ip6_config, &route);
-		}
+	if (NM_FLAGS_ANY (changed,   NM_NDISC_CONFIG_ROUTES
+	                           | NM_NDISC_CONFIG_GATEWAYS)) {
+		nm_ip6_config_reset_routes_ndisc (priv->ac_ip6_config,
+		                                  rdata->gateways,
+		                                  rdata->gateways_n,
+		                                  rdata->routes,
+		                                  rdata->routes_n,
+		                                  nm_device_get_route_table (self, AF_INET6, TRUE),
+		                                  nm_device_get_route_metric (self, AF_INET6),
+		                                  nm_platform_check_kernel_support (nm_device_get_platform (self),
+		                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
 	}
 
 	if (changed & NM_NDISC_CONFIG_DNS_SERVERS) {
@@ -7465,8 +7594,10 @@ addrconf6_start_with_link_ready (NMDevice *self)
 	}
 
 	/* Apply any manual configuration before starting RA */
-	if (!ip6_config_merge_and_apply (self, TRUE))
+	if (!ip6_config_merge_and_apply (self, TRUE)) {
 		_LOGW (LOGD_IP6, "failed to apply manual IPv6 configuration");
+		g_clear_object (&priv->con_ip6_config);
+	}
 
 	/* XXX: These sysctls would probably be better set by the lndp ndisc itself. */
 	switch (nm_ndisc_get_node_type (priv->ndisc)) {
@@ -7537,6 +7668,9 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 		priv->ac_ip6_config = NULL;
 	}
 
+	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
+
 	s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection));
 	g_assert (s_ip6);
 
@@ -7559,7 +7693,8 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 	priv->ndisc_use_tempaddr = use_tempaddr;
 
 	if (   NM_IN_SET (use_tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
-	    && !nm_platform_check_support_kernel_extended_ifa_flags (nm_device_get_platform (self))) {
+	    && !nm_platform_check_kernel_support (nm_device_get_platform (self),
+	                                          NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
 		_LOGW (LOGD_IP6, "The kernel does not support extended IFA_FLAGS needed by NM for "
 		                 "IPv6 private addresses. This feature is not available");
 	}
@@ -7590,6 +7725,8 @@ addrconf6_cleanup (NMDevice *self)
 	nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE);
 
 	g_clear_object (&priv->ac_ip6_config);
+	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
 	g_clear_object (&priv->ndisc);
 }
 
@@ -7616,8 +7753,13 @@ save_ip6_properties (NMDevice *self)
 
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
+	if (!nm_device_get_ip_ifindex (self))
+		return;
+
 	for (i = 0; i < G_N_ELEMENTS (ip6_properties_to_save); i++) {
-		value = nm_platform_sysctl_get (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (ifname, ip6_properties_to_save[i])));
+		char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
+		value = nm_platform_sysctl_get (nm_device_get_platform (self), NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, ifname, ip6_properties_to_save[i])));
 		if (value) {
 			g_hash_table_insert (priv->ip6_saved_properties,
 			                     (char *) ip6_properties_to_save[i],
@@ -7657,7 +7799,8 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 	int ifindex = nm_device_get_ip_ifindex (self);
 	char *value;
 
-	if (!nm_platform_check_support_user_ipv6ll (nm_device_get_platform (self)))
+	if (!nm_platform_check_kernel_support (nm_device_get_platform (self),
+	                                       NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL))
 		return;
 
 	priv->nm_ipv6ll = enable;
@@ -7672,13 +7815,15 @@ set_nm_ipv6ll (NMDevice *self, gboolean enable)
 			        LOGD_IP6,
 			        "failed to %s userspace IPv6LL address handling (%s)",
 			        detail,
-			        nm_platform_error_to_string (plerr));
+			        nm_platform_error_to_string_a (plerr));
 		}
 
 		if (enable) {
+			char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
 			/* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
 			value = nm_platform_sysctl_get (nm_device_get_platform (self),
-			                                NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (nm_device_get_ip_iface (self), "disable_ipv6")));
+			                                NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, nm_device_get_ip_iface (self), "disable_ipv6")));
 			if (g_strcmp0 (value, "0") == 0)
 				nm_device_ipv6_sysctl_set (self, "disable_ipv6", "1");
 			g_free (value);
@@ -7739,6 +7884,9 @@ _ip6_privacy_get (NMDevice *self)
 	if (ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN)
 		return ip6_privacy;
 
+	if (!nm_device_get_ip_ifindex (self))
+		return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;;
+
 	/* 3.) No valid default-value configured. Fallback to reading sysctl.
 	 *
 	 * Instead of reading static config files in /etc, just read the current sysctl value.
@@ -7775,29 +7923,23 @@ act_stage3_ip6_config_start (NMDevice *self,
 	const char *method;
 	NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 	const char *ip6_privacy_str = "0";
-	GSList *slaves;
-	gboolean ready_slaves;
 
 	connection = nm_device_get_applied_connection (self);
 	g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
 
 	if (   connection_ip6_method_requires_carrier (connection, NULL)
-	    && priv->is_master
+	    && nm_device_is_master (self)
 	    && !priv->carrier) {
 		_LOGI (LOGD_IP6 | LOGD_DEVICE,
 		       "IPv6 config waiting until carrier is on");
 		return NM_ACT_STAGE_RETURN_IP_WAIT;
 	}
 
-	if (priv->is_master && ip6_requires_slaves (connection)) {
+	if (nm_device_is_master (self) && ip6_requires_slaves (connection)) {
 		/* If the master has no ready slaves, and depends on slaves for
 		 * a successful IPv6 attempt, then postpone IPv6 addressing.
 		 */
-		slaves = nm_device_master_get_slaves (self);
-		ready_slaves = NM_DEVICE_GET_CLASS (self)->have_any_ready_slaves (self, slaves);
-		g_slist_free (slaves);
-
-		if (ready_slaves == FALSE) {
+		if (!have_any_ready_slaves (self)) {
 			_LOGI (LOGD_DEVICE | LOGD_IP6,
 			       "IPv6 config waiting until slaves are ready");
 			return NM_ACT_STAGE_RETURN_IP_WAIT;
@@ -7810,7 +7952,8 @@ act_stage3_ip6_config_start (NMDevice *self,
 	method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
 
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
-		if (!priv->master) {
+		if (   !priv->master
+		    && !nm_device_sys_iface_state_is_external (self)) {
 			gboolean old_nm_ipv6ll = priv->nm_ipv6ll;
 
 			/* When activating an IPv6 'ignore' connection we need to revert back
@@ -7847,7 +7990,8 @@ act_stage3_ip6_config_start (NMDevice *self,
 	 */
 	nm_platform_process_events (nm_device_get_platform (self));
 	g_clear_object (&priv->ext_ip6_config_captured);
-	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_platform (self),
+	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
+	                                                       nm_device_get_platform (self),
 	                                                       nm_device_get_ip_ifindex (self),
 	                                                       FALSE,
 	                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
@@ -7911,17 +8055,11 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 
 	g_assert (priv->ip4_state == IP_WAIT);
 
-	/* Slaves stay in IP_CONFIG state until master is ready, and then
-	 * they go directly to SECONDARIES without configuring IPv4.
-	 */
-	if (nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request)))
-		return TRUE;
-
 	_set_ip_state (self, AF_INET, IP_CONF);
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		if (!ip4_config)
-			ip4_config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+			ip4_config = _ip4_config_new (self);
 		nm_device_activate_schedule_ip4_config_result (self, ip4_config);
 		g_object_unref (ip4_config);
 	} else if (ret == NM_ACT_STAGE_RETURN_IP_DONE) {
@@ -7958,17 +8096,11 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 
 	g_assert (priv->ip6_state == IP_WAIT);
 
-	/* Slaves stay in IP_CONFIG state until master is ready, and then
-	 * they go directly to SECONDARIES without configuring IPv6.
-	 */
-	if (nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request)))
-		return TRUE;
-
 	_set_ip_state (self, AF_INET6, IP_CONF);
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
 		if (!ip6_config)
-			ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+			ip6_config = _ip6_config_new (self);
 		/* Here we get a static IPv6 config, like for Shared where it's
 		 * autogenerated or from modems where it comes from ModemManager.
 		 */
@@ -8002,39 +8134,18 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 static void
 activate_stage3_ip_config_start (NMDevice *self)
 {
-	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	NMActiveConnection *master;
-	NMDevice *master_device;
-
 	_set_ip_state (self, AF_INET, IP_WAIT);
 	_set_ip_state (self, AF_INET6, IP_WAIT);
 
+	_active_connection_set_state_flags (self,
+	                                    NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
+
 	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
 	/* Device should be up before we can do anything with it */
 	if (!nm_platform_link_is_up (nm_device_get_platform (self), nm_device_get_ip_ifindex (self)))
 		_LOGW (LOGD_DEVICE, "interface %s not up for IP configuration", nm_device_get_ip_iface (self));
 
-	/* If the device is a slave, then we don't do any IP configuration but we
-	 * use the IP config stage to indicate to the master we're ready for
-	 * enslavement.  If the master is already activating, it will have tried to
-	 * enslave us when we changed state to IP_CONFIG, causing us to queue a
-	 * transition to SECONDARIES (or FAILED if the enslavement failed), with
-	 * our IP states set to IP_DONE either way.  If the master isn't yet
-	 * activating, then they'll still be in IP_WAIT.  Either way, we bail out
-	 * of IP config here.
-	 */
-	master = nm_active_connection_get_master (NM_ACTIVE_CONNECTION (priv->act_request));
-	if (master) {
-		master_device = nm_active_connection_get_device (master);
-		if (priv->ip4_state == IP_WAIT && priv->ip6_state == IP_WAIT) {
-			_LOGI (LOGD_DEVICE, "Activation: connection '%s' waiting on master '%s'",
-			       nm_connection_get_id (nm_device_get_applied_connection (self)),
-			       master_device ? nm_device_get_iface (master_device) : "(unknown)");
-		}
-		return;
-	}
-
 	/* IPv4 */
 	if (   nm_device_activate_ip4_state_in_wait (self)
 	    && !nm_device_activate_stage3_ip4_start (self))
@@ -8309,18 +8420,20 @@ start_sharing (NMDevice *self, NMIP4Config *config)
 	char str_addr[INET_ADDRSTRLEN + 1];
 	char str_mask[INET_ADDRSTRLEN + 1];
 	guint32 netmask, network;
-	const NMPlatformIP4Address *ip4_addr;
+	const NMPlatformIP4Address *ip4_addr = NULL;
 	const char *ip_iface;
 
 	g_return_val_if_fail (config != NULL, FALSE);
 
 	ip_iface = nm_device_get_ip_iface (self);
+	if (!ip_iface)
+		return FALSE;
 
-	ip4_addr = nm_ip4_config_get_address (config, 0);
+	ip4_addr = nm_ip4_config_get_first_address (config);
 	if (!ip4_addr || !ip4_addr->address)
 		return FALSE;
 
-	netmask = nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
+	netmask = _nm_utils_ip4_prefix_to_netmask (ip4_addr->plen);
 	if (!inet_ntop (AF_INET, &netmask, str_mask, sizeof (str_mask)))
 		return FALSE;
 
@@ -8420,7 +8533,7 @@ arp_announce (NMDevice *self)
 }
 
 static void
-activate_stage5_ip4_config_commit (NMDevice *self)
+activate_stage5_ip4_config_result (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActRequest *req;
@@ -8442,7 +8555,7 @@ activate_stage5_ip4_config_commit (NMDevice *self)
 	}
 
 	/* NULL to use the existing priv->dev_ip4_config */
-	if (!ip4_config_merge_and_apply (self, NULL, TRUE)) {
+	if (!ip4_config_merge_and_apply (self, TRUE)) {
 		_LOGD (LOGD_DEVICE | LOGD_IP4, "Activation: Stage 5 of 5 (IPv4 Commit) failed");
 		nm_device_ip_method_failed (self, AF_INET, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 		return;
@@ -8492,7 +8605,7 @@ nm_device_activate_schedule_ip4_config_result (NMDevice *self, NMIP4Config *conf
 	if (config)
 		priv->dev_ip4_config = g_object_ref (config);
 
-	activation_source_schedule (self, activate_stage5_ip4_config_commit, AF_INET);
+	activation_source_schedule (self, activate_stage5_ip4_config_result, AF_INET);
 }
 
 gboolean
@@ -8530,7 +8643,8 @@ dad6_get_pending_addresses (NMDevice *self)
 	                         priv->wwan_ip6_config };
 	const NMPlatformIP6Address *addr, *pl_addr;
 	NMIP6Config *dad6_config = NULL;
-	guint i, j, num;
+	NMDedupMultiIter ipconf_iter;
+	guint i;
 	int ifindex;
 
 	ifindex = nm_device_get_ip_ifindex (self);
@@ -8541,13 +8655,11 @@ dad6_get_pending_addresses (NMDevice *self)
 	 */
 	for (i = 0; i < G_N_ELEMENTS (confs); i++) {
 		if (confs[i]) {
-			num = nm_ip6_config_get_num_addresses (confs[i]);
-			for (j = 0; j < num; j++) {
-				addr = nm_ip6_config_get_address (confs[i], j);
+
+			nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) {
 				pl_addr = nm_platform_ip6_address_get (nm_device_get_platform (self),
 				                                       ifindex,
-				                                       addr->address,
-				                                       addr->plen);
+				                                       addr->address);
 				if (   pl_addr
 				    && NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
 				    && !NM_FLAGS_HAS (pl_addr->n_ifa_flags, IFA_F_DADFAILED)
@@ -8556,7 +8668,7 @@ dad6_get_pending_addresses (NMDevice *self)
 					       nm_platform_ip6_address_to_string (pl_addr, NULL, 0));
 
 					if (!dad6_config)
-						dad6_config = nm_ip6_config_new (ifindex);
+						dad6_config = _ip6_config_new (self);
 
 					nm_ip6_config_add_address (dad6_config, pl_addr);
 				}
@@ -8584,6 +8696,8 @@ activate_stage5_ip6_config_commit (NMDevice *self)
 
 	/* Interface must be IFF_UP before IP config can be applied */
 	ip_ifindex = nm_device_get_ip_ifindex (self);
+	g_return_if_fail (ip_ifindex);
+
 	if (!nm_platform_link_is_up (nm_device_get_platform (self), ip_ifindex) && !nm_device_sys_iface_state_is_external_or_assume (self)) {
 		nm_platform_link_set_up (nm_device_get_platform (self), ip_ifindex, NULL);
 		if (!nm_platform_link_is_up (nm_device_get_platform (self), ip_ifindex))
@@ -8756,16 +8870,15 @@ static void
 _update_ip4_address (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint32 addr;
+	const NMPlatformIP4Address *address;
 
 	g_return_if_fail (NM_IS_DEVICE (self));
 
 	if (   priv->ip4_config
 	    && ip_config_valid (priv->state)
-	    && nm_ip4_config_get_num_addresses (priv->ip4_config)) {
-		addr = nm_ip4_config_get_address (priv->ip4_config, 0)->address;
-		if (addr != priv->ip4_address) {
-			priv->ip4_address = addr;
+	    && (address = nm_ip4_config_get_first_address (priv->ip4_config))) {
+		if (address->address != priv->ip4_address) {
+			priv->ip4_address = address->address;
 			_notify (self, PROP_IP4_ADDRESS);
 		}
 	}
@@ -8802,7 +8915,7 @@ delete_on_deactivate_link_delete (gpointer user_data)
 
 		if (!nm_device_unrealize (data->device, TRUE, &error))
 			_LOGD (LOGD_DEVICE, "delete_on_deactivate: unrealizing %d failed (%s)", data->ifindex, error->message);
-	} else
+	} else if (data->ifindex > 0)
 		nm_platform_link_delete (nm_device_get_platform (self), data->ifindex);
 
 	g_free (data);
@@ -8833,8 +8946,6 @@ delete_on_deactivate_check_and_schedule (NMDevice *self, int ifindex)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	DeleteOnDeactivateData *data;
 
-	if (ifindex <= 0)
-		return;
 	if (!priv->nm_owned)
 		return;
 	if (priv->queued_act_request)
@@ -8905,7 +9016,7 @@ _nm_device_hash_check_invalid_keys (GHashTable *hash, const char *setting_name,
 #if NM_MORE_ASSERTS > 10
 	/* Assert that the keys are unique. */
 	{
-		gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+		gs_unref_hashtable GHashTable *check_dups = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
 
 		for (i = 0; argv[i]; i++) {
 			if (!nm_g_hash_table_add (check_dups, (char *) argv[i]))
@@ -8971,10 +9082,11 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 	if (priv->ip4_state != IP_NONE) {
 		g_clear_object (&priv->con_ip4_config);
 		g_clear_object (&priv->ext_ip4_config);
-		priv->con_ip4_config = nm_ip4_config_new (nm_device_get_ip_ifindex (self));
+		priv->con_ip4_config = _ip4_config_new (self);
 		nm_ip4_config_merge_setting (priv->con_ip4_config,
 		                             s_ip4_new,
-		                             nm_device_get_ip4_route_metric (self));
+		                             nm_device_get_route_table (self, AF_INET, TRUE),
+		                             nm_device_get_route_metric (self, AF_INET));
 
 		if (!force_restart) {
 			method_old = s_ip4_old
@@ -8992,7 +9104,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 			if (!nm_device_activate_stage3_ip4_start (self))
 				_LOGW (LOGD_IP4, "Failed to apply IPv4 configuration");
 		} else {
-			if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+			if (!ip4_config_merge_and_apply (self, TRUE))
 				_LOGW (LOGD_IP4, "Failed to reapply IPv4 configuration");
 		}
 	}
@@ -9013,10 +9125,11 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 	if (priv->ip6_state != IP_NONE) {
 		g_clear_object (&priv->con_ip6_config);
 		g_clear_object (&priv->ext_ip6_config);
-		priv->con_ip6_config = nm_ip6_config_new (nm_device_get_ip_ifindex (self));
+		priv->con_ip6_config = _ip6_config_new (self);
 		nm_ip6_config_merge_setting (priv->con_ip6_config,
 		                             s_ip6_new,
-		                             nm_device_get_ip6_route_metric (self));
+		                             nm_device_get_route_table (self, AF_INET6, TRUE),
+		                             nm_device_get_route_metric (self, AF_INET6));
 
 		if (!force_restart) {
 			method_old = s_ip6_old
@@ -9096,7 +9209,27 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 	                         NM_SETTING_IP4_CONFIG_SETTING_NAME,
 	                         NM_SETTING_IP6_CONFIG_SETTING_NAME,
 	                         NM_SETTING_PROXY_SETTING_NAME)) {
-		/* accept all */
+		if (g_hash_table_contains (diffs, NM_SETTING_IP_CONFIG_ROUTE_TABLE)) {
+			/* changing the route-table setting is complicated, because it affects
+			 * how we sync the routes. Don't support changing it without full
+			 * re-activation.
+			 *
+			 * The problem is really that changing the setting also affects the sync
+			 * mode. So, switching from NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN to
+			 * NM_IP_ROUTE_TABLE_SYNC_MODE_FULL would somehow require us to get rid
+			 * of additional routes, but we don't know which routes were added by NM
+			 * and which should be removed.
+			 *
+			 * Note how nm_device_get_route_table() caches the value for the duration of the
+			 * activation. */
+			g_set_error (error,
+			             NM_DEVICE_ERROR,
+			             NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+			             "Can't reapply changes to '%s.%s' setting",
+			             setting_name,
+			             NM_SETTING_IP_CONFIG_ROUTE_TABLE);
+			return FALSE;
+		}
 		return TRUE;
 	} else {
 		g_set_error (error,
@@ -9484,6 +9617,97 @@ impl_device_get_applied_connection (NMDevice *self,
 
 /*****************************************************************************/
 
+typedef struct {
+	gint64 timestamp_ms;
+	bool dirty;
+} IP6RoutesTemporaryNotAvailableData;
+
+static gboolean
+_rt6_temporary_not_available_timeout (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->rt6_temporary_not_available_id = 0;
+	nm_device_activate_schedule_ip6_config_result (self);
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_rt6_temporary_not_available_set (NMDevice *self,
+                                  GPtrArray *temporary_not_available)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	IP6RoutesTemporaryNotAvailableData *data;
+	GHashTableIter iter;
+	gint64 now_ms, oldest_ms;
+	const gint64 MAX_AGE_MS = 20000;
+	guint i;
+	gboolean success = TRUE;
+
+	if (   !temporary_not_available
+	    || !temporary_not_available->len) {
+		/* nothing outstanding. Clear tracking the routes. */
+		g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+		nm_clear_g_source (&priv->rt6_temporary_not_available_id);
+		return success;
+	}
+
+	if (priv->rt6_temporary_not_available) {
+		g_hash_table_iter_init (&iter, priv->rt6_temporary_not_available);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data))
+			data->dirty = TRUE;
+	} else {
+		priv->rt6_temporary_not_available = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
+		                                                           (GEqualFunc) nmp_object_id_equal,
+		                                                           (GDestroyNotify) nmp_object_unref,
+		                                                           nm_g_slice_free_fcn (IP6RoutesTemporaryNotAvailableData));
+	}
+
+	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	oldest_ms = now_ms;
+
+	for (i = 0; i < temporary_not_available->len; i++) {
+		const NMPObject *o = temporary_not_available->pdata[i];
+
+		data = g_hash_table_lookup (priv->rt6_temporary_not_available, o);
+		if (data) {
+			if (!data->dirty)
+				continue;
+			data->dirty = FALSE;
+			nm_assert (data->timestamp_ms > 0 && data->timestamp_ms <= now_ms);
+			if (now_ms > data->timestamp_ms + MAX_AGE_MS) {
+				/* timeout. Could not add this address. */
+				_LOGW (LOGD_DEVICE, "failure to add IPv6 route: %s",
+				       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+				success = FALSE;
+			} else
+				oldest_ms = MIN (data->timestamp_ms, oldest_ms);
+			continue;
+		}
+
+		data = g_slice_new0 (IP6RoutesTemporaryNotAvailableData);
+		data->timestamp_ms = now_ms;
+		g_hash_table_insert (priv->rt6_temporary_not_available, (gpointer) nmp_object_ref (o), data);
+	}
+
+	g_hash_table_iter_init (&iter, priv->rt6_temporary_not_available);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data)) {
+		if (data->dirty)
+			g_hash_table_iter_remove (&iter);
+	}
+
+	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
+	priv->rt6_temporary_not_available_id = g_timeout_add (oldest_ms + MAX_AGE_MS - now_ms,
+	                                                      _rt6_temporary_not_available_timeout,
+	                                                      self);
+
+	return success;
+}
+
+/*****************************************************************************/
+
 static void
 disconnect_cb (NMDevice *self,
                GDBusMethodInvocation *context,
@@ -9835,47 +10059,43 @@ nm_device_get_ip4_config (NMDevice *self)
 static gboolean
 nm_device_set_ip4_config (NMDevice *self,
                           NMIP4Config *new_config,
-                          guint32 default_route_metric,
                           gboolean commit,
-                          gboolean routes_full_sync)
+                          GPtrArray *ip4_dev_route_blacklist)
 {
 	NMDevicePrivate *priv;
 	NMIP4Config *old_config = NULL;
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
-	gboolean def_route_changed;
-	int ip_ifindex, config_ifindex;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	_LOGD (LOGD_IP4, "ip4-config: update (commit=%d, routes-full-sync=%d, new-config=%p)",
-	       commit, routes_full_sync, new_config);
+	_LOGD (LOGD_IP4, "ip4-config: update (commit=%d, new-config=%p)",
+	       commit, new_config);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	ip_ifindex = nm_device_get_ip_ifindex (self);
+	nm_assert (   !new_config
+	           || (   new_config
+	               && ({
+	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
 
-	if (new_config) {
-		config_ifindex = nm_ip4_config_get_ifindex (new_config);
-		if (config_ifindex > 0)
-			g_return_val_if_fail (ip_ifindex == config_ifindex, FALSE);
-	}
+	                    (   ip_ifindex > 0
+	                     && ip_ifindex == nm_ip4_config_get_ifindex (new_config));
+	                  })));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	old_config = priv->ip4_config;
 
 	/* Always commit to nm-platform to update lifetimes */
 	if (commit && new_config) {
-		gboolean assumed = nm_device_sys_iface_state_is_external_or_assume (self);
-
 		_commit_mtu (self, new_config);
-		/* For assumed devices we must not touch the kernel-routes, such as the device-route.
-		 * FIXME: this is wrong in case where "assumed" means "take-over-seamlessly". In this
-		 * case, we should manage the device route, for example on new DHCP lease. */
 		success = nm_ip4_config_commit (new_config,
 		                                nm_device_get_platform (self),
-		                                nm_netns_get_route_manager (priv->netns),
-		                                ip_ifindex,
-		                                routes_full_sync,
-		                                assumed ? (gint64) -1 : (gint64) default_route_metric);
+		                                nm_device_get_route_table (self, AF_INET, FALSE)
+		                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+		                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN);
+		nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self),
+		                                         nm_ip4_config_get_ifindex (new_config),
+		                                         ip4_dev_route_blacklist);
 	}
 
 	if (new_config) {
@@ -9906,7 +10126,6 @@ nm_device_set_ip4_config (NMDevice *self,
 		g_clear_object (&priv->dev_ip4_config);
 	}
 
-	def_route_changed = nm_default_route_manager_ip4_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
 	concheck_periodic_update (self);
 
 	if (!nm_device_sys_iface_state_is_external_or_assume (self))
@@ -9940,9 +10159,6 @@ nm_device_set_ip4_config (NMDevice *self,
 		}
 
 		nm_device_queue_recheck_assume (self);
-	} else if (def_route_changed) {
-		_LOGD (LOGD_IP4, "ip4-config: default route changed");
-		g_signal_emit (self, signals[IP4_CONFIG_CHANGED], 0, priv->ip4_config, priv->ip4_config);
 	}
 
 	return success;
@@ -9986,11 +10202,16 @@ nm_device_replace_vpn4_config (NMDevice *self, NMIP4Config *old, NMIP4Config *co
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	nm_assert (!old || NM_IS_IP4_CONFIG (old));
+	nm_assert (!config || NM_IS_IP4_CONFIG (config));
+	nm_assert (!old || nm_ip4_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
+	nm_assert (!config || nm_ip4_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
+
 	if (!_replace_vpn_config_in_list (&priv->vpn4_configs, (GObject *) old, (GObject *) config))
 		return;
 
 	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+	if (!ip4_config_merge_and_apply (self, TRUE))
 		_LOGW (LOGD_IP4, "failed to set VPN routes for device");
 }
 
@@ -10007,47 +10228,53 @@ nm_device_set_wwan_ip4_config (NMDevice *self, NMIP4Config *config)
 		priv->wwan_ip4_config = g_object_ref (config);
 
 	/* NULL to use existing configs */
-	if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+	if (!ip4_config_merge_and_apply (self, TRUE))
 		_LOGW (LOGD_IP4, "failed to set WWAN IPv4 configuration");
 }
 
 static gboolean
 nm_device_set_ip6_config (NMDevice *self,
                           NMIP6Config *new_config,
-                          gboolean commit,
-                          gboolean routes_full_sync)
+                          gboolean commit)
 {
 	NMDevicePrivate *priv;
 	NMIP6Config *old_config = NULL;
 	gboolean has_changes = FALSE;
 	gboolean success = TRUE;
-	gboolean def_route_changed;
-	int ip_ifindex, config_ifindex;
 
 	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
 
-	_LOGD (LOGD_IP6, "ip6-config: update (commit=%d, routes-full-sync=%d, new-config=%p)",
-	       commit, routes_full_sync, new_config);
+	_LOGD (LOGD_IP6, "ip6-config: update (commit=%d, new-config=%p)",
+	       commit, new_config);
 
-	priv = NM_DEVICE_GET_PRIVATE (self);
-	ip_ifindex = nm_device_get_ip_ifindex (self);
+	nm_assert (   !new_config
+	           || (   new_config
+	               && ({
+	                    int ip_ifindex = nm_device_get_ip_ifindex (self);
 
-	if (new_config) {
-		config_ifindex = nm_ip6_config_get_ifindex (new_config);
-		if (config_ifindex > 0)
-			g_return_val_if_fail (ip_ifindex == config_ifindex, FALSE);
-	}
+	                    (   ip_ifindex > 0
+	                     && ip_ifindex == nm_ip6_config_get_ifindex (new_config));
+	                  })));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
 
 	old_config = priv->ip6_config;
 
 	/* Always commit to nm-platform to update lifetimes */
 	if (commit && new_config) {
+		gs_unref_ptrarray GPtrArray *temporary_not_available = NULL;
+
 		_commit_mtu (self, priv->ip4_config);
+
 		success = nm_ip6_config_commit (new_config,
 		                                nm_device_get_platform (self),
-		                                nm_netns_get_route_manager (priv->netns),
-		                                ip_ifindex,
-		                                routes_full_sync);
+		                                nm_device_get_route_table (self, AF_INET6, FALSE)
+		                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+		                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
+		                                &temporary_not_available);
+
+		if (!_rt6_temporary_not_available_set (self, temporary_not_available))
+			success = FALSE;
 	}
 
 	if (new_config) {
@@ -10077,8 +10304,6 @@ nm_device_set_ip6_config (NMDevice *self,
 		       nm_exported_object_get_path (NM_EXPORTED_OBJECT (old_config)));
 	}
 
-	def_route_changed = nm_default_route_manager_ip6_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-
 	if (has_changes) {
 		NMSettingsConnection *settings_connection;
 
@@ -10108,9 +10333,6 @@ nm_device_set_ip6_config (NMDevice *self,
 
 		if (priv->ndisc)
 			ndisc_set_router_config (priv->ndisc, self);
-	} else if (def_route_changed) {
-		_LOGD (LOGD_IP6, "ip6-config: default route changed");
-		g_signal_emit (self, signals[IP6_CONFIG_CHANGED], 0, priv->ip6_config, priv->ip6_config);
 	}
 
 	return success;
@@ -10121,6 +10343,11 @@ nm_device_replace_vpn6_config (NMDevice *self, NMIP6Config *old, NMIP6Config *co
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
+	nm_assert (!old || NM_IS_IP6_CONFIG (old));
+	nm_assert (!config || NM_IS_IP6_CONFIG (config));
+	nm_assert (!old || nm_ip6_config_get_ifindex (old) == nm_device_get_ip_ifindex (self));
+	nm_assert (!config || nm_ip6_config_get_ifindex (config) == nm_device_get_ip_ifindex (self));
+
 	if (!_replace_vpn_config_in_list (&priv->vpn6_configs, (GObject *) old, (GObject *) config))
 		return;
 
@@ -10370,7 +10597,7 @@ nm_device_start_ip_check (NMDevice *self)
 	NMSettingConnection *s_con;
 	guint timeout = 0;
 	const char *ping_binary = NULL;
-	char buf[INET6_ADDRSTRLEN] = { 0 };
+	char buf[NM_UTILS_INET_ADDRSTRLEN];
 	NMLogDomain log_domain = LOGD_IP4;
 
 	/* Shouldn't be any active ping here, since IP_CHECK happens after the
@@ -10389,25 +10616,24 @@ nm_device_start_ip_check (NMDevice *self)
 	g_assert (s_con);
 	timeout = nm_setting_connection_get_gateway_ping_timeout (s_con);
 
+	buf[0] = '\0';
 	if (timeout) {
-		if (priv->ip4_config && priv->ip4_state == IP_DONE) {
-			guint gw = 0;
+		const NMPObject *gw;
 
-			ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
-			log_domain = LOGD_IP4;
-
-			gw = nm_ip4_config_get_gateway (priv->ip4_config);
-			if (gw && !inet_ntop (AF_INET, &gw, buf, sizeof (buf)))
-				buf[0] = '\0';
+		if (priv->ip4_config && priv->ip4_state == IP_DONE) {
+			gw = nm_ip4_config_best_default_route_get (priv->ip4_config);
+			if (gw) {
+				nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (gw)->gateway, buf);
+				ping_binary = nm_utils_find_helper ("ping", "/usr/bin/ping", NULL);
+				log_domain = LOGD_IP4;
+			}
 		} else if (priv->ip6_config && priv->ip6_state == IP_DONE) {
-			const struct in6_addr *gw = NULL;
-
-			ping_binary = nm_utils_find_helper ("ping6", "/usr/bin/ping6", NULL);
-			log_domain = LOGD_IP6;
-
-			gw = nm_ip6_config_get_gateway (priv->ip6_config);
-			if (gw && !inet_ntop (AF_INET6, gw, buf, sizeof (buf)))
-				buf[0] = '\0';
+			gw = nm_ip6_config_best_default_route_get (priv->ip6_config);
+			if (gw) {
+				nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (gw)->gateway, buf);
+				ping_binary = nm_utils_find_helper ("ping6", "/usr/bin/ping6", NULL);
+				log_domain = LOGD_IP6;
+			}
 		}
 	}
 
@@ -10507,6 +10733,8 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 	 * a timeout is reached.
 	 */
 	if (nm_device_has_capability (self, NM_DEVICE_CAP_CARRIER_DETECT)) {
+		gint64 now_ms, until_ms;
+
 		/* we start a grace period of 5 seconds during which we will schedule
 		 * a pending action whenever we have no carrier.
 		 *
@@ -10515,7 +10743,10 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 		nm_clear_g_source (&priv->carrier_wait_id);
 		if (!priv->carrier)
 			nm_device_add_pending_action (self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
-		priv->carrier_wait_id = g_timeout_add_seconds (5, carrier_wait_timeout, self);
+
+		now_ms = nm_utils_get_monotonic_timestamp_ms ();
+		until_ms = NM_MAX (now_ms + CARRIER_WAIT_TIME_MS, priv->carrier_wait_until_ms);
+		priv->carrier_wait_id = g_timeout_add (until_ms - now_ms, carrier_wait_timeout, self);
 	}
 
 	/* Can only get HW address of some devices when they are up */
@@ -10525,7 +10756,7 @@ nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
 
 	/* when the link comes up, we must restore IP configuration if necessary. */
 	if (priv->ip4_state == IP_DONE) {
-		if (!ip4_config_merge_and_apply (self, NULL, TRUE))
+		if (!ip4_config_merge_and_apply (self, TRUE))
 			_LOGW (LOGD_IP4, "failed applying IP4 config after bringing link up");
 	}
 	if (priv->ip6_state == IP_DONE) {
@@ -10608,20 +10839,28 @@ find_ip4_lease_config (NMDevice *self,
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 
 	leases = nm_dhcp_manager_get_lease_ip_configs (nm_dhcp_manager_get (),
+	                                               nm_device_get_multi_index (self),
+	                                               AF_INET,
 	                                               ip_iface,
 	                                               ip_ifindex,
 	                                               nm_connection_get_uuid (connection),
-	                                               FALSE,
-	                                               nm_device_get_ip4_route_metric (self));
+	                                               nm_device_get_route_table (self, AF_INET, TRUE),
+	                                               nm_device_get_route_metric (self, AF_INET));
 	for (liter = leases; liter && !found; liter = liter->next) {
 		NMIP4Config *lease_config = liter->data;
-		const NMPlatformIP4Address *address = nm_ip4_config_get_address (lease_config, 0);
-		guint32 gateway = nm_ip4_config_get_gateway (lease_config);
+		const NMPlatformIP4Address *address = nm_ip4_config_get_first_address (lease_config);
+		const NMPObject *gw1, *gw2;
 
 		g_assert (address);
 		if (!nm_ip4_config_address_exists (ext_ip4_config, address))
 			continue;
-		if (gateway != nm_ip4_config_get_gateway (ext_ip4_config))
+		gw1 = nm_ip4_config_best_default_route_get (lease_config);
+		if (!gw1)
+			continue;
+		gw2 = nm_ip4_config_best_default_route_get (ext_ip4_config);
+		if (!gw2)
+			continue;
+		if (NMP_OBJECT_CAST_IP4_ROUTE (gw1)->gateway != NMP_OBJECT_CAST_IP4_ROUTE (gw2)->gateway)
 			continue;
 		found = g_object_ref (lease_config);
 	}
@@ -10641,23 +10880,24 @@ capture_lease_config (NMDevice *self,
 	NMSettingsConnection *const*connections;
 	guint i;
 	gboolean dhcp_used = FALSE;
+	NMDedupMultiIter ipconf_iter;
 
 	/* Ensure at least one address on the device has a non-infinite lifetime,
 	 * otherwise DHCP cannot possibly be active on the device right now.
 	 */
 	if (ext_ip4_config && out_ip4_config) {
-		for (i = 0; i < nm_ip4_config_get_num_addresses (ext_ip4_config); i++) {
-			const NMPlatformIP4Address *addr = nm_ip4_config_get_address (ext_ip4_config, i);
+		const NMPlatformIP4Address *addr;
 
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ext_ip4_config, &addr) {
 			if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
 				dhcp_used = TRUE;
 				break;
 			}
 		}
 	} else if (ext_ip6_config && out_ip6_config) {
-		for (i = 0; i < nm_ip6_config_get_num_addresses (ext_ip6_config); i++) {
-			const NMPlatformIP6Address *addr = nm_ip6_config_get_address (ext_ip6_config, i);
+		const NMPlatformIP6Address *addr;
 
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ext_ip6_config, &addr) {
 			if (addr->lifetime != NM_PLATFORM_LIFETIME_PERMANENT) {
 				dhcp_used = TRUE;
 				break;
@@ -10695,184 +10935,161 @@ capture_lease_config (NMDevice *self,
 	}
 }
 
-static void
-_ip4_config_intersect (gpointer value, gpointer user_data)
-{
-	NMIP4Config *dst = (NMIP4Config *) value;
-	NMIP4Config *src = (NMIP4Config *) user_data;
-
-	nm_ip4_config_intersect (dst, src);
-}
-
-static void
-_ip4_config_subtract (gpointer value, gpointer user_data)
-{
-	NMIP4Config *dst = (NMIP4Config *) user_data;
-	NMIP4Config *src = (NMIP4Config *) value;
-
-	nm_ip4_config_subtract (dst, src);
-}
-
-static void
-update_ip4_config (NMDevice *self, gboolean initial)
+static gboolean
+update_ext_ip_config (NMDevice *self, int addr_family, gboolean initial, gboolean intersect_configs)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	int ifindex;
 	gboolean capture_resolv_conf;
+	GSList *iter;
 
-	/* If a commit is scheduled, this function would potentially interfere with
-	 * it changing IP configurations before they are applied. Postpone the
-	 * update in such case.
-	 */
-	if (   !initial
-	    && activation_source_is_scheduled (self,
-	                                       activate_stage5_ip4_config_commit,
-	                                       AF_INET)) {
-		priv->queued_ip4_config_pending = FALSE;
-		priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
-		_LOGT (LOGD_DEVICE, "IP4 update was postponed");
-		return;
-	}
+	nm_assert_addr_family (addr_family);
 
 	ifindex = nm_device_get_ip_ifindex (self);
 	if (!ifindex)
-		return;
+		return FALSE;
 
 	capture_resolv_conf =    initial
 	                      && nm_dns_manager_get_resolv_conf_explicit (nm_dns_manager_get ());
 
-	/* IPv4 */
-	g_clear_object (&priv->ext_ip4_config);
-	priv->ext_ip4_config = nm_ip4_config_capture (nm_device_get_platform (self),
-	                                              ifindex,
-	                                              capture_resolv_conf);
-	if (priv->ext_ip4_config) {
-		if (initial) {
-			g_clear_object (&priv->dev_ip4_config);
-			capture_lease_config (self, priv->ext_ip4_config, &priv->dev_ip4_config, NULL, NULL);
-		}
-
-		/* FIXME: ext_ip4_config does not contain routes with source==RTPROT_KERNEL.
-		 * Hence, we will wrongly remove device-routes with metric=0 if they were added by
-		 * the user on purpose. This should be fixed by also tracking and exposing
-		 * kernel routes. */
-
-		/* This function was called upon external changes. Remove the configuration
-		 * (addresses,routes) that is no longer present externally from the internal
-		 * config. This way, we don't re-add addresses that were manually removed
-		 * by the user. */
-		if (priv->con_ip4_config)
-			nm_ip4_config_intersect (priv->con_ip4_config, priv->ext_ip4_config);
-		if (priv->dev_ip4_config)
-			nm_ip4_config_intersect (priv->dev_ip4_config, priv->ext_ip4_config);
+	if (addr_family == AF_INET) {
 
-		g_slist_foreach (priv->vpn4_configs, _ip4_config_intersect, priv->ext_ip4_config);
+		g_clear_object (&priv->ext_ip4_config);
+		priv->ext_ip4_config = nm_ip4_config_capture (nm_device_get_multi_index (self),
+		                                              nm_device_get_platform (self),
+		                                              ifindex,
+		                                              capture_resolv_conf);
+		if (priv->ext_ip4_config) {
+			if (initial) {
+				g_clear_object (&priv->dev_ip4_config);
+				capture_lease_config (self, priv->ext_ip4_config, &priv->dev_ip4_config, NULL, NULL);
+			}
 
-		if (priv->wwan_ip4_config)
-			nm_ip4_config_intersect (priv->wwan_ip4_config, priv->ext_ip4_config);
+			if (intersect_configs) {
+				/* This function was called upon external changes. Remove the configuration
+				 * (addresses,routes) that is no longer present externally from the internal
+				 * config. This way, we don't re-add addresses that were manually removed
+				 * by the user. */
+				if (priv->con_ip4_config) {
+					nm_ip4_config_intersect (priv->con_ip4_config, priv->ext_ip4_config,
+					                         default_route_metric_penalty_get (self, AF_INET));
+				}
+				if (priv->dev_ip4_config) {
+					nm_ip4_config_intersect (priv->dev_ip4_config, priv->ext_ip4_config,
+					                         default_route_metric_penalty_get (self, AF_INET));
+				}
+				if (priv->wwan_ip4_config) {
+					nm_ip4_config_intersect (priv->wwan_ip4_config, priv->ext_ip4_config,
+					                         default_route_metric_penalty_get (self, AF_INET));
+				}
+				for (iter = priv->vpn4_configs; iter; iter = iter->next)
+					nm_ip4_config_intersect (iter->data, priv->ext_ip4_config, 0);
+			}
 
-		/* Remove parts from ext_ip4_config to only contain the information that
-		 * was configured externally -- we already have the same configuration from
-		 * internal origins. */
-		if (priv->con_ip4_config)
-			nm_ip4_config_subtract (priv->ext_ip4_config, priv->con_ip4_config);
-		if (priv->dev_ip4_config)
-			nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config);
+			/* Remove parts from ext_ip4_config to only contain the information that
+			 * was configured externally -- we already have the same configuration from
+			 * internal origins. */
+			if (priv->con_ip4_config) {
+				nm_ip4_config_subtract (priv->ext_ip4_config, priv->con_ip4_config,
+				                        default_route_metric_penalty_get (self, AF_INET));
+			}
+			if (priv->dev_ip4_config) {
+				nm_ip4_config_subtract (priv->ext_ip4_config, priv->dev_ip4_config,
+				                        default_route_metric_penalty_get (self, AF_INET));
+			}
+			if (priv->wwan_ip4_config) {
+				nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config,
+				                        default_route_metric_penalty_get (self, AF_INET));
+			}
+			for (iter = priv->vpn4_configs; iter; iter = iter->next)
+				nm_ip4_config_subtract (priv->ext_ip4_config, iter->data, 0);
+		}
 
-		g_slist_foreach (priv->vpn4_configs, _ip4_config_subtract, priv->ext_ip4_config);
+	} else {
+		nm_assert (addr_family == AF_INET6);
 
-		if (priv->wwan_ip4_config)
-			nm_ip4_config_subtract (priv->ext_ip4_config, priv->wwan_ip4_config);
+		g_clear_object (&priv->ext_ip6_config);
+		g_clear_object (&priv->ext_ip6_config_captured);
+		priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_multi_index (self),
+		                                                       nm_device_get_platform (self),
+		                                                       ifindex,
+		                                                       capture_resolv_conf,
+		                                                       NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+		if (priv->ext_ip6_config_captured) {
+
+			priv->ext_ip6_config = nm_ip6_config_new_cloned (priv->ext_ip6_config_captured);
+
+			if (intersect_configs) {
+				/* This function was called upon external changes. Remove the configuration
+				 * (addresses,routes) that is no longer present externally from the internal
+				 * config. This way, we don't re-add addresses that were manually removed
+				 * by the user. */
+				if (priv->con_ip6_config) {
+					nm_ip6_config_intersect (priv->con_ip6_config, priv->ext_ip6_config,
+					                         default_route_metric_penalty_get (self, AF_INET6));
+				}
+				if (priv->ac_ip6_config) {
+					nm_ip6_config_intersect (priv->ac_ip6_config, priv->ext_ip6_config,
+					                         default_route_metric_penalty_get (self, AF_INET6));
+				}
+				if (priv->dhcp6.ip6_config) {
+					nm_ip6_config_intersect (priv->dhcp6.ip6_config, priv->ext_ip6_config,
+					                         default_route_metric_penalty_get (self, AF_INET6));
+				}
+				if (priv->wwan_ip6_config) {
+					nm_ip6_config_intersect (priv->wwan_ip6_config, priv->ext_ip6_config,
+					                         default_route_metric_penalty_get (self, AF_INET6));
+				}
+				for (iter = priv->vpn6_configs; iter; iter = iter->next)
+					nm_ip6_config_intersect (iter->data, priv->ext_ip6_config, 0);
+			}
 
-		ip4_config_merge_and_apply (self, NULL, FALSE);
+			/* Remove parts from ext_ip6_config to only contain the information that
+			 * was configured externally -- we already have the same configuration from
+			 * internal origins. */
+			if (priv->con_ip6_config) {
+				nm_ip6_config_subtract (priv->ext_ip6_config, priv->con_ip6_config,
+				                        default_route_metric_penalty_get (self, AF_INET6));
+			}
+			if (priv->ac_ip6_config) {
+				nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config,
+				                        default_route_metric_penalty_get (self, AF_INET6));
+			}
+			if (priv->dhcp6.ip6_config) {
+				nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6.ip6_config,
+				                        default_route_metric_penalty_get (self, AF_INET6));
+			}
+			if (priv->wwan_ip6_config) {
+				nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config,
+				                        default_route_metric_penalty_get (self, AF_INET6));
+			}
+			for (iter = priv->vpn6_configs; iter; iter = iter->next)
+				nm_ip6_config_subtract (priv->ext_ip6_config, iter->data, 0);
+		}
 	}
-}
-
-static void
-_ip6_config_intersect (gpointer value, gpointer user_data)
-{
-	NMIP6Config *dst = (NMIP6Config *) value;
-	NMIP6Config *src = (NMIP6Config *) user_data;
-
-	nm_ip6_config_intersect (dst, src);
-}
-
-static void
-_ip6_config_subtract (gpointer value, gpointer user_data)
-{
-	NMIP6Config *dst = (NMIP6Config *) user_data;
-	NMIP6Config *src = (NMIP6Config *) value;
 
-	nm_ip6_config_subtract (dst, src);
+	return TRUE;
 }
 
 static void
-update_ip6_config (NMDevice *self, gboolean initial)
+update_ip_config (NMDevice *self, int addr_family, gboolean initial)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	int ifindex;
-	gboolean capture_resolv_conf;
 
-	/* If a commit is scheduled, this function would potentially interfere with
-	 * it changing IP configurations before they are applied. Postpone the
-	 * update in such case.
-	 */
-	if (   !initial
-	    && activation_source_is_scheduled (self,
-	                                       activate_stage5_ip6_config_commit,
-	                                       AF_INET6)) {
-		priv->queued_ip6_config_pending = FALSE;
-		priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
-		_LOGT (LOGD_DEVICE, "IP6 update was postponed");
-		return;
-	}
-
-	ifindex = nm_device_get_ip_ifindex (self);
-	if (!ifindex)
-		return;
+	nm_assert_addr_family (addr_family);
 
-	capture_resolv_conf =    initial
-	                      && nm_dns_manager_get_resolv_conf_explicit (nm_dns_manager_get ());
+	if (update_ext_ip_config (self, addr_family, initial, TRUE)) {
+		if (addr_family == AF_INET) {
+			if (priv->ext_ip4_config)
+				ip4_config_merge_and_apply (self, FALSE);
+		} else {
+			if (priv->ext_ip6_config_captured)
+				ip6_config_merge_and_apply (self, FALSE);
+		}
+	}
 
-	/* IPv6 */
-	g_clear_object (&priv->ext_ip6_config);
-	g_clear_object (&priv->ext_ip6_config_captured);
-	priv->ext_ip6_config_captured = nm_ip6_config_capture (nm_device_get_platform (self), ifindex, capture_resolv_conf, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
-	if (priv->ext_ip6_config_captured) {
-
-		priv->ext_ip6_config = nm_ip6_config_new_cloned (priv->ext_ip6_config_captured);
-
-		/* This function was called upon external changes. Remove the configuration
-		 * (addresses,routes) that is no longer present externally from the internal
-		 * config. This way, we don't re-add addresses that were manually removed
-		 * by the user. */
-		if (priv->con_ip6_config)
-			nm_ip6_config_intersect (priv->con_ip6_config, priv->ext_ip6_config);
-		if (priv->ac_ip6_config)
-			nm_ip6_config_intersect (priv->ac_ip6_config, priv->ext_ip6_config);
-		if (priv->dhcp6.ip6_config)
-			nm_ip6_config_intersect (priv->dhcp6.ip6_config, priv->ext_ip6_config);
-		if (priv->wwan_ip6_config)
-			nm_ip6_config_intersect (priv->wwan_ip6_config, priv->ext_ip6_config);
-		g_slist_foreach (priv->vpn6_configs, _ip6_config_intersect, priv->ext_ip6_config);
-
-		/* Remove parts from ext_ip6_config to only contain the information that
-		 * was configured externally -- we already have the same configuration from
-		 * internal origins. */
-		if (priv->con_ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->con_ip6_config);
-		if (priv->ac_ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->ac_ip6_config);
-		if (priv->dhcp6.ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->dhcp6.ip6_config);
-		if (priv->wwan_ip6_config)
-			nm_ip6_config_subtract (priv->ext_ip6_config, priv->wwan_ip6_config);
-		g_slist_foreach (priv->vpn6_configs, _ip6_config_subtract, priv->ext_ip6_config);
-
-		ip6_config_merge_and_apply (self, FALSE);
-	}
-
-	if (   priv->linklocal6_timeout_id
+	if (   addr_family == AF_INET6
+	    && priv->linklocal6_timeout_id
 	    && priv->ext_ip6_config_captured
 	    && nm_ip6_config_get_address_first_nontentative (priv->ext_ip6_config_captured, TRUE)) {
 		/* linklocal6 is ready now, do the state transition... we are also
@@ -10885,8 +11102,8 @@ update_ip6_config (NMDevice *self, gboolean initial)
 void
 nm_device_capture_initial_config (NMDevice *self)
 {
-	update_ip4_config (self, TRUE);
-	update_ip6_config (self, TRUE);
+	update_ip_config (self, AF_INET,  TRUE);
+	update_ip_config (self, AF_INET6, TRUE);
 }
 
 static gboolean
@@ -10906,10 +11123,27 @@ queued_ip4_config_change (gpointer user_data)
 		return TRUE;
 
 	priv->queued_ip4_config_id = 0;
-	update_ip4_config (self, FALSE);
+
+	/* If a commit is scheduled, this function would potentially interfere with
+	 * it changing IP configurations before they are applied. Postpone the
+	 * update in such case.
+	 */
+	if (activation_source_is_scheduled (self,
+	                                    activate_stage5_ip4_config_result,
+	                                    AF_INET)) {
+		priv->queued_ip4_config_pending = FALSE;
+		priv->queued_ip4_config_id = g_idle_add (queued_ip4_config_change, self);
+		_LOGT (LOGD_DEVICE, "IP4 update was postponed");
+	} else
+		update_ip_config (self, AF_INET, FALSE);
 
 	set_unmanaged_external_down (self, TRUE);
 
+	if (!nm_device_sys_iface_state_is_external_or_assume (self)) {
+		priv->v4_has_shadowed_routes = _v4_has_shadowed_routes_detect (self);;
+		ip4_rp_filter_update (self);
+	}
+
 	return FALSE;
 }
 
@@ -10932,7 +11166,19 @@ queued_ip6_config_change (gpointer user_data)
 		return TRUE;
 
 	priv->queued_ip6_config_id = 0;
-	update_ip6_config (self, FALSE);
+
+	/* If a commit is scheduled, this function would potentially interfere with
+	 * it changing IP configurations before they are applied. Postpone the
+	 * update in such case.
+	 */
+	if (activation_source_is_scheduled (self,
+	                                    activate_stage5_ip6_config_commit,
+	                                    AF_INET6)) {
+		priv->queued_ip6_config_pending = FALSE;
+		priv->queued_ip6_config_id = g_idle_add (queued_ip6_config_change, self);
+		_LOGT (LOGD_DEVICE, "IP6 update was postponed");
+	} else
+		update_ip_config (self, AF_INET6, FALSE);
 
 	if (priv->state < NM_DEVICE_STATE_DEACTIVATING
 	    && nm_platform_link_get (nm_device_get_platform (self), priv->ifindex)) {
@@ -10977,6 +11223,8 @@ queued_ip6_config_change (gpointer user_data)
 			g_clear_object (&priv->dad6_ip6_config);
 			_set_ip_state (self, AF_INET6, IP_DONE);
 			check_ip_state (self, FALSE);
+			if (priv->rt6_temporary_not_available)
+				nm_device_activate_schedule_ip6_config_result (self);
 		}
 	}
 
@@ -11052,6 +11300,7 @@ NM_UTILS_FLAGS2STR_DEFINE (nm_unmanaged_flags2str, NMUnmanagedFlags,
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_EXPLICIT, "user-explicit"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_BY_DEFAULT, "by-default"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_SETTINGS, "user-settings"),
+	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_CONF, "user-conf"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_USER_UDEV, "user-udev"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_EXTERNAL_DOWN, "external-down"),
 	NM_UTILS_FLAGS2STR (NM_UNMANAGED_IS_SLAVE, "is-slave"),
@@ -11144,11 +11393,19 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 
 	if (NM_FLAGS_ANY (mask, NM_UNMANAGED_USER_UDEV)) {
 		/* configuration from udev or nm-config overwrites the by-default flag
-		 * which is based on the device type. */
-		flags &= ~NM_UNMANAGED_BY_DEFAULT;
+		 * which is based on the device type.
+		 * configuration from udev overwrites external-down */
+		flags &= ~(  NM_UNMANAGED_BY_DEFAULT
+		           | NM_UNMANAGED_EXTERNAL_DOWN);
+	}
 
-		/* configuration from udev overwrites external-down */
-		flags &= ~NM_UNMANAGED_EXTERNAL_DOWN;
+	if (NM_FLAGS_ANY (mask, NM_UNMANAGED_USER_CONF)) {
+		/* configuration from NetworkManager.conf overwrites the by-default flag
+		 * which is based on the device type.
+		 * It also overwrites the udev configuration and external-down */
+		flags &= ~(   NM_UNMANAGED_BY_DEFAULT
+		           | NM_UNMANAGED_USER_UDEV
+		           | NM_UNMANAGED_EXTERNAL_DOWN);
 	}
 
 	if (   NM_FLAGS_HAS (mask, NM_UNMANAGED_IS_SLAVE)
@@ -11160,9 +11417,9 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 	if (NM_FLAGS_HAS (mask, NM_UNMANAGED_USER_EXPLICIT)) {
 		/* if the device is managed by user-decision, certain other flags
 		 * are ignored. */
-
 		flags &= ~(  NM_UNMANAGED_BY_DEFAULT
 		           | NM_UNMANAGED_USER_UDEV
+		           | NM_UNMANAGED_USER_CONF
 		           | NM_UNMANAGED_EXTERNAL_DOWN);
 	}
 
@@ -11469,6 +11726,35 @@ nm_device_set_unmanaged_by_user_udev (NMDevice *self)
 }
 
 void
+nm_device_set_unmanaged_by_user_conf (NMDevice *self)
+{
+	gboolean value;
+	NMUnmanFlagOp set_op;
+
+	value = nm_config_data_get_device_config_boolean (NM_CONFIG_GET_DATA,
+	                                                  NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
+	                                                  self,
+	                                                  -1,
+	                                                  TRUE);
+	switch (value) {
+	case TRUE:
+		set_op = NM_UNMAN_FLAG_OP_SET_MANAGED;
+		break;
+	case FALSE:
+		set_op = NM_UNMAN_FLAG_OP_SET_UNMANAGED;
+		break;
+	default:
+		set_op = NM_UNMAN_FLAG_OP_FORGET;
+		break;
+	}
+
+	nm_device_set_unmanaged_by_flags (self,
+	                                  NM_UNMANAGED_USER_CONF,
+	                                  set_op,
+	                                  NM_DEVICE_STATE_REASON_USER_REQUESTED);
+}
+
+void
 nm_device_set_unmanaged_by_quitting (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -11489,14 +11775,6 @@ nm_device_set_unmanaged_by_quitting (NMDevice *self)
 /*****************************************************************************/
 
 void
-nm_device_set_dhcp_timeout (NMDevice *self, guint32 timeout)
-{
-	g_return_if_fail (NM_IS_DEVICE (self));
-
-	NM_DEVICE_GET_PRIVATE (self)->dhcp_timeout = timeout;
-}
-
-void
 nm_device_set_dhcp_anycast_address (NMDevice *self, const char *addr)
 {
 	NMDevicePrivate *priv;
@@ -11766,6 +12044,12 @@ check_connection_available (NMDevice *self,
 		return TRUE;
 	}
 
+	/* master types are always available even without carrier.
+	 * Making connection non-available would un-enslave slaves which
+	 * is not desired. */
+	if (nm_device_is_master (self))
+		return TRUE;
+
 	return FALSE;
 }
 
@@ -12030,7 +12314,8 @@ nm_device_has_pending_action (NMDevice *self)
 	if (priv->pending_actions)
 		return TRUE;
 
-	if (nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
+	if (   nm_device_is_real (self)
+	    && nm_device_get_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT)) {
 		/* as long as the platform link is not yet initialized, we have a pending
 		 * action. */
 		return TRUE;
@@ -12091,26 +12376,22 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
-		_update_default_route (self, AF_INET,  FALSE, FALSE);
-		_update_default_route (self, AF_INET6, FALSE, FALSE);
-	} else {
-		_update_default_route (self, AF_INET,  priv->default_route.v4_has, TRUE);
-		_update_default_route (self, AF_INET6, priv->default_route.v6_has, TRUE);
-	}
-	_update_default_route (self, AF_INET,  FALSE, TRUE);
-	_update_default_route (self, AF_INET6, FALSE, TRUE);
-
 	priv->v4_commit_first_time = TRUE;
 	priv->v6_commit_first_time = TRUE;
 
+	priv->v4_route_table_initalized = FALSE;
+	priv->v6_route_table_initalized = FALSE;
+
+	priv->default_route_metric_penalty_ip4_has = FALSE;
+	priv->default_route_metric_penalty_ip6_has = FALSE;
+
 	priv->linklocal6_dad_counter = 0;
 
 	/* Clean up IP configs; this does not actually deconfigure the
 	 * interface; the caller must flush routes and addresses explicitly.
 	 */
-	nm_device_set_ip4_config (self, NULL, 0, TRUE, TRUE);
-	nm_device_set_ip6_config (self, NULL, TRUE, TRUE);
+	nm_device_set_ip4_config (self, NULL, TRUE, NULL);
+	nm_device_set_ip6_config (self, NULL, TRUE);
 	g_clear_object (&priv->proxy_config);
 	g_clear_object (&priv->con_ip4_config);
 	g_clear_object (&priv->dev_ip4_config);
@@ -12125,6 +12406,9 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	g_clear_object (&priv->ip6_config);
 	g_clear_object (&priv->dad6_ip6_config);
 
+	g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref);
+	nm_clear_g_source (&priv->rt6_temporary_not_available_id);
+
 	g_slist_free_full (priv->vpn4_configs, g_object_unref);
 	priv->vpn4_configs = NULL;
 	g_slist_free_full (priv->vpn6_configs, g_object_unref);
@@ -12198,18 +12482,24 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	if (NM_DEVICE_GET_CLASS (self)->deactivate)
 		NM_DEVICE_GET_CLASS (self)->deactivate (self);
 
+	ifindex = nm_device_get_ip_ifindex (self);
+
 	if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
 		/* master: release slaves */
 		nm_device_master_release_slaves (self);
 
 		/* Take out any entries in the routing table and any IP address the device had. */
-		ifindex = nm_device_get_ip_ifindex (self);
 		if (ifindex > 0) {
-			nm_route_manager_route_flush (nm_netns_get_route_manager (priv->netns), ifindex);
-			nm_platform_address_flush (nm_device_get_platform (self), ifindex);
+			NMPlatform *platform = nm_device_get_platform (self);
+
+			nm_platform_ip_route_flush (platform, AF_UNSPEC, ifindex);
+			nm_platform_ip_address_flush (platform, AF_UNSPEC, ifindex);
 		}
 	}
 
+	if (ifindex > 0)
+		nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self), ifindex, NULL);
+
 	/* slave: mark no longer enslaved */
 	if (   priv->master
 	    && nm_platform_link_get_master (nm_device_get_platform (self), priv->ifindex) <= 0)
@@ -12241,8 +12531,10 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 		    && cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
 			_LOGT (LOGD_DEVICE, "mtu: reset device-mtu: %u, ipv6-mtu: %u, ifindex: %d",
 			       (guint) priv->mtu_initial, (guint) priv->ip6_mtu_initial, ifindex);
-			if (priv->mtu_initial)
+			if (priv->mtu_initial) {
 				nm_platform_link_set_mtu (nm_device_get_platform (self), ifindex, priv->mtu_initial);
+				priv->carrier_wait_until_ms = nm_utils_get_monotonic_timestamp_ms () + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+			}
 			if (priv->ip6_mtu_initial) {
 				char sbuf[64];
 
@@ -12267,15 +12559,13 @@ static char *
 find_dhcp4_address (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	guint i, n;
+	const NMPlatformIP4Address *a;
+	NMDedupMultiIter ipconf_iter;
 
 	if (!priv->ip4_config)
 		return NULL;
 
-	n = nm_ip4_config_get_num_addresses (priv->ip4_config);
-	for (i = 0; i < n; i++) {
-		const NMPlatformIP4Address *a = nm_ip4_config_get_address (priv->ip4_config, i);
-
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip4_config, &a) {
 		if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
 			return g_strdup (nm_utils_inet4_ntop (a->address, NULL));
 	}
@@ -12346,7 +12636,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		g_assert (s_ip4);
 
 		g_ptr_array_add (argv, g_strdup ("--priority4"));
-		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_ip4_route_metric (self)));
+		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_route_metric (self, AF_INET)));
 
 		g_ptr_array_add (argv, g_strdup ("--dhcp4"));
 		g_ptr_array_add (argv, g_strdup (dhcp4_address));
@@ -12388,7 +12678,7 @@ nm_device_spawn_iface_helper (NMDevice *self)
 		g_assert (s_ip6);
 
 		g_ptr_array_add (argv, g_strdup ("--priority6"));
-		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_ip6_route_metric (self)));
+		g_ptr_array_add (argv, g_strdup_printf ("%u", nm_device_get_route_metric (self, AF_INET6)));
 
 		g_ptr_array_add (argv, g_strdup ("--slaac"));
 
@@ -12555,7 +12845,7 @@ _set_state_full (NMDevice *self,
 	if (   (priv->state == state)
 	    && (   state != NM_DEVICE_STATE_UNAVAILABLE
 	        || !priv->firmware_missing)) {
-		_LOGD (LOGD_DEVICE, "state change: %s -> %s (reason '%s', internal state '%s'%s)",
+		_LOGD (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)",
 		       nm_device_state_to_str (old_state),
 		       nm_device_state_to_str (state),
 		       reason_to_string (reason),
@@ -12564,7 +12854,7 @@ _set_state_full (NMDevice *self,
 		return;
 	}
 
-	_LOGI (LOGD_DEVICE, "state change: %s -> %s (reason '%s', internal state '%s')",
+	_LOGI (LOGD_DEVICE, "state change: %s -> %s (reason '%s', sys-iface-state: '%s')",
 	       nm_device_state_to_str (old_state),
 	       nm_device_state_to_str (state),
 	       reason_to_string (reason),
@@ -12593,6 +12883,10 @@ _set_state_full (NMDevice *self,
 	                        NM_DEVICE_SYS_IFACE_STATE_ASSUME))
 		nm_device_sys_iface_state_set (self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
 
+	if (   state <= NM_DEVICE_STATE_DISCONNECTED
+	    || state >= NM_DEVICE_STATE_ACTIVATED)
+		priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET;
+
 	if (state > NM_DEVICE_STATE_DISCONNECTED)
 		nm_device_assume_state_reset (self);
 
@@ -12752,15 +13046,11 @@ _set_state_full (NMDevice *self,
 		if (   priv->queued_act_request
 		    && !priv->queued_act_request_is_waiting_for_carrier) {
 			NMActRequest *queued_req;
-			gboolean success;
 
 			queued_req = priv->queued_act_request;
 			priv->queued_act_request = NULL;
-			success = _device_activate (self, queued_req);
+			_device_activate (self, queued_req);
 			g_object_unref (queued_req);
-			if (success)
-				break;
-			/* fall through */
 		}
 		break;
 	case NM_DEVICE_STATE_ACTIVATED:
@@ -13380,7 +13670,7 @@ handle_fail:
 		_NMLOG (plerr == NM_PLATFORM_ERROR_NOT_FOUND ? LOGL_DEBUG : LOGL_WARN,
 		        LOGD_DEVICE, "set-hw-addr: failed to %s MAC address to %s (%s) (%s)",
 		        operation, addr, detail,
-		        nm_platform_error_to_string (plerr));
+		        nm_platform_error_to_string_a (plerr));
 	}
 
 	if (was_up) {
@@ -13683,6 +13973,12 @@ nm_device_get_initial_hw_address (NMDevice *self)
 gboolean
 nm_device_spec_match_list (NMDevice *self, const GSList *specs)
 {
+	return nm_device_spec_match_list_full (self, specs, FALSE);
+}
+
+int
+nm_device_spec_match_list_full (NMDevice *self, const GSList *specs, int no_match_value)
+{
 	NMDeviceClass *klass;
 	NMMatchSpecMatchType m;
 
@@ -13697,7 +13993,17 @@ nm_device_spec_match_list (NMDevice *self, const GSList *specs)
 	                          nm_device_get_driver_version (self),
 	                          nm_device_get_permanent_hw_address (self),
 	                          klass->get_s390_subchannels ? klass->get_s390_subchannels (self) : NULL);
-	return m == NM_MATCH_SPEC_MATCH;
+
+	switch (m) {
+	case NM_MATCH_SPEC_MATCH:
+		return TRUE;
+	case NM_MATCH_SPEC_NEG_MATCH:
+		return FALSE;
+	case NM_MATCH_SPEC_NO_MATCH:
+		return no_match_value;
+	}
+	nm_assert_not_reached ();
+	return no_match_value;
 }
 
 guint
@@ -13727,6 +14033,54 @@ nm_device_get_supplicant_timeout (NMDevice *self)
 	                                     SUPPLICANT_DEFAULT_TIMEOUT);
 }
 
+gboolean
+nm_device_auth_retries_try_next (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+	NMSettingConnection *s_con;
+	int auth_retries;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	auth_retries = priv->auth_retries;
+
+	if (G_UNLIKELY (auth_retries == NM_DEVICE_AUTH_RETRIES_UNSET)) {
+		auth_retries = -1;
+
+		s_con = NM_SETTING_CONNECTION (nm_device_get_applied_setting (self, NM_TYPE_SETTING_CONNECTION));
+		if (s_con)
+			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);
+		}
+
+		if (auth_retries == 0)
+			auth_retries = NM_DEVICE_AUTH_RETRIES_INFINITY;
+		else if (auth_retries == -1)
+			auth_retries = NM_DEVICE_AUTH_RETRIES_DEFAULT;
+		else
+			nm_assert (auth_retries > 0);
+
+		priv->auth_retries = auth_retries;
+	}
+
+	if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY)
+		return TRUE;
+	if (auth_retries <= 0) {
+		nm_assert (auth_retries == 0);
+		return FALSE;
+	}
+	priv->auth_retries--;
+	return TRUE;
+}
+
 /*****************************************************************************/
 
 static const char *
@@ -13742,7 +14096,7 @@ _activation_func_to_string (ActivationHandleFunc func)
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage3_ip_config_start);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip4_config_timeout);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage4_ip6_config_timeout);
-	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_commit);
+	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip4_config_result);
 	FUNC_TO_STRING_CHECK_AND_RETURN (func, activate_stage5_ip6_config_commit);
 	g_return_val_if_reached ("unknown");
 }
@@ -13758,23 +14112,22 @@ nm_device_init (NMDevice *self)
 
 	self->_priv = priv;
 
+	c_list_init (&priv->slaves);
+
 	priv->netns = g_object_ref (NM_NETNS_GET);
 
+	priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET;
 	priv->type = NM_DEVICE_TYPE_UNKNOWN;
 	priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
 	priv->state = NM_DEVICE_STATE_UNMANAGED;
 	priv->state_reason = NM_DEVICE_STATE_REASON_NONE;
-	priv->dhcp_timeout = 0;
 	priv->rfkill_type = RFKILL_TYPE_UNKNOWN;
 	priv->unmanaged_flags = NM_UNMANAGED_PLATFORM_INIT;
 	priv->unmanaged_mask = priv->unmanaged_flags;
 	priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
-	priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+	priv->ip6_saved_properties = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 	priv->sys_iface_state = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
 
-	priv->default_route.v4_is_assumed = TRUE;
-	priv->default_route.v6_is_assumed = TRUE;
-
 	priv->v4_commit_first_time = TRUE;
 	priv->v6_commit_first_time = TRUE;
 }
@@ -13843,9 +14196,6 @@ constructed (GObject *object)
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (device_ipx_changed), self);
 	g_signal_connect (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (link_changed_cb), self);
 
-	g_signal_connect (nm_netns_get_route_manager (priv->netns), NM_ROUTE_MANAGER_IP4_ROUTES_CHANGED,
-	                  G_CALLBACK (ip4_routes_changed_changed_cb), self);
-
 	priv->settings = g_object_ref (NM_SETTINGS_GET);
 	g_assert (priv->settings);
 
@@ -13886,9 +14236,6 @@ dispose (GObject *object)
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (device_ipx_changed), self);
 	g_signal_handlers_disconnect_by_func (platform, G_CALLBACK (link_changed_cb), self);
 
-	g_signal_handlers_disconnect_by_func (nm_netns_get_route_manager (priv->netns),
-	                                      G_CALLBACK (ip4_routes_changed_changed_cb), self);
-
 	g_slist_free_full (priv->arping.dad_list, (GDestroyNotify) nm_arping_manager_destroy);
 	priv->arping.dad_list = NULL;
 
@@ -13904,7 +14251,7 @@ dispose (GObject *object)
 
 	_cleanup_generic_pre (self, CLEANUP_TYPE_KEEP);
 
-	g_warn_if_fail (priv->slaves == NULL);
+	g_warn_if_fail (c_list_is_empty (&priv->slaves));
 	g_assert (priv->master_ready_id == 0);
 
 	/* Let the kernel manage IPv6LL again */
@@ -14083,10 +14430,6 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->rfkill_type = g_value_get_uint (value);
 		break;
-	case PROP_IS_MASTER:
-		/* construct-only */
-		priv->is_master = g_value_get_boolean (value);
-		break;
 	case PROP_PERM_HW_ADDRESS:
 		/* construct-only */
 		priv->hw_addr_perm = g_value_dup_string (value);
@@ -14221,9 +14564,6 @@ get_property (GObject *object, guint prop_id,
 	case PROP_PHYSICAL_PORT_ID:
 		g_value_set_string (value, priv->physical_port_id);
 		break;
-	case PROP_IS_MASTER:
-		g_value_set_boolean (value, priv->is_master);
-		break;
 	case PROP_MASTER:
 		g_value_set_object (value, nm_device_get_master (self));
 		break;
@@ -14260,13 +14600,15 @@ get_property (GObject *object, guint prop_id,
 		g_value_set_boolean (value, nm_device_is_real (self));
 		break;
 	case PROP_SLAVES: {
-		GSList *slave_iter;
+		CList *slave_iter;
 		char **slave_list;
-		guint i;
+		gsize i, n;
 
-		slave_list = g_new (char *, g_slist_length (priv->slaves) + 1);
-		for (slave_iter = priv->slaves, i = 0; slave_iter; slave_iter = slave_iter->next) {
-			SlaveInfo *info = slave_iter->data;
+		n = c_list_length (&priv->slaves);
+		slave_list = g_new (char *, n + 1);
+		i = 0;
+		c_list_for_each (slave_iter, &priv->slaves) {
+			SlaveInfo *info = c_list_entry (slave_iter, SlaveInfo, lst_slave);
 			const char *path;
 
 			if (!NM_DEVICE_GET_PRIVATE (info->slave)->is_enslaved)
@@ -14275,6 +14617,7 @@ get_property (GObject *object, guint prop_id,
 			if (path)
 				slave_list[i++] = g_strdup (path);
 		}
+		nm_assert (i <= n);
 		slave_list[i] = NULL;
 		g_value_take_boxed (value, slave_list);
 		break;
@@ -14323,7 +14666,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	klass->act_stage3_ip6_config_start = act_stage3_ip6_config_start;
 	klass->act_stage4_ip4_config_timeout = act_stage4_ip4_config_timeout;
 	klass->act_stage4_ip6_config_timeout = act_stage4_ip6_config_timeout;
-	klass->have_any_ready_slaves = have_any_ready_slaves;
 
 	klass->get_type_description = get_type_description;
 	klass->get_autoconnect_allowed = get_autoconnect_allowed;
@@ -14484,11 +14826,6 @@ nm_device_class_init (NMDeviceClass *klass)
 	                         NULL,
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
-	obj_properties[PROP_IS_MASTER] =
-	    g_param_spec_boolean (NM_DEVICE_IS_MASTER, "", "",
-	                          FALSE,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_MASTER] =
 	    g_param_spec_object (NM_DEVICE_MASTER, "", "",
 	                         NM_TYPE_DEVICE,
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 6d17d7c9..350a17b2 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -120,7 +120,6 @@ nm_device_state_reason_check (NMDeviceStateReason reason)
 #define NM_DEVICE_TYPE_DESC        "type-desc"      /* Internal only */
 #define NM_DEVICE_RFKILL_TYPE      "rfkill-type"    /* Internal only */
 #define NM_DEVICE_IFINDEX          "ifindex"        /* Internal only */
-#define NM_DEVICE_IS_MASTER        "is-master"      /* Internal only */
 #define NM_DEVICE_MASTER           "master"         /* Internal only */
 #define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */
 
@@ -196,6 +195,10 @@ typedef struct {
 	const char *connection_type;
 	const NMLinkType *link_types;
 
+	/* Whether the device type is a master-type. This depends purely on the
+	 * type (NMDeviceClass), not the actual device instance. */
+	bool is_master:1;
+
 	void (*state_changed) (NMDevice *device,
 	                       NMDeviceState new_state,
 	                       NMDeviceState old_state,
@@ -364,9 +367,6 @@ typedef struct {
 	                                   NMDevice *slave,
 	                                   gboolean configure);
 
-	gboolean        (* have_any_ready_slaves) (NMDevice *self,
-	                                           const GSList *slaves);
-
 	void            (* parent_changed_notify) (NMDevice *self,
 	                                           int old_ifindex,
 	                                           NMDevice *old_parent,
@@ -378,10 +378,10 @@ typedef struct {
 	 * @self: the #NMDevice
 	 * @component: the component (device, modem, etc) which was added
 	 *
-	 * Notifies @self that a new component was added to the Manager.  This
-	 * may include any kind of %GObject subclass, and the device is expected
-	 * to match only specific components they care about, like %NMModem objects
-	 * or %NMDevice objects.
+	 * Notifies @self that a new component that a device might be interested
+	 * in was detected by some device factory. It may include an object of
+	 * %GObject subclass to help the devices decide whether it claims that
+	 * particular object itself and the emitting factory should not.
 	 *
 	 * Returns: %TRUE if the component was claimed exclusively and no further
 	 * devices should be notified of the new component.  %FALSE to indicate
@@ -406,6 +406,9 @@ typedef struct {
 	void            (* reapply_connection) (NMDevice *self,
 	                                        NMConnection *con_old,
 	                                        NMConnection *con_new);
+
+	guint32         (* get_dhcp_timeout) (NMDevice *self,
+	                                      int addr_family);
 } NMDeviceClass;
 
 typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
@@ -416,6 +419,7 @@ typedef void (*NMDeviceAuthRequestFunc) (NMDevice *device,
 
 GType nm_device_get_type (void);
 
+struct _NMDedupMultiIndex *nm_device_get_multi_index (NMDevice *self);
 NMNetns *nm_device_get_netns (NMDevice *self);
 NMPlatform *nm_device_get_platform (NMDevice *self);
 
@@ -443,9 +447,8 @@ NMDeviceType    nm_device_get_device_type       (NMDevice *dev);
 NMLinkType      nm_device_get_link_type         (NMDevice *dev);
 NMMetered       nm_device_get_metered           (NMDevice *dev);
 
-int             nm_device_get_priority          (NMDevice *dev);
-guint32         nm_device_get_ip4_route_metric  (NMDevice *dev);
-guint32         nm_device_get_ip6_route_metric  (NMDevice *dev);
+guint32         nm_device_get_route_table       (NMDevice *self, int addr_family, gboolean fallback_main);
+guint32         nm_device_get_route_metric      (NMDevice *dev, int addr_family);
 
 const char *    nm_device_get_hw_address        (NMDevice *dev);
 const char *    nm_device_get_permanent_hw_address (NMDevice *self);
@@ -493,6 +496,8 @@ NMSetting *     nm_device_get_applied_setting   (NMDevice *dev, GType setting_ty
 
 void            nm_device_removed               (NMDevice *self, gboolean unconfigure_ip_config);
 
+gboolean        nm_device_ignore_carrier_by_default (NMDevice *self);
+
 gboolean        nm_device_is_available          (NMDevice *dev, NMDeviceCheckDevAvailableFlags flags);
 gboolean        nm_device_has_carrier           (NMDevice *dev);
 
@@ -522,6 +527,7 @@ gboolean nm_device_check_slave_connection_compatible (NMDevice *device, NMConnec
 gboolean nm_device_unmanage_on_quit (NMDevice *self);
 
 gboolean nm_device_spec_match_list (NMDevice *device, const GSList *specs);
+int      nm_device_spec_match_list_full (NMDevice *self, const GSList *specs, int no_match_value);
 
 gboolean nm_device_is_activating (NMDevice *dev);
 gboolean nm_device_autoconnect_allowed (NMDevice *self);
@@ -561,6 +567,10 @@ void nm_device_copy_ip6_dns_config (NMDevice *self, NMDevice *from_device);
  *   the settings plugins, such as NM_CONTROLLED=no in ifcfg-rh), it cannot
  *   be overruled and is authorative. That is because users may depend on
  *   dropping a ifcfg-rh file to ensure the device is unmanaged.
+ * @NM_UNMANAGED_USER_CONF: %TRUE when unmanaged by user decision via
+ *   the NetworkManager.conf ("unmanaged" in the [device] section).
+ *   Contray to @NM_UNMANAGED_USER_SETTINGS, this can be overwritten via
+ *   D-Bus.
  * @NM_UNMANAGED_BY_DEFAULT: %TRUE for certain device types where we unmanage
  *   them by default
  * @NM_UNMANAGED_USER_UDEV: %TRUE when unmanaged by user decision (via UDev rule)
@@ -585,6 +595,7 @@ typedef enum { /*< skip >*/
 	/* These flags can be non-effective and be overwritten
 	 * by other flags. */
 	NM_UNMANAGED_BY_DEFAULT    = (1LL <<  8),
+	NM_UNMANAGED_USER_CONF     = (1LL <<  9),
 	NM_UNMANAGED_USER_UDEV     = (1LL << 10),
 	NM_UNMANAGED_EXTERNAL_DOWN = (1LL << 11),
 	NM_UNMANAGED_IS_SLAVE      = (1LL << 12),
@@ -615,6 +626,7 @@ void nm_device_set_unmanaged_by_flags_queue (NMDevice *self,
                                              NMDeviceStateReason reason);
 void nm_device_set_unmanaged_by_user_settings (NMDevice *self);
 void nm_device_set_unmanaged_by_user_udev (NMDevice *self);
+void nm_device_set_unmanaged_by_user_conf (NMDevice *self);
 void nm_device_set_unmanaged_by_quitting (NMDevice *device);
 
 gboolean nm_device_is_nm_owned (NMDevice *device);
@@ -648,6 +660,9 @@ gboolean nm_device_unrealize          (NMDevice *device,
                                        gboolean remove_resources,
                                        GError **error);
 
+void nm_device_update_from_platform_link (NMDevice *self,
+                                          const NMPlatformLink *plink);
+
 gboolean nm_device_get_autoconnect (NMDevice *device);
 void nm_device_set_autoconnect_intern (NMDevice *device, gboolean autoconnect);
 void nm_device_emit_recheck_auto_activate (NMDevice *device);
@@ -694,8 +709,8 @@ gboolean nm_device_owns_iface (NMDevice *device, const char *iface);
 
 NMConnection *nm_device_new_default_connection (NMDevice *self);
 
-const NMPlatformIP4Route *nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed);
-const NMPlatformIP6Route *nm_device_get_ip6_default_route (NMDevice *self, gboolean *out_is_assumed);
+const NMPObject *nm_device_get_best_default_route (NMDevice *self,
+                                                   int addr_family);
 
 void nm_device_spawn_iface_helper (NMDevice *self);
 
@@ -720,6 +735,9 @@ void nm_device_update_initial_hw_address (NMDevice *self);
 void nm_device_update_permanent_hw_address (NMDevice *self, gboolean force_freeze);
 void nm_device_update_dynamic_ip_setup (NMDevice *self);
 guint nm_device_get_supplicant_timeout (NMDevice *self);
+
+gboolean nm_device_auth_retries_try_next (NMDevice *self);
+
 gboolean nm_device_hw_addr_get_cloned (NMDevice *self,
                                        NMConnection *connection,
                                        gboolean is_wifi,
@@ -735,6 +753,16 @@ void nm_device_check_connectivity (NMDevice *self,
                                    gpointer user_data);
 NMConnectivityState nm_device_get_connectivity_state (NMDevice *self);
 
+typedef struct _NMBtVTableNetworkServer NMBtVTableNetworkServer;
+struct _NMBtVTableNetworkServer {
+	gboolean (*is_available) (const NMBtVTableNetworkServer *vtable,
+	                          const char *addr);
+	gboolean (*register_bridge) (const NMBtVTableNetworkServer *vtable,
+	                             const char *addr,
+	                             NMDevice *device);
+	gboolean (*unregister_bridge) (const NMBtVTableNetworkServer *vtable,
+	                               NMDevice *device);
+};
 
 const char *nm_device_state_to_str (NMDeviceState state);
 
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index bfd631f0..2ed2a7d9 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -274,13 +274,15 @@ static guint
 lldp_neighbor_id_hash (gconstpointer ptr)
 {
 	const LldpNeighbor *neigh = ptr;
-	guint hash;
-
-	hash =   23423423u  + ((guint) (neigh->chassis_id ? g_str_hash (neigh->chassis_id) : 12321u));
-	hash = (hash * 33u) + ((guint) (neigh->port_id ? g_str_hash (neigh->port_id) : 34342343u));
-	hash = (hash * 33u) + ((guint) neigh->chassis_id_type);
-	hash = (hash * 33u) + ((guint) neigh->port_id_type);
-	return hash;
+	NMHashState h;
+
+	nm_hash_init (&h, 23423423u);
+	nm_hash_update_str0 (&h, neigh->chassis_id);
+	nm_hash_update_str0 (&h, neigh->port_id);
+	nm_hash_update_vals (&h,
+	                     neigh->chassis_id_type,
+	                     neigh->port_id_type);
+	return nm_hash_complete (&h);
 }
 
 static int
diff --git a/src/devices/ovs/nm-device-ovs-bridge.c b/src/devices/ovs/nm-device-ovs-bridge.c
new file mode 100644
index 00000000..53ea2b82
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-bridge.c
@@ -0,0 +1,156 @@
+/* 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 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ovs-bridge.h"
+#include "nm-device-ovs-port.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-bridge.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.OvsBridge.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceOvsBridge);
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsBridge {
+	NMDevice parent;
+};
+
+struct _NMDeviceOvsBridgeClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceOvsBridge, nm_device_ovs_bridge, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "ovs-bridge";
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	/* The actual backing resources will be created on enslavement by the port
+	 * when it can identify the port and the bridge. */
+
+	return TRUE;
+}
+
+static gboolean
+unrealize (NMDevice *device, GError **error)
+{
+	return TRUE;
+}
+
+static NMDeviceCapabilities
+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,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *device,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static gboolean
+enslave_slave (NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+{
+	if (!configure)
+		return TRUE;
+
+	if (!NM_IS_DEVICE_OVS_PORT (slave))
+		return FALSE;
+
+	return TRUE;
+}
+
+static void
+release_slave (NMDevice *device, NMDevice *slave, gboolean configure)
+{
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_bridge_init (NMDeviceOvsBridge *self)
+{
+}
+
+static void
+nm_device_ovs_bridge_class_init (NMDeviceOvsBridgeClass *klass)
+{
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	device_class->connection_type = NM_SETTING_OVS_BRIDGE_SETTING_NAME;
+	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;
+	device_class->release_slave = release_slave;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_OVS_BRIDGE_SKELETON,
+	                                        NULL);
+}
diff --git a/src/devices/ovs/nm-device-ovs-bridge.h b/src/devices/ovs/nm-device-ovs-bridge.h
new file mode 100644
index 00000000..631b4754
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-bridge.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 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__
+#define __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__
+
+#define NM_TYPE_DEVICE_OVS_BRIDGE            (nm_device_ovs_bridge_get_type ())
+#define NM_DEVICE_OVS_BRIDGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridge))
+#define NM_DEVICE_OVS_BRIDGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass))
+#define NM_IS_DEVICE_OVS_BRIDGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OVS_BRIDGE))
+#define NM_IS_DEVICE_OVS_BRIDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_OVS_BRIDGE))
+#define NM_DEVICE_OVS_BRIDGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_OVS_BRIDGE, NMDeviceOvsBridgeClass))
+
+typedef struct _NMDeviceOvsBridge NMDeviceOvsBridge;
+typedef struct _NMDeviceOvsBridgeClass NMDeviceOvsBridgeClass;
+
+GType nm_device_ovs_bridge_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_BRIDGE_H__ */
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
new file mode 100644
index 00000000..426521c5
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -0,0 +1,191 @@
+/* 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 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ovs-interface.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-interface.h"
+#include "nm-setting-ovs-port.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.OvsInterface.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceOvsInterface);
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsInterface {
+	NMDevice parent;
+};
+
+struct _NMDeviceOvsInterfaceClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceOvsInterface, nm_device_ovs_interface, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "ovs-interface";
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	/* The actual backing resources will be created once an interface is
+	 * added to a port of ours, since there can be neither an empty port nor
+	 * an empty bridge. */
+
+	return TRUE;
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities (NMDevice *device)
+{
+	return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static gboolean
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection)
+{
+	NMSettingConnection *s_con;
+	NMSettingOvsInterface *s_ovs_iface;
+
+	if (!NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->check_connection_compatible (device, connection))
+		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) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void
+link_changed (NMDevice *device,
+              const NMPlatformLink *pllink)
+{
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+		nm_device_bring_up (device, TRUE, NULL);
+		nm_device_activate_schedule_stage3_ip_config_start (device);
+	}
+}
+
+static gboolean
+_is_internal_interface (NMDevice *device)
+{
+	NMConnection *connection = nm_device_get_applied_connection (device);
+	NMSettingOvsInterface *s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
+
+	g_return_val_if_fail (s_ovs_iface, FALSE);
+
+	return strcmp (nm_setting_ovs_interface_get_interface_type (s_ovs_iface), "internal") == 0;
+}
+
+static NMActStageReturn
+act_stage3_ip4_config_start (NMDevice *device,
+                             NMIP4Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	if (!_is_internal_interface (device))
+		return NM_ACT_STAGE_RETURN_IP_FAIL;
+
+	if (!nm_device_get_ip_ifindex (device))
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
+}
+
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *device,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	if (!_is_internal_interface (device))
+		return NM_ACT_STAGE_RETURN_IP_FAIL;
+
+	if (!nm_device_get_ip_ifindex (device))
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+
+	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
+}
+
+static gboolean
+can_unmanaged_external_down (NMDevice *self)
+{
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_interface_init (NMDeviceOvsInterface *self)
+{
+}
+
+static void
+nm_device_ovs_interface_class_init (NMDeviceOvsInterfaceClass *klass)
+{
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_OPENVSWITCH);
+
+	device_class->connection_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
+	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->is_available = is_available;
+	device_class->check_connection_compatible = check_connection_compatible;
+	device_class->link_changed = link_changed;
+	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->can_unmanaged_external_down = can_unmanaged_external_down;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_OVS_INTERFACE_SKELETON,
+	                                        NULL);
+}
diff --git a/src/devices/ovs/nm-device-ovs-interface.h b/src/devices/ovs/nm-device-ovs-interface.h
new file mode 100644
index 00000000..a748e206
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-interface.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 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__
+#define __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__
+
+#define NM_TYPE_DEVICE_OVS_INTERFACE            (nm_device_ovs_interface_get_type ())
+#define NM_DEVICE_OVS_INTERFACE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterface))
+#define NM_DEVICE_OVS_INTERFACE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass))
+#define NM_IS_DEVICE_OVS_INTERFACE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OVS_INTERFACE))
+#define NM_IS_DEVICE_OVS_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_OVS_INTERFACE))
+#define NM_DEVICE_OVS_INTERFACE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_OVS_INTERFACE, NMDeviceOvsInterfaceClass))
+
+typedef struct _NMDeviceOvsInterface NMDeviceOvsInterface;
+typedef struct _NMDeviceOvsInterfaceClass NMDeviceOvsInterfaceClass;
+
+GType nm_device_ovs_interface_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_INTERFACE_H__ */
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
new file mode 100644
index 00000000..83199f2d
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -0,0 +1,201 @@
+/* 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 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-device-ovs-port.h"
+#include "nm-ovsdb.h"
+
+#include "devices/nm-device-private.h"
+#include "nm-active-connection.h"
+#include "nm-setting-connection.h"
+#include "nm-setting-ovs-port.h"
+#include "nm-setting-ovs-port.h"
+
+#include "introspection/org.freedesktop.NetworkManager.Device.OvsPort.h"
+
+#include "devices/nm-device-logging.h"
+_LOG_DECLARE_SELF(NMDeviceOvsPort);
+
+/*****************************************************************************/
+
+struct _NMDeviceOvsPort {
+	NMDevice parent;
+};
+
+struct _NMDeviceOvsPortClass {
+	NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE (NMDeviceOvsPort, nm_device_ovs_port, NM_TYPE_DEVICE)
+
+/*****************************************************************************/
+
+static const char *
+get_type_description (NMDevice *device)
+{
+	return "ovs-port";
+}
+
+static gboolean
+create_and_realize (NMDevice *device,
+                    NMConnection *connection,
+                    NMDevice *parent,
+                    const NMPlatformLink **out_plink,
+                    GError **error)
+{
+	/* The port will be added to ovsdb when an interface is enslaved,
+	 * because there's no such thing like an empty port. */
+
+	return TRUE;
+}
+
+static NMDeviceCapabilities
+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,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static NMActStageReturn
+act_stage3_ip6_config_start (NMDevice *device,
+                             NMIP6Config **out_config,
+                             NMDeviceStateReason *out_failure_reason)
+{
+	return NM_ACT_STAGE_RETURN_IP_FAIL;
+}
+
+static void
+add_iface_cb (GError *error, gpointer user_data)
+{
+	NMDevice *slave = user_data;
+
+	if (error) {
+	        nm_log_warn (LOGD_DEVICE, "device %s could not be added to a ovs port: %s",
+		             nm_device_get_iface (slave), error->message);
+	        nm_device_state_changed (slave,
+	                                 NM_DEVICE_STATE_FAILED,
+	                                 NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+	}
+
+	g_object_unref (slave);
+}
+
+static gboolean
+enslave_slave (NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+{
+        NMActiveConnection *ac_port = NULL;
+        NMActiveConnection *ac_bridge = NULL;
+
+	if (!configure)
+		return TRUE;
+
+
+        ac_port = NM_ACTIVE_CONNECTION (nm_device_get_act_request (device));
+        ac_bridge = nm_active_connection_get_master (ac_port);
+        if (!ac_bridge)
+                ac_bridge = ac_port;
+
+        nm_ovsdb_add_interface (nm_ovsdb_get (),
+                                nm_active_connection_get_applied_connection (ac_bridge),
+                                nm_device_get_applied_connection (device),
+                                nm_device_get_applied_connection (slave),
+                                add_iface_cb, g_object_ref (slave));
+
+	return TRUE;
+}
+
+static void
+del_iface_cb (GError *error, gpointer user_data)
+{
+	NMDevice *slave = user_data;
+
+	if (error) {
+	        nm_log_warn (LOGD_DEVICE, "device %s could not be removed from a ovs port: %s",
+		             nm_device_get_iface (slave), error->message);
+	        nm_device_state_changed (slave,
+	                                 NM_DEVICE_STATE_FAILED,
+	                                 NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+	}
+
+	g_object_unref (slave);
+}
+
+static void
+release_slave (NMDevice *device, NMDevice *slave, gboolean configure)
+{
+	nm_ovsdb_del_interface (nm_ovsdb_get (), nm_device_get_iface (slave),
+				del_iface_cb, g_object_ref (slave));
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ovs_port_init (NMDeviceOvsPort *self)
+{
+}
+
+static void
+nm_device_ovs_port_class_init (NMDeviceOvsPortClass *klass)
+{
+	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
+
+	device_class->connection_type = NM_SETTING_OVS_PORT_SETTING_NAME;
+	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;
+	device_class->release_slave = release_slave;
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_OVS_PORT_SKELETON,
+	                                        NULL);
+}
diff --git a/src/devices/ovs/nm-device-ovs-port.h b/src/devices/ovs/nm-device-ovs-port.h
new file mode 100644
index 00000000..5ccf1ec1
--- /dev/null
+++ b/src/devices/ovs/nm-device-ovs-port.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 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_OVS_PORT_H__
+#define __NETWORKMANAGER_DEVICE_OVS_PORT_H__
+
+#define NM_TYPE_DEVICE_OVS_PORT            (nm_device_ovs_port_get_type ())
+#define NM_DEVICE_OVS_PORT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPort))
+#define NM_DEVICE_OVS_PORT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass))
+#define NM_IS_DEVICE_OVS_PORT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEVICE_OVS_PORT))
+#define NM_IS_DEVICE_OVS_PORT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_DEVICE_OVS_PORT))
+#define NM_DEVICE_OVS_PORT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_DEVICE_OVS_PORT, NMDeviceOvsPortClass))
+
+typedef struct _NMDeviceOvsPort NMDeviceOvsPort;
+typedef struct _NMDeviceOvsPortClass NMDeviceOvsPortClass;
+
+GType nm_device_ovs_port_get_type (void);
+
+#endif /* __NETWORKMANAGER_DEVICE_OVS_PORT_H__ */
diff --git a/src/devices/ovs/nm-ovs-factory.c b/src/devices/ovs/nm-ovs-factory.c
new file mode 100644
index 00000000..830f94fc
--- /dev/null
+++ b/src/devices/ovs/nm-ovs-factory.c
@@ -0,0 +1,195 @@
+/* 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) 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-manager.h"
+#include "nm-ovsdb.h"
+#include "nm-device-ovs-interface.h"
+#include "nm-device-ovs-port.h"
+#include "nm-device-ovs-bridge.h"
+#include "platform/nm-platform.h"
+#include "nm-core-internal.h"
+#include "devices/nm-device-factory.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	NMDeviceFactory parent;
+} NMOvsFactory;
+
+typedef struct {
+	NMDeviceFactoryClass parent;
+} NMOvsFactoryClass;
+
+#define NM_TYPE_OVS_FACTORY            (nm_ovs_factory_get_type ())
+#define NM_OVS_FACTORY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_OVS_FACTORY, NMOvsFactory))
+#define NM_OVS_FACTORY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass))
+#define NM_IS_OVS_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_OVS_FACTORY))
+#define NM_IS_OVS_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_OVS_FACTORY))
+#define NM_OVS_FACTORY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_OVS_FACTORY, NMOvsFactoryClass))
+
+static GType nm_ovs_factory_get_type (void);
+G_DEFINE_TYPE (NMOvsFactory, nm_ovs_factory, NM_TYPE_DEVICE_FACTORY)
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_DEVICE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "ovs", __VA_ARGS__)
+
+/*****************************************************************************/
+
+NM_DEVICE_FACTORY_DECLARE_TYPES (
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_OPENVSWITCH)
+	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_OVS_BRIDGE_SETTING_NAME,
+	                                         NM_SETTING_OVS_INTERFACE_SETTING_NAME,
+	                                         NM_SETTING_OVS_PORT_SETTING_NAME)
+)
+
+G_MODULE_EXPORT NMDeviceFactory *
+nm_device_factory_create (GError **error)
+{
+	return (NMDeviceFactory *) g_object_new (NM_TYPE_OVS_FACTORY, NULL);
+}
+
+static NMDevice *
+new_device_from_type (const char *name, NMDeviceType device_type)
+{
+	GType type;
+	const char *type_desc;
+	NMLinkType link_type = NM_LINK_TYPE_NONE;
+
+	if (nm_manager_get_device (nm_manager_get (), name, device_type))
+		return NULL;
+
+	if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE) {
+		type = NM_TYPE_DEVICE_OVS_INTERFACE;
+		type_desc = "OpenVSwitch Interface";
+		link_type = NM_LINK_TYPE_OPENVSWITCH;
+	} else if (device_type == NM_DEVICE_TYPE_OVS_PORT) {
+		type = NM_TYPE_DEVICE_OVS_PORT;
+		type_desc = "OpenVSwitch Port";
+	} else if (device_type == NM_DEVICE_TYPE_OVS_BRIDGE) {
+		type = NM_TYPE_DEVICE_OVS_BRIDGE;
+		type_desc = "OpenVSwitch Bridge";
+	} else {
+		return NULL;
+	}
+
+	return g_object_new (type,
+	                     NM_DEVICE_IFACE, name,
+	                     NM_DEVICE_DRIVER, "openvswitch",
+	                     NM_DEVICE_DEVICE_TYPE, device_type,
+	                     NM_DEVICE_TYPE_DESC, type_desc,
+	                     NM_DEVICE_LINK_TYPE, link_type,
+	                     NULL);
+}
+
+static void
+ovsdb_device_added (NMOvsdb *ovsdb, const char *name, NMDeviceType device_type,
+                    NMDeviceFactory *self)
+{
+	NMDevice *device = NULL;
+
+	device = new_device_from_type (name, device_type);
+	if (!device)
+		return;
+
+	g_signal_emit_by_name (self, NM_DEVICE_FACTORY_DEVICE_ADDED, device);
+	g_object_unref (device);
+}
+
+static void
+ovsdb_device_removed (NMOvsdb *ovsdb, const char *name, NMDeviceType device_type,
+                      NMDeviceFactory *self)
+{
+	NMDevice *device;
+	NMDeviceState device_state;
+
+	device = nm_manager_get_device (nm_manager_get (), name, device_type);
+	if (!device)
+		return;
+
+	device_state = nm_device_get_state (device);
+	if (   device_type == NM_DEVICE_TYPE_OVS_INTERFACE
+	    && device_state > NM_DEVICE_STATE_DISCONNECTED
+	    && device_state < NM_DEVICE_STATE_DEACTIVATING) {
+		nm_device_state_changed (device,
+		                         NM_DEVICE_STATE_DEACTIVATING,
+		                         NM_DEVICE_STATE_REASON_REMOVED);
+	} else if (device_state == NM_DEVICE_STATE_UNMANAGED) {
+		nm_device_unrealize (device, TRUE, NULL);
+	}
+}
+
+static void
+start (NMDeviceFactory *self)
+{
+	NMOvsdb *ovsdb;
+
+	ovsdb = nm_ovsdb_get ();
+
+	g_signal_connect_object (ovsdb, NM_OVSDB_DEVICE_ADDED, G_CALLBACK (ovsdb_device_added), self, (GConnectFlags) 0);
+	g_signal_connect_object (ovsdb, NM_OVSDB_DEVICE_REMOVED, G_CALLBACK (ovsdb_device_removed), self, (GConnectFlags) 0);
+}
+
+static NMDevice *
+create_device (NMDeviceFactory *self,
+               const char *iface,
+               const NMPlatformLink *plink,
+               NMConnection *connection,
+               gboolean *out_ignore)
+{
+	NMDeviceType device_type = NM_DEVICE_TYPE_UNKNOWN;
+	const char *connection_type = NULL;
+
+	if (g_strcmp0 (iface, "ovs-system") == 0) {
+		*out_ignore = TRUE;
+		return NULL;
+	}
+
+	if (connection)
+		connection_type = nm_connection_get_connection_type (connection);
+
+	if (plink)
+		device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+	else if (g_strcmp0 (connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) == 0)
+		device_type = NM_DEVICE_TYPE_OVS_INTERFACE;
+	else if (g_strcmp0 (connection_type, NM_SETTING_OVS_PORT_SETTING_NAME) == 0)
+		device_type = NM_DEVICE_TYPE_OVS_PORT;
+	else if (g_strcmp0 (connection_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME) == 0)
+		device_type = NM_DEVICE_TYPE_OVS_BRIDGE;
+
+	return new_device_from_type (iface, device_type);
+}
+
+static void
+nm_ovs_factory_init (NMOvsFactory *self)
+{
+}
+
+static void
+nm_ovs_factory_class_init (NMOvsFactoryClass *klass)
+{
+	NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS (klass);
+
+	factory_class->get_supported_types = get_supported_types;
+	factory_class->start = start;
+	factory_class->create_device = create_device;
+}
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
new file mode 100644
index 00000000..b44668c0
--- /dev/null
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -0,0 +1,1607 @@
+/* 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) 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-ovsdb.h"
+
+#include <string.h>
+#include <jansson.h>
+#include <gmodule.h>
+#include <gio/gunixsocketaddress.h>
+
+#include "devices/nm-device.h"
+#include "platform/nm-platform.h"
+#include "nm-core-internal.h"
+
+/* Added in Jansson v2.4 (released Sep 23 2012), but travis.ci has v2.2. */
+#ifndef json_boolean
+#define json_boolean(val) ((val) ? json_true() : json_false())
+#endif
+
+/* Added in Jansson v2.5 (released Sep 19 2013), but travis.ci has v2.2. */
+#ifndef json_array_foreach
+#define json_array_foreach(array, index, value) \
+	for (index = 0; \
+	     index < json_array_size(array) && (value = json_array_get(array, index)); \
+	     index++)
+#endif
+
+/* Added in Jansson v2.3 (released Jan 27 2012) */
+#ifndef json_object_foreach
+#define json_object_foreach(object, key, value) \
+    for(key = json_object_iter_key(json_object_iter(object)); \
+        key && (value = json_object_iter_value(json_object_key_to_iter(key))); \
+        key = json_object_iter_key(json_object_iter_next(object, json_object_key_to_iter(key))))
+#endif
+
+/*****************************************************************************/
+
+typedef struct {
+	char *name;
+	char *connection_uuid;
+	GPtrArray *interfaces;          /* interface uuids */
+} OpenvswitchPort;
+
+typedef struct {
+	char *name;
+	char *connection_uuid;
+	GPtrArray *ports;               /* port uuids */
+} OpenvswitchBridge;
+
+typedef struct {
+	char *name;
+	char *type;
+	char *connection_uuid;
+} OpenvswitchInterface;
+
+/*****************************************************************************/
+
+enum {
+	DEVICE_ADDED,
+	DEVICE_REMOVED,
+	DEVICE_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct {
+	GSocketClient *client;
+	GSocketConnection *conn;
+	GCancellable *cancellable;
+	char buf[4096];                 /* Input buffer */
+	size_t bufp;                    /* Last decoded byte in the input buffer. */
+	GString *input;                 /* JSON stream waiting for decoding. */
+	GString *output;                /* JSON stream to be sent. */
+	gint64 seq;
+	GArray *calls;                  /* Method calls waiting for a response. */
+	GHashTable *interfaces;         /* interface uuid => OpenvswitchInterface */
+	GHashTable *ports;              /* port uuid => OpenvswitchPort */
+	GHashTable *bridges;            /* bridge uuid => OpenvswitchBridge */
+	const char *db_uuid;
+} NMOvsdbPrivate;
+
+struct _NMOvsdb {
+	GObject parent;
+	NMOvsdbPrivate _priv;
+};
+
+struct _NMOvsdbClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMOvsdb, nm_ovsdb, G_TYPE_OBJECT)
+
+#define NM_OVSDB_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMOvsdb, NM_IS_OVSDB)
+
+#define _NMLOG_DOMAIN      LOGD_DEVICE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "ovsdb", __VA_ARGS__)
+
+NM_DEFINE_SINGLETON_GETTER (NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB);
+
+/*****************************************************************************/
+
+static void ovsdb_try_connect (NMOvsdb *self);
+static void ovsdb_disconnect (NMOvsdb *self);
+static void ovsdb_read (NMOvsdb *self);
+static void ovsdb_write (NMOvsdb *self);
+static void ovsdb_next_command (NMOvsdb *self);
+
+/*****************************************************************************/
+
+/* ovsdb command abstraction. */
+
+typedef void (*OvsdbMethodCallback) (NMOvsdb *self, json_t *response,
+                                     GError *error, gpointer user_data);
+
+typedef enum {
+	OVSDB_MONITOR,
+	OVSDB_ADD_INTERFACE,
+	OVSDB_DEL_INTERFACE,
+} OvsdbCommand;
+
+typedef struct {
+	gint64 id;
+#define COMMAND_PENDING -1                      /* id not yet assigned */
+	OvsdbCommand command;
+	OvsdbMethodCallback callback;
+	gpointer user_data;
+	union {
+		const char *ifname;
+		struct {
+			NMConnection *bridge;
+			NMConnection *port;
+			NMConnection *interface;
+		};
+	};
+} OvsdbMethodCall;
+
+static void
+_call_trace (const char *comment, OvsdbMethodCall *call, json_t *msg)
+{
+#ifdef NM_MORE_LOGGING
+	char *str = NULL;
+
+	if (msg)
+		str = json_dumps (msg, 0);
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		_LOGT ("%s: monitor%s%s",
+		       comment,
+		       msg ? ": " : "",
+		       msg ? str : "");
+		break;
+	case OVSDB_ADD_INTERFACE:
+		_LOGT ("%s: add-iface bridge=%s port=%s interface=%s%s%s",
+		       comment,
+		       nm_connection_get_interface_name (call->bridge),
+		       nm_connection_get_interface_name (call->port),
+		       nm_connection_get_interface_name (call->interface),
+		       msg ? ": " : "",
+		       msg ? str : "");
+		break;
+	case OVSDB_DEL_INTERFACE:
+		_LOGT ("%s: del-iface interface=%s%s%s",
+		       comment, call->ifname,
+		       msg ? ": " : "",
+		       msg ? str : "");
+		break;
+	}
+
+	if (msg)
+		g_free (str);
+#endif
+}
+
+/**
+ * ovsdb_call_method:
+ *
+ * Queues the ovsdb command. Eventually fires the command right away if
+ * there's no command pending completion.
+ */
+static void
+ovsdb_call_method (NMOvsdb *self, OvsdbCommand command,
+                   const char *ifname,
+                   NMConnection *bridge, NMConnection *port, NMConnection *interface,
+                   OvsdbMethodCallback callback, gpointer user_data)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	OvsdbMethodCall *call;
+
+	/* Ensure we're not unsynchronized before we queue the method call. */
+	ovsdb_try_connect (self);
+
+	g_array_set_size (priv->calls, priv->calls->len + 1);
+	call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
+	call->id = COMMAND_PENDING;
+	call->command = command;
+	call->callback = callback;
+	call->user_data = user_data;
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		break;
+	case OVSDB_ADD_INTERFACE:
+		call->bridge = nm_simple_connection_new_clone (bridge);
+		call->port = nm_simple_connection_new_clone (port);
+		call->interface = nm_simple_connection_new_clone (interface);
+		break;
+	case OVSDB_DEL_INTERFACE:
+		call->ifname = g_strdup (ifname);
+		break;
+	}
+
+	_call_trace ("enqueue", call, NULL);
+
+	ovsdb_next_command (self);
+}
+
+/*****************************************************************************/
+
+/* Create and process the JSON-RPC messages from ovsdb. */
+
+/**
+ * _expect_ovs_bridges:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * bridges doesn't match @bridges. This is a way of detecting race conditions
+ * with other ovsdb clients that might be adding or removing bridges
+ * at the same time.
+ */
+static void
+_expect_ovs_bridges (json_t *params, const char *db_uuid, json_t *bridges)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, [s, s]]]}",
+		           "op", "wait", "table", "Open_vSwitch",
+		           "timeout", 0, "columns", "bridges",
+		           "until", "==", "rows", "bridges", "set", bridges,
+		           "where", "_uuid", "==", "uuid", db_uuid)
+	);
+}
+
+/**
+ * _set_ovs_bridges:
+ *
+ * Return a command that will update the list of bridges in @db_uuid
+ * database to @new_bridges.
+ */
+static void
+_set_ovs_bridges (json_t *params, const char *db_uuid, json_t *new_bridges)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, [s, s]]]}",
+		           "op", "update", "table", "Open_vSwitch",
+		           "row", "bridges", "set", new_bridges,
+		           "where", "_uuid", "==", "uuid", db_uuid)
+	);
+}
+
+/**
+ * _expect_bridge_ports:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * ports in bridge @ifname doesn't match @ports. This is a way of detecting
+ * race conditions with other ovsdb clients that might be adding or removing
+ * bridge ports at the same time.
+ */
+static void
+_expect_bridge_ports (json_t *params, const char *ifname, json_t *ports)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}",
+		           "op", "wait", "table", "Bridge",
+		           "timeout", 0, "columns", "ports",
+		           "until", "==", "rows", "ports", "set", ports,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _set_bridge_ports:
+ *
+ * Return a command that will update the list of ports of bridge
+ * @ifname to @new_ports.
+ */
+static void
+_set_bridge_ports (json_t *params, const char *ifname, json_t *new_ports)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}",
+		           "op", "update", "table", "Bridge",
+		           "row", "ports", "set", new_ports,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _expect_port_interfaces:
+ *
+ * Return a command that will fail the transaction if the actual set of
+ * interfaces in port @ifname doesn't match @interfaces. This is a way of
+ * detecting race conditions with other ovsdb clients that might be adding
+ * or removing port interfaces at the same time.
+ */
+static void
+_expect_port_interfaces (json_t *params, const char *ifname, json_t *interfaces)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:i, s:[s], s:s, s:[{s:[s, O]}], s:[[s, s, s]]}",
+		           "op", "wait", "table", "Port",
+		           "timeout", 0, "columns", "interfaces",
+		           "until", "==", "rows", "interfaces", "set", interfaces,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _set_port_interfaces:
+ *
+ * Return a command that will update the list of interfaces of port @ifname
+ * to @new_interfaces.
+ */
+static void
+_set_port_interfaces (json_t *params, const char *ifname, json_t *new_interfaces)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:[s, O]}, s:[[s, s, s]]}",
+		           "op", "update", "table", "Port",
+		           "row", "interfaces", "set", new_interfaces,
+		           "where", "name", "==", ifname)
+	);
+}
+
+/**
+ * _insert_interface:
+ *
+ * Returns an commands that adds new interface from a given connection.
+ */
+static void
+_insert_interface (json_t *params, NMConnection *interface)
+{
+	const char *type = NULL;
+	NMSettingOvsInterface *s_ovs_iface;
+	NMSettingOvsPatch *s_ovs_patch;
+	json_t *options = json_array ();
+
+	s_ovs_iface = nm_connection_get_setting_ovs_interface (interface);
+	if (s_ovs_iface)
+		type = nm_setting_ovs_interface_get_interface_type (s_ovs_iface);
+
+	json_array_append (options, json_string ("map"));
+	s_ovs_patch = nm_connection_get_setting_ovs_patch (interface);
+	if (s_ovs_patch) {
+		json_array_append (options, json_pack ("[[s, s]]",
+		                                       "peer",
+		                                        nm_setting_ovs_patch_get_peer (s_ovs_patch)));
+	} else {
+		json_array_append (options, json_array ());
+	}
+
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:s, s:s, s:o, s:[s, [[s, s]]]}, s:s}",
+		           "op", "insert", "table", "Interface", "row",
+		           "name", nm_connection_get_interface_name (interface),
+		           "type", type ? type : "",
+		           "options", options,
+		           "external_ids", "map", "NM.connection.uuid", nm_connection_get_uuid (interface),
+		           "uuid-name", "rowInterface"));
+}
+
+/**
+ * _insert_port:
+ *
+ * Returns an commands that adds new port from a given connection.
+ */
+static void
+_insert_port (json_t *params, NMConnection *port, json_t *new_interfaces)
+{
+	NMSettingOvsPort *s_ovs_port;
+	const char *vlan_mode = NULL;
+	guint tag = 0;
+	const char *lacp = NULL;
+	const char *bond_mode = NULL;
+	guint bond_updelay = 0;
+	guint bond_downdelay = 0;
+	json_t *row;
+
+	s_ovs_port = nm_connection_get_setting_ovs_port (port);
+
+	row = json_object ();
+
+	if (s_ovs_port) {
+		vlan_mode = nm_setting_ovs_port_get_vlan_mode (s_ovs_port);
+		tag = nm_setting_ovs_port_get_tag (s_ovs_port);
+		lacp = nm_setting_ovs_port_get_lacp (s_ovs_port);
+		bond_mode = nm_setting_ovs_port_get_bond_mode (s_ovs_port);
+		bond_updelay = nm_setting_ovs_port_get_bond_updelay (s_ovs_port);
+		bond_downdelay = nm_setting_ovs_port_get_bond_downdelay (s_ovs_port);
+	}
+
+	if (vlan_mode)
+		json_object_set_new (row, "vlan_mode", json_string (vlan_mode));
+	if (tag)
+		json_object_set_new (row, "tag", json_integer (tag));
+	if (lacp)
+		json_object_set_new (row, "lacp", json_string (lacp));
+	if (bond_mode)
+		json_object_set_new (row, "bond_mode", json_string (bond_mode));
+	if (bond_updelay)
+		json_object_set_new (row, "bond_updelay", json_integer (bond_updelay));
+	if (bond_downdelay)
+		json_object_set_new (row, "bond_downdelay", json_integer (bond_downdelay));
+
+	json_object_set_new (row, "name", json_string (nm_connection_get_interface_name (port)));
+	json_object_set_new (row, "interfaces", json_pack ("[s, O]", "set", new_interfaces));
+	json_object_set_new (row, "external_ids",
+		json_pack ("[s, [[s, s]]]", "map",
+		           "NM.connection.uuid", nm_connection_get_uuid (port)));
+
+	/* Create a new one. */
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:o, s:s}", "op", "insert", "table", "Port",
+		           "row", row, "uuid-name", "rowPort"));
+}
+
+/**
+ * _insert_bridge:
+ *
+ * Returns an commands that adds new bridge from a given connection.
+ */
+static void
+_insert_bridge (json_t *params, NMConnection *bridge, json_t *new_ports)
+{
+	NMSettingOvsBridge *s_ovs_bridge;
+	const char *fail_mode = NULL;
+	gboolean mcast_snooping_enable = FALSE;
+	gboolean rstp_enable = FALSE;
+	gboolean stp_enable = FALSE;
+	json_t *row;
+
+	s_ovs_bridge = nm_connection_get_setting_ovs_bridge (bridge);
+
+	row = json_object ();
+
+	if (s_ovs_bridge) {
+		fail_mode = nm_setting_ovs_bridge_get_fail_mode (s_ovs_bridge);
+		mcast_snooping_enable = nm_setting_ovs_bridge_get_mcast_snooping_enable (s_ovs_bridge);
+		rstp_enable = nm_setting_ovs_bridge_get_rstp_enable (s_ovs_bridge);
+		stp_enable = nm_setting_ovs_bridge_get_stp_enable (s_ovs_bridge);
+	}
+
+	if (fail_mode)
+		json_object_set_new (row, "fail_mode", json_string (fail_mode));
+	if (mcast_snooping_enable)
+		json_object_set_new (row, "mcast_snooping_enable", json_boolean (mcast_snooping_enable));
+	if (rstp_enable)
+		json_object_set_new (row, "rstp_enable", json_boolean (rstp_enable));
+	if (stp_enable)
+		json_object_set_new (row, "stp_enable", json_boolean (stp_enable));
+
+	json_object_set_new (row, "name", json_string (nm_connection_get_interface_name (bridge)));
+	json_object_set_new (row, "ports", json_pack ("[s, O]", "set", new_ports));
+	json_object_set_new (row, "external_ids",
+		json_pack ("[s, [[s, s]]]", "map",
+		           "NM.connection.uuid", nm_connection_get_uuid (bridge)));
+
+	/* Create a new one. */
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:o, s:s}", "op", "insert", "table", "Bridge",
+		           "row", row, "uuid-name", "rowBridge"));
+}
+
+/**
+ * _inc_next_cfg:
+ *
+ * Returns an mutate command that bumps next_cfg upon successful completion
+ * of the transaction it is in.
+ */
+static json_t *
+_inc_next_cfg (const char *db_uuid)
+{
+	return json_pack ("{s:s, s:s, s:[[s, s, i]], s:[[s, s, [s, s]]]}",
+                          "op", "mutate", "table", "Open_vSwitch",
+	                  "mutations", "next_cfg", "+=", 1,
+	                  "where", "_uuid", "==", "uuid", db_uuid);
+}
+
+/**
+ * _add_interface:
+ *
+ * Adds an interface as specified by @interface connection, optionally creating
+ * a parent @port and @bridge if needed.
+ */
+static void
+_add_interface (NMOvsdb *self, json_t *params,
+                NMConnection *bridge, NMConnection *port, NMConnection *interface)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GHashTableIter iter;
+	const char *bridge_uuid;
+	const char *port_uuid;
+	const char *interface_uuid;
+	OpenvswitchBridge *ovs_bridge = NULL;
+	OpenvswitchPort *ovs_port = NULL;
+	OpenvswitchInterface *ovs_interface = NULL;
+	int pi;
+	int ii;
+	json_t *bridges, *new_bridges;
+	json_t *ports, *new_ports;
+	json_t *interfaces, *new_interfaces;
+	gboolean has_interface = FALSE;
+
+	bridges = json_array ();
+	ports = json_array ();
+	interfaces = json_array ();
+	new_bridges = json_array ();
+	new_ports = json_array ();
+	new_interfaces = json_array ();
+
+	g_hash_table_iter_init (&iter, priv->bridges);
+	while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
+		json_array_append_new (bridges, json_pack ("[s, s]", "uuid", bridge_uuid));
+
+		if (   g_strcmp0 (ovs_bridge->name, nm_connection_get_interface_name (bridge)) != 0
+		    || g_strcmp0 (ovs_bridge->connection_uuid, nm_connection_get_uuid (bridge)) != 0)
+			continue;
+
+		for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+			port_uuid = g_ptr_array_index (ovs_bridge->ports, pi);
+			ovs_port = g_hash_table_lookup (priv->ports, port_uuid);
+
+			json_array_append_new (ports, json_pack ("[s, s]", "uuid", port_uuid));
+
+			if (   g_strcmp0 (ovs_port->name, nm_connection_get_interface_name (port)) != 0
+			    || g_strcmp0 (ovs_port->connection_uuid, nm_connection_get_uuid (port)) != 0)
+				continue;
+
+			for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
+				interface_uuid = g_ptr_array_index (ovs_port->interfaces, ii);
+				ovs_interface = g_hash_table_lookup (priv->interfaces, interface_uuid);
+
+				json_array_append_new (interfaces, json_pack ("[s, s]", "uuid", interface_uuid));
+
+				if (   g_strcmp0 (ovs_interface->name, nm_connection_get_interface_name (interface)) == 0
+				    && g_strcmp0 (ovs_interface->connection_uuid, nm_connection_get_uuid (interface)) == 0)
+					has_interface = TRUE;
+			}
+
+			break;
+		}
+
+		break;
+	}
+
+	json_array_extend (new_bridges, bridges);
+	json_array_extend (new_ports, ports);
+	json_array_extend (new_interfaces, interfaces);
+
+	if (json_array_size (interfaces) == 0) {
+		/* Need to create a port. */
+		if (json_array_size (ports) == 0) {
+			/* Need to create a bridge. */
+			_expect_ovs_bridges (params, priv->db_uuid, bridges);
+			json_array_append_new (new_bridges, json_pack ("[s, s]", "named-uuid", "rowBridge"));
+			_set_ovs_bridges (params, priv->db_uuid, new_bridges);
+			_insert_bridge (params, bridge, new_ports);
+		} else {
+			/* Bridge already exists. */
+			g_return_if_fail (ovs_bridge);
+			_expect_bridge_ports (params, ovs_bridge->name, ports);
+			_set_bridge_ports (params, nm_connection_get_interface_name (bridge), new_ports);
+		}
+
+		json_array_append_new (new_ports, json_pack ("[s, s]", "named-uuid", "rowPort"));
+		_insert_port (params, port, new_interfaces);
+	} else {
+		/* Port already exists */
+		g_return_if_fail (ovs_port);
+		_expect_port_interfaces (params, ovs_port->name, interfaces);
+		_set_port_interfaces (params, nm_connection_get_interface_name (port), new_interfaces);
+	}
+
+	if (!has_interface) {
+		_insert_interface (params, interface);
+		json_array_append_new (new_interfaces, json_pack ("[s, s]", "named-uuid", "rowInterface"));
+	}
+
+	json_decref (interfaces);
+	json_decref (ports);
+	json_decref (bridges);
+
+	json_decref (new_interfaces);
+	json_decref (new_ports);
+	json_decref (new_bridges);
+}
+
+/**
+ * _delete_interface:
+ *
+ * Removes an interface of @ifname name, collecting empty ports and bridge
+ * if last item is removed from them.
+ */
+static void
+_delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GHashTableIter iter;
+	char *bridge_uuid;
+	char *port_uuid;
+	char *interface_uuid;
+	OpenvswitchBridge *ovs_bridge;
+	OpenvswitchPort *ovs_port;
+	OpenvswitchInterface *ovs_interface;
+	int pi;
+	int ii;
+	json_t *bridges, *new_bridges;
+	json_t *ports, *new_ports;
+	json_t *interfaces, *new_interfaces;
+	gboolean bridges_changed;
+	gboolean ports_changed;
+	gboolean interfaces_changed;
+
+	bridges = json_array ();
+	new_bridges = json_array ();
+	bridges_changed = FALSE;
+
+	g_hash_table_iter_init (&iter, priv->bridges);
+	while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
+		json_array_append_new (bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+
+		ports = json_array ();
+		new_ports = json_array ();
+		ports_changed = FALSE;
+
+		for (pi = 0; pi < ovs_bridge->ports->len; pi++) {
+			port_uuid = g_ptr_array_index (ovs_bridge->ports, pi);
+			ovs_port = g_hash_table_lookup (priv->ports, port_uuid);
+
+			json_array_append_new (ports, json_pack ("[s,s]", "uuid", port_uuid));
+
+			interfaces = json_array ();
+			new_interfaces = json_array ();
+			interfaces_changed = FALSE;
+
+			for (ii = 0; ii < ovs_port->interfaces->len; ii++) {
+				interface_uuid = g_ptr_array_index (ovs_port->interfaces, ii);
+				ovs_interface = g_hash_table_lookup (priv->interfaces, interface_uuid);
+
+				json_array_append_new (interfaces, json_pack ("[s,s]", "uuid", interface_uuid));
+
+				if (strcmp (ovs_interface->name, ifname) == 0) {
+					/* skip the interface */
+					interfaces_changed = TRUE;
+					continue;
+				}
+
+				json_array_append_new (new_interfaces, json_pack ("[s,s]", "uuid", interface_uuid));
+			}
+
+			if (json_array_size (new_interfaces) == 0) {
+				ports_changed = TRUE;
+			} else {
+				if (interfaces_changed) {
+					_expect_port_interfaces (params, ovs_port->name, interfaces);
+					_set_port_interfaces (params, ovs_port->name, new_interfaces);
+				}
+				json_array_append_new (new_ports, json_pack ("[s,s]", "uuid", port_uuid));
+			}
+
+			json_decref (interfaces);
+			json_decref (new_interfaces);
+		}
+
+		if (json_array_size (new_ports) == 0) {
+			bridges_changed = TRUE;
+		} else {
+			if (ports_changed) {
+				_expect_bridge_ports (params, ovs_bridge->name, ports);
+				_set_bridge_ports (params, ovs_bridge->name, new_ports);
+			}
+			json_array_append_new (new_bridges, json_pack ("[s,s]", "uuid", bridge_uuid));
+		}
+
+		json_decref (ports);
+		json_decref (new_ports);
+	}
+
+	if (bridges_changed) {
+		_expect_ovs_bridges (params, priv->db_uuid, bridges);
+		_set_ovs_bridges (params, priv->db_uuid, new_bridges);
+	}
+}
+
+/**
+ * ovsdb_next_command:
+ *
+ * Translates a higher level operation (add/remove bridge/port) to a RFC 7047
+ * command serialized into JSON ands sends it over to the database.
+
+ * Only called when no command is waiting for a response, since the serialized
+ * command might depend on result of a previous one (add and remove need to
+ * include an up to date bridge list in their transactions to rule out races).
+ */
+static void
+ovsdb_next_command (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	OvsdbMethodCall *call = NULL;
+	char *cmd;
+	json_t *msg = NULL;
+	json_t *params;
+
+	if (!priv->conn)
+		return;
+	if (!priv->calls->len)
+		return;
+	call = &g_array_index (priv->calls, OvsdbMethodCall, 0);
+	if (call->id != COMMAND_PENDING)
+		return;
+	call->id = priv->seq++;
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		msg = json_pack ("{s:i, s:s, s:[s, n, {"
+		                 "  s:[{s:[s, s, s]}],"
+		                 "  s:[{s:[s, s, s]}],"
+		                 "  s:[{s:[s, s, s]}],"
+		                 "  s:[{s:[]}]"
+		                 "}]}",
+		                 "id", call->id,
+		                 "method", "monitor", "params", "Open_vSwitch",
+		                 "Bridge", "columns", "name", "ports", "external_ids",
+		                 "Port", "columns", "name", "interfaces", "external_ids",
+		                 "Interface", "columns", "name", "type", "external_ids",
+		                 "Open_vSwitch", "columns");
+		break;
+	case OVSDB_ADD_INTERFACE:
+		params = json_array ();
+		json_array_append_new (params, json_string ("Open_vSwitch"));
+		json_array_append_new (params, _inc_next_cfg (priv->db_uuid));
+
+		_add_interface (self, params, call->bridge, call->port, call->interface);
+
+		msg = json_pack ("{s:i, s:s, s:o}",
+		                 "id", call->id,
+		                 "method", "transact", "params", params);
+		break;
+	case OVSDB_DEL_INTERFACE:
+		params = json_array ();
+		json_array_append_new (params, json_string ("Open_vSwitch"));
+		json_array_append_new (params, _inc_next_cfg (priv->db_uuid));
+
+		_delete_interface (self, params, call->ifname);
+
+		msg = json_pack ("{s:i, s:s, s:o}",
+		                 "id", call->id,
+		                 "method", "transact", "params", params);
+		break;
+	}
+
+	g_return_if_fail (msg);
+	_call_trace ("send", call, msg);
+	cmd = json_dumps (msg, 0);
+
+	g_string_append (priv->output, cmd);
+	json_decref (msg);
+	free (cmd);
+
+	ovsdb_write (self);
+}
+
+/**
+ * _uuids_to_array:
+ *
+ * This tidies up the somewhat non-straightforward way ovsdb represents an array
+ * of UUID elements. The single element is a tuple (called <atom> in RFC7047),
+ *
+ *   [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ]
+ *
+ * while the list of multiple UUIDs are turned into a set of such tuples ("atoms"):
+ *
+ *   [ "set", [ [ "uuid", "aa095ffb-e1f1-0fc4-8038-82c1ea7e4797" ],
+ *              [ "uuid", "185c93f6-0b39-424e-8587-77d074aa7ce0" ], ... ] ]
+ */
+static void
+_uuids_to_array (GPtrArray *array, const json_t *items)
+{
+	const char *key;
+	json_t *value;
+	size_t index = 0;
+	json_t *set_value;
+	size_t set_index;
+
+	while (index < json_array_size (items)) {
+		key = json_string_value (json_array_get (items, index));
+		index++;
+		value = json_array_get (items, index);
+		index++;
+
+		if (!value)
+			return;
+
+		if (g_strcmp0 (key, "uuid") == 0 && json_is_string (value)) {
+			g_ptr_array_add (array, g_strdup (json_string_value (value)));
+		} else if (g_strcmp0 (key, "set") == 0 && json_is_array (value)) {
+			json_array_foreach (value, set_index, set_value) {
+				_uuids_to_array (array, set_value);
+			}
+		}
+	}
+}
+
+static char *
+_connection_uuid_from_external_ids (json_t *external_ids)
+{
+	json_t *value;
+	size_t index;
+
+	if (g_strcmp0 ("map", json_string_value (json_array_get (external_ids, 0))) != 0)
+		return NULL;
+
+	json_array_foreach (json_array_get (external_ids, 1), index, value) {
+		if (g_strcmp0 ("NM.connection.uuid", json_string_value (json_array_get (value, 0))) == 0)
+			return g_strdup (json_string_value (json_array_get (value, 1)));
+	}
+
+	return NULL;
+}
+
+/**
+ * ovsdb_got_update:
+ *
+ * Called when we've got an "update" method call (we asked for it with the monitor
+ * command). We use it to maintain a consistent view of bridge list regardless of
+ * whether the changes are done by us or externally.
+ */
+static void
+ovsdb_got_update (NMOvsdb *self, json_t *msg)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	json_t *ovs = NULL;
+	json_t *bridge = NULL;
+	json_t *port = NULL;
+	json_t *interface = NULL;
+	json_t *items;
+	json_t *external_ids;
+	json_error_t json_error = { 0, };
+	void *iter;
+	const char *name;
+	const char *key;
+	const char *type;
+	json_t *value;
+	OpenvswitchBridge *ovs_bridge;
+	OpenvswitchPort *ovs_port;
+	OpenvswitchInterface *ovs_interface;
+
+	if (json_unpack_ex (msg, &json_error, 0, "{s?:o, s?:o, s?:o, s?:o}",
+	                    "Open_vSwitch", &ovs,
+	                    "Bridge", &bridge,
+	                    "Port", &port,
+	                    "Interface", &interface) == -1) {
+		/* This doesn't really have to be an error; the key might
+		 * be missing if there really are no bridges present. */
+		_LOGD ("Bad update: %s", json_error.text);
+	}
+
+	if (ovs) {
+		iter = json_object_iter (ovs);
+		priv->db_uuid = g_strdup (iter ? json_object_iter_key (iter) : NULL);
+	}
+
+	/* Interfaces */
+	json_object_foreach (interface, key, value) {
+		gboolean old = FALSE;
+		gboolean new = FALSE;
+
+		if (json_unpack (value, "{s:{}}", "old") == 0)
+			old = TRUE;
+
+		if (json_unpack (value, "{s:{s:s, s:s, s:o}}", "new",
+		                 "name", &name,
+		                 "type", &type,
+		                 "external_ids", &external_ids) == 0)
+			new = TRUE;
+
+		if (old) {
+			ovs_interface = g_hash_table_lookup (priv->interfaces, key);
+			if (!new || g_strcmp0 (ovs_interface->name, name) != 0) {
+				old = FALSE;
+				_LOGT ("removed an '%s' interface: %s%s%s",
+				       ovs_interface->type, ovs_interface->name,
+				       ovs_interface->connection_uuid ? ", " : "",
+				       ovs_interface->connection_uuid ? ovs_interface->connection_uuid : "");
+				if (g_strcmp0 (ovs_interface->type, "internal") == 0) {
+					/* Currently the factory only creates NMDevices for
+					 * internal interfaces. Ignore the rest. */
+					g_signal_emit (self, signals[DEVICE_REMOVED], 0,
+					               ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE);
+				}
+			}
+			g_hash_table_remove (priv->interfaces, key);
+		}
+
+		if (new) {
+			ovs_interface = g_slice_new (OpenvswitchInterface);
+			ovs_interface->name = g_strdup (name);
+			ovs_interface->type = g_strdup (type);
+			ovs_interface->connection_uuid = _connection_uuid_from_external_ids (external_ids);
+			if (old) {
+				_LOGT ("changed an '%s' interface: %s%s%s", type, ovs_interface->name,
+				       ovs_interface->connection_uuid ? ", " : "",
+				       ovs_interface->connection_uuid ? ovs_interface->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_CHANGED], 0,
+				               "ovs-interface", ovs_interface->name);
+			} else {
+				_LOGT ("added an '%s' interface: %s%s%s",
+				       ovs_interface->type, ovs_interface->name,
+				       ovs_interface->connection_uuid ? ", " : "",
+				       ovs_interface->connection_uuid ? ovs_interface->connection_uuid : "");
+				if (g_strcmp0 (ovs_interface->type, "internal") == 0) {
+					/* Currently the factory only creates NMDevices for
+					 * internal interfaces. Ignore the rest. */
+					g_signal_emit (self, signals[DEVICE_ADDED], 0,
+					               ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE);
+				}
+			}
+			g_hash_table_insert (priv->interfaces, g_strdup (key), ovs_interface);
+		}
+	}
+
+	/* Ports */
+	json_object_foreach (port, key, value) {
+		gboolean old = FALSE;
+		gboolean new = FALSE;
+
+		if (json_unpack (value, "{s:{}}", "old") == 0)
+			old = TRUE;
+
+		if (json_unpack (value, "{s:{s:s, s:o, s:o}}", "new",
+		                 "name", &name,
+		                 "external_ids", &external_ids,
+		                 "interfaces", &items) == 0)
+			new = TRUE;
+
+		if (old) {
+			ovs_port = g_hash_table_lookup (priv->ports, key);
+			if (!new || g_strcmp0 (ovs_port->name, name) != 0) {
+				old = FALSE;
+				_LOGT ("removed a port: %s%s%s", ovs_port->name,
+				       ovs_port->connection_uuid ? ", " : "",
+				       ovs_port->connection_uuid ? ovs_port->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_REMOVED], 0,
+				               ovs_port->name, NM_DEVICE_TYPE_OVS_PORT);
+			}
+			g_hash_table_remove (priv->ports, key);
+		}
+
+		if (new) {
+			ovs_port = g_slice_new (OpenvswitchPort);
+			ovs_port->name = g_strdup (name);
+			ovs_port->connection_uuid = _connection_uuid_from_external_ids (external_ids);
+			ovs_port->interfaces = g_ptr_array_new_with_free_func (g_free);
+			_uuids_to_array (ovs_port->interfaces, items);
+			if (old) {
+				_LOGT ("changed a port: %s%s%s", ovs_port->name,
+				       ovs_port->connection_uuid ? ", " : "",
+				       ovs_port->connection_uuid ? ovs_port->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_CHANGED], 0,
+				               NM_SETTING_OVS_PORT_SETTING_NAME, ovs_port->name);
+			} else {
+				_LOGT ("added a port: %s%s%s", ovs_port->name,
+				       ovs_port->connection_uuid ? ", " : "",
+				       ovs_port->connection_uuid ? ovs_port->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_ADDED], 0,
+				               ovs_port->name, NM_DEVICE_TYPE_OVS_PORT);
+			}
+			g_hash_table_insert (priv->ports, g_strdup (key), ovs_port);
+		}
+	}
+
+	/* Bridges */
+	json_object_foreach (bridge, key, value) {
+		gboolean old = FALSE;
+		gboolean new = FALSE;
+
+		if (json_unpack (value, "{s:{}}", "old") == 0)
+			old = TRUE;
+
+		if (json_unpack (value, "{s:{s:s, s:o, s:o}}", "new",
+		                 "name", &name,
+		                 "external_ids", &external_ids,
+		                 "ports", &items) == 0)
+			new = TRUE;
+
+		if (old) {
+			ovs_bridge = g_hash_table_lookup (priv->bridges, key);
+			if (!new || g_strcmp0 (ovs_bridge->name, name) != 0) {
+				old = FALSE;
+				_LOGT ("removed a bridge: %s%s%s", ovs_bridge->name,
+				       ovs_bridge->connection_uuid ? ", " : "",
+				       ovs_bridge->connection_uuid ? ovs_bridge->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_REMOVED], 0,
+				               ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE);
+			}
+			g_hash_table_remove (priv->bridges, key);
+		}
+
+		if (new) {
+			ovs_bridge = g_slice_new (OpenvswitchBridge);
+			ovs_bridge->name = g_strdup (name);
+			ovs_bridge->connection_uuid = _connection_uuid_from_external_ids (external_ids);
+			ovs_bridge->ports = g_ptr_array_new_with_free_func (g_free);
+			_uuids_to_array (ovs_bridge->ports, items);
+			if (old) {
+				_LOGT ("changed a bridge: %s%s%s", ovs_bridge->name,
+				       ovs_bridge->connection_uuid ? ", " : "",
+				       ovs_bridge->connection_uuid ? ovs_bridge->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_CHANGED], 0,
+				               NM_SETTING_OVS_BRIDGE_SETTING_NAME, ovs_bridge->name);
+			} else {
+				_LOGT ("added a bridge: %s%s%s", ovs_bridge->name,
+				       ovs_bridge->connection_uuid ? ", " : "",
+				       ovs_bridge->connection_uuid ? ovs_bridge->connection_uuid : "");
+				g_signal_emit (self, signals[DEVICE_ADDED], 0,
+				               ovs_bridge->name, NM_DEVICE_TYPE_OVS_BRIDGE);
+			}
+			g_hash_table_insert (priv->bridges, g_strdup (key), ovs_bridge);
+		}
+	}
+
+}
+
+/**
+ * ovsdb_got_echo:
+ *
+ * Only implemented because the specification mandates it. Actual ovsdb hasn't been
+ * seen doing this.
+ */
+static void
+ovsdb_got_echo (NMOvsdb *self, json_int_t id, json_t *data)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	json_t *msg;
+	char *reply;
+	gboolean output_was_empty;
+
+	output_was_empty = priv->output->len == 0;
+
+	msg = json_pack ("{s:I, s:O}", "id", id, "result", data);
+	reply = json_dumps (msg, 0);
+	g_string_append (priv->output, reply);
+	json_decref (msg);
+	free (reply);
+
+	if (output_was_empty)
+		ovsdb_write (self);
+}
+
+/**
+ * ovsdb_got_msg::
+ *
+ * Called when when a complete JSON object was seen and unmarshalled.
+ * Either finishes a method call or processes a method call.
+ */
+static void
+ovsdb_got_msg (NMOvsdb *self, json_t *msg)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	json_error_t json_error = { 0, };
+	json_t *json_id = NULL;
+	gint64 id = -1;
+	const char *method = NULL;
+	json_t *params = NULL;
+	json_t *result = NULL;
+	json_t *error = NULL;
+	OvsdbMethodCall *call = NULL;
+	OvsdbMethodCallback callback;
+	gpointer user_data;
+	GError *local = NULL;
+
+	if (json_unpack_ex (msg, &json_error, 0, "{s?:o, s?:s, s?:o, s?:o, s?:o}",
+	                    "id", &json_id,
+	                    "method", &method,
+	                    "params", &params,
+	                    "result", &result,
+	                    "error", &error) == -1) {
+		_LOGW ("couldn't grok the message: %s", json_error.text);
+		ovsdb_disconnect (self);
+		return;
+	}
+
+	if (json_is_number (json_id))
+		id = json_integer_value (json_id);
+
+	if (method) {
+		/* It's a method call! */
+		if (!params) {
+			_LOGW ("a method call with no params: '%s'", method);
+			ovsdb_disconnect (self);
+			return;
+		}
+
+		if (g_strcmp0 (method, "update") == 0) {
+			/* This is a update method call. */
+			ovsdb_got_update (self, json_array_get (params, 1));
+		} else if (g_strcmp0 (method, "echo") == 0) {
+			/* This is an echo request. */
+			ovsdb_got_echo (self, id, params);
+		} else {
+			_LOGW ("got an unknown method call: '%s'", method);
+		}
+		return;
+	}
+
+	if (id > -1) {
+		/* This is a response to a method call. */
+		if (!priv->calls->len) {
+			_LOGE ("there are no queued calls expecting response %" G_GUINT64_FORMAT, id);
+			ovsdb_disconnect (self);
+			return;
+		}
+		call = &g_array_index (priv->calls, OvsdbMethodCall, 0);
+		if (call->id != id) {
+			_LOGE ("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT, call->id, id);
+			ovsdb_disconnect (self);
+			return;
+		}
+		/* Cool, we found a corresponsing call. Finish it. */
+
+		_call_trace ("response", call, msg);
+
+		if (!json_is_null (error)) {
+			/* The response contains an error. */
+			g_set_error (&local, G_IO_ERROR, G_IO_ERROR_FAILED,
+			             "Error call to OVSDB returned an error: %s",
+			              json_string_value (error));
+		}
+
+		callback = call->callback;
+		user_data = call->user_data;
+		g_array_remove_index (priv->calls, 0);
+		callback (self, result, local, user_data);
+
+		/* Don't progress further commands in case the callback hit an error
+		 * and disconnected us. */
+		if (!priv->conn)
+			return;
+
+		/* Now we're free to serialize and send the next command, if any. */
+		ovsdb_next_command (self);
+
+		return;
+	}
+
+
+	/* This is a message we are not interested in. */
+	_LOGW ("got an unknown message, ignoring");
+}
+
+/*****************************************************************************/
+
+/* Lower level marshalling and demarshalling of the JSON-RPC traffic on the
+ * ovsdb socket. */
+
+static size_t
+_json_callback (void *buffer, size_t buflen, void *user_data)
+{
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	if (priv->bufp == priv->input->len) {
+		/* No more bytes buffered for decoding. */
+		return 0;
+	}
+
+	/* Pass one more byte to the JSON decoder. */
+	*(char *)buffer = priv->input->str[priv->bufp];
+	priv->bufp++;
+
+	return (size_t)1;
+}
+
+/**
+ * ovsdb_read_cb:
+ *
+ * Read out the data available from the ovsdb socket and try to deserialize
+ * the JSON. If we see a complete object, pass it upwards to ovsdb_got_msg().
+ */
+static void
+ovsdb_read_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GInputStream *stream = G_INPUT_STREAM (source_object);
+	GError *error = NULL;
+	gssize size;
+	json_t *msg;
+	json_error_t json_error = { 0, };
+
+	size = g_input_stream_read_finish (stream, res, &error);
+	if (size == -1) {
+		_LOGW ("short read from ovsdb: %s", error->message);
+		g_clear_error (&error);
+		ovsdb_disconnect (self);
+		return;
+	}
+
+	g_string_append_len (priv->input, priv->buf, size);
+	do {
+		priv->bufp = 0;
+		/* The callback always eats up only up to a single byte. This makes
+		 * it possible for us to identify complete JSON objects in spite of
+		 * us not knowing the length in advance. */
+		msg = json_load_callback (_json_callback, self, JSON_DISABLE_EOF_CHECK, &json_error);
+		if (msg) {
+			ovsdb_got_msg (self, msg);
+			g_string_erase (priv->input, 0, priv->bufp);
+		}
+		json_decref (msg);
+	} while (msg);
+
+	if (!priv->conn)
+		return;
+
+	if (size)
+		ovsdb_read (self);
+}
+
+static void
+ovsdb_read (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	g_input_stream_read_async (g_io_stream_get_input_stream (G_IO_STREAM (priv->conn)),
+	                           priv->buf, sizeof(priv->buf),
+	                           G_PRIORITY_DEFAULT, NULL, ovsdb_read_cb, self);
+}
+
+static void
+ovsdb_write_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	GOutputStream *stream = G_OUTPUT_STREAM (source_object);
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GError *error = NULL;
+	gssize size;
+
+	size = g_output_stream_write_finish (stream, res, &error);
+	if (size == -1) {
+		_LOGW ("short write to ovsdb: %s", error->message);
+		g_clear_error (&error);
+		ovsdb_disconnect (self);
+		return;
+	}
+
+	if (!priv->conn)
+		return;
+
+	g_string_erase (priv->output, 0, size);
+
+	ovsdb_write (self);
+}
+
+static void
+ovsdb_write (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GOutputStream *stream;
+
+	if (!priv->output->len)
+		return;
+
+	stream = g_io_stream_get_output_stream (G_IO_STREAM (priv->conn));
+	if (g_output_stream_has_pending (stream))
+		return;
+
+	g_output_stream_write_async (stream,
+	                             priv->output->str, priv->output->len,
+	                             G_PRIORITY_DEFAULT, NULL, ovsdb_write_cb, self);
+}
+
+/*****************************************************************************/
+
+/* Routines to maintain the ovsdb connection. */
+
+/**
+ * ovsdb_disconnect:
+ *
+ * Clean up the internal state to the point equivalent to before connecting.
+ * Apart from clean shutdown this is a good response to unexpected trouble,
+ * since the next method call attempt a will trigger reconnect which hopefully
+ * puts us back in sync.
+ */
+static void
+ovsdb_disconnect (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	OvsdbMethodCall *call;
+	OvsdbMethodCallback callback;
+	gpointer user_data;
+	GError *error;
+
+	_LOGD ("disconnecting from ovsdb");
+
+	while (priv->calls->len) {
+		error = NULL;
+		call = &g_array_index (priv->calls, OvsdbMethodCall, priv->calls->len - 1);
+		g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
+
+		callback = call->callback;
+		user_data = call->user_data;
+		g_array_remove_index (priv->calls, priv->calls->len - 1);
+		callback (self, NULL, error, user_data);
+	}
+
+	priv->bufp = 0;
+	g_string_truncate (priv->input, 0);
+	g_string_truncate (priv->output, 0);
+	g_clear_object (&priv->client);
+	g_clear_object (&priv->conn);
+	g_clear_pointer (&priv->db_uuid, g_free);
+}
+
+static void
+_monitor_bridges_cb (NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
+{
+	if (error) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+			_LOGI ("%s", error->message);
+			ovsdb_disconnect (self);
+		}
+
+		g_clear_error (&error);
+		return;
+	}
+
+	/* Treat the first response the same as the subsequent "update"
+	 * messages we eventually get. */
+	ovsdb_got_update (self, result);
+}
+
+static void
+_client_connect_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	GSocketClient *client = G_SOCKET_CLIENT (source_object);
+	NMOvsdb *self = NM_OVSDB (user_data);
+	NMOvsdbPrivate *priv;
+	GError *error = NULL;
+	GSocketConnection *conn;
+
+	conn = g_socket_client_connect_finish (client, res, &error);
+	if (conn == NULL) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			_LOGI ("%s", error->message);
+
+		ovsdb_disconnect (self);
+		g_clear_error (&error);
+		return;
+	}
+
+	priv = NM_OVSDB_GET_PRIVATE (self);
+	priv->conn = conn;
+	g_clear_object (&priv->cancellable);
+
+	ovsdb_read (self);
+	ovsdb_next_command (self);
+}
+
+/**
+ * ovsdb_try_connect:
+ *
+ * Establish a connection to ovsdb unless it's already established or being
+ * established. Queues a monitor command as a very first one so that we're in
+ * sync when other commands are issued.
+ */
+static void
+ovsdb_try_connect (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+	GSocketAddress *addr;
+
+	if (priv->client)
+		return;
+
+	/* XXX: This should probably be made configurable via NetworkManager.conf */
+	addr = g_unix_socket_address_new (RUNSTATEDIR "/openvswitch/db.sock");
+
+	priv->client = g_socket_client_new ();
+	priv->cancellable = g_cancellable_new ();
+	g_socket_client_connect_async (priv->client, G_SOCKET_CONNECTABLE (addr),
+	                               priv->cancellable, _client_connect_cb, self);
+	g_object_unref (addr);
+
+	/* Queue a monitor call before any other command, ensuring that we have an up
+	 * to date view of existing bridged that we need for add and remove ops. */
+	ovsdb_call_method (self, OVSDB_MONITOR, NULL,
+	                   NULL, NULL, NULL, _monitor_bridges_cb, NULL);
+}
+
+/*****************************************************************************/
+
+/* Public functions useful for NMDeviceOpenvswitch to maintain the life cycle of
+ * their ovsdb entries without having to deal with ovsdb complexities themselves. */
+
+typedef struct {
+	NMOvsdbCallback callback;
+	gpointer user_data;
+} OvsdbCall;
+
+static void
+_transact_cb (NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
+{
+	OvsdbCall *call = user_data;
+	const char *err;
+	const char *err_details;
+	size_t index;
+	json_t *value;
+
+	if (error)
+		goto out;
+
+	json_array_foreach (result, index, value) {
+		if (json_unpack (value, "{s:s, s:s}", "error", &err, "details", &err_details) == 0) {
+			g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			             "Error running the transaction: %s: %s", err, err_details);
+			goto out;
+		}
+	}
+
+out:
+	call->callback (error, call->user_data);
+	g_slice_free (OvsdbCall, call);
+}
+
+void
+nm_ovsdb_add_interface (NMOvsdb *self,
+                        NMConnection *bridge, NMConnection *port, NMConnection *interface,
+                        NMOvsdbCallback callback, gpointer user_data)
+{
+	OvsdbCall *call;
+
+	call = g_slice_new (OvsdbCall);
+	call->callback = callback;
+	call->user_data = user_data;
+
+	ovsdb_call_method (self, OVSDB_ADD_INTERFACE, NULL,
+	                   bridge, port, interface, _transact_cb, call);
+}
+
+void
+nm_ovsdb_del_interface (NMOvsdb *self, const char *ifname,
+                        NMOvsdbCallback callback, gpointer user_data)
+{
+	OvsdbCall *call;
+
+	call = g_slice_new (OvsdbCall);
+	call->callback = callback;
+	call->user_data = user_data;
+
+	ovsdb_call_method (self, OVSDB_DEL_INTERFACE, ifname,
+	                   NULL, NULL, NULL, _transact_cb, call);
+}
+
+/*****************************************************************************/
+
+static void
+_clear_call (gpointer data)
+{
+	OvsdbMethodCall *call = data;
+
+	switch (call->command) {
+	case OVSDB_MONITOR:
+		break;
+	case OVSDB_ADD_INTERFACE:
+		g_clear_object (&call->bridge);
+		g_clear_object (&call->port);
+		g_clear_object (&call->interface);
+		break;
+	case OVSDB_DEL_INTERFACE:
+		g_clear_pointer (&call->ifname, g_free);
+		break;
+	}
+}
+
+static void
+_free_bridge (gpointer data)
+{
+	OpenvswitchBridge *ovs_bridge = data;
+
+	g_free (ovs_bridge->name);
+	g_free (ovs_bridge->connection_uuid);
+	g_ptr_array_free (ovs_bridge->ports, TRUE);
+	g_slice_free (OpenvswitchBridge, ovs_bridge);
+}
+
+static void
+_free_port (gpointer data)
+{
+	OpenvswitchPort *ovs_port = data;
+
+	g_free (ovs_port->name);
+	g_free (ovs_port->connection_uuid);
+	g_ptr_array_free (ovs_port->interfaces, TRUE);
+	g_slice_free (OpenvswitchPort, ovs_port);
+}
+
+static void
+_free_interface (gpointer data)
+{
+	OpenvswitchInterface *ovs_interface = data;
+
+	g_free (ovs_interface->name);
+	g_free (ovs_interface->connection_uuid);
+	g_free (ovs_interface->type);
+	g_slice_free (OpenvswitchInterface, ovs_interface);
+}
+
+static void
+nm_ovsdb_init (NMOvsdb *self)
+{
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	priv->calls = g_array_new (FALSE, TRUE, sizeof (OvsdbMethodCall));
+	g_array_set_clear_func (priv->calls, _clear_call);
+	priv->input = g_string_new (NULL);
+	priv->output = g_string_new (NULL);
+	priv->bridges = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _free_bridge);
+	priv->ports = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _free_port);
+	priv->interfaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _free_interface);
+
+	ovsdb_try_connect (self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMOvsdb *self = NM_OVSDB (object);
+	NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE (self);
+
+	ovsdb_disconnect (self);
+
+	g_string_free (priv->input, TRUE);
+	priv->input = NULL;
+	g_string_free (priv->output, TRUE);
+	priv->output = NULL;
+
+	if (priv->calls) {
+		g_array_free (priv->calls, TRUE);
+		priv->calls = NULL;
+	}
+
+	g_clear_pointer (&priv->bridges, g_hash_table_destroy);
+	g_clear_pointer (&priv->ports, g_hash_table_destroy);
+	g_clear_pointer (&priv->interfaces, g_hash_table_destroy);
+
+	g_cancellable_cancel (priv->cancellable);
+	g_clear_object (&priv->cancellable);
+
+	G_OBJECT_CLASS (nm_ovsdb_parent_class)->dispose (object);
+}
+
+static void
+nm_ovsdb_class_init (NMOvsdbClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	signals[DEVICE_ADDED] =
+		g_signal_new (NM_OVSDB_DEVICE_ADDED,
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0, NULL, NULL, NULL,
+		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+	signals[DEVICE_REMOVED] =
+		g_signal_new (NM_OVSDB_DEVICE_REMOVED,
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0, NULL, NULL, NULL,
+		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+
+	signals[DEVICE_CHANGED] =
+		g_signal_new (NM_OVSDB_DEVICE_CHANGED,
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0, NULL, NULL, NULL,
+		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+}
diff --git a/src/devices/ovs/nm-ovsdb.h b/src/devices/ovs/nm-ovsdb.h
new file mode 100644
index 00000000..cf9fe2a2
--- /dev/null
+++ b/src/devices/ovs/nm-ovsdb.h
@@ -0,0 +1,50 @@
+/* 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 2017 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_OVSDB_H__
+#define __NETWORKMANAGER_OVSDB_H__
+
+#define NM_TYPE_OVSDB            (nm_ovsdb_get_type ())
+#define NM_OVSDB(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_OVSDB, NMOvsdb))
+#define NM_OVSDB_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_OVSDB, NMOvsdbClass))
+#define NM_IS_OVSDB(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_OVSDB))
+#define NM_IS_OVSDB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_OVSDB))
+#define NM_OVSDB_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_OVSDB, NMOvsdbClass))
+
+#define NM_OVSDB_DEVICE_ADDED   "device-added"
+#define NM_OVSDB_DEVICE_REMOVED "device-removed"
+#define NM_OVSDB_DEVICE_CHANGED "device-changed"
+
+typedef struct _NMOvsdb NMOvsdb;
+typedef struct _NMOvsdbClass NMOvsdbClass;
+
+typedef void (*NMOvsdbCallback) (GError *error, gpointer user_data);
+
+NMOvsdb *nm_ovsdb_get (void);
+
+GType nm_ovsdb_get_type (void);
+
+void nm_ovsdb_add_interface (NMOvsdb *self,
+                             NMConnection *bridge, NMConnection *port, NMConnection *interface,
+                             NMOvsdbCallback callback, gpointer user_data);
+
+void nm_ovsdb_del_interface (NMOvsdb *self, const char *ifname,
+                             NMOvsdbCallback callback, gpointer user_data);
+
+#endif /* __NETWORKMANAGER_OVSDB_H__ */
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 1c4d2ef6..098cd437 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -56,6 +56,8 @@ typedef struct {
 	guint teamd_read_timeout;
 	guint teamd_dbus_watch;
 	char *config;
+	gboolean kill_in_progress;
+	NMConnection *connection;
 } NMDeviceTeamPrivate;
 
 struct _NMDeviceTeam {
@@ -84,24 +86,6 @@ get_generic_capabilities (NMDevice *device)
 }
 
 static gboolean
-is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
-{
-	return TRUE;
-}
-
-static gboolean
-check_connection_available (NMDevice *device,
-                            NMConnection *connection,
-                            NMDeviceCheckConAvailableFlags flags,
-                            const char *specific_object)
-{
-	/* Connections are always available because the carrier state is determined
-	 * by the team port carrier states, not the team's state.
-	 */
-	return TRUE;
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMSettingTeam *s_team;
@@ -306,6 +290,26 @@ master_update_slave_connection (NMDevice *self,
 }
 
 /*****************************************************************************/
+static void
+teamd_kill_cb (pid_t pid, gboolean success, int child_status, void *user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceTeam *self = (NMDeviceTeam *) device;
+	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
+
+	priv->kill_in_progress = FALSE;
+
+	if (priv->connection) {
+		_LOGT (LOGD_TEAM, "kill terminated, starting teamd...");
+		if (!teamd_start (device, priv->connection)) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED);
+		}
+		g_clear_object (&priv->connection);
+	}
+	g_object_unref (device);
+}
 
 static void
 teamd_cleanup (NMDevice *device, gboolean free_tdc)
@@ -317,7 +321,12 @@ teamd_cleanup (NMDevice *device, gboolean free_tdc)
 	nm_clear_g_source (&priv->teamd_read_timeout);
 
 	if (priv->teamd_pid > 0) {
-		nm_utils_kill_child_async (priv->teamd_pid, SIGTERM, LOGD_TEAM, "teamd", 2000, NULL, NULL);
+		priv->kill_in_progress = TRUE;
+		nm_utils_kill_child_async (priv->teamd_pid, SIGTERM,
+		                           LOGD_TEAM, "teamd",
+		                           2000,
+		                           teamd_kill_cb,
+		                           g_object_ref (device));
 		priv->teamd_pid = 0;
 	}
 
@@ -340,7 +349,7 @@ teamd_timeout_cb (gpointer user_data)
 
 	if (priv->teamd_pid && !priv->tdc) {
 		/* Timed out launching our own teamd process */
-		_LOGW (LOGD_TEAM, "teamd timed out.");
+		_LOGW (LOGD_TEAM, "teamd timed out");
 		teamd_cleanup (device, TRUE);
 
 		g_warn_if_fail (nm_device_is_activating (device));
@@ -568,7 +577,7 @@ teamd_start (NMDevice *device, NMConnection *connection)
 		/* Inject the hwaddr property into the JSON configuration.
 		 * While doing so, detect potential conflicts */
 
-		json = json_loads (config ?: "{}", 0, &jerror);
+		json = json_loads (config ?: "{}", JSON_REJECT_DUPLICATES, &jerror);
 		g_return_val_if_fail (json, FALSE);
 
 		hwaddr = json_object_get (json, "hwaddr");
@@ -663,6 +672,12 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		teamd_cleanup (device, TRUE);
 	}
 
+	if (priv->kill_in_progress) {
+		_LOGT (LOGD_TEAM, "kill in progress, wait before starting teamd");
+		priv->connection = g_object_ref (connection);
+		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
 	return teamd_start (device, connection) ?
 		NM_ACT_STAGE_RETURN_POSTPONE : NM_ACT_STAGE_RETURN_FAILURE;
 }
@@ -679,6 +694,7 @@ deactivate (NMDevice *device)
 	if (!priv->teamd_pid)
 		teamd_kill (self, NULL, NULL);
 	teamd_cleanup (device, TRUE);
+	g_clear_object (&priv->connection);
 }
 
 static gboolean
@@ -792,7 +808,7 @@ create_and_realize (NMDevice *device,
 		             "Failed to create team master interface '%s' for '%s': %s",
 		             iface,
 		             nm_connection_get_id (connection),
-		             nm_platform_error_to_string (plerr));
+		             nm_platform_error_to_string_a (plerr));
 		return FALSE;
 	}
 
@@ -822,6 +838,7 @@ get_property (GObject *object, guint prop_id,
 static void
 nm_device_team_init (NMDeviceTeam * self)
 {
+	nm_assert (nm_device_is_master (NM_DEVICE (self)));
 }
 
 static void
@@ -854,7 +871,6 @@ nm_device_team_new (const char *iface)
 	                                  NM_DEVICE_TYPE_DESC, "Team",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_TEAM,
 	                                  NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_TEAM,
-	                                  NM_DEVICE_IS_MASTER, TRUE,
 	                                  NULL);
 }
 
@@ -887,11 +903,10 @@ nm_device_team_class_init (NMDeviceTeamClass *klass)
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
 
+	parent_class->is_master = TRUE;
 	parent_class->create_and_realize = create_and_realize;
 	parent_class->get_generic_capabilities = get_generic_capabilities;
-	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->update_connection = update_connection;
 	parent_class->master_update_slave_connection = master_update_slave_connection;
diff --git a/src/devices/tests/test-arping.c b/src/devices/tests/test-arping.c
index 59223f11..4b4642f3 100644
--- a/src/devices/tests/test-arping.c
+++ b/src/devices/tests/test-arping.c
@@ -40,9 +40,8 @@ static void
 fixture_setup (test_fixture *fixture, gconstpointer user_data)
 {
 	/* create veth pair. */
-	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
-	fixture->ifindex0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
-	fixture->ifindex1 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+	fixture->ifindex0 = nmtstp_link_veth_add (NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex;
+	fixture->ifindex1 = nmtstp_link_get_typed (NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex;
 
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex0, NULL));
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex1, NULL));
@@ -87,7 +86,7 @@ test_arping_common (test_fixture *fixture, TestInfo *info)
 	g_signal_connect (manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
 	                  G_CALLBACK (arping_manager_probe_terminated), loop);
 	g_assert (nm_arping_manager_start_probe (manager, 100, NULL));
-	g_assert (nmtst_main_loop_run (loop, 1000));
+	g_assert (nmtst_main_loop_run (loop, 2000));
 
 	for (i = 0; info->addresses[i]; i++) {
 		g_assert_cmpint (nm_arping_manager_check_address (manager, info->addresses[i]),
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 24811931..ac78757d 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -312,13 +312,13 @@ companion_state_changed_cb (NMDeviceWifi *companion,
 }
 
 static gboolean
-companion_scan_allowed_cb (NMDeviceWifi *companion, gpointer user_data)
+companion_scan_prohibited_cb (NMDeviceWifi *companion, gpointer user_data)
 {
 	NMDeviceOlpcMesh *self = NM_DEVICE_OLPC_MESH (user_data);
 	NMDeviceState state = nm_device_get_state (NM_DEVICE (self));
 
 	/* Don't allow the companion to scan while configuring the mesh interface */
-	return (state < NM_DEVICE_STATE_PREPARE) || (state > NM_DEVICE_STATE_IP_CONFIG);
+	return (state >= NM_DEVICE_STATE_PREPARE) && (state <= NM_DEVICE_STATE_IP_CONFIG);
 }
 
 static gboolean
@@ -358,8 +358,8 @@ check_companion (NMDeviceOlpcMesh *self, NMDevice *other)
 	g_signal_connect (G_OBJECT (other), "notify::" NM_DEVICE_WIFI_SCANNING,
 	                  G_CALLBACK (companion_notify_cb), self);
 
-	g_signal_connect (G_OBJECT (other), NM_DEVICE_WIFI_SCANNING_ALLOWED,
-	                  G_CALLBACK (companion_scan_allowed_cb), self);
+	g_signal_connect (G_OBJECT (other), NM_DEVICE_WIFI_SCANNING_PROHIBITED,
+	                  G_CALLBACK (companion_scan_prohibited_cb), self);
 
 	g_signal_connect (G_OBJECT (other), NM_DEVICE_AUTOCONNECT_ALLOWED,
 	                  G_CALLBACK (companion_autoconnect_allowed_cb), self);
@@ -425,6 +425,13 @@ state_changed (NMDevice *device,
 		find_companion (NM_DEVICE_OLPC_MESH (device));
 }
 
+static guint32
+get_dhcp_timeout (NMDevice *device, int addr_family)
+{
+	/* shorter timeout for mesh connectivity */
+	return 20;
+}
+
 /*****************************************************************************/
 
 static void
@@ -465,11 +472,8 @@ constructed (GObject *object)
 
 	priv->manager = g_object_ref (nm_manager_get ());
 
-	g_signal_connect (priv->manager, "device-added", G_CALLBACK (device_added_cb), self);
-	g_signal_connect (priv->manager, "device-removed", G_CALLBACK (device_removed_cb), self);
-
-	/* shorter timeout for mesh connectivity */
-	nm_device_set_dhcp_timeout (NM_DEVICE (self), 20);
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK (device_added_cb), self);
+	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK (device_removed_cb), self);
 }
 
 NMDevice *
@@ -519,6 +523,7 @@ nm_device_olpc_mesh_class_init (NMDeviceOlpcMeshClass *klass)
 	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;
 
 	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 20692ed9..8bfddbd9 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.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 - 2012 Red Hat, Inc.
+ * Copyright (C) 2005 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -63,8 +63,6 @@ _LOG_DECLARE_SELF(NMDeviceWifi);
 
 #define SCAN_RAND_MAC_ADDRESS_EXPIRE_MIN 5
 
-static NM_CACHED_QUARK_FCN ("wireless-secrets-tries", wireless_secrets_tries_quark)
-
 /*****************************************************************************/
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
@@ -79,7 +77,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceWifi,
 enum {
 	ACCESS_POINT_ADDED,
 	ACCESS_POINT_REMOVED,
-	SCANNING_ALLOWED,
+	SCANNING_PROHIBITED,
 
 	LAST_SIGNAL
 };
@@ -119,6 +117,8 @@ typedef struct {
 	NMDeviceWifiCapabilities capabilities;
 
 	gint32 hw_addr_scan_expire;
+
+	guint             wps_timeout_id;
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi
@@ -132,7 +132,7 @@ struct _NMDeviceWifiClass
 	NMDeviceClass parent;
 
 	/* Signals */
-	gboolean (*scanning_allowed) (NMDeviceWifi *device);
+	gboolean (*scanning_prohibited) (NMDeviceWifi *device, gboolean periodic);
 };
 
 /*****************************************************************************/
@@ -143,7 +143,7 @@ G_DEFINE_TYPE (NMDeviceWifi, nm_device_wifi, NM_TYPE_DEVICE)
 
 /*****************************************************************************/
 
-static gboolean check_scanning_allowed (NMDeviceWifi *self);
+static gboolean check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic);
 
 static void schedule_scan (NMDeviceWifi *self, gboolean backoff);
 
@@ -169,6 +169,10 @@ static void supplicant_iface_scan_done_cb (NMSupplicantInterface * iface,
                                            gboolean success,
                                            NMDeviceWifi * self);
 
+static void supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
+                                                 GVariant *credentials,
+                                                 NMDeviceWifi *self);
+
 static void supplicant_iface_notify_scanning_cb (NMSupplicantInterface * iface,
                                                  GParamSpec * pspec,
                                                  NMDeviceWifi * self);
@@ -177,7 +181,10 @@ static void supplicant_iface_notify_current_bss (NMSupplicantInterface *iface,
                                                  GParamSpec *pspec,
                                                  NMDeviceWifi *self);
 
-static void request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant *scan_options);
+static void request_wireless_scan (NMDeviceWifi *self,
+                                   gboolean periodic,
+                                   gboolean force_if_scanning,
+                                   const GPtrArray *ssids);
 
 static void ap_add_remove (NMDeviceWifi *self,
                            guint signum,
@@ -268,6 +275,10 @@ supplicant_interface_acquire (NMDeviceWifi *self)
 	                  G_CALLBACK (supplicant_iface_scan_done_cb),
 	                  self);
 	g_signal_connect (priv->sup_iface,
+	                  NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
+	                  G_CALLBACK (supplicant_iface_wps_credentials_cb),
+	                  self);
+	g_signal_connect (priv->sup_iface,
 	                  "notify::"NM_SUPPLICANT_INTERFACE_SCANNING,
 	                  G_CALLBACK (supplicant_iface_notify_scanning_cb),
 	                  self);
@@ -559,7 +570,7 @@ deactivate (NMDevice *device)
 
 	/* Ensure we trigger a scan after deactivating a Hotspot */
 	if (old_mode == NM_802_11_MODE_AP)
-		request_wireless_scan (self, FALSE, NULL);
+		request_wireless_scan (self, FALSE, FALSE, NULL);
 }
 
 static void
@@ -781,15 +792,18 @@ complete_connection (NMDevice *device,
 	NMSettingWireless *s_wifi;
 	const char *setting_mac;
 	char *str_ssid = NULL;
-	NMWifiAP *ap = NULL;
+	NMWifiAP *ap;
 	const GByteArray *ssid = NULL;
 	GByteArray *tmp_ssid = NULL;
 	GBytes *setting_ssid = NULL;
 	gboolean hidden = FALSE;
 	const char *perm_hw_addr;
+	const char *mode;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 
+	mode = s_wifi ? nm_setting_wireless_get_mode (s_wifi) : NULL;
+
 	if (!specific_object) {
 		/* If not given a specific object, we need at minimum an SSID */
 		if (!s_wifi) {
@@ -809,19 +823,29 @@ complete_connection (NMDevice *device,
 			return FALSE;
 		}
 
-		/* Find a compatible AP in the scan list */
-		ap = find_first_compatible_ap (self, connection, FALSE);
+		if (!nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+			/* Find a compatible AP in the scan list */
+			ap = find_first_compatible_ap (self, connection, FALSE);
 
-		/* If we still don't have an AP, then the WiFI settings needs to be
-		 * fully specified by the client.  Might not be able to find an AP
-		 * if the network isn't broadcasting the SSID for example.
-		 */
-		if (!ap) {
+			/* If we still don't have an AP, then the WiFI settings needs to be
+			 * fully specified by the client.  Might not be able to find an AP
+			 * if the network isn't broadcasting the SSID for example.
+			 */
+			if (!ap) {
+				if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+					return FALSE;
+
+				hidden = TRUE;
+			}
+		} else {
 			if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
 				return FALSE;
-
-			hidden = TRUE;
+			ap = NULL;
 		}
+	} else if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP)) {
+		if (!nm_setting_verify (NM_SETTING (s_wifi), connection, error))
+			return FALSE;
+		ap = NULL;
 	} else {
 		ap = get_ap_by_path (self, specific_object);
 		if (!ap) {
@@ -1169,23 +1193,65 @@ _hw_addr_set_scanning (NMDeviceWifi *self, gboolean do_reset)
 	}
 }
 
+static GPtrArray *
+ssids_options_to_ptrarray (GVariant *value, GError **error)
+{
+	GPtrArray *ssids = NULL;
+	GByteArray *ssid_array;
+	GVariant *v;
+	const guint8 *bytes;
+	gsize len;
+	int num_ssids, i;
+
+	num_ssids = g_variant_n_children (value);
+	if (num_ssids > 32) {
+		g_set_error_literal (error,
+		                     NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_NOT_ALLOWED,
+		                     "too many SSIDs requested to scan");
+		return NULL;
+	}
+
+	if (num_ssids) {
+		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_byte_array_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));
+			if (len > 32) {
+				g_set_error (error,
+				             NM_DEVICE_ERROR,
+				             NM_DEVICE_ERROR_NOT_ALLOWED,
+				             "SSID at index %d more than 32 bytes", i);
+				g_ptr_array_unref (ssids);
+				return NULL;
+			}
+
+			ssid_array = g_byte_array_new ();
+			g_byte_array_append (ssid_array, bytes, len);
+			g_ptr_array_add (ssids, ssid_array);
+		}
+	}
+	return ssids;
+}
+
 static void
-request_scan_cb (NMDevice *device,
-                 GDBusMethodInvocation *context,
-                 NMAuthSubject *subject,
-                 GError *error,
-                 gpointer user_data)
+dbus_request_scan_cb (NMDevice *device,
+                      GDBusMethodInvocation *context,
+                      NMAuthSubject *subject,
+                      GError *error,
+                      gpointer user_data)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv;
-	gs_unref_variant GVariant *new_scan_options = user_data;
+	gs_unref_variant GVariant *scan_options = user_data;
+	gs_unref_ptrarray GPtrArray *ssids = NULL;
 
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
 		return;
 	}
 
-	if (!check_scanning_allowed (self)) {
+	if (check_scanning_prohibited (self, FALSE)) {
 		g_dbus_method_invocation_return_error_literal (context,
 		                                               NM_DEVICE_ERROR,
 		                                               NM_DEVICE_ERROR_NOT_ALLOWED,
@@ -1195,7 +1261,29 @@ request_scan_cb (NMDevice *device,
 
 	priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
-	request_wireless_scan (self, FALSE, new_scan_options);
+	if (scan_options) {
+		gs_unref_variant GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
+
+		if (val) {
+			gs_free_error GError *ssid_error = NULL;
+
+			if (!g_variant_is_of_type (val, G_VARIANT_TYPE ("aay"))) {
+				g_dbus_method_invocation_return_error_literal (context,
+				                                               NM_DEVICE_ERROR,
+				                                               NM_DEVICE_ERROR_NOT_ALLOWED,
+				                                               "Invalid 'ssid' scan option");
+				return;
+			}
+
+			ssids = ssids_options_to_ptrarray (val, &ssid_error);
+			if (ssid_error) {
+				g_dbus_method_invocation_return_gerror (context, ssid_error);
+				return;
+			}
+		}
+	}
+
+	request_wireless_scan (self, FALSE, FALSE, ssids);
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
@@ -1243,22 +1331,23 @@ impl_device_wifi_request_scan (NMDeviceWifi *self,
 	                       NULL,
 	                       NM_AUTH_PERMISSION_NETWORK_CONTROL,
 	                       TRUE,
-	                       request_scan_cb,
+	                       dbus_request_scan_cb,
 	                       options ? g_variant_ref (options) : NULL);
 }
 
 static gboolean
-scanning_allowed (NMDeviceWifi *self)
+scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSupplicantInterfaceState supplicant_state;
-	NMConnection *connection;
 
-	g_return_val_if_fail (priv->sup_iface != NULL, FALSE);
+	g_return_val_if_fail (priv->sup_iface != NULL, TRUE);
 
-	/* Scanning not done in AP mode */
-	if (priv->mode == NM_802_11_MODE_AP)
-		return FALSE;
+	/* Don't scan when a an AP or Ad-Hoc connection is active as it will
+	 * disrupt connected clients or peers.
+	 */
+	if (priv->mode == NM_802_11_MODE_ADHOC || priv->mode == NM_802_11_MODE_AP)
+		return TRUE;
 
 	switch (nm_device_get_state (NM_DEVICE (self))) {
 	case NM_DEVICE_STATE_UNKNOWN:
@@ -1271,78 +1360,42 @@ scanning_allowed (NMDeviceWifi *self)
 	case NM_DEVICE_STATE_IP_CHECK:
 	case NM_DEVICE_STATE_SECONDARIES:
 	case NM_DEVICE_STATE_DEACTIVATING:
-		/* Don't scan when unusable or activating */
-		return FALSE;
+		/* Prohibit scans when unusable or activating */
+		return TRUE;
 	case NM_DEVICE_STATE_DISCONNECTED:
 	case NM_DEVICE_STATE_FAILED:
 		/* Can always scan when disconnected */
-		return TRUE;
+		return FALSE;
 	case NM_DEVICE_STATE_ACTIVATED:
-		/* Need to do further checks when activated */
+		/* Prohibit periodic scans when connected; we ask the supplicant to
+		 * background scan for us, unless the connection is locked to a specifc
+		 * BSSID.
+		 */
+		if (periodic)
+			return TRUE;
 		break;
 	}
 
-	/* Don't scan if the supplicant is busy */
+	/* Prohibit scans if the supplicant is busy */
 	supplicant_state = nm_supplicant_interface_get_state (priv->sup_iface);
 	if (   supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATING
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_ASSOCIATED
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_4WAY_HANDSHAKE
 	    || supplicant_state == NM_SUPPLICANT_INTERFACE_STATE_GROUP_HANDSHAKE
 	    || nm_supplicant_interface_get_scanning (priv->sup_iface))
-		return FALSE;
-
-	connection = nm_device_get_applied_connection (NM_DEVICE (self));
-	if (connection) {
-		NMSettingWireless *s_wifi;
-		const char *ip4_method = NULL;
-
-		/* Don't scan when a shared connection is active; it makes drivers mad */
-		ip4_method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-
-		if (!strcmp (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
-			return FALSE;
-
-		/* Don't scan when the connection is locked to a specifc AP, since
-		 * intra-ESS roaming (which requires periodic scanning) isn't being
-		 * used due to the specific AP lock. (bgo #513820)
-		 */
-		s_wifi = nm_connection_get_setting_wireless (connection);
-		g_assert (s_wifi);
-		if (nm_setting_wireless_get_bssid (s_wifi))
-			return FALSE;
-	}
-
-	return TRUE;
-}
+		return TRUE;
 
-static gboolean
-scanning_allowed_accumulator (GSignalInvocationHint *ihint,
-                              GValue *return_accu,
-                              const GValue *handler_return,
-                              gpointer data)
-{
-	if (!g_value_get_boolean (handler_return))
-		g_value_set_boolean (return_accu, FALSE);
-	return TRUE;
+	/* Allow the scan */
+	return FALSE;
 }
 
 static gboolean
-check_scanning_allowed (NMDeviceWifi *self)
+check_scanning_prohibited (NMDeviceWifi *self, gboolean periodic)
 {
-	GValue instance = G_VALUE_INIT;
-	GValue retval = G_VALUE_INIT;
-
-	g_value_init (&instance, G_TYPE_OBJECT);
-	g_value_take_object (&instance, self);
-
-	g_value_init (&retval, G_TYPE_BOOLEAN);
-	g_value_set_boolean (&retval, TRUE);
+	gboolean prohibited = FALSE;
 
-	/* Use g_signal_emitv() rather than g_signal_emit() to avoid the return
-	 * value being changed if no handlers are connected */
-	g_signal_emitv (&instance, signals[SCANNING_ALLOWED], 0, &retval);
-
-	return g_value_get_boolean (&retval);
+	g_signal_emit (self, signals[SCANNING_PROHIBITED], 0, periodic, &prohibited);
+	return prohibited;
 }
 
 static gboolean
@@ -1354,8 +1407,12 @@ hidden_filter_func (NMSettings *settings,
 
 	if (!nm_connection_is_type (NM_CONNECTION (connection), NM_SETTING_WIRELESS_SETTING_NAME))
 		return FALSE;
-	s_wifi = (NMSettingWireless *) nm_connection_get_setting_wireless (NM_CONNECTION (connection));
-	return s_wifi ? nm_setting_wireless_get_hidden (s_wifi) : FALSE;
+	s_wifi = nm_connection_get_setting_wireless (NM_CONNECTION (connection));
+	if (!s_wifi)
+		return FALSE;
+	if (nm_streq0 (nm_setting_wireless_get_mode (s_wifi), NM_SETTING_WIRELESS_MODE_AP))
+		return FALSE;
+	return nm_setting_wireless_get_hidden (s_wifi);
 }
 
 static GPtrArray *
@@ -1410,32 +1467,11 @@ build_hidden_probe_list (NMDeviceWifi *self)
 	return ssids;
 }
 
-static GPtrArray *
-ssids_options_to_ptrarray (GVariant *value)
-{
-	GPtrArray *ssids = NULL;
-	GByteArray *ssid_array;
-	GVariant *v;
-	const guint8 *bytes;
-	gsize len;
-	int num_ssids, i;
-
-	num_ssids = g_variant_n_children (value);
-	if (num_ssids) {
-		ssids = g_ptr_array_new_full (num_ssids, (GDestroyNotify) g_byte_array_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));
-			ssid_array = g_byte_array_new ();
-			g_byte_array_append (ssid_array, bytes, len);
-			g_ptr_array_add (ssids, ssid_array);
-		}
-	}
-	return ssids;
-}
-
 static void
-request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant *scan_options)
+request_wireless_scan (NMDeviceWifi *self,
+                       gboolean periodic,
+                       gboolean force_if_scanning,
+                       const GPtrArray *ssids)
 {
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	gboolean request_started = FALSE;
@@ -1447,24 +1483,14 @@ request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant
 		return;
 	}
 
-	if (check_scanning_allowed (self)) {
-		gs_unref_ptrarray GPtrArray *ssids = NULL;
+	if (!check_scanning_prohibited (self, periodic)) {
+		gs_unref_ptrarray GPtrArray *hidden_ssids = NULL;
 
 		_LOGD (LOGD_WIFI, "wifi-scan: scanning requested");
 
-		if (scan_options) {
-			GVariant *val = g_variant_lookup_value (scan_options, "ssids", NULL);
-
-			if (val) {
-				if (g_variant_is_of_type (val, G_VARIANT_TYPE ("aay")))
-					ssids = ssids_options_to_ptrarray (val);
-				else
-					_LOGD (LOGD_WIFI, "wifi-scan: ignoring invalid 'ssids' scan option");
-				g_variant_unref (val);
-			}
+		if (!ssids) {
+			ssids = hidden_ssids = build_hidden_probe_list (self);
 		}
-		if (!ssids)
-			ssids = build_hidden_probe_list (self);
 
 		if (_LOGD_ENABLED (LOGD_WIFI)) {
 			if (ssids) {
@@ -1478,7 +1504,7 @@ request_wireless_scan (NMDeviceWifi *self, gboolean force_if_scanning, GVariant
 					      ? 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, "\"", "<hidden>"));
+					       i, NM_PRINT_FMT_QUOTED (foo, "\"", foo, "\"", "*any*"));
 					g_free (foo);
 				}
 			} else
@@ -1504,7 +1530,7 @@ request_wireless_scan_periodic (gpointer user_data)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	priv->pending_scan_id = 0;
-	request_wireless_scan (self, FALSE, NULL);
+	request_wireless_scan (self, TRUE, FALSE, NULL);
 	return G_SOURCE_REMOVE;
 }
 
@@ -1747,6 +1773,7 @@ cleanup_association_attempt (NMDeviceWifi *self, gboolean disconnect)
 
 	nm_clear_g_source (&priv->sup_timeout_id);
 	nm_clear_g_source (&priv->link_timeout_id);
+	nm_clear_g_source (&priv->wps_timeout_id);
 	if (disconnect && priv->sup_iface)
 		nm_supplicant_interface_disconnect (priv->sup_iface);
 }
@@ -1789,9 +1816,19 @@ wifi_secrets_cb (NMActRequest *req,
 
 	if (error) {
 		_LOGW (LOGD_WIFI, "%s", error->message);
-		nm_device_state_changed (device,
-		                         NM_DEVICE_STATE_FAILED,
-		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+
+		if (g_error_matches (error, NM_AGENT_MANAGER_ERROR,
+		                     NM_AGENT_MANAGER_ERROR_USER_CANCELED)) {
+			/* Don't wait for WPS timeout on an explicit cancel. */
+			nm_clear_g_source (&priv->wps_timeout_id);
+		}
+
+		if (!priv->wps_timeout_id) {
+			/* Fail the device only if the WPS period is over too. */
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+		}
 	} else
 		nm_device_activate_schedule_stage1_device_prepare (device);
 }
@@ -1807,6 +1844,78 @@ wifi_secrets_cancel (NMDeviceWifi *self)
 }
 
 static void
+supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
+                                     GVariant *credentials,
+                                     NMDeviceWifi *self)
+{
+	NMActRequest *req;
+	GVariant *val, *secrets = NULL;
+	const char *array;
+	gsize psk_len = 0;
+	GError *error = NULL;
+
+	if (nm_device_get_state (NM_DEVICE (self)) != NM_DEVICE_STATE_NEED_AUTH) {
+		_LOGI (LOGD_DEVICE | LOGD_WIFI, "WPS: The connection can't be updated with credentials");
+		return;
+	}
+
+	_LOGI (LOGD_DEVICE | LOGD_WIFI, "WPS: Updating the connection with credentials");
+
+	req = nm_device_get_act_request (NM_DEVICE (self));
+	g_return_if_fail (NM_IS_ACT_REQUEST (req));
+
+	val = g_variant_lookup_value (credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
+	if (val) {
+		char psk[64];
+
+		array = g_variant_get_fixed_array (val, &psk_len, 1);
+		if (psk_len >= 8 && psk_len <= 63) {
+			memcpy (psk, array, psk_len);
+			psk[psk_len] = '\0';
+			if (g_utf8_validate (psk, psk_len, NULL)) {
+				secrets = g_variant_new_parsed ("[{%s, [{%s, <%s>}]}]",
+				                                NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+				                                NM_SETTING_WIRELESS_SECURITY_PSK, psk);
+				g_variant_ref_sink (secrets);
+			}
+		}
+		if (!secrets)
+			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK");
+		g_variant_unref (val);
+	}
+	if (secrets) {
+		if (nm_settings_connection_new_secrets (nm_act_request_get_settings_connection (req),
+		                                         nm_act_request_get_applied_connection (req),
+		                                         NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+		                                         secrets, &error)) {
+			wifi_secrets_cancel (self);
+			nm_device_activate_schedule_stage1_device_prepare (NM_DEVICE (self));
+		} else {
+			_LOGW (LOGD_DEVICE | LOGD_WIFI, "WPS: Could not update the connection with credentials: %s", error->message);
+			g_error_free (error);
+		}
+		g_variant_unref (secrets);
+	}
+}
+
+static gboolean
+wps_timeout_cb (gpointer user_data)
+{
+	NMDeviceWifi *self = NM_DEVICE_WIFI (user_data);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+
+	priv->wps_timeout_id = 0;
+	if (!priv->wifi_secrets_id) {
+		/* Fail only if the secrets are not being requested. */
+		nm_device_state_changed (NM_DEVICE (self),
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_NO_SECRETS);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
 wifi_secrets_get_secrets (NMDeviceWifi *self,
                           const char *setting_name,
                           NMSecretAgentGetSecretsFlags flags)
@@ -2055,6 +2164,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 	case NM_SUPPLICANT_INTERFACE_STATE_COMPLETED:
 		nm_clear_g_source (&priv->sup_timeout_id);
 		nm_clear_g_source (&priv->link_timeout_id);
+		nm_clear_g_source (&priv->wps_timeout_id);
 
 		/* If this is the initial association during device activation,
 		 * schedule the next activation stage.
@@ -2131,7 +2241,7 @@ supplicant_iface_state_cb (NMSupplicantInterface *iface,
 		/* we would clear _requested_scan_set() and trigger a new scan.
 		 * However, we don't want to cancel the current pending action, so force
 		 * a new scan request. */
-		request_wireless_scan (self, TRUE, NULL);
+		request_wireless_scan (self, FALSE, TRUE, NULL);
 		break;
 	default:
 		break;
@@ -2222,9 +2332,15 @@ handle_auth_or_fail (NMDeviceWifi *self,
                      NMActRequest *req,
                      gboolean new_secrets)
 {
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	const char *setting_name;
-	guint32 tries;
 	NMConnection *applied_connection;
+	NMSettingWirelessSecurity *s_wsec;
+	const char *bssid = NULL;
+	NM80211ApFlags ap_flags;
+	NMSettingWirelessSecurityWpsMethod wps_method;
+	const char *type;
+	NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
 	g_return_val_if_fail (NM_IS_DEVICE_WIFI (self), FALSE);
 
@@ -2233,14 +2349,50 @@ handle_auth_or_fail (NMDeviceWifi *self,
 		g_return_val_if_fail (req, FALSE);
 	}
 
-	applied_connection = nm_act_request_get_applied_connection (req);
-
-	tries = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark ()));
-	if (tries > 3)
+	if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
 		return FALSE;
 
 	nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
 
+	applied_connection = nm_act_request_get_applied_connection (req);
+	s_wsec = nm_connection_get_setting_wireless_security (applied_connection);
+	wps_method = nm_setting_wireless_security_get_wps_method (s_wsec);
+
+	/* Negotiate the WPS method */
+	if (wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
+		wps_method = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO;
+
+	if (   wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO
+	    && priv->current_ap) {
+		/* Determine the method to use from AP capabilities. */
+		ap_flags = nm_wifi_ap_get_flags (priv->current_ap);
+		if (ap_flags & NM_802_11_AP_FLAGS_WPS_PBC)
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
+		if (ap_flags & NM_802_11_AP_FLAGS_WPS_PIN)
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
+		if (   ap_flags & NM_802_11_AP_FLAGS_WPS
+		    && wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_AUTO) {
+			/* The AP doesn't specify which methods are supported. Allow all. */
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC;
+			wps_method |= NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN;
+		}
+	}
+
+	if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PBC) {
+		get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_WPS_PBC_ACTIVE;
+		type = "pbc";
+	} else if (wps_method & NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
+		type = "pin";
+	} else
+		type = NULL;
+
+	if (type) {
+		priv->wps_timeout_id = g_timeout_add_seconds (30, wps_timeout_cb, self);
+		if (priv->current_ap)
+			bssid = nm_wifi_ap_get_address (priv->current_ap);
+		nm_supplicant_interface_enroll_wps (priv->sup_iface, type, bssid, NULL);
+	}
+
 	nm_act_request_clear_secrets (req);
 	setting_name = nm_connection_need_secrets (applied_connection, NULL);
 	if (!setting_name) {
@@ -2248,10 +2400,9 @@ handle_auth_or_fail (NMDeviceWifi *self,
 		return FALSE;
 	}
 
-	wifi_secrets_get_secrets (self, setting_name,
-	                          NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
-	                          | (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), GUINT_TO_POINTER (++tries));
+	if (new_secrets)
+		get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+	wifi_secrets_get_secrets (self, setting_name, get_secret_flags);
 	return TRUE;
 }
 
@@ -2348,6 +2499,8 @@ build_supplicant_config (NMDeviceWifi *self,
 	NMSupplicantConfig *config = NULL;
 	NMSettingWireless *s_wireless;
 	NMSettingWirelessSecurity *s_wireless_sec;
+	NMSettingWirelessSecurityPmf pmf;
+	gs_free char *value = NULL;
 
 	g_return_val_if_fail (priv->sup_iface, NULL);
 
@@ -2370,6 +2523,11 @@ build_supplicant_config (NMDeviceWifi *self,
 		goto error;
 	}
 
+	if (!nm_supplicant_config_add_bgscan (config, connection, error)) {
+		g_prefix_error (error, "bgscan: ");
+		goto error;
+	}
+
 	s_wireless_sec = nm_connection_get_setting_wireless_security (connection);
 	if (s_wireless_sec) {
 		NMSetting8021x *s_8021x;
@@ -2378,12 +2536,46 @@ build_supplicant_config (NMDeviceWifi *self,
 		                                        nm_device_get_ifindex (NM_DEVICE (self)));
 
 		g_assert (con_uuid);
+
+		/* 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);
+		}
+
+		/* Don't try to enable PMF on non-WPA networks */
+		if (!NM_IN_STRSET (nm_setting_wireless_security_get_key_mgmt (s_wireless_sec),
+		                   "wpa-eap",
+		                   "wpa-psk"))
+			pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+
+		/* Check if we actually support PMF */
+		if (nm_supplicant_interface_get_pmf_support (priv->sup_iface) != NM_SUPPLICANT_FEATURE_YES) {
+			if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
+				g_set_error_literal (error, NM_SUPPLICANT_ERROR, NM_SUPPLICANT_ERROR_CONFIG,
+				                     "Supplicant does not support PMF");
+				goto error;
+			} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL) {
+				/* To be on the safe side, assume no support if we can't determine
+				 * capabilities.
+				 */
+				pmf = NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE;
+			}
+		}
+
 		s_8021x = nm_connection_get_setting_802_1x (connection);
 		if (!nm_supplicant_config_add_setting_wireless_security (config,
 		                                                         s_wireless_sec,
 		                                                         s_8021x,
 		                                                         con_uuid,
 		                                                         mtu,
+		                                                         pmf,
 		                                                         error)) {
 			g_prefix_error (error, "802-11-wireless-security: ");
 			goto error;
@@ -2430,6 +2622,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	s_wireless = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wireless, NM_ACT_STAGE_RETURN_FAILURE);
 
+	nm_supplicant_interface_cancel_wps (priv->sup_iface);
+
 	mode = nm_setting_wireless_get_mode (s_wireless);
 	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_INFRA) == 0)
 		priv->mode = NM_802_11_MODE_INFRA;
@@ -2578,6 +2772,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	nm_clear_g_source (&priv->sup_timeout_id);
 	nm_clear_g_source (&priv->link_timeout_id);
+	nm_clear_g_source (&priv->wps_timeout_id);
 
 	req = nm_device_get_act_request (device);
 	g_return_val_if_fail (req, NM_ACT_STAGE_RETURN_FAILURE);
@@ -2873,9 +3068,6 @@ activation_success_handler (NMDevice *device)
 	/* Clear any critical protocol notification in the wifi stack */
 	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), ifindex, FALSE);
 
-	/* Clear wireless secrets tries on success */
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
-
 	/* There should always be a current AP, either a fake one because we haven't
 	 * seen a scan result for the activated AP yet, or a real one from the
 	 * supplicant's scan list.
@@ -2922,21 +3114,6 @@ activation_success_handler (NMDevice *device)
 }
 
 static void
-activation_failure_handler (NMDevice *device)
-{
-	NMConnection *applied_connection;
-
-	applied_connection = nm_device_get_applied_connection (device);
-	g_assert (applied_connection);
-
-	/* Clear wireless secrets tries on failure */
-	g_object_set_qdata (G_OBJECT (applied_connection), wireless_secrets_tries_quark (), NULL);
-
-	/* Clear any critical protocol notification in the wifi stack */
-	nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
-}
-
-static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
@@ -2990,12 +3167,13 @@ device_state_changed (NMDevice *device,
 		activation_success_handler (device);
 		break;
 	case NM_DEVICE_STATE_FAILED:
-		activation_failure_handler (device);
+		/* Clear any critical protocol notification in the wifi stack */
+		nm_platform_wifi_indicate_addressing_running (nm_device_get_platform (device), nm_device_get_ifindex (device), FALSE);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		/* Kick off a scan to get latest results */
 		priv->scan_interval = SCAN_INTERVAL_MIN;
-		request_wireless_scan (self, FALSE, NULL);
+		request_wireless_scan (self, FALSE, FALSE, NULL);
 		break;
 	default:
 		break;
@@ -3159,7 +3337,7 @@ nm_device_wifi_init (NMDeviceWifi *self)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 
 	priv->mode = NM_802_11_MODE_INFRA;
-	priv->aps = g_hash_table_new (g_str_hash, g_str_equal);
+	priv->aps = g_hash_table_new (nm_str_hash, g_str_equal);
 }
 
 static void
@@ -3261,7 +3439,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 
 	parent_class->state_changed = device_state_changed;
 
-	klass->scanning_allowed = scanning_allowed;
+	klass->scanning_prohibited = scanning_prohibited;
 
 	obj_properties[PROP_MODE] =
 	    g_param_spec_uint (NM_DEVICE_WIFI_MODE, "", "",
@@ -3322,13 +3500,13 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	                  G_TYPE_NONE, 1,
 	                  NM_TYPE_WIFI_AP);
 
-	signals[SCANNING_ALLOWED] =
-	    g_signal_new (NM_DEVICE_WIFI_SCANNING_ALLOWED,
+	signals[SCANNING_PROHIBITED] =
+	    g_signal_new (NM_DEVICE_WIFI_SCANNING_PROHIBITED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
 	                  G_SIGNAL_RUN_LAST,
-	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_allowed),
-	                  scanning_allowed_accumulator, NULL, NULL,
-	                  G_TYPE_BOOLEAN, 0);
+	                  G_STRUCT_OFFSET (NMDeviceWifiClass, scanning_prohibited),
+	                  NULL, NULL, NULL,
+	                  G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN);
 
 	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
 	                                        NMDBUS_TYPE_DEVICE_WIFI_SKELETON,
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 024fe0ef..09707d4f 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -44,7 +44,7 @@
 #define NM_DEVICE_WIFI_ACCESS_POINT_REMOVED "access-point-removed"
 
 /* internal signals */
-#define NM_DEVICE_WIFI_SCANNING_ALLOWED    "scanning-allowed"
+#define NM_DEVICE_WIFI_SCANNING_PROHIBITED    "scanning-prohibited"
 
 typedef struct _NMDeviceWifi NMDeviceWifi;
 typedef struct _NMDeviceWifiClass NMDeviceWifiClass;
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index 7de0838f..bc823af0 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.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) 2004 - 2011 Red Hat, Inc.
+ * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -71,7 +71,7 @@ typedef struct {
 	/* Non-scanned attributes */
 	bool                fake:1;       /* Whether or not the AP is from a scan */
 	bool                hotspot:1;    /* Whether the AP is a local device's hotspot network */
-	gint32              last_seen;  /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
+	gint32              last_seen;    /* Timestamp when the AP was seen lastly (obtained via nm_utils_get_monotonic_timestamp_s()) */
 } NMWifiAPPrivate;
 
 struct _NMWifiAP {
@@ -79,7 +79,7 @@ struct _NMWifiAP {
 	NMWifiAPPrivate _priv;
 };
 
-struct _NMWifiAPClass{
+struct _NMWifiAPClass {
 	NMExportedObjectClass parent;
 };
 
@@ -376,6 +376,14 @@ nm_wifi_ap_set_fake (NMWifiAP *ap, gboolean fake)
 	return FALSE;
 }
 
+NM80211ApFlags
+nm_wifi_ap_get_flags (const NMWifiAP *ap)
+{
+	g_return_val_if_fail (NM_IS_WIFI_AP (ap), NM_802_11_AP_FLAGS_NONE);
+
+	return NM_WIFI_AP_GET_PRIVATE (ap)->flags;
+}
+
 static gboolean
 nm_wifi_ap_set_last_seen (NMWifiAP *ap, gint32 last_seen)
 {
@@ -435,6 +443,326 @@ security_from_vardict (GVariant *security)
 	return flags;
 }
 
+/*****************************************************************************/
+
+static guint32
+get_max_rate_ht_20 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 6500000;
+	case 1:
+	case 8:  return 13000000;
+	case 2:
+	case 16: return 19500000;
+	case 3:
+	case 9:
+	case 24: return 26000000;
+	case 4:
+	case 10:
+	case 17: return 39000000;
+	case 5:
+	case 11:
+	case 25: return 52000000;
+	case 6:
+	case 18: return 58500000;
+	case 7:  return 65000000;
+	case 12:
+	case 19:
+	case 26: return 78000000;
+	case 13:
+	case 27: return 104000000;
+	case 14:
+	case 20: return 117000000;
+	case 15: return 130000000;
+	case 21:
+	case 28: return 156000000;
+	case 22: return 175500000;
+	case 23: return 195000000;
+	case 29: return 208000000;
+	case 30: return 234000000;
+	case 31: return 260000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_ht_40 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 13500000;
+	case 1:
+	case 8:  return 27000000;
+	case 2:  return 40500000;
+	case 3:
+	case 9:
+	case 24: return 54000000;
+	case 4:
+	case 10:
+	case 17: return 81000000;
+	case 5:
+	case 11:
+	case 25: return 108000000;
+	case 6:
+	case 18: return 121500000;
+	case 7:  return 135000000;
+	case 12:
+	case 19:
+	case 26: return 162000000;
+	case 13:
+	case 27: return 216000000;
+	case 14:
+	case 20: return 243000000;
+	case 15: return 270000000;
+	case 16: return 40500000;
+	case 21:
+	case 28: return 324000000;
+	case 22: return 364500000;
+	case 23: return 405000000;
+	case 29: return 432000000;
+	case 30: return 486000000;
+	case 31: return 540000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss1 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 29300000;
+	case 1:  return 58500000;
+	case 2:  return 87800000;
+	case 3:  return 117000000;
+	case 4:  return 175500000;
+	case 5:  return 234000000;
+	case 6:  return 263300000;
+	case 7:  return 292500000;
+	case 8:  return 351000000;
+	case 9:  return 390000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss2 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 58500000;
+	case 1:  return 117000000;
+	case 2:  return 175500000;
+	case 3:  return 234000000;
+	case 4:  return 351000000;
+	case 5:  return 468000000;
+	case 6:  return 526500000;
+	case 7:  return 585000000;
+	case 8:  return 702000000;
+	case 9:  return 780000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_80_ss3 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 87800000;
+	case 1:  return 175500000;
+	case 2:  return 263300000;
+	case 3:  return 351000000;
+	case 4:  return 526500000;
+	case 5:  return 702000000;
+	case 6:  return 0;
+	case 7:  return 877500000;
+	case 8:  return 105300000;
+	case 9:  return 117000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss1 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 58500000;
+	case 1:  return 117000000;
+	case 2:  return 175500000;
+	case 3:  return 234000000;
+	case 4:  return 351000000;
+	case 5:  return 468000000;
+	case 6:  return 526500000;
+	case 7:  return 585000000;
+	case 8:  return 702000000;
+	case 9:  return 780000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss2 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 117000000;
+	case 1:  return 234000000;
+	case 2:  return 351000000;
+	case 3:  return 468000000;
+	case 4:  return 702000000;
+	case 5:  return 936000000;
+	case 6:  return 1053000000;
+	case 7:  return 1170000000;
+	case 8:  return 1404000000;
+	case 9:  return 1560000000;
+	}
+	return 0;
+}
+
+static guint32
+get_max_rate_vht_160_ss3 (int mcs)
+{
+	switch (mcs) {
+	case 0:  return 175500000;
+	case 1:  return 351000000;
+	case 2:  return 526500000;
+	case 3:  return 702000000;
+	case 4:  return 1053000000;
+	case 5:  return 1404000000;
+	case 6:  return 1579500000;
+	case 7:  return 1755000000;
+	case 8:  return 2106000000;
+	case 9:  return 0;
+	}
+	return 0;
+}
+
+static gboolean
+get_max_rate_ht (const guint8 *bytes, guint len, guint32 *out_maxrate)
+{
+	guint32 mcs, i;
+	guint8 ht_cap_info;
+	const guint8 *supported_mcs_set;
+	guint32 rate;
+
+	/* http://standards.ieee.org/getieee802/download/802.11-2012.pdf
+	 * https://mrncciew.com/2014/10/19/cwap-ht-capabilities-ie/
+	 */
+
+	if (len != 26)
+		return FALSE;
+
+	ht_cap_info = bytes[0];
+	supported_mcs_set = &bytes[3];
+	*out_maxrate = 0;
+
+	/* Find the maximum supported mcs rate */
+	mcs = -1;
+	for (i = 0; i <= 76; i++) {
+		unsigned int mcs_octet = i / 8;
+		unsigned int MCS_RATE_BIT = 1 << i % 8;
+
+		if (supported_mcs_set[mcs_octet] & MCS_RATE_BIT) {
+			/* Check for 40Mhz wide channel support */
+			if (ht_cap_info & (1 << 1))
+				rate = get_max_rate_ht_40 (i);
+			else
+				rate = get_max_rate_ht_20 (i);
+
+			if (rate > *out_maxrate)
+				*out_maxrate = rate;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+get_max_rate_vht (const guint8 *bytes, guint len, guint32 *out_maxrate)
+{
+	guint32 mcs, m;
+	guint8 vht_cap, tx_map;
+
+	/* https://tda802dot11.blogspot.it/2014/10/vht-capabilities-element-vht.html
+	 * http://chimera.labs.oreilly.com/books/1234000001739/ch03.html#management_frames */
+
+	if (len != 12)
+		return FALSE;
+
+	vht_cap = bytes[0];
+	tx_map = bytes[8];
+
+	/* Check for mcs rates 8 and 9 support */
+	if (tx_map & 0x2a)
+		mcs = 9;
+	else if (tx_map & 0x15)
+		mcs = 8;
+	else
+		mcs = 7;
+
+	/* Check for 160Mhz wide channel support and
+	 * spatial stream support */
+	if (vht_cap & (1 << 2)) {
+		if (tx_map & 0x30)
+			m = get_max_rate_vht_160_ss3 (mcs);
+		else if (tx_map & 0x0C)
+			m = get_max_rate_vht_160_ss2 (mcs);
+		else
+			m = get_max_rate_vht_160_ss1 (mcs);
+	} else {
+		if (tx_map & 0x30)
+			m = get_max_rate_vht_80_ss3 (mcs);
+		else if (tx_map & 0x0C)
+			m = get_max_rate_vht_80_ss2 (mcs);
+		else
+			m = get_max_rate_vht_80_ss1 (mcs);
+	}
+
+	*out_maxrate = m;
+	return TRUE;
+}
+
+/* Management Frame Information Element IDs, ieee80211_eid */
+#define WLAN_EID_HT_CAPABILITY       45
+#define WLAN_EID_VHT_CAPABILITY     191
+
+static guint32
+get_max_rate (const guint8 *bytes, gsize len)
+{
+	guint8 id, elem_len;
+	guint32 max_rate = 0;
+
+	while (len) {
+		guint32 m;
+
+		if (len < 2)
+			return 0;
+
+		id = *bytes++;
+		elem_len = *bytes++;
+		len -= 2;
+
+		if (elem_len > len)
+			return 0;
+
+		switch (id) {
+		case WLAN_EID_HT_CAPABILITY:
+			if (!get_max_rate_ht (bytes, elem_len, &m))
+				return 0;
+			max_rate = NM_MAX (max_rate, m);
+			break;
+		case WLAN_EID_VHT_CAPABILITY:
+			if (!get_max_rate_vht (bytes, elem_len, &m))
+				return 0;
+			max_rate = NM_MAX (max_rate, m);
+			break;
+		}
+
+		len -= elem_len;
+		bytes += elem_len;
+	}
+
+	return max_rate;
+}
+
+/*****************************************************************************/
+
 gboolean
 nm_wifi_ap_update_from_properties (NMWifiAP *ap,
                                    const char *supplicant_path,
@@ -444,11 +772,13 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	const guint8 *bytes;
 	GVariant *v;
 	gsize len;
+	gsize i;
 	gboolean b = FALSE;
 	const char *s;
 	gint16 i16;
 	guint16 u16;
 	gboolean changed = FALSE;
+	guint32 max_rate;
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
 	g_return_val_if_fail (properties, FALSE);
@@ -460,6 +790,18 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 	if (g_variant_lookup (properties, "Privacy", "b", &b) && b)
 		changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_PRIVACY);
 
+	v = g_variant_lookup_value (properties, "WPS", G_VARIANT_TYPE_VARDICT);
+	if (v) {
+		if (g_variant_lookup (v, "Type", "&s", &s)) {
+			changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS);
+			if (strcmp (s, "pbc") == 0)
+				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PBC);
+			else if (strcmp (s, "pin") == 0)
+				changed |= nm_wifi_ap_set_flags (ap, priv->flags | NM_802_11_AP_FLAGS_WPS_PIN);
+		}
+		g_variant_unref (v);
+	}
+
 	if (g_variant_lookup (properties, "Mode", "&s", &s)) {
 		if (!g_strcmp0 (s, "infrastructure"))
 			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
@@ -497,21 +839,23 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 		g_variant_unref (v);
 	}
 
+	max_rate = 0;
 	v = g_variant_lookup_value (properties, "Rates", G_VARIANT_TYPE ("au"));
 	if (v) {
 		const guint32 *rates = g_variant_get_fixed_array (v, &len, sizeof (guint32));
-		guint32 maxrate = 0;
-		int i;
 
-		/* Find the max AP rate */
-		for (i = 0; i < len; i++) {
-			if (rates[i] > maxrate)
-				maxrate = rates[i];
-		}
-		if (maxrate)
-			changed |= nm_wifi_ap_set_max_bitrate (ap, maxrate / 1000);
+		for (i = 0; i < len; i++)
+			max_rate = NM_MAX (max_rate, rates[i]);
+		g_variant_unref (v);
+	}
+	v = g_variant_lookup_value (properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
+	if (v) {
+		bytes = g_variant_get_fixed_array (v, &len, 1);
+		max_rate = NM_MAX (max_rate, get_max_rate (bytes, len));
 		g_variant_unref (v);
 	}
+	if (max_rate)
+		changed |= nm_wifi_ap_set_max_bitrate (ap, max_rate / 1000);
 
 	v = g_variant_lookup_value (properties, "WPA", G_VARIANT_TYPE_VARDICT);
 	if (v) {
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 5e64087c..dd5a4ad1 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.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) 2004 - 2011 Red Hat, Inc.
+ * Copyright (C) 2004 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -88,6 +88,7 @@ gboolean          nm_wifi_ap_set_max_bitrate          (NMWifiAP *ap,
 gboolean          nm_wifi_ap_get_fake                 (const NMWifiAP *ap);
 gboolean          nm_wifi_ap_set_fake                 (NMWifiAP *ap,
                                                        gboolean fake);
+NM80211ApFlags    nm_wifi_ap_get_flags                (const NMWifiAP *self);
 
 const char       *nm_wifi_ap_to_string                (const NMWifiAP *self,
                                                        char *str_buf,
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index 06da92ce..3ff82004 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -777,10 +777,8 @@ nm_wifi_utils_level_to_quality (gint val)
 		val = 100 - (int) ((100.0 * (double) val) / 60.0);
 	} else {
 		/* Assume signal is a "quality" percentage */
-		val = CLAMP (val, 0, 100);
 	}
-	g_assert (val >= 0);
 
-	return (guint32) val;
+	return CLAMP (val, 0, 100);
 }
 
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-general.c
index 3e61c5f0..89eebb22 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-general.c
@@ -44,16 +44,16 @@
 \
 		success = nm_connection_compare (src, expected, NM_SETTING_COMPARE_FLAG_EXACT); \
 		if (success == FALSE && DEBUG) { \
-			g_message ("\n- COMPLETED ---------------------------------\n"); \
+			g_print ("\n- COMPLETED ---------------------------------\n"); \
 			nm_connection_dump (src); \
-			g_message ("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \
+			g_print ("+ EXPECTED ++++++++++++++++++++++++++++++++++++\n"); \
 			nm_connection_dump (expected); \
-			g_message ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \
+			g_print ("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); \
 		} \
 		g_assert (success == TRUE); \
 	} else { \
 		if (success) { \
-			g_message ("\n- COMPLETED ---------------------------------\n"); \
+			g_print ("\n- COMPLETED ---------------------------------\n"); \
 			nm_connection_dump (src); \
 		} \
 		g_assert (success == FALSE); \
@@ -1334,6 +1334,28 @@ test_strength_wext (void)
 	g_assert_cmpint (nm_wifi_utils_level_to_quality (215), ==, 99);
 }
 
+#define _assert_strength_in_range(x) \
+	({ \
+		guint32 _x = (x); \
+		g_assert_cmpint (_x, >=, 0); \
+		g_assert_cmpint (_x, <=, 100); \
+	})
+
+static void
+test_strength_all (void)
+{
+	int val;
+
+	for (val = -200; val < 300; val++)
+		_assert_strength_in_range (nm_wifi_utils_level_to_quality (val));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT32));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT32));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MININT16));
+	_assert_strength_in_range (nm_wifi_utils_level_to_quality (G_MAXINT16));
+}
+
 /*****************************************************************************/
 
 NMTST_DEFINE ();
@@ -1497,6 +1519,8 @@ main (int argc, char **argv)
 	                 test_strength_percent);
 	g_test_add_func ("/wifi/strength/wext",
 	                 test_strength_wext);
+	g_test_add_func ("/wifi/strength/all",
+	                 test_strength_all);
 
 	return g_test_run ();
 }
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/devices/wwan/libnm-wwan.ver
index eb577aaf..6efcb03f 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/devices/wwan/libnm-wwan.ver
@@ -20,7 +20,11 @@ global:
 	nm_modem_get_type;
 	nm_modem_get_uid;
 	nm_modem_ip4_pre_commit;
+	nm_modem_manager_get;
 	nm_modem_manager_get_type;
+	nm_modem_manager_name_owner_get;
+	nm_modem_manager_name_owner_ref;
+	nm_modem_manager_name_owner_unref;
 	nm_modem_owns_port;
 	nm_modem_set_mm_enabled;
 	nm_modem_stage3_ip4_config_start;
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 4a4d2f2c..22fb8c67 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -364,9 +364,8 @@ device_state_changed (NMDevice *device,
 {
 	NMDeviceModem *self = NM_DEVICE_MODEM (device);
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self);
-	NMSettingsConnection *connection = nm_device_get_settings_connection (device);
 
-	g_assert (priv->modem);
+	g_return_if_fail (priv->modem);
 
 	if (new_state == NM_DEVICE_STATE_UNAVAILABLE &&
 	    old_state < NM_DEVICE_STATE_UNAVAILABLE) {
@@ -374,30 +373,7 @@ device_state_changed (NMDevice *device,
 		_LOGI (LOGD_MB, "modem state '%s'",
 		       nm_modem_state_to_string (nm_modem_get_state (priv->modem)));
 	}
-
 	nm_modem_device_state_changed (priv->modem, new_state, old_state);
-
-	switch (nm_device_state_reason_check (reason)) {
-	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
-	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
-	case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
-	case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
-	case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
-	case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
-		/* Block autoconnect of the just-failed connection for situations
-		 * where a retry attempt would just fail again.
-		 */
-		if (connection) {
-			nm_settings_connection_set_autoconnect_blocked_reason (connection,
-			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_BLOCKED);
-		}
-		break;
-	default:
-		break;
-	}
 }
 
 static NMDeviceCapabilities
@@ -664,6 +640,16 @@ set_modem (NMDeviceModem *self, NMModem *modem)
 	g_signal_connect (modem, "notify::" NM_MODEM_SIM_OPERATOR_ID, G_CALLBACK (ids_changed_cb), self);
 }
 
+static guint32
+get_dhcp_timeout (NMDevice *device, int addr_family)
+{
+	/* DHCP is always done by the modem firmware, not by the network, and
+	 * by the time we get around to DHCP the firmware should already know
+	 * the IP addressing details.  So the DHCP timeout can be much shorter.
+	 */
+	return 15;
+}
+
 /*****************************************************************************/
 
 static void
@@ -718,18 +704,6 @@ nm_device_modem_init (NMDeviceModem *self)
 {
 }
 
-static void
-constructed (GObject *object)
-{
-	G_OBJECT_CLASS (nm_device_modem_parent_class)->constructed (object);
-
-	/* DHCP is always done by the modem firmware, not by the network, and
-	 * by the time we get around to DHCP the firmware should already know
-	 * the IP addressing details.  So the DHCP timeout can be much shorter.
-	 */
-	nm_device_set_dhcp_timeout (NM_DEVICE (object), 15);
-}
-
 NMDevice *
 nm_device_modem_new (NMModem *modem)
 {
@@ -786,7 +760,6 @@ nm_device_modem_class_init (NMDeviceModemClass *mclass)
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
-	object_class->constructed = constructed;
 
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->get_type_description = get_type_description;
@@ -807,6 +780,7 @@ nm_device_modem_class_init (NMDeviceModemClass *mclass)
 	device_class->is_available = is_available;
 	device_class->get_ip_iface_identifier = get_ip_iface_identifier;
 	device_class->get_configured_mtu = nm_modem_get_configured_mtu;
+	device_class->get_dhcp_timeout = get_dhcp_timeout;
 
 	device_class->state_changed = device_state_changed;
 
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 4b16fb14..6e5f10a0 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -90,6 +90,9 @@ typedef struct {
 	MMBearerIpConfig *ipv4_config;
 	MMBearerIpConfig *ipv6_config;
 
+	guint idle_id_ip4;
+	guint idle_id_ip6;
+
 	guint32 pin_tries;
 } NMModemBroadbandPrivate;
 
@@ -860,21 +863,6 @@ set_mm_enabled (NMModem *_self,
 /* IPv4 method static */
 
 static gboolean
-ip4_string_to_num (const gchar *str, guint32 *out)
-{
-	guint32 addr = 0;
-	gboolean success = FALSE;
-
-	if (!str || inet_pton (AF_INET, str, &addr) != 1)
-		addr = 0;
-	else
-		success = TRUE;
-
-	*out = (guint32)addr;
-	return success;
-}
-
-static gboolean
 static_stage3_ip4_done (NMModemBroadband *self)
 {
 	GError *error = NULL;
@@ -883,7 +871,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	const gchar *address_string;
 	const gchar *gw_string;
 	guint32 address_network;
-	guint32 gw;
+	guint32 gw = 0;
 	NMPlatformIP4Address address;
 	const gchar **dns;
 	guint i;
@@ -895,7 +883,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->_priv.ipv4_config);
-	if (!ip4_string_to_num (address_string, &address_network)) {
+	if (!nm_utils_parse_inaddr_bin (AF_INET, address_string, &address_network)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 		                     "(%s) retrieving IP4 configuration failed: invalid address given '%s'",
@@ -906,11 +894,20 @@ static_stage3_ip4_done (NMModemBroadband *self)
 
 	/* Missing gateway not a hard failure */
 	gw_string = mm_bearer_ip_config_get_gateway (self->_priv.ipv4_config);
-	ip4_string_to_num (gw_string, &gw);
+	if (   !gw_string
+	    || !nm_utils_parse_inaddr_bin (AF_INET, gw_string, &gw)) {
+		error = g_error_new (NM_DEVICE_ERROR,
+		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                     "(%s) retrieving IP4 configuration failed: invalid gateway address %s%s%s",
+		                     nm_modem_get_uid (NM_MODEM (self)),
+		                     NM_PRINT_FMT_QUOTE_STRING (gw_string));
+		goto out;
+	}
 
 	data_port = mm_bearer_get_interface (self->_priv.bearer);
 	g_assert (data_port);
-	config = nm_ip4_config_new (nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
+	config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+	                            nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
 
 	memset (&address, 0, sizeof (address));
 	address.address = address_network;
@@ -923,14 +920,30 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	_LOGI ("  address %s/%d", address_string, address.plen);
 
 	if (gw) {
-		nm_ip4_config_set_gateway (config, gw);
-		_LOGI ("  gateway %s", gw_string);
+		guint32 ip4_route_table, ip4_route_metric;
+
+		nm_modem_get_route_parameters (NM_MODEM (self),
+		                               &ip4_route_table,
+		                               &ip4_route_metric,
+		                               NULL,
+		                               NULL);
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
+				.gateway = gw,
+				.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
+				.metric = ip4_route_metric,
+			};
+
+			_LOGI ("  gateway %s", gw_string);
+			nm_ip4_config_add_route (config, &r, NULL);
+		}
 	}
 
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->_priv.ipv4_config);
 	for (i = 0; dns && dns[i]; i++) {
-		if (   ip4_string_to_num (dns[i], &address_network)
+		if (   nm_utils_parse_inaddr_bin (AF_INET, dns[i], &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
 			_LOGI ("  DNS %s", dns[i]);
@@ -944,15 +957,17 @@ out:
 }
 
 static NMActStageReturn
-static_stage3_ip4_config_start (NMModem *_self,
+static_stage3_ip4_config_start (NMModem *modem,
                                 NMActRequest *req,
                                 NMDeviceStateReason *out_failure_reason)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
 
 	/* We schedule it in an idle just to follow the same logic as in the
 	 * generic modem implementation. */
-	g_idle_add ((GSourceFunc) static_stage3_ip4_done, self);
+	nm_clear_g_source (&priv->idle_id_ip4);
+	priv->idle_id_ip4 = g_idle_add ((GSourceFunc) static_stage3_ip4_done, self);
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
@@ -1004,7 +1019,8 @@ stage3_ip6_done (NMModemBroadband *self)
 
 	data_port = mm_bearer_get_interface (self->_priv.bearer);
 	g_assert (data_port);
-	config = nm_ip6_config_new (nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
+	config = nm_ip6_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+	                            nm_platform_link_get_ifindex (NM_PLATFORM_GET, data_port));
 
 	address.plen = mm_bearer_ip_config_get_prefix (self->_priv.ipv6_config);
 	if (address.plen <= 128)
@@ -1014,7 +1030,9 @@ stage3_ip6_done (NMModemBroadband *self)
 
 	address_string = mm_bearer_ip_config_get_gateway (self->_priv.ipv6_config);
 	if (address_string) {
-		if (!inet_pton (AF_INET6, address_string, (void *) &(address.address))) {
+		guint32 ip6_route_table, ip6_route_metric;
+
+		if (inet_pton (AF_INET6, address_string, &address.address) != 1) {
 			error = g_error_new (NM_DEVICE_ERROR,
 			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
 			                     "(%s) retrieving IPv6 configuration failed: invalid gateway given '%s'",
@@ -1022,8 +1040,23 @@ stage3_ip6_done (NMModemBroadband *self)
 			                     address_string);
 			goto out;
 		}
-		_LOGI ("  gateway %s", address_string);
-		nm_ip6_config_set_gateway (config, &address.address);
+
+		nm_modem_get_route_parameters (NM_MODEM (self),
+		                               NULL,
+		                               NULL,
+		                               &ip6_route_table,
+		                               &ip6_route_metric);
+		{
+			const NMPlatformIP6Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
+				.gateway = address.address,
+				.table_coerced = nm_platform_route_table_coerce (ip6_route_table),
+				.metric = ip6_route_metric,
+			};
+
+			_LOGI ("  gateway %s", address_string);
+			nm_ip6_config_add_route (config, &r, NULL);
+		}
 	} else if (ip_method == NM_MODEM_IP_METHOD_STATIC) {
 		/* Gateway required for the 'static' method */
 		error = g_error_new (NM_DEVICE_ERROR,
@@ -1052,13 +1085,15 @@ out:
 }
 
 static NMActStageReturn
-stage3_ip6_config_request (NMModem *_self, NMDeviceStateReason *out_failure_reason)
+stage3_ip6_config_request (NMModem *modem, NMDeviceStateReason *out_failure_reason)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
 
 	/* We schedule it in an idle just to follow the same logic as in the
 	 * generic modem implementation. */
-	g_idle_add ((GSourceFunc) stage3_ip6_done, self);
+	nm_clear_g_source (&priv->idle_id_ip6);
+	priv->idle_id_ip6 = g_idle_add ((GSourceFunc) stage3_ip6_done, self);
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
@@ -1409,6 +1444,10 @@ static void
 dispose (GObject *object)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (object);
+	NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE (self);
+
+	nm_clear_g_source (&priv->idle_id_ip4);
+	nm_clear_g_source (&priv->idle_id_ip6);
 
 	connect_context_clear (self);
 	g_clear_object (&self->_priv.ipv4_config);
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index b1f6d92e..59cd2bca 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -45,6 +45,10 @@
 
 /*****************************************************************************/
 
+NM_GOBJECT_PROPERTIES_DEFINE (NMModemManager,
+	PROP_NAME_OWNER,
+);
+
 enum {
 	MODEM_ADDED,
 	LAST_SIGNAL,
@@ -54,14 +58,38 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	GDBusConnection *dbus_connection;
-	MMManager *modem_manager;
-	guint mm_launch_id;
-	gulong mm_name_owner_changed_id;
-	gulong mm_object_added_id;
-	gulong mm_object_removed_id;
+
+	/* used during g_bus_get() and later during mm_manager_new(). */
+	GCancellable *main_cancellable;
+
+	struct {
+		MMManager *manager;
+		GCancellable *poke_cancellable;
+		gulong handle_name_owner_changed_id;
+		gulong handle_object_added_id;
+		gulong handle_object_removed_id;
+		guint relaunch_id;
+
+		/* this only has one use: that the <info> logging line about
+		 * ModemManager available distinguishes between first-time
+		 * and later name-owner-changed. */
+		enum {
+			LOG_AVAILABLE_NOT_INITIALIZED = 0,
+			LOG_AVAILABLE_YES,
+			LOG_AVAILABLE_NO,
+		} log_available:3;
+
+		GDBusProxy *proxy;
+		GCancellable *proxy_cancellable;
+		guint proxy_ref_count;
+		char *proxy_name_owner;
+	} modm;
 
 #if WITH_OFONO
-	GDBusProxy *ofono_proxy;
+	struct {
+		GDBusProxy *proxy;
+		GCancellable *cancellable;
+	} ofono;
 #endif
 
 	GHashTable *modems;
@@ -82,19 +110,35 @@ G_DEFINE_TYPE (NMModemManager, nm_modem_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_MB
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "modem-manager", __VA_ARGS__)
+
+/*****************************************************************************/
+
+NM_DEFINE_SINGLETON_GETTER (NMModemManager, nm_modem_manager_get, NM_TYPE_MODEM_MANAGER);
+
+/*****************************************************************************/
+
+static void modm_schedule_manager_relaunch (NMModemManager *self,
+                                            guint n_seconds);
+static void modm_ensure_manager (NMModemManager *self);
+
+/*****************************************************************************/
+
 static void
 handle_new_modem (NMModemManager *self, NMModem *modem)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	const char *path;
 
 	path = nm_modem_get_path (modem);
-	if (g_hash_table_lookup (self->_priv.modems, path)) {
+	if (g_hash_table_lookup (priv->modems, path)) {
 		g_warn_if_reached ();
 		return;
 	}
 
 	/* Track the new modem */
-	g_hash_table_insert (self->_priv.modems, g_strdup (path), modem);
+	g_hash_table_insert (priv->modems, g_strdup (path), modem);
 	g_signal_emit (self, signals[MODEM_ADDED], 0, modem);
 }
 
@@ -105,22 +149,27 @@ remove_one_modem (gpointer key, gpointer value, gpointer user_data)
 	return TRUE;
 }
 
+/*****************************************************************************/
+
 static void
-clear_modem_manager (NMModemManager *self)
+modm_clear_manager (NMModemManager *self)
 {
-	if (!self->_priv.modem_manager)
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->modm.manager)
 		return;
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_name_owner_changed_id);
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_object_added_id);
-	nm_clear_g_signal_handler (self->_priv.modem_manager, &self->_priv.mm_object_removed_id);
-	g_clear_object (&self->_priv.modem_manager);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_name_owner_changed_id);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_object_added_id);
+	nm_clear_g_signal_handler (priv->modm.manager, &priv->modm.handle_object_removed_id);
+	g_clear_object (&priv->modm.manager);
 }
 
 static void
-modem_object_added (MMManager *modem_manager,
-                    MMObject  *modem_object,
-                    NMModemManager *self)
+modm_handle_object_added (MMManager *modem_manager,
+                          MMObject  *modem_object,
+                          NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	const gchar *path;
 	MMModem *modem_iface;
 	NMModem *modem;
@@ -128,21 +177,21 @@ modem_object_added (MMManager *modem_manager,
 
 	/* Ensure we don't have the same modem already */
 	path = mm_object_get_path (modem_object);
-	if (g_hash_table_lookup (self->_priv.modems, path)) {
-		nm_log_warn (LOGD_MB, "modem with path %s already exists, ignoring", path);
+	if (g_hash_table_lookup (priv->modems, path)) {
+		_LOGW ("modem with path %s already exists, ignoring", path);
 		return;
 	}
 
 	/* Ensure we have the 'Modem' interface at least */
 	modem_iface = mm_object_peek_modem (modem_object);
 	if (!modem_iface) {
-		nm_log_warn (LOGD_MB, "modem with path %s doesn't have the Modem interface, ignoring", path);
+		_LOGW ("modem with path %s doesn't have the Modem interface, ignoring", path);
 		return;
 	}
 
 	/* Ensure we have a primary port reported */
 	if (!mm_modem_get_primary_port (modem_iface)) {
-		nm_log_warn (LOGD_MB, "modem with path %s has unknown primary port, ignoring", path);
+		_LOGW ("modem with path %s has unknown primary port, ignoring", path);
 		return;
 	}
 
@@ -150,65 +199,68 @@ modem_object_added (MMManager *modem_manager,
 	modem = nm_modem_broadband_new (G_OBJECT (modem_object), &error);
 	if (modem)
 		handle_new_modem (self, modem);
-	else {
-		nm_log_warn (LOGD_MB, "failed to create modem: %s",
-		             error->message);
-	}
+	else
+		_LOGW ("failed to create modem: %s", error->message);
 	g_clear_error (&error);
 }
 
 static void
-modem_object_removed (MMManager *manager,
-                      MMObject  *modem_object,
-                      NMModemManager *self)
+modm_handle_object_removed (MMManager *manager,
+                            MMObject  *modem_object,
+                            NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	NMModem *modem;
 	const gchar *path;
 
 	path = mm_object_get_path (modem_object);
-	modem = (NMModem *) g_hash_table_lookup (self->_priv.modems, path);
+	modem = (NMModem *) g_hash_table_lookup (priv->modems, path);
 	if (!modem)
 		return;
 
 	nm_modem_emit_removed (modem);
-	g_hash_table_remove (self->_priv.modems, path);
+	g_hash_table_remove (priv->modems, path);
 }
 
 static void
-modem_manager_available (NMModemManager *self)
+modm_manager_available (NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	GList *modems, *l;
 
-	nm_log_info (LOGD_MB, "ModemManager available in the bus");
+	if (priv->modm.log_available != LOG_AVAILABLE_YES) {
+		_LOGI ("ModemManager %savailable", priv->modm.log_available ? "now " : "");
+		priv->modm.log_available = LOG_AVAILABLE_YES;
+	}
 
 	/* Update initial modems list */
-	modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->_priv.modem_manager));
+	modems = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (priv->modm.manager));
 	for (l = modems; l; l = g_list_next (l))
-		modem_object_added (self->_priv.modem_manager, MM_OBJECT (l->data), self);
+		modm_handle_object_added (priv->modm.manager, MM_OBJECT (l->data), self);
 	g_list_free_full (modems, (GDestroyNotify) g_object_unref);
 }
 
-static void schedule_modem_manager_relaunch (NMModemManager *self,
-                                             guint n_seconds);
-static void ensure_modem_manager (NMModemManager *self);
-
 static void
-modem_manager_name_owner_changed (MMManager *modem_manager,
-                                  GParamSpec *pspec,
-                                  NMModemManager *self)
+modm_handle_name_owner_changed (MMManager *modem_manager,
+                                GParamSpec *pspec,
+                                NMModemManager *self)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gchar *name_owner;
 
 	/* Quit poking, if any */
-	nm_clear_g_source (&self->_priv.mm_launch_id);
+	nm_clear_g_source (&priv->modm.relaunch_id);
 
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (modem_manager));
 	if (!name_owner) {
-		nm_log_info (LOGD_MB, "ModemManager disappeared from bus");
+		if (priv->modm.log_available != LOG_AVAILABLE_NO) {
+			_LOGI ("ModemManager %savailable", priv->modm.log_available ? "no longer " : "not ");
+			priv->modm.log_available = LOG_AVAILABLE_NO;
+		}
 
 		/* If not managed by systemd, schedule relaunch */
 		if (!sd_booted ())
-			schedule_modem_manager_relaunch (self, 0);
+			modm_schedule_manager_relaunch (self, 0);
 
 		return;
 	}
@@ -220,18 +272,322 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
 	 * nor 'object-removed' if it was created while there was no ModemManager in
 	 * the bus. This hack avoids this issue until we get a GIO with the fix
 	 * included... */
-	clear_modem_manager (self);
-	ensure_modem_manager (self);
+	modm_clear_manager (self);
+	modm_ensure_manager (self);
 
 	/* Whenever GDBusObjectManagerClient is fixed, we can just do the following:
-	 * modem_manager_available (self);
+	 * modm_manager_available (self);
 	 */
 }
 
+static void
+modm_manager_poke_cb (GObject *connection,
+                      GAsyncResult *res,
+                      gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *result = NULL;
+
+	result = g_dbus_connection_call_finish (G_DBUS_CONNECTION (connection), res, &error);
+
+	if (   !result
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modm.poke_cancellable);
+
+	if (error) {
+		_LOGW ("error poking ModemManager: %s", error->message);
+
+		/* Don't reschedule poke is MM service doesn't exist. */
+		if (   !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
+			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
+
+			/* Setup timeout to relaunch */
+			modm_schedule_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		}
+	}
+}
+
+static void
+modm_manager_poke (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_clear_g_cancellable (&priv->modm.poke_cancellable);
+	priv->modm.poke_cancellable = g_cancellable_new ();
+
+	/* If there is no current owner right away, ensure we poke to get one */
+	g_dbus_connection_call (priv->dbus_connection,
+	                        NM_MODEM_MANAGER_MM_DBUS_SERVICE,
+	                        NM_MODEM_MANAGER_MM_DBUS_PATH,
+	                        DBUS_INTERFACE_PEER,
+	                        "Ping",
+	                        NULL,
+	                        NULL,
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1,
+	                        priv->modm.poke_cancellable,
+	                        modm_manager_poke_cb,
+	                        self);
+}
+
+static void
+modm_manager_check_name_owner (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+	gs_free gchar *name_owner = NULL;
+
+	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (priv->modm.manager));
+	if (name_owner) {
+		modm_manager_available (self);
+		return;
+	}
+
+	/* If the lifecycle is not managed by systemd, poke */
+	if (!sd_booted ())
+		modm_manager_poke (self);
+}
+
+static void
+modm_manager_new_cb (GObject *source,
+                     GAsyncResult *res,
+                     gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	MMManager *modem_manager;
+
+	modem_manager = mm_manager_new_finish (res, &error);
+	if (   !modem_manager
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (!priv->modm.manager);
+
+	g_clear_object (&priv->main_cancellable);
+
+	if (!modem_manager) {
+		/* We're not really supposed to get any error here. If we do get one,
+		 * though, just re-schedule the MMManager creation after some time.
+		 * During this period, name-owner changes won't be followed. */
+		_LOGW ("error creating ModemManager client: %s", error->message);
+		/* Setup timeout to relaunch */
+		modm_schedule_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		return;
+	}
+
+	priv->modm.manager = modem_manager;
+
+	/* Setup signals in the GDBusObjectManagerClient */
+	priv->modm.handle_name_owner_changed_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "notify::name-owner",
+	                      G_CALLBACK (modm_handle_name_owner_changed),
+	                      self);
+	priv->modm.handle_object_added_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "object-added",
+	                      G_CALLBACK (modm_handle_object_added),
+	                      self);
+	priv->modm.handle_object_removed_id =
+	    g_signal_connect (priv->modm.manager,
+	                      "object-removed",
+	                      G_CALLBACK (modm_handle_object_removed),
+	                      self);
+
+	modm_manager_check_name_owner (self);
+}
+
+static void
+modm_ensure_manager (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_assert (priv->dbus_connection);
+
+	/* Create the GDBusObjectManagerClient. We do not request to autostart, as
+	 * we don't really want the MMManager creation to fail. We can always poke
+	 * later on if we want to request the autostart */
+	if (!priv->modm.manager) {
+		if (!priv->main_cancellable)
+			priv->main_cancellable = g_cancellable_new ();
+		mm_manager_new (priv->dbus_connection,
+		                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+		                priv->main_cancellable,
+		                modm_manager_new_cb,
+		                self);
+		return;
+	}
+
+	/* If already available, recheck name owner! */
+	modm_manager_check_name_owner (self);
+}
+
+static gboolean
+modm_schedule_manager_relaunch_cb (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	priv->modm.relaunch_id = 0;
+	modm_ensure_manager (self);
+	return G_SOURCE_REMOVE;
+}
+
+static void
+modm_schedule_manager_relaunch (NMModemManager *self,
+                                guint n_seconds)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	/* No need to pass an extra reference to self; timeout/idle will be
+	 * cancelled if the object gets disposed. */
+	if (n_seconds)
+		priv->modm.relaunch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)modm_schedule_manager_relaunch_cb, self);
+	else
+		priv->modm.relaunch_id = g_idle_add ((GSourceFunc)modm_schedule_manager_relaunch_cb, self);
+}
+
+/*****************************************************************************/
+
+static void
+modm_proxy_name_owner_reset (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+	char *name = NULL;
+
+	if (priv->modm.proxy)
+		name = g_dbus_proxy_get_name_owner (priv->modm.proxy);
+
+	if (nm_streq0 (priv->modm.proxy_name_owner, name)) {
+		g_free (name);
+		return;
+	}
+	g_free (priv->modm.proxy_name_owner);
+	priv->modm.proxy_name_owner = name;
+
+	_notify (self, PROP_NAME_OWNER);
+}
+
+static void
+modm_proxy_name_owner_changed_cb (GObject    *object,
+                                  GParamSpec *pspec,
+                                  gpointer    user_data)
+{
+	modm_proxy_name_owner_reset (user_data);
+}
+
+static void
+modm_proxy_new_cb (GObject *source_object,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	GDBusProxy *proxy;
+	gs_free_error GError *error = NULL;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modm.proxy_cancellable);
+
+	if (!proxy) {
+		_LOGW ("could not obtain D-Bus proxy for ModemManager: %s", error->message);
+		return;
+	}
+
+	priv->modm.proxy = proxy;
+	g_signal_connect (priv->modm.proxy, "notify::g-name-owner",
+	                  G_CALLBACK (modm_proxy_name_owner_changed_cb), self);
+
+	modm_proxy_name_owner_reset (self);
+}
+
+void
+nm_modem_manager_name_owner_ref (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM_MANAGER (self));
+
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	if (priv->modm.proxy_ref_count++ > 0) {
+		/* only try once to create the proxy. If proxy creation
+		 * for the first "ref" failed, it's unclear what to do.
+		 * The proxy is hosed. */
+		return;
+	}
+
+	nm_assert (!priv->modm.proxy && !priv->modm.proxy_cancellable);
+
+	priv->modm.proxy_cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+	                          | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
+	                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                          NULL,
+	                          NM_MODEM_MANAGER_MM_DBUS_SERVICE,
+	                          NM_MODEM_MANAGER_MM_DBUS_PATH,
+	                          NM_MODEM_MANAGER_MM_DBUS_INTERFACE,
+	                          priv->modm.proxy_cancellable,
+	                          modm_proxy_new_cb,
+	                          self);
+}
+
+void
+nm_modem_manager_name_owner_unref (NMModemManager *self)
+{
+	NMModemManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM_MANAGER (self));
+
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->modm.proxy_ref_count > 0);
+
+	if (--priv->modm.proxy_ref_count > 0)
+		return;
+
+	nm_clear_g_cancellable (&priv->modm.proxy_cancellable);
+	g_clear_object (&priv->modm.proxy);
+
+	modm_proxy_name_owner_reset (self);
+}
+
+const char *
+nm_modem_manager_name_owner_get (NMModemManager *self)
+{
+	g_return_val_if_fail (NM_IS_MODEM_MANAGER (self), NULL);
+	nm_assert (NM_MODEM_MANAGER_GET_PRIVATE (self)->modm.proxy_ref_count > 0);
+
+	return NM_MODEM_MANAGER_GET_PRIVATE (self)->modm.proxy_name_owner;
+}
+
+/*****************************************************************************/
+
 #if WITH_OFONO
+
 static void
 ofono_create_modem (NMModemManager *self, const char *path)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	NMModem *modem = NULL;
 
 	/* Ensure duplicate modems aren't created.  Because we're not using the
@@ -239,12 +595,12 @@ ofono_create_modem (NMModemManager *self, const char *path)
 	 * receive ModemAdded signals before GetModems() returns, so some of the
 	 * modems returned from GetModems() may already have been created.
 	 */
-	if (!g_hash_table_lookup (self->_priv.modems, path)) {
+	if (!g_hash_table_lookup (priv->modems, path)) {
 		modem = nm_modem_ofono_new (path);
 		if (modem)
 			handle_new_modem (self, modem);
 		else
-			nm_log_warn (LOGD_MB, "Failed to create oFono modem for %s", path);
+			_LOGW ("Failed to create oFono modem for %s", path);
 	}
 }
 
@@ -256,80 +612,96 @@ ofono_signal_cb (GDBusProxy *proxy,
                  gpointer user_data)
 {
 	NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gchar *object_path;
 	NMModem *modem;
 
 	if (g_strcmp0 (signal_name, "ModemAdded") == 0) {
 		g_variant_get (parameters, "(oa{sv})", &object_path, NULL);
-		nm_log_info (LOGD_MB, "oFono modem appeared: %s", object_path);
+		_LOGI ("oFono modem appeared: %s", object_path);
 
 		ofono_create_modem (NM_MODEM_MANAGER (user_data), object_path);
 		g_free (object_path);
 	} else if (g_strcmp0 (signal_name, "ModemRemoved") == 0) {
 		g_variant_get (parameters, "(o)", &object_path);
-		nm_log_info (LOGD_MB, "oFono modem removed: %s", object_path);
+		_LOGI ("oFono modem removed: %s", object_path);
 
-		modem = (NMModem *) g_hash_table_lookup (self->_priv.modems, object_path);
+		modem = (NMModem *) g_hash_table_lookup (priv->modems, object_path);
 		if (modem) {
 			nm_modem_emit_removed (modem);
-			g_hash_table_remove (self->_priv.modems, object_path);
+			g_hash_table_remove (priv->modems, object_path);
 		} else {
-			nm_log_warn (LOGD_MB, "could not remove modem %s, not found in table",
-			             object_path);
+			_LOGW ("could not remove modem %s, not found in table",
+			       object_path);
 		}
 		g_free (object_path);
 	}
 }
 
 static void
-ofono_enumerate_devices_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+ofono_enumerate_devices_done (GObject *proxy,
+                              GAsyncResult *res,
+                              gpointer user_data)
 {
-	NMModemManager *manager = NM_MODEM_MANAGER (user_data);
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
 	gs_free_error GError *error = NULL;
 	GVariant *results;
 	GVariantIter *iter;
 	const char *path;
 
-	results = g_dbus_proxy_call_finish (proxy, res, &error);
-	if (results) {
-		g_variant_get (results, "(a(oa{sv}))", &iter);
-		while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
-			ofono_create_modem (manager, path);
-		g_variant_iter_free (iter);
-		g_variant_unref (results);
-	}
+	results = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
+	if (   !results
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
 
-	if (error) {
-		nm_log_warn (LOGD_MB, "failed to enumerate oFono devices: %s",
-		             error->message);
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->ofono.cancellable);
+
+	if (!results) {
+		_LOGW ("failed to enumerate oFono devices: %s",
+		       error->message);
+		return;
 	}
+
+	g_variant_get (results, "(a(oa{sv}))", &iter);
+	while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
+		ofono_create_modem (self, path);
+	g_variant_iter_free (iter);
+	g_variant_unref (results);
 }
 
 static void
-ofono_check_name_owner (NMModemManager *self)
+ofono_check_name_owner (NMModemManager *self, gboolean first_invocation)
 {
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 	gs_free char *name_owner = NULL;
 
-	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->_priv.ofono_proxy));
+	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (priv->ofono.proxy));
 	if (name_owner) {
-		nm_log_info (LOGD_MB, "oFono is now available");
+		_LOGI ("oFono is %savailable", first_invocation ? "" : "now ");
+
+		nm_clear_g_cancellable (&priv->ofono.cancellable);
+		priv->ofono.cancellable = g_cancellable_new ();
 
-		g_dbus_proxy_call (self->_priv.ofono_proxy,
+		g_dbus_proxy_call (priv->ofono.proxy,
 		                   "GetModems",
 		                   NULL,
 		                   G_DBUS_CALL_FLAGS_NONE,
 		                   -1,
-		                   NULL,
-		                   (GAsyncReadyCallback) ofono_enumerate_devices_done,
-		                   g_object_ref (self));
+		                   priv->ofono.cancellable,
+		                   ofono_enumerate_devices_done,
+		                   self);
 	} else {
 		GHashTableIter iter;
 		NMModem *modem;
 
-		nm_log_info (LOGD_MB, "oFono disappeared from bus");
+		_LOGI ("oFono is %savailable", first_invocation ? "not " : "no longer ");
 
 		/* Remove any oFono modems that might be left around */
-		g_hash_table_iter_init (&iter, self->_priv.modems);
+		g_hash_table_iter_init (&iter, priv->modems);
 		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &modem)) {
 			if (NM_IS_MODEM_OFONO (modem)) {
 				nm_modem_emit_removed (modem);
@@ -344,219 +716,121 @@ ofono_name_owner_changed (GDBusProxy *ofono_proxy,
                           GParamSpec *pspec,
                           NMModemManager *self)
 {
-	ofono_check_name_owner (self);
+	ofono_check_name_owner (self, FALSE);
 }
 
 static void
-ofono_proxy_new_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+ofono_proxy_new_cb (GObject *source_object,
+                    GAsyncResult *res,
+                    gpointer user_data)
 {
-	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
 	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	self->_priv.ofono_proxy = g_dbus_proxy_new_finish (res, &error);
-	if (error) {
-		nm_log_warn (LOGD_MB, "error getting oFono bus proxy: %s", error->message);
+	proxy = g_dbus_proxy_new_finish (res, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	g_clear_object (&priv->ofono.cancellable);
+
+	if (!proxy) {
+		_LOGW ("error getting oFono bus proxy: %s", error->message);
 		return;
 	}
 
-	g_signal_connect (self->_priv.ofono_proxy,
+	priv->ofono.proxy = proxy;
+
+	g_signal_connect (priv->ofono.proxy,
 	                  "notify::g-name-owner",
 	                  G_CALLBACK (ofono_name_owner_changed),
 	                  self);
 
-	g_signal_connect (self->_priv.ofono_proxy,
+	g_signal_connect (priv->ofono.proxy,
 	                  "g-signal",
 	                  G_CALLBACK (ofono_signal_cb),
 	                  self);
 
-	ofono_check_name_owner (self);
+	ofono_check_name_owner (self, TRUE);
 }
 
 static void
-ensure_ofono_client (NMModemManager *self)
+ofono_init_proxy (NMModemManager *self)
 {
-	g_assert (self->_priv.dbus_connection);
-	g_dbus_proxy_new (self->_priv.dbus_connection,
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (priv->dbus_connection);
+	nm_assert (!priv->ofono.cancellable);
+
+	priv->ofono.cancellable = g_cancellable_new ();
+
+	g_dbus_proxy_new (priv->dbus_connection,
 	                  G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 	                  NULL,
 	                  OFONO_DBUS_SERVICE,
 	                  OFONO_DBUS_PATH,
 	                  OFONO_DBUS_INTERFACE,
-	                  NULL,
-	                  (GAsyncReadyCallback) ofono_proxy_new_cb,
-	                  g_object_ref (self));
+	                  priv->ofono.cancellable,
+	                  ofono_proxy_new_cb,
+	                  self);
 }
 #endif
 
-static void
-modem_manager_poke_cb (GDBusConnection *connection,
-                       GAsyncResult *res,
-                       NMModemManager *self)
-{
-	GError *error = NULL;
-	GVariant *result;
-
-	result = g_dbus_connection_call_finish (connection, res, &error);
-	if (error) {
-		nm_log_warn (LOGD_MB, "error poking ModemManager: %s",
-					error ? error->message : "");
-
-		/* Don't reschedule poke is MM service doesn't exist. */
-		if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
-			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
-
-			/* Setup timeout to relaunch */
-			schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-		}
-
-		g_error_free (error);
-	} else
-		g_variant_unref (result);
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
-static void
-modem_manager_poke (NMModemManager *self)
-{
-	/* If there is no current owner right away, ensure we poke to get one */
-	g_dbus_connection_call (self->_priv.dbus_connection,
-	                        "org.freedesktop.ModemManager1",
-	                        "/org/freedesktop/ModemManager1",
-	                        DBUS_INTERFACE_PEER,
-	                        "Ping",
-	                        NULL, /* inputs */
-	                        NULL, /* outputs */
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1,
-	                        NULL, /* cancellable */
-	                        (GAsyncReadyCallback)modem_manager_poke_cb, /* callback */
-	                        g_object_ref (self)); /* user_data */
-}
+/*****************************************************************************/
 
 static void
-modem_manager_check_name_owner (NMModemManager *self)
+bus_get_ready (GObject *source,
+               GAsyncResult *res,
+               gpointer user_data)
 {
-	gs_free gchar *name_owner = NULL;
+	NMModemManager *self;
+	NMModemManagerPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusConnection *connection;
 
-	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (self->_priv.modem_manager));
-	if (name_owner) {
-		/* Available! */
-		modem_manager_available (self);
+	connection = g_bus_get_finish (res, &error);
+	if (   !connection
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
-	}
-
-	/* If the lifecycle is not managed by systemd, poke */
-	if (!sd_booted ())
-		modem_manager_poke (self);
-}
-
-static void
-manager_new_ready (GObject *source,
-                   GAsyncResult *res,
-                   NMModemManager *self)
-{
-	/* Note we always get an extra reference to self here */
 
-	GError *error = NULL;
+	self = NM_MODEM_MANAGER (user_data);
+	priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 
-	g_return_if_fail (!self->_priv.modem_manager);
-
-	self->_priv.modem_manager = mm_manager_new_finish (res, &error);
-	if (!self->_priv.modem_manager) {
-		/* We're not really supposed to get any error here. If we do get one,
-		 * though, just re-schedule the MMManager creation after some time.
-		 * During this period, name-owner changes won't be followed. */
-		nm_log_warn (LOGD_MB, "error creating ModemManager client: %s", error->message);
-		g_error_free (error);
-		/* Setup timeout to relaunch */
-		schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-	} else {
-		/* Setup signals in the GDBusObjectManagerClient */
-		self->_priv.mm_name_owner_changed_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "notify::name-owner",
-		                      G_CALLBACK (modem_manager_name_owner_changed),
-		                      self);
-		self->_priv.mm_object_added_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "object-added",
-		                      G_CALLBACK (modem_object_added),
-		                      self);
-		self->_priv.mm_object_removed_id =
-		    g_signal_connect (self->_priv.modem_manager,
-		                      "object-removed",
-		                      G_CALLBACK (modem_object_removed),
-		                      self);
-
-		modem_manager_check_name_owner (self);
-	}
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
-static void
-ensure_modem_manager (NMModemManager *self)
-{
-	g_assert (self->_priv.dbus_connection);
-
-	/* Create the GDBusObjectManagerClient. We do not request to autostart, as
-	 * we don't really want the MMManager creation to fail. We can always poke
-	 * later on if we want to request the autostart */
-	if (!self->_priv.modem_manager) {
-		mm_manager_new (self->_priv.dbus_connection,
-		                G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
-		                NULL,
-		                (GAsyncReadyCallback)manager_new_ready,
-		                g_object_ref (self));
+	if (!connection) {
+		_LOGW ("error getting bus connection: %s", error->message);
 		return;
 	}
 
-	/* If already available, recheck name owner! */
-	modem_manager_check_name_owner (self);
-}
+	priv->dbus_connection = connection;
 
-static gboolean
-mm_launch_cb (NMModemManager *self)
-{
-	self->_priv.mm_launch_id = 0;
-	ensure_modem_manager (self);
-	return G_SOURCE_REMOVE;
+	modm_ensure_manager (self);
+#if WITH_OFONO
+	ofono_init_proxy (self);
+#endif
 }
 
-static void
-schedule_modem_manager_relaunch (NMModemManager *self,
-                                 guint n_seconds)
-{
-	/* No need to pass an extra reference to self; timeout/idle will be
-	 * cancelled if the object gets disposed. */
-	if (n_seconds)
-		self->_priv.mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)mm_launch_cb, self);
-	else
-		self->_priv.mm_launch_id = g_idle_add ((GSourceFunc)mm_launch_cb, self);
-}
+/*****************************************************************************/
 
 static void
-bus_get_ready (GObject *source,
-               GAsyncResult *res,
-               gpointer user_data)
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
 {
-	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
-	gs_free_error GError *error = NULL;
-
-	self->_priv.dbus_connection = g_bus_get_finish (res, &error);
-	if (!self->_priv.dbus_connection) {
-		nm_log_warn (LOGD_MB, "error getting bus connection: %s", error->message);
-		return;
+	NMModemManager *self = NM_MODEM_MANAGER (object);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	switch (prop_id) {
+	case PROP_NAME_OWNER:
+		g_value_set_string (value, priv->modm.proxy_name_owner);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
 	}
-
-	/* Got the bus, ensure clients */
-	ensure_modem_manager (self);
-#if WITH_OFONO
-	ensure_ofono_client (self);
-#endif
 }
 
 /*****************************************************************************/
@@ -564,36 +838,50 @@ bus_get_ready (GObject *source,
 static void
 nm_modem_manager_init (NMModemManager *self)
 {
-	self->_priv.modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
+
+	priv->modems = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+
+	priv->main_cancellable = g_cancellable_new ();
 
 	g_bus_get (G_BUS_TYPE_SYSTEM,
-	           NULL,
-	           (GAsyncReadyCallback)bus_get_ready,
-	           g_object_ref (self));
+	           priv->main_cancellable,
+	           bus_get_ready,
+	           self);
 }
 
 static void
 dispose (GObject *object)
 {
 	NMModemManager *self = NM_MODEM_MANAGER (object);
+	NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE (self);
 
-	nm_clear_g_source (&self->_priv.mm_launch_id);
+	nm_clear_g_cancellable (&priv->main_cancellable);
+	nm_clear_g_cancellable (&priv->modm.poke_cancellable);
 
-	clear_modem_manager (self);
+	nm_clear_g_source (&priv->modm.relaunch_id);
+
+	nm_clear_g_cancellable (&priv->modm.proxy_cancellable);
+	g_clear_object (&priv->modm.proxy);
+	nm_clear_g_free (&priv->modm.proxy_name_owner);
+
+	modm_clear_manager (self);
 
 #if WITH_OFONO
-	if (self->_priv.ofono_proxy) {
-		g_signal_handlers_disconnect_by_func (self->_priv.ofono_proxy, ofono_name_owner_changed, self);
-		g_signal_handlers_disconnect_by_func (self->_priv.ofono_proxy, ofono_signal_cb, self);
-		g_clear_object (&self->_priv.ofono_proxy);
+	if (priv->ofono.proxy) {
+		g_signal_handlers_disconnect_by_func (priv->ofono.proxy, ofono_name_owner_changed, self);
+		g_signal_handlers_disconnect_by_func (priv->ofono.proxy, ofono_signal_cb, self);
+		g_clear_object (&priv->ofono.proxy);
 	}
+	nm_clear_g_cancellable (&priv->ofono.cancellable);
 #endif
 
-	g_clear_object (&self->_priv.dbus_connection);
+	g_clear_object (&priv->dbus_connection);
 
-	if (self->_priv.modems) {
-		g_hash_table_foreach_remove (self->_priv.modems, remove_one_modem, object);
-		g_hash_table_destroy (self->_priv.modems);
+	if (priv->modems) {
+		g_hash_table_foreach_remove (priv->modems, remove_one_modem, object);
+		g_hash_table_destroy (priv->modems);
+		priv->modems = NULL;
 	}
 
 	G_OBJECT_CLASS (nm_modem_manager_parent_class)->dispose (object);
@@ -605,6 +893,15 @@ nm_modem_manager_class_init (NMModemManagerClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->dispose = dispose;
+	object_class->get_property = get_property;
+
+	obj_properties[PROP_NAME_OWNER] =
+	     g_param_spec_string (NM_MODEM_MANAGER_NAME_OWNER, "", "",
+	                          NULL,
+	                          G_PARAM_READABLE
+	                          | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[MODEM_ADDED] =
 	    g_signal_new (NM_MODEM_MANAGER_MODEM_ADDED,
diff --git a/src/devices/wwan/nm-modem-manager.h b/src/devices/wwan/nm-modem-manager.h
index 65594dfa..5f913083 100644
--- a/src/devices/wwan/nm-modem-manager.h
+++ b/src/devices/wwan/nm-modem-manager.h
@@ -34,9 +34,22 @@
 
 #define NM_MODEM_MANAGER_MODEM_ADDED "modem-added"
 
+#define NM_MODEM_MANAGER_NAME_OWNER "name-owner"
+
+#define NM_MODEM_MANAGER_MM_DBUS_SERVICE   "org.freedesktop.ModemManager1"
+#define NM_MODEM_MANAGER_MM_DBUS_PATH      "/org/freedesktop/ModemManager1"
+#define NM_MODEM_MANAGER_MM_DBUS_INTERFACE "org.freedesktop.ModemManager1"
+
 typedef struct _NMModemManager NMModemManager;
 typedef struct _NMModemManagerClass NMModemManagerClass;
 
 GType nm_modem_manager_get_type (void);
 
+NMModemManager *nm_modem_manager_get (void);
+
+void nm_modem_manager_name_owner_ref (NMModemManager *self);
+void nm_modem_manager_name_owner_unref (NMModemManager *self);
+
+const char *nm_modem_manager_name_owner_get (NMModemManager *self);
+
 #endif /* __NETWORKMANAGER_MODEM_MANAGER_H__ */
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 52b335c7..8b3fc2e8 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -46,6 +46,11 @@ typedef struct {
 	GDBusProxy *context_proxy;
 	GDBusProxy *sim_proxy;
 
+	GCancellable *modem_proxy_cancellable;
+	GCancellable *connman_proxy_cancellable;
+	GCancellable *context_proxy_cancellable;
+	GCancellable *sim_proxy_cancellable;
+
 	GError *property_error;
 
 	char *context_path;
@@ -99,22 +104,6 @@ G_DEFINE_TYPE (NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
 
 /*****************************************************************************/
 
-static gboolean
-ip_string_to_network_address (const gchar *str,
-                              guint32 *out)
-{
-	guint32 addr = 0;
-	gboolean success = FALSE;
-
-	if (!str || inet_pton (AF_INET, str, &addr) != 1)
-		addr = 0;
-	else
-		success = TRUE;
-
-	*out = (guint32)addr;
-	return success;
-}
-
 static void
 get_capabilities (NMModem *_self,
                   NMDeviceModemCapabilities *modem_caps,
@@ -165,29 +154,17 @@ typedef struct {
 static void
 disconnect_context_complete (DisconnectContext *ctx)
 {
-	g_simple_async_result_complete_in_idle (ctx->result);
 	if (ctx->cancellable)
 		g_object_unref (ctx->cancellable);
-	g_object_unref (ctx->result);
+	if (ctx->result) {
+		g_simple_async_result_complete_in_idle (ctx->result);
+		g_object_unref (ctx->result);
+	}
 	g_object_unref (ctx->self);
 	g_slice_free (DisconnectContext, ctx);
 }
 
 static gboolean
-disconnect_context_complete_if_cancelled (DisconnectContext *ctx)
-{
-	GError *error = NULL;
-
-	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
-		g_simple_async_result_take_error (ctx->result, error);
-		disconnect_context_complete (ctx);
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
-static gboolean
 disconnect_finish (NMModem *self,
                    GAsyncResult *result,
                    GError **error)
@@ -196,25 +173,25 @@ disconnect_finish (NMModem *self,
 }
 
 static void
-disconnect_done (GDBusProxy *proxy,
-				 GAsyncResult *result,
-				 gpointer user_data)
+disconnect_done (GObject *source,
+                 GAsyncResult *result,
+                 gpointer user_data)
 {
 	DisconnectContext *ctx = (DisconnectContext*) user_data;
 	NMModemOfono *self = ctx->self;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v = NULL;
 
-	g_dbus_proxy_call_finish (proxy, result, &error);
+	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-		_LOGD ("disconnect cancelled");
+		if (ctx->result)
+			g_simple_async_result_take_error (ctx->result, g_steal_pointer (&error));
+		disconnect_context_complete (ctx);
 		return;
 	}
 
-	if (error) {
-		if (ctx->warn)
-			_LOGW ("failed to disconnect modem: %s", error->message);
-		g_clear_error (&error);
-	}
+	if (error && ctx->warn)
+		_LOGW ("failed to disconnect modem: %s", error->message);
 
 	_LOGD ("modem disconnected");
 
@@ -233,18 +210,15 @@ disconnect (NMModem *modem,
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
 	DisconnectContext *ctx;
 	NMModemState state = nm_modem_get_state (NM_MODEM (self));
+	GError *error = NULL;
 
 	_LOGD ("warn: %s modem_state: %s",
 	       warn ? "TRUE" : "FALSE",
 	       nm_modem_state_to_string (state));
 
-	if (state != NM_MODEM_STATE_CONNECTED)
-		return;
-
-	ctx = g_slice_new (DisconnectContext);
+	ctx = g_slice_new0 (DisconnectContext);
 	ctx->self = g_object_ref (self);
 	ctx->warn = warn;
-
 	if (callback) {
 		ctx->result = g_simple_async_result_new (G_OBJECT (self),
 		                                         callback,
@@ -252,9 +226,28 @@ disconnect (NMModem *modem,
 		                                         disconnect);
 	}
 
-	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-	if (disconnect_context_complete_if_cancelled (ctx))
+	if (state != NM_MODEM_STATE_CONNECTED) {
+		if (ctx->result) {
+			g_set_error_literal (&error,
+			                     NM_UTILS_ERROR,
+			                     NM_UTILS_ERROR_UNKNOWN,
+			                     ("modem is currently not connected"));
+			g_simple_async_result_take_error (ctx->result, error);
+		}
+		disconnect_context_complete (ctx);
 		return;
+	}
+
+	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		if (ctx->result)
+			g_simple_async_result_take_error (ctx->result, error);
+		else
+			g_clear_error (&error);
+		disconnect_context_complete (ctx);
+		return;
+	}
+
+	ctx->cancellable = nm_g_object_ref (cancellable);
 
 	nm_modem_set_state (NM_MODEM (self),
 	                    NM_MODEM_STATE_DISCONNECTING,
@@ -267,8 +260,8 @@ disconnect (NMModem *modem,
 	                                  g_variant_new ("b", warn)),
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   20000,
-	                   NULL,
-	                   (GAsyncReadyCallback) disconnect_done,
+	                   ctx->cancellable,
+	                   disconnect_done,
 	                   ctx);
 }
 
@@ -375,22 +368,35 @@ sim_property_changed (GDBusProxy *proxy,
 }
 
 static void
-sim_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+sim_get_properties_done (GObject *source,
+                         GAsyncResult *result,
+                         gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	GError *error = NULL;
-	GVariant *v_properties, *v_dict, *v;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v_properties = NULL;
+	gs_unref_variant GVariant *v_dict = NULL;
+	GVariant *v;
 	GVariantIter i;
 	const char *property;
 
-	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	v_properties = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source),
 	                                           result,
 	                                           G_VARIANT_TYPE ("(a{sv})"),
 	                                           &error);
+	if (   !v_properties
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->sim_proxy_cancellable);
+
 	if (!v_properties) {
 		g_dbus_error_strip_remote_error (error);
 		_LOGW ("error getting sim properties: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
@@ -418,9 +424,49 @@ sim_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 		handle_sim_property (NULL, property, v, self);
 		g_variant_unref (v);
 	}
+}
 
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
+static void
+_sim_proxy_new_cb (GObject *source,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("failed to create SimManager proxy: %s", error->message);
+		g_clear_object (&priv->sim_proxy_cancellable);
+		return;
+	}
+
+	priv->sim_proxy = proxy;
+
+	/* Watch for custom ofono PropertyChanged signals */
+	_nm_dbus_signal_connect (priv->sim_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (sim_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->sim_proxy,
+	                   "GetProperties",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   priv->sim_proxy_cancellable,
+	                   sim_get_properties_done,
+	                   self);
 }
 
 static void
@@ -430,47 +476,30 @@ handle_sim_iface (NMModemOfono *self, gboolean found)
 
 	_LOGD ("SimManager interface %sfound", found ? "" : "not ");
 
-	if (!found && priv->sim_proxy) {
+	if (!found && (priv->sim_proxy || priv->sim_proxy_cancellable)) {
 		_LOGI ("SimManager interface disappeared");
-		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
-		g_clear_object (&priv->sim_proxy);
+		nm_clear_g_cancellable (&priv->sim_proxy_cancellable);
+		if (priv->sim_proxy) {
+			g_signal_handlers_disconnect_by_data (priv->sim_proxy, self);
+			g_clear_object (&priv->sim_proxy);
+		}
 		g_clear_pointer (&priv->imsi, g_free);
 		update_modem_state (self);
-	} else if (found && !priv->sim_proxy) {
-		GError *error = NULL;
-
+	} else if (found && (!priv->sim_proxy && !priv->sim_proxy_cancellable)) {
 		_LOGI ("found new SimManager interface");
 
-		priv->sim_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-		                                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
-		                                                 | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-		                                                 NULL, /* GDBusInterfaceInfo */
-		                                                 OFONO_DBUS_SERVICE,
-		                                                 nm_modem_get_path (NM_MODEM (self)),
-		                                                 OFONO_DBUS_INTERFACE_SIM_MANAGER,
-		                                                 NULL, /* GCancellable */
-		                                                 &error);
-		if (priv->sim_proxy == NULL) {
-			_LOGW ("failed to create SimManager proxy: %s", error->message);
-			g_error_free (error);
-			return;
-		}
-
-		/* Watch for custom ofono PropertyChanged signals */
-		_nm_dbus_signal_connect (priv->sim_proxy,
-		                         "PropertyChanged",
-		                         G_VARIANT_TYPE ("(sv)"),
-		                         G_CALLBACK (sim_property_changed),
-		                         self);
-
-		g_dbus_proxy_call (priv->sim_proxy,
-		                   "GetProperties",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   20000,
-		                   NULL,
-		                   (GAsyncReadyCallback) sim_get_properties_done,
-		                   g_object_ref (self));
+		priv->sim_proxy_cancellable = g_cancellable_new ();
+
+		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+		                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                          NULL, /* GDBusInterfaceInfo */
+		                          OFONO_DBUS_SERVICE,
+		                          nm_modem_get_path (NM_MODEM (self)),
+		                          OFONO_DBUS_INTERFACE_SIM_MANAGER,
+		                          priv->sim_proxy_cancellable, /* GCancellable */
+		                          _sim_proxy_new_cb,
+		                          self);
 	}
 }
 
@@ -514,22 +543,35 @@ connman_property_changed (GDBusProxy *proxy,
 }
 
 static void
-connman_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+connman_get_properties_done (GObject *source,
+                             GAsyncResult *result,
+                             gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	GError *error = NULL;
-	GVariant *v_properties, *v_dict, *v;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v_properties = NULL;
+	gs_unref_variant GVariant *v_dict = NULL;
+	GVariant *v;
 	GVariantIter i;
 	const char *property;
 
-	v_properties = _nm_dbus_proxy_call_finish (proxy,
-		                                       result,
-		                                       G_VARIANT_TYPE ("(a{sv})"),
-		                                       &error);
+	v_properties = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source),
+	                                           result,
+	                                           G_VARIANT_TYPE ("(a{sv})"),
+	                                           &error);
+	if (   !v_properties
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->connman_proxy_cancellable);
+
 	if (!v_properties) {
 		g_dbus_error_strip_remote_error (error);
 		_LOGW ("error getting connman properties: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
@@ -549,9 +591,48 @@ connman_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer u
 		handle_connman_property (NULL, property, v, self);
 		g_variant_unref (v);
 	}
+}
+
+static void
+_connman_proxy_new_cb (GObject *source,
+                       GAsyncResult *result,
+                       gpointer user_data)
+{
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = user_data;
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("failed to create ConnectionManager proxy: %s", error->message);
+		g_clear_object (&priv->connman_proxy_cancellable);
+		return;
+	}
+
+	priv->connman_proxy = proxy;
+
+	_nm_dbus_signal_connect (priv->connman_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (connman_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->connman_proxy,
+	                   "GetProperties",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   priv->connman_proxy_cancellable,
+	                   connman_get_properties_done,
+	                   self);
 }
 
 static void
@@ -561,11 +642,13 @@ handle_connman_iface (NMModemOfono *self, gboolean found)
 
 	_LOGD ("ConnectionManager interface %sfound", found ? "" : "not ");
 
-	if (!found && priv->connman_proxy) {
+	if (!found && (priv->connman_proxy || priv->connman_proxy_cancellable)) {
 		_LOGI ("ConnectionManager interface disappeared");
-
-		g_signal_handlers_disconnect_by_data (priv->connman_proxy, NM_MODEM_OFONO (self));
-		g_clear_object (&priv->connman_proxy);
+		nm_clear_g_cancellable (&priv->connman_proxy_cancellable);
+		if (priv->connman_proxy) {
+			g_signal_handlers_disconnect_by_data (priv->connman_proxy, self);
+			g_clear_object (&priv->connman_proxy);
+		}
 
 		/* The connection manager proxy disappeared, we should
 		 * consider the modem disabled.
@@ -573,41 +656,21 @@ handle_connman_iface (NMModemOfono *self, gboolean found)
 		priv->gprs_attached = FALSE;
 
 		update_modem_state (self);
-	} else if (found && !priv->connman_proxy) {
-		GError *error = NULL;
-
+	} else if (found && (!priv->connman_proxy && !priv->connman_proxy_cancellable)) {
 		_LOGI ("found new ConnectionManager interface");
 
-		priv->connman_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
-		                                                     G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
-		                                                     | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
-		                                                     NULL, /* GDBusInterfaceInfo */
-		                                                     OFONO_DBUS_SERVICE,
-		                                                     nm_modem_get_path (NM_MODEM (self)),
-		                                                     OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
-		                                                     NULL, /* GCancellable */
-		                                                     &error);
-		if (priv->connman_proxy == NULL) {
-			_LOGW ("failed to create ConnectionManager proxy: %s", error->message);
-			g_error_free (error);
-			return;
-		}
-
-		/* Watch for custom ofono PropertyChanged signals */
-		_nm_dbus_signal_connect (priv->connman_proxy,
-		                         "PropertyChanged",
-		                         G_VARIANT_TYPE ("(sv)"),
-		                         G_CALLBACK (connman_property_changed),
-		                         self);
-
-		g_dbus_proxy_call (priv->connman_proxy,
-		                   "GetProperties",
-		                   NULL,
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   20000,
-		                   NULL,
-		                   (GAsyncReadyCallback) connman_get_properties_done,
-		                   g_object_ref (self));
+		priv->connman_proxy_cancellable = g_cancellable_new ();
+
+		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+		                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                          | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                          NULL, /* GDBusInterfaceInfo */
+		                          OFONO_DBUS_SERVICE,
+		                          nm_modem_get_path (NM_MODEM (self)),
+		                          OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
+		                          priv->connman_proxy_cancellable,
+		                          _connman_proxy_new_cb,
+		                          NULL);
 	}
 }
 
@@ -667,22 +730,35 @@ modem_property_changed (GDBusProxy *proxy,
 }
 
 static void
-modem_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+modem_get_properties_done (GObject *source,
+                           GAsyncResult *result,
+                           gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	GError *error = NULL;
-	GVariant *v_properties, *v_dict, *v;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v_properties = NULL;
+	gs_unref_variant GVariant *v_dict = NULL;
+	GVariant *v;
 	GVariantIter i;
 	const char *property;
 
-	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	v_properties = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (source),
 	                                           result,
 	                                           G_VARIANT_TYPE ("(a{sv})"),
 	                                           &error);
+	if (   !v_properties
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->modem_proxy_cancellable);
+
 	if (!v_properties) {
 		g_dbus_error_strip_remote_error (error);
 		_LOGW ("error getting modem properties: %s", error->message);
-		g_error_free (error);
 		return;
 	}
 
@@ -706,21 +782,29 @@ modem_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer use
 		handle_modem_property (NULL, property, v, self);
 		g_variant_unref (v);
 	}
-
-	g_variant_unref (v_dict);
-	g_variant_unref (v_properties);
 }
 
 static void
-stage1_prepare_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+stage1_prepare_done (GObject *source,
+                     GAsyncResult *result,
+                     gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
-	GError *error = NULL;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *v = NULL;
+
+	v = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_clear_object (&priv->context_proxy_cancellable);
 
 	g_clear_pointer (&priv->connect_properties, g_hash_table_destroy);
 
-	g_dbus_proxy_call_finish (proxy, result, &error);
 	if (error) {
 		_LOGW ("connection failed: %s", error->message);
 
@@ -732,8 +816,6 @@ stage1_prepare_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data
 		 * leading to the connection being disabled, and a 5m
 		 * timeout...
 		 */
-
-		g_clear_error (&error);
 	}
 }
 
@@ -751,6 +833,7 @@ context_property_changed (GDBusProxy *proxy,
 	const gchar *s, *addr_s;
 	const gchar **array, **iter;
 	guint32 address_network, gateway_network;
+	guint32 ip4_route_table, ip4_route_metric;
 	guint prefix = 0;
 
 	_LOGD ("PropertyChanged: %s", property);
@@ -809,15 +892,20 @@ context_property_changed (GDBusProxy *proxy,
 	 * 'Interface'.
 	 *
 	 * This needs discussion with upstream.
+	 *
+	 * FIXME: it is no longer allowed to omit the ifindex for NMIP4Config instances.
+	 * This is broken.
 	 */
-	priv->ip4_config = nm_ip4_config_new (0);
+	priv->ip4_config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+	                                      0);
 
 	/* TODO: simply if/else error logic! */
 
 	if (g_variant_lookup (v_dict, "Address", "&s", &addr_s)) {
 		_LOGD ("Address: %s", addr_s);
 
-		if (ip_string_to_network_address (addr_s, &address_network)) {
+		if (   addr_s
+		    && nm_utils_parse_inaddr_bin (AF_INET, addr_s, &address_network)) {
 			addr.address = address_network;
 			addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
 		} else {
@@ -833,7 +921,8 @@ context_property_changed (GDBusProxy *proxy,
 	if (g_variant_lookup (v_dict, "Netmask", "&s", &s)) {
 		_LOGD ("Netmask: %s", s);
 
-		if (s && ip_string_to_network_address (s, &address_network)) {
+		if (   s
+		    && nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
 			prefix = nm_utils_ip4_netmask_to_prefix (address_network);
 			if (prefix > 0)
 				addr.plen = prefix;
@@ -850,15 +939,30 @@ context_property_changed (GDBusProxy *proxy,
 
 	nm_ip4_config_add_address (priv->ip4_config, &addr);
 
-	if (g_variant_lookup (v_dict, "Gateway", "&s", &s)) {
-		if (s && ip_string_to_network_address (s, &gateway_network)) {
-			_LOGI ("Gateway: %s", s);
-			nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
-		} else {
+	if (   g_variant_lookup (v_dict, "Gateway", "&s", &s)
+	    && s) {
+
+		if (!nm_utils_parse_inaddr_bin (AF_INET, s, &gateway_network)) {
 			_LOGW ("invalid 'Gateway': %s", s);
 			goto out;
 		}
-		nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+
+		nm_modem_get_route_parameters (NM_MODEM (self),
+		                               &ip4_route_table,
+		                               &ip4_route_metric,
+		                               NULL,
+		                               NULL);
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
+				.gateway = gateway_network,
+				.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
+				.metric = ip4_route_metric,
+			};
+
+			_LOGI ("Gateway: %s", s);
+			nm_ip4_config_add_route (priv->ip4_config, &r, NULL);
+		}
 	} else {
 		_LOGW ("Settings 'Gateway' missing");
 		goto out;
@@ -867,7 +971,8 @@ context_property_changed (GDBusProxy *proxy,
 	if (g_variant_lookup (v_dict, "DomainNameServers", "^a&s", &array)) {
 		if (array) {
 			for (iter = array; *iter; iter++) {
-				if (ip_string_to_network_address (*iter, &address_network) && address_network > 0) {
+				if (   nm_utils_parse_inaddr_bin (AF_INET, *iter, &address_network)
+				    && address_network) {
 					_LOGI ("DNS: %s", *iter);
 					nm_ip4_config_add_nameserver (priv->ip4_config, address_network);
 				} else {
@@ -889,16 +994,25 @@ context_property_changed (GDBusProxy *proxy,
 
 	if (g_variant_lookup (v_dict, "MessageProxy", "&s", &s)) {
 		_LOGI ("MessageProxy: %s", s);
-		if (s && ip_string_to_network_address (s, &address_network)) {
-			NMPlatformIP4Route mms_route;
-
-			mms_route.network = address_network;
-			mms_route.plen = 32;
-			mms_route.gateway = gateway_network;
-
-			mms_route.metric = 1;
-
-			nm_ip4_config_add_route (priv->ip4_config, &mms_route);
+		if (   s
+		    && nm_utils_parse_inaddr_bin (AF_INET, s, &address_network)) {
+			nm_modem_get_route_parameters (NM_MODEM (self),
+			                               &ip4_route_table,
+			                               &ip4_route_metric,
+			                               NULL,
+			                               NULL);
+
+			{
+				const NMPlatformIP4Route mms_route = {
+					.network = address_network,
+					.plen = 32,
+					.gateway = gateway_network,
+					.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
+					.metric = ip4_route_metric,
+				};
+
+				nm_ip4_config_add_route (priv->ip4_config, &mms_route, NULL);
+			}
 		} else {
 			_LOGW ("invalid MessageProxy: %s", s);
 		}
@@ -946,21 +1060,33 @@ static_stage3_ip4_config_start (NMModem *modem,
 }
 
 static void
-context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+context_proxy_new_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
-	GError *error = NULL;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	priv->context_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (error) {
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    || g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
 		_LOGE ("failed to create ofono ConnectionContext DBus proxy: %s", error->message);
+		g_clear_object (&priv->context_proxy_cancellable);
 		nm_modem_emit_prepare_result (NM_MODEM (self), FALSE,
 		                              NM_DEVICE_STATE_REASON_MODEM_BUSY);
 		return;
 	}
 
+	priv->context_proxy = proxy;
+
 	if (!priv->gprs_attached) {
+		g_clear_object (&priv->context_proxy_cancellable);
 		nm_modem_emit_prepare_result (NM_MODEM (self), FALSE,
 		                              NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
 		return;
@@ -972,7 +1098,6 @@ context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 	 */
 	g_clear_object (&priv->ip4_config);
 
-	/* Watch for custom ofono PropertyChanged signals */
 	_nm_dbus_signal_connect (priv->context_proxy,
 	                         "PropertyChanged",
 	                         G_VARIANT_TYPE ("(sv)"),
@@ -986,9 +1111,9 @@ context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
 	                                   g_variant_new ("b", TRUE)),
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   20000,
-	                   NULL,
-	                   (GAsyncReadyCallback) stage1_prepare_done,
-	                   g_object_ref (self));
+	                   priv->context_proxy_cancellable,
+	                   stage1_prepare_done,
+	                   self);
 }
 
 static void
@@ -998,16 +1123,20 @@ do_context_activate (NMModemOfono *self)
 
 	g_return_if_fail (NM_IS_MODEM_OFONO (self));
 
+	nm_clear_g_cancellable (&priv->context_proxy_cancellable);
 	g_clear_object (&priv->context_proxy);
+
+	priv->context_proxy_cancellable = g_cancellable_new ();
+
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
 	                          NULL,
 	                          OFONO_DBUS_SERVICE,
 	                          priv->context_path,
 	                          OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT,
-	                          NULL,
-	                          (GAsyncReadyCallback) context_proxy_new_cb,
-	                          g_object_ref (self));
+	                          priv->context_proxy_cancellable,
+	                          context_proxy_new_cb,
+	                          self);
 }
 
 static GHashTable *
@@ -1018,7 +1147,7 @@ create_connect_properties (NMConnection *connection)
 	const char *str;
 
 	setting = nm_connection_get_setting_gsm (connection);
-	properties = g_hash_table_new (g_str_hash, g_str_equal);
+	properties = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	str = nm_setting_gsm_get_apn (setting);
 	if (str)
@@ -1081,19 +1210,29 @@ act_stage1_prepare (NMModem *modem,
 }
 
 static void
-modem_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+modem_proxy_new_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
-	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
-	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
-	GError *error = NULL;
+	NMModemOfono *self;
+	NMModemOfonoPrivate *priv;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
 
-	priv->modem_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
-	if (error) {
+	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	self = NM_MODEM_OFONO (user_data);
+	priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (!proxy) {
 		_LOGE ("failed to create ofono modem DBus proxy: %s", error->message);
+		g_clear_object (&priv->modem_proxy_cancellable);
 		return;
 	}
 
-	/* Watch for custom ofono PropertyChanged signals */
+	priv->modem_proxy = proxy;
+
 	_nm_dbus_signal_connect (priv->modem_proxy,
 	                         "PropertyChanged",
 	                         G_VARIANT_TYPE ("(sv)"),
@@ -1105,9 +1244,9 @@ modem_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 	                   NULL,
 	                   G_DBUS_CALL_FLAGS_NONE,
 	                   20000,
-	                   NULL,
-	                   (GAsyncReadyCallback) modem_get_properties_done,
-	                   g_object_ref (self));
+	                   priv->modem_proxy_cancellable,
+	                   modem_get_properties_done,
+	                   self);
 }
 
 /*****************************************************************************/
@@ -1121,6 +1260,9 @@ static void
 constructed (GObject *object)
 {
 	NMModemOfono *self = NM_MODEM_OFONO (object);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	priv->modem_proxy_cancellable = g_cancellable_new ();
 
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
@@ -1128,9 +1270,11 @@ constructed (GObject *object)
 	                          OFONO_DBUS_SERVICE,
 	                          nm_modem_get_path (NM_MODEM (self)),
 	                          OFONO_DBUS_INTERFACE_MODEM,
-	                          NULL,
-	                          (GAsyncReadyCallback) modem_proxy_new_cb,
-	                          g_object_ref (self));
+	                          priv->modem_proxy_cancellable,
+	                          modem_proxy_new_cb,
+	                          self);
+
+	G_OBJECT_CLASS (nm_modem_ofono_parent_class)->constructed (object);
 }
 
 NMModem *
@@ -1163,6 +1307,11 @@ dispose (GObject *object)
 	NMModemOfono *self = NM_MODEM_OFONO (object);
 	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
 
+	nm_clear_g_cancellable (&priv->modem_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->connman_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->context_proxy_cancellable);
+	nm_clear_g_cancellable (&priv->sim_proxy_cancellable);
+
 	if (priv->connect_properties) {
 		g_hash_table_destroy (priv->connect_properties);
 		priv->connect_properties = NULL;
@@ -1171,15 +1320,22 @@ dispose (GObject *object)
 	g_clear_object (&priv->ip4_config);
 
 	if (priv->modem_proxy) {
-		g_signal_handlers_disconnect_by_data (priv->modem_proxy, NM_MODEM_OFONO (self));
+		g_signal_handlers_disconnect_by_data (priv->modem_proxy, self);
 		g_clear_object (&priv->modem_proxy);
 	}
 
-	g_clear_object (&priv->connman_proxy);
-	g_clear_object (&priv->context_proxy);
+	if (priv->connman_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->connman_proxy, self);
+		g_clear_object (&priv->connman_proxy);
+	}
+
+	if (priv->context_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->context_proxy, self);
+		g_clear_object (&priv->context_proxy);
+	}
 
 	if (priv->sim_proxy) {
-		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+		g_signal_handlers_disconnect_by_data (priv->sim_proxy, self);
 		g_clear_object (&priv->sim_proxy);
 	}
 
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 6494b849..77495b62 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -26,13 +26,13 @@
 #include <fcntl.h>
 #include <string.h>
 #include <termios.h>
+#include <linux/rtnetlink.h>
 
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-setting-connection.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
-#include "nm-route-manager.h"
 #include "nm-netns.h"
 #include "nm-act-request.h"
 #include "nm-ip4-config.h"
@@ -98,6 +98,11 @@ typedef struct _NMModemPrivate {
 
 	guint32 mm_ip_timeout;
 
+	guint32 ip4_route_table;
+	guint32 ip4_route_metric;
+	guint32 ip6_route_table;
+	guint32 ip6_route_metric;
+
 	/* PPP stats */
 	guint32 in_bytes;
 	guint32 out_bytes;
@@ -108,6 +113,46 @@ G_DEFINE_TYPE (NMModem, nm_modem, G_TYPE_OBJECT)
 #define NM_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMModem, NM_IS_MODEM)
 
 /*****************************************************************************/
+
+#define _NMLOG_PREFIX_BUFLEN              64
+#define _NMLOG_PREFIX_NAME                "modem"
+#define _NMLOG_DOMAIN                     LOGD_MB
+
+static const char *
+_nmlog_prefix (char *prefix, NMModem *self)
+{
+	const char *uuid;
+	int c;
+
+	if (!self)
+		return "";
+
+	uuid = nm_modem_get_uid (self);
+
+	if (uuid) {
+		char pp[_NMLOG_PREFIX_BUFLEN - 5];
+
+		c = g_snprintf (prefix, _NMLOG_PREFIX_BUFLEN, "[%s]",
+		                nm_strquote (pp, sizeof (pp), uuid));
+	} else
+		c = g_snprintf (prefix, _NMLOG_PREFIX_BUFLEN, "[%p]", self);
+	nm_assert (c < _NMLOG_PREFIX_BUFLEN);
+
+	return prefix;
+}
+
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        char _prefix[_NMLOG_PREFIX_BUFLEN]; \
+        \
+        nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
+                "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+                _NMLOG_PREFIX_NAME, \
+                _nmlog_prefix (_prefix, (self)) \
+                _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
+    } G_STMT_END
+
+/*****************************************************************************/
 /* State/enabled/connected */
 
 static const char *state_table[] = {
@@ -151,11 +196,10 @@ nm_modem_set_state (NMModem *self,
 	priv->prev_state = NM_MODEM_STATE_UNKNOWN;
 
 	if (new_state != old_state) {
-		nm_log_info (LOGD_MB, "(%s): modem state changed, '%s' --> '%s' (reason: %s)\n",
-		             nm_modem_get_uid (self),
-		             nm_modem_state_to_string (old_state),
-		             nm_modem_state_to_string (new_state),
-		             reason ? reason : "none");
+		_LOGI ("modem state changed, '%s' --> '%s' (reason: %s)",
+		       nm_modem_state_to_string (old_state),
+		       nm_modem_state_to_string (new_state),
+		       reason ? reason : "none");
 
 		priv->state = new_state;
 		_notify (self, PROP_STATE);
@@ -181,24 +225,20 @@ nm_modem_set_mm_enabled (NMModem *self,
 	NMModemState prev_state = priv->state;
 
 	if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) {
-		nm_log_dbg (LOGD_MB, "(%s): cannot enable modem: already enabled",
-		            nm_modem_get_uid (self));
+		_LOGD ("cannot enable modem: already enabled");
 		return;
 	}
 	if (!enabled && priv->state <= NM_MODEM_STATE_DISABLING) {
-		nm_log_dbg (LOGD_MB, "(%s): cannot disable modem: already disabled",
-		            nm_modem_get_uid (self));
+		_LOGD ("cannot disable modem: already disabled");
 		return;
 	}
 
 	if (priv->state <= NM_MODEM_STATE_INITIALIZING) {
-		nm_log_dbg (LOGD_MB, "(%s): cannot enable/disable modem: initializing or failed",
-		            nm_modem_get_uid (self));
+		_LOGD ("cannot enable/disable modem: initializing or failed");
 		return;
 	} else if (priv->state == NM_MODEM_STATE_LOCKED) {
 		/* Don't try to enable if the modem is locked since that will fail */
-		nm_log_warn (LOGD_MB, "(%s): cannot enable/disable modem: locked",
-		             nm_modem_get_uid (self));
+		_LOGW ("cannot enable/disable modem: locked");
 
 		/* Try to unlock the modem if it's being enabled */
 		if (enabled)
@@ -468,7 +508,7 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	}
 
 	if (!num || dns_workaround) {
-		nm_log_warn (LOGD_PPP, "compensating for invalid PPP-provided nameservers");
+		_LOGW ("compensating for invalid PPP-provided nameservers");
 		nm_ip4_config_reset_nameservers (config);
 		nm_ip4_config_add_nameserver (config, good_dns1);
 		nm_ip4_config_add_nameserver (config, good_dns2);
@@ -561,9 +601,8 @@ ppp_stage3_ip_config_start (NMModem *self,
 	/* Check if ModemManager requested a specific IP timeout to be used. If 0 reported,
 	 * use the default one (30s) */
 	if (priv->mm_ip_timeout > 0) {
-		nm_log_info (LOGD_PPP, "(%s): using modem-specified IP timeout: %u seconds",
-		             nm_modem_get_uid (self),
-		             priv->mm_ip_timeout);
+		_LOGI ("using modem-specified IP timeout: %u seconds",
+		       priv->mm_ip_timeout);
 		ip_timeout = priv->mm_ip_timeout;
 	}
 
@@ -577,12 +616,18 @@ ppp_stage3_ip_config_start (NMModem *self,
 
 	priv->ppp_manager = nm_ppp_manager_create (priv->data_port, &error);
 
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     priv->ip4_route_table,
+		                                     priv->ip4_route_metric,
+		                                     priv->ip6_route_table,
+		                                     priv->ip6_route_metric);
+	}
+
 	if (   !priv->ppp_manager
 	    || !nm_ppp_manager_start (priv->ppp_manager, req, ppp_name,
 	                              ip_timeout, baud_override, &error)) {
-		nm_log_err (LOGD_PPP, "(%s): error starting PPP: %s",
-		            nm_modem_get_uid (self),
-		            error->message);
+		_LOGE ("error starting PPP: %s", error->message);
 		g_error_free (error);
 
 		g_clear_object (&priv->ppp_manager);
@@ -621,7 +666,7 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 	const char *method;
 	NMActStageReturn ret;
 
-	nm_log_dbg (LOGD_MB, "ip4_config_start");
+	_LOGD ("ip4_config_start");
 
 	g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE);
 	g_return_val_if_fail (NM_IS_DEVICE (device), NM_ACT_STAGE_RETURN_FAILURE);
@@ -640,9 +685,8 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) != 0) {
-		nm_log_warn (LOGD_MB | LOGD_IP4,
-		             "(%s): unhandled WWAN IPv4 method '%s'; will fail",
-		             nm_modem_get_uid (self), method);
+		_LOGW ("unhandled WWAN IPv4 method '%s'; will fail",
+		       method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
@@ -653,15 +697,15 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		ret = ppp_stage3_ip_config_start (self, req, out_failure_reason);
 		break;
 	case NM_MODEM_IP_METHOD_STATIC:
-		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_STATIC");
+		_LOGD ("MODEM_IP_METHOD_STATIC");
 		ret = NM_MODEM_GET_CLASS (self)->static_stage3_ip4_config_start (self, req, out_failure_reason);
 		break;
 	case NM_MODEM_IP_METHOD_AUTO:
-		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_AUTO");
+		_LOGD ("MODEM_IP_METHOD_AUTO");
 		ret = device_class->act_stage3_ip4_config_start (device, NULL, out_failure_reason);
 		break;
 	default:
-		nm_log_info (LOGD_MB, "(%s): IPv4 configuration disabled", nm_modem_get_uid (self));
+		_LOGI ("IPv4 configuration disabled");
 		ret = NM_ACT_STAGE_RETURN_IP_FAIL;
 		break;
 	}
@@ -676,13 +720,15 @@ nm_modem_ip4_pre_commit (NMModem *modem,
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (modem);
 
+	nm_modem_set_route_parameters_from_device (modem, device);
+
 	/* If the modem has an ethernet-type data interface (ie, not PPP and thus
 	 * not point-to-point) and IP config has a /32 prefix, then we assume that
 	 * ARP will be pointless and we turn it off.
 	 */
 	if (   priv->ip4_method == NM_MODEM_IP_METHOD_STATIC
 	    || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO) {
-		const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, 0);
+		const NMPlatformIP4Address *address = nm_ip4_config_get_first_address (config);
 
 		g_assert (address);
 		if (address->plen == 32)
@@ -698,7 +744,8 @@ nm_modem_emit_ip6_config_result (NMModem *self,
                                  GError *error)
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
-	guint i, num;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *addr;
 	gboolean do_slaac = TRUE;
 
 	if (error) {
@@ -710,11 +757,7 @@ nm_modem_emit_ip6_config_result (NMModem *self,
 		/* If the IPv6 configuration only included a Link-Local address, then
 		 * we have to run SLAAC to get the full IPv6 configuration.
 		 */
-		num = nm_ip6_config_get_num_addresses (config);
-		g_assert (num > 0);
-		for (i = 0; i < num; i++) {
-			const NMPlatformIP6Address * addr = nm_ip6_config_get_address (config, i);
-
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, config, &addr) {
 			if (IN6_IS_ADDR_LINKLOCAL (&addr->address)) {
 				if (!priv->iid.id)
 					priv->iid.id = ((guint64 *)(&addr->address.s6_addr))[1];
@@ -757,9 +800,8 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 		return NM_ACT_STAGE_RETURN_IP_DONE;
 
 	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) != 0) {
-		nm_log_warn (LOGD_MB | LOGD_IP6,
-		             "(%s): unhandled WWAN IPv6 method '%s'; will fail",
-		             nm_modem_get_uid (self), method);
+		_LOGW ("unhandled WWAN IPv6 method '%s'; will fail",
+		       method);
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
@@ -778,7 +820,7 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 		ret = NM_MODEM_GET_CLASS (self)->stage3_ip6_config_request (self, out_failure_reason);
 		break;
 	default:
-		nm_log_info (LOGD_MB, "(%s): IPv6 configuration disabled", nm_modem_get_uid (self));
+		_LOGI ("IPv6 configuration disabled");
 		ret = NM_ACT_STAGE_RETURN_IP_FAIL;
 		break;
 	}
@@ -854,7 +896,7 @@ modem_secrets_cb (NMActRequest *req,
 		return;
 
 	if (error)
-		nm_log_warn (LOGD_MB, "(%s): %s", nm_modem_get_uid (self), error->message);
+		_LOGW ("modem-secrets: %s", error->message);
 
 	g_signal_emit (self, signals[AUTH_RESULT], 0, error);
 }
@@ -975,17 +1017,15 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 		str = nm_setting_gsm_get_device_id (s_gsm);
 		if (str) {
 			if (!priv->device_id) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s has device-id, device does not",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s has device-id, device does not",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 			if (strcmp (str, priv->device_id)) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s device-id mismatch",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s device-id mismatch",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 		}
@@ -998,10 +1038,9 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 		str = nm_setting_gsm_get_sim_id (s_gsm);
 		if (str && priv->sim_id) {
 			if (strcmp (str, priv->sim_id)) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s sim-id mismatch",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s sim-id mismatch",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 		}
@@ -1009,10 +1048,9 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection)
 		str = nm_setting_gsm_get_sim_operator_id (s_gsm);
 		if (str && priv->sim_operator_id) {
 			if (strcmp (str, priv->sim_operator_id)) {
-				nm_log_dbg (LOGD_MB, "(%s): %s/%s sim-operator-id mismatch",
-				            priv->uid,
-				            nm_connection_get_uuid (connection),
-				            nm_connection_get_id (connection));
+				_LOGD ("%s/%s sim-operator-id mismatch",
+				       nm_connection_get_uuid (connection),
+				       nm_connection_get_id (connection));
 				return FALSE;
 			}
 		}
@@ -1069,10 +1107,11 @@ deactivate_cleanup (NMModem *self, NMDevice *device)
 		    priv->ip6_method == NM_MODEM_IP_METHOD_AUTO) {
 			ifindex = nm_device_get_ip_ifindex (device);
 			if (ifindex > 0) {
-				nm_route_manager_route_flush (nm_netns_get_route_manager (nm_device_get_netns (device)),
-				                              ifindex);
-				nm_platform_address_flush (nm_device_get_platform (device), ifindex);
-				nm_platform_link_set_down (nm_device_get_platform (device), ifindex);
+				NMPlatform *platform = nm_device_get_platform (device);
+
+				nm_platform_ip_route_flush (platform, AF_UNSPEC, ifindex);
+				nm_platform_ip_address_flush (platform, AF_UNSPEC, ifindex);
+				nm_platform_link_set_down (platform, ifindex);
 			}
 		}
 	}
@@ -1149,12 +1188,12 @@ ppp_manager_stop_ready (NMPPPManager *ppp_manager,
                         GAsyncResult *res,
                         DeactivateContext *ctx)
 {
+	NMModem *self = ctx->self;
 	GError *error = NULL;
 
 	if (!nm_ppp_manager_stop_finish (ppp_manager, res, &error)) {
-		nm_log_warn (LOGD_MB, "(%s): cannot stop PPP manager: %s",
-		             nm_modem_get_uid (ctx->self),
-		             error->message);
+		_LOGW ("cannot stop PPP manager: %s",
+		       error->message);
 		g_simple_async_result_take_error (ctx->result, error);
 		deactivate_context_complete (ctx);
 		return;
@@ -1168,7 +1207,8 @@ ppp_manager_stop_ready (NMPPPManager *ppp_manager,
 static void
 deactivate_step (DeactivateContext *ctx)
 {
-	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (ctx->self);
+	NMModem *self = ctx->self;
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
 	GError *error = NULL;
 
 	/* Check cancellable in each step */
@@ -1187,7 +1227,7 @@ deactivate_step (DeactivateContext *ctx)
 		if (priv->ppp_manager)
 			ctx->ppp_manager = g_object_ref (priv->ppp_manager);
 		/* Run cleanup */
-		NM_MODEM_GET_CLASS (ctx->self)->deactivate_cleanup (ctx->self, ctx->device);
+		NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, ctx->device);
 		ctx->step++;
 		/* fall through */
 	case DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP:
@@ -1203,16 +1243,15 @@ deactivate_step (DeactivateContext *ctx)
 		/* fall through */
 	case DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT:
 		/* Disconnect asynchronously */
-		NM_MODEM_GET_CLASS (ctx->self)->disconnect (ctx->self,
-		                                            FALSE,
-		                                            ctx->cancellable,
-		                                            (GAsyncReadyCallback) disconnect_ready,
-		                                            ctx);
+		NM_MODEM_GET_CLASS (self)->disconnect (self,
+		                                       FALSE,
+		                                       ctx->cancellable,
+		                                       (GAsyncReadyCallback) disconnect_ready,
+		                                       ctx);
 		return;
 
 	case DEACTIVATE_CONTEXT_STEP_LAST:
-		nm_log_dbg (LOGD_MB, "(%s): modem deactivation finished",
-		            nm_modem_get_uid (ctx->self));
+		_LOGD ("modem deactivation finished");
 		deactivate_context_complete (ctx);
 		return;
 	}
@@ -1377,6 +1416,76 @@ nm_modem_get_iid (NMModem *self, NMUtilsIPv6IfaceId *out_iid)
 /*****************************************************************************/
 
 void
+nm_modem_get_route_parameters (NMModem *self,
+                               guint32 *out_ip4_route_table,
+                               guint32 *out_ip4_route_metric,
+                               guint32 *out_ip6_route_table,
+                               guint32 *out_ip6_route_metric)
+{
+	NMModemPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM (self));
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+	NM_SET_OUT (out_ip4_route_table, priv->ip4_route_table);
+	NM_SET_OUT (out_ip4_route_metric, priv->ip4_route_metric);
+	NM_SET_OUT (out_ip6_route_table, priv->ip6_route_table);
+	NM_SET_OUT (out_ip6_route_metric, priv->ip6_route_metric);
+}
+
+void
+nm_modem_set_route_parameters (NMModem *self,
+                               guint32 ip4_route_table,
+                               guint32 ip4_route_metric,
+                               guint32 ip6_route_table,
+                               guint32 ip6_route_metric)
+{
+	NMModemPrivate *priv;
+
+	g_return_if_fail (NM_IS_MODEM (self));
+
+	priv = NM_MODEM_GET_PRIVATE (self);
+	if (   priv->ip4_route_table  != ip4_route_table
+	    || priv->ip4_route_metric != ip4_route_metric
+	    || priv->ip6_route_table  != ip6_route_table
+	    || priv->ip6_route_metric != ip6_route_metric) {
+		priv->ip4_route_table = ip4_route_table;
+		priv->ip4_route_metric = ip4_route_metric;
+		priv->ip6_route_table = ip6_route_table;
+		priv->ip6_route_metric = ip6_route_metric;
+
+		_LOGT ("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u",
+		       priv->ip4_route_table,
+		       priv->ip4_route_metric,
+		       priv->ip6_route_table,
+		       priv->ip6_route_metric);
+	}
+
+	if (priv->ppp_manager) {
+		nm_ppp_manager_set_route_parameters (priv->ppp_manager,
+		                                     priv->ip4_route_table,
+		                                     priv->ip4_route_metric,
+		                                     priv->ip6_route_table,
+		                                     priv->ip6_route_metric);
+	}
+}
+
+void
+nm_modem_set_route_parameters_from_device (NMModem *self,
+                                           NMDevice *device)
+{
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	nm_modem_set_route_parameters (self,
+	                               nm_device_get_route_table (device, AF_INET, TRUE),
+	                               nm_device_get_route_metric (device, AF_INET),
+	                               nm_device_get_route_table (device, AF_INET6, TRUE),
+	                               nm_device_get_route_metric (device, AF_INET6));
+}
+
+/*****************************************************************************/
+
+void
 nm_modem_get_capabilities (NMModem *self,
                            NMDeviceModemCapabilities *modem_caps,
                            NMDeviceModemCapabilities *current_caps)
@@ -1451,6 +1560,7 @@ set_property (GObject *object, guint prop_id,
 	case PROP_PATH:
 		/* construct-only */
 		priv->path = g_value_dup_string (value);
+		g_return_if_fail (priv->path);
 		break;
 	case PROP_DRIVER:
 		/* construct-only */
@@ -1509,40 +1619,27 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_modem_init (NMModem *self)
 {
+	NMModemPrivate *priv;
+
 	self->_priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_MODEM, NMModemPrivate);
+	priv = self->_priv;
+
+	priv->ip4_route_table = RT_TABLE_MAIN;
+	priv->ip4_route_metric = 700;
+	priv->ip6_route_table = RT_TABLE_MAIN;
+	priv->ip6_route_metric = 700;
 }
 
-static GObject*
-constructor (GType type,
-             guint n_construct_params,
-             GObjectConstructParam *construct_params)
+static void
+constructed (GObject *object)
 {
-	GObject *object;
 	NMModemPrivate *priv;
 
-	object = G_OBJECT_CLASS (nm_modem_parent_class)->constructor (type,
-	                                                              n_construct_params,
-	                                                              construct_params);
-	if (!object)
-		return NULL;
-
-	priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
+	G_OBJECT_CLASS (nm_modem_parent_class)->constructed (object);
 
-	if (!priv->data_port && !priv->control_port) {
-		nm_log_err (LOGD_PLATFORM, "neither modem command nor data interface provided");
-		goto err;
-	}
+	priv = NM_MODEM_GET_PRIVATE (NM_MODEM (object));
 
-	if (!priv->path) {
-		nm_log_err (LOGD_PLATFORM, "D-Bus path not provided");
-		goto err;
-	}
-
-	return object;
-
-err:
-	g_object_unref (object);
-	return NULL;
+	g_return_if_fail (priv->data_port || priv->control_port);
 }
 
 /*****************************************************************************/
@@ -1552,10 +1649,7 @@ dispose (GObject *object)
 {
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE ((NMModem *) object);
 
-	if (priv->act_request) {
-		g_object_unref (priv->act_request);
-		priv->act_request = NULL;
-	}
+	g_clear_object (&priv->act_request);
 
 	G_OBJECT_CLASS (nm_modem_parent_class)->dispose (object);
 }
@@ -1584,7 +1678,7 @@ nm_modem_class_init (NMModemClass *klass)
 
 	g_type_class_add_private (object_class, sizeof (NMModemPrivate));
 
-	object_class->constructor = constructor;
+	object_class->constructed = constructed;
 	object_class->set_property = set_property;
 	object_class->get_property = get_property;
 	object_class->dispose = dispose;
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index a50727a9..9546e4a1 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -105,10 +105,12 @@ typedef enum {  /*< underscore_name=nm_modem_state >*/
 
 struct _NMModemPrivate;
 
-typedef struct {
+struct _NMModem {
 	GObject parent;
 	struct _NMModemPrivate *_priv;
-} NMModem;
+};
+
+typedef struct _NMModem NMModem;
 
 typedef struct {
 	GObjectClass parent;
@@ -185,6 +187,21 @@ gboolean nm_modem_complete_connection (NMModem *self,
                                        const GSList *existing_connections,
                                        GError **error);
 
+void nm_modem_get_route_parameters (NMModem *self,
+                                    guint32 *out_ip4_route_table,
+                                    guint32 *out_ip4_route_metric,
+                                    guint32 *out_ip6_route_table,
+                                    guint32 *out_ip6_route_metric);
+
+void nm_modem_set_route_parameters (NMModem *self,
+                                    guint32 ip4_route_table,
+                                    guint32 ip4_route_metric,
+                                    guint32 ip6_route_table,
+                                    guint32 ip6_route_metric);
+
+void nm_modem_set_route_parameters_from_device (NMModem *modem,
+                                                NMDevice *device);
+
 NMActStageReturn nm_modem_act_stage1_prepare (NMModem *modem,
                                               NMActRequest *req,
                                               NMDeviceStateReason *out_failure_reason);
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index fa4c8dbb..663102de 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -127,8 +127,8 @@ start (NMDeviceFactory *factory)
 	NMWwanFactory *self = NM_WWAN_FACTORY (factory);
 	NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE (self);
 
-	priv->mm = g_object_new (NM_TYPE_MODEM_MANAGER, NULL);
-	g_assert (priv->mm);
+	priv->mm = g_object_ref (nm_modem_manager_get ());
+
 	g_signal_connect (priv->mm,
 	                  NM_MODEM_MANAGER_MODEM_ADDED,
 	                  G_CALLBACK (modem_added_cb),
diff --git a/src/dhcp/nm-dhcp-client-logging.h b/src/dhcp/nm-dhcp-client-logging.h
index 1047a7d7..1ed47170 100644
--- a/src/dhcp/nm-dhcp-client-logging.h
+++ b/src/dhcp/nm-dhcp-client-logging.h
@@ -23,6 +23,23 @@
 
 #include "nm-dhcp-client.h"
 
+static inline NMLogDomain
+_nm_dhcp_client_get_domain (NMDhcpClient *self)
+{
+	if (self) {
+		switch (nm_dhcp_client_get_addr_family (self)) {
+		case AF_INET:
+			return LOGD_DHCP4;
+		case AF_INET6:
+			return LOGD_DHCP6;
+		default:
+			nm_assert_not_reached ();
+			break;
+		}
+	}
+	return LOGD_DHCP;
+}
+
 #define _NMLOG_PREFIX_NAME    "dhcp"
 #define _NMLOG_DOMAIN         LOGD_DHCP
 #define _NMLOG(level, ...) \
@@ -38,9 +55,7 @@
         if (nm_logging_enabled (_level, _NMLOG_DOMAIN)) { \
             NMDhcpClient *_self = (NMDhcpClient *) (self); \
             const char *__ifname = _self ? nm_dhcp_client_get_iface (_self) : NULL; \
-            const NMLogDomain _domain = !_self \
-                                            ? LOGD_DHCP \
-                                            : (nm_dhcp_client_get_ipv6 (_self) ? LOGD_DHCP6 : LOGD_DHCP4); \
+            const NMLogDomain _domain = _nm_dhcp_client_get_domain (_self); \
             \
             nm_log (_level, _domain, __ifname, NULL, \
                     "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 0906f5be..20ea092f 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -29,6 +29,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <uuid/uuid.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
+#include "nm-utils/nm-random-utils.h"
 
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
@@ -48,33 +52,36 @@ enum {
 static guint signals[LAST_SIGNAL] = { 0 };
 
 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_MULTI_IDX,
+	PROP_ADDR_FAMILY,
 	PROP_IFACE,
 	PROP_IFINDEX,
 	PROP_HWADDR,
-	PROP_IPV6,
 	PROP_UUID,
-	PROP_PRIORITY,
+	PROP_ROUTE_TABLE,
+	PROP_ROUTE_METRIC,
 	PROP_TIMEOUT,
 );
 
 typedef struct _NMDhcpClientPrivate {
+	NMDedupMultiIndex *multi_idx;
 	char *       iface;
-	int          ifindex;
 	GByteArray * hwaddr;
-	gboolean     ipv6;
 	char *       uuid;
-	guint32      priority;
-	guint32      timeout;
 	GByteArray * duid;
 	GBytes *     client_id;
 	char *       hostname;
-	gboolean     use_fqdn;
-
-	NMDhcpState  state;
 	pid_t        pid;
 	guint        timeout_id;
 	guint        watch_id;
-	gboolean     info_only;
+	int          addr_family;
+	int          ifindex;
+	guint32      route_table;
+	guint32      route_metric;
+	guint32      timeout;
+	NMDhcpState  state;
+	bool         info_only:1;
+	bool         use_fqdn:1;
 } NMDhcpClientPrivate;
 
 G_DEFINE_TYPE_EXTENDED (NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {})
@@ -91,6 +98,14 @@ nm_dhcp_client_get_pid (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->pid;
 }
 
+NMDedupMultiIndex *
+nm_dhcp_client_get_multi_idx (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->multi_idx;
+}
+
 const char *
 nm_dhcp_client_get_iface (NMDhcpClient *self)
 {
@@ -107,12 +122,12 @@ nm_dhcp_client_get_ifindex (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->ifindex;
 }
 
-gboolean
-nm_dhcp_client_get_ipv6 (NMDhcpClient *self)
+int
+nm_dhcp_client_get_addr_family (NMDhcpClient *self)
 {
-	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), AF_UNSPEC);
 
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->ipv6;
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->addr_family;
 }
 
 const char *
@@ -140,11 +155,19 @@ nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 }
 
 guint32
-nm_dhcp_client_get_priority (NMDhcpClient *self)
+nm_dhcp_client_get_route_table (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), RT_TABLE_MAIN);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_table;
+}
+
+guint32
+nm_dhcp_client_get_route_metric (NMDhcpClient *self)
 {
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), G_MAXUINT32);
 
-	return NM_DHCP_CLIENT_GET_PRIVATE (self)->priority;
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->route_metric;
 }
 
 guint32
@@ -303,8 +326,8 @@ nm_dhcp_client_set_state (NMDhcpClient *self,
 		watch_cleanup (self);
 
 	if (new_state == NM_DHCP_STATE_BOUND) {
-		g_assert (   (priv->ipv6 && NM_IS_IP6_CONFIG (ip_config))
-		          || (!priv->ipv6 && NM_IS_IP4_CONFIG (ip_config)));
+		g_assert (   (priv->addr_family == AF_INET  && NM_IS_IP4_CONFIG (ip_config))
+		          || (priv->addr_family == AF_INET6 && NM_IS_IP6_CONFIG (ip_config)));
 		g_assert (options);
 	} else {
 		g_assert (ip_config == NULL);
@@ -319,7 +342,8 @@ nm_dhcp_client_set_state (NMDhcpClient *self,
 	if ((priv->state == new_state) && (new_state != NM_DHCP_STATE_BOUND))
 		return;
 
-	if (priv->ipv6 && new_state == NM_DHCP_STATE_BOUND) {
+	if (   priv->addr_family == AF_INET6
+	    && new_state == NM_DHCP_STATE_BOUND) {
 		char *start, *iaid;
 
 		iaid = g_hash_table_lookup (options, "iaid");
@@ -392,6 +416,10 @@ nm_dhcp_client_start_timeout (NMDhcpClient *self)
 
 	/* Set up a timeout on the transaction to kill it after the timeout */
 	g_assert (priv->timeout_id == 0);
+
+	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
+		return;
+
 	priv->timeout_id = g_timeout_add_seconds (priv->timeout,
 	                                          transaction_timeout,
 	                                          self);
@@ -426,10 +454,13 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 
 	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
 	g_return_val_if_fail (priv->pid == -1, FALSE);
-	g_return_val_if_fail (priv->ipv6 == FALSE, FALSE);
+	g_return_val_if_fail (priv->addr_family == AF_INET, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
 
-	_LOGI ("activation: beginning transaction (timeout in %d seconds)", priv->timeout);
+	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
+		_LOGI ("activation: beginning transaction (no timeout)");
+	else
+		_LOGI ("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
 
 	if (dhcp_client_id)
 		tmp = nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id);
@@ -451,8 +482,6 @@ generate_duid_from_machine_id (void)
 	gsize sumlen = sizeof (buffer);
 	const guint16 duid_type = g_htons (4);
 	uuid_t uuid;
-	GRand *generator;
-	guint i;
 	gs_free char *machine_id_s = NULL;
 	gs_free char *str = NULL;
 
@@ -468,10 +497,7 @@ generate_duid_from_machine_id (void)
 		             "or " LOCALSTATEDIR "/lib/dbus/machine-id to generate "
 		             "DHCPv6 DUID; creating non-persistent random DUID.");
 
-		generator = g_rand_new ();
-		for (i = 0; i < sizeof (buffer) / sizeof (guint32); i++)
-			((guint32 *) buffer)[i] = g_rand_int (generator);
-		g_rand_free (generator);
+		nm_utils_random_bytes (buffer, sizeof (buffer));
 	}
 
 	/* Generate a DHCP Unique Identifier for DHCPv6 using the
@@ -528,7 +554,7 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 
 	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
 	g_return_val_if_fail (priv->pid == -1, FALSE);
-	g_return_val_if_fail (priv->ipv6 == TRUE, FALSE);
+	g_return_val_if_fail (priv->addr_family == AF_INET6, FALSE);
 	g_return_val_if_fail (priv->uuid != NULL, FALSE);
 
 	/* If we don't have one yet, read the default DUID for this DHCPv6 client
@@ -544,8 +570,10 @@ nm_dhcp_client_start_ip6 (NMDhcpClient *self,
 
 	priv->info_only = info_only;
 
-	_LOGI ("activation: beginning transaction (timeout in %d seconds)",
-	       priv->timeout);
+	if (priv->timeout == NM_DHCP_TIMEOUT_INFINITY)
+		_LOGI ("activation: beginning transaction (no timeout)");
+	else
+		_LOGI ("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
 
 	return NM_DHCP_CLIENT_GET_CLASS (self)->ip6_start (self,
 	                                                   dhcp_anycast_addr,
@@ -744,7 +772,7 @@ nm_dhcp_client_handle_event (gpointer unused,
 		GVariant *value;
 
 		/* Copy options */
-		str_options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		str_options = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 		g_variant_iter_init (&iter, options);
 		while (g_variant_iter_next (&iter, "{&sv}", &name, &value)) {
 			maybe_add_option (self, str_options, name, value);
@@ -763,18 +791,20 @@ nm_dhcp_client_handle_event (gpointer unused,
 		/* Create the IP config */
 		g_warn_if_fail (g_hash_table_size (str_options));
 		if (g_hash_table_size (str_options)) {
-			if (priv->ipv6) {
-				prefix = nm_dhcp_utils_ip6_prefix_from_options (str_options);
-				ip_config = (GObject *) nm_dhcp_utils_ip6_config_from_options (priv->ifindex,
+			if (priv->addr_family == AF_INET) {
+				ip_config = (GObject *) nm_dhcp_utils_ip4_config_from_options (nm_dhcp_client_get_multi_idx (self),
+				                                                               priv->ifindex,
 				                                                               priv->iface,
 				                                                               str_options,
-				                                                               priv->priority,
-				                                                               priv->info_only);
+				                                                               priv->route_table,
+				                                                               priv->route_metric);
 			} else {
-				ip_config = (GObject *) nm_dhcp_utils_ip4_config_from_options (priv->ifindex,
+				prefix = nm_dhcp_utils_ip6_prefix_from_options (str_options);
+				ip_config = (GObject *) nm_dhcp_utils_ip6_config_from_options (nm_dhcp_client_get_multi_idx (self),
+				                                                               priv->ifindex,
 				                                                               priv->iface,
 				                                                               str_options,
-				                                                               priv->priority);
+				                                                               priv->info_only);
 			}
 		}
 	}
@@ -822,14 +852,14 @@ get_property (GObject *object, guint prop_id,
 	case PROP_HWADDR:
 		g_value_set_boxed (value, priv->hwaddr);
 		break;
-	case PROP_IPV6:
-		g_value_set_boolean (value, priv->ipv6);
+	case PROP_ADDR_FAMILY:
+		g_value_set_int (value, priv->addr_family);
 		break;
 	case PROP_UUID:
 		g_value_set_string (value, priv->uuid);
 		break;
-	case PROP_PRIORITY:
-		g_value_set_uint (value, priv->priority);
+	case PROP_ROUTE_METRIC:
+		g_value_set_uint (value, priv->route_metric);
 		break;
 	case PROP_TIMEOUT:
 		g_value_set_uint (value, priv->timeout);
@@ -847,6 +877,13 @@ set_property (GObject *object, guint prop_id,
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE ((NMDhcpClient *) object);
 
 	switch (prop_id) {
+	case PROP_MULTI_IDX:
+		/* construct-only */
+		priv->multi_idx = g_value_get_pointer (value);
+		if (!priv->multi_idx)
+			g_return_if_reached ();
+		nm_dedup_multi_index_ref (priv->multi_idx);
+		break;
 	case PROP_IFACE:
 		/* construct-only */
 		priv->iface = g_value_dup_string (value);
@@ -860,19 +897,26 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->hwaddr = g_value_dup_boxed (value);
 		break;
-	case PROP_IPV6:
+	case PROP_ADDR_FAMILY:
 		/* construct-only */
-		priv->ipv6 = g_value_get_boolean (value);
+		priv->addr_family = g_value_get_int (value);
+		if (!NM_IN_SET (priv->addr_family, AF_INET, AF_INET6))
+			g_return_if_reached ();
 		break;
 	case PROP_UUID:
 		/* construct-only */
 		priv->uuid = g_value_dup_string (value);
 		break;
-	case PROP_PRIORITY:
+	case PROP_ROUTE_TABLE:
+		/* construct-only */
+		priv->route_table = g_value_get_uint (value);
+		break;
+	case PROP_ROUTE_METRIC:
 		/* construct-only */
-		priv->priority = g_value_get_uint (value);
+		priv->route_metric = g_value_get_uint (value);
 		break;
 	case PROP_TIMEOUT:
+		/* construct-only */
 		priv->timeout = g_value_get_uint (value);
 		break;
 	default:
@@ -924,6 +968,8 @@ dispose (GObject *object)
 	}
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
+
+	priv->multi_idx = nm_dedup_multi_index_unref (priv->multi_idx);
 }
 
 static void
@@ -940,6 +986,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	client_class->stop = stop;
 	client_class->get_duid = get_duid;
 
+	obj_properties[PROP_MULTI_IDX] =
+	    g_param_spec_pointer (NM_DHCP_CLIENT_MULTI_IDX, "", "",
+	                            G_PARAM_WRITABLE
+	                          | G_PARAM_CONSTRUCT_ONLY
+	                          | G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_IFACE] =
 	    g_param_spec_string (NM_DHCP_CLIENT_INTERFACE, "", "",
 	                         NULL,
@@ -958,11 +1010,11 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_IPV6] =
-	    g_param_spec_boolean (NM_DHCP_CLIENT_IPV6, "", "",
-	                          FALSE,
-	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_ADDR_FAMILY] =
+	    g_param_spec_int (NM_DHCP_CLIENT_ADDR_FAMILY, "", "",
+	                      0, G_MAXINT, AF_UNSPEC,
+	                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_UUID] =
 	    g_param_spec_string (NM_DHCP_CLIENT_UUID, "", "",
@@ -970,15 +1022,21 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_PRIORITY] =
-	    g_param_spec_uint (NM_DHCP_CLIENT_PRIORITY, "", "",
+	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_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_STATIC_STRINGS);
 
 	obj_properties[PROP_TIMEOUT] =
 	    g_param_spec_uint (NM_DHCP_CLIENT_TIMEOUT, "", "",
-	                       0, G_MAXUINT, 45,
+	                       1, G_MAXINT32, NM_DHCP_TIMEOUT_DEFAULT,
 	                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                       G_PARAM_STATIC_STRINGS);
 
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index e41a59a2..02804002 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -24,6 +24,9 @@
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
 
+#define NM_DHCP_TIMEOUT_DEFAULT ((guint32) 45) /* default DHCP timeout, in seconds */
+#define NM_DHCP_TIMEOUT_INFINITY G_MAXINT32
+
 #define NM_TYPE_DHCP_CLIENT            (nm_dhcp_client_get_type ())
 #define NM_DHCP_CLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClient))
 #define NM_DHCP_CLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
@@ -32,16 +35,19 @@
 #define NM_DHCP_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
 
 #define NM_DHCP_CLIENT_INTERFACE "iface"
+#define NM_DHCP_CLIENT_ADDR_FAMILY "addr-family"
 #define NM_DHCP_CLIENT_IFINDEX   "ifindex"
 #define NM_DHCP_CLIENT_HWADDR    "hwaddr"
-#define NM_DHCP_CLIENT_IPV6      "ipv6"
 #define NM_DHCP_CLIENT_UUID      "uuid"
-#define NM_DHCP_CLIENT_PRIORITY  "priority"
+#define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
+#define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
 #define NM_DHCP_CLIENT_TIMEOUT   "timeout"
+#define NM_DHCP_CLIENT_MULTI_IDX "multi-idx"
 
 #define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed"
 #define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
 
+
 typedef enum {
 	NM_DHCP_STATE_UNKNOWN = 0,
 	NM_DHCP_STATE_BOUND,        /* new lease or lease changed */
@@ -101,21 +107,25 @@ typedef struct {
 
 GType nm_dhcp_client_get_type (void);
 
+struct _NMDedupMultiIndex *nm_dhcp_client_get_multi_idx (NMDhcpClient *self);
+
 pid_t nm_dhcp_client_get_pid (NMDhcpClient *self);
 
+int nm_dhcp_client_get_addr_family (NMDhcpClient *self);
+
 const char *nm_dhcp_client_get_iface (NMDhcpClient *self);
 
 int         nm_dhcp_client_get_ifindex (NMDhcpClient *self);
 
-gboolean nm_dhcp_client_get_ipv6 (NMDhcpClient *self);
-
 const char *nm_dhcp_client_get_uuid (NMDhcpClient *self);
 
 const GByteArray *nm_dhcp_client_get_duid (NMDhcpClient *self);
 
 const GByteArray *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
-guint32 nm_dhcp_client_get_priority (NMDhcpClient *self);
+guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
+
+guint32 nm_dhcp_client_get_route_metric (NMDhcpClient *self);
 
 guint32 nm_dhcp_client_get_timeout (NMDhcpClient *self);
 
@@ -173,13 +183,16 @@ typedef struct {
 	GType (*get_type)(void);
 	const char *name;
 	const char *(*get_path) (void);
-	GSList *(*get_lease_ip_configs) (const char *iface,
+	GSList *(*get_lease_ip_configs) (struct _NMDedupMultiIndex *multi_idx,
+	                                 int addr_family,
+	                                 const char *iface,
 	                                 int ifindex,
 	                                 const char *uuid,
-	                                 gboolean ipv6,
-	                                 guint32 default_route_metric);
+	                                 guint32 route_table,
+	                                 guint32 route_metric);
 } NMDhcpClientFactory;
 
+extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd;
 extern const NMDhcpClientFactory _nm_dhcp_client_factory_internal;
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 6a1b6865..e63e6a86 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -25,6 +25,8 @@
 #include <ctype.h>
 #include <arpa/inet.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-dhcp-utils.h"
 #include "nm-ip4-config.h"
 #include "nm-utils.h"
@@ -162,13 +164,9 @@ add_ip4_config (GString *str, GBytes *client_id, const char *hostname, gboolean
 static void
 add_hostname6 (GString *str, const char *hostname)
 {
-	/* dhclient only supports the fqdn.fqdn for DHCPv6 and requires a fully-
-	 * qualified name for this option, so we must require one here too.
-	 */
-	if (hostname && strchr (hostname, '.')) {
+	if (hostname) {
 		g_string_append_printf (str, FQDN_FORMAT "\n", hostname);
 		g_string_append (str,
-		                 "send fqdn.encoded on;\n"
 		                 "send fqdn.server-update on;\n");
 		g_string_append_c (str, '\n');
 	}
@@ -261,7 +259,7 @@ read_interface (const char *line, char *interface, guint size)
 
 char *
 nm_dhcp_dhclient_create_config (const char *interface,
-                                gboolean is_ip6,
+                                int addr_family,
                                 GBytes *client_id,
                                 const char *anycast_addr,
                                 const char *hostname,
@@ -277,6 +275,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 	int i;
 
 	g_return_val_if_fail (!anycast_addr || nm_utils_hwaddr_valid (anycast_addr, ETH_ALEN), NULL);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
 
 	new_contents = g_string_new (_("# Created by NetworkManager\n"));
 	fqdn_opts = g_ptr_array_sized_new (5);
@@ -397,18 +396,18 @@ nm_dhcp_dhclient_create_config (const char *interface,
 		g_string_append_printf (new_contents, "timeout %u;\n", timeout);
 	}
 
-	if (is_ip6) {
-		add_hostname6 (new_contents, hostname);
-		add_request (reqs, "dhcp6.name-servers");
-		add_request (reqs, "dhcp6.domain-search");
-		add_request (reqs, "dhcp6.client-id");
-	} else {
+	if (addr_family == AF_INET) {
 		add_ip4_config (new_contents, client_id, hostname, use_fqdn);
 		add_request (reqs, "rfc3442-classless-static-routes");
 		add_request (reqs, "ms-classless-static-routes");
 		add_request (reqs, "static-routes");
 		add_request (reqs, "wpad");
 		add_request (reqs, "ntp-servers");
+	} else {
+		add_hostname6 (new_contents, hostname);
+		add_request (reqs, "dhcp6.name-servers");
+		add_request (reqs, "dhcp6.domain-search");
+		add_request (reqs, "dhcp6.client-id");
 	}
 
 	if (reset_reqlist)
@@ -686,24 +685,30 @@ lease_validity_span (const char *str_expire, GDateTime *now)
 
 /**
  * nm_dhcp_dhclient_read_lease_ip_configs:
+ * @multi_idx: the multi index instance for the ip config object
+ * @addr_family: whether to read IPv4 or IPv6 leases
  * @iface: the interface name to match leases with
  * @ifindex: interface index of @iface
+ * @route_table: the route table for the default route.
+ * @route_metric: the route metric for the default route.
  * @contents: the contents of a dhclient leasefile
- * @ipv6: whether to read IPv4 or IPv6 leases
  * @now: the current UTC date/time; pass %NULL to automatically use current
  *  UTC time.  Testcases may need a different value for 'now'
  *
  * Reads dhclient leases from @contents and parses them into either
- * #NMIP4Config or #NMIP6Config objects depending on the value of @ipv6.
+ * #NMIP4Config or #NMIP6Config objects depending on the value of @addr_family.
  *
- * Returns: a #GSList of #NMIP4Config objects (if @ipv6 is %FALSE) or a list of
- * #NMIP6Config objects (if @ipv6 is %TRUE) containing the lease data.
+ * Returns: a #GSList of #NMIP4Config objects (if @addr_family is %AF_INET) or a list of
+ * #NMIP6Config objects (if @addr_family is %AF_INET6) containing the lease data.
  */
 GSList *
-nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
+nm_dhcp_dhclient_read_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                        int addr_family,
+                                        const char *iface,
                                         int ifindex,
+                                        guint32 route_table,
+                                        guint32 route_metric,
                                         const char *contents,
-                                        gboolean ipv6,
                                         GDateTime *now)
 {
 	GSList *parsed = NULL, *iter, *leases = NULL;
@@ -712,6 +717,7 @@ nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
 	gint32 now_monotonic_ts;
 
 	g_return_val_if_fail (contents != NULL, NULL);
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
 	split = g_strsplit_set (contents, "\n\r", -1);
 	if (!split)
@@ -733,7 +739,7 @@ nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
 				g_hash_table_destroy (hash);
 			}
 
-			hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+			hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 		} else if (hash && strlen (*line))
 			add_lease_option (hash, *line);
 	}
@@ -804,15 +810,25 @@ nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
 
 		/* Get default netmask for the IP according to appropriate class. */
 		if (!address.plen)
-			address.plen = nm_utils_ip4_get_default_prefix (address.address);
+			address.plen = _nm_utils_ip4_get_default_prefix (address.address);
 
 		address.timestamp = now_monotonic_ts;
 		address.lifetime = address.preferred = expiry;
 		address.addr_source = NM_IP_CONFIG_SOURCE_DHCP;
 
-		ip4 = nm_ip4_config_new (ifindex);
+		ip4 = nm_ip4_config_new (multi_idx, ifindex);
 		nm_ip4_config_add_address (ip4, &address);
-		nm_ip4_config_set_gateway (ip4, gw);
+
+		{
+			const NMPlatformIP4Route r = {
+				.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+				.gateway = gw,
+				.table_coerced = nm_platform_route_table_coerce (route_table),
+				.metric = route_metric,
+			};
+
+			nm_ip4_config_add_route (ip4, &r, NULL);
+		}
 
 		value = g_hash_table_lookup (hash, "option domain-name-servers");
 		if (value) {
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index 2268890b..94de1963 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -23,7 +23,7 @@
 #include "nm-setting-ip6-config.h"
 
 char *nm_dhcp_dhclient_create_config (const char *interface,
-                                      gboolean is_ip6,
+                                      int addr_family,
                                       GBytes *client_id,
                                       const char *anycast_addr,
                                       const char *hostname,
@@ -43,10 +43,13 @@ gboolean nm_dhcp_dhclient_save_duid (const char *leasefile,
                                      const char *escaped_duid,
                                      GError **error);
 
-GSList *nm_dhcp_dhclient_read_lease_ip_configs (const char *iface,
+GSList *nm_dhcp_dhclient_read_lease_ip_configs (struct _NMDedupMultiIndex *multi_idx,
+                                                int addr_family,
+                                                const char *iface,
                                                 int ifindex,
+                                                guint32 route_table,
+                                                guint32 route_metric,
                                                 const char *contents,
-                                                gboolean ipv6,
                                                 GDateTime *now);
 
 GBytes *nm_dhcp_dhclient_get_client_id_from_config_file (const char *path);
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index f20158c6..74d920a8 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -38,6 +38,8 @@
 #include <arpa/inet.h>
 #include <ctype.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-utils.h"
 #include "nm-dhcp-dhclient-utils.h"
 #include "nm-dhcp-manager.h"
@@ -47,6 +49,15 @@
 
 /*****************************************************************************/
 
+static const char *
+_addr_family_to_path_part (int addr_family)
+{
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	return (addr_family == AF_INET6) ? "6" : "";
+}
+
+/*****************************************************************************/
+
 #define NM_TYPE_DHCP_DHCLIENT            (nm_dhcp_dhclient_get_type ())
 #define NM_DHCP_DHCLIENT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient))
 #define NM_DHCP_DHCLIENT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
@@ -92,9 +103,9 @@ nm_dhcp_dhclient_get_path (void)
 
 /**
  * get_dhclient_leasefile():
+ * @addr_family: AF_INET or AF_INET6
  * @iface: the interface name of the device on which DHCP will be done
  * @uuid: the connection UUID to which the returned lease should belong
- * @ipv6: %TRUE for IPv6, %FALSE for IPv4
  * @out_preferred_path: on return, the "most preferred" leasefile path
  *
  * Returns the path of an existing leasefile (if any) for this interface and
@@ -104,16 +115,16 @@ nm_dhcp_dhclient_get_path (void)
  * Returns: an existing leasefile, or %NULL if no matching leasefile could be found
  */
 static char *
-get_dhclient_leasefile (const char *iface,
+get_dhclient_leasefile (int addr_family,
+                        const char *iface,
                         const char *uuid,
-                        gboolean ipv6,
                         char **out_preferred_path)
 {
 	char *path;
 
 	/* /var/lib/NetworkManager is the preferred leasefile path */
 	path = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s-%s.lease",
-	                        ipv6 ? "6" : "",
+	                        _addr_family_to_path_part (addr_family),
 	                        uuid,
 	                        iface);
 	if (out_preferred_path)
@@ -131,14 +142,14 @@ get_dhclient_leasefile (const char *iface,
 	/* Old Debian, SUSE, and Mandriva location */
 	g_free (path);
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhcp/dhclient%s-%s-%s.lease",
-	                        ipv6 ? "6" : "", uuid, iface);
+	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 
 	/* Old Red Hat and Fedora location */
 	g_free (path);
 	path = g_strdup_printf (LOCALSTATEDIR "/lib/dhclient/dhclient%s-%s-%s.lease",
-	                        ipv6 ? "6" : "", uuid, iface);
+	                        _addr_family_to_path_part (addr_family), uuid, iface);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 
@@ -148,37 +159,36 @@ get_dhclient_leasefile (const char *iface,
 }
 
 static GSList *
-nm_dhcp_dhclient_get_lease_ip_configs (const char *iface,
+nm_dhcp_dhclient_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                       int addr_family,
+                                       const char *iface,
                                        int ifindex,
                                        const char *uuid,
-                                       gboolean ipv6,
-                                       guint32 default_route_metric)
+                                       guint32 route_table,
+                                       guint32 route_metric)
 {
-	char *contents = NULL;
-	char *leasefile;
-	GSList *leases = NULL;
+	gs_free char *contents = NULL;
+	gs_free char *leasefile = NULL;
 
-	leasefile = get_dhclient_leasefile (iface, uuid, FALSE, NULL);
+	leasefile = get_dhclient_leasefile (addr_family, iface, uuid, NULL);
 	if (!leasefile)
 		return NULL;
 
 	if (   g_file_test (leasefile, G_FILE_TEST_EXISTS)
 	    && g_file_get_contents (leasefile, &contents, NULL, NULL)
 	    && contents
-	    && contents[0])
-		leases = nm_dhcp_dhclient_read_lease_ip_configs (iface, ifindex, contents, ipv6, NULL);
-
-	g_free (leasefile);
-	g_free (contents);
-
-	return leases;
+	    && contents[0]) {
+		return nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, addr_family, iface, ifindex,
+		                                               route_table, route_metric, contents, NULL);
+	}
+	return NULL;
 }
 
 static gboolean
 merge_dhclient_config (NMDhcpDhclient *self,
+                       int addr_family,
                        const char *iface,
                        const char *conf_file,
-                       gboolean is_ip6,
                        GBytes *client_id,
                        const char *anycast_addr,
                        const char *hostname,
@@ -204,10 +214,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
 		}
 	}
 
-	if (is_ip6 && hostname && !strchr (hostname, '.'))
-		_LOGW ("hostname is not a FQDN, it will be ignored");
-
-	new = nm_dhcp_dhclient_create_config (iface, is_ip6, client_id, anycast_addr, hostname, timeout,
+	new = nm_dhcp_dhclient_create_config (iface, addr_family, client_id, anycast_addr, hostname, timeout,
 	                                      use_fqdn, orig_path, orig, out_new_client_id);
 	g_assert (new);
 	success = g_file_set_contents (conf_file, new, -1, error);
@@ -218,7 +225,7 @@ merge_dhclient_config (NMDhcpDhclient *self,
 }
 
 static char *
-find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid, gboolean ipv6)
+find_existing_config (NMDhcpDhclient *self, int addr_family, const char *iface, const char *uuid)
 {
 	char *path;
 
@@ -227,20 +234,20 @@ find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid,
 	 * or generic.
 	 */
 	if (uuid) {
-		path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", uuid);
+		path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), uuid);
 		_LOGD ("looking for existing config %s", path);
 		if (g_file_test (path, G_FILE_TEST_EXISTS))
 			return path;
 		g_free (path);
 	}
 
-	path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	path = g_strdup_printf (NMCONFDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (NMCONFDIR "/dhclient%s.conf", ipv6 ? "6" : "");
+	path = g_strdup_printf (NMCONFDIR "/dhclient%s.conf", _addr_family_to_path_part (addr_family));
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
@@ -254,25 +261,25 @@ find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid,
 	 * which is then used by many other distributions. Some distributions
 	 * (including Fedora) don't even provide a default configuration file.
 	 */
-	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (SYSCONFDIR "/dhclient%s-%s.conf", ipv6 ? "6" : "", iface);
+	path = g_strdup_printf (SYSCONFDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s.conf", ipv6 ? "6" : "");
+	path = g_strdup_printf (SYSCONFDIR "/dhcp/dhclient%s.conf", _addr_family_to_path_part (addr_family));
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
 	g_free (path);
 
-	path = g_strdup_printf (SYSCONFDIR "/dhclient%s.conf", ipv6 ? "6" : "");
+	path = g_strdup_printf (SYSCONFDIR "/dhclient%s.conf", _addr_family_to_path_part (addr_family));
 	_LOGD ("looking for existing config %s", path);
 	if (g_file_test (path, G_FILE_TEST_EXISTS))
 		return path;
@@ -290,8 +297,8 @@ find_existing_config (NMDhcpDhclient *self, const char *iface, const char *uuid,
  */
 static char *
 create_dhclient_config (NMDhcpDhclient *self,
+                        int addr_family,
                         const char *iface,
-                        gboolean is_ip6,
                         const char *uuid,
                         GBytes *client_id,
                         const char *dhcp_anycast_addr,
@@ -306,17 +313,17 @@ create_dhclient_config (NMDhcpDhclient *self,
 
 	g_return_val_if_fail (iface != NULL, NULL);
 
-	new = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s.conf", is_ip6 ? "6" : "", iface);
+	new = g_strdup_printf (NMSTATEDIR "/dhclient%s-%s.conf", _addr_family_to_path_part (addr_family), iface);
 	_LOGD ("creating composite dhclient config %s", new);
 
-	orig = find_existing_config (self, iface, uuid, is_ip6);
+	orig = find_existing_config (self, addr_family, iface, uuid);
 	if (orig)
 		_LOGD ("merging existing dhclient config %s", orig);
 	else
 		_LOGD ("no existing dhclient configuration to merge");
 
 	error = NULL;
-	success = merge_dhclient_config (self, iface, new, is_ip6, client_id, dhcp_anycast_addr,
+	success = merge_dhclient_config (self, addr_family, iface, new, client_id, dhcp_anycast_addr,
 	                                 hostname, timeout, use_fqdn, orig, out_new_client_id, &error);
 	if (!success) {
 		_LOGW ("error creating dhclient configuration: %s", error->message);
@@ -343,14 +350,15 @@ dhclient_start (NMDhcpClient *client,
 	GError *error = NULL;
 	const char *iface, *uuid, *system_bus_address, *dhclient_path = NULL;
 	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
-	gboolean ipv6, success;
+	int addr_family;
+	gboolean success;
 	char *escaped, *preferred_leasefile_path = NULL;
 
 	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
 
 	iface = nm_dhcp_client_get_iface (client);
 	uuid = nm_dhcp_client_get_uuid (client);
-	ipv6 = nm_dhcp_client_get_ipv6 (client);
+	addr_family = nm_dhcp_client_get_addr_family (client);
 
 	dhclient_path = nm_dhcp_dhclient_get_path ();
 	if (!dhclient_path) {
@@ -359,8 +367,8 @@ dhclient_start (NMDhcpClient *client,
 	}
 
 	pid_file = g_strdup_printf (RUNSTATEDIR "/dhclient%s-%s.pid",
-		                        ipv6 ? "6" : "",
-		                        iface);
+	                            _addr_family_to_path_part (addr_family),
+	                            iface);
 
 	/* Kill any existing dhclient from the pidfile */
 	binary_name = g_path_get_basename (dhclient_path);
@@ -374,7 +382,7 @@ dhclient_start (NMDhcpClient *client,
 	}
 
 	g_free (priv->lease_file);
-	priv->lease_file = get_dhclient_leasefile (iface, uuid, ipv6, &preferred_leasefile_path);
+	priv->lease_file = get_dhclient_leasefile (addr_family, iface, uuid, &preferred_leasefile_path);
 	if (!priv->lease_file) {
 		/* No existing leasefile, dhclient will create one at the preferred path */
 		priv->lease_file = g_strdup (preferred_leasefile_path);
@@ -400,7 +408,7 @@ dhclient_start (NMDhcpClient *client,
 	g_free (preferred_leasefile_path);
 
 	/* Save the DUID to the leasefile dhclient will actually use */
-	if (ipv6) {
+	if (addr_family == AF_INET6) {
 		escaped = nm_dhcp_dhclient_escape_duid (duid);
 		success = nm_dhcp_dhclient_save_duid (priv->lease_file, escaped, &error);
 		g_free (escaped);
@@ -424,7 +432,7 @@ dhclient_start (NMDhcpClient *client,
 	if (release)
 		g_ptr_array_add (argv, (gpointer) "-r");
 
-	if (ipv6) {
+	if (addr_family == AF_INET6) {
 		g_ptr_array_add (argv, (gpointer) "-6");
 		if (mode_opt)
 			g_ptr_array_add (argv, (gpointer) mode_opt);
@@ -507,7 +515,7 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	timeout = nm_dhcp_client_get_timeout (client);
 	use_fqdn = nm_dhcp_client_get_use_fqdn (client);
 
-	priv->conf_file = create_dhclient_config (self, iface, FALSE, uuid, client_id, dhcp_anycast_addr,
+	priv->conf_file = create_dhclient_config (self, AF_INET, iface, uuid, client_id, dhcp_anycast_addr,
 	                                          hostname, timeout, use_fqdn, &new_client_id);
 	if (priv->conf_file) {
 		if (new_client_id)
@@ -538,7 +546,7 @@ ip6_start (NMDhcpClient *client,
 	hostname = nm_dhcp_client_get_hostname (client);
 	timeout = nm_dhcp_client_get_timeout (client);
 
-	priv->conf_file = create_dhclient_config (self, iface, TRUE, uuid, NULL, dhcp_anycast_addr,
+	priv->conf_file = create_dhclient_config (self, AF_INET6, iface, uuid, NULL, dhcp_anycast_addr,
 	                                          hostname, timeout, TRUE, NULL);
 	if (!priv->conf_file) {
 		_LOGW ("error creating dhclient configuration file");
@@ -605,9 +613,9 @@ get_duid (NMDhcpClient *client)
 	GError *error = NULL;
 
 	/* Look in interface-specific leasefile first for backwards compat */
-	leasefile = get_dhclient_leasefile (nm_dhcp_client_get_iface (client),
+	leasefile = get_dhclient_leasefile (AF_INET6,
+	                                    nm_dhcp_client_get_iface (client),
 	                                    nm_dhcp_client_get_uuid (client),
-	                                    TRUE,
 	                                    NULL);
 	if (leasefile) {
 		_LOGD ("looking for DUID in '%s'", leasefile);
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
new file mode 100644
index 00000000..d7ddd194
--- /dev/null
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -0,0 +1,272 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-dhcp-dhcpcanon.c - dhcpcanon specific hooks for NetworkManager
+ *
+ * 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, 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) 2017 juga <juga at riseup dot net>
+ */
+
+#include "nm-default.h"
+
+#if WITH_DHCPCANON
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "nm-utils.h"
+#include "nm-dhcp-manager.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dhcp-listener.h"
+#include "nm-dhcp-client-logging.h"
+
+#define NM_TYPE_DHCP_DHCPCANON            (nm_dhcp_dhcpcanon_get_type ())
+#define NM_DHCP_DHCPCANON(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanon))
+#define NM_DHCP_DHCPCANON_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
+#define NM_IS_DHCP_DHCPCANON(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_DHCPCANON))
+#define NM_IS_DHCP_DHCPCANON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_DHCPCANON))
+#define NM_DHCP_DHCPCANON_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
+
+typedef struct _NMDhcpDhcpcanon NMDhcpDhcpcanon;
+typedef struct _NMDhcpDhcpcanonClass NMDhcpDhcpcanonClass;
+
+static GType nm_dhcp_dhcpcanon_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	char *conf_file;
+	const char *def_leasefile;
+	char *lease_file;
+	char *pid_file;
+	NMDhcpListener *dhcp_listener;
+} NMDhcpDhcpcanonPrivate;
+
+struct _NMDhcpDhcpcanon {
+	NMDhcpClient parent;
+	NMDhcpDhcpcanonPrivate _priv;
+};
+
+struct _NMDhcpDhcpcanonClass {
+	NMDhcpClientClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpDhcpcanon, nm_dhcp_dhcpcanon, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_DHCPCANON_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpDhcpcanon, NM_IS_DHCP_DHCPCANON)
+
+/*****************************************************************************/
+
+static const char *
+nm_dhcp_dhcpcanon_get_path (void)
+{
+	return nm_utils_find_helper ("dhcpcanon", DHCPCANON_PATH, NULL);
+}
+
+
+static gboolean
+dhcpcanon_start (NMDhcpClient *client,
+                const char *mode_opt,
+                const GByteArray *duid,
+                gboolean release,
+                pid_t *out_pid,
+                int prefixes)
+{
+	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+	GPtrArray *argv = NULL;
+	pid_t pid;
+	GError *error = NULL;
+	const char *iface, *system_bus_address, *dhcpcanon_path = NULL;
+	char *binary_name, *cmd_str, *pid_file = NULL, *system_bus_address_env = NULL;
+	int addr_family;
+
+	g_return_val_if_fail (priv->pid_file == NULL, FALSE);
+
+	iface = nm_dhcp_client_get_iface (client);
+	addr_family = nm_dhcp_client_get_addr_family (client);
+	dhcpcanon_path = nm_dhcp_dhcpcanon_get_path ();
+	_LOGD ("dhcpcanon_path: %s", dhcpcanon_path);
+	if (!dhcpcanon_path) {
+		_LOGW ("dhcpcanon could not be found");
+		return FALSE;
+	}
+
+	pid_file = g_strdup_printf (RUNSTATEDIR "/dhcpcanon%c-%s.pid",
+	                            nm_utils_addr_family_to_char (addr_family),
+	                            iface);
+	_LOGD ("pid_file: %s", pid_file);
+
+	/* Kill any existing dhcpcanon from the pidfile */
+	binary_name = g_path_get_basename (dhcpcanon_path);
+	nm_dhcp_client_stop_existing (pid_file, binary_name);
+	g_free (binary_name);
+
+	argv = g_ptr_array_new ();
+	g_ptr_array_add (argv, (gpointer) dhcpcanon_path);
+
+	g_ptr_array_add (argv, (gpointer) "-sf");	/* Set script file */
+	g_ptr_array_add (argv, (gpointer) nm_dhcp_helper_path);
+
+	if (pid_file) {
+		g_ptr_array_add (argv, (gpointer) "-pf");	/* Set pid file */
+		g_ptr_array_add (argv, (gpointer) pid_file);
+	}
+
+	if (priv->conf_file) {
+		g_ptr_array_add (argv, (gpointer) "-cf");	/* Set interface config file */
+		g_ptr_array_add (argv, (gpointer) priv->conf_file);
+	}
+
+	/* Usually the system bus address is well-known; but if it's supposed
+	 * to be something else, we need to push it to dhcpcanon, since dhcpcanon
+	 * sanitizes the environment it gives the action scripts.
+	 */
+	system_bus_address = getenv ("DBUS_SYSTEM_BUS_ADDRESS");
+	if (system_bus_address) {
+		system_bus_address_env = g_strdup_printf ("DBUS_SYSTEM_BUS_ADDRESS=%s", system_bus_address);
+		g_ptr_array_add (argv, (gpointer) "-e");
+		g_ptr_array_add (argv, (gpointer) system_bus_address_env);
+	}
+
+
+	g_ptr_array_add (argv, (gpointer) iface);
+	g_ptr_array_add (argv, NULL);
+
+	cmd_str = g_strjoinv (" ", (gchar **) argv->pdata);
+	g_free (cmd_str);
+
+	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,
+	                   nm_utils_setpgid, NULL, &pid, &error)) {
+		 g_assert (pid > 0);
+		_LOGI ("dhcpcanon started with pid %d", pid);
+		nm_dhcp_client_watch_child (client, pid);
+		priv->pid_file = pid_file;
+	} else {
+		_LOGW ("dhcpcanon failed to start: '%s'", error->message);
+		g_error_free (error);
+		g_free (pid_file);
+	}
+
+	g_ptr_array_free (argv, TRUE);
+	g_free (system_bus_address_env);
+	return pid > 0 ? TRUE : FALSE;
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+{
+	gboolean success = FALSE;
+	success = dhcpcanon_start (client, NULL, NULL, FALSE, NULL, 0);
+	return success;
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const struct in6_addr *ll_addr,
+           gboolean info_only,
+           NMSettingIP6ConfigPrivacy privacy,
+           const GByteArray *duid,
+           guint needed_prefixes)
+{
+	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
+
+	_LOGW ("the dhcpcd backend does not support IPv6");
+	return FALSE;
+}
+static void
+stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+{
+	NMDhcpDhcpcanon *self = NM_DHCP_DHCPCANON (client);
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_dhcpcanon_parent_class)->stop (client, release, duid);
+
+	if (priv->pid_file) {
+		if (remove (priv->pid_file) == -1)
+			_LOGD ("could not remove dhcp pid file \"%s\": %d (%s)", priv->pid_file, errno, g_strerror (errno));
+		g_free (priv->pid_file);
+		priv->pid_file = NULL;
+	}
+}
+
+static void
+state_changed (NMDhcpClient *client,
+               NMDhcpState state,
+               GObject *ip_config,
+               GHashTable *options)
+{
+	if (nm_dhcp_client_get_client_id (client))
+		return;
+	if (state != NM_DHCP_STATE_BOUND)
+		return;
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_dhcpcanon_init (NMDhcpDhcpcanon *self)
+{
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE (self);
+
+	priv->dhcp_listener = g_object_ref (nm_dhcp_listener_get ());
+	g_signal_connect (priv->dhcp_listener,
+	                  NM_DHCP_LISTENER_EVENT,
+	                  G_CALLBACK (nm_dhcp_client_handle_event),
+	                  self);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpDhcpcanonPrivate *priv = NM_DHCP_DHCPCANON_GET_PRIVATE ((NMDhcpDhcpcanon *) object);
+
+	if (priv->dhcp_listener) {
+		g_signal_handlers_disconnect_by_func (priv->dhcp_listener,
+		                                      G_CALLBACK (nm_dhcp_client_handle_event),
+		                                      NM_DHCP_DHCPCANON (object));
+		g_clear_object (&priv->dhcp_listener);
+	}
+
+	nm_clear_g_free (&priv->pid_file);
+
+	G_OBJECT_CLASS (nm_dhcp_dhcpcanon_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_dhcpcanon_class_init (NMDhcpDhcpcanonClass *dhcpcanon_class)
+{
+	NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (dhcpcanon_class);
+	GObjectClass *object_class = G_OBJECT_CLASS (dhcpcanon_class);
+
+	object_class->dispose = dispose;
+
+	client_class->ip4_start = ip4_start;
+	client_class->ip6_start = ip6_start;
+	client_class->stop = stop;
+	client_class->state_changed = state_changed;
+}
+
+const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = {
+	.name = "dhcpcanon",
+	.get_type = nm_dhcp_dhcpcanon_get_type,
+	.get_path = nm_dhcp_dhcpcanon_get_path,
+	.get_lease_ip_configs = NULL,
+};
+
+#endif /* WITH_DHCPCANON */
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index ca697ab3..a0449816 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -31,6 +31,7 @@
 
 #include "nm-dhcp-helper-api.h"
 #include "nm-dhcp-client.h"
+#include "nm-dhcp-manager.h"
 #include "nm-core-internal.h"
 #include "nm-bus-manager.h"
 #include "NetworkManagerUtils.h"
@@ -40,10 +41,13 @@
 
 /*****************************************************************************/
 
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[3] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 	/* the order here matters, as we will try the plugins in this order to find
 	 * the first available plugin. */
 
+#if WITH_DHCPCANON
+	&_nm_dhcp_client_factory_dhcpcanon,
+#endif
 #if WITH_DHCLIENT
 	&_nm_dhcp_client_factory_dhclient,
 #endif
@@ -135,49 +139,35 @@ get_option (GVariant *options, const char *key)
 }
 
 static void
-_method_call (GDBusConnection *connection,
-              const char *sender,
-              const char *object_path,
-              const char *interface_name,
-              const char *method_name,
-              GVariant *parameters,
-              GDBusMethodInvocation *invocation,
-              gpointer user_data)
+_method_call_handle (NMDhcpListener *self,
+                     GVariant *parameters)
 {
-	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
-	char *iface = NULL;
-	char *pid_str = NULL;
-	char *reason = NULL;
-	gint pid;
+	gs_free char *iface = NULL;
+	gs_free char *pid_str = NULL;
+	gs_free char *reason = NULL;
+	gs_unref_variant GVariant *options;
+	int pid;
 	gboolean handled = FALSE;
-	GVariant *options;
-
-	if (!nm_streq0 (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME))
-		g_return_if_reached ();
-	if (!nm_streq0 (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY))
-		g_return_if_reached ();
-	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
-		g_return_if_reached ();
 
 	g_variant_get (parameters, "(@a{sv})", &options);
 
 	iface = get_option (options, "interface");
 	if (iface == NULL) {
 		_LOGW ("dhcp-event: didn't have associated interface.");
-		goto out;
+		return;
 	}
 
 	pid_str = get_option (options, "pid");
 	pid = _nm_utils_ascii_str_to_int64 (pid_str, 10, 0, G_MAXINT32, -1);
 	if (pid == -1) {
 		_LOGW ("dhcp-event: couldn't convert PID '%s' to an integer", pid_str ? pid_str : "(null)");
-		goto out;
+		return;
 	}
 
 	reason = get_option (options, "reason");
 	if (reason == NULL) {
 		_LOGW ("dhcp-event: (pid %d) DHCP event didn't have a reason", pid);
-		goto out;
+		return;
 	}
 
 	g_signal_emit (self, signals[EVENT], 0, iface, pid, options, reason, &handled);
@@ -188,12 +178,29 @@ _method_call (GDBusConnection *connection,
 		} else
 			_LOGW ("dhcp-event: (pid %d) unhandled DHCP event for interface %s", pid, iface);
 	}
+}
+
+static void
+_method_call (GDBusConnection *connection,
+              const char *sender,
+              const char *object_path,
+              const char *interface_name,
+              const char *method_name,
+              GVariant *parameters,
+              GDBusMethodInvocation *invocation,
+              gpointer user_data)
+{
+	NMDhcpListener *self = NM_DHCP_LISTENER (user_data);
+
+	if (!nm_streq0 (interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME))
+		g_return_if_reached ();
+	if (!nm_streq0 (method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY))
+		g_return_if_reached ();
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(a{sv})")))
+		g_return_if_reached ();
+
+	_method_call_handle (self, parameters);
 
-out:
-	g_free (iface);
-	g_free (pid_str);
-	g_free (reason);
-	g_variant_unref (options);
 	g_dbus_method_invocation_return_value (invocation, NULL);
 }
 
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index fff9f9ec..f5c7c84b 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -34,11 +34,11 @@
 #include <fcntl.h>
 #include <stdio.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-config.h"
 #include "NetworkManagerUtils.h"
 
-#define DHCP_TIMEOUT 45 /* default DHCP timeout, in seconds */
-
 /*****************************************************************************/
 
 typedef struct {
@@ -95,7 +95,7 @@ _client_factory_available (const NMDhcpClientFactory *client_factory)
 /*****************************************************************************/
 
 static NMDhcpClient *
-get_client_for_ifindex (NMDhcpManager *manager, int ifindex, gboolean ip6)
+get_client_for_ifindex (NMDhcpManager *manager, int addr_family, int ifindex)
 {
 	NMDhcpManagerPrivate *priv;
 	GHashTableIter iter;
@@ -111,7 +111,7 @@ get_client_for_ifindex (NMDhcpManager *manager, int ifindex, gboolean ip6)
 		NMDhcpClient *candidate = NM_DHCP_CLIENT (value);
 
 		if (   nm_dhcp_client_get_ifindex (candidate) == ifindex
-		    && nm_dhcp_client_get_ipv6 (candidate) == ip6)
+		    && nm_dhcp_client_get_addr_family (candidate) == addr_family)
 			return candidate;
 	}
 
@@ -152,12 +152,14 @@ client_state_changed (NMDhcpClient *client,
 
 static NMDhcpClient *
 client_start (NMDhcpManager *self,
+              int addr_family,
+              NMDedupMultiIndex *multi_idx,
               const char *iface,
               int ifindex,
               const GByteArray *hwaddr,
               const char *uuid,
-              guint32 priority,
-              gboolean ipv6,
+              guint32 route_table,
+              guint32 route_metric,
               const struct in6_addr *ipv6_ll_addr,
               const char *dhcp_client_id,
               guint32 timeout,
@@ -185,7 +187,7 @@ client_start (NMDhcpManager *self,
 		return NULL;
 
 	/* Kill any old client instance */
-	client = get_client_for_ifindex (self, ifindex, ipv6);
+	client = get_client_for_ifindex (self, addr_family, ifindex);
 	if (client) {
 		g_object_ref (client);
 		remove_client (self, client);
@@ -195,21 +197,23 @@ client_start (NMDhcpManager *self,
 
 	/* And make a new one */
 	client = g_object_new (priv->client_factory->get_type (),
+	                       NM_DHCP_CLIENT_MULTI_IDX, multi_idx,
+	                       NM_DHCP_CLIENT_ADDR_FAMILY, addr_family,
 	                       NM_DHCP_CLIENT_INTERFACE, iface,
 	                       NM_DHCP_CLIENT_IFINDEX, ifindex,
 	                       NM_DHCP_CLIENT_HWADDR, hwaddr,
-	                       NM_DHCP_CLIENT_IPV6, ipv6,
 	                       NM_DHCP_CLIENT_UUID, uuid,
-	                       NM_DHCP_CLIENT_PRIORITY, priority,
-	                       NM_DHCP_CLIENT_TIMEOUT, timeout ? timeout : DHCP_TIMEOUT,
+	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
+	                       NM_DHCP_CLIENT_ROUTE_METRIC, (guint) route_metric,
+	                       NM_DHCP_CLIENT_TIMEOUT, (guint) timeout,
 	                       NULL);
 	g_hash_table_insert (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client, g_object_ref (client));
 	g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self);
 
-	if (ipv6)
-		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
-	else
+	if (addr_family == AF_INET)
 		success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, hostname_use_fqdn, last_ip4_address);
+	else
+		success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy, needed_prefixes);
 
 	if (!success) {
 		remove_client (self, client);
@@ -222,11 +226,13 @@ client_start (NMDhcpManager *self,
 /* Caller owns a reference to the NMDhcpClient on return */
 NMDhcpClient *
 nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
+                           NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
                            const GByteArray *hwaddr,
                            const char *uuid,
-                           guint32 priority,
+                           guint32 route_table,
+                           guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            const char *dhcp_fqdn,
@@ -267,7 +273,8 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 		}
 	}
 
-	return client_start (self, iface, ifindex, hwaddr, uuid, priority, FALSE, NULL,
+	return client_start (self, AF_INET, multi_idx, iface, ifindex, hwaddr, uuid,
+	                     route_table, route_metric, NULL,
 	                     dhcp_client_id, timeout, dhcp_anycast_addr, hostname,
 	                     use_fqdn, FALSE, 0, last_ip_address, 0);
 }
@@ -275,12 +282,14 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 /* Caller owns a reference to the NMDhcpClient on return */
 NMDhcpClient *
 nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
+                           NMDedupMultiIndex *multi_idx,
                            const char *iface,
                            int ifindex,
                            const GByteArray *hwaddr,
                            const struct in6_addr *ll_addr,
                            const char *uuid,
-                           guint32 priority,
+                           guint32 route_table,
+                           guint32 route_metric,
                            gboolean send_hostname,
                            const char *dhcp_hostname,
                            guint32 timeout,
@@ -299,8 +308,9 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 		/* Always prefer the explicit dhcp-hostname if given */
 		hostname = dhcp_hostname ? dhcp_hostname : priv->default_hostname;
 	}
-	return client_start (self, iface, ifindex, hwaddr, uuid, priority, TRUE,
-	                     ll_addr, NULL, timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
+	return client_start (self, AF_INET6, multi_idx, iface, ifindex, hwaddr, uuid,
+	                     route_table, route_metric, ll_addr,
+	                     NULL, timeout, dhcp_anycast_addr, hostname, TRUE, info_only,
 	                     privacy, NULL, needed_prefixes);
 }
 
@@ -320,11 +330,13 @@ nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager, const char *hostna
 
 GSList *
 nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                      NMDedupMultiIndex *multi_idx,
+                                      int addr_family,
                                       const char *iface,
                                       int ifindex,
                                       const char *uuid,
-                                      gboolean ipv6,
-                                      guint32 default_route_metric)
+                                      guint32 route_table,
+                                      guint32 route_metric)
 {
 	NMDhcpManagerPrivate *priv;
 
@@ -332,11 +344,12 @@ nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
 	g_return_val_if_fail (iface != NULL, NULL);
 	g_return_val_if_fail (ifindex >= -1, NULL);
 	g_return_val_if_fail (uuid != NULL, NULL);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET, AF_INET6), NULL);
 
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 	if (   priv->client_factory
 	    && priv->client_factory->get_lease_ip_configs)
-		return priv->client_factory->get_lease_ip_configs (iface, ifindex, uuid, ipv6, default_route_metric);
+		return priv->client_factory->get_lease_ip_configs (multi_idx, addr_family, iface, ifindex, uuid, route_table, route_metric);
 	return NULL;
 }
 
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index 66fdd145..078117ff 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -46,11 +46,13 @@ void           nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager,
                                                      const char *hostname);
 
 NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
+                                              struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
                                               const GByteArray *hwaddr,
                                               const char *uuid,
-                                              guint32 priority,
+                                              guint32 route_table,
+                                              guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               const char *dhcp_fqdn,
@@ -60,12 +62,14 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               const char *last_ip_address);
 
 NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
+                                              struct _NMDedupMultiIndex *multi_idx,
                                               const char *iface,
                                               int ifindex,
                                               const GByteArray *hwaddr,
                                               const struct in6_addr *ll_addr,
                                               const char *uuid,
-                                              guint32 priority,
+                                              guint32 route_table,
+                                              guint32 route_metric,
                                               gboolean send_hostname,
                                               const char *dhcp_hostname,
                                               guint32 timeout,
@@ -75,15 +79,17 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               guint needed_prefixes);
 
 GSList *       nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
+                                                     struct _NMDedupMultiIndex *multi_idx,
+                                                     int addr_family,
                                                      const char *iface,
                                                      int ifindex,
                                                      const char *uuid,
-                                                     gboolean ipv6,
-                                                     guint32 default_route_metric);
+                                                     guint32 route_table,
+                                                     guint32 route_metric);
 
 /* For testing only */
 extern const char* nm_dhcp_helper_path;
 
-extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[3];
+extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4];
 
 #endif /* __NETWORKMANAGER_DHCP_MANAGER_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index aa902701..9b1a4433 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -28,6 +28,8 @@
 #include <ctype.h>
 #include <net/if_arp.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-utils.h"
 #include "nm-dhcp-utils.h"
 #include "NetworkManagerUtils.h"
@@ -79,9 +81,6 @@ G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
 
 #define DHCP_OPTION_NIS_DOMAIN         40
 #define DHCP_OPTION_NIS_SERVERS        41
-#define DHCP_OPTION_DOMAIN_SEARCH     119
-#define DHCP_OPTION_MS_ROUTES         249
-#define DHCP_OPTION_WPAD              252
 
 /* Internal values */
 #define DHCP_OPTION_IP_ADDRESS       1024
@@ -105,53 +104,53 @@ typedef struct {
 #define REQPREFIX "requested_"
 
 static const ReqOption dhcp4_requests[] = {
-	{ SD_DHCP_OPTION_SUBNET_MASK,            REQPREFIX "subnet_mask",                     TRUE },
-	{ SD_DHCP_OPTION_TIME_OFFSET,            REQPREFIX "time_offset",                     TRUE },
-	{ SD_DHCP_OPTION_ROUTER,                 REQPREFIX "routers",                         TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,     REQPREFIX "domain_name_servers",             TRUE },
-	{ SD_DHCP_OPTION_HOST_NAME,              REQPREFIX "host_name",                       TRUE },
-	{ SD_DHCP_OPTION_DOMAIN_NAME,            REQPREFIX "domain_name",                     TRUE },
-	{ SD_DHCP_OPTION_INTERFACE_MTU,          REQPREFIX "interface_mtu",                   TRUE },
-	{ SD_DHCP_OPTION_BROADCAST,              REQPREFIX "broadcast_address",               TRUE },
-	{ SD_DHCP_OPTION_STATIC_ROUTE,           REQPREFIX "static_routes",                   TRUE },
-	{ DHCP_OPTION_NIS_DOMAIN,                REQPREFIX "nis_domain",                      TRUE },
-	{ DHCP_OPTION_NIS_SERVERS,               REQPREFIX "nis_servers",                     TRUE },
-	{ SD_DHCP_OPTION_NTP_SERVER,             REQPREFIX "ntp_servers",                     TRUE },
-	{ SD_DHCP_OPTION_SERVER_IDENTIFIER,      REQPREFIX "dhcp_server_identifier",          TRUE },
-	{ DHCP_OPTION_DOMAIN_SEARCH,             REQPREFIX "domain_search",                   TRUE },
-	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, REQPREFIX "rfc3442_classless_static_routes", TRUE },
-	{ DHCP_OPTION_MS_ROUTES,                 REQPREFIX "ms_classless_static_routes",      TRUE },
-	{ DHCP_OPTION_WPAD,                      REQPREFIX "wpad",                            TRUE },
+	{ SD_DHCP_OPTION_SUBNET_MASK,                    REQPREFIX "subnet_mask",                     TRUE },
+	{ SD_DHCP_OPTION_TIME_OFFSET,                    REQPREFIX "time_offset",                     TRUE },
+	{ SD_DHCP_OPTION_ROUTER,                         REQPREFIX "routers",                         TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             REQPREFIX "domain_name_servers",             TRUE },
+	{ SD_DHCP_OPTION_HOST_NAME,                      REQPREFIX "host_name",                       TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_NAME,                    REQPREFIX "domain_name",                     TRUE },
+	{ SD_DHCP_OPTION_INTERFACE_MTU,                  REQPREFIX "interface_mtu",                   TRUE },
+	{ SD_DHCP_OPTION_BROADCAST,                      REQPREFIX "broadcast_address",               TRUE },
+	{ SD_DHCP_OPTION_STATIC_ROUTE,                   REQPREFIX "static_routes",                   TRUE },
+	{ DHCP_OPTION_NIS_DOMAIN,                        REQPREFIX "nis_domain",                      TRUE },
+	{ DHCP_OPTION_NIS_SERVERS,                       REQPREFIX "nis_servers",                     TRUE },
+	{ SD_DHCP_OPTION_NTP_SERVER,                     REQPREFIX "ntp_servers",                     TRUE },
+	{ SD_DHCP_OPTION_SERVER_IDENTIFIER,              REQPREFIX "dhcp_server_identifier",          TRUE },
+	{ SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             REQPREFIX "domain_search",                   TRUE },
+	{ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         REQPREFIX "rfc3442_classless_static_routes", TRUE },
+	{ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, REQPREFIX "ms_classless_static_routes",      TRUE },
+	{ SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    REQPREFIX "wpad",                            TRUE },
 
 	/* Internal values */
-	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,  REQPREFIX "expiry",                          FALSE },
-	{ SD_DHCP_OPTION_CLIENT_IDENTIFIER,      REQPREFIX "dhcp_client_identifier",          FALSE },
-	{ DHCP_OPTION_IP_ADDRESS,                REQPREFIX "ip_address",                      FALSE },
+	{ SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          REQPREFIX "expiry",                          FALSE },
+	{ SD_DHCP_OPTION_CLIENT_IDENTIFIER,              REQPREFIX "dhcp_client_identifier",          FALSE },
+	{ DHCP_OPTION_IP_ADDRESS,                        REQPREFIX "ip_address",                      FALSE },
 	{ 0, NULL, FALSE }
 };
 
 static const ReqOption dhcp6_requests[] = {
-	{ SD_DHCP6_OPTION_CLIENTID,              REQPREFIX "dhcp6_client_id",     TRUE },
+	{ SD_DHCP6_OPTION_CLIENTID,                      REQPREFIX "dhcp6_client_id",     TRUE },
 
 	/* Don't request server ID by default; some servers don't reply to
 	 * Information Requests that request the Server ID.
 	 */
-	{ SD_DHCP6_OPTION_SERVERID,              REQPREFIX "dhcp6_server_id",     FALSE },
+	{ SD_DHCP6_OPTION_SERVERID,                      REQPREFIX "dhcp6_server_id",     FALSE },
 
-	{ SD_DHCP6_OPTION_DNS_SERVERS,           REQPREFIX "dhcp6_name_servers",  TRUE },
-	{ SD_DHCP6_OPTION_DOMAIN_LIST,           REQPREFIX "dhcp6_domain_search", TRUE },
-	{ SD_DHCP6_OPTION_SNTP_SERVERS,          REQPREFIX "dhcp6_sntp_servers",  TRUE },
+	{ SD_DHCP6_OPTION_DNS_SERVERS,                   REQPREFIX "dhcp6_name_servers",  TRUE },
+	{ SD_DHCP6_OPTION_DOMAIN_LIST,                   REQPREFIX "dhcp6_domain_search", TRUE },
+	{ SD_DHCP6_OPTION_SNTP_SERVERS,                  REQPREFIX "dhcp6_sntp_servers",  TRUE },
 
 	/* Internal values */
-	{ DHCP6_OPTION_IP_ADDRESS,               REQPREFIX "ip6_address",         FALSE },
-	{ DHCP6_OPTION_PREFIXLEN,                REQPREFIX "ip6_prefixlen",       FALSE },
-	{ DHCP6_OPTION_PREFERRED_LIFE,           REQPREFIX "preferred_life",      FALSE },
-	{ DHCP6_OPTION_MAX_LIFE,                 REQPREFIX "max_life",            FALSE },
-	{ DHCP6_OPTION_STARTS,                   REQPREFIX "starts",              FALSE },
-	{ DHCP6_OPTION_LIFE_STARTS,              REQPREFIX "life_starts",         FALSE },
-	{ DHCP6_OPTION_RENEW,                    REQPREFIX "renew",               FALSE },
-	{ DHCP6_OPTION_REBIND,                   REQPREFIX "rebind",              FALSE },
-	{ DHCP6_OPTION_IAID,                     REQPREFIX "iaid",                FALSE },
+	{ DHCP6_OPTION_IP_ADDRESS,                       REQPREFIX "ip6_address",         FALSE },
+	{ DHCP6_OPTION_PREFIXLEN,                        REQPREFIX "ip6_prefixlen",       FALSE },
+	{ DHCP6_OPTION_PREFERRED_LIFE,                   REQPREFIX "preferred_life",      FALSE },
+	{ DHCP6_OPTION_MAX_LIFE,                         REQPREFIX "max_life",            FALSE },
+	{ DHCP6_OPTION_STARTS,                           REQPREFIX "starts",              FALSE },
+	{ DHCP6_OPTION_LIFE_STARTS,                      REQPREFIX "life_starts",         FALSE },
+	{ DHCP6_OPTION_RENEW,                            REQPREFIX "renew",               FALSE },
+	{ DHCP6_OPTION_REBIND,                           REQPREFIX "rebind",              FALSE },
+	{ DHCP6_OPTION_IAID,                             REQPREFIX "iaid",                FALSE },
 	{ 0, NULL, FALSE }
 };
 
@@ -212,16 +211,18 @@ add_requests_to_options (GHashTable *options, const ReqOption *requests)
 #define LOG_LEASE(domain, ...) \
 G_STMT_START { \
 	if (log_lease) { \
-		_LOG2I ((domain), (iface), __VA_ARGS__); \
+		_LOG2I ((domain), (iface), "  "__VA_ARGS__); \
 	} \
 } G_STMT_END
 
 static NMIP4Config *
-lease_to_ip4_config (const char *iface,
+lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
+                     const char *iface,
                      int ifindex,
                      sd_dhcp_lease *lease,
                      GHashTable *options,
-                     guint32 default_priority,
+                     guint32 route_table,
+                     guint32 route_metric,
                      gboolean log_lease,
                      GError **error)
 {
@@ -229,11 +230,12 @@ lease_to_ip4_config (const char *iface,
 	struct in_addr tmp_addr;
 	const struct in_addr *addr_list;
 	char buf[INET_ADDRSTRLEN];
-	const char *str;
+	const char *s;
 	guint32 lifetime = 0, i;
 	NMPlatformIP4Address address;
-	GString *l;
+	nm_auto_free_gstring GString *str = NULL;
 	gs_free sd_dhcp_route **routes = NULL;
+	const char *const*search_domains = NULL;
 	guint16 mtu;
 	int r, num;
 	guint64 end_time;
@@ -241,24 +243,26 @@ lease_to_ip4_config (const char *iface,
 	gsize data_len;
 	gboolean metered = FALSE;
 	gboolean static_default_gateway = FALSE;
+	gboolean gateway_has = FALSE;
+	in_addr_t gateway = 0;
 
 	g_return_val_if_fail (lease != NULL, NULL);
 
-	ip4_config = nm_ip4_config_new (ifindex);
+	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 
 	/* Address */
 	sd_dhcp_lease_get_address (lease, &tmp_addr);
 	memset (&address, 0, sizeof (address));
 	address.address = tmp_addr.s_addr;
 	address.peer_address = tmp_addr.s_addr;
-	str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-	LOG_LEASE (LOGD_DHCP4, "  address %s", str);
-	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, str);
+	s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+	LOG_LEASE (LOGD_DHCP4, "address %s", s);
+	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, s);
 
 	/* Prefix/netmask */
 	sd_dhcp_lease_get_netmask (lease, &tmp_addr);
 	address.plen = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
-	LOG_LEASE (LOGD_DHCP4, "  plen %d", address.plen);
+	LOG_LEASE (LOGD_DHCP4, "plen %d", address.plen);
 	add_option (options,
 	            dhcp4_requests,
 	            SD_DHCP_OPTION_SUBNET_MASK,
@@ -269,7 +273,7 @@ lease_to_ip4_config (const char *iface,
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 	address.lifetime = address.preferred = lifetime;
 	end_time = (guint64) time (NULL) + lifetime;
-	LOG_LEASE (LOGD_DHCP4, "  expires in %" G_GUINT32_FORMAT " seconds", lifetime);
+	LOG_LEASE (LOGD_DHCP4, "expires in %" G_GUINT32_FORMAT " seconds", lifetime);
 	add_option_u64 (options,
 	                dhcp4_requests,
 	                SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
@@ -281,47 +285,58 @@ lease_to_ip4_config (const char *iface,
 	/* DNS Servers */
 	num = sd_dhcp_lease_get_dns (lease, &addr_list);
 	if (num > 0) {
-		l = g_string_sized_new (30);
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			if (addr_list[i].s_addr) {
 				nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
-				str = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
-				LOG_LEASE (LOGD_DHCP4, "  nameserver '%s'", str);
-				g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+				s = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
+				LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", s);
+				g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
 			}
 		}
-		if (l->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, l->str);
-		g_string_free (l, TRUE);
+		if (str->len)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
+	}
+
+	/* Search domains */
+	num = sd_dhcp_lease_get_search_domains (lease, (char ***) &search_domains);
+	if (num > 0) {
+		nm_gstring_prepare (&str);
+		for (i = 0; i < num; i++) {
+			nm_ip4_config_add_search (ip4_config, search_domains[i]);
+			g_string_append_printf (str, "%s%s", str->len ? " " : "", search_domains[i]);
+			LOG_LEASE (LOGD_DHCP4, "domain search '%s'", search_domains[i]);
+		}
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
 	}
 
 	/* Domain Name */
-	r = sd_dhcp_lease_get_domainname (lease, &str);
+	r = sd_dhcp_lease_get_domainname (lease, &s);
 	if (r == 0) {
 		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
 		 * As systemd escapes such characters, split them at \\032. */
-		char **domains = g_strsplit (str, "\\032", 0);
-		char **s;
+		char **domains = g_strsplit (s, "\\032", 0);
+		char **d;
 
-		for (s = domains; *s; s++) {
-			LOG_LEASE (LOGD_DHCP4, "  domain name '%s'", *s);
-			nm_ip4_config_add_domain (ip4_config, *s);
+		for (d = domains; *d; d++) {
+			LOG_LEASE (LOGD_DHCP4, "domain name '%s'", *d);
+			nm_ip4_config_add_domain (ip4_config, *d);
 		}
 		g_strfreev (domains);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, str);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
 	}
 
 	/* Hostname */
-	r = sd_dhcp_lease_get_hostname (lease, &str);
+	r = sd_dhcp_lease_get_hostname (lease, &s);
 	if (r == 0) {
-		LOG_LEASE (LOGD_DHCP4, "  hostname '%s'", str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, str);
+		LOG_LEASE (LOGD_DHCP4, "hostname '%s'", s);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, s);
 	}
 
 	/* Routes */
 	num = sd_dhcp_lease_get_routes (lease, &routes);
 	if (num > 0) {
-		l = g_string_sized_new (30);
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			NMPlatformIP4Route route = { 0 };
 			const char *gw_str;
@@ -330,12 +345,13 @@ lease_to_ip4_config (const char *iface,
 
 			if (sd_dhcp_route_get_destination (routes[i], &a) < 0)
 				continue;
-			route.network = a.s_addr;
 
 			if (   sd_dhcp_route_get_destination_prefix_length (routes[i], &plen) < 0
 			    || plen > 32)
 				continue;
+
 			route.plen = plen;
+			route.network = nm_utils_ip4_address_clear_host_address (a.s_addr, plen);
 
 			if (sd_dhcp_route_get_gateway (routes[i], &a) < 0)
 				continue;
@@ -343,28 +359,29 @@ lease_to_ip4_config (const char *iface,
 
 			if (route.plen) {
 				route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-				route.metric = default_priority;
-				nm_ip4_config_add_route (ip4_config, &route);
+				route.metric = route_metric;
+				route.table_coerced = nm_platform_route_table_coerce (route_table);
+				nm_ip4_config_add_route (ip4_config, &route, NULL);
 
-				str = nm_utils_inet4_ntop (route.network, buf);
+				s = nm_utils_inet4_ntop (route.network, buf);
 				gw_str = nm_utils_inet4_ntop (route.gateway, NULL);
-				LOG_LEASE (LOGD_DHCP4, "  static route %s/%d gw %s", str, route.plen, gw_str);
+				LOG_LEASE (LOGD_DHCP4, "static route %s/%d gw %s", s, route.plen, gw_str);
 
-				g_string_append_printf (l, "%s%s/%d %s", l->len ? " " : "", str, route.plen, gw_str);
+				g_string_append_printf (str, "%s%s/%d %s", str->len ? " " : "", s, route.plen, gw_str);
 			} else {
 				if (!static_default_gateway) {
 					static_default_gateway = TRUE;
-					nm_ip4_config_set_gateway (ip4_config, route.gateway);
+					gateway_has = TRUE;
+					gateway = route.gateway;
 
-					str = nm_utils_inet4_ntop (route.gateway, NULL);
-					LOG_LEASE (LOGD_DHCP4, "  gateway %s", str);
-					add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str);
+					s = nm_utils_inet4_ntop (route.gateway, NULL);
+					LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
+					add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
 				}
 			}
 		}
-		if (l->len)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, l->str);
-		g_string_free (l, TRUE);
+		if (str->len)
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str->str);
 	}
 
 	/* If the DHCP server returns both a Classless Static Routes option and a
@@ -376,32 +393,43 @@ lease_to_ip4_config (const char *iface,
 	if (!static_default_gateway) {
 		r = sd_dhcp_lease_get_router (lease, &tmp_addr);
 		if (r == 0) {
-			nm_ip4_config_set_gateway (ip4_config, tmp_addr.s_addr);
-			str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
-			LOG_LEASE (LOGD_DHCP4, "  gateway %s", str);
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str);
+			gateway_has = TRUE;
+			gateway = tmp_addr.s_addr;
+			s = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+			LOG_LEASE (LOGD_DHCP4, "gateway %s", s);
+			add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, s);
 		}
 	}
 
+	if (gateway_has) {
+		const NMPlatformIP4Route rt = {
+			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+			.gateway = gateway,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric = route_metric,
+		};
+
+		nm_ip4_config_add_route (ip4_config, &rt, NULL);
+	}
+
 	/* MTU */
 	r = sd_dhcp_lease_get_mtu (lease, &mtu);
 	if (r == 0 && mtu) {
 		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
 		add_option_u32 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
-		LOG_LEASE (LOGD_DHCP4, "  mtu %u", mtu);
+		LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
 	}
 
 	/* NTP servers */
 	num = sd_dhcp_lease_get_ntp (lease, &addr_list);
 	if (num > 0) {
-		l = g_string_sized_new (30);
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
-			str = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
-			LOG_LEASE (LOGD_DHCP4, "  ntp server '%s'", str);
-			g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+			s = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
+			LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", s);
+			g_string_append_printf (str, "%s%s", str->len ? " " : "", s);
 		}
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, l->str);
-		g_string_free (l, TRUE);
+		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
 	}
 
 	r = sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len);
@@ -415,20 +443,22 @@ lease_to_ip4_config (const char *iface,
 /*****************************************************************************/
 
 static char *
-get_leasefile_path (const char *iface, const char *uuid, gboolean ipv6)
+get_leasefile_path (int addr_family, const char *iface, const char *uuid)
 {
 	return g_strdup_printf (NMSTATEDIR "/internal%s-%s-%s.lease",
-	                        ipv6 ? "6" : "",
+	                        addr_family == AF_INET6 ? "6" : "",
 	                        uuid,
 	                        iface);
 }
 
 static GSList *
-nm_dhcp_systemd_get_lease_ip_configs (const char *iface,
+nm_dhcp_systemd_get_lease_ip_configs (NMDedupMultiIndex *multi_idx,
+                                      int addr_family,
+                                      const char *iface,
                                       int ifindex,
                                       const char *uuid,
-                                      gboolean ipv6,
-                                      guint32 default_route_metric)
+                                      guint32 route_table,
+                                      guint32 route_metric)
 {
 	GSList *leases = NULL;
 	gs_free char *path = NULL;
@@ -436,13 +466,13 @@ nm_dhcp_systemd_get_lease_ip_configs (const char *iface,
 	NMIP4Config *ip4_config;
 	int r;
 
-	if (ipv6)
+	if (addr_family != AF_INET)
 		return NULL;
 
-	path = get_leasefile_path (iface, uuid, FALSE);
+	path = get_leasefile_path (addr_family, iface, uuid);
 	r = dhcp_lease_load (&lease, path);
 	if (r == 0 && lease) {
-		ip4_config = lease_to_ip4_config (iface, ifindex, lease, NULL, default_route_metric, FALSE, NULL);
+		ip4_config = lease_to_ip4_config (multi_idx, iface, ifindex, lease, NULL, route_table, route_metric, FALSE, NULL);
 		if (ip4_config)
 			leases = g_slist_append (leases, ip4_config);
 		sd_dhcp_lease_unref (lease);
@@ -495,12 +525,14 @@ bound4_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
-	ip4_config = lease_to_ip4_config (iface,
+	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+	ip4_config = lease_to_ip4_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
+	                                  iface,
 	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
 	                                  lease,
 	                                  options,
-	                                  nm_dhcp_client_get_priority (NM_DHCP_CLIENT (self)),
+	                                  nm_dhcp_client_get_route_table (NM_DHCP_CLIENT (self)),
+	                                  nm_dhcp_client_get_route_metric (NM_DHCP_CLIENT (self)),
 	                                  TRUE,
 	                                  &error);
 	if (ip4_config) {
@@ -589,9 +621,9 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	g_assert (priv->client6 == NULL);
 
 	g_free (priv->lease_file);
-	priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), FALSE);
+	priv->lease_file = get_leasefile_path (AF_INET, iface, nm_dhcp_client_get_uuid (client));
 
-	r = sd_dhcp_client_new (&priv->client4);
+	r = sd_dhcp_client_new (&priv->client4, FALSE);
 	if (r < 0) {
 		_LOGW ("failed to create client (%d)", r);
 		return FALSE;
@@ -716,7 +748,8 @@ error:
 }
 
 static NMIP6Config *
-lease_to_ip6_config (const char *iface,
+lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
+                     const char *iface,
                      int ifindex,
                      sd_dhcp6_lease *lease,
                      GHashTable *options,
@@ -729,17 +762,17 @@ lease_to_ip6_config (const char *iface,
 	NMIP6Config *ip6_config;
 	const char *addr_str;
 	char **domains;
-	GString *str;
+	nm_auto_free_gstring GString *str = NULL;
 	int num, i;
 	gint32 ts;
 
 	g_return_val_if_fail (lease, NULL);
-	ip6_config = nm_ip6_config_new (ifindex);
+	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
 	ts = nm_utils_get_monotonic_timestamp_s ();
-	str = g_string_sized_new (30);
 
 	/* Addresses */
 	sd_dhcp6_lease_reset_address_iter (lease);
+	nm_gstring_prepare (&str);
 	while (sd_dhcp6_lease_get_address (lease, &tmp_addr, &lft_pref, &lft_valid) >= 0) {
 		NMPlatformIP6Address address = {
 			.plen = 128,
@@ -756,17 +789,14 @@ lease_to_ip6_config (const char *iface,
 		g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
 
 		LOG_LEASE (LOGD_DHCP6,
-		           "  address %s",
+		           "address %s",
 		           nm_platform_ip6_address_to_string (&address, NULL, 0));
 	};
 
-	if (str->len) {
+	if (str->len)
 		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
-		g_string_set_size (str , 0);
-	}
 
 	if (!info_only && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
-		g_string_free (str, TRUE);
 		g_object_unref (ip6_config);
 		g_set_error_literal (error,
 		                     NM_MANAGER_ERROR,
@@ -778,30 +808,28 @@ lease_to_ip6_config (const char *iface,
 	/* DNS servers */
 	num = sd_dhcp6_lease_get_dns (lease, &dns);
 	if (num > 0) {
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
 			addr_str = nm_utils_inet6_ntop (&dns[i], NULL);
 			g_string_append_printf (str, "%s%s", str->len ? " " : "", addr_str);
-			LOG_LEASE (LOGD_DHCP6, "  nameserver %s", addr_str);
+			LOG_LEASE (LOGD_DHCP6, "nameserver %s", addr_str);
 		}
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
-		g_string_set_size (str, 0);
 	}
 
 	/* Search domains */
 	num = sd_dhcp6_lease_get_domains (lease, &domains);
 	if (num > 0) {
+		nm_gstring_prepare (&str);
 		for (i = 0; i < num; i++) {
 			nm_ip6_config_add_search (ip6_config, domains[i]);
 			g_string_append_printf (str, "%s%s", str->len ? " " : "", domains[i]);
-			LOG_LEASE (LOGD_DHCP6, "  domain name '%s'", domains[i]);
+			LOG_LEASE (LOGD_DHCP6, "domain name '%s'", domains[i]);
 		}
 		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
-		g_string_set_size (str, 0);
 	}
 
-	g_string_free (str, TRUE);
-
 	return ip6_config;
 }
 
@@ -825,8 +853,9 @@ bound6_handle (NMDhcpSystemd *self)
 
 	_LOGD ("lease available");
 
-	options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
-	ip6_config = lease_to_ip6_config (iface,
+	options = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+	ip6_config = lease_to_ip6_config (nm_dhcp_client_get_multi_idx (NM_DHCP_CLIENT (self)),
+	                                  iface,
 	                                  nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
 	                                  lease,
 	                                  options,
@@ -893,7 +922,7 @@ ip6_start (NMDhcpClient *client,
 	g_return_val_if_fail (duid != NULL, FALSE);
 
 	g_free (priv->lease_file);
-	priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), TRUE);
+	priv->lease_file = get_leasefile_path (AF_INET6, iface, nm_dhcp_client_get_uuid (client));
 	priv->info_only = info_only;
 
 	r = sd_dhcp6_client_new (&priv->client6);
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index e55a21b4..4b2d57b9 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -24,6 +24,8 @@
 #include <unistd.h>
 #include <arpa/inet.h>
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "nm-dhcp-utils.h"
 #include "nm-utils.h"
 #include "NetworkManagerUtils.h"
@@ -36,7 +38,8 @@
 static gboolean
 ip4_process_dhcpcd_rfc3442_routes (const char *iface,
                                    const char *str,
-                                   guint32 priority,
+                                   guint32 route_table,
+                                   guint32 route_metric,
                                    NMIP4Config *ip4_config,
                                    guint32 *gwaddr)
 {
@@ -84,12 +87,13 @@ ip4_process_dhcpcd_rfc3442_routes (const char *iface,
 		} else {
 			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s", *r, rt_cidr, *(r + 1));
 			memset (&route, 0, sizeof (route));
-			route.network = rt_addr;
+			route.network = nm_utils_ip4_address_clear_host_address (rt_addr, rt_cidr);
 			route.plen = rt_cidr;
 			route.gateway = rt_route;
 			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-			route.metric = priority;
-			nm_ip4_config_add_route (ip4_config, &route);
+			route.metric = route_metric;
+			route.table_coerced = nm_platform_route_table_coerce (route_table);
+			nm_ip4_config_add_route (ip4_config, &route, NULL);
 		}
 	}
 
@@ -142,8 +146,7 @@ process_dhclient_rfc3442_route (const char **octets,
 			goto error;
 		}
 		g_free (str_addr);
-		tmp_addr &= nm_utils_ip4_prefix_to_netmask ((guint32) tmp);
-		route->network = tmp_addr;
+		route->network = nm_utils_ip4_address_clear_host_address (tmp_addr, tmp);
 	}
 
 	/* Handle next hop */
@@ -165,7 +168,8 @@ error:
 static gboolean
 ip4_process_dhclient_rfc3442_routes (const char *iface,
                                      const char *str,
-                                     guint32 priority,
+                                     guint32 route_table,
+                                     guint32 route_metric,
                                      NMIP4Config *ip4_config,
                                      guint32 *gwaddr)
 {
@@ -197,8 +201,9 @@ ip4_process_dhclient_rfc3442_routes (const char *iface,
 
 			/* normal route */
 			route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-			route.metric = priority;
-			nm_ip4_config_add_route (ip4_config, &route);
+			route.metric = route_metric;
+			route.table_coerced = nm_platform_route_table_coerce (route_table);
+			nm_ip4_config_add_route (ip4_config, &route, NULL);
 
 			_LOG2I (LOGD_DHCP4, iface, "  classless static route %s/%d gw %s",
 			        nm_utils_inet4_ntop (route.network, addr), route.plen,
@@ -214,7 +219,8 @@ out:
 static gboolean
 ip4_process_classless_routes (const char *iface,
                               GHashTable *options,
-                              guint32 priority,
+                              guint32 route_table,
+                              guint32 route_metric,
                               NMIP4Config *ip4_config,
                               guint32 *gwaddr)
 {
@@ -270,16 +276,17 @@ ip4_process_classless_routes (const char *iface,
 
 	if (strchr (str, '/')) {
 		/* dhcpcd format */
-		return ip4_process_dhcpcd_rfc3442_routes (iface, str, priority, ip4_config, gwaddr);
+		return ip4_process_dhcpcd_rfc3442_routes (iface, str, route_table, route_metric, ip4_config, gwaddr);
 	}
 
-	return ip4_process_dhclient_rfc3442_routes (iface, str, priority, ip4_config, gwaddr);
+	return ip4_process_dhclient_rfc3442_routes (iface, str, route_table, route_metric, ip4_config, gwaddr);
 }
 
 static void
 process_classful_routes (const char *iface,
                          GHashTable *options,
-                         guint32 priority,
+                         guint32 route_table,
+                         guint32 route_metric,
                          NMIP4Config *ip4_config)
 {
 	const char *str;
@@ -316,16 +323,19 @@ process_classful_routes (const char *iface,
 		   The Static Routes option (option 33) does not provide a subnet mask
 		   for each route - it is assumed that the subnet mask is implicit in
 		   whatever network number is specified in each route entry */
-		route.plen = nm_utils_ip4_get_default_prefix (rt_addr);
-		if (rt_addr & ~nm_utils_ip4_prefix_to_netmask (route.plen)) {
+		route.plen = _nm_utils_ip4_get_default_prefix (rt_addr);
+		if (rt_addr & ~_nm_utils_ip4_prefix_to_netmask (route.plen)) {
 			/* RFC 943: target not "this network"; using host routing */
 			route.plen = 32;
 		}
 		route.gateway = rt_route;
 		route.rt_source = NM_IP_CONFIG_SOURCE_DHCP;
-		route.metric = priority;
+		route.metric = route_metric;
+		route.table_coerced = nm_platform_route_table_coerce (route_table);
+
+		route.network = nm_utils_ip4_address_clear_host_address (route.network, route.plen);
 
-		nm_ip4_config_add_route (ip4_config, &route);
+		nm_ip4_config_add_route (ip4_config, &route, NULL);
 		_LOG2I (LOGD_DHCP, iface, "  static route %s",
 		             nm_platform_ip4_route_to_string (&route, NULL, 0));
 	}
@@ -383,22 +393,25 @@ ip4_add_domain_search (gpointer data, gpointer user_data)
 }
 
 NMIP4Config *
-nm_dhcp_utils_ip4_config_from_options (int ifindex,
+nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
+                                       int ifindex,
                                        const char *iface,
                                        GHashTable *options,
-                                       guint32 priority)
+                                       guint32 route_table,
+                                       guint32 route_metric)
 {
 	NMIP4Config *ip4_config = NULL;
 	guint32 tmp_addr;
 	in_addr_t addr;
 	NMPlatformIP4Address address;
 	char *str = NULL;
-	guint32 gwaddr = 0;
+	gboolean gateway_has = FALSE;
+	guint32 gateway = 0;
 	guint8 plen = 0;
 
 	g_return_val_if_fail (options != NULL, NULL);
 
-	ip4_config = nm_ip4_config_new (ifindex);
+	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 	memset (&address, 0, sizeof (address));
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 
@@ -414,7 +427,7 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 		_LOG2I (LOGD_DHCP4, iface, "  plen %d (%s)", plen, str);
 	} else {
 		/* Get default netmask for the IP according to appropriate class. */
-		plen = nm_utils_ip4_get_default_prefix (addr);
+		plen = _nm_utils_ip4_get_default_prefix (addr);
 		_LOG2I (LOGD_DHCP4, iface, "  plen %d (default)", plen);
 	}
 	nm_platform_ip4_address_set_addr (&address, addr, plen);
@@ -422,12 +435,12 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 	/* Routes: if the server returns classless static routes, we MUST ignore
 	 * the 'static_routes' option.
 	 */
-	if (!ip4_process_classless_routes (iface, options, priority, ip4_config, &gwaddr))
-		process_classful_routes (iface, options, priority, ip4_config);
+	if (!ip4_process_classless_routes (iface, options, route_table, route_metric, ip4_config, &gateway))
+		process_classful_routes (iface, options, route_table, route_metric, ip4_config);
 
-	if (gwaddr) {
-		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gwaddr, NULL));
-		nm_ip4_config_set_gateway (ip4_config, gwaddr);
+	if (gateway) {
+		_LOG2I (LOGD_DHCP4, iface, "  gateway %s", nm_utils_inet4_ntop (gateway, NULL));
+		gateway_has = TRUE;
 	} else {
 		/* If the gateway wasn't provided as a classless static route with a
 		 * subnet length of 0, try to find it using the old-style 'routers' option.
@@ -439,9 +452,9 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 
 			for (s = routers; *s; s++) {
 				/* FIXME: how to handle multiple routers? */
-				if (inet_pton (AF_INET, *s, &gwaddr) > 0) {
-					nm_ip4_config_set_gateway (ip4_config, gwaddr);
+				if (inet_pton (AF_INET, *s, &gateway) > 0) {
 					_LOG2I (LOGD_DHCP4, iface, "  gateway %s", *s);
+					gateway_has = TRUE;
 					break;
 				} else
 					_LOG2W (LOGD_DHCP4, iface, "ignoring invalid gateway '%s'", *s);
@@ -450,6 +463,17 @@ nm_dhcp_utils_ip4_config_from_options (int ifindex,
 		}
 	}
 
+	if (gateway_has) {
+		const NMPlatformIP4Route r = {
+			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+			.gateway = gateway,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric = route_metric,
+		};
+
+		nm_ip4_config_add_route (ip4_config, &r, NULL);
+	}
+
 	str = g_hash_table_lookup (options, "dhcp_lease_time");
 	if (str) {
 		address.lifetime = address.preferred = strtoul (str, NULL, 10);
@@ -616,10 +640,10 @@ nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options)
 }
 
 NMIP6Config *
-nm_dhcp_utils_ip6_config_from_options (int ifindex,
+nm_dhcp_utils_ip6_config_from_options (NMDedupMultiIndex *multi_idx,
+                                       int ifindex,
                                        const char *iface,
                                        GHashTable *options,
-                                       guint32 priority,
                                        gboolean info_only)
 {
 	NMIP6Config *ip6_config = NULL;
@@ -633,7 +657,7 @@ nm_dhcp_utils_ip6_config_from_options (int ifindex,
 	address.plen = 128;
 	address.timestamp = nm_utils_get_monotonic_timestamp_s ();
 
-	ip6_config = nm_ip6_config_new (ifindex);
+	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
 
 	str = g_hash_table_lookup (options, "max_life");
 	if (str) {
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 05982b16..32140f48 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -24,15 +24,17 @@
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
 
-NMIP4Config *nm_dhcp_utils_ip4_config_from_options (int ifindex,
+NMIP4Config *nm_dhcp_utils_ip4_config_from_options (struct _NMDedupMultiIndex *multi_idx,
+                                                    int ifindex,
                                                     const char *iface,
                                                     GHashTable *options,
-                                                    guint priority);
+                                                    guint32 route_table,
+                                                    guint32 route_metric);
 
-NMIP6Config *nm_dhcp_utils_ip6_config_from_options (int ifindex,
+NMIP6Config *nm_dhcp_utils_ip6_config_from_options (struct _NMDedupMultiIndex *multi_idx,
+                                                    int ifindex,
                                                     const char *iface,
                                                     GHashTable *options,
-                                                    guint priority,
                                                     gboolean info_only);
 
 NMPlatformIP6Address nm_dhcp_utils_ip6_prefix_from_options (GHashTable *options);
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 5816932b..f2e1f321 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -23,6 +23,9 @@
 #include <string.h>
 #include <unistd.h>
 #include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
 
 #include "NetworkManagerUtils.h"
 #include "dhcp/nm-dhcp-dhclient-utils.h"
@@ -35,10 +38,14 @@
 
 #define DEBUG 1
 
+static const int IFINDEX = 5;
+static const guint32 ROUTE_TABLE = RT_TABLE_MAIN;
+static const guint32 ROUTE_METRIC = 100;
+
 static void
 test_config (const char *orig,
              const char *expected,
-             gboolean ipv6,
+             int addr_family,
              const char *hostname,
              guint32 timeout,
              gboolean use_fqdn,
@@ -57,7 +64,7 @@ test_config (const char *orig,
 	}
 
 	new = nm_dhcp_dhclient_create_config (iface,
-	                                      ipv6,
+	                                      addr_family,
 	                                      client_id,
 	                                      anycast_addr,
 	                                      hostname,
@@ -106,7 +113,7 @@ static const char *orig_missing_expected = \
 static void
 test_orig_missing (void)
 {
-	test_config (NULL, orig_missing_expected, FALSE, NULL, 0, FALSE, NULL, NULL, "eth0", NULL);
+	test_config (NULL, orig_missing_expected, AF_INET, NULL, 0, FALSE, NULL, NULL, "eth0", NULL);
 }
 
 /*****************************************************************************/
@@ -135,7 +142,7 @@ static void
 test_override_client_id (void)
 {
 	test_config (override_client_id_orig, override_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "11:22:33:44:55:66",
 	             NULL,
 	             "eth0",
@@ -164,7 +171,7 @@ static void
 test_quote_client_id (void)
 {
 	test_config (NULL, quote_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "1234",
 	             NULL,
 	             "eth0",
@@ -193,7 +200,7 @@ static void
 test_ascii_client_id (void)
 {
 	test_config (NULL, ascii_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "qb:cd:ef:12:34:56",
 	             NULL,
 	             "eth0",
@@ -222,7 +229,7 @@ static void
 test_hex_single_client_id (void)
 {
 	test_config (NULL, hex_single_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             "ab:cd:e:12:34:56",
 	             NULL,
 	             "eth0",
@@ -259,7 +266,7 @@ test_existing_hex_client_id (void)
 
 	new_client_id = g_bytes_new (bytes, sizeof (bytes));
 	test_config (existing_hex_client_id_orig, existing_hex_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             new_client_id,
 	             "eth0",
@@ -299,7 +306,7 @@ test_existing_ascii_client_id (void)
 	memcpy (buf + 1, EACID, NM_STRLEN (EACID));
 	new_client_id = g_bytes_new (buf, sizeof (buf));
 	test_config (existing_ascii_client_id_orig, existing_ascii_client_id_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             new_client_id,
 	             "eth0",
@@ -328,7 +335,7 @@ static void
 test_fqdn (void)
 {
 	test_config (NULL, fqdn_expected,
-	             FALSE, "foo.bar.com", 0,
+	             AF_INET, "foo.bar.com", 0,
 	             TRUE, NULL,
 	             NULL,
 	             "eth0",
@@ -368,7 +375,7 @@ test_fqdn_options_override (void)
 {
 	test_config (fqdn_options_override_orig,
 	             fqdn_options_override_expected,
-	             FALSE, "example2.com", 0,
+	             AF_INET, "example2.com", 0,
 	             TRUE, NULL,
 	             NULL,
 	             "eth0",
@@ -401,7 +408,7 @@ static void
 test_override_hostname (void)
 {
 	test_config (override_hostname_orig, override_hostname_expected,
-	             FALSE, "blahblah", 0, FALSE,
+	             AF_INET, "blahblah", 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -418,7 +425,6 @@ static const char *override_hostname6_expected = \
 	"# Merged from /path/to/dhclient.conf\n"
 	"\n"
 	"send fqdn.fqdn \"blahblah.local\"; # added by NetworkManager\n"
-	"send fqdn.encoded on;\n"
 	"send fqdn.server-update on;\n"
 	"\n"
 	"also request dhcp6.name-servers;\n"
@@ -430,7 +436,7 @@ static void
 test_override_hostname6 (void)
 {
 	test_config (override_hostname6_orig, override_hostname6_expected,
-	             TRUE, "blahblah.local", 0, TRUE,
+	             AF_INET6, "blahblah.local", 0, TRUE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -442,6 +448,9 @@ test_override_hostname6 (void)
 static const char *nonfqdn_hostname6_expected = \
 	"# Created by NetworkManager\n"
 	"\n"
+	"send fqdn.fqdn \"blahblah\"; # added by NetworkManager\n"
+	"send fqdn.server-update on;\n"
+	"\n"
 	"also request dhcp6.name-servers;\n"
 	"also request dhcp6.domain-search;\n"
 	"also request dhcp6.client-id;\n"
@@ -450,9 +459,9 @@ static const char *nonfqdn_hostname6_expected = \
 static void
 test_nonfqdn_hostname6 (void)
 {
-	/* Non-FQDN hostname can't be used with dhclient */
+	/* Non-FQDN hostname can now be used with dhclient */
 	test_config (NULL, nonfqdn_hostname6_expected,
-	             TRUE, "blahblah", 0, TRUE,
+	             AF_INET6, "blahblah", 0, TRUE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -487,7 +496,7 @@ static void
 test_existing_alsoreq (void)
 {
 	test_config (existing_alsoreq_orig, existing_alsoreq_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -525,7 +534,7 @@ static void
 test_existing_req (void)
 {
 	test_config (existing_req_orig, existing_req_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -564,7 +573,7 @@ static void
 test_existing_multiline_alsoreq (void)
 {
 	test_config (existing_multiline_alsoreq_orig, existing_multiline_alsoreq_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -778,7 +787,7 @@ static void
 test_interface1 (void)
 {
 	test_config (interface1_orig, interface1_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -823,7 +832,7 @@ static void
 test_interface2 (void)
 {
 	test_config (interface2_orig, interface2_expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth1",
@@ -877,7 +886,7 @@ test_config_req_intf (void)
 		"\n";
 
 	test_config (orig, expected,
-	             FALSE, NULL, 0, FALSE,
+	             AF_INET, NULL, 0, FALSE,
 	             NULL,
 	             NULL,
 	             "eth0",
@@ -889,6 +898,7 @@ test_config_req_intf (void)
 static void
 test_read_lease_ip4_config_basic (void)
 {
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
@@ -905,7 +915,7 @@ test_read_lease_ip4_config_basic (void)
 
 	/* Date from before the least expiration */
 	now = g_date_time_new_utc (2013, 11, 1, 19, 55, 32);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
 	g_assert_cmpint (g_slist_length (leases), ==, 2);
 
 	/* IP4Config #1 */
@@ -914,19 +924,19 @@ test_read_lease_ip4_config_basic (void)
 
 	/* Address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
-	g_assert (inet_aton ("192.168.1.180", (struct in_addr *) &expected_addr));
-	addr = nm_ip4_config_get_address (config, 0);
+	expected_addr = nmtst_inet4_from_string ("192.168.1.180");
+	addr = _nmtst_ip4_config_get_address (config, 0);
 	g_assert_cmpint (addr->address, ==, expected_addr);
 	g_assert_cmpint (addr->peer_address, ==, expected_addr);
 	g_assert_cmpint (addr->plen, ==, 24);
 
 	/* Gateway */
-	g_assert (inet_aton ("192.168.1.1", (struct in_addr *) &expected_addr));
-	g_assert_cmpint (nm_ip4_config_get_gateway (config), ==, expected_addr);
+	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
+	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
 
 	/* DNS */
 	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 1);
-	g_assert (inet_aton ("192.168.1.1", (struct in_addr *) &expected_addr));
+	expected_addr = nmtst_inet4_from_string ("192.168.1.1");
 	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
 
 	g_assert_cmpint (nm_ip4_config_get_num_domains (config), ==, 0);
@@ -937,21 +947,21 @@ test_read_lease_ip4_config_basic (void)
 
 	/* Address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (config), ==, 1);
-	g_assert (inet_aton ("10.77.52.141", (struct in_addr *) &expected_addr));
-	addr = nm_ip4_config_get_address (config, 0);
+	expected_addr = nmtst_inet4_from_string ("10.77.52.141");
+	addr = _nmtst_ip4_config_get_address (config, 0);
 	g_assert_cmpint (addr->address, ==, expected_addr);
 	g_assert_cmpint (addr->peer_address, ==, expected_addr);
 	g_assert_cmpint (addr->plen, ==, 8);
 
 	/* Gateway */
-	g_assert (inet_aton ("10.77.52.254", (struct in_addr *) &expected_addr));
-	g_assert_cmpint (nm_ip4_config_get_gateway (config), ==, expected_addr);
+	expected_addr = nmtst_inet4_from_string ("10.77.52.254");
+	g_assert_cmpint (nmtst_ip4_config_get_gateway (config), ==, expected_addr);
 
 	/* DNS */
 	g_assert_cmpint (nm_ip4_config_get_num_nameservers (config), ==, 2);
-	g_assert (inet_aton ("8.8.8.8", (struct in_addr *) &expected_addr));
+	expected_addr = nmtst_inet4_from_string ("8.8.8.8");
 	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 0), ==, expected_addr);
-	g_assert (inet_aton ("8.8.4.4", (struct in_addr *) &expected_addr));
+	expected_addr = nmtst_inet4_from_string ("8.8.4.4");
 	g_assert_cmpint (nm_ip4_config_get_nameserver (config, 1), ==, expected_addr);
 
 	/* Domains */
@@ -966,6 +976,7 @@ test_read_lease_ip4_config_basic (void)
 static void
 test_read_lease_ip4_config_expired (void)
 {
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
@@ -979,7 +990,7 @@ test_read_lease_ip4_config_expired (void)
 
 	/* Date from *after* the lease expiration */
 	now = g_date_time_new_utc (2013, 12, 1, 19, 55, 32);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
 	g_assert (leases == NULL);
 
 	g_date_time_unref (now);
@@ -989,6 +1000,7 @@ test_read_lease_ip4_config_expired (void)
 static void
 test_read_lease_ip4_config_expect_failure (gconstpointer user_data)
 {
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
 	GError *error = NULL;
 	char *contents = NULL;
 	gboolean success;
@@ -1001,7 +1013,7 @@ test_read_lease_ip4_config_expect_failure (gconstpointer user_data)
 
 	/* Date from before the least expiration */
 	now = g_date_time_new_utc (2013, 11, 1, 1, 1, 1);
-	leases = nm_dhcp_dhclient_read_lease_ip_configs ("wlan0", -1, contents, FALSE, now);
+	leases = nm_dhcp_dhclient_read_lease_ip_configs (multi_idx, AF_INET, "wlan0", IFINDEX, ROUTE_TABLE, ROUTE_METRIC, contents, now);
 	g_assert (leases == NULL);
 
 	g_date_time_unref (now);
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index ffd63493..72f31191 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -22,7 +22,9 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <string.h>
+#include <linux/rtnetlink.h>
 
+#include "nm-utils/nm-dedup-multi.h"
 #include "nm-utils.h"
 
 #include "dhcp/nm-dhcp-utils.h"
@@ -30,6 +32,20 @@
 
 #include "nm-test-utils-core.h"
 
+static NMIP4Config *
+_ip4_config_from_options (int ifindex,
+                          const char *iface,
+                          GHashTable *options,
+                          guint32 route_metric)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+	NMIP4Config *config;
+
+	config = nm_dhcp_utils_ip4_config_from_options (multi_idx, ifindex, iface, options, RT_TABLE_MAIN, route_metric);
+	g_assert (config);
+	return config;
+}
+
 typedef struct {
 	const char *name;
 	const char *value;
@@ -41,7 +57,7 @@ fill_table (const Option *test_options, GHashTable *table)
 	const Option *opt;
 
 	if (!table)
-		table = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+		table = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
 	for (opt = test_options; opt->name; opt++)
 		g_hash_table_insert (table, (gpointer) opt->name, (gpointer) opt->value);
 	return table;
@@ -86,12 +102,11 @@ test_generic_options (void)
 	const char *expected_route2_gw = "10.1.1.1";
 
 	options = fill_table (generic_options, NULL);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (inet_pton (AF_INET, expected_addr, &tmp) > 0);
 	g_assert (address->address == tmp);
 	g_assert (address->peer_address == tmp);
@@ -99,7 +114,7 @@ test_generic_options (void)
 
 	/* Gateway */
 	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
-	g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp);
+	g_assert (nmtst_ip4_config_get_gateway (ip4_config) == tmp);
 
 	g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 0);
 
@@ -118,10 +133,10 @@ test_generic_options (void)
 	g_assert (nm_ip4_config_get_nameserver (ip4_config, 1) == tmp);
 
 	/* Routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 
 	/* Route #1 */
-	route = nm_ip4_config_get_route (ip4_config, 0);
+	route = _nmtst_ip4_config_get_route (ip4_config, 0);
 	g_assert (inet_pton (AF_INET, expected_route1_dest, &tmp) > 0);
 	g_assert (route->network == tmp);
 	g_assert (inet_pton (AF_INET, expected_route1_gw, &tmp) > 0);
@@ -130,14 +145,18 @@ test_generic_options (void)
 	g_assert_cmpint (route->metric, ==, 0);
 
 	/* Route #2 */
-	route = nm_ip4_config_get_route (ip4_config, 1);
-	g_assert (inet_pton (AF_INET, expected_route2_dest, &tmp) > 0);
-	g_assert (route->network == tmp);
-	g_assert (inet_pton (AF_INET, expected_route2_gw, &tmp) > 0);
-	g_assert (route->gateway == tmp);
+	route = _nmtst_ip4_config_get_route (ip4_config, 1);
+	g_assert (route->network == nmtst_inet4_from_string (expected_route2_dest));
+	g_assert (route->gateway == nmtst_inet4_from_string (expected_route2_gw));
 	g_assert_cmpint (route->plen, ==, 32);
 	g_assert_cmpint (route->metric, ==, 0);
 
+	route = _nmtst_ip4_config_get_route (ip4_config, 2);
+	g_assert (route->network == nmtst_inet4_from_string ("0.0.0.0"));
+	g_assert (route->gateway == nmtst_inet4_from_string ("192.168.1.1"));
+	g_assert_cmpint (route->plen, ==, 0);
+	g_assert_cmpint (route->metric, ==, 0);
+
 	g_hash_table_destroy (options);
 }
 
@@ -157,12 +176,11 @@ test_wins_options (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 address */
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (address);
 	g_assert_cmpint (nm_ip4_config_get_num_wins (ip4_config), ==, 2);
 	g_assert (inet_pton (AF_INET, expected_wins1, &tmp) > 0);
@@ -184,16 +202,14 @@ test_vendor_option_metered (void)
 	};
 
 	options = fill_table (generic_options, NULL);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_assert (nm_ip4_config_get_metered (ip4_config) == FALSE);
 	g_hash_table_destroy (options);
 	g_clear_object (&ip4_config);
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_assert (nm_ip4_config_get_metered (ip4_config) == TRUE);
 	g_hash_table_destroy (options);
 }
@@ -210,7 +226,7 @@ ip4_test_route (NMIP4Config *ip4_config,
 
 	g_assert (expected_prefix <= 32);
 
-	route = nm_ip4_config_get_route (ip4_config, route_num);
+	route = _nmtst_ip4_config_get_route (ip4_config, route_num);
 	g_assert (inet_pton (AF_INET, expected_dest, &tmp) > 0);
 	g_assert (route->network == tmp);
 	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
@@ -226,7 +242,7 @@ ip4_test_gateway (NMIP4Config *ip4_config, const char *expected_gw)
 
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
 	g_assert (inet_pton (AF_INET, expected_gw, &tmp) > 0);
-	g_assert (nm_ip4_config_get_gateway (ip4_config) == tmp);
+	g_assert (nmtst_ip4_config_get_gateway (ip4_config) == tmp);
 }
 
 static void
@@ -246,13 +262,13 @@ test_classless_static_routes_1 (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -274,13 +290,13 @@ test_classless_static_routes_2 (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 8);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -303,13 +319,13 @@ test_fedora_dhclient_classless_static_routes (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 25);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 7);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", expected_route1_gw, 0);
 
 	/* Gateway */
 	ip4_test_gateway (ip4_config, expected_gateway);
@@ -335,13 +351,13 @@ test_dhclient_invalid_classless_routes_1 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -366,16 +382,16 @@ test_dhcpcd_invalid_classless_routes_1 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* Test falling back to old-style static routes if the classless static
 	 * routes are invalid.
 	 */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -399,16 +415,16 @@ test_dhclient_invalid_classless_routes_2 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* Test falling back to old-style static routes if the classless static
 	 * routes are invalid.
 	 */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -432,8 +448,7 @@ test_dhcpcd_invalid_classless_routes_2 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* Test falling back to old-style static routes if the classless static
@@ -441,9 +456,10 @@ test_dhcpcd_invalid_classless_routes_2 (void)
 	 */
 
 	/* Routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 3);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 32);
 	ip4_test_route (ip4_config, 1, expected_route2_dest, expected_route2_gw, 32);
+	ip4_test_route (ip4_config, 2, "0.0.0.0", "192.168.1.1", 0);
 
 	g_hash_table_destroy (options);
 }
@@ -465,13 +481,13 @@ test_dhclient_invalid_classless_routes_3 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*ignoring invalid classless static routes*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -493,13 +509,13 @@ test_dhcpcd_invalid_classless_routes_3 (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*DHCP provided invalid classless static route*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", expected_route1_gw, 0);
 
 	g_hash_table_destroy (options);
 }
@@ -519,12 +535,12 @@ test_dhclient_gw_in_classless_routes (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", "192.2.3.4", 0);
 
 	/* Gateway */
 	ip4_test_gateway (ip4_config, expected_gateway);
@@ -547,12 +563,12 @@ test_dhcpcd_gw_in_classless_routes (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* IP4 routes */
-	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 1);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (ip4_config), ==, 2);
 	ip4_test_route (ip4_config, 0, expected_route1_dest, expected_route1_gw, 24);
+	ip4_test_route (ip4_config, 1, "0.0.0.0", "192.2.3.4", 0);
 
 	/* Gateway */
 	ip4_test_gateway (ip4_config, expected_gateway);
@@ -575,8 +591,7 @@ test_escaped_domain_searches (void)
 
 	options = fill_table (generic_options, NULL);
 	options = fill_table (data, options);
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	/* domain searches */
 	g_assert_cmpint (nm_ip4_config_get_num_searches (ip4_config), ==, 3);
@@ -602,8 +617,7 @@ test_invalid_escaped_domain_searches (void)
 
 	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_MESSAGE,
 	                       "*invalid domain search*");
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 	g_test_assert_expected_messages ();
 
 	/* domain searches */
@@ -623,11 +637,10 @@ test_ip4_missing_prefix (const char *ip, guint32 expected_prefix)
 	g_hash_table_insert (options, "ip_address", (gpointer) ip);
 	g_hash_table_remove (options, "subnet_mask");
 
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (address);
 	g_assert_cmpint (address->plen, ==, expected_prefix);
 
@@ -668,11 +681,10 @@ test_ip4_prefix_classless (void)
 	g_hash_table_insert (options, "ip_address", "172.16.54.22");
 	g_hash_table_insert (options, "subnet_mask", "255.255.252.0");
 
-	ip4_config = nm_dhcp_utils_ip4_config_from_options (1, "eth0", options, 0);
-	g_assert (ip4_config);
+	ip4_config = _ip4_config_from_options (1, "eth0", options, 0);
 
 	g_assert_cmpint (nm_ip4_config_get_num_addresses (ip4_config), ==, 1);
-	address = nm_ip4_config_get_address (ip4_config, 0);
+	address = _nmtst_ip4_config_get_address (ip4_config, 0);
 	g_assert (address);
 	g_assert_cmpint (address->plen, ==, 22);
 
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index a6204ae4..3ec1fd2d 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -80,22 +80,20 @@ get_ip4_rdns_domains (NMIP4Config *ip4)
 {
 	char **strv;
 	GPtrArray *domains = NULL;
-	int i;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
 
 	g_return_val_if_fail (ip4 != NULL, NULL);
 
 	domains = g_ptr_array_sized_new (5);
 
-	for (i = 0; i < nm_ip4_config_get_num_addresses (ip4); i++) {
-		const NMPlatformIP4Address *address = nm_ip4_config_get_address (ip4, i);
-
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address)
 		nm_utils_get_reverse_dns_domains_ip4 (address->address, address->plen, domains);
-	}
-
-	for (i = 0; i < nm_ip4_config_get_num_routes (ip4); i++) {
-		const NMPlatformIP4Route *route = nm_ip4_config_get_route (ip4, i);
 
-		nm_utils_get_reverse_dns_domains_ip4 (route->network, route->plen, domains);
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
+		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+			nm_utils_get_reverse_dns_domains_ip4 (route->network, route->plen, domains);
 	}
 
 	/* Terminating NULL so we can use g_strfreev() to free it */
@@ -112,22 +110,20 @@ get_ip6_rdns_domains (NMIP6Config *ip6)
 {
 	char **strv;
 	GPtrArray *domains = NULL;
-	int i;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *address;
+	const NMPlatformIP6Route *route;
 
 	g_return_val_if_fail (ip6 != NULL, NULL);
 
 	domains = g_ptr_array_sized_new (5);
 
-	for (i = 0; i < nm_ip6_config_get_num_addresses (ip6); i++) {
-		const NMPlatformIP6Address *address = nm_ip6_config_get_address (ip6, i);
-
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address)
 		nm_utils_get_reverse_dns_domains_ip6 (&address->address, address->plen, domains);
-	}
-
-	for (i = 0; i < nm_ip6_config_get_num_routes (ip6); i++) {
-		const NMPlatformIP6Route *route = nm_ip6_config_get_route (ip6, i);
 
-		nm_utils_get_reverse_dns_domains_ip6 (&route->network, route->plen, domains);
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
+		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+			nm_utils_get_reverse_dns_domains_ip6 (&route->network, route->plen, domains);
 	}
 
 	/* Terminating NULL so we can use g_strfreev() to free it */
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 952468e3..d5392b7d 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -497,7 +497,7 @@ dispatch_netconfig (NMDnsManager *self,
 		g_free (str);
 	}
 
-	close (fd);
+	nm_close (fd);
 
 	/* Wait until the process exits */
 	if (!nm_utils_kill_child_sync (pid, 0, LOGD_DNS, "netconfig", &status, 1000, 0)) {
@@ -1167,9 +1167,16 @@ update_dns (NMDnsManager *self,
 	 * but only uses the local caching nameserver.
 	 */
 	if (caching) {
+		const char *lladdr = "127.0.0.1";
+
+		if (NM_IS_DNS_SYSTEMD_RESOLVED (priv->plugin)) {
+			/* systemd-resolved uses a different link-local address */
+			lladdr = "127.0.0.53";
+		}
+
 		g_strfreev (nameservers);
-		nameservers = g_new0 (char*, 2);
-		nameservers[0] = g_strdup ("127.0.0.1");
+		nameservers = g_new0 (char *, 2);
+		nameservers[0] = g_strdup (lladdr);
 	}
 
 	if (update) {
@@ -1427,6 +1434,7 @@ nm_dns_manager_set_initial_hostname (NMDnsManager *self,
 {
 	NMDnsManagerPrivate *priv = NM_DNS_MANAGER_GET_PRIVATE (self);
 
+	g_free (priv->hostname);
 	priv->hostname = g_strdup (hostname);
 }
 
@@ -1625,7 +1633,7 @@ _check_resconf_immutable (NMDnsManagerResolvConfManager rc_manager)
 		if (fd != -1) {
 			if (ioctl (fd, FS_IOC_GETFLAGS, &flags) != -1)
 				immutable = NM_FLAGS_HAS (flags, FS_IMMUTABLE_FL);
-			close (fd);
+			nm_close (fd);
 		}
 		return immutable ? NM_DNS_MANAGER_RESOLV_CONF_MAN_IMMUTABLE : rc_manager;
 	}
@@ -1634,32 +1642,68 @@ _check_resconf_immutable (NMDnsManagerResolvConfManager rc_manager)
 static gboolean
 _resolvconf_resolved_managed (void)
 {
-	static const char *const resolved_paths[] = {
+	static const char *const RESOLVED_PATHS[] = {
 		"/run/systemd/resolve/resolv.conf",
 		"/lib/systemd/resolv.conf",
 		"/usr/lib/systemd/resolv.conf",
 	};
-	GFile *f;
-	GFileInfo *info;
-	gboolean ret = FALSE;
+	struct stat st, st_test;
+	guint i;
 
-	f = g_file_new_for_path (_PATH_RESCONF);
-	info = g_file_query_info (f,
-	                          G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK","\
-	                          G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
-	                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-	                          NULL, NULL);
+	if (lstat (_PATH_RESCONF, &st) != 0)
+		return FALSE;
 
-	if (info && g_file_info_get_is_symlink (info)) {
-		ret = nm_utils_strv_find_first ((gchar **) resolved_paths,
-		                                G_N_ELEMENTS (resolved_paths),
-		                                g_file_info_get_symlink_target (info)) >= 0;
-	}
+	if (S_ISLNK (st.st_mode)) {
+		gs_free char *full_path = NULL;
+		nm_auto_free char *real_path = NULL;
+
+		/* see if resolv.conf is a symlink with a target that is
+		 * exactly like one of the candidates.
+		 *
+		 * This check will work for symlinks, even if the target
+		 * does not exist and realpath() cannot resolve anything.
+		 *
+		 * We want to handle that, because systemd-resolved might not
+		 * have started yet. */
+		full_path = g_file_read_link (_PATH_RESCONF, NULL);
+		if (nm_utils_strv_find_first ((char **) RESOLVED_PATHS,
+		                              G_N_ELEMENTS (RESOLVED_PATHS),
+		                              full_path) >= 0)
+			return TRUE;
+
+		/* see if resolv.conf is a symlink that resolves exactly one
+		 * of the candidate paths.
+		 *
+		 * This check will work for symlinks that can be resolved
+		 * to a realpath, but the actual file might not exist.
+		 *
+		 * We want to handle that, because systemd-resolved might not
+		 * have started yet. */
+		real_path = realpath (_PATH_RESCONF, NULL);
+		if (nm_utils_strv_find_first ((char **) RESOLVED_PATHS,
+		                              G_N_ELEMENTS (RESOLVED_PATHS),
+		                              real_path) >= 0)
+			return TRUE;
 
-	g_clear_object(&info);
-	g_clear_object(&f);
+		/* fall-through and resolve the symlink, to check the file
+		 * it points to (below).
+		 *
+		 * This check is the most reliable, but it only works if
+		 * systemd-resolved already started and created the file. */
+		if (stat (_PATH_RESCONF, &st) != 0)
+			return FALSE;
+	}
+
+	/* see if resolv.conf resolves to one of the candidate
+	 * paths (or whether it is hard-linked). */
+	for (i = 0; i < G_N_ELEMENTS (RESOLVED_PATHS); i++) {
+		if (   stat (RESOLVED_PATHS[i], &st_test) == 0
+		    && st.st_dev == st_test.st_dev
+		    && st.st_ino == st_test.st_ino)
+			return TRUE;
+	}
 
-	return ret;
+	return FALSE;
 }
 
 static void
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index fce1fef1..315f2c4d 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -136,81 +136,75 @@ add_interface_configuration (NMDnsSystemdResolved *self,
 }
 
 static void
-add_domain (GVariantBuilder *domains,
-            const char *domain,
-            gboolean never_default)
-{
-	/* If this link is never the default (e.g. only used for resources on this
-	 * network) add a routing domain. */
-	g_variant_builder_add (domains, "(sb)", domain, never_default);
-}
-
-static void
-update_add_ip6_config (NMDnsSystemdResolved *self,
-                       GVariantBuilder *dns,
-                       GVariantBuilder *domains,
-                       const NMIP6Config *config)
+update_add_ip_config (NMDnsSystemdResolved *self,
+                      GVariantBuilder *dns,
+                      GVariantBuilder *domains,
+                      gpointer config)
 {
+	int addr_family;
+	gsize addr_size;
 	guint i, n;
+	gboolean route_only;
 
-	n = nm_ip6_config_get_num_nameservers (config);
-	for (i = 0 ; i < n; i++) {
-		const struct in6_addr *ip;
-
-		g_variant_builder_open (dns, G_VARIANT_TYPE ("(iay)"));
-		g_variant_builder_add (dns, "i", AF_INET6);
-		ip = nm_ip6_config_get_nameserver (config, i),
-
-		g_variant_builder_add_value (dns, g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, ip, 16, 1));
-		g_variant_builder_close (dns);
-	}
-
-	n = nm_ip6_config_get_num_searches (config);
-	if (n > 0) {
-		for (i = 0; i < n; i++) {
-			add_domain (domains, nm_ip6_config_get_search (config, i),
-			            nm_ip6_config_get_never_default (config));
-		}
-	} else {
-		n = nm_ip6_config_get_num_domains (config);
-		for (i = 0; i < n; i++) {
-			add_domain (domains, nm_ip6_config_get_domain (config, i),
-			            nm_ip6_config_get_never_default (config));
-		}
-	}
-}
+	if (NM_IS_IP4_CONFIG (config))
+		addr_family = AF_INET;
+	else if (NM_IS_IP6_CONFIG (config))
+		addr_family = AF_INET6;
+	else
+		g_return_if_reached ();
 
-static void
-update_add_ip4_config (NMDnsSystemdResolved *self,
-                       GVariantBuilder *dns,
-                       GVariantBuilder *domains,
-                       const NMIP4Config *config)
-{
-	guint i, n;
+	addr_size = nm_utils_addr_family_to_size (addr_family);
 
-	n = nm_ip4_config_get_num_nameservers (config);
+	n =   addr_family == AF_INET
+	    ? nm_ip4_config_get_num_nameservers (config)
+	    : nm_ip6_config_get_num_nameservers (config);
 	for (i = 0 ; i < n; i++) {
-		guint32 ns;
+		in_addr_t ns4;
+		gconstpointer ns;
 
-		g_variant_builder_open (dns, G_VARIANT_TYPE ("(iay)"));
-		g_variant_builder_add (dns, "i", AF_INET);
-		ns = nm_ip4_config_get_nameserver (config, i),
+		if (addr_family == AF_INET) {
+			ns4 = nm_ip4_config_get_nameserver (config, i);
+			ns = &ns4;
+		} else
+			ns = nm_ip6_config_get_nameserver (config, i);
 
-		g_variant_builder_add_value (dns, g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, &ns, 4, 1));
+		g_variant_builder_open (dns, G_VARIANT_TYPE ("(iay)"));
+		g_variant_builder_add (dns, "i", addr_family);
+		g_variant_builder_add_value (dns,
+		                             g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+		                                                        ns,
+		                                                        addr_size,
+		                                                        1));
 		g_variant_builder_close (dns);
 	}
 
-	n = nm_ip4_config_get_num_searches (config);
+	/* If this link is never the default (e.g. only used for resources on this
+	 * network) add a routing domain. */
+	route_only =   addr_family == AF_INET
+	             ? !nm_ip4_config_best_default_route_get (config)
+	             : !nm_ip6_config_best_default_route_get (config);
+
+	n =   addr_family == AF_INET
+	    ? nm_ip4_config_get_num_searches (config)
+	    : nm_ip6_config_get_num_searches (config);
 	if (n  > 0) {
 		for (i = 0; i < n; i++) {
-			add_domain (domains, nm_ip4_config_get_search (config, i),
-			            nm_ip4_config_get_never_default (config));
+			g_variant_builder_add (domains, "(sb)",
+			                       addr_family == AF_INET
+			                         ? nm_ip4_config_get_search (config, i)
+			                         : nm_ip6_config_get_search (config, i),
+			                       route_only);
 		}
 	} else {
-		n = nm_ip4_config_get_num_domains (config);
+		n =   addr_family == AF_INET
+		    ? nm_ip4_config_get_num_domains (config)
+		    : nm_ip6_config_get_num_domains (config);
 		for (i = 0; i < n; i++) {
-			add_domain (domains, nm_ip4_config_get_domain (config, i),
-			            nm_ip4_config_get_never_default (config));
+			g_variant_builder_add (domains, "(sb)",
+			                       addr_family == AF_INET
+			                         ? nm_ip4_config_get_domain (config, i)
+			                         : nm_ip6_config_get_domain (config, i),
+			                       route_only);
 		}
 	}
 }
@@ -243,14 +237,9 @@ prepare_one_interface (NMDnsSystemdResolved *self, InterfaceConfig *ic)
 	g_variant_builder_add (&domains, "i", ic->ifindex);
 	g_variant_builder_open (&domains, G_VARIANT_TYPE ("a(sb)"));
 
-	for (l = ic->configs ; l != NULL ; l = g_list_next (l)) {
-		if (NM_IS_IP4_CONFIG (l->data))
-			update_add_ip4_config (self, &dns, &domains, l->data);
-		else if (NM_IS_IP6_CONFIG (l->data))
-			update_add_ip6_config (self, &dns, &domains, l->data);
-		else
-			g_assert_not_reached ();
-	}
+	for (l = ic->configs; l; l = l->next)
+		update_add_ip_config (self, &dns, &domains, l->data);
+
 	g_variant_builder_close (&dns);
 	g_variant_builder_close (&domains);
 
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index f76c983d..4ac1e7c3 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -162,7 +162,7 @@ create_dm_cmd_line (const char *iface,
 	const NMPlatformIP4Address *listen_address;
 	guint i, n;
 
-	listen_address = nm_ip4_config_get_address (ip4_config, 0);
+	listen_address = nm_ip4_config_get_first_address (ip4_config);
 	g_return_val_if_fail (listen_address, NULL);
 
 	dm_binary = nm_utils_find_helper ("dnsmasq", DNSMASQ_PATH, error);
@@ -221,7 +221,7 @@ create_dm_cmd_line (const char *iface,
 	nm_cmd_line_add_string (cmd, s->str);
 	g_string_truncate (s, 0);
 
-	if (!nm_ip4_config_get_never_default (ip4_config)) {
+	if (nm_ip4_config_best_default_route_get (ip4_config)) {
 		g_string_append (s, "--dhcp-option=option:router,");
 		g_string_append (s, localaddr);
 		nm_cmd_line_add_string (cmd, s->str);
diff --git a/src/dnsmasq/nm-dnsmasq-utils.c b/src/dnsmasq/nm-dnsmasq-utils.c
index e4f4324b..382b3aeb 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.c
+++ b/src/dnsmasq/nm-dnsmasq-utils.c
@@ -35,11 +35,12 @@ nm_dnsmasq_utils_get_range (const NMPlatformIP4Address *addr,
 {
 	guint32 host = addr->address;
 	guint8 prefix = addr->plen;
-	guint32 netmask = nm_utils_ip4_prefix_to_netmask (prefix);
-	guint32 first, last, reserved;
+	guint32 netmask;
+	guint32 first, last, mid, reserved;
+	const guint32 NUM = 256;
 
-	g_return_val_if_fail (out_first != NULL, FALSE);
-	g_return_val_if_fail (out_last != NULL, FALSE);
+	g_return_val_if_fail (out_first, FALSE);
+	g_return_val_if_fail (out_last, FALSE);
 
 	if (prefix > 30) {
 		if (out_error_desc)
@@ -47,29 +48,69 @@ nm_dnsmasq_utils_get_range (const NMPlatformIP4Address *addr,
 		return FALSE;
 	}
 
-	/* Find the first available address *after* the local machine's IP */
-	first = (host & netmask) + htonl (1);
+	if (prefix < 24) {
+		/* if the subnet is larger then /24, we partition it and treat it
+		 * like it would be a /24.
+		 *
+		 * Hence, the resulting range will always be between x.x.x.1/24
+		 * and x.x.x.254/24, with x.x.x.0 being the network address of the
+		 * host.
+		 *
+		 * In this case, only a /24 portion of the subnet is used.
+		 * No particular reason for that, but it's unlikely that a user
+		 * would use NetworkManager's shared method when having hundered
+		 * of DHCP clients. So, restrict the range to the same /24 in
+		 * which the host address lies.
+		 */
+		prefix = 24;
+	}
+
+	netmask = _nm_utils_ip4_prefix_to_netmask (prefix);
+
+	/* treat addresses in host-order from here on. */
+	netmask = ntohl (netmask);
+	host = ntohl (host);
 
-	/* Shortcut: allow a max of 253 addresses; the - htonl(1) here is to assure
-	 * that we don't set 'last' to the broadcast address of the network. */
-	if (prefix < 24)
-		last = (host | ~nm_utils_ip4_prefix_to_netmask (24)) - htonl (1);
-	else
-		last = (host | ~netmask) - htonl(1);
+	/* if host is the network or broadcast address, coerce it to
+	 * one above or below. Usually, we wouldn't expect the user
+	 * to pick such an address. */
+	if (host == (host & netmask))
+		host++;
+	else if (host == (host | ~netmask))
+		host--;
 
-	/* Figure out which range (either above the host address or below it)
-	 * has more addresses.  Reserve some addresses for static IPs.
+	/* Exclude the network and broadcast address. */
+	first = (host &  netmask) + 1;
+	last =  (host | ~netmask) - 1;
+
+	/* Depending on whether host is above or below the middle of
+	 * the subnet, the larger part if handed out.
+	 *
+	 * If the host is in the lower half, the range starts
+	 * at the lower end with the host (plus reserved), until the
+	 * broadcast address
+	 *
+	 * If the host is in the upper half, the range starts above
+	 * the network-address and goes up until the host (except reserved).
+	 *
+	 * reserved is up to 8 addresses, 10% of the determined range.
 	 */
-	if (ntohl (host) - ntohl (first) > ntohl (last) - ntohl (host)) {
-		/* Range below the host's IP address */
-		reserved = (guint32) ((ntohl (host) - ntohl (first)) / 10);
-		last = host - htonl (MIN (reserved, 8)) - htonl (1);
+	mid =   (host & netmask) | (((first + last) / 2) & ~netmask);
+	if (host > mid) {
+		/* use lower range */
+		reserved = NM_MIN (((host - first) / 10), 8);
+		last = host - 1 - reserved;
+		first = NM_MAX (first, last > NUM ? last - NUM : 0);
 	} else {
-		/* Range above host's IP address */
-		reserved = (guint32) ((ntohl (last) - ntohl (host)) / 10);
-		first = host + htonl (MIN (reserved, 8)) + htonl (1);
+		/* use upper range */
+		reserved = NM_MIN (((last - host) / 10), 8);
+		first = host + 1 + reserved;
+		last = NM_MIN (last, first < 0xFFFFFFFF - NUM ? first + NUM : 0xFFFFFFFF);
 	}
 
+	first = htonl (first);
+	last = htonl (last);
+
 	nm_utils_inet4_ntop (first, out_first);
 	nm_utils_inet4_ntop (last, out_last);
 
diff --git a/src/dnsmasq/tests/test-dnsmasq-utils.c b/src/dnsmasq/tests/test-dnsmasq-utils.c
index 00ff6610..b311ccb4 100644
--- a/src/dnsmasq/tests/test-dnsmasq-utils.c
+++ b/src/dnsmasq/tests/test-dnsmasq-utils.c
@@ -29,58 +29,138 @@
 static void
 test_address_ranges (void)
 {
-	NMPlatformIP4Address addr;
-	char first[INET_ADDRSTRLEN];
-	char last[INET_ADDRSTRLEN];
-	char *error_desc = NULL;
-
-	addr = *nmtst_platform_ip4_address ("192.168.0.1", NULL, 24);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
-	g_assert (error_desc == NULL);
-	g_assert_cmpstr (first, ==, "192.168.0.10");
-	g_assert_cmpstr (last, ==, "192.168.0.254");
-
-	addr = *nmtst_platform_ip4_address ("192.168.0.99", NULL, 24);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
-	g_assert (error_desc == NULL);
-	g_assert_cmpstr (first, ==, "192.168.0.108");
-	g_assert_cmpstr (last, ==, "192.168.0.254");
-
-	addr = *nmtst_platform_ip4_address ("192.168.0.254", NULL, 24);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
-	g_assert (error_desc == NULL);
-	g_assert_cmpstr (first, ==, "192.168.0.1");
-	g_assert_cmpstr (last, ==, "192.168.0.245");
-
-	/* Smaller networks */
-	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 30);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
-	g_assert (error_desc == NULL);
-	g_assert_cmpstr (first, ==, "1.2.3.2");
-	g_assert_cmpstr (last, ==, "1.2.3.2");
-
-	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 29);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
-	g_assert (error_desc == NULL);
-	g_assert_cmpstr (first, ==, "1.2.3.2");
-	g_assert_cmpstr (last, ==, "1.2.3.6");
-
-	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 28);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
-	g_assert (error_desc == NULL);
-	g_assert_cmpstr (first, ==, "1.2.3.3");
-	g_assert_cmpstr (last, ==, "1.2.3.14");
-
-	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 26);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc));
-	g_assert (error_desc == NULL);
-	g_assert_cmpstr (first, ==, "1.2.3.8");
-	g_assert_cmpstr (last, ==, "1.2.3.62");
-
-	addr = *nmtst_platform_ip4_address ("1.2.3.1", NULL, 31);
-	g_assert (nm_dnsmasq_utils_get_range (&addr, first, last, &error_desc) == FALSE);
-	g_assert (error_desc);
-	g_free (error_desc);
+#define _test_address_range(addr, plen, expected_first, expected_last) \
+	G_STMT_START { \
+		char *_error_desc = NULL; \
+		char _first[INET_ADDRSTRLEN]; \
+		char _last[INET_ADDRSTRLEN]; \
+		\
+		if (!nm_dnsmasq_utils_get_range (nmtst_platform_ip4_address ((addr""), NULL, (plen)), \
+		                                 _first, _last, &_error_desc)) \
+			g_assert_not_reached (); \
+		g_assert (!_error_desc); \
+		g_assert_cmpstr (_first, ==, (expected_first"")); \
+		g_assert_cmpstr (_last, ==, (expected_last"")); \
+		g_assert_cmpint ((ntohl (nmtst_inet4_from_string (_last)) - ntohl (nmtst_inet4_from_string (_first))), <=, 244); \
+	} G_STMT_END
+
+#define _test_address_range_fail(addr, plen) \
+	G_STMT_START { \
+		char *_error_desc = NULL; \
+		char _first[INET_ADDRSTRLEN]; \
+		char _last[INET_ADDRSTRLEN]; \
+		\
+		if (nm_dnsmasq_utils_get_range (nmtst_platform_ip4_address ((addr""), NULL, (plen)), \
+		                                _first, _last, &_error_desc)) \
+			g_assert_not_reached (); \
+		g_assert (_error_desc); \
+		g_free (_error_desc); \
+	} G_STMT_END
+
+	_test_address_range_fail ("1.2.3.1", 31);
+
+	_test_address_range ("0.0.0.0", 30, "0.0.0.2",  "0.0.0.2");
+	_test_address_range ("0.0.0.1", 30, "0.0.0.2",  "0.0.0.2");
+	_test_address_range ("0.0.0.2", 30, "0.0.0.1",  "0.0.0.1");
+	_test_address_range ("0.0.0.3", 30, "0.0.0.1",  "0.0.0.1");
+	_test_address_range ("1.2.3.0", 30, "1.2.3.2",  "1.2.3.2");
+	_test_address_range ("1.2.3.1", 30, "1.2.3.2",  "1.2.3.2");
+	_test_address_range ("1.2.3.2", 30, "1.2.3.1",  "1.2.3.1");
+	_test_address_range ("1.2.3.3", 30, "1.2.3.1",  "1.2.3.1");
+	_test_address_range ("1.2.3.4", 30, "1.2.3.6",  "1.2.3.6");
+	_test_address_range ("1.2.3.5", 30, "1.2.3.6",  "1.2.3.6");
+	_test_address_range ("1.2.3.6", 30, "1.2.3.5",  "1.2.3.5");
+	_test_address_range ("1.2.3.7", 30, "1.2.3.5",  "1.2.3.5");
+	_test_address_range ("1.2.3.8", 30, "1.2.3.10", "1.2.3.10");
+	_test_address_range ("1.2.3.9", 30, "1.2.3.10", "1.2.3.10");
+	_test_address_range ("255.255.255.0", 30, "255.255.255.2",  "255.255.255.2");
+	_test_address_range ("255.255.255.1", 30, "255.255.255.2",  "255.255.255.2");
+	_test_address_range ("255.255.255.2", 30, "255.255.255.1",  "255.255.255.1");
+	_test_address_range ("255.255.255.3", 30, "255.255.255.1",  "255.255.255.1");
+	_test_address_range ("255.255.255.248", 30, "255.255.255.250",  "255.255.255.250");
+	_test_address_range ("255.255.255.249", 30, "255.255.255.250",  "255.255.255.250");
+	_test_address_range ("255.255.255.250", 30, "255.255.255.249",  "255.255.255.249");
+	_test_address_range ("255.255.255.251", 30, "255.255.255.249",  "255.255.255.249");
+	_test_address_range ("255.255.255.252", 30, "255.255.255.254",  "255.255.255.254");
+	_test_address_range ("255.255.255.253", 30, "255.255.255.254",  "255.255.255.254");
+	_test_address_range ("255.255.255.254", 30, "255.255.255.253",  "255.255.255.253");
+	_test_address_range ("255.255.255.255", 30, "255.255.255.253",  "255.255.255.253");
+
+	_test_address_range ("0.0.0.0", 29, "0.0.0.2",  "0.0.0.6");
+	_test_address_range ("0.0.0.1", 29, "0.0.0.2",  "0.0.0.6");
+	_test_address_range ("0.0.0.2", 29, "0.0.0.3",  "0.0.0.6");
+	_test_address_range ("0.0.0.3", 29, "0.0.0.4",  "0.0.0.6");
+	_test_address_range ("0.0.0.4", 29, "0.0.0.1",  "0.0.0.3");
+	_test_address_range ("0.0.0.5", 29, "0.0.0.1",  "0.0.0.4");
+	_test_address_range ("0.0.0.6", 29, "0.0.0.1",  "0.0.0.5");
+	_test_address_range ("0.0.0.7", 29, "0.0.0.1",  "0.0.0.5");
+	_test_address_range ("0.0.0.8", 29, "0.0.0.10", "0.0.0.14");
+	_test_address_range ("0.0.0.9", 29, "0.0.0.10", "0.0.0.14");
+	_test_address_range ("1.2.3.0", 29, "1.2.3.2",  "1.2.3.6");
+	_test_address_range ("1.2.3.1", 29, "1.2.3.2",  "1.2.3.6");
+	_test_address_range ("1.2.3.2", 29, "1.2.3.3",  "1.2.3.6");
+	_test_address_range ("1.2.3.3", 29, "1.2.3.4",  "1.2.3.6");
+	_test_address_range ("1.2.3.4", 29, "1.2.3.1",  "1.2.3.3");
+	_test_address_range ("1.2.3.5", 29, "1.2.3.1",  "1.2.3.4");
+	_test_address_range ("1.2.3.6", 29, "1.2.3.1",  "1.2.3.5");
+	_test_address_range ("1.2.3.7", 29, "1.2.3.1",  "1.2.3.5");
+	_test_address_range ("1.2.3.8", 29, "1.2.3.10", "1.2.3.14");
+	_test_address_range ("1.2.3.9", 29, "1.2.3.10", "1.2.3.14");
+	_test_address_range ("255.255.255.248", 29, "255.255.255.250",  "255.255.255.254");
+	_test_address_range ("255.255.255.249", 29, "255.255.255.250",  "255.255.255.254");
+	_test_address_range ("255.255.255.250", 29, "255.255.255.251",  "255.255.255.254");
+	_test_address_range ("255.255.255.251", 29, "255.255.255.252",  "255.255.255.254");
+	_test_address_range ("255.255.255.252", 29, "255.255.255.249",  "255.255.255.251");
+	_test_address_range ("255.255.255.253", 29, "255.255.255.249",  "255.255.255.252");
+	_test_address_range ("255.255.255.254", 29, "255.255.255.249",  "255.255.255.253");
+	_test_address_range ("255.255.255.255", 29, "255.255.255.249",  "255.255.255.253");
+
+	_test_address_range ("1.2.3.1", 29, "1.2.3.2", "1.2.3.6");
+	_test_address_range ("1.2.3.1", 28, "1.2.3.3", "1.2.3.14");
+	_test_address_range ("1.2.3.1", 26, "1.2.3.8", "1.2.3.62");
+
+	_test_address_range ("192.167.255.255", 24, "192.167.255.1", "192.167.255.245");
+	_test_address_range ("192.168.0.0",     24, "192.168.0.10",  "192.168.0.254");
+	_test_address_range ("192.168.0.1",     24, "192.168.0.10",  "192.168.0.254");
+	_test_address_range ("192.168.0.2",     24, "192.168.0.11",  "192.168.0.254");
+	_test_address_range ("192.168.0.99",    24, "192.168.0.108", "192.168.0.254");
+	_test_address_range ("192.168.0.126",   24, "192.168.0.135", "192.168.0.254");
+	_test_address_range ("192.168.0.127",   24, "192.168.0.136", "192.168.0.254");
+	_test_address_range ("192.168.0.128",   24, "192.168.0.1",   "192.168.0.119");
+	_test_address_range ("192.168.0.129",   24, "192.168.0.1",   "192.168.0.120");
+	_test_address_range ("192.168.0.130",   24, "192.168.0.1",   "192.168.0.121");
+	_test_address_range ("192.168.0.254",   24, "192.168.0.1",   "192.168.0.245");
+	_test_address_range ("192.168.0.255",   24, "192.168.0.1",   "192.168.0.245");
+	_test_address_range ("192.168.1.0",     24, "192.168.1.10",  "192.168.1.254");
+	_test_address_range ("192.168.1.1",     24, "192.168.1.10",  "192.168.1.254");
+	_test_address_range ("192.168.1.2",     24, "192.168.1.11",  "192.168.1.254");
+	_test_address_range ("192.168.1.10",    24, "192.168.1.19",  "192.168.1.254");
+	_test_address_range ("192.168.15.253",  24, "192.168.15.1",  "192.168.15.244");
+	_test_address_range ("192.168.15.254",  24, "192.168.15.1",  "192.168.15.245");
+	_test_address_range ("192.168.15.255",  24, "192.168.15.1",  "192.168.15.245");
+	_test_address_range ("192.168.16.0",    24, "192.168.16.10", "192.168.16.254");
+	_test_address_range ("192.168.16.1",    24, "192.168.16.10", "192.168.16.254");
+
+	_test_address_range ("192.167.255.255", 20, "192.167.255.1", "192.167.255.245");
+	_test_address_range ("192.168.0.0",     20, "192.168.0.10",  "192.168.0.254");
+	_test_address_range ("192.168.0.1",     20, "192.168.0.10",  "192.168.0.254");
+	_test_address_range ("192.168.0.2",     20, "192.168.0.11",  "192.168.0.254");
+	_test_address_range ("192.168.0.126",   20, "192.168.0.135", "192.168.0.254");
+	_test_address_range ("192.168.0.127",   20, "192.168.0.136", "192.168.0.254");
+	_test_address_range ("192.168.0.128",   20, "192.168.0.1",   "192.168.0.119");
+	_test_address_range ("192.168.0.129",   20, "192.168.0.1",   "192.168.0.120");
+	_test_address_range ("192.168.0.130",   20, "192.168.0.1",   "192.168.0.121");
+	_test_address_range ("192.168.0.254",   20, "192.168.0.1",   "192.168.0.245");
+	_test_address_range ("192.168.0.255",   20, "192.168.0.1",   "192.168.0.245");
+	_test_address_range ("192.168.1.0",     20, "192.168.1.10",  "192.168.1.254");
+	_test_address_range ("192.168.1.1",     20, "192.168.1.10",  "192.168.1.254");
+	_test_address_range ("192.168.1.2",     20, "192.168.1.11",  "192.168.1.254");
+	_test_address_range ("192.168.1.10",    20, "192.168.1.19",  "192.168.1.254");
+	_test_address_range ("192.168.15.253",  20, "192.168.15.1",  "192.168.15.244");
+	_test_address_range ("192.168.15.254",  20, "192.168.15.1",  "192.168.15.245");
+	_test_address_range ("192.168.15.255",  20, "192.168.15.1",  "192.168.15.245");
+	_test_address_range ("192.168.16.0",    20, "192.168.16.10", "192.168.16.254");
+	_test_address_range ("192.168.16.1",    20, "192.168.16.10", "192.168.16.254");
 }
 
 /*****************************************************************************/
diff --git a/src/main.c b/src/main.c
index 52f0b7c8..d59da052 100644
--- a/src/main.c
+++ b/src/main.c
@@ -110,6 +110,7 @@ _init_nm_debug (NMConfig *config)
 	flags  = nm_utils_parse_debug_string (env, keys, G_N_ELEMENTS (keys));
 	flags |= nm_utils_parse_debug_string (debug, keys, G_N_ELEMENTS (keys));
 
+#if ! defined (__SANITIZE_ADDRESS__)
 	if (NM_FLAGS_HAS (flags, D_RLIMIT_CORE)) {
 		/* only enable this, if explicitly requested, because it might
 		 * expose sensitive data. */
@@ -120,6 +121,8 @@ _init_nm_debug (NMConfig *config)
 		};
 		setrlimit (RLIMIT_CORE, &limit);
 	}
+#endif
+
 	if (NM_FLAGS_HAS (flags, D_FATAL_WARNINGS))
 		_set_g_fatal_warnings ();
 }
@@ -194,7 +197,7 @@ do_early_setup (int *argc, char **argv[], NMConfigCmdLineOptions *config_cli)
 		  N_("Log domains separated by ',': any combination of [%s]"),
 		  "PLATFORM,RFKILL,WIFI" },
 		{ "g-fatal-warnings", 0, 0, G_OPTION_ARG_NONE, &global_opt.g_fatal_warnings, N_("Make all warnings fatal"), NULL },
-		{ "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &global_opt.pidfile, N_("Specify the location of a PID file"), N_(NM_DEFAULT_PID_FILE) },
+		{ "pid-file", 'p', 0, G_OPTION_ARG_FILENAME, &global_opt.pidfile, N_("Specify the location of a PID file"), NM_DEFAULT_PID_FILE },
 		{ "run-from-build-dir", 0, 0, G_OPTION_ARG_NONE, &global_opt.run_from_build_dir, "Run from build directory", NULL },
 		{ "print-config", 0, 0, G_OPTION_ARG_NONE, &global_opt.print_config, N_("Print NetworkManager configuration and exit"), NULL },
 		{NULL}
@@ -354,11 +357,15 @@ main (int argc, char *argv[])
 	/* Set up unix signal handling - before creating threads, but after daemonizing! */
 	nm_main_utils_setup_signals (main_loop);
 
-	nm_logging_syslog_openlog (nm_config_data_get_value_cached (NM_CONFIG_GET_DATA_ORIG,
-	                                                            NM_CONFIG_KEYFILE_GROUP_LOGGING,
-	                                                            NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
-	                                                            NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY),
-	                           nm_config_get_is_debug (config));
+	{
+		gs_free char *v = NULL;
+
+		v = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
+		                              NM_CONFIG_KEYFILE_GROUP_LOGGING,
+		                              NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
+		                              NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
+		nm_logging_syslog_openlog (v, nm_config_get_is_debug (config));
+	}
 
 	nm_log_info (LOGD_CORE, "NetworkManager (version " NM_DIST_VERSION ") is starting... (%s)",
 	             nm_config_get_first_start (config) ? "for the first time" : "after a restart");
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c
index 7a9fb110..15abee88 100644
--- a/src/ndisc/nm-fake-ndisc.c
+++ b/src/ndisc/nm-fake-ndisc.c
@@ -51,7 +51,7 @@ typedef struct {
         guint32 timestamp;
         guint32 lifetime;
         guint32 preferred;
-        NMNDiscPreference preference;
+        NMIcmpv6RouterPref preference;
 } FakePrefix;
 
 /*****************************************************************************/
@@ -145,7 +145,7 @@ nm_fake_ndisc_add_gateway (NMFakeNDisc *self,
                            const char *addr,
                            guint32 timestamp,
                            guint32 lifetime,
-                           NMNDiscPreference preference)
+                           NMIcmpv6RouterPref preference)
 {
 	NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE (self);
 	FakeRa *ra = find_ra (priv->ras, ra_id);
@@ -169,7 +169,7 @@ nm_fake_ndisc_add_prefix (NMFakeNDisc *self,
                           guint32 timestamp,
                           guint32 lifetime,
                           guint32 preferred,
-                          NMNDiscPreference preference)
+                          NMIcmpv6RouterPref preference)
 {
 	NMFakeNDiscPrivate *priv = NM_FAKE_NDISC_GET_PRIVATE (self);
 	FakeRa *ra = find_ra (priv->ras, ra_id);
@@ -250,7 +250,7 @@ receive_ra (gpointer user_data)
 	NMNDiscDataInternal *rdata = ndisc->rdata;
 	FakeRa *ra = priv->ras->data;
 	NMNDiscConfigMap changed = 0;
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_s ();
 	guint i;
 	NMNDiscDHCPLevel dhcp_level;
 
diff --git a/src/ndisc/nm-fake-ndisc.h b/src/ndisc/nm-fake-ndisc.h
index 2544c456..3266dc89 100644
--- a/src/ndisc/nm-fake-ndisc.h
+++ b/src/ndisc/nm-fake-ndisc.h
@@ -50,7 +50,7 @@ void nm_fake_ndisc_add_gateway    (NMFakeNDisc *self,
                                    const char *addr,
                                    guint32 timestamp,
                                    guint32 lifetime,
-                                   NMNDiscPreference preference);
+                                   NMIcmpv6RouterPref preference);
 
 void nm_fake_ndisc_add_prefix     (NMFakeNDisc *self,
                                    guint ra_id,
@@ -60,7 +60,7 @@ void nm_fake_ndisc_add_prefix     (NMFakeNDisc *self,
                                    guint32 timestamp,
                                    guint32 lifetime,
                                    guint32 preferred,
-                                   NMNDiscPreference preference);
+                                   NMIcmpv6RouterPref preference);
 
 void nm_fake_ndisc_add_dns_server (NMFakeNDisc *self,
                                    guint ra_id,
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index 3bc1590e..70200ed3 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -93,12 +93,20 @@ send_rs (NMNDisc *ndisc, GError **error)
 	return TRUE;
 }
 
-_NM_UTILS_LOOKUP_DEFINE (static, translate_preference, enum ndp_route_preference, NMNDiscPreference,
-	NM_UTILS_LOOKUP_DEFAULT (NM_NDISC_PREFERENCE_INVALID),
-	NM_UTILS_LOOKUP_ITEM (NDP_ROUTE_PREF_LOW,    NM_NDISC_PREFERENCE_LOW),
-	NM_UTILS_LOOKUP_ITEM (NDP_ROUTE_PREF_MEDIUM, NM_NDISC_PREFERENCE_MEDIUM),
-	NM_UTILS_LOOKUP_ITEM (NDP_ROUTE_PREF_HIGH,   NM_NDISC_PREFERENCE_HIGH),
-);
+static NMIcmpv6RouterPref
+_route_preference_coerce (enum ndp_route_preference pref)
+{
+	switch (pref) {
+	case NDP_ROUTE_PREF_LOW:
+		return NM_ICMPV6_ROUTER_PREF_LOW;
+	case NDP_ROUTE_PREF_MEDIUM:
+		return NM_ICMPV6_ROUTER_PREF_MEDIUM;
+	case NDP_ROUTE_PREF_HIGH:
+		return NM_ICMPV6_ROUTER_PREF_HIGH;
+	}
+	/* unexpected value must be treated as MEDIUM (RFC 4191). */
+	return NM_ICMPV6_ROUTER_PREF_MEDIUM;
+}
 
 static int
 receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
@@ -108,7 +116,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	NMNDiscConfigMap changed = 0;
 	struct ndp_msgra *msgra = ndp_msgra (msg);
 	struct in6_addr gateway_addr;
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_s ();
 	int offset;
 	int hop_limit;
 
@@ -123,7 +131,11 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	 * single time when the configuration is finished and updates can
 	 * come at any time.
 	 */
-	_LOGD ("received router advertisement at %u", now);
+	_LOGD ("received router advertisement at %d", (int) now);
+
+	gateway_addr = *ndp_msg_addrto (msg);
+	if (IN6_IS_ADDR_UNSPECIFIED (&gateway_addr))
+		g_return_val_if_reached (0);
 
 	/* DHCP level:
 	 *
@@ -159,13 +171,12 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	 * on the network. We should present all of them in router preference
 	 * order.
 	 */
-	gateway_addr = *ndp_msg_addrto (msg);
 	{
-		NMNDiscGateway gateway = {
+		const NMNDiscGateway gateway = {
 		    .address = gateway_addr,
 		    .timestamp = now,
 		    .lifetime = ndp_msgra_router_lifetime (msgra),
-		    .preference = translate_preference (ndp_msgra_route_preference (msgra)),
+		    .preference = _route_preference_coerce (ndp_msgra_route_preference (msgra)),
 		};
 
 		if (nm_ndisc_add_gateway (ndisc, &gateway))
@@ -218,7 +229,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 		    .plen = ndp_msg_opt_route_prefix_len (msg, offset),
 		    .timestamp = now,
 		    .lifetime = ndp_msg_opt_route_lifetime (msg, offset),
-		    .preference = translate_preference (ndp_msg_opt_route_preference (msg, offset)),
+		    .preference = _route_preference_coerce (ndp_msg_opt_route_preference (msg, offset)),
 		};
 
 		if (route.plen == 0 || route.plen > 128)
@@ -337,7 +348,7 @@ send_ra (NMNDisc *ndisc, GError **error)
 {
 	NMLndpNDiscPrivate *priv = NM_LNDP_NDISC_GET_PRIVATE ((NMLndpNDisc *) ndisc);
 	NMNDiscDataInternal *rdata = ndisc->rdata;
-	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	gint32 now = nm_utils_get_monotonic_timestamp_s ();
 	int errsv;
 	struct in6_addr *addr;
 	struct ndp_msg *msg;
@@ -367,14 +378,14 @@ send_ra (NMNDisc *ndisc, GError **error)
 	 * whose prefixes are suitable for delegating. Let's announce them. */
 	for (i = 0; i < rdata->addresses->len; i++) {
 		NMNDiscAddress *address = &g_array_index (rdata->addresses, NMNDiscAddress, i);
-		guint32 age = now - address->timestamp;
+		guint32 age = NM_CLAMP ((gint64) now - (gint64) address->timestamp, 0, G_MAXUINT32 - 1);
 		guint32 lifetime = address->lifetime;
 		guint32 preferred = address->preferred;
 
 		/* Clamp the life times if they're not forever. */
-		if (lifetime != 0xffffffff)
+		if (lifetime != NM_NDISC_INFINITY)
 			lifetime = lifetime > age ? lifetime - age : 0;
-		if (preferred != 0xffffffff)
+		if (preferred != NM_NDISC_INFINITY)
 			preferred = preferred > age ? preferred - age : 0;
 
 		prefix = _ndp_msg_add_option (msg, sizeof(*prefix));
@@ -520,8 +531,10 @@ start (NMNDisc *ndisc)
 static inline int
 ipv6_sysctl_get (NMPlatform *platform, const char *ifname, const char *property, int min, int max, int defval)
 {
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+
 	return (int) nm_platform_sysctl_get_int_checked (platform,
-	                                                 NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (ifname, property)),
+	                                                 NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, buf, ifname, property)),
 	                                                 10,
 	                                                 min,
 	                                                 max,
diff --git a/src/ndisc/nm-ndisc-private.h b/src/ndisc/nm-ndisc-private.h
index 10bcc64f..bbecb01a 100644
--- a/src/ndisc/nm-ndisc-private.h
+++ b/src/ndisc/nm-ndisc-private.h
@@ -36,7 +36,7 @@ struct _NMNDiscDataInternal {
 
 typedef struct _NMNDiscDataInternal NMNDiscDataInternal;
 
-void nm_ndisc_ra_received (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap changed);
+void nm_ndisc_ra_received (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed);
 void nm_ndisc_rs_received (NMNDisc *ndisc);
 
 gboolean nm_ndisc_add_gateway              (NMNDisc *ndisc, const NMNDiscGateway *new);
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index a50bbe43..6b44a96c 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -106,6 +106,24 @@ static void _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed);
 
 /*****************************************************************************/
 
+static guint8
+_preference_to_priority (NMIcmpv6RouterPref pref)
+{
+	switch (pref) {
+	case NM_ICMPV6_ROUTER_PREF_LOW:
+		return 1;
+	case NM_ICMPV6_ROUTER_PREF_MEDIUM:
+		return 2;
+	case NM_ICMPV6_ROUTER_PREF_HIGH:
+		return 3;
+	case NM_ICMPV6_ROUTER_PREF_INVALID:
+		break;
+	}
+	return 0;
+}
+
+/*****************************************************************************/
+
 NMPNetns *
 nm_ndisc_netns_get (NMNDisc *self)
 {
@@ -160,9 +178,43 @@ nm_ndisc_get_node_type (NMNDisc *self)
 
 /*****************************************************************************/
 
+static void
+_ASSERT_data_gateways (const NMNDiscDataInternal *data)
+{
+#if NM_MORE_ASSERTS > 10
+	guint i, j;
+	const NMNDiscGateway *item_prev = NULL;
+
+	if (!data->gateways->len)
+		return;
+
+	for (i = 0; i < data->gateways->len; i++) {
+		const NMNDiscGateway *item = &g_array_index (data->gateways, NMNDiscGateway, i);
+
+		nm_assert (!IN6_IS_ADDR_UNSPECIFIED (&item->address));
+		nm_assert (item->timestamp > 0 && item->timestamp <= G_MAXINT32);
+		for (j = 0; j < i; j++) {
+			const NMNDiscGateway *item2 = &g_array_index (data->gateways, NMNDiscGateway, j);
+
+			nm_assert (!IN6_ARE_ADDR_EQUAL (&item->address, &item2->address));
+		}
+
+		nm_assert (item->lifetime > 0);
+		if (i > 0)
+			nm_assert (_preference_to_priority (item_prev->preference) >= _preference_to_priority (item->preference));
+
+		item_prev = item;
+	}
+#endif
+}
+
+/*****************************************************************************/
+
 static const NMNDiscData *
 _data_complete (NMNDiscDataInternal *data)
 {
+	_ASSERT_data_gateways (data);
+
 #define _SET(data, field) \
 	G_STMT_START { \
 		if ((data->public.field##_n = data->field->len) > 0) \
@@ -194,33 +246,45 @@ gboolean
 nm_ndisc_add_gateway (NMNDisc *ndisc, const NMNDiscGateway *new)
 {
 	NMNDiscDataInternal *rdata = &NM_NDISC_GET_PRIVATE(ndisc)->rdata;
-	int i, insert_idx = -1;
+	guint i;
+	guint insert_idx = G_MAXUINT;
 
-	for (i = 0; i < rdata->gateways->len; i++) {
+	for (i = 0; i < rdata->gateways->len; ) {
 		NMNDiscGateway *item = &g_array_index (rdata->gateways, NMNDiscGateway, i);
 
 		if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) {
 			if (new->lifetime == 0) {
-				g_array_remove_index (rdata->gateways, i--);
+				g_array_remove_index (rdata->gateways, i);
+				_ASSERT_data_gateways (rdata);
 				return TRUE;
 			}
 
 			if (item->preference != new->preference) {
-				g_array_remove_index (rdata->gateways, i--);
+				g_array_remove_index (rdata->gateways, i);
 				continue;
 			}
 
-			memcpy (item, new, sizeof (*new));
+			*item = *new;
+			_ASSERT_data_gateways (rdata);
 			return FALSE;
 		}
 
 		/* Put before less preferable gateways. */
-		if (item->preference < new->preference && insert_idx < 0)
+		if (   _preference_to_priority (item->preference) < _preference_to_priority (new->preference)
+		    && insert_idx == G_MAXUINT)
 			insert_idx = i;
+
+		i++;
 	}
 
-	if (new->lifetime)
-		g_array_insert_val (rdata->gateways, MAX (insert_idx, 0), *new);
+	if (new->lifetime) {
+		g_array_insert_val (rdata->gateways,
+		                    insert_idx == G_MAXUINT
+		                      ? rdata->gateways->len
+		                      : insert_idx,
+		                    *new);
+	}
+	_ASSERT_data_gateways (rdata);
 	return !!new->lifetime;
 }
 
@@ -283,7 +347,7 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new)
 {
 	NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE (ndisc);
 	NMNDiscDataInternal *rdata = &priv->rdata;
-	int i;
+	guint i;
 
 	for (i = 0; i < rdata->addresses->len; i++) {
 		NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
@@ -292,7 +356,7 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new)
 			gboolean changed;
 
 			if (new->lifetime == 0) {
-				g_array_remove_index (rdata->addresses, i--);
+				g_array_remove_index (rdata->addresses, i);
 				return TRUE;
 			}
 
@@ -307,11 +371,12 @@ nm_ndisc_add_address (NMNDisc *ndisc, const NMNDiscAddress *new)
 	 * what the kernel does, because it considers *all* addresses (including
 	 * static and other temporary addresses).
 	 **/
-	if (priv->max_addresses && rdata->addresses->len >= priv->max_addresses)
+	if (   priv->max_addresses
+	    && rdata->addresses->len >= priv->max_addresses)
 		return FALSE;
 
 	if (new->lifetime)
-		g_array_insert_val (rdata->addresses, i, *new);
+		g_array_append_val (rdata->addresses, *new);
 	return !!new->lifetime;
 }
 
@@ -329,7 +394,8 @@ nm_ndisc_add_route (NMNDisc *ndisc, const NMNDiscRoute *new)
 {
 	NMNDiscPrivate *priv;
 	NMNDiscDataInternal *rdata;
-	int i, insert_idx = -1;
+	guint i;
+	guint insert_idx = G_MAXUINT;
 
 	if (new->plen == 0 || new->plen > 128) {
 		/* Only expect non-default routes.  The router has no idea what the
@@ -345,17 +411,17 @@ nm_ndisc_add_route (NMNDisc *ndisc, const NMNDiscRoute *new)
 	priv = NM_NDISC_GET_PRIVATE (ndisc);
 	rdata = &priv->rdata;
 
-	for (i = 0; i < rdata->routes->len; i++) {
+	for (i = 0; i < rdata->routes->len; ) {
 		NMNDiscRoute *item = &g_array_index (rdata->routes, NMNDiscRoute, i);
 
 		if (IN6_ARE_ADDR_EQUAL (&item->network, &new->network) && item->plen == new->plen) {
 			if (new->lifetime == 0) {
-				g_array_remove_index (rdata->routes, i--);
+				g_array_remove_index (rdata->routes, i);
 				return TRUE;
 			}
 
 			if (item->preference != new->preference) {
-				g_array_remove_index (rdata->routes, i--);
+				g_array_remove_index (rdata->routes, i);
 				continue;
 			}
 
@@ -364,12 +430,20 @@ nm_ndisc_add_route (NMNDisc *ndisc, const NMNDiscRoute *new)
 		}
 
 		/* Put before less preferable routes. */
-		if (item->preference < new->preference && insert_idx < 0)
+		if (   _preference_to_priority (item->preference) < _preference_to_priority (new->preference)
+		    && insert_idx == G_MAXUINT)
 			insert_idx = i;
+
+		i++;
 	}
 
-	if (new->lifetime)
-		g_array_insert_val (rdata->routes, CLAMP (insert_idx, 0, G_MAXINT), *new);
+	if (new->lifetime) {
+		g_array_insert_val (rdata->routes,
+		                    insert_idx == G_MAXUINT
+		                      ? 0u
+		                      : insert_idx,
+		                    *new);
+	}
 	return !!new->lifetime;
 }
 
@@ -378,7 +452,7 @@ nm_ndisc_add_dns_server (NMNDisc *ndisc, const NMNDiscDNSServer *new)
 {
 	NMNDiscPrivate *priv;
 	NMNDiscDataInternal *rdata;
-	int i;
+	guint i;
 
 	priv = NM_NDISC_GET_PRIVATE (ndisc);
 	rdata = &priv->rdata;
@@ -400,7 +474,7 @@ nm_ndisc_add_dns_server (NMNDisc *ndisc, const NMNDiscDNSServer *new)
 	}
 
 	if (new->lifetime)
-		g_array_insert_val (rdata->dns_servers, i, *new);
+		g_array_append_val (rdata->dns_servers, *new);
 	return !!new->lifetime;
 }
 
@@ -411,7 +485,7 @@ nm_ndisc_add_dns_domain (NMNDisc *ndisc, const NMNDiscDNSDomain *new)
 	NMNDiscPrivate *priv;
 	NMNDiscDataInternal *rdata;
 	NMNDiscDNSDomain *item;
-	int i;
+	guint i;
 
 	priv = NM_NDISC_GET_PRIVATE (ndisc);
 	rdata = &priv->rdata;
@@ -438,8 +512,10 @@ nm_ndisc_add_dns_domain (NMNDisc *ndisc, const NMNDiscDNSDomain *new)
 	}
 
 	if (new->lifetime) {
-		g_array_insert_val (rdata->dns_domains, i, *new);
-		item = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i);
+		g_array_append_val (rdata->dns_domains, *new);
+		item = &g_array_index (rdata->dns_domains,
+		                       NMNDiscDNSDomain,
+		                       rdata->dns_domains->len - 1);
 		item->domain = g_strdup (new->domain);
 	}
 	return !!new->lifetime;
@@ -498,7 +574,8 @@ static void
 solicit_routers (NMNDisc *ndisc)
 {
 	NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE (ndisc);
-	gint64 next, now;
+	gint32 now, next;
+	gint64 t;
 
 	if (priv->send_rs_id)
 		return;
@@ -506,9 +583,9 @@ solicit_routers (NMNDisc *ndisc)
 	now = nm_utils_get_monotonic_timestamp_s ();
 	priv->solicitations_left = priv->router_solicitations;
 
-	next = (((gint64) priv->last_rs) + priv->router_solicitation_interval) - now;
-	next = CLAMP (next, 0, G_MAXINT32);
-	_LOGD ("scheduling explicit router solicitation request in %" G_GINT64_FORMAT " seconds.",
+	t = (((gint64) priv->last_rs) + priv->router_solicitation_interval) - now;
+	next = CLAMP (t, 0, G_MAXINT32);
+	_LOGD ("scheduling explicit router solicitation request in %" G_GINT32_FORMAT " seconds.",
 	       next);
 	priv->send_rs_id = g_timeout_add_seconds ((guint32) next, (GSourceFunc) send_rs_timeout, ndisc);
 }
@@ -600,7 +677,7 @@ nm_ndisc_set_config (NMNDisc *ndisc,
                      const GArray *dns_servers,
                      const GArray *dns_domains)
 {
-	int changed = FALSE;
+	gboolean changed = FALSE;
 	guint i;
 
 	for (i = 0; i < addresses->len; i++) {
@@ -718,21 +795,23 @@ void
 nm_ndisc_dad_failed (NMNDisc *ndisc, struct in6_addr *address)
 {
 	NMNDiscDataInternal *rdata;
-	int i;
+	guint i;
 	gboolean changed = FALSE;
 
 	rdata = &NM_NDISC_GET_PRIVATE (ndisc)->rdata;
 
-	for (i = 0; i < rdata->addresses->len; i++) {
+	for (i = 0; i < rdata->addresses->len; ) {
 		NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
-		if (!IN6_ARE_ADDR_EQUAL (&item->address, address))
-			continue;
-
-		_LOGD ("DAD failed for discovered address %s", nm_utils_inet6_ntop (address, NULL));
-		if (!complete_address (ndisc, item))
-			g_array_remove_index (rdata->addresses, i--);
-		changed = TRUE;
+		if (IN6_ARE_ADDR_EQUAL (&item->address, address)) {
+			_LOGD ("DAD failed for discovered address %s", nm_utils_inet6_ntop (address, NULL));
+			changed = TRUE;
+			if (!complete_address (ndisc, item)) {
+				g_array_remove_index (rdata->addresses, i);
+				continue;
+			}
+		}
+		i++;
 	}
 
 	if (changed)
@@ -774,16 +853,49 @@ dhcp_level_to_string (NMNDiscDHCPLevel dhcp_level)
 	}
 }
 
-#define expiry(item) (item->timestamp + item->lifetime)
+static gint32
+get_expiry_time (guint32 timestamp, guint32 lifetime)
+{
+	gint64 t;
+
+	/* timestamp is supposed to come from nm_utils_get_monotonic_timestamp_s().
+	 * It is expected to be within a certain range. */
+	nm_assert (timestamp > 0);
+	nm_assert (timestamp <= G_MAXINT32);
+
+	if (lifetime == NM_NDISC_INFINITY)
+		return G_MAXINT32;
+
+	t = (gint64) timestamp + (gint64) lifetime;
+	return CLAMP (t, 0, G_MAXINT32 - 1);
+}
+
+#define get_expiry(item) \
+	({ \
+		typeof (item) _item = (item); \
+		nm_assert (_item); \
+		get_expiry_time ((_item->timestamp), (_item->lifetime)); \
+	})
+
+#define get_expiry_half(item) \
+	({ \
+		typeof (item) _item = (item); \
+		nm_assert (_item); \
+		get_expiry_time ((_item->timestamp),\
+		                 (_item->lifetime) == NM_NDISC_INFINITY \
+		                   ? NM_NDISC_INFINITY \
+		                   : (_item->lifetime) / 2); \
+	})
 
 static void
 _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 {
 	NMNDiscPrivate *priv;
 	NMNDiscDataInternal *rdata;
-	int i;
+	guint i;
 	char changedstr[CONFIG_MAP_MAX_STR];
 	char addrstr[INET6_ADDRSTRLEN];
+	char str_pref[35];
 
 	if (!_LOGD_ENABLED ())
 		return;
@@ -798,165 +910,188 @@ _config_changed_log (NMNDisc *ndisc, NMNDiscConfigMap changed)
 		NMNDiscGateway *gateway = &g_array_index (rdata->gateways, NMNDiscGateway, i);
 
 		inet_ntop (AF_INET6, &gateway->address, addrstr, sizeof (addrstr));
-		_LOGD ("  gateway %s pref %d exp %u", addrstr, gateway->preference, expiry (gateway));
+		_LOGD ("  gateway %s pref %s exp %d", addrstr,
+		       nm_icmpv6_router_pref_to_string (gateway->preference, str_pref, sizeof (str_pref)),
+		       get_expiry (gateway));
 	}
 	for (i = 0; i < rdata->addresses->len; i++) {
 		NMNDiscAddress *address = &g_array_index (rdata->addresses, NMNDiscAddress, i);
 
 		inet_ntop (AF_INET6, &address->address, addrstr, sizeof (addrstr));
-		_LOGD ("  address %s exp %u", addrstr, expiry (address));
+		_LOGD ("  address %s exp %d", addrstr, get_expiry (address));
 	}
 	for (i = 0; i < rdata->routes->len; i++) {
 		NMNDiscRoute *route = &g_array_index (rdata->routes, NMNDiscRoute, i);
 
 		inet_ntop (AF_INET6, &route->network, addrstr, sizeof (addrstr));
-		_LOGD ("  route %s/%d via %s pref %d exp %u", addrstr, (int) route->plen,
-		       nm_utils_inet6_ntop (&route->gateway, NULL), route->preference,
-		       expiry (route));
+		_LOGD ("  route %s/%u via %s pref %s exp %d", addrstr, (guint) route->plen,
+		       nm_utils_inet6_ntop (&route->gateway, NULL),
+		       nm_icmpv6_router_pref_to_string (route->preference, str_pref, sizeof (str_pref)),
+		       get_expiry (route));
 	}
 	for (i = 0; i < rdata->dns_servers->len; i++) {
 		NMNDiscDNSServer *dns_server = &g_array_index (rdata->dns_servers, NMNDiscDNSServer, i);
 
 		inet_ntop (AF_INET6, &dns_server->address, addrstr, sizeof (addrstr));
-		_LOGD ("  dns_server %s exp %u", addrstr, expiry (dns_server));
+		_LOGD ("  dns_server %s exp %d", addrstr, get_expiry (dns_server));
 	}
 	for (i = 0; i < rdata->dns_domains->len; i++) {
 		NMNDiscDNSDomain *dns_domain = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i);
 
-		_LOGD ("  dns_domain %s exp %u", dns_domain->domain, expiry (dns_domain));
+		_LOGD ("  dns_domain %s exp %d", dns_domain->domain, get_expiry (dns_domain));
 	}
 }
 
 static void
-clean_gateways (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap *changed, guint32 *nextevent)
+clean_gateways (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
 {
 	NMNDiscDataInternal *rdata;
 	guint i;
 
 	rdata = &NM_NDISC_GET_PRIVATE (ndisc)->rdata;
 
-	for (i = 0; i < rdata->gateways->len; i++) {
+	for (i = 0; i < rdata->gateways->len; ) {
 		NMNDiscGateway *item = &g_array_index (rdata->gateways, NMNDiscGateway, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
 
-		if (item->lifetime == G_MAXUINT32)
-			continue;
+		if (item->lifetime != NM_NDISC_INFINITY) {
+			gint32 expiry = get_expiry (item);
 
-		if (now >= expiry) {
-			g_array_remove_index (rdata->gateways, i--);
-			*changed |= NM_NDISC_CONFIG_GATEWAYS;
-		} else if (*nextevent > expiry)
-			*nextevent = expiry;
+			if (now >= expiry) {
+				g_array_remove_index (rdata->gateways, i);
+				*changed |= NM_NDISC_CONFIG_GATEWAYS;
+				continue;
+			}
+			if (*nextevent > expiry)
+				*nextevent = expiry;
+		}
+		i++;
 	}
+
+	_ASSERT_data_gateways (rdata);
 }
 
 static void
-clean_addresses (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap *changed, guint32 *nextevent)
+clean_addresses (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
 {
 	NMNDiscDataInternal *rdata;
 	guint i;
 
 	rdata = &NM_NDISC_GET_PRIVATE (ndisc)->rdata;
 
-	for (i = 0; i < rdata->addresses->len; i++) {
+	for (i = 0; i < rdata->addresses->len; ) {
 		NMNDiscAddress *item = &g_array_index (rdata->addresses, NMNDiscAddress, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
 
-		if (item->lifetime == G_MAXUINT32)
-			continue;
+		if (item->lifetime != NM_NDISC_INFINITY) {
+			gint32 expiry = get_expiry (item);
 
-		if (now >= expiry) {
-			g_array_remove_index (rdata->addresses, i--);
-			*changed |= NM_NDISC_CONFIG_ADDRESSES;
-		} else if (*nextevent > expiry)
-			*nextevent = expiry;
+			if (now >= expiry) {
+				g_array_remove_index (rdata->addresses, i);
+				*changed |= NM_NDISC_CONFIG_ADDRESSES;
+				continue;
+			}
+			if (*nextevent > expiry)
+				*nextevent = expiry;
+		}
+		i++;
 	}
 }
 
 static void
-clean_routes (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap *changed, guint32 *nextevent)
+clean_routes (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
 {
 	NMNDiscDataInternal *rdata;
 	guint i;
 
 	rdata = &NM_NDISC_GET_PRIVATE (ndisc)->rdata;
 
-	for (i = 0; i < rdata->routes->len; i++) {
+	for (i = 0; i < rdata->routes->len; ) {
 		NMNDiscRoute *item = &g_array_index (rdata->routes, NMNDiscRoute, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
 
-		if (item->lifetime == G_MAXUINT32)
-			continue;
+		if (item->lifetime != NM_NDISC_INFINITY) {
+			gint32 expiry = get_expiry (item);
 
-		if (now >= expiry) {
-			g_array_remove_index (rdata->routes, i--);
-			*changed |= NM_NDISC_CONFIG_ROUTES;
-		} else if (*nextevent > expiry)
-			*nextevent = expiry;
+			if (now >= expiry) {
+				g_array_remove_index (rdata->routes, i);
+				*changed |= NM_NDISC_CONFIG_ROUTES;
+				continue;
+			}
+			if (*nextevent > expiry)
+				*nextevent = expiry;
+		}
+		i++;
 	}
 }
 
 static void
-clean_dns_servers (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap *changed, guint32 *nextevent)
+clean_dns_servers (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
 {
 	NMNDiscDataInternal *rdata;
 	guint i;
 
 	rdata = &NM_NDISC_GET_PRIVATE (ndisc)->rdata;
 
-	for (i = 0; i < rdata->dns_servers->len; i++) {
+	for (i = 0; i < rdata->dns_servers->len; ) {
 		NMNDiscDNSServer *item = &g_array_index (rdata->dns_servers, NMNDiscDNSServer, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
-		guint64 refresh = (guint64) item->timestamp + item->lifetime / 2;
 
-		if (item->lifetime == G_MAXUINT32)
-			continue;
+		if (item->lifetime != NM_NDISC_INFINITY) {
+			gint32 expiry = get_expiry (item);
+			gint32 refresh;
 
-		if (now >= expiry) {
-			g_array_remove_index (rdata->dns_servers, i--);
-			*changed |= NM_NDISC_CONFIG_DNS_SERVERS;
-		} else if (now >= refresh)
-			solicit_routers (ndisc);
-		else if (*nextevent > refresh)
-			*nextevent = refresh;
+			if (now >= expiry) {
+				g_array_remove_index (rdata->dns_servers, i);
+				*changed |= NM_NDISC_CONFIG_DNS_SERVERS;
+				continue;
+			}
+
+			refresh = get_expiry_half (item);
+			if (now >= refresh)
+				solicit_routers (ndisc);
+			else if (*nextevent > refresh)
+				*nextevent = refresh;
+		}
+		i++;
 	}
 }
 
 static void
-clean_dns_domains (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap *changed, guint32 *nextevent)
+clean_dns_domains (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap *changed, gint32 *nextevent)
 {
 	NMNDiscDataInternal *rdata;
 	guint i;
 
 	rdata = &NM_NDISC_GET_PRIVATE (ndisc)->rdata;
 
-	for (i = 0; i < rdata->dns_domains->len; i++) {
+	for (i = 0; i < rdata->dns_domains->len; ) {
 		NMNDiscDNSDomain *item = &g_array_index (rdata->dns_domains, NMNDiscDNSDomain, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
-		guint64 refresh = (guint64) item->timestamp + item->lifetime / 2;
 
-		if (item->lifetime == G_MAXUINT32)
-			continue;
+		if (item->lifetime != NM_NDISC_INFINITY) {
+			gint32 expiry = get_expiry (item);
+			gint32 refresh;
 
-		if (now >= expiry) {
-			g_array_remove_index (rdata->dns_domains, i--);
-			*changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
-		} else if (now >= refresh)
-			solicit_routers (ndisc);
-		else if (*nextevent > refresh)
-			*nextevent = refresh;
+			if (now >= expiry) {
+				g_array_remove_index (rdata->dns_domains, i);
+				*changed |= NM_NDISC_CONFIG_DNS_DOMAINS;
+				continue;
+			}
+
+			refresh = get_expiry_half (item);
+			if (now >= refresh)
+				solicit_routers (ndisc);
+			else if (*nextevent > refresh)
+				*nextevent = refresh;
+		}
+		i++;
 	}
 }
 
 static gboolean timeout_cb (gpointer user_data);
 
 static void
-check_timestamps (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap changed)
+check_timestamps (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
 {
 	NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE (ndisc);
 	/* Use a magic date in the distant future (~68 years) */
-	guint32 never = G_MAXINT32;
-	guint32 nextevent = never;
+	gint32 nextevent = G_MAXINT32;
 
 	nm_clear_g_source (&priv->timeout_id);
 
@@ -969,10 +1104,11 @@ check_timestamps (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap changed)
 	if (changed)
 		_emit_config_change (ndisc, changed);
 
-	if (nextevent != never) {
-		g_return_if_fail (nextevent > now);
-		_LOGD ("scheduling next now/lifetime check: %u seconds",
-		       nextevent - now);
+	if (nextevent != G_MAXINT32) {
+		if (nextevent <= now)
+			g_return_if_reached ();
+		_LOGD ("scheduling next now/lifetime check: %d seconds",
+		       (int) (nextevent - now));
 		priv->timeout_id = g_timeout_add_seconds (nextevent - now, timeout_cb, ndisc);
 	}
 }
@@ -988,7 +1124,7 @@ timeout_cb (gpointer user_data)
 }
 
 void
-nm_ndisc_ra_received (NMNDisc *ndisc, guint32 now, NMNDiscConfigMap changed)
+nm_ndisc_ra_received (NMNDisc *ndisc, gint32 now, NMNDiscConfigMap changed)
 {
 	NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE (ndisc);
 
diff --git a/src/ndisc/nm-ndisc.h b/src/ndisc/nm-ndisc.h
index 7c67289d..b66c2289 100644
--- a/src/ndisc/nm-ndisc.h
+++ b/src/ndisc/nm-ndisc.h
@@ -55,36 +55,34 @@ typedef enum {
 	NM_NDISC_DHCP_LEVEL_MANAGED
 } NMNDiscDHCPLevel;
 
-typedef enum {
-	NM_NDISC_PREFERENCE_INVALID,
-	NM_NDISC_PREFERENCE_LOW,
-	NM_NDISC_PREFERENCE_MEDIUM,
-	NM_NDISC_PREFERENCE_HIGH
-} NMNDiscPreference;
+#define NM_NDISC_INFINITY  G_MAXUINT32
 
-typedef struct {
+struct _NMNDiscGateway {
 	struct in6_addr address;
 	guint32 timestamp;
 	guint32 lifetime;
-	NMNDiscPreference preference;
-} NMNDiscGateway;
+	NMIcmpv6RouterPref preference;
+};
+typedef struct _NMNDiscGateway NMNDiscGateway;
 
-typedef struct {
+struct _NMNDiscAddress {
 	struct in6_addr address;
 	guint8 dad_counter;
 	guint32 timestamp;
 	guint32 lifetime;
 	guint32 preferred;
-} NMNDiscAddress;
+};
+typedef struct _NMNDiscAddress NMNDiscAddress;
 
-typedef struct {
+struct _NMNDiscRoute {
 	struct in6_addr network;
 	guint8 plen;
 	struct in6_addr gateway;
 	guint32 timestamp;
 	guint32 lifetime;
-	NMNDiscPreference preference;
-} NMNDiscRoute;
+	NMIcmpv6RouterPref preference;
+};
+typedef struct _NMNDiscRoute NMNDiscRoute;
 
 typedef struct {
 	struct in6_addr address;
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index 006aea7f..e99d2fc5 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -46,7 +46,7 @@ ndisc_new (void)
 }
 
 static void
-match_gateway (const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts, guint32 lt, NMNDiscPreference pref)
+match_gateway (const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts, guint32 lt, NMIcmpv6RouterPref pref)
 {
 	const NMNDiscGateway *gw;
 	char buf[INET6_ADDRSTRLEN];
@@ -82,7 +82,7 @@ match_address (const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts
 }
 
 static void
-match_route (const NMNDiscData *rdata, guint idx, const char *nw, int plen, const char *gw, guint32 ts, guint32 lt, NMNDiscPreference pref)
+match_route (const NMNDiscData *rdata, guint idx, const char *nw, int plen, const char *gw, guint32 ts, guint32 lt, NMIcmpv6RouterPref pref)
 {
 	const NMNDiscRoute *route;
 	char buf[INET6_ADDRSTRLEN];
@@ -158,7 +158,7 @@ test_simple_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int
 	                              NM_NDISC_CONFIG_HOP_LIMIT |
 	                              NM_NDISC_CONFIG_MTU);
 	g_assert_cmpint (rdata->dhcp_level, ==, NM_NDISC_DHCP_LEVEL_OTHERCONF);
-	match_gateway (rdata, 0, "fe80::1", data->timestamp1, 10, NM_NDISC_PREFERENCE_MEDIUM);
+	match_gateway (rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 	match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
 	match_route (rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10);
 	match_dns_server (rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10);
@@ -179,7 +179,7 @@ test_simple (void)
 
 	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_OTHERCONF, 4, 1500);
 	g_assert (id);
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_NDISC_PREFERENCE_MEDIUM);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 10);
 	nm_fake_ndisc_add_dns_server (ndisc, id, "2001:db8:c:c::1", now, 10);
 	nm_fake_ndisc_add_dns_domain (ndisc, id, "foobar.com", now, 10);
@@ -219,7 +219,7 @@ test_everything_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 		                              NM_NDISC_CONFIG_DNS_DOMAINS |
 		                              NM_NDISC_CONFIG_HOP_LIMIT |
 		                              NM_NDISC_CONFIG_MTU);
-		match_gateway (rdata, 0, "fe80::1", data->timestamp1, 10, NM_NDISC_PREFERENCE_MEDIUM);
+		match_gateway (rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
 		match_route (rdata, 0, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 10);
 		match_dns_server (rdata, 0, "2001:db8:c:c::1", data->timestamp1, 10);
@@ -232,7 +232,7 @@ test_everything_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 		                              NM_NDISC_CONFIG_DNS_DOMAINS);
 
 		g_assert_cmpint (rdata->gateways_n, ==, 1);
-		match_gateway (rdata, 0, "fe80::2", data->timestamp1, 10, NM_NDISC_PREFERENCE_MEDIUM);
+		match_gateway (rdata, 0, "fe80::2", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 		g_assert_cmpint (rdata->addresses_n, ==, 1);
 		match_address (rdata, 0, "2001:db8:a:b::1", data->timestamp1, 10, 10);
 		g_assert_cmpint (rdata->routes_n, ==, 1);
@@ -260,7 +260,7 @@ test_everything (void)
 
 	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
 	g_assert (id);
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_NDISC_PREFERENCE_MEDIUM);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 10);
 	nm_fake_ndisc_add_dns_server (ndisc, id, "2001:db8:c:c::1", now, 10);
 	nm_fake_ndisc_add_dns_domain (ndisc, id, "foobar.com", now, 10);
@@ -268,13 +268,13 @@ test_everything (void)
 	/* expire everything from the first RA in the second */
 	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
 	g_assert (id);
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 0, NM_NDISC_PREFERENCE_MEDIUM);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 0, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 0, 0, 0);
 	nm_fake_ndisc_add_dns_server (ndisc, id, "2001:db8:c:c::1", now, 0);
 	nm_fake_ndisc_add_dns_domain (ndisc, id, "foobar.com", now, 0);
 
 	/* and add some new stuff */
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::2", now, 10, NM_NDISC_PREFERENCE_MEDIUM);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::2", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10);
 	nm_fake_ndisc_add_dns_server (ndisc, id, "2001:db8:c:c::2", now, 10);
 	nm_fake_ndisc_add_dns_domain (ndisc, id, "foobar2.com", now, 10);
@@ -298,7 +298,67 @@ test_everything (void)
 }
 
 static void
-test_preference_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
+test_preference_order_cb (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
+{
+	NMNDiscConfigMap changed = changed_int;
+
+	if (data->counter == 1) {
+		g_assert_cmpint (changed, ==, NM_NDISC_CONFIG_GATEWAYS |
+		                              NM_NDISC_CONFIG_ADDRESSES |
+		                              NM_NDISC_CONFIG_ROUTES);
+
+		g_assert_cmpint (rdata->gateways_n, ==, 2);
+		match_gateway (rdata, 0, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
+		match_gateway (rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_LOW);
+		g_assert_cmpint (rdata->addresses_n, ==, 2);
+		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
+		match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
+		g_assert_cmpint (rdata->routes_n, ==, 2);
+		match_route (rdata, 0, "2001:db8:a:b::", 64, "fe80::2", data->timestamp1 + 1, 10, 10);
+		match_route (rdata, 1, "2001:db8:a:a::", 64, "fe80::1", data->timestamp1, 10, 5);
+
+		g_assert (nm_fake_ndisc_done (NM_FAKE_NDISC (ndisc)));
+		g_main_loop_quit (data->loop);
+	}
+
+	data->counter++;
+}
+
+static void
+test_preference_order (void)
+{
+	NMFakeNDisc *ndisc = ndisc_new ();
+	guint32 now = nm_utils_get_monotonic_timestamp_s ();
+	TestData data = { g_main_loop_new (NULL, FALSE), 0, 0, now };
+	guint id;
+
+	/* Test insertion order of gateways */
+
+	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+	g_assert (id);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
+	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 5);
+
+	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
+	g_assert (id);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::2", ++now, 10, NM_ICMPV6_ROUTER_PREF_LOW);
+	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10);
+
+	g_signal_connect (ndisc,
+	                  NM_NDISC_CONFIG_RECEIVED,
+	                  G_CALLBACK (test_preference_order_cb),
+	                  &data);
+
+	nm_ndisc_start (NM_NDISC (ndisc));
+	g_main_loop_run (data.loop);
+	g_assert_cmpint (data.counter, ==, 2);
+
+	g_object_unref (ndisc);
+	g_main_loop_unref (data.loop);
+}
+
+static void
+test_preference_changed_cb (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, TestData *data)
 {
 	NMNDiscConfigMap changed = changed_int;
 
@@ -307,8 +367,8 @@ test_preference_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 		                              NM_NDISC_CONFIG_ADDRESSES |
 		                              NM_NDISC_CONFIG_ROUTES);
 		g_assert_cmpint (rdata->gateways_n, ==, 2);
-		match_gateway (rdata, 0, "fe80::2", data->timestamp1 + 1, 10, NM_NDISC_PREFERENCE_MEDIUM);
-		match_gateway (rdata, 1, "fe80::1", data->timestamp1, 10, NM_NDISC_PREFERENCE_LOW);
+		match_gateway (rdata, 0, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
+		match_gateway (rdata, 1, "fe80::1", data->timestamp1, 10, NM_ICMPV6_ROUTER_PREF_LOW);
 		g_assert_cmpint (rdata->addresses_n, ==, 2);
 		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1, 10, 10);
 		match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
@@ -321,8 +381,8 @@ test_preference_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 		                              NM_NDISC_CONFIG_ROUTES);
 
 		g_assert_cmpint (rdata->gateways_n, ==, 2);
-		match_gateway (rdata, 0, "fe80::1", data->timestamp1 + 2, 10, NM_NDISC_PREFERENCE_HIGH);
-		match_gateway (rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_NDISC_PREFERENCE_MEDIUM);
+		match_gateway (rdata, 0, "fe80::1", data->timestamp1 + 2, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
+		match_gateway (rdata, 1, "fe80::2", data->timestamp1 + 1, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 		g_assert_cmpint (rdata->addresses_n, ==, 2);
 		match_address (rdata, 0, "2001:db8:a:a::1", data->timestamp1 + 2, 10, 10);
 		match_address (rdata, 1, "2001:db8:a:b::1", data->timestamp1 + 1, 10, 10);
@@ -338,7 +398,7 @@ test_preference_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed
 }
 
 static void
-test_preference (void)
+test_preference_changed (void)
 {
 	NMFakeNDisc *ndisc = ndisc_new ();
 	guint32 now = nm_utils_get_monotonic_timestamp_s ();
@@ -352,22 +412,22 @@ test_preference (void)
 
 	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
 	g_assert (id);
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_NDISC_PREFERENCE_LOW);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_LOW);
 	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 5);
 
 	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
 	g_assert (id);
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::2", ++now, 10, NM_NDISC_PREFERENCE_MEDIUM);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::2", ++now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:b::", 64, "fe80::2", now, 10, 10, 10);
 
 	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
 	g_assert (id);
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", ++now, 10, NM_NDISC_PREFERENCE_HIGH);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", ++now, 10, NM_ICMPV6_ROUTER_PREF_HIGH);
 	nm_fake_ndisc_add_prefix (ndisc, id, "2001:db8:a:a::", 64, "fe80::1", now, 10, 10, 15);
 
 	g_signal_connect (ndisc,
 	                  NM_NDISC_CONFIG_RECEIVED,
-	                  G_CALLBACK (test_preference_changed),
+	                  G_CALLBACK (test_preference_changed_cb),
 	                  &data);
 
 	nm_ndisc_start (NM_NDISC (ndisc));
@@ -411,7 +471,7 @@ test_dns_solicit_loop_rs_sent (NMFakeNDisc *ndisc, TestData *data)
 		 */
 		id = nm_fake_ndisc_add_ra (ndisc, 0, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
 		g_assert (id);
-		nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_NDISC_PREFERENCE_MEDIUM);
+		nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_MEDIUM);
 
 		nm_fake_ndisc_emit_new_ras (ndisc);
 	} else if (data->rs_counter >= 6) {
@@ -440,7 +500,7 @@ test_dns_solicit_loop (void)
 
 	id = nm_fake_ndisc_add_ra (ndisc, 1, NM_NDISC_DHCP_LEVEL_NONE, 4, 1500);
 	g_assert (id);
-	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_NDISC_PREFERENCE_LOW);
+	nm_fake_ndisc_add_gateway (ndisc, id, "fe80::1", now, 10, NM_ICMPV6_ROUTER_PREF_LOW);
 	nm_fake_ndisc_add_dns_server (ndisc, id, "2001:db8:c:c::1", now, 6);
 
 	g_signal_connect (ndisc,
@@ -476,7 +536,8 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/ndisc/simple", test_simple);
 	g_test_add_func ("/ndisc/everything-changed", test_everything);
-	g_test_add_func ("/ndisc/preference-changed", test_preference);
+	g_test_add_func ("/ndisc/preference-order", test_preference_order);
+	g_test_add_func ("/ndisc/preference-changed", test_preference_changed);
 	g_test_add_func ("/ndisc/dns-solicit-loop", test_dns_solicit_loop);
 
 	return g_test_run ();
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index 862754f9..002f11f7 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -44,6 +44,8 @@ typedef struct _NMActiveConnectionPrivate {
 
 	char *pending_activation_id;
 
+	NMActivationStateFlags state_flags;
+
 	NMActiveConnectionState state;
 	bool is_default:1;
 	bool is_default6:1;
@@ -73,6 +75,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMActiveConnection,
 	PROP_SPECIFIC_OBJECT,
 	PROP_DEVICES,
 	PROP_STATE,
+	PROP_STATE_FLAGS,
 	PROP_DEFAULT,
 	PROP_IP4_CONFIG,
 	PROP_DHCP4_CONFIG,
@@ -120,12 +123,11 @@ static void _set_activation_type_managed (NMActiveConnection *self);
 #define _NMLOG(level, ...) \
     G_STMT_START { \
         char _sbuf[64]; \
-        NMDevice *_device = (self) ? NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->device : NULL; \
-        NMConnection *_applied_connection = _device ? NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->applied_connection : NULL; \
+        NMActiveConnectionPrivate *_priv = self ? NM_ACTIVE_CONNECTION_GET_PRIVATE (self) : NULL; \
         \
         nm_log ((level), _NMLOG_DOMAIN, \
-                (_device) ? nm_device_get_iface (_device) : NULL, \
-                (_applied_connection) ? nm_connection_get_uuid (_applied_connection) : NULL, \
+                (_priv && _priv->device) ? nm_device_get_iface (_priv->device) : NULL, \
+                (_priv && _priv->applied_connection) ? nm_connection_get_uuid (_priv->applied_connection) : NULL, \
                 "%s%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
                 _NMLOG_PREFIX_NAME, \
                 self ? nm_sprintf_buf (_sbuf, "[%p]", self) : "" \
@@ -144,6 +146,16 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_state_to_string, NMActiveConnectionState,
 );
 #define state_to_string(state) NM_UTILS_LOOKUP_STR (_state_to_string, state)
 
+NM_UTILS_FLAGS2STR_DEFINE_STATIC (_state_flags_to_string, NMActivationStateFlags,
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_NONE,                 "none"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_MASTER,            "is-master"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IS_SLAVE,             "is-slave"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_LAYER2_READY,         "layer2-ready"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP4_READY,            "ip4-ready"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_IP6_READY,            "ip6-ready"),
+	NM_UTILS_FLAGS2STR (NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES,    "master-has-slaves"),
+);
+
 /*****************************************************************************/
 
 static void
@@ -280,6 +292,33 @@ nm_active_connection_set_state (NMActiveConnection *self,
 	}
 }
 
+NMActivationStateFlags
+nm_active_connection_get_state_flags (NMActiveConnection *self)
+{
+	return NM_ACTIVE_CONNECTION_GET_PRIVATE (self)->state_flags;
+}
+
+void
+nm_active_connection_set_state_flags_full (NMActiveConnection *self,
+                                           NMActivationStateFlags state_flags,
+                                           NMActivationStateFlags mask)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+	NMActivationStateFlags f;
+
+	f = (priv->state_flags & ~mask) | (state_flags & mask);
+	if (f != priv->state_flags) {
+		char buf1[G_N_ELEMENTS (_nm_utils_to_string_buffer)];
+		char buf2[G_N_ELEMENTS (_nm_utils_to_string_buffer)];
+
+		_LOGD ("set state-flags %s (was %s)",
+		       _state_flags_to_string (f, buf1, sizeof (buf1)),
+		       _state_flags_to_string (priv->state_flags, buf2, sizeof (buf2)));
+		priv->state_flags = f;
+		_notify (self, PROP_STATE_FLAGS);
+	}
+}
+
 const char *
 nm_active_connection_get_settings_connection_id (NMActiveConnection *self)
 {
@@ -331,6 +370,39 @@ nm_active_connection_get_applied_connection (NMActiveConnection *self)
 	return con;
 }
 
+static void
+_set_applied_connection_take (NMActiveConnection *self,
+                              NMConnection *applied_connection)
+{
+	NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	NMActivationStateFlags flags_val = 0;
+
+	nm_assert (NM_IS_CONNECTION (applied_connection));
+	nm_assert (!priv->applied_connection);
+
+	/* we take ownership of @applied_connection. Ensure to pass in a reference. */
+	priv->applied_connection = applied_connection;
+	nm_connection_clear_secrets (priv->applied_connection);
+
+	/* we determine whether the connection is a master/slave, based solely
+	 * on the connection properties itself. */
+	s_con = nm_connection_get_setting_connection (priv->applied_connection);
+	if (nm_setting_connection_get_master (s_con))
+		flags_val |= NM_ACTIVATION_STATE_FLAG_IS_SLAVE;
+
+	if (NM_IN_STRSET (nm_setting_connection_get_connection_type (s_con),
+	                  NM_SETTING_BOND_SETTING_NAME,
+	                  NM_SETTING_BRIDGE_SETTING_NAME,
+	                  NM_SETTING_TEAM_SETTING_NAME))
+		flags_val |= NM_ACTIVATION_STATE_FLAG_IS_MASTER;
+
+	nm_active_connection_set_state_flags_full (self,
+	                                           flags_val,
+	                                             NM_ACTIVATION_STATE_FLAG_IS_MASTER
+	                                           | NM_ACTIVATION_STATE_FLAG_IS_SLAVE);
+}
+
 void
 nm_active_connection_set_settings_connection (NMActiveConnection *self,
                                               NMSettingsConnection *connection)
@@ -355,8 +427,9 @@ nm_active_connection_set_settings_connection (NMActiveConnection *self,
 	g_return_if_fail (!nm_exported_object_is_exported (NM_EXPORTED_OBJECT (self)));
 
 	_set_settings_connection (self, connection);
-	priv->applied_connection = nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection));
-	nm_connection_clear_secrets (priv->applied_connection);
+
+	_set_applied_connection_take (self,
+	                              nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection)));
 }
 
 gboolean
@@ -1091,6 +1164,9 @@ get_property (GObject *object, guint prop_id,
 			g_value_set_uint (value, NM_ACTIVE_CONNECTION_STATE_ACTIVATING);
 		}
 		break;
+	case PROP_STATE_FLAGS:
+		g_value_set_uint (value, priv->state_flags);
+		break;
 	case PROP_DEFAULT:
 		g_value_set_boolean (value, priv->is_default);
 		break;
@@ -1151,8 +1227,15 @@ set_property (GObject *object, guint prop_id,
 	case PROP_INT_APPLIED_CONNECTION:
 		/* construct-only */
 		acon = g_value_get_object (value);
-		if (acon)
+		if (acon) {
+			/* we don't call _set_applied_connection_take() yet, because the instance
+			 * is not yet fully initialized. We are currently in the process of setting
+			 * the constructor properties.
+			 *
+			 * For now, just piggyback the connection, but call _set_applied_connection_take()
+			 * in constructed(). */
 			priv->applied_connection = g_object_ref (acon);
+		}
 		break;
 	case PROP_INT_DEVICE:
 		/* construct-only */
@@ -1220,17 +1303,25 @@ constructed (GObject *object)
 
 	G_OBJECT_CLASS (nm_active_connection_parent_class)->constructed (object);
 
-	if (!priv->applied_connection && priv->settings_connection)
+	if (   !priv->applied_connection
+	    && priv->settings_connection)
 		priv->applied_connection = nm_simple_connection_new_clone (NM_CONNECTION (priv->settings_connection));
 
-	if (priv->applied_connection)
-		nm_connection_clear_secrets (priv->applied_connection);
-
 	_LOGD ("constructed (%s, version-id %llu, type %s)",
 	       G_OBJECT_TYPE_NAME (self),
 	       (unsigned long long) priv->version_id,
 	       nm_activation_type_to_string (priv->activation_type));
 
+	if (priv->applied_connection) {
+		/* priv->applied_connection was set during the construction of the object.
+		 * It's not yet fully initialized, so do that now.
+		 *
+		 * We delayed that, because we may log in _set_applied_connection_take(), and the
+		 * first logging line should be "constructed" above). */
+		_set_applied_connection_take (self,
+		                              g_steal_pointer (&priv->applied_connection));
+	}
+
 	g_return_if_fail (priv->subject);
 }
 
@@ -1330,6 +1421,12 @@ nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
 	                        G_PARAM_READABLE |
 	                        G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_STATE_FLAGS] =
+	     g_param_spec_uint (NM_ACTIVE_CONNECTION_STATE_FLAGS, "", "",
+	                        0, G_MAXUINT32, NM_ACTIVATION_STATE_FLAG_NONE,
+	                        G_PARAM_READABLE |
+	                        G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_DEFAULT] =
 	     g_param_spec_boolean (NM_ACTIVE_CONNECTION_DEFAULT, "", "",
 	                           FALSE,
diff --git a/src/nm-active-connection.h b/src/nm-active-connection.h
index 8d3478c7..5562b42f 100644
--- a/src/nm-active-connection.h
+++ b/src/nm-active-connection.h
@@ -39,6 +39,7 @@
 #define NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT "specific-object"
 #define NM_ACTIVE_CONNECTION_DEVICES         "devices"
 #define NM_ACTIVE_CONNECTION_STATE           "state"
+#define NM_ACTIVE_CONNECTION_STATE_FLAGS     "state-flags"
 #define NM_ACTIVE_CONNECTION_DEFAULT         "default"
 #define NM_ACTIVE_CONNECTION_IP4_CONFIG      "ip4-config"
 #define NM_ACTIVE_CONNECTION_DHCP4_CONFIG    "dhcp4-config"
@@ -145,6 +146,19 @@ void          nm_active_connection_set_state (NMActiveConnection *self,
                                               NMActiveConnectionState state,
                                               NMActiveConnectionStateReason reason);
 
+NMActivationStateFlags  nm_active_connection_get_state_flags (NMActiveConnection *self);
+
+void          nm_active_connection_set_state_flags_full (NMActiveConnection *self,
+                                                         NMActivationStateFlags state_flags,
+                                                         NMActivationStateFlags mask);
+
+static inline void
+nm_active_connection_set_state_flags (NMActiveConnection *self,
+                                      NMActivationStateFlags state_flags)
+{
+	nm_active_connection_set_state_flags_full (self, state_flags, state_flags);
+}
+
 NMDevice *    nm_active_connection_get_device (NMActiveConnection *self);
 
 gboolean      nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device);
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index b8e64ceb..f1aff430 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -24,6 +24,7 @@
 
 #include <string.h>
 
+#include "nm-utils/nm-hash-utils.h"
 #include "nm-setting-connection.h"
 #include "nm-auth-subject.h"
 #include "nm-auth-manager.h"
@@ -131,7 +132,7 @@ nm_auth_chain_new_subject (NMAuthSubject *subject,
 
 	self = g_slice_new0 (NMAuthChain);
 	self->refcount = 1;
-	self->data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, chain_data_free);
+	self->data = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, chain_data_free);
 	self->done_func = done_func;
 	self->user_data = user_data;
 	self->context = context ? g_object_ref (context) : NULL;
diff --git a/src/nm-bus-manager.c b/src/nm-bus-manager.c
index ca07e3d9..f6b86e90 100644
--- a/src/nm-bus-manager.c
+++ b/src/nm-bus-manager.c
@@ -171,7 +171,10 @@ close_connection_in_idle (gpointer user_data)
 
 	g_hash_table_iter_init (&iter, server->obj_managers);
 	while (g_hash_table_iter_next (&iter, (gpointer) &manager, NULL)) {
-		if (g_dbus_object_manager_server_get_connection (manager) == info->connection) {
+		gs_unref_object GDBusConnection *connection = NULL;
+
+		connection = g_dbus_object_manager_server_get_connection (manager);
+		if (connection == info->connection) {
 			g_hash_table_iter_remove (&iter);
 			break;
 		}
@@ -250,6 +253,7 @@ private_server_manager_destroy (GDBusObjectManagerServer *manager)
 		g_dbus_connection_close (connection, NULL, NULL, NULL);
 	g_dbus_object_manager_server_set_connection (manager, NULL);
 	g_object_unref (manager);
+	g_object_unref (connection);
 }
 
 static gboolean
@@ -368,7 +372,10 @@ private_server_get_connection_owner (PrivateServer *s, GDBusConnection *connecti
 
 	g_hash_table_iter_init (&iter, s->obj_managers);
 	while (g_hash_table_iter_next (&iter, (gpointer) &manager, (gpointer) &owner)) {
-		if (g_dbus_object_manager_server_get_connection (manager) == connection)
+		gs_unref_object GDBusConnection *c = NULL;
+
+		c = g_dbus_object_manager_server_get_connection (manager);
+		if (c == connection)
 			return owner;
 	}
 	return NULL;
@@ -606,7 +613,10 @@ nm_bus_manager_get_unix_user (NMBusManager *self,
 
 	/* Check if it's a private connection sender, which we fake */
 	for (iter = priv->private_servers; iter; iter = iter->next) {
-		if (private_server_get_connection_by_owner (iter->data, sender)) {
+		gs_unref_object GDBusConnection *connection = NULL;
+
+		connection = private_server_get_connection_by_owner (iter->data, sender);
+		if (connection) {
 			*out_uid = 0;
 			return TRUE;
 		}
diff --git a/src/nm-checkpoint-manager.c b/src/nm-checkpoint-manager.c
index 7f9aa398..033c11cc 100644
--- a/src/nm-checkpoint-manager.c
+++ b/src/nm-checkpoint-manager.c
@@ -283,7 +283,7 @@ nm_checkpoint_manager_new (NMManager *manager)
 	 * of NMManager shall surpass the lifetime of the NMCheckpointManager
 	 * instance. */
 	self->_manager = manager;
-	self->checkpoints = g_hash_table_new_full (g_str_hash, g_str_equal,
+	self->checkpoints = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                           NULL, checkpoint_destroy);
 
 	return self;
diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c
index 04f00e89..c3a2e743 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -259,8 +259,8 @@ activate:
 					nm_connection_replace_settings_from_connection (NM_CONNECTION (connection),
 					                                                dev_checkpoint->settings_connection);
 					nm_settings_connection_commit_changes (connection,
-					                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 					                                       NULL,
+					                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 					                                       NULL);
 				}
 			} else {
@@ -343,7 +343,7 @@ next_dev:
 			                            nm_settings_connection_get_uuid (con))) {
 				_LOGD ("rollback: deleting new connection %s",
 				       nm_settings_connection_get_uuid (con));
-				nm_settings_connection_delete (con, NULL, NULL);
+				nm_settings_connection_delete (con, NULL);
 			}
 		}
 	}
@@ -506,7 +506,7 @@ nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_time
 	priv->flags = flags;
 
 	if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
-		priv->connection_uuids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+		priv->connection_uuids = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 		for (con = nm_settings_get_connections (nm_settings_get (), NULL); *con; con++) {
 			g_hash_table_add (priv->connection_uuids,
 			                  g_strdup (nm_settings_connection_get_uuid (*con)));
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 8f4d1121..5b06e00a 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -65,6 +65,7 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 	PROP_CONFIG_DESCRIPTION,
 	PROP_KEYFILE_USER,
 	PROP_KEYFILE_INTERN,
+	PROP_CONNECTIVITY_ENABLED,
 	PROP_CONNECTIVITY_URI,
 	PROP_CONNECTIVITY_INTERVAL,
 	PROP_CONNECTIVITY_RESPONSE,
@@ -88,6 +89,7 @@ typedef struct {
 	MatchSectionInfo *device_infos;
 
 	struct {
+		gboolean enabled;
 		char *uri;
 		char *response;
 		guint interval;
@@ -106,9 +108,6 @@ typedef struct {
 	char *rc_manager;
 
 	NMGlobalDnsConfig *global_dns;
-
-	/* mutable field */
-	char *value_cached;
 } NMConfigDataPrivate;
 
 struct _NMConfigData {
@@ -169,22 +168,6 @@ nm_config_data_get_value (const NMConfigData *self, const char *group, const cha
 	return nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, flags);
 }
 
-const char *nm_config_data_get_value_cached (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags)
-{
-	const NMConfigDataPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), NULL);
-	g_return_val_if_fail (group && *group, NULL);
-	g_return_val_if_fail (key && *key, NULL);
-
-	priv = NM_CONFIG_DATA_GET_PRIVATE (self);
-
-	/* we modify @value_cached. In C++ jargon, the field is mutable. */
-	g_free (((NMConfigDataPrivate *) priv)->value_cached);
-	((NMConfigDataPrivate *) priv)->value_cached = nm_config_keyfile_get_value (priv->keyfile, group, key, flags);
-	return priv->value_cached;
-}
-
 gboolean
 nm_config_data_has_value (const NMConfigData *self, const char *group, const char *key, NMConfigGetValueFlags flags)
 {
@@ -209,7 +192,7 @@ nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, c
 	g_return_val_if_fail (key && *key, default_value);
 
 	/* when parsing the boolean, base it on the raw value from g_key_file_get_value(). */
-	str = g_key_file_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, NULL);
+	str = nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, NM_CONFIG_GET_VALUE_RAW);
 	if (str) {
 		value = nm_config_parse_boolean (str, default_value);
 		g_free (str);
@@ -217,6 +200,28 @@ nm_config_data_get_value_boolean (const NMConfigData *self, const char *group, c
 	return 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)
+{
+	int errsv;
+	gint64 val;
+	char *str;
+
+	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), fallback);
+	g_return_val_if_fail (group && *group, fallback);
+	g_return_val_if_fail (key && *key, fallback);
+
+	str = nm_config_keyfile_get_value (NM_CONFIG_DATA_GET_PRIVATE (self)->keyfile, group, key, NM_CONFIG_GET_VALUE_NONE);
+	val = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback);
+	if (str) {
+		/* preserve errno from the parsing. */
+		errsv = errno;
+		g_free (str);
+		errno = errsv;
+	}
+	return val;
+}
+
 char **
 nm_config_data_get_plugins (const NMConfigData *self, gboolean allow_default)
 {
@@ -238,6 +243,14 @@ nm_config_data_get_plugins (const NMConfigData *self, gboolean allow_default)
 	return _nm_utils_strv_cleanup (list, TRUE, TRUE, TRUE);
 }
 
+gboolean
+nm_config_data_get_connectivity_enabled (const NMConfigData *self)
+{
+	g_return_val_if_fail (self, FALSE);
+
+	return NM_CONFIG_DATA_GET_PRIVATE (self)->connectivity.enabled;
+}
+
 const char *
 nm_config_data_get_connectivity_uri (const NMConfigData *self)
 {
@@ -304,15 +317,22 @@ nm_config_data_get_ignore_carrier (const NMConfigData *self, NMDevice *device)
 {
 	gs_free char *value = NULL;
 	gboolean has_match;
+	int m;
 
 	g_return_val_if_fail (NM_IS_CONFIG_DATA (self), FALSE);
 	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
 
 	value = nm_config_data_get_device_config (self, NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER, device, &has_match);
 	if (has_match)
-		return nm_config_parse_boolean (value, FALSE);
+		m = nm_config_parse_boolean (value, -1);
+	else
+		m = nm_device_spec_match_list_full (device, NM_CONFIG_DATA_GET_PRIVATE (self)->ignore_carrier, -1);
+
+	if (NM_IN_SET (m, TRUE, FALSE))
+		return m;
 
-	return nm_device_spec_match_list (device, NM_CONFIG_DATA_GET_PRIVATE (self)->ignore_carrier);
+	/* if ignore-carrier is not explicitly configed, then it depends on the device (type). */
+	return nm_device_ignore_carrier_by_default (device);
 }
 
 gboolean
@@ -858,7 +878,7 @@ load_global_dns (GKeyFile *keyfile, gboolean internal)
 		return NULL;
 
 	conf = g_malloc0 (sizeof (NMGlobalDnsConfig));
-	conf->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
+	conf->domains = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                       g_free, (GDestroyNotify) global_dns_domain_free);
 
 	strv = g_key_file_get_string_list (keyfile, group, "searches", NULL, NULL);
@@ -1060,7 +1080,7 @@ nm_global_dns_config_from_dbus (const GValue *value, GError **error)
 	}
 
 	dns_config = g_malloc0 (sizeof (NMGlobalDnsConfig));
-	dns_config->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
+	dns_config->domains = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                             g_free, (GDestroyNotify) global_dns_domain_free);
 
 	g_variant_iter_init (&iter, variant);
@@ -1373,7 +1393,8 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data)
 	    || g_strcmp0 (nm_config_data_get_config_description (old_data), nm_config_data_get_config_description (new_data)) != 0)
 		changes |= NM_CONFIG_CHANGE_CONFIG_FILES;
 
-	if (   nm_config_data_get_connectivity_interval (old_data) != nm_config_data_get_connectivity_interval (new_data)
+	if (   nm_config_data_get_connectivity_enabled (old_data) != nm_config_data_get_connectivity_enabled (new_data)
+	    || nm_config_data_get_connectivity_interval (old_data) != nm_config_data_get_connectivity_interval (new_data)
 	    || g_strcmp0 (nm_config_data_get_connectivity_uri (old_data), nm_config_data_get_connectivity_uri (new_data))
 	    || g_strcmp0 (nm_config_data_get_connectivity_response (old_data), nm_config_data_get_connectivity_response (new_data)))
 		changes |= NM_CONFIG_CHANGE_CONNECTIVITY;
@@ -1413,6 +1434,9 @@ get_property (GObject *object,
 	case PROP_CONFIG_DESCRIPTION:
 		g_value_set_string (value, nm_config_data_get_config_description (self));
 		break;
+	case PROP_CONNECTIVITY_ENABLED:
+		g_value_set_boolean (value, nm_config_data_get_connectivity_enabled (self));
+		break;
 	case PROP_CONNECTIVITY_URI:
 		g_value_set_string (value, nm_config_data_get_connectivity_uri (self));
 		break;
@@ -1510,6 +1534,7 @@ constructed (GObject *object)
 	priv->connection_infos = _match_section_infos_construct (priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_CONNECTION);
 	priv->device_infos = _match_section_infos_construct (priv->keyfile, NM_CONFIG_KEYFILE_GROUPPREFIX_DEVICE);
 
+	priv->connectivity.enabled = nm_config_keyfile_get_boolean (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "enabled", TRUE);
 	priv->connectivity.uri = nm_strstrip (g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "uri", NULL));
 	priv->connectivity.response = g_key_file_get_string (priv->keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, "response", NULL);
 
@@ -1614,8 +1639,6 @@ finalize (GObject *gobject)
 		g_key_file_unref (priv->keyfile_intern);
 
 	G_OBJECT_CLASS (nm_config_data_parent_class)->finalize (gobject);
-
-	g_free (priv->value_cached);
 }
 
 static void
@@ -1656,6 +1679,12 @@ nm_config_data_class_init (NMConfigDataClass *config_class)
 	                         G_PARAM_CONSTRUCT_ONLY |
 	                         G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_CONNECTIVITY_ENABLED] =
+	     g_param_spec_string (NM_CONFIG_DATA_CONNECTIVITY_ENABLED, "", "",
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_CONNECTIVITY_URI] =
 	     g_param_spec_string (NM_CONFIG_DATA_CONNECTIVITY_URI, "", "",
 	                          NULL,
diff --git a/src/nm-config-data.h b/src/nm-config-data.h
index 98c66751..3efe4259 100644
--- a/src/nm-config-data.h
+++ b/src/nm-config-data.h
@@ -32,6 +32,7 @@
 #define NM_CONFIG_DATA_CONFIG_DESCRIPTION    "config-description"
 #define NM_CONFIG_DATA_KEYFILE_USER          "keyfile-user"
 #define NM_CONFIG_DATA_KEYFILE_INTERN        "keyfile-intern"
+#define NM_CONFIG_DATA_CONNECTIVITY_ENABLED  "connectivity-enabled"
 #define NM_CONFIG_DATA_CONNECTIVITY_URI      "connectivity-uri"
 #define NM_CONFIG_DATA_CONNECTIVITY_INTERVAL "connectivity-interval"
 #define NM_CONFIG_DATA_CONNECTIVITY_RESPONSE "connectivity-response"
@@ -155,10 +156,11 @@ 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);
-const char *nm_config_data_get_value_cached (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);
+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);
+gboolean nm_config_data_get_connectivity_enabled (const NMConfigData *config_data);
 const char *nm_config_data_get_connectivity_uri (const NMConfigData *config_data);
 guint nm_config_data_get_connectivity_interval (const NMConfigData *config_data);
 const char *nm_config_data_get_connectivity_response (const NMConfigData *config_data);
diff --git a/src/nm-config.c b/src/nm-config.c
index 91c21de7..de727c98 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -483,20 +483,20 @@ nm_config_cmd_line_options_add_to_entries (NMConfigCmdLineOptions *cli,
 
 	{
 		GOptionEntry config_options[] = {
-			{ "config", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_main_file, N_("Config file location"), N_(DEFAULT_CONFIG_MAIN_FILE) },
-			{ "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), N_(DEFAULT_CONFIG_DIR) },
-			{ "system-config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->system_config_dir, N_("System config directory location"), N_(DEFAULT_SYSTEM_CONFIG_DIR) },
-			{ "intern-config", 0, 0, G_OPTION_ARG_FILENAME, &cli->intern_config_file, N_("Internal config file location"), N_(DEFAULT_INTERN_CONFIG_FILE) },
-			{ "state-file", 0, 0, G_OPTION_ARG_FILENAME, &cli->state_file, N_("State file location"), N_(DEFAULT_STATE_FILE) },
-			{ "no-auto-default", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &cli->no_auto_default_file, N_("State file for no-auto-default devices"), N_(DEFAULT_NO_AUTO_DEFAULT_FILE) },
-			{ "plugins", 0, 0, G_OPTION_ARG_STRING, &cli->plugins, N_("List of plugins separated by ','"), N_(NM_CONFIG_DEFAULT_MAIN_PLUGINS) },
+			{ "config", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_main_file, N_("Config file location"), DEFAULT_CONFIG_MAIN_FILE },
+			{ "config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->config_dir, N_("Config directory location"), DEFAULT_CONFIG_DIR },
+			{ "system-config-dir", 0, 0, G_OPTION_ARG_FILENAME, &cli->system_config_dir, N_("System config directory location"), DEFAULT_SYSTEM_CONFIG_DIR },
+			{ "intern-config", 0, 0, G_OPTION_ARG_FILENAME, &cli->intern_config_file, N_("Internal config file location"), DEFAULT_INTERN_CONFIG_FILE },
+			{ "state-file", 0, 0, G_OPTION_ARG_FILENAME, &cli->state_file, N_("State file location"), DEFAULT_STATE_FILE },
+			{ "no-auto-default", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &cli->no_auto_default_file, N_("State file for no-auto-default devices"), DEFAULT_NO_AUTO_DEFAULT_FILE },
+			{ "plugins", 0, 0, G_OPTION_ARG_STRING, &cli->plugins, N_("List of plugins separated by ','"), NM_CONFIG_DEFAULT_MAIN_PLUGINS },
 			{ "configure-and-quit", 0, 0, G_OPTION_ARG_NONE, &cli->configure_and_quit, N_("Quit after initial configuration"), NULL },
 			{ "debug", 'd', 0, G_OPTION_ARG_NONE, &cli->is_debug, N_("Don't become a daemon, and log to stderr"), NULL },
 
 				/* These three are hidden for now, and should eventually just go away. */
 			{ "connectivity-uri", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_uri, N_("An http(s) address for checking internet connectivity"), "http://example.com" },
 			{ "connectivity-interval", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_INT, &cli->connectivity_interval, N_("The interval between connectivity checks (in seconds)"), G_STRINGIFY (NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL) },
-			{ "connectivity-response", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_response, N_("The expected start of the response"), N_(NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE) },
+			{ "connectivity-response", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &cli->connectivity_response, N_("The expected start of the response"), NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE },
 			{ 0 },
 		};
 
@@ -1590,6 +1590,31 @@ done:
 	return TRUE;
 }
 
+/*****************************************************************************/
+
+void nm_config_set_connectivity_check_enabled (NMConfig *self,
+                                               gboolean enabled)
+{
+	NMConfigPrivate *priv;
+	GKeyFile *keyfile;
+
+	g_return_if_fail (NM_IS_CONFIG (self));
+
+	priv = NM_CONFIG_GET_PRIVATE (self);
+	g_return_if_fail (priv->config_data);
+
+	keyfile = nm_config_data_clone_keyfile_intern (priv->config_data);
+
+	/* Remove existing groups */
+	g_key_file_remove_group (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY, NULL);
+
+	g_key_file_set_value (keyfile, NM_CONFIG_KEYFILE_GROUP_CONNECTIVITY,
+	                      "enabled", enabled ? "true" : "false");
+
+	nm_config_set_values (self, keyfile, TRUE, FALSE);
+	g_key_file_unref (keyfile);
+}
+
 /**
  * nm_config_set_values:
  * @self: the NMConfig instance
diff --git a/src/nm-config.h b/src/nm-config.h
index c5ff7c67..47e92988 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -75,6 +75,7 @@
 #define NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED              "managed"
 #define NM_CONFIG_KEYFILE_KEY_AUDIT                         "audit"
 
+#define NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED                "managed"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER         "ignore-carrier"
 #define NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS          "sriov-num-vfs"
 
@@ -179,6 +180,8 @@ void _nm_config_sort_groups (char **groups, gsize ngroups);
 
 gboolean nm_config_set_global_dns (NMConfig *self, NMGlobalDnsConfig *global_dns, GError **error);
 
+void nm_config_set_connectivity_check_enabled (NMConfig *self, gboolean enabled);
+
 /* internal defines ... */
 extern guint _nm_config_match_nm_version;
 extern char *_nm_config_match_env;
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index b895a82b..4ccc5719 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -35,6 +35,7 @@
 typedef struct {
 	char *uri;
 	char *response;
+	gboolean enabled;
 	guint interval;
 	NMConfig *config;
 	guint periodic_check_id;
@@ -124,6 +125,7 @@ finish_cb_data (ConCheckCbData *cb_data, NMConnectivityState new_state)
 	g_object_unref (cb_data->simple);
 	curl_slist_free_all (cb_data->request_headers);
 	g_free (cb_data->response);
+	g_free (cb_data->msg);
 	g_free (cb_data->ifspec);
 	g_source_remove (cb_data->timeout_id);
 	g_slice_free (ConCheckCbData, cb_data);
@@ -343,7 +345,7 @@ nm_connectivity_check_async (NMConnectivity      *self,
 	simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
 	                                    nm_connectivity_check_async);
 
-	if (priv->uri && priv->interval && priv->curl_mhandle)
+	if (priv->enabled)
 		ehandle = curl_easy_init ();
 
 	if (ehandle) {
@@ -401,7 +403,7 @@ nm_connectivity_check_enabled (NMConnectivity *self)
 {
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
-	return (priv->uri && priv->interval && priv->curl_mhandle);
+	return priv->enabled;
 }
 
 /*****************************************************************************/
@@ -419,6 +421,7 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 	NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 	const char *uri, *response;
 	guint interval;
+	gboolean enabled;
 	gboolean changed = FALSE;
 
 	/* Set the URI. */
@@ -454,6 +457,18 @@ update_config (NMConnectivity *self, NMConfigData *config_data)
 		changed = TRUE;
 	}
 
+	/* Set enabled flag. */
+	enabled = nm_config_data_get_connectivity_enabled (config_data);
+	/* connectivity checking also requires a valid URI, interval and
+	 * curl_mhandle */
+	if (!(priv->uri && priv->interval && priv->curl_mhandle)) {
+		enabled = FALSE;
+	}
+	if (priv->enabled != enabled) {
+		priv->enabled = enabled;
+		changed = TRUE;
+	}
+
 	/* Set the response. */
 	response = nm_config_data_get_connectivity_response (config_data);
 	if (g_strcmp0 (response, priv->response) != 0) {
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index aaaf7b6c..a8ff3513 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -26,7 +26,6 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <string.h>
-#include <poll.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <resolv.h>
@@ -37,6 +36,7 @@
 #include <linux/if_infiniband.h>
 #include <net/ethernet.h>
 
+#include "nm-utils/nm-random-utils.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-connection.h"
@@ -110,10 +110,6 @@ _nm_utils_set_testing (NMUtilsTestFlags flags)
 
 /*****************************************************************************/
 
-const NMIPAddr nm_ip_addr_zero = NMIPAddrInit;
-
-/*****************************************************************************/
-
 static GSList *_singletons = NULL;
 static gboolean _singletons_shutdown = FALSE;
 
@@ -265,7 +261,7 @@ nm_utils_ipx_address_clear_host_address (int family, gpointer dst, gconstpointer
 in_addr_t
 nm_utils_ip4_address_clear_host_address (in_addr_t addr, guint8 plen)
 {
-	return addr & nm_utils_ip4_prefix_to_netmask (plen);
+	return addr & _nm_utils_ip4_prefix_to_netmask (plen);
 }
 
 /* nm_utils_ip6_address_clear_host_address:
@@ -274,15 +270,17 @@ nm_utils_ip4_address_clear_host_address (in_addr_t addr, guint8 plen)
  * @plen: prefix length of network
  *
  * Note: this function is self assignment safe, to update @src inplace, set both
- * @dst and @src to the same destination.
+ * @dst and @src to the same destination or set @src NULL.
  */
 const struct in6_addr *
 nm_utils_ip6_address_clear_host_address (struct in6_addr *dst, const struct in6_addr *src, guint8 plen)
 {
 	g_return_val_if_fail (plen <= 128, NULL);
-	g_return_val_if_fail (src, NULL);
 	g_return_val_if_fail (dst, NULL);
 
+	if (!src)
+		src = dst;
+
 	if (plen < 128) {
 		guint nbytes = plen / 8;
 		guint nbits = plen % 8;
@@ -301,28 +299,28 @@ nm_utils_ip6_address_clear_host_address (struct in6_addr *dst, const struct in6_
 	return dst;
 }
 
-gboolean
-nm_utils_ip6_address_same_prefix (const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint8 plen)
+int
+nm_utils_ip6_address_same_prefix_cmp (const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint8 plen)
 {
 	int nbytes;
-	guint8 t, m;
+	guint8 va, vb, m;
 
 	if (plen >= 128)
-		return memcmp (addr_a, addr_b, sizeof (struct in6_addr)) == 0;
-
-	nbytes = plen / 8;
-	if (nbytes) {
-		if (memcmp (addr_a, addr_b, nbytes) != 0)
-			return FALSE;
+		NM_CMP_DIRECT_MEMCMP (addr_a, addr_b, sizeof (struct in6_addr));
+	else {
+		nbytes = plen / 8;
+		if (nbytes)
+			NM_CMP_DIRECT_MEMCMP (addr_a, addr_b, nbytes);
+
+		plen = plen % 8;
+		if (plen != 0) {
+			m = ~((1 << (8 - plen)) - 1);
+			va = ((((const guint8 *) addr_a))[nbytes]) & m;
+			vb = ((((const guint8 *) addr_b))[nbytes]) & m;
+			NM_CMP_DIRECT (va, vb);
+		}
 	}
-
-	plen = plen % 8;
-	if (plen == 0)
-		return TRUE;
-
-	m = ~((1 << (8 - plen)) - 1);
-	t = ((((const guint8 *) addr_a))[nbytes]) ^ ((((const guint8 *) addr_b))[nbytes]);
-	return (t & m) == 0;
+	return 0;
 }
 
 /*****************************************************************************/
@@ -1820,81 +1818,6 @@ nm_match_spec_join (GSList *specs)
 
 /*****************************************************************************/
 
-char _nm_utils_to_string_buffer[];
-
-void
-nm_utils_to_string_buffer_init (char **buf, gsize *len)
-{
-	if (!*buf) {
-		*buf = _nm_utils_to_string_buffer;
-		*len = sizeof (_nm_utils_to_string_buffer);
-	}
-}
-
-gboolean
-nm_utils_to_string_buffer_init_null (gconstpointer obj, char **buf, gsize *len)
-{
-	nm_utils_to_string_buffer_init (buf, len);
-	if (!obj) {
-		g_strlcpy (*buf, "(null)", *len);
-		return FALSE;
-	}
-	return TRUE;
-}
-
-const char *
-nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs,
-                    gsize n_descs,
-                    unsigned flags,
-                    char *buf,
-                    gsize len)
-{
-	gsize i;
-	char *p;
-
-#if NM_MORE_ASSERTS > 10
-	nm_assert (descs);
-	nm_assert (n_descs > 0);
-	for (i = 0; i < n_descs; i++) {
-		gsize j;
-
-		nm_assert (descs[i].flag && nm_utils_is_power_of_two (descs[i].flag));
-		nm_assert (descs[i].name && descs[i].name[0]);
-		for (j = 0; j < i; j++)
-			nm_assert (descs[j].flag != descs[i].flag);
-	}
-#endif
-
-	nm_utils_to_string_buffer_init (&buf, &len);
-
-	if (!len)
-		return buf;
-
-	buf[0] = '\0';
-	if (!flags) {
-		return buf;
-	}
-
-	p = buf;
-	for (i = 0; flags && i < n_descs; i++) {
-		if (NM_FLAGS_HAS (flags, descs[i].flag)) {
-			flags &= ~descs[i].flag;
-
-			if (buf[0] != '\0')
-				nm_utils_strbuf_append_c (&p, &len, ',');
-			nm_utils_strbuf_append_str (&p, &len, descs[i].name);
-		}
-	}
-	if (flags) {
-		if (buf[0] != '\0')
-			nm_utils_strbuf_append_c (&p, &len, ',');
-		nm_utils_strbuf_append (&p, &len, "0x%x", flags);
-	}
-	return buf;
-};
-
-/*****************************************************************************/
-
 char *
 nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id)
 {
@@ -1952,101 +1875,109 @@ nm_utils_new_infiniband_name (char *name, const char *parent_name, int p_key)
 	return name;
 }
 
-/**
- * nm_utils_read_resolv_conf_nameservers():
- * @rc_contents: contents of a resolv.conf; or %NULL to read /etc/resolv.conf
- *
- * Reads all nameservers out of @rc_contents or /etc/resolv.conf and returns
- * them.
- *
- * Returns: a #GPtrArray of 'char *' elements of each nameserver line from
- * @contents or resolv.conf
- */
-GPtrArray *
-nm_utils_read_resolv_conf_nameservers (const char *rc_contents)
-{
-	GPtrArray *nameservers = NULL;
-	char *contents = NULL;
-	char **lines, **iter;
-	char *p;
-
-	if (rc_contents)
-		contents = g_strdup (rc_contents);
-	else {
-		if (!g_file_get_contents (_PATH_RESCONF, &contents, NULL, NULL))
-			return NULL;
-	}
-
-	nameservers = g_ptr_array_new_full (3, g_free);
-
-	lines = g_strsplit_set (contents, "\r\n", -1);
-	for (iter = lines; *iter; iter++) {
-		if (!g_str_has_prefix (*iter, "nameserver"))
-			continue;
-		p = *iter + strlen ("nameserver");
-		if (!g_ascii_isspace (*p++))
-			continue;
-		/* Skip intermediate whitespace */
-		while (g_ascii_isspace (*p))
-			p++;
-		g_strchomp (p);
+/*****************************************************************************/
 
-		g_ptr_array_add (nameservers, g_strdup (p));
-	}
-	g_strfreev (lines);
-	g_free (contents);
+gboolean
+nm_utils_resolve_conf_parse (int addr_family,
+                             const char *rc_contents,
+                             GArray *nameservers,
+                             GPtrArray *dns_options)
+{
+	guint i;
+	gboolean changed = FALSE;
+	gs_free const char **lines = NULL;
+	gsize l;
+
+	g_return_val_if_fail (rc_contents, FALSE);
+	g_return_val_if_fail (nameservers, FALSE);
+	g_return_val_if_fail (   (   addr_family == AF_INET
+	                          && g_array_get_element_size (nameservers) == sizeof (in_addr_t))
+	                      || (   addr_family == AF_INET6
+	                          && g_array_get_element_size (nameservers) == sizeof (struct in6_addr)), FALSE);
+
+	lines = nm_utils_strsplit_set (rc_contents, "\r\n");
+	if (!lines)
+		return FALSE;
 
-	return nameservers;
-}
+/* like glibc's MATCH() macro in resolv/res_init.c. */
+#define RC_MATCH(line, option, out_arg) \
+    ({ \
+        const char *const _line = (line); \
+        gboolean _match = FALSE; \
+        \
+        if (   (strncmp (_line, option, NM_STRLEN (option)) == 0) \
+            && (NM_IN_SET (_line[NM_STRLEN (option)], ' ', '\t'))) { \
+            _match = TRUE;\
+            (out_arg) = &_line[NM_STRLEN (option) + 1]; \
+        } \
+        _match; \
+    })
+
+	for (l = 0; lines[l]; l++) {
+		const char *const line = lines[l];
+		const char *s = NULL;
+
+		if (RC_MATCH (line, "nameserver", s)) {
+			gs_free char *s_cpy = NULL;
+			NMIPAddr ns;
+
+			s = nm_strstrip_avoid_copy (s, &s_cpy);
+			if (inet_pton (addr_family, s, &ns) != 1)
+				continue;
 
-/**
- * nm_utils_read_resolv_conf_dns_options():
- * @rc_contents: contents of a resolv.conf; or %NULL to read /etc/resolv.conf
- *
- * Reads all dns options out of @rc_contents or /etc/resolv.conf and returns
- * them.
- *
- * Returns: a #GPtrArray of 'char *' elements of each option
- */
-GPtrArray *
-nm_utils_read_resolv_conf_dns_options (const char *rc_contents)
-{
-	GPtrArray *options = NULL;
-	char *contents = NULL;
-	char **lines, **line_iter;
-	char **tokens, **token_iter;
-	char *p;
-
-	if (rc_contents)
-		contents = g_strdup (rc_contents);
-	else {
-		if (!g_file_get_contents (_PATH_RESCONF, &contents, NULL, NULL))
-			return NULL;
-	}
+			if (addr_family == AF_INET) {
+				if (!ns.addr4)
+					continue;
+				for (i = 0; i < nameservers->len; i++) {
+					if (g_array_index (nameservers, guint32, i) == ns.addr4)
+						break;
+				}
+			} else {
+				if (IN6_IS_ADDR_UNSPECIFIED (&ns.addr6))
+					continue;
+				for (i = 0; i < nameservers->len; i++) {
+					struct in6_addr *t = &g_array_index (nameservers, struct in6_addr, i);
 
-	options = g_ptr_array_new_full (3, g_free);
+					if (IN6_ARE_ADDR_EQUAL (t, &ns.addr6))
+						break;
+				}
+			}
 
-	lines = g_strsplit_set (contents, "\r\n", -1);
-	for (line_iter = lines; *line_iter; line_iter++) {
-		if (!g_str_has_prefix (*line_iter, "options"))
-			continue;
-		p = *line_iter + strlen ("options");
-		if (!g_ascii_isspace (*p++))
+			if (i == nameservers->len) {
+				g_array_append_val (nameservers, ns);
+				changed = TRUE;
+			}
 			continue;
+		}
 
-		tokens = g_strsplit (p, " ", 0);
-		for (token_iter = tokens; token_iter && *token_iter; token_iter++) {
-			g_strstrip (*token_iter);
-			if (!*token_iter[0])
+		if (RC_MATCH (line, "options", s)) {
+			if (!dns_options)
 				continue;
-			g_ptr_array_add (options, g_strdup (*token_iter));
+
+			s = nm_str_skip_leading_spaces (s);
+			if (s[0]) {
+				gs_free const char **tokens = NULL;
+				gsize i_tokens;
+
+				tokens = nm_utils_strsplit_set (s, " \t");
+				nm_assert (tokens);
+				for (i_tokens = 0; tokens[i_tokens]; i_tokens++) {
+					gs_free char *t = g_strstrip (g_strdup (tokens[i_tokens]));
+
+					if (   _nm_utils_dns_option_validate (t, NULL, NULL,
+					                                      addr_family == AF_INET6,
+					                                      _nm_utils_dns_option_descs)
+					    && _nm_utils_dns_option_find_idx (dns_options, t) < 0) {
+						g_ptr_array_add (dns_options, g_steal_pointer (&t));
+						changed = TRUE;
+					}
+				}
+			}
+			continue;
 		}
-		g_strfreev (tokens);
 	}
-	g_strfreev (lines);
-	g_free (contents);
 
-	return options;
+	return changed;
 }
 
 /*****************************************************************************/
@@ -2131,7 +2062,7 @@ monotonic_timestamp_get (struct timespec *tp)
 		break;
 	case 2:
 		/* fallback, return CLOCK_MONOTONIC. Kernels prior to 2.6.39
-		 * don't support CLOCK_BOOTTIME. */
+		 * (released on 18 May, 2011) don't support CLOCK_BOOTTIME. */
 		err = clock_gettime (CLOCK_MONOTONIC, tp);
 		break;
 	}
@@ -2292,7 +2223,7 @@ _log_connection_sort_hashes_fcn (gconstpointer a, gconstpointer b)
 {
 	const LogConnectionSettingData *v1 = a;
 	const LogConnectionSettingData *v2 = b;
-	guint32 p1, p2;
+	NMSettingPriority p1, p2;
 	NMSetting *s1, *s2;
 
 	s1 = v1->setting ? v1->setting : v1->diff_base_setting;
@@ -2432,12 +2363,26 @@ nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base,
 	if (!name)
 		name = "";
 
-	connection_diff_are_same = nm_connection_diff (connection, diff_base, NM_SETTING_COMPARE_FLAG_EXACT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT, &connection_diff);
+	connection_diff_are_same = nm_connection_diff (connection, diff_base,
+	                                               NM_SETTING_COMPARE_FLAG_EXACT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT,
+	                                               &connection_diff);
 	if (connection_diff_are_same) {
-		if (diff_base)
-			nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s and %p/%s): no difference", prefix, name, connection, G_OBJECT_TYPE_NAME (connection), diff_base, G_OBJECT_TYPE_NAME (diff_base));
-		else
-			nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s): no properties set", prefix, name, connection, G_OBJECT_TYPE_NAME (connection));
+		const char *t1, *t2;
+
+		t1 = nm_connection_get_connection_type (connection);
+		if (diff_base) {
+			t2 = nm_connection_get_connection_type (diff_base);
+			nm_log (level, domain, NULL, NULL,
+			        "%sconnection '%s' (%p/%s/%s%s%s and %p/%s/%s%s%s): no difference",
+			        prefix, name,
+			        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1),
+			        diff_base, G_OBJECT_TYPE_NAME (diff_base), NM_PRINT_FMT_QUOTE_STRING (t2));
+		} else {
+			nm_log (level, domain, NULL, NULL,
+			        "%sconnection '%s' (%p/%s/%s%s%s): no properties set",
+			        prefix, name,
+			        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1));
+		}
 		g_assert (!connection_diff);
 		return;
 	}
@@ -2471,12 +2416,20 @@ nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base,
 			if (print_header) {
 				GError *err_verify = NULL;
 				const char *path = nm_connection_get_path (connection);
+				const char *t1, *t2;
 
+				t1 = nm_connection_get_connection_type (connection);
 				if (diff_base) {
-					nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s < %p/%s)%s%s%s:", prefix, name, connection, G_OBJECT_TYPE_NAME (connection), diff_base, G_OBJECT_TYPE_NAME (diff_base),
+					t2 = nm_connection_get_connection_type (diff_base);
+					nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s/%s%s%s < %p/%s/%s%s%s)%s%s%s:",
+					        prefix, name,
+					        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1),
+					        diff_base, G_OBJECT_TYPE_NAME (diff_base), NM_PRINT_FMT_QUOTE_STRING (t2),
 					        NM_PRINT_FMT_QUOTED (path, " [", path, "]", ""));
 				} else {
-					nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s):%s%s%s", prefix, name, connection, G_OBJECT_TYPE_NAME (connection),
+					nm_log (level, domain, NULL, NULL, "%sconnection '%s' (%p/%s/%s%s%s):%s%s%s",
+					        prefix, name,
+					        connection, G_OBJECT_TYPE_NAME (connection), NM_PRINT_FMT_QUOTE_STRING (t1),
 					        NM_PRINT_FMT_QUOTED (path, " [", path, "]", ""));
 				}
 				print_header = FALSE;
@@ -2572,55 +2525,88 @@ nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_
 #define IPV6_PROPERTY_DIR "/proc/sys/net/ipv6/conf/"
 #define IPV4_PROPERTY_DIR "/proc/sys/net/ipv4/conf/"
 G_STATIC_ASSERT (sizeof (IPV4_PROPERTY_DIR) == sizeof (IPV6_PROPERTY_DIR));
+G_STATIC_ASSERT (NM_STRLEN (IPV6_PROPERTY_DIR) + IFNAMSIZ + 60 == NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE);
 
-static const char *
-_get_property_path (const char *ifname,
-                    const char *property,
-                    gboolean ipv6)
+/**
+ * nm_utils_sysctl_ip_conf_path:
+ * @addr_family: either AF_INET or AF_INET6.
+ * @buf: the output buffer where to write the path. It
+ *   must be at least NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE bytes
+ *   long.
+ * @ifname: an interface name
+ * @property: a property name
+ *
+ * Returns: the path to IPv6 property @property on @ifname. Note that
+ * this returns the input argument @buf.
+ */
+const char *
+nm_utils_sysctl_ip_conf_path (int addr_family, char *buf, const char *ifname, const char *property)
 {
-	static char path[sizeof (IPV6_PROPERTY_DIR) + IFNAMSIZ + 32];
 	int len;
 
-	ifname = NM_ASSERT_VALID_PATH_COMPONENT (ifname);
+	nm_assert (buf);
+	nm_assert_addr_family (addr_family);
+
+	g_assert (nm_utils_is_valid_iface_name (ifname, NULL));
 	property = NM_ASSERT_VALID_PATH_COMPONENT (property);
 
-	len = g_snprintf (path,
-	                  sizeof (path),
+	len = g_snprintf (buf,
+	                  NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE,
 	                  "%s%s/%s",
-	                  ipv6 ? IPV6_PROPERTY_DIR : IPV4_PROPERTY_DIR,
+	                  addr_family == AF_INET6 ? IPV6_PROPERTY_DIR : IPV4_PROPERTY_DIR,
 	                  ifname,
 	                  property);
-	g_assert (len < sizeof (path) - 1);
-
-	return path;
+	g_assert (len < NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE - 1);
+	return buf;
 }
 
-/**
- * nm_utils_ip6_property_path:
- * @ifname: an interface name
- * @property: a property name
- *
- * Returns the path to IPv6 property @property on @ifname. Note that
- * this uses a static buffer.
- */
-const char *
-nm_utils_ip6_property_path (const char *ifname, const char *property)
+gboolean
+nm_utils_sysctl_ip_conf_is_path (int addr_family, const char *path, const char *ifname, const char *property)
 {
-	return _get_property_path (ifname, property, TRUE);
-}
+	g_return_val_if_fail (path, FALSE);
+	NM_ASSERT_VALID_PATH_COMPONENT (property);
+	g_assert (!ifname || nm_utils_is_valid_iface_name (ifname, NULL));
 
-/**
- * nm_utils_ip4_property_path:
- * @ifname: an interface name
- * @property: a property name
- *
- * Returns the path to IPv4 property @property on @ifname. Note that
- * this uses a static buffer.
- */
-const char *
-nm_utils_ip4_property_path (const char *ifname, const char *property)
-{
-	return _get_property_path (ifname, property, FALSE);
+	if (addr_family == AF_INET) {
+		if (!g_str_has_prefix (path, IPV4_PROPERTY_DIR))
+			return FALSE;
+		path += NM_STRLEN (IPV4_PROPERTY_DIR);
+	} else if (addr_family == AF_INET6) {
+		if (!g_str_has_prefix (path, IPV6_PROPERTY_DIR))
+			return FALSE;
+		path += NM_STRLEN (IPV6_PROPERTY_DIR);
+	} else
+		g_return_val_if_reached (FALSE);
+
+	if (ifname) {
+		if (!g_str_has_prefix (path, ifname))
+			return FALSE;
+		path += strlen (ifname);
+		if (path[0] != '/')
+			return FALSE;
+		path++;
+	} else {
+		const char *slash;
+		char buf[IFNAMSIZ];
+		gsize l;
+
+		slash = strchr (path, '/');
+		if (!slash)
+			return FALSE;
+		l = slash - path;
+		if (l >= IFNAMSIZ)
+			return FALSE;
+		memcpy (buf, path, l);
+		buf[l] = '\0';
+		if (!nm_utils_is_valid_iface_name (buf, NULL))
+			return FALSE;
+		path = slash + 1;
+	}
+
+	if (!nm_streq (path, property))
+		return FALSE;
+
+	return TRUE;
 }
 
 gboolean
@@ -2733,99 +2719,6 @@ nm_utils_machine_id_read (void)
 
 /*****************************************************************************/
 
-/* taken from systemd's fd_wait_for_event(). Note that the timeout
- * is here in nano-seconds, not micro-seconds. */
-int
-nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns)
-{
-	struct pollfd pollfd = {
-		.fd = fd,
-		.events = event,
-	};
-	struct timespec ts, *pts;
-	int r;
-
-	if (timeout_ns < 0)
-		pts = NULL;
-	else {
-		ts.tv_sec = (time_t) (timeout_ns / NM_UTILS_NS_PER_SECOND);
-		ts.tv_nsec = (long int) (timeout_ns % NM_UTILS_NS_PER_SECOND);
-		pts = &ts;
-	}
-
-	r = ppoll (&pollfd, 1, pts, NULL);
-	if (r < 0)
-		return -errno;
-	if (r == 0)
-		return 0;
-	return pollfd.revents;
-}
-
-/* taken from systemd's loop_read() */
-ssize_t
-nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll)
-{
-	uint8_t *p = buf;
-	ssize_t n = 0;
-
-	g_return_val_if_fail (fd >= 0, -EINVAL);
-	g_return_val_if_fail (buf, -EINVAL);
-
-	/* If called with nbytes == 0, let's call read() at least
-	 * once, to validate the operation */
-
-	if (nbytes > (size_t) SSIZE_MAX)
-		return -EINVAL;
-
-	do {
-		ssize_t k;
-
-		k = read (fd, p, nbytes);
-		if (k < 0) {
-			if (errno == EINTR)
-				continue;
-
-			if (errno == EAGAIN && do_poll) {
-
-				/* We knowingly ignore any return value here,
-				 * and expect that any error/EOF is reported
-				 * via read() */
-
-				(void) nm_utils_fd_wait_for_event (fd, POLLIN, -1);
-				continue;
-			}
-
-			return n > 0 ? n : -errno;
-		}
-
-		if (k == 0)
-			return n;
-
-		g_assert ((size_t) k <= nbytes);
-
-		p += k;
-		nbytes -= k;
-		n += k;
-	} while (nbytes > 0);
-
-	return n;
-}
-
-/* taken from systemd's loop_read_exact() */
-int
-nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll)
-{
-	ssize_t n;
-
-	n = nm_utils_fd_read_loop (fd, buf, nbytes, do_poll);
-	if (n < 0)
-		return (int) n;
-	if ((size_t) n != nbytes)
-		return -EIO;
-
-	return 0;
-}
-
 _nm_printf (3, 4)
 static int
 _get_contents_error (GError **error, int errsv, const char *format, ...)
@@ -2856,6 +2749,8 @@ _get_contents_error (GError **error, int errsv, const char *format, ...)
  * nm_utils_fd_get_contents:
  * @fd: open file descriptor to read. The fd will not be closed,
  *   but don't rely on it's 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.
@@ -2883,11 +2778,13 @@ _get_contents_error (GError **error, int errsv, const char *format, ...)
  */
 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;
 
@@ -2933,9 +2830,20 @@ nm_utils_fd_get_contents (int fd,
 		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 = dup (fd);
+			if (fd2 < 0)
+				return _get_contents_error (error, 0, "error during dup");
+		}
 
-		if (!(f = fdopen (fd, "r")))
+		if (!(f = fdopen (fd2, "r"))) {
+			close (fd2);
 			return _get_contents_error (error, 0, "failure during fdopen");
+		}
 
 		n_have = 0;
 		n_alloc = 0;
@@ -3025,7 +2933,7 @@ nm_utils_file_get_contents (int dirfd,
                             gsize *length,
                             GError **error)
 {
-	nm_auto_close int fd = -1;
+	int fd;
 	int errsv;
 
 	g_return_val_if_fail (filename && filename[0], -EINVAL);
@@ -3058,6 +2966,7 @@ nm_utils_file_get_contents (int dirfd,
 		}
 	}
 	return nm_utils_fd_get_contents (fd,
+	                                 TRUE,
 	                                 max_length,
 	                                 contents,
 	                                 length,
@@ -3066,30 +2975,6 @@ nm_utils_file_get_contents (int dirfd,
 
 /*****************************************************************************/
 
-/* taken from systemd's dev_urandom(). */
-int
-nm_utils_read_urandom (void *p, size_t nbytes)
-{
-	int fd = -1;
-	int r;
-
-again:
-	fd = open ("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOCTTY);
-	if (fd < 0) {
-		r = errno;
-		if (r == EINTR)
-			goto again;
-		return r == ENOENT ? -ENOSYS : -r;
-	}
-
-	r = nm_utils_fd_read_loop_exact (fd, p, nbytes, TRUE);
-	close (fd);
-
-	return r;
-}
-
-/*****************************************************************************/
-
 guint8 *
 nm_utils_secret_key_read (gsize *out_key_len, GError **error)
 {
@@ -3108,7 +2993,6 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error)
 			key_len = 0;
 		}
 	} else {
-		int r;
 		mode_t key_mask;
 
 		/* RFC7217 mandates the key SHOULD be at least 128 bits.
@@ -3116,10 +3000,9 @@ nm_utils_secret_key_read (gsize *out_key_len, GError **error)
 		key_len = 32;
 		secret_key = g_malloc (key_len);
 
-		r = nm_utils_read_urandom (secret_key, key_len);
-		if (r < 0) {
+		if (!nm_utils_random_bytes (secret_key, key_len)) {
 			g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
-			             "Can't read /dev/urandom: %s", strerror (-r));
+			             "Can't get random data to generate secret key");
 			key_len = 0;
 			goto out;
 		}
@@ -3373,8 +3256,7 @@ nm_utils_stable_id_random (void)
 {
 	char buf[15];
 
-	if (nm_utils_read_urandom (buf, sizeof (buf)) < 0)
-		g_return_val_if_reached (nm_utils_uuid_generate ());
+	nm_utils_random_bytes (buf, sizeof (buf));
 	return g_base64_encode ((guchar *) buf, sizeof (buf));
 }
 
@@ -3437,7 +3319,7 @@ nm_utils_stable_id_parse (const char *stable_id,
 	g_return_val_if_fail (out_generated, NM_UTILS_STABLE_TYPE_RANDOM);
 
 	if (!stable_id) {
-		out_generated = NULL;
+		*out_generated = NULL;
 		return NM_UTILS_STABLE_TYPE_UUID;
 	}
 
@@ -3505,7 +3387,7 @@ nm_utils_stable_id_parse (const char *stable_id,
 			_stable_id_append (str, bootid ?: nm_utils_get_boot_id ());
 		else if (g_str_has_prefix (&stable_id[i], "${RANDOM}")) {
 			/* RANDOM makes not so much sense for cloned-mac-address
-			 * as the result is simmilar to specifing "cloned-mac-address=random".
+			 * as the result is simmilar to specyifing "cloned-mac-address=random".
 			 * It makes however sense for RFC 7217 Stable Privacy IPv6 addresses
 			 * where this is effectively the only way to generate a different
 			 * (random) host identifier for each connect.
@@ -3758,8 +3640,7 @@ nm_utils_hw_addr_gen_random_eth (const char *current_mac_address,
 {
 	struct ether_addr bin_addr;
 
-	if (nm_utils_read_urandom (&bin_addr, ETH_ALEN) < 0)
-		return NULL;
+	nm_utils_random_bytes (&bin_addr, ETH_ALEN);
 	_hw_addr_eth_complete (&bin_addr, current_mac_address, generate_mac_address_mask);
 	return nm_utils_hwaddr_ntoa (&bin_addr, ETH_ALEN);
 }
@@ -4451,6 +4332,13 @@ nm_utils_format_con_diff_for_audit (GHashTable *diff)
 
 /*****************************************************************************/
 
+NM_UTILS_ENUM2STR_DEFINE (nm_icmpv6_router_pref_to_string, NMIcmpv6RouterPref,
+	NM_UTILS_ENUM2STR (NM_ICMPV6_ROUTER_PREF_LOW,     "low"),
+	NM_UTILS_ENUM2STR (NM_ICMPV6_ROUTER_PREF_MEDIUM,  "medium"),
+	NM_UTILS_ENUM2STR (NM_ICMPV6_ROUTER_PREF_HIGH,    "high"),
+	NM_UTILS_ENUM2STR (NM_ICMPV6_ROUTER_PREF_INVALID, "invalid"),
+);
+
 NM_UTILS_LOOKUP_STR_DEFINE (nm_activation_type_to_string, NMActivationType,
 	NM_UTILS_LOOKUP_DEFAULT_WARN ("(unknown)"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_ACTIVATION_TYPE_MANAGED,  "managed"),
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 0f37bd20..cc784724 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -25,6 +25,8 @@
 #include <stdio.h>
 #include <arpa/inet.h>
 
+#include "nm-utils/nm-hash-utils.h"
+
 #include "nm-connection.h"
 
 /*****************************************************************************/
@@ -90,31 +92,59 @@ GETTER (void) \
 
 /*****************************************************************************/
 
-typedef struct {
-	union {
-		guint8 addr_ptr[1];
-		in_addr_t addr4;
-		struct in6_addr addr6;
-
-		/* NMIPAddr is really a union for IP addresses.
-		 * However, as ethernet addresses fit in here nicely, use
-		 * it also for an ethernet MAC address. */
-		guint8 addr_eth[6 /*ETH_ALEN*/];
-	};
-} NMIPAddr;
-
-extern const NMIPAddr nm_ip_addr_zero;
-
-#define NMIPAddrInit { .addr6 = IN6ADDR_ANY_INIT }
-
-/*****************************************************************************/
-
 gboolean nm_ethernet_address_is_valid (gconstpointer addr, gssize len);
 
 gconstpointer nm_utils_ipx_address_clear_host_address (int family, gpointer dst, gconstpointer src, guint8 plen);
 in_addr_t nm_utils_ip4_address_clear_host_address (in_addr_t addr, guint8 plen);
 const struct in6_addr *nm_utils_ip6_address_clear_host_address (struct in6_addr *dst, const struct in6_addr *src, guint8 plen);
-gboolean nm_utils_ip6_address_same_prefix (const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint8 plen);
+
+static inline int
+nm_utils_ip4_address_same_prefix_cmp (in_addr_t addr_a, in_addr_t addr_b, guint8 plen)
+{
+	NM_CMP_DIRECT (htonl (nm_utils_ip4_address_clear_host_address (addr_a, plen)),
+	               htonl (nm_utils_ip4_address_clear_host_address (addr_b, plen)));
+	return 0;
+}
+
+int nm_utils_ip6_address_same_prefix_cmp (const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint8 plen);
+
+static inline gboolean
+nm_utils_ip4_address_same_prefix (in_addr_t addr_a, in_addr_t addr_b, guint8 plen)
+{
+	return nm_utils_ip4_address_same_prefix_cmp (addr_a, addr_b, plen) == 0;
+}
+
+static inline gboolean
+nm_utils_ip6_address_same_prefix (const struct in6_addr *addr_a, const struct in6_addr *addr_b, guint8 plen)
+{
+	return nm_utils_ip6_address_same_prefix_cmp (addr_a, addr_b, plen) == 0;
+}
+
+#define NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a, b, plen) \
+    NM_CMP_RETURN (nm_utils_ip4_address_same_prefix_cmp ((a), (b), (plen)))
+
+#define NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(a, b, plen) \
+    NM_CMP_RETURN (nm_utils_ip6_address_same_prefix_cmp ((a), (b), (plen)))
+
+static inline void
+nm_hash_update_in6addr (NMHashState *h, const struct in6_addr *addr)
+{
+	nm_assert (addr);
+
+	nm_hash_update (h, addr, sizeof (*addr));
+}
+
+static inline void
+nm_hash_update_in6addr_prefix (NMHashState *h, const struct in6_addr *addr, guint8 plen)
+{
+	struct in6_addr a;
+
+	nm_assert (addr);
+
+	nm_utils_ip6_address_clear_host_address (&a, addr, plen);
+	/* we don't hash plen itself. The caller may want to do that.*/
+	nm_hash_update_in6addr (h, &a);
+}
 
 double nm_utils_exp10 (gint16 e);
 
@@ -133,6 +163,21 @@ nm_utils_ip6_route_metric_normalize (guint32 metric)
 	return metric ? metric : 1024 /*NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6*/;
 }
 
+static inline guint32
+nm_utils_ip_route_metric_normalize (int addr_family, guint32 metric)
+{
+	return addr_family == AF_INET6 ? nm_utils_ip6_route_metric_normalize (metric) : metric;
+}
+
+static inline guint32
+nm_utils_ip_route_metric_penalize (int addr_family, guint32 metric, guint32 penalty)
+{
+	metric = nm_utils_ip_route_metric_normalize (addr_family, metric);
+	if (metric < G_MAXUINT32 - penalty)
+		return metric + penalty;
+	return G_MAXUINT32;
+}
+
 int nm_utils_modprobe (GError **error, gboolean suppress_error_loggin, const char *arg1, ...) G_GNUC_NULL_TERMINATED;
 
 guint64 nm_utils_get_start_time_for_pid (pid_t pid, char *out_state, pid_t *out_ppid);
@@ -174,135 +219,27 @@ NMMatchSpecMatchType nm_match_spec_config (const GSList *specs,
 GSList *nm_match_spec_split (const char *value);
 char *nm_match_spec_join (GSList *specs);
 
-extern char _nm_utils_to_string_buffer[2096];
-
-void     nm_utils_to_string_buffer_init (char **buf, gsize *len);
-gboolean nm_utils_to_string_buffer_init_null (gconstpointer obj, char **buf, gsize *len);
-
-/*****************************************************************************/
-
-typedef struct {
-	unsigned flag;
-	const char *name;
-} NMUtilsFlags2StrDesc;
-
-#define NM_UTILS_FLAGS2STR(f, n) { .flag = f, .name = ""n, }
-
-#define _NM_UTILS_FLAGS2STR_DEFINE(scope, fcn_name, flags_type, ...) \
-scope const char * \
-fcn_name (flags_type flags, char *buf, gsize len) \
-{ \
-	static const NMUtilsFlags2StrDesc descs[] = { \
-		__VA_ARGS__ \
-	}; \
-	G_STATIC_ASSERT (sizeof (flags_type) <= sizeof (unsigned)); \
-	return nm_utils_flags2str (descs, G_N_ELEMENTS (descs), flags, buf, len); \
-};
-
-#define NM_UTILS_FLAGS2STR_DEFINE(fcn_name, flags_type, ...) \
-	_NM_UTILS_FLAGS2STR_DEFINE (, fcn_name, flags_type, __VA_ARGS__)
-#define NM_UTILS_FLAGS2STR_DEFINE_STATIC(fcn_name, flags_type, ...) \
-	_NM_UTILS_FLAGS2STR_DEFINE (static, fcn_name, flags_type, __VA_ARGS__)
-
-const char *nm_utils_flags2str (const NMUtilsFlags2StrDesc *descs,
-                                gsize n_descs,
-                                unsigned flags,
-                                char *buf,
-                                gsize len);
-
-/*****************************************************************************/
-
-#define NM_UTILS_ENUM2STR(v, n)     (void) 0; case v: s = ""n""; break; (void) 0
-#define NM_UTILS_ENUM2STR_IGNORE(v) (void) 0; case v: break; (void) 0
-
-#define _NM_UTILS_ENUM2STR_DEFINE(scope, fcn_name, lookup_type, int_fmt, ...) \
-scope const char * \
-fcn_name (lookup_type val, char *buf, gsize len) \
-{ \
-	nm_utils_to_string_buffer_init (&buf, &len); \
-	if (len) { \
-		const char *s = NULL; \
-		switch (val) { \
-			(void) 0, \
-			__VA_ARGS__ \
-			(void) 0; \
-		}; \
-		if (s) \
-			g_strlcpy (buf, s, len); \
-		else \
-			g_snprintf (buf, len, "(%"int_fmt")", val); \
-	} \
-	return buf; \
-}
-
-#define NM_UTILS_ENUM2STR_DEFINE(fcn_name, lookup_type, ...) \
-	_NM_UTILS_ENUM2STR_DEFINE (, fcn_name, lookup_type, "d", __VA_ARGS__)
-#define NM_UTILS_ENUM2STR_DEFINE_STATIC(fcn_name, lookup_type, ...) \
-	_NM_UTILS_ENUM2STR_DEFINE (static, fcn_name, lookup_type, "d", __VA_ARGS__)
-
-/*****************************************************************************/
-
-#define NM_UTILS_LOOKUP_DEFAULT(v)            return (v)
-#define NM_UTILS_LOOKUP_DEFAULT_WARN(v)       g_return_val_if_reached (v)
-#define NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(v)  { nm_assert_not_reached (); return (v); }
-#define NM_UTILS_LOOKUP_ITEM(v, n)            (void) 0; case v: return (n); (void) 0
-#define NM_UTILS_LOOKUP_STR_ITEM(v, n)        NM_UTILS_LOOKUP_ITEM(v, ""n"")
-#define NM_UTILS_LOOKUP_ITEM_IGNORE(v)        (void) 0; case v: break; (void) 0
-#define NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER()   (void) 0; default: break; (void) 0
-
-#define _NM_UTILS_LOOKUP_DEFINE(scope, fcn_name, lookup_type, result_type, unknown_val, ...) \
-scope result_type \
-fcn_name (lookup_type val) \
-{ \
-	switch (val) { \
-		(void) 0, \
-		__VA_ARGS__ \
-		(void) 0; \
-	}; \
-	{ unknown_val; } \
-}
-
-#define NM_UTILS_LOOKUP_STR_DEFINE(fcn_name, lookup_type, unknown_val, ...) \
-	_NM_UTILS_LOOKUP_DEFINE (, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__)
-#define NM_UTILS_LOOKUP_STR_DEFINE_STATIC(fcn_name, lookup_type, unknown_val, ...) \
-	_NM_UTILS_LOOKUP_DEFINE (static, fcn_name, lookup_type, const char *, unknown_val, __VA_ARGS__)
-
-/* Call the string-lookup-table function @fcn_name. If the function returns
- * %NULL, the numeric index is converted to string using a alloca() buffer.
- * Beware: this macro uses alloca(). */
-#define NM_UTILS_LOOKUP_STR(fcn_name, idx) \
-	({ \
-		typeof (idx) _idx = (idx); \
-		const char *_s; \
-		\
-		_s = fcn_name (_idx); \
-		if (!_s) { \
-			_s = g_alloca (30); \
-			\
-			g_snprintf ((char *) _s, 30, "(%lld)", (long long) _idx); \
-		} \
-		_s; \
-	})
-
 /*****************************************************************************/
 
 const char *nm_utils_get_ip_config_method (NMConnection *connection,
                                            GType         ip_setting_type);
 
+gboolean nm_utils_connection_has_default_route (NMConnection *connection,
+                                                int addr_family,
+                                                gboolean *out_is_never_default);
+
 char *nm_utils_new_vlan_name (const char *parent_iface, guint32 vlan_id);
 const char *nm_utils_new_infiniband_name (char *name, const char *parent_name, int p_key);
 
-GPtrArray *nm_utils_read_resolv_conf_nameservers (const char *rc_contents);
-GPtrArray *nm_utils_read_resolv_conf_dns_options (const char *rc_contents);
+gboolean nm_utils_resolve_conf_parse (int addr_family,
+                                      const char *rc_contents,
+                                      GArray *nameservers,
+                                      GPtrArray *dns_options);
 
 int nm_utils_cmp_connection_by_autoconnect_priority (NMConnection *a, NMConnection *b);
 
 void nm_utils_log_connection_diff (NMConnection *connection, NMConnection *diff_base, guint32 level, guint64 domain, const char *name, const char *prefix);
 
-#define NM_UTILS_NS_PER_SECOND  ((gint64) 1000000000)
-#define NM_UTILS_NS_PER_MSEC    ((gint64) 1000000)
-#define NM_UTILS_NS_TO_MSEC_CEIL(nsec)      (((nsec) + (NM_UTILS_NS_PER_MSEC - 1)) / NM_UTILS_NS_PER_MSEC)
-
 gint64 nm_utils_get_monotonic_timestamp_ns (void);
 gint64 nm_utils_get_monotonic_timestamp_us (void);
 gint64 nm_utils_get_monotonic_timestamp_ms (void);
@@ -311,16 +248,17 @@ gint64 nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timest
 
 gboolean    nm_utils_is_valid_path_component (const char *name);
 const char *NM_ASSERT_VALID_PATH_COMPONENT (const char *name);
-const char *nm_utils_ip6_property_path (const char *ifname, const char *property);
-const char *nm_utils_ip4_property_path (const char *ifname, const char *property);
 
-gboolean nm_utils_is_specific_hostname (const char *name);
+#define NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE 100
+
+const char *nm_utils_sysctl_ip_conf_path (int addr_family, char *buf, const char *ifname, const char *property);
 
-int nm_utils_fd_wait_for_event (int fd, int event, gint64 timeout_ns);
-ssize_t nm_utils_fd_read_loop (int fd, void *buf, size_t nbytes, bool do_poll);
-int nm_utils_fd_read_loop_exact (int fd, void *buf, size_t nbytes, bool do_poll);
+gboolean nm_utils_sysctl_ip_conf_is_path (int addr_family, const char *path, const char *ifname, const char *property);
+
+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,
@@ -339,8 +277,6 @@ gboolean nm_utils_file_set_contents (const gchar *filename,
                                      mode_t mode,
                                      GError **error);
 
-int nm_utils_read_urandom (void *p, size_t n);
-
 char *nm_utils_machine_id_read (void);
 gboolean nm_utils_machine_id_parse (const char *id_str, /*uuid_t*/ guchar *out_uuid);
 
@@ -348,7 +284,7 @@ guint8 *nm_utils_secret_key_read (gsize *out_key_len, GError **error);
 
 const char *nm_utils_get_boot_id (void);
 
-/* IPv6 Interface Identifer helpers */
+/* IPv6 Interface Identifier helpers */
 
 /**
  * NMUtilsIPv6IfaceId:
@@ -494,6 +430,19 @@ gboolean nm_utils_validate_plugin (const char *path, struct stat *stat, GError *
 char **nm_utils_read_plugin_paths (const char *dirname, const char *prefix);
 char *nm_utils_format_con_diff_for_audit (GHashTable *diff);
 
+/*****************************************************************************/
+
+/* this enum is compatible with ICMPV6_ROUTER_PREF_* (from <linux/icmpv6.h>,
+ * the values for netlink attribute RTA_PREF) and "enum ndp_route_preference"
+ * from <ndp.h>. */
+typedef enum {
+	NM_ICMPV6_ROUTER_PREF_MEDIUM      = 0x0, /* ICMPV6_ROUTER_PREF_MEDIUM */
+	NM_ICMPV6_ROUTER_PREF_LOW         = 0x3, /* ICMPV6_ROUTER_PREF_LOW */
+	NM_ICMPV6_ROUTER_PREF_HIGH        = 0x1, /* ICMPV6_ROUTER_PREF_HIGH */
+	NM_ICMPV6_ROUTER_PREF_INVALID     = 0x2, /* ICMPV6_ROUTER_PREF_INVALID */
+} NMIcmpv6RouterPref;
+
+const char *nm_icmpv6_router_pref_to_string (NMIcmpv6RouterPref pref, char *buf, gsize len);
 
 /*****************************************************************************/
 
diff --git a/src/nm-default-route-manager.c b/src/nm-default-route-manager.c
deleted file mode 100644
index 1ab8f02d..00000000
--- a/src/nm-default-route-manager.c
+++ /dev/null
@@ -1,1611 +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) 2014 Red Hat, Inc.
- */
-
-
-#include "nm-default.h"
-
-#include "nm-default-route-manager.h"
-
-#include <string.h>
-
-#include "devices/nm-device.h"
-#include "vpn/nm-vpn-connection.h"
-#include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
-#include "nm-manager.h"
-#include "nm-ip4-config.h"
-#include "nm-ip6-config.h"
-#include "nm-act-request.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_LOG_WITH_PTR,
-	PROP_PLATFORM,
-);
-
-typedef struct {
-	GPtrArray *entries_ip4;
-	GPtrArray *entries_ip6;
-
-	NMPlatform *platform;
-
-	struct {
-		guint guard;
-		guint backoff_wait_time_ms;
-		guint idle_handle;
-		gboolean has_v4_changes;
-		gboolean has_v6_changes;
-	} resync;
-
-	/* During disposing, we unref the sources of all entries. This happens usually
-	 * during shutdown, which might call the final deletion of the object. That
-	 * again might cause calls back into NMDefaultRouteManager, which finds dangling
-	 * pointers.
-	 * Guard every publicly accessible function to return early if the instance
-	 * is already disposing. */
-	bool disposed;
-
-	bool log_with_ptr;
-} NMDefaultRouteManagerPrivate;
-
-struct _NMDefaultRouteManager {
-	GObject parent;
-	NMDefaultRouteManagerPrivate _priv;
-};
-
-struct _NMDefaultRouteManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMDefaultRouteManager, nm_default_route_manager, G_TYPE_OBJECT)
-
-#define NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDefaultRouteManager, NM_IS_DEFAULT_ROUTE_MANAGER)
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME   "default-route"
-#undef  _NMLOG_ENABLED
-#define _NMLOG_ENABLED(level, addr_family) \
-    ({ \
-        const int __addr_family = (addr_family); \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        nm_logging_enabled (__level, __domain); \
-    })
-#define _NMLOG(level, addr_family, ...) \
-    G_STMT_START { \
-        const int __addr_family = (addr_family); \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            char __prefix_buf[100]; \
-            \
-            _nm_log (__level, __domain, 0, NULL, NULL, \
-                     "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                     NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self)->log_with_ptr \
-                        ? nm_sprintf_buf (__prefix_buf, "%s%c[%p]", \
-                                          _NMLOG2_PREFIX_NAME, \
-                                          __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'), \
-                                          self) \
-                        : _NMLOG2_PREFIX_NAME \
-                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-        } \
-    } G_STMT_END
-
-#define _NMLOG2_PREFIX_NAME   _NMLOG_PREFIX_NAME
-#undef  _NMLOG2_ENABLED
-#define _NMLOG2_ENABLED       _NMLOG_ENABLED
-#define _NMLOG2(level, vtable, entry_idx, entry, ...) \
-    G_STMT_START { \
-        const int __addr_family = (vtable)->vt->addr_family; \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            char __prefix_buf[100]; \
-            guint __entry_idx = (entry_idx); \
-            const Entry *const __entry = (entry); \
-            \
-            _nm_log (__level, __domain, 0, NULL, NULL, \
-                     "%s: entry[%u/%s:%p:%s:%chas:%csync]: "_NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                     NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self)->log_with_ptr \
-                        ? nm_sprintf_buf (__prefix_buf, "%s%c[%p]", \
-                                          _NMLOG2_PREFIX_NAME, \
-                                          __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'), \
-                                          self) \
-                        : _NMLOG2_PREFIX_NAME, \
-                     __entry_idx, \
-                     NM_IS_DEVICE (__entry->source.pointer) ? "dev" : "vpn", \
-                     __entry->source.pointer, \
-                     NM_IS_DEVICE (__entry->source.pointer) ? nm_device_get_iface (__entry->source.device) : nm_active_connection_get_settings_connection_id (NM_ACTIVE_CONNECTION (__entry->source.vpn)), \
-                     (__entry->never_default ? '-' : '+'), \
-                     (__entry->synced ? '+' : '-') \
-                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-        } \
-    } G_STMT_END
-
-/*****************************************************************************/
-
-static void _resync_idle_cancel (NMDefaultRouteManager *self);
-
-/*****************************************************************************/
-
-typedef struct {
-	union {
-		void *pointer;
-		GObject *object;
-		NMDevice *device;
-		NMVpnConnection *vpn;
-	} source;
-	NMPlatformIPXRoute route;
-
-	/* Whether the route is synced to platform and has a default route.
-	 *
-	 * ( synced && !never_default): the interface gets a default route that
-	 *     is enforced and managed by NMDefaultRouteManager.
-	 *
-	 * (!synced && !never_default): the interface has this route, but it is assumed.
-	 *     Assumed interfaces are those that have no tracked entry or that only have
-	 *     (!synced && !never_default) entries. NMDefaultRouteManager will not touch
-	 *     default routes on these interfaces.
-	 *     This combination makes only sense for device sources.
-	 *     They are tracked so that assumed devices can also be the best device.
-	 *
-	 * ( synced &&  never_default): entries of this kind are a placeholder
-	 *     to indicate that the ifindex is managed but has no default-route.
-	 *     Missing entries also indicate that a certain ifindex has no default-route.
-	 *     The difference is that missing entries are considered assumed while on
-	 *     (synced && never_default) entries the absence of the default route
-	 *     is enforced. NMDefaultRouteManager will actively remove any default
-	 *     route on such ifindexes.
-	 *     Also, for VPN sources in addition we track them so that a never-default
-	 *     VPN connection can be choosen by get_best_config() to receive the DNS configuration.
-	 *
-	 * (!synced &&  never_default): this combination makes no sense.
-	 */
-	gboolean synced;
-	gboolean never_default;
-
-	guint32 effective_metric;
-} Entry;
-
-typedef struct {
-	const NMPlatformVTableRoute *vt;
-	GPtrArray *(*get_entries) (NMDefaultRouteManagerPrivate *priv);
-} VTableIP;
-
-static const VTableIP vtable_ip4, vtable_ip6;
-
-static NMPlatformIPRoute *
-_vt_route_index (const VTableIP *vtable, GArray *routes, guint index)
-{
-	if (vtable->vt->is_ip4)
-		return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, index);
-	else
-		return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, index);
-}
-
-static gboolean
-_vt_routes_has_entry (const VTableIP *vtable, GArray *routes, const Entry *entry)
-{
-	guint i;
-	NMPlatformIPXRoute route = entry->route;
-
-	route.rx.metric = entry->effective_metric;
-
-	if (vtable->vt->is_ip4) {
-		for (i = 0; i < routes->len; i++) {
-			NMPlatformIP4Route *r = &g_array_index (routes, NMPlatformIP4Route, i);
-
-			route.rx.rt_source = r->rt_source;
-			if (nm_platform_ip4_route_cmp (r, &route.r4) == 0)
-				return TRUE;
-		}
-	} else {
-		for (i = 0; i < routes->len; i++) {
-			NMPlatformIP6Route *r = &g_array_index (routes, NMPlatformIP6Route, i);
-
-			route.rx.rt_source = r->rt_source;
-			if (nm_platform_ip6_route_cmp (r, &route.r6) == 0)
-				return TRUE;
-		}
-	}
-	return FALSE;
-}
-
-static void
-_entry_free (Entry *entry)
-{
-	if (entry) {
-		g_object_unref (entry->source.object);
-		g_slice_free (Entry, entry);
-	}
-}
-
-static Entry *
-_entry_find_by_source (GPtrArray *entries, gpointer source, guint *out_idx)
-{
-	guint i;
-
-	for (i = 0; i < entries->len; i++) {
-		Entry *e = g_ptr_array_index (entries, i);
-
-		if (e->source.pointer == source) {
-			if (out_idx)
-				*out_idx = i;
-			return e;
-		}
-	}
-
-	if (out_idx)
-		*out_idx = G_MAXUINT;
-	return NULL;
-}
-
-static gboolean
-_platform_route_sync_add (const VTableIP *vtable, NMDefaultRouteManager *self, guint32 metric)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	GPtrArray *entries = vtable->get_entries (priv);
-	char buf1[sizeof (_nm_utils_to_string_buffer)];
-	char buf2[sizeof (_nm_utils_to_string_buffer)];
-	guint i;
-	Entry *entry_unsynced = NULL;
-	Entry *entry = NULL;
-	gboolean success;
-
-	/* Find the entries for the given metric.
-	 * The effective metric for synced entries is choosen in a way that it
-	 * is unique (except for G_MAXUINT32, where a clash is not solvable). */
-	for (i = 0; i < entries->len; i++) {
-		Entry *e = g_ptr_array_index (entries, i);
-
-		if (e->never_default)
-			continue;
-
-		if (e->effective_metric != metric)
-			continue;
-
-		if (e->synced) {
-			g_assert (!entry || metric == G_MAXUINT32);
-			if (!entry)
-				entry = e;
-		} else
-			entry_unsynced = e;
-	}
-
-	/* We don't expect to have an unsynced *and* a synced entry for the same metric.
-	 * Unless, (a) their metric is G_MAXUINT32, in which case we could not find an unused effective metric,
-	 * or (b) if we have an unsynced and a synced entry for the same ifindex.
-	 * The latter case happens for example when activating an openvpn connection (synced) and
-	 * assuming the corresponding tun0 interface (unsynced). */
-	g_assert (!entry || !entry_unsynced || (entry->route.rx.ifindex == entry_unsynced->route.rx.ifindex) || metric == G_MAXUINT32);
-
-	/* we only add the route, if we have an (to be synced) entry for it. */
-	if (!entry)
-		return FALSE;
-
-	if (vtable->vt->is_ip4) {
-		NMPlatformIP4Route rt = entry->route.r4;
-		const NMPlatformIP4Route *plat_rt;
-
-		rt.network = 0;
-		rt.plen = 0;
-		rt.metric = entry->effective_metric;
-		rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-
-		plat_rt = nm_platform_ip4_route_get (priv->platform,
-		                                     entry->route.r4.ifindex,
-		                                     0,
-		                                     0,
-		                                     entry->effective_metric);
-		if (plat_rt && nm_platform_ip4_route_cmp (plat_rt, &rt) == 0) {
-			_LOGt (AF_INET, "already exists: %s",
-			       nm_platform_ip4_route_to_string (&rt, NULL, 0));
-			return FALSE;
-		}
-
-		rt.rt_source = entry->route.r4.rt_source;
-
-		if (plat_rt) {
-			_LOGt (AF_INET, "update platform route: %s; with route: %s",
-			       nm_platform_ip4_route_to_string (plat_rt, buf1, sizeof (buf1)),
-			       nm_platform_ip4_route_to_string (&rt, buf2, sizeof (buf2)));
-		}
-
-		success = nm_platform_ip4_route_add (priv->platform, &rt);
-	} else {
-		NMPlatformIP6Route rt = entry->route.r6;
-		const NMPlatformIP6Route *plat_rt;
-
-		rt.network = in6addr_any;
-		rt.plen = 0;
-		rt.metric = entry->effective_metric;
-		rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-
-		plat_rt = nm_platform_ip6_route_get (priv->platform,
-		                                     entry->route.r6.ifindex,
-		                                     in6addr_any,
-		                                     0,
-		                                     entry->effective_metric);
-		if (plat_rt && nm_platform_ip6_route_cmp (plat_rt, &rt) == 0) {
-			_LOGt (AF_INET6, "already exists: %s",
-			       nm_platform_ip6_route_to_string (&rt, NULL, 0));
-			return FALSE;
-		}
-
-		rt.rt_source = entry->route.r6.rt_source;
-
-		if (plat_rt) {
-			_LOGt (AF_INET, "update platform route: %s; with route: %s",
-			       nm_platform_ip6_route_to_string (plat_rt, buf1, sizeof (buf1)),
-			       nm_platform_ip6_route_to_string (&rt, buf2, sizeof (buf2)));
-		}
-
-		success = nm_platform_ip6_route_add (priv->platform, &rt);
-	}
-
-	if (!success) {
-		_LOGW (vtable->vt->addr_family, "failed to add default route %s with effective metric %u",
-		       vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric);
-	}
-	return TRUE;
-}
-
-static gboolean
-_platform_route_sync_flush (const VTableIP *vtable, NMDefaultRouteManager *self, int ifindex_to_flush)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	GPtrArray *entries = vtable->get_entries (priv);
-	GArray *routes;
-	guint i, j;
-	gboolean changed = FALSE;
-
-	/* prune all other default routes from this device. */
-	routes = vtable->vt->route_get_all (priv->platform, 0, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-
-	for (i = 0; i < routes->len; i++) {
-		const NMPlatformIPRoute *route;
-		gboolean has_ifindex_synced = FALSE;
-		Entry *entry = NULL;
-
-		route = _vt_route_index (vtable, routes, i);
-
-		/* look at all entries and see if the route for this ifindex pair is
-		 * a known entry. */
-		for (j = 0; j < entries->len; j++) {
-			Entry *e = g_ptr_array_index (entries, j);
-
-			if (   e->route.rx.ifindex == route->ifindex
-			    && e->synced) {
-				has_ifindex_synced = TRUE;
-				if (   !e->never_default
-				    && e->effective_metric == route->metric)
-					entry = e;
-			}
-		}
-
-		/* we only delete the route if we don't have a matching entry,
-		 * and there is at least one entry that references this ifindex
-		 * (indicating that the ifindex is managed by us -- not assumed).
-		 *
-		 * Otherwise, don't delete the route because it's configured
-		 * externally (and will be assumed -- or already is assumed).
-		 */
-		if (   !entry
-		    && (has_ifindex_synced || ifindex_to_flush == route->ifindex)) {
-			vtable->vt->route_delete_default (priv->platform, route->ifindex, route->metric);
-			changed = TRUE;
-		}
-	}
-	g_array_free (routes, TRUE);
-	return changed;
-}
-
-static int
-_sort_entries_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
-{
-	guint32 m_a, m_b;
-	const Entry *e_a = *((const Entry **) a);
-	const Entry *e_b = *((const Entry **) b);
-
-	/* when comparing routes, we consider the (original) metric. */
-	m_a = e_a->route.rx.metric;
-	m_b = e_b->route.rx.metric;
-
-	/* we normalize route.metric already in _ipx_update_default_route().
-	 * so we can just compare the metrics numerically */
-
-	if (m_a != m_b)
-		return (m_a < m_b) ? -1 : 1;
-
-	/* If the metrics are equal, we prefer the one that is !never_default */
-	if (!!e_a->never_default != !!e_b->never_default)
-		return e_a->never_default ? 1 : -1;
-
-	/* If the metrics are equal, we prefer the one that is assumed (!synced).
-	 * Entries that we sync, can be modified so that only the best
-	 * entry has a (deterministically) lowest metric.
-	 * With assumed devices we cannot increase/change the metric.
-	 * For example: two devices, both metric 0. One is assumed the other is
-	 * synced.
-	 * If we would choose the synced entry as best, we cannot
-	 * increase the metric of the assumed one and we would have non-determinism.
-	 * If we instead prefer the assumed device, we can increase the metric
-	 * of the synced device and the assumed device is (deterministically)
-	 * prefered.
-	 * If both devices are assumed, we also have non-determinism, but also
-	 * we don't reorder either.
-	 */
-	if (!!e_a->synced != !!e_b->synced)
-		return e_a->synced ? 1 : -1;
-
-	/* otherwise, do not reorder */
-	return 0;
-}
-
-static GHashTable *
-_get_assumed_interface_metrics (const VTableIP *vtable, NMDefaultRouteManager *self, GArray *routes)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	GPtrArray *entries;
-	guint i, j;
-	GHashTable *result;
-
-	/* create a list of all metrics that are currently assigned on an interface
-	 * that is *not* already covered by one of our synced entries.
-	 * IOW, returns the metrics that are in use by assumed interfaces
-	 * that we want to preserve. */
-
-	entries = vtable->get_entries (priv);
-
-	result = g_hash_table_new (NULL, NULL);
-
-	for (i = 0; i < routes->len; i++) {
-		gboolean ifindex_has_synced_entry = FALSE;
-		const NMPlatformIPRoute *route;
-
-		route = _vt_route_index (vtable, routes, i);
-
-		for (j = 0; j < entries->len; j++) {
-			Entry *e = g_ptr_array_index (entries, j);
-
-			if (   e->synced
-			    && e->route.rx.ifindex == route->ifindex) {
-				ifindex_has_synced_entry = TRUE;
-				break;
-			}
-		}
-
-		if (!ifindex_has_synced_entry)
-			g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (route->metric)));
-	}
-
-	/* also add all non-synced metrics from our entries list. We might have there some metrics that
-	 * we track as non-synced but that are no longer part of platform routes. Anyway, for now
-	 * we still want to treat them as assumed. */
-	for (i = 0; i < entries->len; i++) {
-		gboolean ifindex_has_synced_entry = FALSE;
-		Entry *e_i = g_ptr_array_index (entries, i);
-
-		if (e_i->synced)
-			continue;
-
-		for (j = 0; j < entries->len; j++) {
-			Entry *e_j = g_ptr_array_index (entries, j);
-
-			if (   j != i
-			    && (e_j->synced && e_j->route.rx.ifindex == e_i->route.rx.ifindex)) {
-				ifindex_has_synced_entry = TRUE;
-				break;
-			}
-		}
-
-		if (!ifindex_has_synced_entry)
-			g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (e_i->route.rx.metric)));
-	}
-
-	return result;
-}
-
-static gboolean
-_resync_all (const VTableIP *vtable, NMDefaultRouteManager *self, const Entry *changed_entry, const Entry *old_entry, gboolean external_change)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	Entry *entry;
-	guint i, j;
-	gint64 last_metric = -1;
-	guint32 expected_metric;
-	GPtrArray *entries;
-	GArray *changed_metrics = g_array_new (FALSE, FALSE, sizeof (guint32));
-	GHashTable *assumed_metrics;
-	GArray *routes;
-	gboolean changed = FALSE;
-	int ifindex_to_flush = 0;
-
-	g_assert (priv->resync.guard == 0);
-	priv->resync.guard++;
-
-	if (!external_change) {
-		if (vtable->vt->is_ip4)
-			priv->resync.has_v4_changes = FALSE;
-		else
-			priv->resync.has_v6_changes = FALSE;
-		if (!priv->resync.has_v4_changes && !priv->resync.has_v6_changes)
-			_resync_idle_cancel (self);
-	}
-
-	entries = vtable->get_entries (priv);
-
-	routes = vtable->vt->route_get_all (priv->platform, 0, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-
-	assumed_metrics = _get_assumed_interface_metrics (vtable, self, routes);
-
-	if (old_entry && old_entry->synced && !old_entry->never_default) {
-		/* The old version obviously changed. */
-		g_array_append_val (changed_metrics, old_entry->effective_metric);
-	}
-
-	/* first iterate over all entries and adjust the effective metrics. */
-	for (i = 0; i < entries->len; i++) {
-		entry = g_ptr_array_index (entries, i);
-
-		if (entry->never_default)
-			continue;
-
-		if (!entry->synced) {
-			gboolean has_synced_entry = FALSE;
-
-			/* A non synced entry is completely ignored, if we have
-			 * a synced entry for the same if index.
-			 * Otherwise the metric of the entry is still remembered as
-			 * last_metric to avoid reusing it. */
-			for (j = 0; j < entries->len; j++) {
-				const Entry *e = g_ptr_array_index (entries, j);
-
-				if (   e->synced
-					&& e->route.rx.ifindex == entry->route.rx.ifindex) {
-					has_synced_entry = TRUE;
-					break;
-				}
-			}
-			if (!has_synced_entry)
-				last_metric = MAX (last_metric, (gint64) entry->effective_metric);
-			continue;
-		}
-
-		expected_metric = entry->route.rx.metric;
-		if ((gint64) expected_metric <= last_metric)
-			expected_metric = last_metric == G_MAXUINT32 ? G_MAXUINT32 : last_metric + 1;
-
-		while (   expected_metric < G_MAXUINT32
-		       && g_hash_table_contains (assumed_metrics, GUINT_TO_POINTER (expected_metric))) {
-			gboolean has_metric_for_ifindex = FALSE;
-
-			/* Check if there are assumed devices that have default routes with this metric.
-			 * If there are any, we have to pick another effective_metric. */
-
-			/* However, if there is a matching route (ifindex+metric) for our current entry, we are done. */
-			for (j = 0; j < routes->len; j++) {
-				const NMPlatformIPRoute *r = _vt_route_index (vtable, routes, i);
-
-				if (   r->metric == expected_metric
-				    && r->ifindex == entry->route.rx.ifindex) {
-					has_metric_for_ifindex = TRUE;
-					break;
-				}
-			}
-			if (has_metric_for_ifindex)
-				break;
-			expected_metric++;
-		}
-
-		if (changed_entry == entry) {
-			/* for the changed entry, the previous metric was either old_entry->effective_metric,
-			 * or none. Hence, we only have to remember what is going to change. */
-			g_array_append_val (changed_metrics, expected_metric);
-			if (!old_entry) {
-				_LOG2D (vtable, i, entry, "sync:add    %s (%u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) expected_metric);
-			} else if (old_entry != changed_entry) {
-				_LOG2D (vtable, i, entry, "sync:update %s (%u -> %u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) old_entry->effective_metric,
-				        (guint) expected_metric);
-			} else {
-				_LOG2D (vtable, i, entry, "sync:resync %s (%u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) expected_metric);
-			}
-		} else if (entry->effective_metric != expected_metric) {
-			g_array_append_val (changed_metrics, entry->effective_metric);
-			g_array_append_val (changed_metrics, expected_metric);
-			_LOG2D (vtable, i, entry, "sync:metric %s (%u -> %u)",
-			        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric,
-			        (guint) expected_metric);
-		} else {
-			if (!_vt_routes_has_entry (vtable, routes, entry)) {
-				g_array_append_val (changed_metrics, entry->effective_metric);
-				_LOG2D (vtable, i, entry, "sync:re-add %s (%u -> %u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric,
-				        (guint) entry->effective_metric);
-			}
-		}
-
-		if (entry->effective_metric != expected_metric) {
-			entry->effective_metric = expected_metric;
-			changed = TRUE;
-		}
-		last_metric = expected_metric;
-	}
-
-	g_array_free (routes, TRUE);
-
-	g_array_sort_with_data (changed_metrics, nm_cmp_uint32_p_with_data, NULL);
-	last_metric = -1;
-	for (j = 0; j < changed_metrics->len; j++) {
-		expected_metric = g_array_index (changed_metrics, guint32, j);
-
-		if (last_metric == (gint64) expected_metric) {
-			/* skip duplicates. */
-			continue;
-		}
-		changed |= _platform_route_sync_add (vtable, self, expected_metric);
-		last_metric = expected_metric;
-	}
-
-	if (   old_entry
-	    && !changed_entry
-	    && old_entry->synced
-	    && !old_entry->never_default) {
-		/* If we entriely remove an entry that was synced before, we must make
-		 * sure to flush routes for this ifindex too. Otherwise they linger
-		 * around as "assumed" routes */
-		ifindex_to_flush = old_entry->route.rx.ifindex;
-	}
-
-	changed |= _platform_route_sync_flush (vtable, self, ifindex_to_flush);
-
-	g_array_free (changed_metrics, TRUE);
-	g_hash_table_unref (assumed_metrics);
-
-	priv->resync.guard--;
-	return changed;
-}
-
-static gboolean
-_entry_at_idx_update (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx, const Entry *old_entry)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	Entry *entry;
-	GPtrArray *entries;
-
-	entries = vtable->get_entries (priv);
-	g_assert (entry_idx < entries->len);
-
-	entry = g_ptr_array_index (entries, entry_idx);
-
-	g_assert (   !old_entry
-	          || (entry->source.pointer == old_entry->source.pointer && entry->route.rx.ifindex == old_entry->route.rx.ifindex));
-
-	if (!entry->synced && !entry->never_default)
-		entry->effective_metric = entry->route.rx.metric;
-
-	_LOG2D (vtable, entry_idx, entry, "%s %s (%"G_GUINT32_FORMAT")",
-	        old_entry
-	            ? (entry != old_entry
-	                   ? "record:update"
-	                   : "record:resync")
-	            : "record:add   ",
-	        vtable->vt->route_to_string (&entry->route, NULL, 0),
-	        entry->effective_metric);
-
-	g_ptr_array_sort_with_data (entries, _sort_entries_cmp, NULL);
-
-	return _resync_all (vtable, self, entry, old_entry, FALSE);
-}
-
-static gboolean
-_entry_at_idx_remove (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	Entry *entry;
-	GPtrArray *entries;
-	gboolean ret;
-
-	entries = vtable->get_entries (priv);
-
-	g_assert (entry_idx < entries->len);
-
-	entry = g_ptr_array_index (entries, entry_idx);
-
-	_LOG2D (vtable, entry_idx, entry, "record:remove %s (%u)",
-	       vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric);
-
-	/* Remove the entry from the list (but don't free it yet) */
-	g_ptr_array_index (entries, entry_idx) = NULL;
-	g_ptr_array_remove_index (entries, entry_idx);
-
-	ret = _resync_all (vtable, self, NULL, entry, FALSE);
-	_entry_free (entry);
-
-	return ret;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_ipx_update_default_route (const VTableIP *vtable,
-                           NMDefaultRouteManager *self,
-                           gpointer source)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	Entry *entry;
-	guint entry_idx;
-	const NMPlatformIPRoute *default_route = NULL;
-	NMPlatformIPXRoute rt;
-	int ip_ifindex;
-	GPtrArray *entries;
-	NMDevice *device = NULL;
-	NMVpnConnection *vpn = NULL;
-	gboolean never_default = FALSE;
-	gboolean synced = FALSE, ret;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return FALSE;
-
-	if (NM_IS_DEVICE (source))
-		device = source;
-	else if (NM_IS_VPN_CONNECTION (source))
-		vpn = source;
-	else
-		g_return_val_if_reached (FALSE);
-
-	if (device)
-		ip_ifindex = nm_device_get_ip_ifindex (device);
-	else
-		ip_ifindex = nm_vpn_connection_get_ip_ifindex (vpn, TRUE);
-
-	entries = vtable->get_entries (priv);
-	entry = _entry_find_by_source (entries, source, &entry_idx);
-
-	if (   entry
-	    && entry->route.rx.ifindex != ip_ifindex) {
-		/* Strange... the ifindex changed... Remove the device and start again. */
-		_LOG2D (vtable, entry_idx, entry, "ifindex changed: %d -> %d",
-		        entry->route.rx.ifindex, ip_ifindex);
-
-		g_object_freeze_notify (G_OBJECT (self));
-		_entry_at_idx_remove (vtable, self, entry_idx);
-		g_assert (!_entry_find_by_source (entries, source, NULL));
-		ret = _ipx_update_default_route (vtable, self, source);
-		g_object_thaw_notify (G_OBJECT (self));
-		return ret;
-	}
-
-	/* get the @default_route from the device. */
-	if (ip_ifindex > 0) {
-		if (device) {
-			gboolean is_assumed = FALSE;
-
-			if (vtable->vt->is_ip4)
-				default_route = (const NMPlatformIPRoute *) nm_device_get_ip4_default_route (device, &is_assumed);
-			else
-				default_route = (const NMPlatformIPRoute *) nm_device_get_ip6_default_route (device, &is_assumed);
-			if (!default_route && !is_assumed) {
-				/* the device has no default route, but it is not assumed. That means, NMDefaultRouteManager
-				 * enforces that the device has no default route.
-				 *
-				 * Hence we have to keep track of this entry, otherwise a missing entry tells us
-				 * that the interface is assumed and NM would not remove the default routes on
-				 * the device. */
-				memset (&rt, 0, sizeof (rt));
-				rt.rx.ifindex = ip_ifindex;
-				rt.rx.rt_source = NM_IP_CONFIG_SOURCE_UNKNOWN;
-				rt.rx.metric = G_MAXUINT32;
-				default_route = &rt.rx;
-
-				never_default = TRUE;
-			}
-			synced = !is_assumed;
-		} else {
-			NMConnection *connection = nm_active_connection_get_applied_connection ((NMActiveConnection *) vpn);
-
-			if (   connection
-			    && nm_vpn_connection_get_vpn_state (vpn) == NM_VPN_CONNECTION_STATE_ACTIVATED) {
-
-				memset (&rt, 0, sizeof (rt));
-				if (vtable->vt->is_ip4) {
-					NMIP4Config *vpn_config;
-
-					vpn_config = nm_vpn_connection_get_ip4_config (vpn);
-					if (vpn_config) {
-						never_default = nm_ip4_config_get_never_default (vpn_config);
-						rt.r4.ifindex = ip_ifindex;
-						rt.r4.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-						rt.r4.gateway = nm_ip4_config_get_gateway (vpn_config);
-						rt.r4.metric = nm_vpn_connection_get_ip4_route_metric (vpn);
-						rt.r4.mss = nm_ip4_config_get_mss (vpn_config);
-						default_route = &rt.rx;
-					}
-				} else {
-					NMIP6Config *vpn_config;
-
-					vpn_config = nm_vpn_connection_get_ip6_config (vpn);
-					if (vpn_config) {
-						const struct in6_addr *int_gw = nm_ip6_config_get_gateway (vpn_config);
-
-						never_default = nm_ip6_config_get_never_default (vpn_config);
-						rt.r6.ifindex = ip_ifindex;
-						rt.r6.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-						rt.r6.gateway = int_gw ? *int_gw : in6addr_any;
-						rt.r6.metric = nm_vpn_connection_get_ip6_route_metric (vpn);
-						rt.r6.mss = nm_ip6_config_get_mss (vpn_config);
-						default_route = &rt.rx;
-					}
-				}
-			}
-			if (nm_vpn_connection_get_ip_ifindex (vpn, FALSE) > 0)
-				synced = TRUE;
-			else {
-				/* a VPN connection without tunnel device cannot have a non-synced, missing default route.
-				 * Either it has a default route (which is synced), or it has no entry. */
-				synced = default_route && !never_default;
-			}
-		}
-	}
-
-	g_assert (!default_route || default_route->plen == 0);
-
-	if (!synced && never_default) {
-		/* having a non-synced, never-default entry is non-sensical. Unset
-		 * @default_route so that we don't add such an entry below. */
-		default_route = NULL;
-	}
-
-	if (!entry && !default_route) {
-		/* nothing to do */
-		return FALSE;
-	} else if (!entry) {
-		/* add */
-		entry = g_slice_new0 (Entry);
-		entry->source.object = g_object_ref (source);
-
-		if (vtable->vt->is_ip4)
-			entry->route.r4 = *((const NMPlatformIP4Route *) default_route);
-		else
-			entry->route.r6 = *((const NMPlatformIP6Route *) default_route);
-
-		/* only use normalized metrics */
-		entry->route.rx.metric = vtable->vt->metric_normalize (entry->route.rx.metric);
-		entry->route.rx.ifindex = ip_ifindex;
-		entry->never_default = never_default;
-		entry->effective_metric = entry->route.rx.metric;
-		entry->synced = synced;
-
-		g_ptr_array_add (entries, entry);
-		return _entry_at_idx_update (vtable, self, entries->len - 1, NULL);
-	} else if (default_route) {
-		/* update */
-		Entry old_entry, new_entry;
-
-		new_entry = *entry;
-		if (vtable->vt->is_ip4)
-			new_entry.route.r4 = *((const NMPlatformIP4Route *) default_route);
-		else
-			new_entry.route.r6 = *((const NMPlatformIP6Route *) default_route);
-		/* only use normalized metrics */
-		new_entry.route.rx.metric = vtable->vt->metric_normalize (new_entry.route.rx.metric);
-		new_entry.route.rx.ifindex = ip_ifindex;
-		new_entry.never_default = never_default;
-		new_entry.synced = synced;
-
-		if (memcmp (entry, &new_entry, sizeof (new_entry)) == 0) {
-			if (!synced) {
-				/* the internal book-keeping doesn't change, so don't do a full
-				 * sync of the configured routes. */
-				return FALSE;
-			}
-			return _entry_at_idx_update (vtable, self, entry_idx, entry);
-		} else {
-			old_entry = *entry;
-			*entry = new_entry;
-			return _entry_at_idx_update (vtable, self, entry_idx, &old_entry);
-		}
-	} else {
-		/* delete */
-		return _entry_at_idx_remove (vtable, self, entry_idx);
-	}
-}
-
-gboolean
-nm_default_route_manager_ip4_update_default_route (NMDefaultRouteManager *self,
-                                                   gpointer source)
-{
-	return _ipx_update_default_route (&vtable_ip4, self, source);
-}
-
-gboolean
-nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *self,
-                                                   gpointer source)
-{
-	return _ipx_update_default_route (&vtable_ip6, self, source);
-}
-
-/*****************************************************************************/
-
-static gboolean
-_ipx_connection_has_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
-{
-	const char *method;
-	NMSettingIPConfig *s_ip;
-	gboolean is_never_default = FALSE;
-	gboolean has_default_route = FALSE;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
-
-	if (!connection)
-		goto out;
-
-	if (vtable->vt->is_ip4)
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-	else
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-	if (!s_ip)
-		goto out;
-	if (nm_setting_ip_config_get_never_default (s_ip)) {
-		is_never_default = TRUE;
-		goto out;
-	}
-
-	if (vtable->vt->is_ip4) {
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-		if (   !method
-		    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
-		    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
-			goto out;
-	} else {
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-		if (   !method
-		    || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
-		    || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
-			goto out;
-	}
-
-	has_default_route = TRUE;
-out:
-	if (out_is_never_default)
-		*out_is_never_default = is_never_default;
-	return has_default_route;
-}
-
-gboolean
-nm_default_route_manager_ip4_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
-{
-	return _ipx_connection_has_default_route (&vtable_ip4, self, connection, out_is_never_default);
-}
-
-gboolean
-nm_default_route_manager_ip6_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
-{
-	return _ipx_connection_has_default_route (&vtable_ip6, self, connection, out_is_never_default);
-}
-
-/*****************************************************************************/
-
-static NMDevice *
-_ipx_get_best_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	GPtrArray *entries;
-	guint i;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	if (!devices)
-		return NULL;
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return NULL;
-	entries = vtable->get_entries (priv);
-
-	for (i = 0; i < entries->len; i++) {
-		Entry *entry = g_ptr_array_index (entries, i);
-		NMDeviceState state;
-
-		if (!NM_IS_DEVICE (entry->source.pointer))
-			continue;
-
-		if (entry->never_default)
-			continue;
-
-		state = nm_device_get_state (entry->source.device);
-		if (   state <= NM_DEVICE_STATE_DISCONNECTED
-		    || state >= NM_DEVICE_STATE_DEACTIVATING) {
-			/* FIXME: we also track unmanaged devices with assumed default routes.
-			 * Skip them, they are (currently) no candidates for best-device.
-			 *
-			 * Later we also want to properly assume connections for unmanaged devices.
-			 *
-			 * Also, we don't want to have DEACTIVATING devices returned as best_device(). */
-			continue;
-		}
-
-		if (g_slist_find ((GSList *) devices, entry->source.device)) {
-			g_return_val_if_fail (nm_device_get_act_request (entry->source.pointer), entry->source.pointer);
-			return entry->source.pointer;
-		}
-	}
-	return NULL;
-}
-
-/** _ipx_get_best_activating_device:
- * @vtable: the virtual table
- * @self: #NMDefaultRouteManager
- * @devices: list of devices to be searched. Only devices from this list will be considered
- * @fully_activated: if #TRUE, only search for devices that are fully activated. Otherwise,
- *   search if there is a best device going to be activated. In the latter case, this will
- *   return NULL if the best device is already activated.
- * @preferred_device: if not-NULL, this device is preferred if there are more devices with
- *   the same priority.
- **/
-static NMDevice *
-_ipx_get_best_activating_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices, NMDevice *preferred_device)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	const GSList *iter;
-	NMDevice *best_device = NULL;
-	guint32 best_prio = G_MAXUINT32;
-	NMDevice *best_activated_device;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return NULL;
-
-	best_activated_device = _ipx_get_best_device (vtable, self, devices);
-
-	for (iter = devices; iter; iter = g_slist_next (iter)) {
-		NMDevice *device = NM_DEVICE (iter->data);
-		guint32 prio;
-		Entry *entry;
-
-		entry = _entry_find_by_source (vtable->get_entries (priv), device, NULL);
-
-		if (entry) {
-			/* of all the device that have an entry, we already know that best_activated_device
-			 * is the best. entry cannot be better. */
-			if (entry->source.device != best_activated_device)
-				continue;
-			prio = entry->effective_metric;
-		} else {
-			NMDeviceState state = nm_device_get_state (device);
-
-			if (   state <= NM_DEVICE_STATE_DISCONNECTED
-			    || state >= NM_DEVICE_STATE_DEACTIVATING)
-				continue;
-
-			if (!_ipx_connection_has_default_route (vtable, self, nm_device_get_applied_connection (device), NULL))
-				continue;
-
-			prio = nm_device_get_ip4_route_metric (device);
-		}
-		prio = vtable->vt->metric_normalize (prio);
-
-		if (   !best_device
-		    || prio < best_prio
-			|| (prio == best_prio && preferred_device == device)) {
-			best_device = device;
-			best_prio = prio;
-		}
-	}
-
-	/* There's only a best activating device if the best device
-	 * among all activating and already-activated devices is a
-	 * still-activating one.
-	 */
-	if (best_device && nm_device_get_state (best_device) >= NM_DEVICE_STATE_SECONDARIES)
-		return NULL;
-	return best_device;
-}
-
-NMDevice *
-nm_default_route_manager_ip4_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
-{
-	if (fully_activated)
-		return _ipx_get_best_device (&vtable_ip4, self, devices);
-	else
-		return _ipx_get_best_activating_device (&vtable_ip4, self, devices, preferred_device);
-}
-
-NMDevice *
-nm_default_route_manager_ip6_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
-{
-	if (fully_activated)
-		return _ipx_get_best_device (&vtable_ip6, self, devices);
-	else
-		return _ipx_get_best_activating_device (&vtable_ip6, self, devices, preferred_device);
-}
-
-/*****************************************************************************/
-
-static gpointer
-_ipx_get_best_config (const VTableIP *vtable,
-                      NMDefaultRouteManager *self,
-                      gboolean ignore_never_default,
-                      const char **out_ip_iface,
-                      NMActiveConnection **out_ac,
-                      NMDevice **out_device,
-                      NMVpnConnection **out_vpn)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	GPtrArray *entries;
-	guint i;
-	gpointer config_result = NULL;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	if (out_ip_iface)
-		*out_ip_iface = NULL;
-	if (out_ac)
-		*out_ac = NULL;
-	if (out_device)
-		*out_device = NULL;
-	if (out_vpn)
-		*out_vpn = NULL;
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return NULL;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	entries = vtable->get_entries (priv);
-
-	for (i = 0; i < entries->len; i++) {
-		Entry *entry = g_ptr_array_index (entries, i);
-
-		if (!NM_IS_DEVICE (entry->source.pointer)) {
-			NMVpnConnection *vpn = NM_VPN_CONNECTION (entry->source.vpn);
-
-			if (entry->never_default && !ignore_never_default)
-				continue;
-
-			if (vtable->vt->is_ip4)
-				config_result = nm_vpn_connection_get_ip4_config (vpn);
-			else
-				config_result = nm_vpn_connection_get_ip6_config (vpn);
-			g_assert (config_result);
-
-			if (out_vpn)
-				*out_vpn = vpn;
-			if (out_ac)
-				*out_ac = NM_ACTIVE_CONNECTION (vpn);
-			if (out_ip_iface)
-				*out_ip_iface = nm_vpn_connection_get_ip_iface (vpn, TRUE);
-		} else {
-			NMDevice *device = entry->source.device;
-			NMActRequest *req;
-			NMDeviceState state;
-
-			if (entry->never_default)
-				continue;
-
-			state = nm_device_get_state (device);
-			if (   state <= NM_DEVICE_STATE_DISCONNECTED
-			    || state >= NM_DEVICE_STATE_DEACTIVATING) {
-				/* FIXME: the device has a default route, but we ignore it due to
-				 * unexpected state. That happens for example for unmanaged devices.
-				 *
-				 * In the future, we want unmanaged devices also assume a connection
-				 * if they are activated externally.
-				 *
-				 * Also, we don't want to have DEACTIVATING devices returned as best_config(). */
-				continue;
-			}
-
-			if (vtable->vt->is_ip4)
-				config_result = nm_device_get_ip4_config (device);
-			else
-				config_result = nm_device_get_ip6_config (device);
-			g_assert (config_result);
-			req = nm_device_get_act_request (device);
-			g_assert (req);
-
-			if (out_device)
-				*out_device = device;
-			if (out_ac)
-				*out_ac = NM_ACTIVE_CONNECTION (req);
-			if (out_ip_iface)
-				*out_ip_iface = nm_device_get_ip_iface (device);
-		}
-		break;
-	}
-
-	return config_result;
-}
-
-NMIP4Config *
-nm_default_route_manager_ip4_get_best_config (NMDefaultRouteManager *self,
-                                              gboolean ignore_never_default,
-                                              const char **out_ip_iface,
-                                              NMActiveConnection **out_ac,
-                                              NMDevice **out_device,
-                                              NMVpnConnection **out_vpn)
-{
-	return _ipx_get_best_config (&vtable_ip4,
-	                             self,
-	                             ignore_never_default,
-	                             out_ip_iface,
-	                             out_ac,
-	                             out_device,
-	                             out_vpn);
-}
-
-NMIP6Config *
-nm_default_route_manager_ip6_get_best_config (NMDefaultRouteManager *self,
-                                              gboolean ignore_never_default,
-                                              const char **out_ip_iface,
-                                              NMActiveConnection **out_ac,
-                                              NMDevice **out_device,
-                                              NMVpnConnection **out_vpn)
-{
-	return _ipx_get_best_config (&vtable_ip6,
-	                             self,
-	                             ignore_never_default,
-	                             out_ip_iface,
-	                             out_ac,
-	                             out_device,
-	                             out_vpn);
-}
-
-/*****************************************************************************/
-
-static GPtrArray *
-_v4_get_entries (NMDefaultRouteManagerPrivate *priv)
-{
-	return priv->entries_ip4;
-}
-
-static GPtrArray *
-_v6_get_entries (NMDefaultRouteManagerPrivate *priv)
-{
-	return priv->entries_ip6;
-}
-
-static const VTableIP vtable_ip4 = {
-	.vt                             = &nm_platform_vtable_route_v4,
-	.get_entries                    = _v4_get_entries,
-};
-
-static const VTableIP vtable_ip6 = {
-	.vt                             = &nm_platform_vtable_route_v6,
-	.get_entries                    = _v6_get_entries,
-};
-
-/*****************************************************************************/
-
-static gboolean
-_resync_now (NMDefaultRouteManager *self)
-{
-	gboolean has_v4_changes, has_v6_changes;
-	gboolean changed = FALSE;
-
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	has_v4_changes = priv->resync.has_v4_changes;
-	has_v6_changes = priv->resync.has_v6_changes;
-
-	_LOGD (0, "resync: sync now (%u) (IPv4 changes: %s, IPv6 changes: %s)", priv->resync.idle_handle,
-	       has_v4_changes ? "yes" : "no", has_v6_changes ? "yes" : "no");
-
-	priv->resync.has_v4_changes = FALSE;
-	priv->resync.has_v6_changes = FALSE;
-	nm_clear_g_source (&priv->resync.idle_handle);
-	priv->resync.backoff_wait_time_ms =
-	    priv->resync.backoff_wait_time_ms == 0
-	    ? 100
-	    : priv->resync.backoff_wait_time_ms * 2;
-
-	if (has_v4_changes)
-		changed |= _resync_all (&vtable_ip4, self, NULL, NULL, TRUE);
-
-	if (has_v6_changes)
-		changed |= _resync_all (&vtable_ip6, self, NULL, NULL, TRUE);
-
-	if (!changed) {
-		/* Nothing changed: reset the backoff wait time */
-		_resync_idle_cancel (self);
-	}
-
-	return changed;
-}
-
-/**
- * nm_default_route_manager_resync:
- * @self: the #NMDefaultRouteManager instance
- * @af_family: the address family to resync, can be
- *   AF_INET, AF_INET6 or AF_UNSPEC to sync both.
- *
- * #NMDefaultRouteManager keeps an internal list of configured
- * routes. Usually, it configures routes in the system only
- *  - when that internal list changes due to
- *    nm_default_route_manager_ip4_update_default_route() or
- *    nm_default_route_manager_ip6_update_default_route().
- *  - when platform notifies about changes, via _resync_idle_now().
- * This forces a resync to update the internal bookkeeping
- * with what is currently configured in the system, but also
- * reconfigure the system with all non-assumed default routes.
- *
- * Returns: %TRUE if anything changed during resync.
- */
-gboolean
-nm_default_route_manager_resync (NMDefaultRouteManager *self,
-                                 int af_family)
-{
-	NMDefaultRouteManagerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
-	g_return_val_if_fail (NM_IN_SET (af_family, AF_INET, AF_INET6, AF_UNSPEC), FALSE);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	if (priv->disposed)
-		return FALSE;
-
-	switch (af_family) {
-	case AF_INET:
-		priv->resync.has_v4_changes = TRUE;
-		break;
-	case AF_INET6:
-		priv->resync.has_v6_changes = TRUE;
-		break;
-	default:
-		priv->resync.has_v4_changes = TRUE;
-		priv->resync.has_v6_changes = TRUE;
-		break;
-	}
-
-	return _resync_now (self);
-}
-
-static gboolean
-_resync_idle_now (NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	priv->resync.idle_handle = 0;
-	_resync_now (self);
-	return G_SOURCE_REMOVE;
-}
-
-static void
-_resync_idle_cancel (NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	if (priv->resync.idle_handle) {
-		_LOGD (0, "resync: cancelled (%u)", priv->resync.idle_handle);
-		g_source_remove (priv->resync.idle_handle);
-		priv->resync.idle_handle = 0;
-	}
-	priv->resync.backoff_wait_time_ms = 0;
-	priv->resync.has_v4_changes = FALSE;
-	priv->resync.has_v6_changes = FALSE;
-}
-
-static void
-_resync_idle_reschedule (NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	/* since we react on external changes and re-add/remove default routes for
-	 * the interfaces we manage, there could be the erroneous situation where two applications
-	 * fight over a certain default route.
-	 * Avoid this, by increasingly wait longer to touch the system (backoff wait time). */
-
-	if (priv->resync.backoff_wait_time_ms == 0) {
-		/* for scheduling idle, always reschedule (to process all other events first) */
-		if (priv->resync.idle_handle)
-			g_source_remove (priv->resync.idle_handle);
-		else
-			_LOGD (0, "resync: schedule on idle");
-		/* Schedule this at low priority so that on an external change to platform
-		 * a NMDevice has a chance to picks up the changes first. */
-		priv->resync.idle_handle = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) _resync_idle_now, self, NULL);
-	} else if (!priv->resync.idle_handle) {
-		priv->resync.idle_handle =  g_timeout_add (priv->resync.backoff_wait_time_ms, (GSourceFunc) _resync_idle_now, self);
-		_LOGD (0, "resync: schedule in %u.%03u seconds (%u)", priv->resync.backoff_wait_time_ms/1000,
-		       priv->resync.backoff_wait_time_ms%1000, priv->resync.idle_handle);
-	}
-}
-
-static void
-_platform_changed_cb (NMPlatform *platform,
-                      int obj_type_i,
-                      int ifindex,
-                      gpointer platform_object,
-                      int change_type_i,
-                      NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	const NMPObjectType obj_type = obj_type_i;
-	const VTableIP *vtable;
-
-	switch (obj_type) {
-	case NMP_OBJECT_TYPE_IP4_ADDRESS:
-		vtable = &vtable_ip4;
-		break;
-	case NMP_OBJECT_TYPE_IP6_ADDRESS:
-		vtable = &vtable_ip6;
-		break;
-	case NMP_OBJECT_TYPE_IP4_ROUTE:
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (platform_object))
-			return;
-		vtable = &vtable_ip4;
-		break;
-	case NMP_OBJECT_TYPE_IP6_ROUTE:
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (platform_object))
-			return;
-		vtable = &vtable_ip6;
-		break;
-	default:
-		g_return_if_reached ();
-	}
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	if (priv->resync.guard) {
-		/* callbacks while executing _resync_all() are ignored. */
-		return;
-	}
-
-	if (vtable->vt->is_ip4)
-		priv->resync.has_v4_changes = TRUE;
-	else
-		priv->resync.has_v6_changes = TRUE;
-
-	_resync_idle_reschedule (self);
-}
-
-/*****************************************************************************/
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	switch (prop_id) {
-	case PROP_LOG_WITH_PTR:
-		/* construct-only */
-		priv->log_with_ptr = g_value_get_boolean (value);
-		break;
-	case PROP_PLATFORM:
-		/* construct-only */
-		priv->platform = g_value_get_object (value) ? : NM_PLATFORM_GET;
-		if (!priv->platform)
-			g_return_if_reached ();
-		g_object_ref (priv->platform);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_default_route_manager_init (NMDefaultRouteManager *self)
-{
-}
-
-static void
-constructed (GObject *object)
-{
-	NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	priv->entries_ip4 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
-	priv->entries_ip6 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
-
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-}
-
-NMDefaultRouteManager *
-nm_default_route_manager_new (gboolean log_with_ptr, NMPlatform *platform)
-{
-	return g_object_new (NM_TYPE_DEFAULT_ROUTE_MANAGER,
-	                     NM_DEFAULT_ROUTE_MANAGER_LOG_WITH_PTR, log_with_ptr,
-	                     NM_DEFAULT_ROUTE_MANAGER_PLATFORM, platform,
-	                     NULL);
-}
-
-static void
-dispose (GObject *object)
-{
-	NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	priv->disposed = TRUE;
-
-	if (priv->platform) {
-		g_signal_handlers_disconnect_by_func (priv->platform, G_CALLBACK (_platform_changed_cb), self);
-		g_clear_object (&priv->platform);
-	}
-
-	_resync_idle_cancel (self);
-
-	/* g_ptr_array_free() invokes the free function for all entries without actually
-	 * removing them and having dangling pointers in the process. _entry_free()
-	 * will unref the source, which might cause the destruction of the object, which
-	 * might trigger calling into @self again. This is guarded by priv->dispose.
-	 * If you remove priv->dispose, you must refactor the lines below to remove enties
-	 * one-by-one.
-	 */
-	if (priv->entries_ip4) {
-		g_ptr_array_free (priv->entries_ip4, TRUE);
-		priv->entries_ip4 = NULL;
-	}
-	if (priv->entries_ip6) {
-		g_ptr_array_free (priv->entries_ip6, TRUE);
-		priv->entries_ip6 = NULL;
-	}
-
-	G_OBJECT_CLASS (nm_default_route_manager_parent_class)->dispose (object);
-}
-
-static void
-nm_default_route_manager_class_init (NMDefaultRouteManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->constructed = constructed;
-	object_class->dispose = dispose;
-	object_class->set_property = set_property;
-
-	obj_properties[PROP_LOG_WITH_PTR] =
-	    g_param_spec_boolean (NM_DEFAULT_ROUTE_MANAGER_LOG_WITH_PTR, "", "",
-	                          TRUE,
-	                          G_PARAM_WRITABLE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_PLATFORM] =
-	    g_param_spec_object (NM_DEFAULT_ROUTE_MANAGER_PLATFORM, "", "",
-	                         NM_TYPE_PLATFORM,
-	                         G_PARAM_WRITABLE |
-	                         G_PARAM_CONSTRUCT_ONLY |
-	                         G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-}
diff --git a/src/nm-default-route-manager.h b/src/nm-default-route-manager.h
deleted file mode 100644
index bac8a6eb..00000000
--- a/src/nm-default-route-manager.h
+++ /dev/null
@@ -1,67 +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) 2014 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_DEFAULT_ROUTE_MANAGER_H__
-#define __NETWORKMANAGER_DEFAULT_ROUTE_MANAGER_H__
-
-#include "nm-connection.h"
-
-#define NM_TYPE_DEFAULT_ROUTE_MANAGER            (nm_default_route_manager_get_type ())
-#define NM_DEFAULT_ROUTE_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DEFAULT_ROUTE_MANAGER, NMDefaultRouteManager))
-#define NM_DEFAULT_ROUTE_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DEFAULT_ROUTE_MANAGER, NMDefaultRouteManagerClass))
-#define NM_IS_DEFAULT_ROUTE_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DEFAULT_ROUTE_MANAGER))
-#define NM_IS_DEFAULT_ROUTE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DEFAULT_ROUTE_MANAGER))
-#define NM_DEFAULT_ROUTE_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DEFAULT_ROUTE_MANAGER, NMDefaultRouteManagerClass))
-
-#define NM_DEFAULT_ROUTE_MANAGER_LOG_WITH_PTR "log-with-ptr"
-#define NM_DEFAULT_ROUTE_MANAGER_PLATFORM     "platform"
-
-typedef struct _NMDefaultRouteManagerClass NMDefaultRouteManagerClass;
-
-GType nm_default_route_manager_get_type (void);
-
-NMDefaultRouteManager *nm_default_route_manager_new (gboolean log_with_ptr, NMPlatform *platform);
-
-gboolean nm_default_route_manager_ip4_update_default_route (NMDefaultRouteManager *manager, gpointer source);
-gboolean nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *manager, gpointer source);
-
-gboolean nm_default_route_manager_ip4_connection_has_default_route (NMDefaultRouteManager *manager, NMConnection *connection, gboolean *out_is_never_default);
-gboolean nm_default_route_manager_ip6_connection_has_default_route (NMDefaultRouteManager *manager, NMConnection *connection, gboolean *out_is_never_default);
-
-NMDevice *nm_default_route_manager_ip4_get_best_device (NMDefaultRouteManager *manager, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device);
-NMDevice *nm_default_route_manager_ip6_get_best_device (NMDefaultRouteManager *manager, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device);
-
-NMIP4Config *nm_default_route_manager_ip4_get_best_config (NMDefaultRouteManager *manager,
-                                                           gboolean ignore_never_default,
-                                                           const char **out_ip_iface,
-                                                           NMActiveConnection **out_ac,
-                                                           NMDevice **out_device,
-                                                           NMVpnConnection **out_vpn);
-NMIP6Config *nm_default_route_manager_ip6_get_best_config (NMDefaultRouteManager *manager,
-                                                           gboolean ignore_never_default,
-                                                           const char **out_ip_iface,
-                                                           NMActiveConnection **out_ac,
-                                                           NMDevice **out_device,
-                                                           NMVpnConnection **out_vpn);
-
-gboolean nm_default_route_manager_resync (NMDefaultRouteManager *self,
-                                          int af_family);
-
-#endif  /* NM_DEFAULT_ROUTE_MANAGER_H */
diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c
index 0d482e0c..237afdef 100644
--- a/src/nm-dispatcher.c
+++ b/src/nm-dispatcher.c
@@ -113,6 +113,8 @@ static void
 dump_ip4_to_props (NMIP4Config *ip4, GVariantBuilder *builder)
 {
 	GVariantBuilder int_builder;
+	NMDedupMultiIter ipconf_iter;
+	gboolean first;
 	guint n, i;
 	const NMPlatformIP4Address *addr;
 	const NMPlatformIP4Route *route;
@@ -120,15 +122,20 @@ dump_ip4_to_props (NMIP4Config *ip4, GVariantBuilder *builder)
 
 	/* Addresses */
 	g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
-	n = nm_ip4_config_get_num_addresses (ip4);
-	for (i = 0; i < n; i++) {
-		addr = nm_ip4_config_get_address (ip4, i);
+	first = TRUE;
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &addr) {
+		const NMPObject *default_route;
+
 		array[0] = addr->address;
 		array[1] = addr->plen;
-		array[2] = (i == 0) ? nm_ip4_config_get_gateway (ip4) : 0;
+		array[2] = (   first
+		            && (default_route = nm_ip4_config_best_default_route_get (ip4)))
+		           ? NMP_OBJECT_CAST_IP4_ROUTE (default_route)->gateway
+		           : (guint32) 0;
 		g_variant_builder_add (&int_builder, "@au",
 		                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
 		                                                  array, 3, sizeof (guint32)));
+		first = FALSE;
 	}
 	g_variant_builder_add (builder, "{sv}",
 	                       "addresses",
@@ -163,9 +170,9 @@ dump_ip4_to_props (NMIP4Config *ip4, GVariantBuilder *builder)
 
 	/* Static routes */
 	g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("aau"));
-	n = nm_ip4_config_get_num_routes (ip4);
-	for (i = 0; i < n; i++) {
-		route = nm_ip4_config_get_route (ip4, i);
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &route) {
+		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+			continue;
 		array[0] = route->network;
 		array[1] = route->plen;
 		array[2] = route->gateway;
@@ -183,25 +190,31 @@ static void
 dump_ip6_to_props (NMIP6Config *ip6, GVariantBuilder *builder)
 {
 	GVariantBuilder int_builder;
+	NMDedupMultiIter ipconf_iter;
 	guint n, i;
+	gboolean first;
 	const NMPlatformIP6Address *addr;
-	const struct in6_addr *gw_bytes;
 	const NMPlatformIP6Route *route;
 	GVariant *ip, *gw;
 
 	/* Addresses */
 	g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuay)"));
-	n = nm_ip6_config_get_num_addresses (ip6);
-	for (i = 0; i < n; i++) {
-		addr = nm_ip6_config_get_address (ip6, i);
-		gw_bytes = nm_ip6_config_get_gateway (ip6);
+
+	first = TRUE;
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &addr) {
+		const NMPObject *default_route;
+
 		ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
 		                                &addr->address,
 		                                sizeof (struct in6_addr), 1);
 		gw = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-		                                (i == 0 && gw_bytes) ? gw_bytes : &in6addr_any,
+		                                (   first
+		                                 && (default_route = nm_ip6_config_best_default_route_get (ip6)))
+		                                  ? &NMP_OBJECT_CAST_IP6_ROUTE (default_route)->gateway
+		                                  : &in6addr_any,
 		                                sizeof (struct in6_addr), 1);
 		g_variant_builder_add (&int_builder, "(@ayu@ay)", ip, addr->plen, gw);
+		first = FALSE;
 	}
 	g_variant_builder_add (builder, "{sv}",
 	                       "addresses",
@@ -231,9 +244,9 @@ dump_ip6_to_props (NMIP6Config *ip6, GVariantBuilder *builder)
 
 	/* Static routes */
 	g_variant_builder_init (&int_builder, G_VARIANT_TYPE ("a(ayuayu)"));
-	n = nm_ip6_config_get_num_routes (ip6);
-	for (i = 0; i < n; i++) {
-		route = nm_ip6_config_get_route (ip6, i);
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &route) {
+		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
+			continue;
 		ip = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
 		                                &route->network,
 		                                sizeof (struct in6_addr), 1);
diff --git a/src/nm-exported-object.c b/src/nm-exported-object.c
index 0e903b89..94264caa 100644
--- a/src/nm-exported-object.c
+++ b/src/nm-exported-object.c
@@ -272,7 +272,7 @@ nm_exported_object_class_add_interface (NMExportedObjectClass *object_class,
 		classinfo = g_slice_new (NMExportedObjectClassInfo);
 		classinfo->skeleton_types = NULL;
 		classinfo->methods = g_array_new (FALSE, FALSE, sizeof (NMExportedObjectDBusMethodImpl));
-		classinfo->properties = g_hash_table_new (g_str_hash, g_str_equal);
+		classinfo->properties = g_hash_table_new (nm_str_hash, g_str_equal);
 		g_type_set_qdata (G_TYPE_FROM_CLASS (object_class),
 		                  nm_exported_object_class_info_quark (), classinfo);
 	}
@@ -579,7 +579,7 @@ _create_export_path (NMExportedObjectClass *klass)
 	p = strchr (class_export_path, '%');
 	if (p) {
 		if (G_UNLIKELY (!prefix_counters))
-			prefix_counters = g_hash_table_new (g_str_hash, g_str_equal);
+			prefix_counters = g_hash_table_new (nm_str_hash, g_str_equal);
 
 		nm_assert (p[1] == 'l');
 		nm_assert (p[2] == 'l');
diff --git a/src/nm-firewall-manager.c b/src/nm-firewall-manager.c
index 4a887b79..b9878592 100644
--- a/src/nm-firewall-manager.c
+++ b/src/nm-firewall-manager.c
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include "NetworkManagerUtils.h"
+#include "nm-utils/c-list.h"
 
 /*****************************************************************************/
 
@@ -39,7 +40,7 @@ typedef struct {
 	GDBusProxy     *proxy;
 	GCancellable   *proxy_cancellable;
 
-	GHashTable     *pending_calls;
+	CList           pending_calls;
 	bool            running;
 } NMFirewallManagerPrivate;
 
@@ -76,6 +77,7 @@ typedef enum {
 } CBInfoMode;
 
 struct _NMFirewallManagerCallId {
+	CList lst;
 	NMFirewallManager *self;
 	CBInfoOpsType ops_type;
 	union {
@@ -176,8 +178,7 @@ _cb_info_create (NMFirewallManager *self,
 	} else
 		info->mode_mutable = CB_INFO_MODE_IDLE;
 
-	if (!nm_g_hash_table_add (priv->pending_calls, info))
-		nm_assert_not_reached ();
+	c_list_link_tail (&priv->pending_calls, &info->lst);
 
 	return info;
 }
@@ -185,6 +186,7 @@ _cb_info_create (NMFirewallManager *self,
 static void
 _cb_info_free (CBInfo *info)
 {
+	c_list_unlink (&info->lst);
 	if (info->mode != CB_INFO_MODE_IDLE) {
 		if (info->dbus.arg)
 			g_variant_unref (info->dbus.arg);
@@ -209,8 +211,9 @@ _cb_info_complete_normal (CBInfo *info, GError *error)
 {
 	NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (info->self);
 
-	if (!g_hash_table_remove (priv->pending_calls, info))
-		g_return_if_reached ();
+	nm_assert (c_list_contains (&priv->pending_calls, &info->lst));
+
+	c_list_unlink_init (&info->lst);
 
 	_cb_info_callback (info, error);
 	_cb_info_free (info);
@@ -423,8 +426,9 @@ nm_firewall_manager_cancel_call (NMFirewallManagerCallId call)
 	self = info->self;
 	priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
 
-	if (!g_hash_table_remove (priv->pending_calls, info))
-		g_return_if_reached ();
+	nm_assert (c_list_contains (&priv->pending_calls, &info->lst));
+
+	c_list_unlink_init (&info->lst);
 
 	nm_utils_error_set_cancelled (&error, FALSE, "NMFirewallManager");
 
@@ -488,8 +492,8 @@ _proxy_new_cb (GObject *source_object,
 	NMFirewallManagerPrivate *priv;
 	GDBusProxy *proxy;
 	gs_free_error GError *error = NULL;
-	GHashTableIter iter;
 	CBInfo *info;
+	CList *iter;
 
 	proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
 	if (   !proxy
@@ -513,8 +517,9 @@ _proxy_new_cb (GObject *source_object,
 		_LOGD (NULL, "firewall %s", "initialized (not running)");
 
 again:
-	g_hash_table_iter_init (&iter, priv->pending_calls);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &info, NULL)) {
+	c_list_for_each (iter, &priv->pending_calls) {
+		info = c_list_entry (iter, CBInfo, lst);
+
 		if (info->mode != CB_INFO_MODE_DBUS_WAITING)
 			continue;
 		if (priv->running) {
@@ -522,7 +527,7 @@ again:
 			_handle_dbus_start (self, info);
 		} else {
 			_LOGD (info, "complete: fake success");
-			g_hash_table_iter_remove (&iter);
+			c_list_unlink_init (&info->lst);
 			_cb_info_callback (info, NULL);
 			_cb_info_free (info);
 			goto again;
@@ -541,7 +546,7 @@ nm_firewall_manager_init (NMFirewallManager * self)
 {
 	NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
 
-	priv->pending_calls = g_hash_table_new (g_direct_hash, g_direct_equal);
+	c_list_init (&priv->pending_calls);
 }
 
 static void
@@ -572,13 +577,9 @@ dispose (GObject *object)
 	NMFirewallManager *self = NM_FIREWALL_MANAGER (object);
 	NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
 
-	if (priv->pending_calls) {
-		/* as every pending operation takes a reference to the manager,
-		 * we don't expect pending operations at this point. */
-		g_assert (g_hash_table_size (priv->pending_calls) == 0);
-		g_hash_table_unref (priv->pending_calls);
-		priv->pending_calls = NULL;
-	}
+	/* as every pending operation takes a reference to the manager,
+	 * we don't expect pending operations at this point. */
+	nm_assert (c_list_is_empty (&priv->pending_calls));
 
 	nm_clear_g_cancellable (&priv->proxy_cancellable);
 	g_clear_object (&priv->proxy);
diff --git a/src/nm-hostname-manager.c b/src/nm-hostname-manager.c
new file mode 100644
index 00000000..f569ab86
--- /dev/null
+++ b/src/nm-hostname-manager.c
@@ -0,0 +1,664 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager
+ *
+ * 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.
+ *
+ * (C) Copyright 2017 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-hostname-manager.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+
+#if HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include "nm-common-macros.h"
+#include "nm-dbus-interface.h"
+#include "nm-connection.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+
+#include "NetworkManagerUtils.h"
+
+/*****************************************************************************/
+
+#define HOSTNAMED_SERVICE_NAME      "org.freedesktop.hostname1"
+#define HOSTNAMED_SERVICE_PATH      "/org/freedesktop/hostname1"
+#define HOSTNAMED_SERVICE_INTERFACE "org.freedesktop.hostname1"
+
+#define HOSTNAME_FILE_DEFAULT        "/etc/hostname"
+#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"
+
+#if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) + defined(HOSTNAME_PERSIST_GENTOO)) > 1
+#error "Can only define one of HOSTNAME_PERSIST_*"
+#endif
+
+#if defined(HOSTNAME_PERSIST_SUSE)
+#define HOSTNAME_FILE           HOSTNAME_FILE_UCASE_HOSTNAME
+#elif defined(HOSTNAME_PERSIST_SLACKWARE)
+#define HOSTNAME_FILE           HOSTNAME_FILE_UCASE_HOSTNAME
+#elif defined(HOSTNAME_PERSIST_GENTOO)
+#define HOSTNAME_FILE           HOSTNAME_FILE_GENTOO
+#else
+#define HOSTNAME_FILE           HOSTNAME_FILE_DEFAULT
+#endif
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE (NMHostnameManager,
+	PROP_HOSTNAME,
+);
+
+typedef struct {
+	char *current_hostname;
+	GFileMonitor *monitor;
+	GFileMonitor *dhcp_monitor;
+	gulong monitor_id;
+	gulong dhcp_monitor_id;
+	GDBusProxy *hostnamed_proxy;
+} NMHostnameManagerPrivate;
+
+struct _NMHostnameManager {
+	GObject parent;
+	NMHostnameManagerPrivate _priv;
+};
+
+struct _NMHostnameManagerClass {
+	GObjectClass parent;
+};
+
+G_DEFINE_TYPE (NMHostnameManager, nm_hostname_manager, G_TYPE_OBJECT);
+
+#define NM_HOSTNAME_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMHostnameManager, NM_IS_HOSTNAME_MANAGER)
+
+NM_DEFINE_SINGLETON_GETTER (NMHostnameManager, nm_hostname_manager_get, NM_TYPE_HOSTNAME_MANAGER);
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_CORE
+#define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "hostname", __VA_ARGS__)
+
+/*****************************************************************************/
+
+#if defined(HOSTNAME_PERSIST_GENTOO)
+static gchar *
+read_hostname_gentoo (const char *path)
+{
+	gs_free char *contents = NULL;
+	gs_strfreev char **all_lines = NULL;
+	const char *tmp;
+	guint i;
+
+	if (!g_file_get_contents (path, &contents, NULL, NULL))
+		return NULL;
+
+	all_lines = g_strsplit (contents, "\n", 0);
+	for (i = 0; all_lines[i]; i++) {
+		g_strstrip (all_lines[i]);
+		if (all_lines[i][0] == '#' || all_lines[i][0] == '\0')
+			continue;
+		if (g_str_has_prefix (all_lines[i], "hostname=")) {
+			tmp = &all_lines[i][NM_STRLEN ("hostname=")];
+			return g_shell_unquote (tmp, NULL);
+		}
+	}
+	return NULL;
+}
+#endif
+
+#if defined(HOSTNAME_PERSIST_SLACKWARE)
+static gchar *
+read_hostname_slackware (const char *path)
+{
+	gs_free char *contents = NULL;
+	gs_strfreev char **all_lines = NULL;
+	char *tmp;
+	guint i, j = 0;
+
+	if (!g_file_get_contents (path, &contents, NULL, NULL))
+		return NULL;
+
+	all_lines = g_strsplit (contents, "\n", 0);
+	for (i = 0; all_lines[i]; i++) {
+		g_strstrip (all_lines[i]);
+		if (all_lines[i][0] == '#' || all_lines[i][0] == '\0')
+			continue;
+		tmp = &all_lines[i][0];
+		/* We only want up to the first '.' -- the rest of the */
+		/* fqdn is defined in /etc/hosts */
+		while (tmp[j] != '\0') {
+			if (tmp[j] == '.') {
+				tmp[j] = '\0';
+				break;
+			}
+			j++;
+		}
+		return g_shell_unquote (tmp, NULL);
+	}
+	return NULL;
+}
+#endif
+
+#if defined(HOSTNAME_PERSIST_SUSE)
+static gboolean
+hostname_is_dynamic (void)
+{
+	GIOChannel *channel;
+	char *str = NULL;
+	gboolean dynamic = FALSE;
+
+	channel = g_io_channel_new_file (CONF_DHCP, "r", NULL);
+	if (!channel)
+		return dynamic;
+
+	while (g_io_channel_read_line (channel, &str, NULL, NULL, NULL) != G_IO_STATUS_EOF) {
+		if (str) {
+			g_strstrip (str);
+			if (g_str_has_prefix (str, "DHCLIENT_SET_HOSTNAME="))
+				dynamic = strcmp (&str[NM_STRLEN ("DHCLIENT_SET_HOSTNAME=")], "\"yes\"") == 0;
+			g_free (str);
+		}
+	}
+
+	g_io_channel_shutdown (channel, FALSE, NULL);
+	g_io_channel_unref (channel);
+
+	return dynamic;
+}
+#endif
+
+/* Returns an allocated string which the caller owns and must eventually free */
+char *
+nm_hostname_manager_read_hostname (NMHostnameManager *self)
+{
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+	char *hostname = NULL;
+
+	if (priv->hostnamed_proxy) {
+		hostname = g_strdup (priv->current_hostname);
+		goto out;
+	}
+
+#if defined(HOSTNAME_PERSIST_SUSE)
+	if (priv->dhcp_monitor_id && hostname_is_dynamic ())
+		return NULL;
+#endif
+
+#if defined(HOSTNAME_PERSIST_GENTOO)
+	hostname = read_hostname_gentoo (HOSTNAME_FILE);
+#elif defined(HOSTNAME_PERSIST_SLACKWARE)
+	hostname = read_hostname_slackware (HOSTNAME_FILE);
+#else
+	if (g_file_get_contents (HOSTNAME_FILE, &hostname, NULL, NULL))
+		g_strchomp (hostname);
+#endif
+
+out:
+	if (hostname && !hostname[0]) {
+		g_free (hostname);
+		return NULL;
+	}
+
+	return hostname;
+}
+
+/*****************************************************************************/
+
+const char *
+nm_hostname_manager_get_hostname (NMHostnameManager *self)
+{
+	g_return_val_if_fail (NM_IS_HOSTNAME_MANAGER (self), NULL);
+	return NM_HOSTNAME_MANAGER_GET_PRIVATE (self)->current_hostname;
+}
+
+static void
+_set_hostname_take (NMHostnameManager *self, char *hostname)
+{
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+
+	_LOGI ("hostname changed from %s%s%s to %s%s%s",
+	       NM_PRINT_FMT_QUOTED (priv->current_hostname, "\"", priv->current_hostname, "\"", "(none)"),
+	       NM_PRINT_FMT_QUOTED (hostname, "\"", hostname, "\"", "(none)"));
+
+	g_free (priv->current_hostname);
+	priv->current_hostname = hostname;
+	_notify (self, PROP_HOSTNAME);
+}
+
+static void
+_set_hostname (NMHostnameManager *self, const char *hostname)
+{
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+
+	hostname = nm_str_not_empty (hostname);
+	if (!nm_streq0 (hostname, priv->current_hostname))
+		_set_hostname_take (self, g_strdup (hostname));
+}
+
+static void
+_set_hostname_read (NMHostnameManager *self)
+{
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+	char *hostname;
+
+	if (priv->hostnamed_proxy) {
+		/* read-hostname returns the current hostname with hostnamed. */
+		return;
+	}
+
+	hostname = nm_hostname_manager_read_hostname (self);
+
+	if (nm_streq0 (hostname, priv->current_hostname)) {
+		g_free (hostname);
+		return;
+	}
+
+	_set_hostname_take (self, hostname);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	char *hostname;
+	NMHostnameManagerSetHostnameCb cb;
+	gpointer user_data;
+} SetHostnameInfo;
+
+static void
+set_transient_hostname_done (GObject *object,
+                             GAsyncResult *res,
+                             gpointer user_data)
+{
+	GDBusProxy *proxy = G_DBUS_PROXY (object);
+	gs_free SetHostnameInfo *info = user_data;
+	gs_unref_variant GVariant *result = NULL;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_proxy_call_finish (proxy, res, &error);
+
+	if (error) {
+		_LOGW ("couldn't set the system hostname to '%s' using hostnamed: %s",
+		       info->hostname, error->message);
+	}
+
+	info->cb (info->hostname, !error, info->user_data);
+	g_free (info->hostname);
+}
+
+void
+nm_hostname_manager_set_transient_hostname (NMHostnameManager *self,
+                                            const char *hostname,
+                                            NMHostnameManagerSetHostnameCb cb,
+                                            gpointer user_data)
+{
+	NMHostnameManagerPrivate *priv;
+	SetHostnameInfo *info;
+
+	g_return_if_fail (NM_IS_HOSTNAME_MANAGER (self));
+
+	priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->hostnamed_proxy) {
+		cb (hostname, FALSE, user_data);
+		return;
+	}
+
+	info = g_new0 (SetHostnameInfo, 1);
+	info->hostname = g_strdup (hostname);
+	info->cb = cb;
+	info->user_data = user_data;
+
+	g_dbus_proxy_call (priv->hostnamed_proxy,
+	                   "SetHostname",
+	                   g_variant_new ("(sb)", hostname, FALSE),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   NULL,
+	                   set_transient_hostname_done,
+	                   info);
+}
+
+gboolean
+nm_hostname_manager_get_transient_hostname (NMHostnameManager *self, char **hostname)
+{
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+	GVariant *v_hostname;
+
+	if (!priv->hostnamed_proxy)
+		return FALSE;
+
+	v_hostname = g_dbus_proxy_get_cached_property (priv->hostnamed_proxy,
+	                                               "Hostname");
+	if (!v_hostname) {
+		_LOGT ("transient hostname retrieval failed");
+		return FALSE;
+	}
+
+	*hostname = g_variant_dup_string (v_hostname, NULL);
+	g_variant_unref (v_hostname);
+
+	return TRUE;
+}
+
+gboolean
+nm_hostname_manager_write_hostname (NMHostnameManager *self, const char *hostname)
+{
+	NMHostnameManagerPrivate *priv;
+	char *hostname_eol;
+	gboolean ret;
+	gs_free_error GError *error = NULL;
+	const char *file = HOSTNAME_FILE;
+	gs_free char *link_path = NULL;
+	gs_unref_variant GVariant *var = NULL;
+	struct stat file_stat;
+#if HAVE_SELINUX
+	security_context_t se_ctx_prev = NULL, se_ctx = NULL;
+	mode_t st_mode = 0;
+#endif
+
+	g_return_val_if_fail (NM_IS_HOSTNAME_MANAGER (self), FALSE);
+
+	priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+
+	if (priv->hostnamed_proxy) {
+		var = g_dbus_proxy_call_sync (priv->hostnamed_proxy,
+		                              "SetStaticHostname",
+		                              g_variant_new ("(sb)", hostname, FALSE),
+		                              G_DBUS_CALL_FLAGS_NONE,
+		                              -1,
+		                              NULL,
+		                              &error);
+		if (error)
+			_LOGW ("could not set hostname: %s", error->message);
+
+		return !error;
+	}
+
+	/* If the hostname file is a symbolic link, follow it to find where the
+	 * real file is located, otherwise g_file_set_contents will attempt to
+	 * replace the link with a plain file.
+	 */
+	if (   lstat (file, &file_stat) == 0
+	    && S_ISLNK (file_stat.st_mode)
+	    && (link_path = nm_utils_read_link_absolute (file, NULL)))
+		file = link_path;
+
+#if HAVE_SELINUX
+	/* Get default context for hostname file and set it for fscreate */
+	if (stat (file, &file_stat) == 0)
+		st_mode = file_stat.st_mode;
+	matchpathcon (file, st_mode, &se_ctx);
+	matchpathcon_fini ();
+	getfscreatecon (&se_ctx_prev);
+	setfscreatecon (se_ctx);
+#endif
+
+#if defined (HOSTNAME_PERSIST_GENTOO)
+	hostname_eol = g_strdup_printf ("#Generated by NetworkManager\n"
+	                                "hostname=\"%s\"\n", hostname);
+#else
+	hostname_eol = g_strdup_printf ("%s\n", hostname);
+#endif
+
+	ret = g_file_set_contents (file, hostname_eol, -1, &error);
+
+#if HAVE_SELINUX
+	/* Restore previous context and cleanup */
+	setfscreatecon (se_ctx_prev);
+	freecon (se_ctx);
+	freecon (se_ctx_prev);
+#endif
+
+	g_free (hostname_eol);
+
+	if (!ret) {
+		_LOGW ("could not save hostname to %s: %s", file, error->message);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_hostname_manager_validate_hostname (const char *hostname)
+{
+	const char *p;
+	gboolean dot = TRUE;
+
+	if (!hostname || !hostname[0])
+		return FALSE;
+
+	for (p = hostname; *p; p++) {
+		if (*p == '.') {
+			if (dot)
+				return FALSE;
+			dot = TRUE;
+		} else {
+			if (!g_ascii_isalnum (*p) && (*p != '-') && (*p != '_'))
+				return FALSE;
+			dot = FALSE;
+		}
+	}
+
+	if (dot)
+		return FALSE;
+
+	return (p - hostname <= HOST_NAME_MAX);
+}
+
+static void
+hostname_file_changed_cb (GFileMonitor *monitor,
+                          GFile *file,
+                          GFile *other_file,
+                          GFileMonitorEvent event_type,
+                          gpointer user_data)
+{
+	_set_hostname_read (user_data);
+}
+
+/*****************************************************************************/
+
+static void
+hostnamed_properties_changed (GDBusProxy *proxy,
+                              GVariant *changed_properties,
+                              char **invalidated_properties,
+                              gpointer user_data)
+{
+	NMHostnameManager *self = user_data;
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+	GVariant *v_hostname;
+
+	v_hostname = g_dbus_proxy_get_cached_property (priv->hostnamed_proxy,
+	                                               "StaticHostname");
+	if (v_hostname) {
+		_set_hostname (self, g_variant_get_string (v_hostname, NULL));
+		g_variant_unref (v_hostname);
+	}
+}
+
+static void
+setup_hostname_file_monitors (NMHostnameManager *self)
+{
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+	GFileMonitor *monitor;
+	const char *path = HOSTNAME_FILE;
+	char *link_path = NULL;
+	struct stat file_stat;
+	GFile *file;
+
+	/* resolve the path to the hostname file if it is a symbolic link */
+	if (   lstat(path, &file_stat) == 0
+	    && S_ISLNK (file_stat.st_mode)
+	    && (link_path = nm_utils_read_link_absolute (path, NULL))) {
+		path = link_path;
+		if (   lstat(link_path, &file_stat) == 0
+		    && S_ISLNK (file_stat.st_mode)) {
+			_LOGW ("only one level of symbolic link indirection is allowed when monitoring "
+			       HOSTNAME_FILE);
+		}
+	}
+
+	/* monitor changes to hostname file */
+	file = g_file_new_for_path (path);
+	monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
+	g_object_unref (file);
+	g_free(link_path);
+	if (monitor) {
+		priv->monitor_id = g_signal_connect (monitor, "changed",
+		                                              G_CALLBACK (hostname_file_changed_cb),
+		                                              self);
+		priv->monitor = monitor;
+	}
+
+#if defined (HOSTNAME_PERSIST_SUSE)
+	/* monitor changes to dhcp file to know whether the hostname is valid */
+	file = g_file_new_for_path (CONF_DHCP);
+	monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
+	g_object_unref (file);
+	if (monitor) {
+		priv->dhcp_monitor_id = g_signal_connect (monitor, "changed",
+		                                                   G_CALLBACK (hostname_file_changed_cb),
+		                                                   self);
+		priv->dhcp_monitor = monitor;
+	}
+#endif
+
+	_set_hostname_read (self);
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMHostnameManager *self = NM_HOSTNAME_MANAGER (object);
+
+	switch (prop_id) {
+	case PROP_HOSTNAME:
+		g_value_set_string (value, nm_hostname_manager_get_hostname (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_hostname_manager_init (NMHostnameManager *self)
+{
+}
+
+static void
+constructed (GObject *object)
+{
+	NMHostnameManager *self = NM_HOSTNAME_MANAGER (object);
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+	GDBusProxy *proxy;
+	GVariant *variant;
+	gs_free_error GError *error = NULL;
+
+	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, 0, NULL,
+	                                       HOSTNAMED_SERVICE_NAME, HOSTNAMED_SERVICE_PATH,
+	                                       HOSTNAMED_SERVICE_INTERFACE, NULL, &error);
+	if (proxy) {
+		variant = g_dbus_proxy_get_cached_property (proxy, "StaticHostname");
+		if (variant) {
+			_LOGI ("hostname: using hostnamed");
+			priv->hostnamed_proxy = proxy;
+			g_signal_connect (proxy, "g-properties-changed",
+			                  G_CALLBACK (hostnamed_properties_changed), self);
+			hostnamed_properties_changed (proxy, NULL, NULL, self);
+			g_variant_unref (variant);
+		} else {
+			_LOGI ("hostname: couldn't get property from hostnamed");
+			g_object_unref (proxy);
+		}
+	} else {
+		_LOGI ("hostname: hostnamed not used as proxy creation failed with: %s",
+		       error->message);
+		g_clear_error (&error);
+	}
+
+	if (!priv->hostnamed_proxy)
+		setup_hostname_file_monitors (self);
+
+	G_OBJECT_CLASS (nm_hostname_manager_parent_class)->constructed (object);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMHostnameManager *self = NM_HOSTNAME_MANAGER (object);
+	NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE (self);
+
+	if (priv->hostnamed_proxy) {
+		g_signal_handlers_disconnect_by_func (priv->hostnamed_proxy,
+		                                      G_CALLBACK (hostnamed_properties_changed),
+		                                      self);
+		g_clear_object (&priv->hostnamed_proxy);
+	}
+
+	if (priv->monitor) {
+		if (priv->monitor_id)
+			g_signal_handler_disconnect (priv->monitor, priv->monitor_id);
+
+		g_file_monitor_cancel (priv->monitor);
+		g_clear_object (&priv->monitor);
+	}
+
+	if (priv->dhcp_monitor) {
+		if (priv->dhcp_monitor_id)
+			g_signal_handler_disconnect (priv->dhcp_monitor,
+			                             priv->dhcp_monitor_id);
+
+		g_file_monitor_cancel (priv->dhcp_monitor);
+		g_clear_object (&priv->dhcp_monitor);
+	}
+
+	nm_clear_g_free (&priv->current_hostname);
+
+	G_OBJECT_CLASS (nm_hostname_manager_parent_class)->dispose (object);
+}
+
+static void
+nm_hostname_manager_class_init (NMHostnameManagerClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->constructed = constructed;
+	object_class->get_property = get_property;
+	object_class->dispose = dispose;
+
+	obj_properties[PROP_HOSTNAME] =
+	    g_param_spec_string (NM_HOSTNAME_MANAGER_HOSTNAME, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/nm-hostname-manager.h b/src/nm-hostname-manager.h
new file mode 100644
index 00000000..a837e9b9
--- /dev/null
+++ b/src/nm-hostname-manager.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager
+ *
+ * Søren Sandmann <sandmann@daimi.au.dk>
+ * Dan Williams <dcbw@redhat.com>
+ * Tambet Ingo <tambet@gmail.com>
+ *
+ * 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.
+ *
+ * (C) Copyright 2007 - 2011, 2017 Red Hat, Inc.
+ * (C) Copyright 2008 Novell, Inc.
+ */
+
+#ifndef __NM_HOSTNAME_MANAGER_H__
+#define __NM_HOSTNAME_MANAGER_H__
+
+#define NM_TYPE_HOSTNAME_MANAGER            (nm_hostname_manager_get_type ())
+#define NM_HOSTNAME_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_HOSTNAME_MANAGER, NMHostnameManager))
+#define NM_HOSTNAME_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_HOSTNAME_MANAGER, NMHostnameManagerClass))
+#define NM_IS_HOSTNAME_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_HOSTNAME_MANAGER))
+#define NM_IS_HOSTNAME_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_HOSTNAME_MANAGER))
+#define NM_HOSTNAME_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_HOSTNAME_MANAGER, NMHostnameManagerClass))
+
+#define NM_HOSTNAME_MANAGER_HOSTNAME "hostname"
+
+typedef struct _NMHostnameManager      NMHostnameManager;
+typedef struct _NMHostnameManagerClass NMHostnameManagerClass;
+
+typedef void (*NMHostnameManagerSetHostnameCb) (const char *name, gboolean result, gpointer user_data);
+
+GType nm_hostname_manager_get_type (void);
+
+NMHostnameManager *nm_hostname_manager_get (void);
+
+const char *nm_hostname_manager_get_hostname (NMHostnameManager *self);
+
+char *nm_hostname_manager_read_hostname (NMHostnameManager *self);
+
+gboolean nm_hostname_manager_write_hostname (NMHostnameManager *self, const char *hostname);
+
+void nm_hostname_manager_set_transient_hostname (NMHostnameManager *self,
+                                                 const char *hostname,
+                                                 NMHostnameManagerSetHostnameCb cb,
+                                                 gpointer user_data);
+
+gboolean nm_hostname_manager_get_transient_hostname (NMHostnameManager *self,
+                                                     char **hostname);
+
+gboolean nm_hostname_manager_validate_hostname (const char *hostname);
+
+#endif  /* __NM_HOSTNAME_MANAGER_H__ */
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index f8df2b9a..1493ef19 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -31,6 +31,7 @@
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <signal.h>
+#include <linux/rtnetlink.h>
 
 #include "main-utils.h"
 #include "NetworkManagerUtils.h"
@@ -42,7 +43,6 @@
 #include "nm-utils.h"
 #include "nm-setting-ip6-config.h"
 #include "systemd/nm-sd.h"
-#include "nm-route-manager.h"
 
 #if !defined(NM_DIST_VERSION)
 # define NM_DIST_VERSION VERSION
@@ -98,12 +98,6 @@ static struct {
 
 /*****************************************************************************/
 
-NMRouteManager *route_manager_get (void);
-
-NM_DEFINE_SINGLETON_GETTER (NMRouteManager, route_manager_get, NM_TYPE_ROUTE_MANAGER);
-
-/*****************************************************************************/
-
 static void
 dhcp4_state_changed (NMDhcpClient *client,
                      NMDhcpState state,
@@ -114,6 +108,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 {
 	static NMIP4Config *last_config = NULL;
 	NMIP4Config *existing;
+	gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
 
 	g_return_if_fail (!ip4_config || NM_IS_IP4_CONFIG (ip4_config));
 
@@ -122,17 +117,31 @@ dhcp4_state_changed (NMDhcpClient *client,
 	switch (state) {
 	case NM_DHCP_STATE_BOUND:
 		g_assert (ip4_config);
-		existing = nm_ip4_config_capture (NM_PLATFORM_GET, gl.ifindex, FALSE);
-		if (last_config)
-			nm_ip4_config_subtract (existing, last_config);
+		g_assert (nm_ip4_config_get_ifindex (ip4_config) == gl.ifindex);
 
-		nm_ip4_config_merge (existing, ip4_config, NM_IP_CONFIG_MERGE_DEFAULT);
-		if (!nm_ip4_config_commit (existing, NM_PLATFORM_GET, route_manager_get (), gl.ifindex, TRUE, global_opt.priority_v4))
+		existing = nm_ip4_config_capture (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+		                                  NM_PLATFORM_GET, gl.ifindex, FALSE);
+		if (last_config)
+			nm_ip4_config_subtract (existing, last_config, 0);
+
+		nm_ip4_config_merge (existing, ip4_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+		nm_ip4_config_add_dependent_routes (existing,
+		                                    RT_TABLE_MAIN,
+		                                    global_opt.priority_v4,
+		                                    &ip4_dev_route_blacklist);
+		if (!nm_ip4_config_commit (existing,
+		                           NM_PLATFORM_GET,
+		                           NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN))
 			_LOGW (LOGD_DHCP4, "failed to apply DHCPv4 config");
 
+		nm_platform_ip4_dev_route_blacklist_set (NM_PLATFORM_GET,
+		                                         gl.ifindex,
+		                                         ip4_dev_route_blacklist);
+
 		if (last_config)
 			g_object_unref (last_config);
-		last_config = nm_ip4_config_new (nm_dhcp_client_get_ifindex (client));
+		last_config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+		                                 nm_dhcp_client_get_ifindex (client));
 		nm_ip4_config_replace (last_config, ip4_config, NULL);
 		break;
 	case NM_DHCP_STATE_TIMEOUT:
@@ -155,86 +164,53 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 	NMNDiscConfigMap changed = changed_int;
 	static NMIP6Config *ndisc_config = NULL;
 	NMIP6Config *existing;
-	int system_support;
-	guint32 ifa_flags = 0x00;
-	int i;
-
-	/*
-	 * Check, whether kernel is recent enough, to help user space handling RA.
-	 * If it's not supported, we have no ipv6-privacy and must add autoconf
-	 * addresses as /128.
-	 * The reason for the /128 is to prevent the kernel
-	 * from adding a prefix route for this address.
-	 **/
-	system_support = nm_platform_check_support_kernel_extended_ifa_flags (NM_PLATFORM_GET);
-
-	if (system_support)
-		ifa_flags = IFA_F_NOPREFIXROUTE;
-	if (global_opt.tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR
-	    || global_opt.tempaddr == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
-	{
-		/* without system_support, this flag will be ignored. Still set it, doesn't seem to do any harm. */
-		ifa_flags |= IFA_F_MANAGETEMPADDR;
-	}
 
-	existing = nm_ip6_config_capture (NM_PLATFORM_GET, gl.ifindex, FALSE, global_opt.tempaddr);
+	existing = nm_ip6_config_capture (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+	                                  NM_PLATFORM_GET, gl.ifindex, FALSE, global_opt.tempaddr);
 	if (ndisc_config)
-		nm_ip6_config_subtract (existing, ndisc_config);
-	else
-		ndisc_config = nm_ip6_config_new (gl.ifindex);
-
-	if (changed & NM_NDISC_CONFIG_GATEWAYS) {
-		/* Use the first gateway as ordered in neighbor discovery cache. */
-		if (rdata->gateways_n)
-			nm_ip6_config_set_gateway (ndisc_config, &rdata->gateways[0].address);
-		else
-			nm_ip6_config_set_gateway (ndisc_config, NULL);
+		nm_ip6_config_subtract (existing, ndisc_config, 0);
+	else {
+		ndisc_config = nm_ip6_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET),
+		                                  gl.ifindex);
 	}
 
 	if (changed & NM_NDISC_CONFIG_ADDRESSES) {
-		/* Rebuild address list from neighbor discovery cache. */
-		nm_ip6_config_reset_addresses (ndisc_config);
-
-		/* ndisc->addresses contains at most max_addresses entries.
-		 * This is different from what the kernel does, which
-		 * also counts static and temporary addresses when checking
-		 * max_addresses.
-		 **/
-		for (i = 0; i < rdata->addresses_n; i++) {
-			const NMNDiscAddress *discovered_address = &rdata->addresses[i];
-			NMPlatformIP6Address address;
-
-			memset (&address, 0, sizeof (address));
-			address.address = discovered_address->address;
-			address.plen = system_support ? 64 : 128;
-			address.timestamp = discovered_address->timestamp;
-			address.lifetime = discovered_address->lifetime;
-			address.preferred = discovered_address->preferred;
-			if (address.preferred > address.lifetime)
-				address.preferred = address.lifetime;
-			address.addr_source = NM_IP_CONFIG_SOURCE_NDISC;
-			address.n_ifa_flags = ifa_flags;
-
-			nm_ip6_config_add_address (ndisc_config, &address);
-		}
+		guint8 plen;
+		guint32 ifa_flags;
+
+		/* Check, whether kernel is recent enough to help user space handling RA.
+		 * If it's not supported, we have no ipv6-privacy and must add autoconf
+		 * addresses as /128. The reason for the /128 is to prevent the kernel
+		 * from adding a prefix route for this address. */
+		ifa_flags = 0;
+		if (nm_platform_check_kernel_support (NM_PLATFORM_GET,
+		                                      NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+			ifa_flags |= IFA_F_NOPREFIXROUTE;
+			if (NM_IN_SET (global_opt.tempaddr, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
+			                                    NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
+				ifa_flags |= IFA_F_MANAGETEMPADDR;
+			plen = 64;
+		} else
+			plen = 128;
+
+		nm_ip6_config_reset_addresses_ndisc (ndisc_config,
+		                                     rdata->addresses,
+		                                     rdata->addresses_n,
+		                                     plen,
+		                                     ifa_flags);
 	}
 
-	if (changed & NM_NDISC_CONFIG_ROUTES) {
-		/* Rebuild route list from neighbor discovery cache. */
-		nm_ip6_config_reset_routes (ndisc_config);
-
-		for (i = 0; i < rdata->routes_n; i++) {
-			const NMNDiscRoute *discovered_route = &rdata->routes[i];
-			const NMPlatformIP6Route route = {
-				.network    = discovered_route->network,
-				.plen       = discovered_route->plen,
-				.gateway    = discovered_route->gateway,
-				.rt_source  = NM_IP_CONFIG_SOURCE_NDISC,
-				.metric     = global_opt.priority_v6,
-			};
-
-			nm_ip6_config_add_route (ndisc_config, &route);
-		}
+	if (NM_FLAGS_ANY (changed,  NM_NDISC_CONFIG_ROUTES
+	                          | NM_NDISC_CONFIG_GATEWAYS)) {
+		nm_ip6_config_reset_routes_ndisc (ndisc_config,
+		                                  rdata->gateways,
+		                                  rdata->gateways_n,
+		                                  rdata->routes,
+		                                  rdata->routes_n,
+		                                  RT_TABLE_MAIN,
+		                                  global_opt.priority_v6,
+		                                  nm_platform_check_kernel_support (NM_PLATFORM_GET,
+		                                                                    NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF));
 	}
 
 	if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
@@ -246,13 +222,20 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 
 	if (changed & NM_NDISC_CONFIG_MTU) {
 		char val[16];
+		char sysctl_path_buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
 		g_snprintf (val, sizeof (val), "%d", rdata->mtu);
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (global_opt.ifname, "mtu")), val);
+		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "mtu")), val);
 	}
 
-	nm_ip6_config_merge (existing, ndisc_config, NM_IP_CONFIG_MERGE_DEFAULT);
-	if (!nm_ip6_config_commit (existing, NM_PLATFORM_GET, route_manager_get (), gl.ifindex, TRUE))
+	nm_ip6_config_merge (existing, ndisc_config, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+	nm_ip6_config_add_dependent_routes (existing,
+	                                    RT_TABLE_MAIN,
+	                                    global_opt.priority_v6);
+	if (!nm_ip6_config_commit (existing,
+	                           NM_PLATFORM_GET,
+	                           NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
+	                           NULL))
 		_LOGW (LOGD_IP6, "failed to apply IPv6 config");
 }
 
@@ -362,6 +345,7 @@ main (int argc, char *argv[])
 	gconstpointer tmp;
 	gs_free NMUtilsIPv6IfaceId *iid = NULL;
 	guint sd_id;
+	char sysctl_path_buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
 	nm_g_type_init ();
 
@@ -466,19 +450,21 @@ main (int argc, char *argv[])
 	}
 
 	if (global_opt.dhcp4_address) {
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip4_property_path (global_opt.ifname, "promote_secondaries")), "1");
+		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET, sysctl_path_buf, global_opt.ifname, "promote_secondaries")), "1");
 
 		dhcp4_client = nm_dhcp_manager_start_ip4 (nm_dhcp_manager_get (),
+		                                          nm_platform_get_multi_idx (NM_PLATFORM_GET),
 		                                          global_opt.ifname,
 		                                          gl.ifindex,
 		                                          hwaddr,
 		                                          global_opt.uuid,
+		                                          RT_TABLE_MAIN,
 		                                          global_opt.priority_v4,
 		                                          !!global_opt.dhcp4_hostname,
 		                                          global_opt.dhcp4_hostname,
 		                                          global_opt.dhcp4_fqdn,
 		                                          global_opt.dhcp4_clientid,
-		                                          45,
+		                                          NM_DHCP_TIMEOUT_DEFAULT,
 		                                          NULL,
 		                                          global_opt.dhcp4_address);
 		g_assert (dhcp4_client);
@@ -513,10 +499,10 @@ main (int argc, char *argv[])
 		if (iid)
 			nm_ndisc_set_iid (ndisc, *iid);
 
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (global_opt.ifname, "accept_ra")), "1");
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (global_opt.ifname, "accept_ra_defrtr")), "0");
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (global_opt.ifname, "accept_ra_pinfo")), "0");
-		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_ip6_property_path (global_opt.ifname, "accept_ra_rtr_pref")), "0");
+		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra")), "1");
+		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra_defrtr")), "0");
+		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra_pinfo")), "0");
+		nm_platform_sysctl_set (NM_PLATFORM_GET, NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (AF_INET6, sysctl_path_buf, global_opt.ifname, "accept_ra_rtr_pref")), "0");
 
 		g_signal_connect (NM_PLATFORM_GET,
 		                  NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
@@ -551,7 +537,7 @@ main (int argc, char *argv[])
 
 /*****************************************************************************/
 
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[3] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 	&_nm_dhcp_client_factory_internal,
 };
 
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index ae6af9c3..e2f99e24 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -25,12 +25,16 @@
 
 #include <string.h>
 #include <arpa/inet.h>
+#include <resolv.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
 
 #include "nm-utils.h"
+#include "platform/nmp-object.h"
 #include "platform/nm-platform.h"
 #include "platform/nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
-#include "nm-route-manager.h"
 #include "nm-core-internal.h"
 
 #include "introspection/org.freedesktop.NetworkManager.IP4Config.h"
@@ -43,7 +47,232 @@ G_STATIC_ASSERT (G_MAXUINT >= 0xFFFFFFFF);
 
 /*****************************************************************************/
 
+static gboolean
+_route_valid (const NMPlatformIP4Route *r)
+{
+	return    r
+	       && r->plen <= 32
+	       && r->network == nm_utils_ip4_address_clear_host_address (r->network, r->plen);
+}
+
+/*****************************************************************************/
+
+static void
+_idx_obj_id_hash_update (const NMDedupMultiIdxType *idx_type,
+                         const NMDedupMultiObj *obj,
+                         NMHashState *h)
+{
+	nmp_object_id_hash_update ((NMPObject *) obj, h);
+}
+
+static gboolean
+_idx_obj_id_equal (const NMDedupMultiIdxType *idx_type,
+                   const NMDedupMultiObj *obj_a,
+                   const NMDedupMultiObj *obj_b)
+{
+	return nmp_object_id_equal ((NMPObject *) obj_a, (NMPObject *) obj_b);
+}
+
+void
+nm_ip_config_dedup_multi_idx_type_init (NMIPConfigDedupMultiIdxType *idx_type,
+                                        NMPObjectType obj_type)
+{
+	static const NMDedupMultiIdxTypeClass idx_type_class = {
+		.idx_obj_id_hash_update = _idx_obj_id_hash_update,
+		.idx_obj_id_equal = _idx_obj_id_equal,
+	};
+
+	nm_dedup_multi_idx_type_init ((NMDedupMultiIdxType *) idx_type,
+	                              &idx_type_class);
+	idx_type->obj_type = obj_type;
+}
+
+/*****************************************************************************/
+
+gboolean
+_nm_ip_config_add_obj (NMDedupMultiIndex *multi_idx,
+                       NMIPConfigDedupMultiIdxType *idx_type,
+                       int ifindex,
+                       const NMPObject *obj_new,
+                       const NMPlatformObject *pl_new,
+                       gboolean merge,
+                       gboolean append_force,
+                       const NMPObject **out_obj_old /* returns a reference! */,
+                       const NMPObject **out_obj_new /* does not return a reference */)
+{
+	NMPObject obj_new_stackinit;
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_new;
+
+	nm_assert (multi_idx);
+	nm_assert (idx_type);
+	nm_assert (NM_IN_SET (idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                                          NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                          NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                          NMP_OBJECT_TYPE_IP6_ROUTE));
+	nm_assert (ifindex > 0);
+
+	/* we go through extra lengths to accept a full obj_new object. That one,
+	 * can be reused by increasing the ref-count. */
+	if (!obj_new) {
+		nm_assert (pl_new);
+		obj_new = nmp_object_stackinit (&obj_new_stackinit, idx_type->obj_type, pl_new);
+		obj_new_stackinit.object.ifindex = ifindex;
+	} else {
+		nm_assert (!pl_new);
+		nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
+		if (obj_new->object.ifindex != ifindex) {
+			obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+			obj_new_stackinit.object.ifindex = ifindex;
+		}
+	}
+	nm_assert (NMP_OBJECT_GET_TYPE (obj_new) == idx_type->obj_type);
+	nm_assert (nmp_object_is_alive (obj_new));
+
+	entry_old = nm_dedup_multi_index_lookup_obj (multi_idx, &idx_type->parent, obj_new);
+
+	if (entry_old) {
+		gboolean modified = FALSE;
+		const NMPObject *obj_old = entry_old->obj;
+
+		if (nmp_object_equal (obj_new, obj_old)) {
+			nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
+			goto append_force_and_out;
+		}
+
+		/* if @merge, we merge the new object with the existing one.
+		 * Otherwise, we replace it entirely. */
+		if (merge) {
+			switch (idx_type->obj_type) {
+			case NMP_OBJECT_TYPE_IP4_ADDRESS:
+			case NMP_OBJECT_TYPE_IP6_ADDRESS:
+				/* for addresses that we read from the kernel, we keep the timestamps as defined
+				 * by the previous source (item_old). The reason is, that the other source configured the lifetimes
+				 * with "what should be" and the kernel values are "what turned out after configuring it".
+				 *
+				 * For other sources, the longer lifetime wins. */
+				if (   (   obj_new->ip_address.addr_source == NM_IP_CONFIG_SOURCE_KERNEL
+				        && obj_old->ip_address.addr_source != NM_IP_CONFIG_SOURCE_KERNEL)
+				    || nm_platform_ip_address_cmp_expiry (NMP_OBJECT_CAST_IP_ADDRESS (obj_old), NMP_OBJECT_CAST_IP_ADDRESS(obj_new)) > 0) {
+					obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+					obj_new_stackinit.ip_address.timestamp = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->timestamp;
+					obj_new_stackinit.ip_address.lifetime  = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->lifetime;
+					obj_new_stackinit.ip_address.preferred = NMP_OBJECT_CAST_IP_ADDRESS (obj_old)->preferred;
+					modified = TRUE;
+				}
+
+				/* keep the maximum addr_source. */
+				if (obj_new->ip_address.addr_source < obj_old->ip_address.addr_source) {
+					obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+					obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source;
+					modified = TRUE;
+				}
+				break;
+			case NMP_OBJECT_TYPE_IP4_ROUTE:
+			case NMP_OBJECT_TYPE_IP6_ROUTE:
+				/* keep the maximum rt_source. */
+				if (obj_new->ip_route.rt_source < obj_old->ip_route.rt_source) {
+					obj_new = nmp_object_stackinit_obj (&obj_new_stackinit, obj_new);
+					obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source;
+					modified = TRUE;
+				}
+				break;
+			default:
+				nm_assert_not_reached ();
+				break;
+			}
+
+			if (   modified
+			    && nmp_object_equal (obj_new, obj_old)) {
+				nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
+				goto append_force_and_out;
+			}
+		}
+	}
+
+	if (!nm_dedup_multi_index_add_full (multi_idx,
+	                                    &idx_type->parent,
+	                                    obj_new,
+	                                    NM_DEDUP_MULTI_IDX_MODE_APPEND,
+	                                    NULL,
+	                                    entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
+	                                    NULL,
+	                                    &entry_new,
+	                                    out_obj_old)) {
+		nm_assert_not_reached ();
+		NM_SET_OUT (out_obj_new, NULL);
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_obj_new, entry_new->obj);
+	return TRUE;
+
+append_force_and_out:
+	NM_SET_OUT (out_obj_old, nmp_object_ref (entry_old->obj));
+	NM_SET_OUT (out_obj_new, entry_old->obj);
+	if (append_force) {
+		if (nm_dedup_multi_entry_reorder (entry_old, NULL, TRUE))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+/**
+ * _nm_ip_config_lookup_ip_route:
+ * @multi_idx:
+ * @idx_type:
+ * @needle:
+ * @cmp_type: after lookup, filter the result by comparing with @cmp_type. Only
+ *   return the result, if it compares equal to @needle according to this @cmp_type.
+ *   Note that the index uses %NM_PLATFORM_IP_ROUTE_CMP_TYPE_DST type, so passing
+ *   that compare-type means not to filter any further.
+ *
+ * Returns: the found entry or %NULL.
+ */
+const NMDedupMultiEntry *
+_nm_ip_config_lookup_ip_route (const NMDedupMultiIndex *multi_idx,
+                               const NMIPConfigDedupMultiIdxType *idx_type,
+                               const NMPObject *needle,
+                               NMPlatformIPRouteCmpType cmp_type)
+{
+	const NMDedupMultiEntry *entry;
+
+	nm_assert (multi_idx);
+	nm_assert (idx_type);
+	nm_assert (NM_IN_SET (idx_type->obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
+	nm_assert (NMP_OBJECT_GET_TYPE (needle) == idx_type->obj_type);
+
+	entry = nm_dedup_multi_index_lookup_obj (multi_idx,
+	                                         &idx_type->parent,
+	                                         needle);
+	if (!entry)
+		return NULL;
+
+	if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
+		nm_assert (   (   NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE
+		               && nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (entry->obj), NMP_OBJECT_CAST_IP4_ROUTE (needle), cmp_type) == 0)
+		           || (   NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP6_ROUTE
+		               && nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (entry->obj), NMP_OBJECT_CAST_IP6_ROUTE (needle), cmp_type) == 0));
+	} else {
+		if (NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE) {
+			if (nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (entry->obj),
+			                               NMP_OBJECT_CAST_IP4_ROUTE (needle),
+			                               cmp_type) != 0)
+				return NULL;
+		} else {
+			if (nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (entry->obj),
+			                               NMP_OBJECT_CAST_IP6_ROUTE (needle),
+			                               cmp_type) != 0)
+				return NULL;
+		}
+	}
+	return entry;
+}
+
+/*****************************************************************************/
+
 NM_GOBJECT_PROPERTIES_DEFINE (NMIP4Config,
+	PROP_MULTI_IDX,
 	PROP_IFINDEX,
 	PROP_ADDRESS_DATA,
 	PROP_ADDRESSES,
@@ -59,18 +288,11 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMIP4Config,
 );
 
 typedef struct {
-	bool never_default:1;
 	bool metered:1;
-	bool has_gateway:1;
-	guint32 gateway;
-	guint32 mss;
 	guint32 mtu;
 	int ifindex;
 	NMIPConfigSource mtu_source;
 	gint dns_priority;
-	gint64 route_metric;
-	GArray *addresses;
-	GArray *routes;
 	GArray *nameservers;
 	GPtrArray *domains;
 	GPtrArray *searches;
@@ -80,6 +302,18 @@ typedef struct {
 	GArray *wins;
 	GVariant *address_data_variant;
 	GVariant *addresses_variant;
+	GVariant *route_data_variant;
+	GVariant *routes_variant;
+	NMDedupMultiIndex *multi_idx;
+	const NMPObject *best_default_route;
+	union {
+		NMIPConfigDedupMultiIdxType idx_ip4_addresses_;
+		NMDedupMultiIdxType idx_ip4_addresses;
+	};
+	union {
+		NMIPConfigDedupMultiIdxType idx_ip4_routes_;
+		NMDedupMultiIdxType idx_ip4_routes;
+	};
 } NMIP4ConfigPrivate;
 
 struct _NMIP4Config {
@@ -97,10 +331,24 @@ G_DEFINE_TYPE (NMIP4Config, nm_ip4_config, NM_TYPE_EXPORTED_OBJECT)
 
 /*****************************************************************************/
 
+static void _add_address (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new);
+static void _add_route (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Route *new, const NMPObject **out_obj_new);
+static const NMDedupMultiEntry *_lookup_route (const NMIP4Config *self,
+                                               const NMPObject *needle,
+                                               NMPlatformIPRouteCmpType cmp_type);
+
+/*****************************************************************************/
+
 int
-nm_ip4_config_get_ifindex (const NMIP4Config *config)
+nm_ip4_config_get_ifindex (const NMIP4Config *self)
+{
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex;
+}
+
+NMDedupMultiIndex *
+nm_ip4_config_get_multi_idx (const NMIP4Config *self)
 {
-	return NM_IP4_CONFIG_GET_PRIVATE (config)->ifindex;
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->multi_idx;
 }
 
 /*****************************************************************************/
@@ -114,83 +362,167 @@ _ipv4_is_zeronet (in_addr_t network)
 
 /*****************************************************************************/
 
-/**
- * nm_ip4_config_capture_resolv_conf():
- * @nameservers: array of guint32
- * @rc_contents: the contents of a resolv.conf or %NULL to read /etc/resolv.conf
- *
- * Reads all resolv.conf IPv4 nameservers and adds them to @nameservers.
- *
- * Returns: %TRUE if nameservers were added, %FALSE if @nameservers is unchanged
- */
-gboolean
-nm_ip4_config_capture_resolv_conf (GArray *nameservers,
-                                   GPtrArray *dns_options,
-                                   const char *rc_contents)
+const NMDedupMultiHeadEntry *
+nm_ip4_config_lookup_addresses (const NMIP4Config *self)
 {
-	GPtrArray *read_ns, *read_options;
-	guint i, j;
-	gboolean changed = FALSE;
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	g_return_val_if_fail (nameservers != NULL, FALSE);
+	return nm_dedup_multi_index_lookup_head (priv->multi_idx,
+	                                         &priv->idx_ip4_addresses,
+	                                         NULL);
+}
 
-	read_ns = nm_utils_read_resolv_conf_nameservers (rc_contents);
-	if (!read_ns)
-		return FALSE;
+void
+nm_ip_config_iter_ip4_address_init (NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
+{
+	g_return_if_fail (NM_IS_IP4_CONFIG (self));
+	nm_dedup_multi_iter_init (ipconf_iter, nm_ip4_config_lookup_addresses (self));
+}
 
-	for (i = 0; i < read_ns->len; i++) {
-		const char *s = g_ptr_array_index (read_ns, i);
-		guint32 ns = 0;
+/*****************************************************************************/
 
-		if (!inet_pton (AF_INET, s, (void *) &ns) || !ns)
-			continue;
+const NMDedupMultiHeadEntry *
+nm_ip4_config_lookup_routes (const NMIP4Config *self)
+{
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-		/* Ignore duplicates */
-		for (j = 0; j < nameservers->len; j++) {
-			if (g_array_index (nameservers, guint32, j) == ns)
-				break;
-		}
+	return nm_dedup_multi_index_lookup_head (priv->multi_idx,
+	                                         &priv->idx_ip4_routes,
+	                                         NULL);
+}
 
-		if (j == nameservers->len) {
-			g_array_append_val (nameservers, ns);
-			changed = TRUE;
+void
+nm_ip_config_iter_ip4_route_init (NMDedupMultiIter *ipconf_iter, const NMIP4Config *self)
+{
+	g_return_if_fail (NM_IS_IP4_CONFIG (self));
+	nm_dedup_multi_iter_init (ipconf_iter, nm_ip4_config_lookup_routes (self));
+}
+
+/*****************************************************************************/
+
+const NMPObject *
+_nm_ip_config_best_default_route_find_better (const NMPObject *obj_cur, const NMPObject *obj_cmp)
+{
+	int addr_family;
+	int c;
+	guint metric_cur, metric_cmp;
+
+	nm_assert (   !obj_cur
+	           || NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_cur), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
+	nm_assert (   !obj_cmp
+	           || (   !obj_cur
+	               && NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_cmp), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE))
+	           || NMP_OBJECT_GET_TYPE (obj_cur) == NMP_OBJECT_GET_TYPE (obj_cmp));
+	nm_assert (   !obj_cur
+	           || nm_ip_config_best_default_route_is (obj_cur));
+
+	/* assumes that @obj_cur is already the best default route (or NULL). It checks whether
+	 * @obj_cmp is also a default route and returns the best of both. */
+	if (   obj_cmp
+	    && nm_ip_config_best_default_route_is (obj_cmp)) {
+		if (!obj_cur)
+			return obj_cmp;
+
+		addr_family = NMP_OBJECT_GET_CLASS (obj_cmp)->addr_family;
+		metric_cur = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (obj_cur)->metric);
+		metric_cmp = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (obj_cmp)->metric);
+
+		if (metric_cmp < metric_cur)
+			return obj_cmp;
+
+		if (metric_cmp == metric_cur) {
+			/* Routes have the same metric. We still want to deterministically
+			 * prefer one or the other. It's important to consistently choose one
+			 * or the other, so that the order doesn't matter how routes are added
+			 * (and merged). */
+			c = nmp_object_cmp (obj_cur, obj_cmp);
+			if (c != 0)
+				return c < 0 ? obj_cur : obj_cmp;
+
+			/* as last resort, compare pointers. */
+			if (obj_cmp < obj_cur)
+				return obj_cmp;
 		}
 	}
-	g_ptr_array_unref (read_ns);
+	return obj_cur;
+}
 
-	if (dns_options) {
-		read_options = nm_utils_read_resolv_conf_dns_options (rc_contents);
-		if (!read_options)
-			return changed;
+gboolean
+_nm_ip_config_best_default_route_set (const NMPObject **best_default_route, const NMPObject *new_candidate)
+{
+	if (new_candidate == *best_default_route)
+		return FALSE;
+	nmp_object_ref (new_candidate);
+	nm_clear_nmp_object (best_default_route);
+	*best_default_route = new_candidate;
+	return TRUE;
+}
 
-		for (i = 0; i < read_options->len; i++) {
-			const char *s = g_ptr_array_index (read_options, i);
+gboolean
+_nm_ip_config_best_default_route_merge (const NMPObject **best_default_route, const NMPObject *new_candidate)
+{
+	new_candidate = _nm_ip_config_best_default_route_find_better (*best_default_route,
+	                                                              new_candidate);
+	return _nm_ip_config_best_default_route_set (best_default_route, new_candidate);
+}
 
-			if (_nm_utils_dns_option_validate (s, NULL, NULL, FALSE, _nm_utils_dns_option_descs) &&
-				_nm_utils_dns_option_find_idx (dns_options, s) < 0) {
-				g_ptr_array_add (dns_options, g_strdup (s));
-				changed = TRUE;
-			}
-		}
-		g_ptr_array_unref (read_options);
+const NMPObject *
+nm_ip4_config_best_default_route_get (const NMIP4Config *self)
+{
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), NULL);
+
+	return NM_IP4_CONFIG_GET_PRIVATE (self)->best_default_route;
+}
+
+const NMPObject *
+_nm_ip4_config_best_default_route_find (const NMIP4Config *self)
+{
+	NMDedupMultiIter ipconf_iter;
+	const NMPObject *new_best_default_route = NULL;
+
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, NULL) {
+		new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route,
+		                                                                       ipconf_iter.current->obj);
 	}
+	return new_best_default_route;
+}
+
+in_addr_t
+nmtst_ip4_config_get_gateway (NMIP4Config *config)
+{
+	const NMPObject *rt;
 
-	return changed;
+	g_assert (NM_IS_IP4_CONFIG (config));
+
+	rt = nm_ip4_config_best_default_route_get (config);
+	if (!rt)
+		return 0;
+	return NMP_OBJECT_CAST_IP4_ROUTE (rt)->gateway;
 }
 
-static gboolean
-addresses_are_duplicate (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
+/*****************************************************************************/
+
+static void
+_notify_addresses (NMIP4Config *self)
 {
-	return    a->address == b->address
-	       && a->plen == b->plen
-	       && ((a->peer_address ^ b->peer_address) & nm_utils_ip4_prefix_to_netmask (a->plen)) == 0;
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (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);
 }
 
-static gboolean
-routes_are_duplicate (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, gboolean consider_gateway_and_metric)
+static void
+_notify_routes (NMIP4Config *self)
 {
-	return a->network == b->network && a->plen == b->plen &&
-	       (!consider_gateway_and_metric || (a->gateway == b->gateway && a->metric == b->metric));
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (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);
 }
 
 /*****************************************************************************/
@@ -203,11 +535,12 @@ _addresses_sort_cmp_get_prio (in_addr_t addr)
 	return 1;
 }
 
-static gint
-_addresses_sort_cmp (gconstpointer a, gconstpointer b)
+static int
+_addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
 {
 	gint p1, p2;
-	const NMPlatformIP4Address *a1 = a, *a2 = b;
+	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;
 
 	/* Sort by address type. For example link local will
@@ -229,29 +562,19 @@ _addresses_sort_cmp (gconstpointer a, gconstpointer b)
 	 * subnet (and thus also the primary/secondary role) is
 	 * preserved.
 	 */
-	n1 = a1->address & nm_utils_ip4_prefix_to_netmask (a1->plen);
-	n2 = a2->address & nm_utils_ip4_prefix_to_netmask (a2->plen);
+	n1 = a1->address & _nm_utils_ip4_prefix_to_netmask (a1->plen);
+	n2 = a2->address & _nm_utils_ip4_prefix_to_netmask (a2->plen);
 
 	return memcmp (&n1, &n2, sizeof (guint32));
 }
 
 /*****************************************************************************/
 
-static void
-notify_addresses (NMIP4Config *self)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (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);
-}
-
-static gint
-sort_captured_addresses (gconstpointer a, gconstpointer b)
+static int
+sort_captured_addresses (const CList *lst_a, const CList *lst_b, gconstpointer user_data)
 {
-	const NMPlatformIP4Address *addr_a = a, *addr_b = b;
+	const NMPlatformIP4Address *addr_a = NMP_OBJECT_CAST_IP4_ADDRESS (c_list_entry (lst_a, NMDedupMultiEntry, lst_entries)->obj);
+	const NMPlatformIP4Address *addr_b = NMP_OBJECT_CAST_IP4_ADDRESS (c_list_entry (lst_b, NMDedupMultiEntry, lst_entries)->obj);
 
 	/* Primary addresses first */
 	return NM_FLAGS_HAS (addr_a->n_ifa_flags, IFA_F_SECONDARY) -
@@ -259,175 +582,237 @@ sort_captured_addresses (gconstpointer a, gconstpointer b)
 }
 
 NMIP4Config *
-nm_ip4_config_capture (NMPlatform *platform, int ifindex, gboolean capture_resolv_conf)
+nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf)
 {
-	NMIP4Config *config;
+	NMIP4Config *self;
 	NMIP4ConfigPrivate *priv;
-	guint i;
-	guint32 lowest_metric = G_MAXUINT32;
-	guint32 old_gateway = 0;
-	gboolean old_has_gateway = FALSE;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *plobj = NULL;
+	gboolean has_addresses = FALSE;
+
+	nm_assert (ifindex > 0);
 
 	/* Slaves have no IP configuration */
 	if (nm_platform_link_get_master (platform, ifindex) > 0)
 		return NULL;
 
-	config = nm_ip4_config_new (ifindex);
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	g_array_unref (priv->addresses);
-	g_array_unref (priv->routes);
-
-	priv->addresses = nm_platform_ip4_address_get_all (platform, ifindex);
-	g_array_sort (priv->addresses, sort_captured_addresses);
-
-	priv->routes = nm_platform_ip4_route_get_all (platform, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-
-	/* Extract gateway from default route */
-	old_gateway = priv->gateway;
-	old_has_gateway = priv->has_gateway;
-	for (i = 0; i < priv->routes->len; ) {
-		const NMPlatformIP4Route *route = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
-			if (route->metric < lowest_metric) {
-				priv->gateway = route->gateway;
-				lowest_metric = route->metric;
-			}
-			priv->has_gateway = TRUE;
-			/* Remove the default route from the list */
-			g_array_remove_index_fast (priv->routes, i);
-			continue;
+	self = nm_ip4_config_new (multi_idx, ifindex);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	head_entry = nm_platform_lookup_addrroute (platform,
+	                                           NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                                           ifindex);
+	if (head_entry) {
+		nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+			if (!_nm_ip_config_add_obj (priv->multi_idx,
+			                            &priv->idx_ip4_addresses_,
+			                            ifindex,
+			                            plobj,
+			                            NULL,
+			                            FALSE,
+			                            TRUE,
+			                            NULL,
+			                            NULL))
+				nm_assert_not_reached ();
 		}
-		i++;
+		head_entry = nm_ip4_config_lookup_addresses (self);
+		nm_assert (head_entry);
+		nm_dedup_multi_head_entry_sort (head_entry,
+		                                sort_captured_addresses,
+		                                NULL);
+		has_addresses = TRUE;
+		_notify_addresses (self);
 	}
 
-	/* we detect the route metric based on the default route. All non-default
-	 * routes have their route metrics explicitly set. */
-	priv->route_metric = priv->has_gateway ? (gint64) lowest_metric : (gint64) -1;
+	head_entry = nm_platform_lookup_addrroute (platform,
+	                                           NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                           ifindex);
 
-	/* If there is a host route to the gateway, ignore that route.  It is
-	 * automatically added by NetworkManager when needed.
-	 */
-	if (priv->has_gateway) {
-		for (i = 0; i < priv->routes->len; i++) {
-			const NMPlatformIP4Route *route = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-			if (   (route->plen == 32)
-			    && (route->network == priv->gateway)
-			    && (route->gateway == 0)) {
-				g_array_remove_index (priv->routes, i);
-				i--;
-			}
-		}
-	}
+	/* Extract gateway from default route */
+	nmp_cache_iter_for_each (&iter, head_entry, &plobj)
+		_add_route (self, plobj, NULL, NULL);
 
 	/* If the interface has the default route, and has IPv4 addresses, capture
 	 * nameservers from /etc/resolv.conf.
 	 */
-	if (priv->addresses->len && priv->has_gateway && capture_resolv_conf) {
-		if (nm_ip4_config_capture_resolv_conf (priv->nameservers, priv->dns_options, NULL))
-			_notify (config, PROP_NAMESERVERS);
+	if (   has_addresses
+	    && priv->best_default_route
+	    && capture_resolv_conf) {
+		gs_free char *rc_contents = NULL;
+
+		if (g_file_get_contents (_PATH_RESCONF, &rc_contents, NULL, NULL)) {
+			if (nm_utils_resolve_conf_parse (AF_INET,
+			                                 rc_contents,
+			                                 priv->nameservers,
+			                                 priv->dns_options))
+				_notify (self, PROP_NAMESERVERS);
+		}
 	}
 
-	/* actually, nobody should be connected to the signal, just to be sure, notify */
-	_notify (config, PROP_ADDRESS_DATA);
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ADDRESSES);
-	_notify (config, PROP_ROUTES);
-	if (   priv->gateway != old_gateway
-	    || priv->has_gateway != old_has_gateway)
-		_notify (config, PROP_GATEWAY);
-
-	return config;
+	return self;
 }
 
-gboolean
-nm_ip4_config_commit (const NMIP4Config *config, NMPlatform *platform, NMRouteManager *route_manager, int ifindex, gboolean routes_full_sync, gint64 default_route_metric)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	gs_unref_ptrarray GPtrArray *added_addresses = NULL;
+void
+nm_ip4_config_add_dependent_routes (NMIP4Config *self,
+                                    guint32 route_table,
+                                    guint32 route_metric,
+                                    GPtrArray **out_ip4_dev_route_blacklist)
+{
+	const NMIP4ConfigPrivate *priv;
+	GPtrArray *ip4_dev_route_blacklist = NULL;
+	const NMPlatformIP4Address *my_addr;
+	const NMPlatformIP4Route *my_route;
+	int ifindex;
+	NMDedupMultiIter iter;
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (config != NULL, FALSE);
+	g_return_if_fail (NM_IS_IP4_CONFIG (self));
 
-	/* Addresses */
-	nm_platform_ip4_address_sync (platform, ifindex, priv->addresses,
-	                              default_route_metric >= 0 ? &added_addresses : NULL);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	/* Routes */
-	{
-		guint i;
-		guint count = nm_ip4_config_get_num_routes (config);
-		GArray *routes = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformIP4Route), count);
-		gboolean success;
-		gs_unref_array GArray *device_route_purge_list = NULL;
-
-		if (   default_route_metric >= 0
-		    && added_addresses) {
-			/* For IPv6, we explicitly add the device-routes (onlink) to NMIP6Config.
-			 * As we don't do that for IPv4, add it here shortly before syncing
-			 * the routes. For NMRouteManager these routes are very much important. */
-			for (i = 0; i < added_addresses->len; i++) {
-				const NMPlatformIP4Address *addr = added_addresses->pdata[i];
-				NMPlatformIP4Route route = { 0 };
-
-				if (addr->plen == 0)
-					continue;
+	ifindex = nm_ip4_config_get_ifindex (self);
+	g_return_if_fail (ifindex > 0);
 
-				nm_assert (addr->plen <= 32);
+	/* For IPv6 slaac, we explicitly add the device-routes (onlink) to NMIP6Config.
+	 * As we don't do that for IPv4 (and manual IPv6 addresses), add them explicitly. */
 
-				route.ifindex = ifindex;
-				route.rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
+	nm_ip_config_iter_ip4_address_for_each (&iter, self, &my_addr) {
+		nm_auto_nmpobj NMPObject *r = NULL;
+		NMPlatformIP4Route *route;
+		in_addr_t network;
 
-				/* The destination network depends on the peer-address. */
-				route.network = nm_utils_ip4_address_clear_host_address (addr->peer_address, addr->plen);
+		if (my_addr->plen == 0)
+			continue;
 
-				if (_ipv4_is_zeronet (route.network)) {
-					/* Kernel doesn't add device-routes for destinations that
-					 * start with 0.x.y.z. Skip them. */
-					continue;
-				}
+		nm_assert (my_addr->plen <= 32);
 
-				route.plen = addr->plen;
-				route.pref_src = addr->address;
-				route.metric = default_route_metric;
+		/* The destination network depends on the peer-address. */
+		network = nm_utils_ip4_address_clear_host_address (my_addr->peer_address, my_addr->plen);
 
-				g_array_append_val (routes, route);
+		if (_ipv4_is_zeronet (network)) {
+			/* Kernel doesn't add device-routes for destinations that
+			 * start with 0.x.y.z. Skip them. */
+			continue;
+		}
 
-				if (default_route_metric != NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE) {
-					if (!device_route_purge_list)
-						device_route_purge_list = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-					route.metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE;
-					g_array_append_val (device_route_purge_list, route);
-				}
+		r = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
+		route = NMP_OBJECT_CAST_IP4_ROUTE (r);
+
+		route->ifindex = ifindex;
+		route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
+		route->network = network;
+		route->plen = my_addr->plen;
+		route->pref_src = my_addr->address;
+		route->table_coerced = nm_platform_route_table_coerce (route_table);
+		route->metric = route_metric;
+		route->scope_inv = nm_platform_route_scope_inv (NM_RT_SCOPE_LINK);
+
+		nm_platform_ip_route_normalize (AF_INET, (NMPlatformIPRoute *) route);
+
+		if (_lookup_route (self,
+		                   r,
+		                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) {
+			/* we already track this route. Don't add it again. */
+		} else
+			_add_route (self, r, NULL, NULL);
+
+		if (   out_ip4_dev_route_blacklist
+		    && (   route_table != RT_TABLE_MAIN
+		        || route_metric != NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE)) {
+			nm_auto_nmpobj NMPObject *r_dev = NULL;
+
+			r_dev = nmp_object_clone (r, FALSE);
+			route = NMP_OBJECT_CAST_IP4_ROUTE (r_dev);
+			route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_MAIN);
+			route->metric = NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE;
+
+			nm_platform_ip_route_normalize (AF_INET, (NMPlatformIPRoute *) route);
+
+			if (_lookup_route (self,
+			                   r_dev,
+			                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) {
+				/* we track such a route explicitly. Don't blacklist it. */
+			} else {
+				if (!ip4_dev_route_blacklist)
+					ip4_dev_route_blacklist = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+
+				g_ptr_array_add (ip4_dev_route_blacklist,
+				                 g_steal_pointer (&r_dev));
 			}
 		}
+	}
 
-		for (i = 0; i < count; i++) {
-			const NMPlatformIP4Route *route;
+again:
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &my_route) {
+		NMPlatformIP4Route rt;
 
-			route = nm_ip4_config_get_route (config, i);
-			/* duplicates in @routes are no problem as route-manager handles them
-			 * gracefully (by ignoring them). */
-			g_array_append_vals (routes, route, 1);
-		}
-
-		nm_route_manager_ip4_route_register_device_route_purge_list (route_manager, device_route_purge_list);
+		if (   !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (my_route)
+		    || my_route->gateway == 0
+		    || NM_IS_IP_CONFIG_SOURCE_RTPROT (my_route->rt_source)
+		    || nm_ip4_config_get_direct_route_for_host (self,
+		                                                my_route->gateway,
+		                                                nm_platform_route_table_uncoerce (my_route->table_coerced, TRUE)))
+			continue;
 
-		success = nm_route_manager_ip4_route_sync (route_manager, ifindex, routes, default_route_metric < 0, routes_full_sync);
-		g_array_unref (routes);
-		if (!success)
-			return FALSE;
+		rt = *my_route;
+		rt.network = my_route->gateway;
+		rt.plen = 32;
+		rt.gateway = 0;
+		_add_route (self, NULL, &rt, NULL);
+		/* adding the route might have invalidated the iteration. Start again. */
+		goto again;
 	}
 
-	return TRUE;
+	NM_SET_OUT (out_ip4_dev_route_blacklist, ip4_dev_route_blacklist);
+}
+
+gboolean
+nm_ip4_config_commit (const NMIP4Config *self,
+                      NMPlatform *platform,
+                      NMIPRouteTableSyncMode route_table_sync)
+{
+	gs_unref_ptrarray GPtrArray *addresses = NULL;
+	gs_unref_ptrarray GPtrArray *routes = NULL;
+	gs_unref_ptrarray GPtrArray *routes_prune = NULL;
+	int ifindex;
+	gboolean success = TRUE;
+
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), FALSE);
+
+	ifindex = nm_ip4_config_get_ifindex (self);
+	g_return_val_if_fail (ifindex > 0, FALSE);
+
+	addresses = nm_dedup_multi_objs_to_ptr_array_head (nm_ip4_config_lookup_addresses (self),
+	                                                   NULL, NULL);
+
+	routes = nm_dedup_multi_objs_to_ptr_array_head (nm_ip4_config_lookup_routes (self),
+	                                                NULL, NULL);
+
+	routes_prune = nm_platform_ip_route_get_prune_list (platform,
+	                                                    AF_INET,
+	                                                    ifindex,
+	                                                    route_table_sync);
+
+	nm_platform_ip4_address_sync (platform, ifindex, addresses);
+
+	if (!nm_platform_ip_route_sync (platform,
+	                                AF_INET,
+	                                ifindex,
+	                                routes,
+	                                routes_prune,
+	                                NULL))
+		success = FALSE;
+
+	return success;
 }
 
 static void
-merge_route_attributes (NMIPRoute *s_route, NMPlatformIP4Route *r)
+merge_route_attributes (NMIPRoute *s_route,
+                        NMPlatformIP4Route *r,
+                        guint32 route_table)
 {
 	GVariant *variant;
+	guint32 u32;
 	in_addr_t addr;
 
 #define GET_ATTR(name, field, variant_type, type) \
@@ -435,6 +820,12 @@ merge_route_attributes (NMIPRoute *s_route, NMPlatformIP4Route *r)
 	if (variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_ ## variant_type)) \
 		r->field = g_variant_get_ ## type (variant);
 
+	variant = nm_ip_route_get_attribute (s_route, NM_IP_ROUTE_ATTRIBUTE_TABLE);
+	u32 =   variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)
+	      ? g_variant_get_uint32 (variant)
+	      : 0;
+	r->table_coerced = nm_platform_route_table_coerce (u32 ?: (route_table ?: RT_TABLE_MAIN));
+
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TOS,            tos,            BYTE,     byte);
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_WINDOW,         window,         UINT32,   uint32);
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_CWND,           cwnd,           UINT32,   uint32);
@@ -457,20 +848,25 @@ merge_route_attributes (NMIPRoute *s_route, NMPlatformIP4Route *r)
 }
 
 void
-nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, guint32 default_route_metric)
+nm_ip4_config_merge_setting (NMIP4Config *self,
+                             NMSettingIPConfig *setting,
+                             guint32 route_table,
+                             guint32 route_metric)
 {
 	NMIP4ConfigPrivate *priv;
 	guint naddresses, nroutes, nnameservers, nsearches;
 	int i, priority;
+	const char *gateway_str;
+	guint32 gateway_bin;
 
 	if (!setting)
 		return;
 
 	g_return_if_fail (NM_IS_SETTING_IP4_CONFIG (setting));
 
-	priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	g_object_freeze_notify (G_OBJECT (config));
+	g_object_freeze_notify (G_OBJECT (self));
 
 	naddresses = nm_setting_ip_config_get_num_addresses (setting);
 	nroutes = nm_setting_ip_config_get_num_routes (setting);
@@ -478,20 +874,20 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 	nsearches = nm_setting_ip_config_get_num_dns_searches (setting);
 
 	/* Gateway */
-	if (nm_setting_ip_config_get_never_default (setting))
-		nm_ip4_config_set_never_default (config, TRUE);
-	else if (nm_setting_ip_config_get_ignore_auto_routes (setting))
-		nm_ip4_config_set_never_default (config, FALSE);
-	if (nm_setting_ip_config_get_gateway (setting)) {
-		guint32 gateway;
+	if (   !nm_setting_ip_config_get_never_default (setting)
+	    && (gateway_str = nm_setting_ip_config_get_gateway (setting))
+	    && inet_pton (AF_INET, gateway_str, &gateway_bin) == 1
+	    && gateway_bin) {
+		const NMPlatformIP4Route r = {
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.gateway = gateway_bin,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric = route_metric,
+		};
 
-		inet_pton (AF_INET, nm_setting_ip_config_get_gateway (setting), &gateway);
-		nm_ip4_config_set_gateway (config, gateway);
+		_add_route (self, NULL, &r, NULL);
 	}
 
-	if (priv->route_metric  == -1)
-		priv->route_metric = nm_setting_ip_config_get_route_metric (setting);
-
 	/* Addresses */
 	for (i = 0; i < naddresses; i++) {
 		NMIPAddress *s_addr = nm_setting_ip_config_get_address (setting, i);
@@ -511,16 +907,21 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 		if (label)
 			g_strlcpy (address.label, g_variant_get_string (label, NULL), sizeof (address.label));
 
-		nm_ip4_config_add_address (config, &address);
+		_add_address (self, NULL, &address);
 	}
 
 	/* Routes */
 	if (nm_setting_ip_config_get_ignore_auto_routes (setting))
-		nm_ip4_config_reset_routes (config);
+		nm_ip4_config_reset_routes (self);
 	for (i = 0; i < nroutes; i++) {
 		NMIPRoute *s_route = nm_setting_ip_config_get_route (setting, i);
 		NMPlatformIP4Route route;
 
+		if (nm_ip_route_get_family (s_route) != AF_INET) {
+			nm_assert_not_reached ();
+			continue;
+		}
+
 		memset (&route, 0, sizeof (route));
 		nm_ip_route_get_dest_binary (s_route, &route.network);
 
@@ -531,73 +932,74 @@ nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, gu
 
 		nm_ip_route_get_next_hop_binary (s_route, &route.gateway);
 		if (nm_ip_route_get_metric (s_route) == -1)
-			route.metric = default_route_metric;
+			route.metric = route_metric;
 		else
 			route.metric = nm_ip_route_get_metric (s_route);
 		route.rt_source = NM_IP_CONFIG_SOURCE_USER;
 
-		merge_route_attributes (s_route, &route);
-		nm_ip4_config_add_route (config, &route);
+		route.network = nm_utils_ip4_address_clear_host_address (route.network, route.plen);
+
+		merge_route_attributes (s_route, &route, route_table);
+		_add_route (self, NULL, &route, NULL);
 	}
 
 	/* DNS */
 	if (nm_setting_ip_config_get_ignore_auto_dns (setting)) {
-		nm_ip4_config_reset_nameservers (config);
-		nm_ip4_config_reset_domains (config);
-		nm_ip4_config_reset_searches (config);
+		nm_ip4_config_reset_nameservers (self);
+		nm_ip4_config_reset_domains (self);
+		nm_ip4_config_reset_searches (self);
 	}
 	for (i = 0; i < nnameservers; i++) {
 		guint32 ip;
 
 		if (inet_pton (AF_INET, nm_setting_ip_config_get_dns (setting, i), &ip) == 1)
-			nm_ip4_config_add_nameserver (config, ip);
+			nm_ip4_config_add_nameserver (self, ip);
 	}
 	for (i = 0; i < nsearches; i++)
-		nm_ip4_config_add_search (config, nm_setting_ip_config_get_dns_search (setting, i));
+		nm_ip4_config_add_search (self, nm_setting_ip_config_get_dns_search (setting, i));
 
 	i = 0;
 	while ((i = nm_setting_ip_config_next_valid_dns_option (setting, i)) >= 0) {
-		nm_ip4_config_add_dns_option (config, nm_setting_ip_config_get_dns_option (setting, i));
+		nm_ip4_config_add_dns_option (self, nm_setting_ip_config_get_dns_option (setting, i));
 		i++;
 	}
 
 	priority = nm_setting_ip_config_get_dns_priority (setting);
 	if (priority)
-		nm_ip4_config_set_dns_priority (config, priority);
+		nm_ip4_config_set_dns_priority (self, priority);
 
-	g_object_thaw_notify (G_OBJECT (config));
+	g_object_thaw_notify (G_OBJECT (self));
 }
 
 NMSetting *
-nm_ip4_config_create_setting (const NMIP4Config *config)
+nm_ip4_config_create_setting (const NMIP4Config *self)
 {
+	const NMIP4ConfigPrivate *priv;
 	NMSettingIPConfig *s_ip4;
-	guint32 gateway;
-	guint naddresses, nroutes, nnameservers, nsearches, noptions;
+	guint nnameservers, nsearches, noptions;
 	const char *method = NULL;
 	int i;
-	gint64 route_metric;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
 
 	s_ip4 = NM_SETTING_IP_CONFIG (nm_setting_ip4_config_new ());
 
-	if (!config) {
+	if (!self) {
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
 		              NULL);
 		return NM_SETTING (s_ip4);
 	}
 
-	gateway = nm_ip4_config_get_gateway (config);
-	naddresses = nm_ip4_config_get_num_addresses (config);
-	nroutes = nm_ip4_config_get_num_routes (config);
-	nnameservers = nm_ip4_config_get_num_nameservers (config);
-	nsearches = nm_ip4_config_get_num_searches (config);
-	noptions = nm_ip4_config_get_num_dns_options (config);
-	route_metric = nm_ip4_config_get_route_metric (config);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	nnameservers = nm_ip4_config_get_num_nameservers (self);
+	nsearches = nm_ip4_config_get_num_searches (self);
+	noptions = nm_ip4_config_get_num_dns_options (self);
 
 	/* Addresses */
-	for (i = 0; i < naddresses; i++) {
-		const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) {
 		NMIPAddress *s_addr;
 
 		/* Detect dynamic address */
@@ -619,10 +1021,12 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 	}
 
 	/* Gateway */
-	if (   nm_ip4_config_has_gateway (config)
+	if (   priv->best_default_route
 	    && nm_setting_ip_config_get_num_addresses (s_ip4) > 0) {
 		g_object_set (s_ip4,
-		              NM_SETTING_IP_CONFIG_GATEWAY, nm_utils_inet4_ntop (gateway, NULL),
+		              NM_SETTING_IP_CONFIG_GATEWAY,
+		              nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
+		                                   NULL),
 		              NULL);
 	}
 
@@ -632,16 +1036,13 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 
 	g_object_set (s_ip4,
 	              NM_SETTING_IP_CONFIG_METHOD, method,
-	              NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64) route_metric,
 	              NULL);
 
 	/* Routes */
-	for (i = 0; i < nroutes; i++) {
-		const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
 		NMIPRoute *s_route;
 
-		/* Ignore default route. */
-		if (!route->plen)
+		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
 			continue;
 
 		/* Ignore routes provided by external sources */
@@ -658,25 +1059,25 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 
 	/* DNS */
 	for (i = 0; i < nnameservers; i++) {
-		guint32 nameserver = nm_ip4_config_get_nameserver (config, i);
+		guint32 nameserver = nm_ip4_config_get_nameserver (self, i);
 
 		nm_setting_ip_config_add_dns (s_ip4, nm_utils_inet4_ntop (nameserver, NULL));
 	}
 	for (i = 0; i < nsearches; i++) {
-		const char *search = nm_ip4_config_get_search (config, i);
+		const char *search = nm_ip4_config_get_search (self, i);
 
 		nm_setting_ip_config_add_dns_search (s_ip4, search);
 	}
 
 	for (i = 0; i < noptions; i++) {
-		const char *option = nm_ip4_config_get_dns_option (config, i);
+		const char *option = nm_ip4_config_get_dns_option (self, i);
 
 		nm_setting_ip_config_add_dns_option (s_ip4, option);
 	}
 
 	g_object_set (s_ip4,
 	              NM_SETTING_IP_CONFIG_DNS_PRIORITY,
-	              nm_ip4_config_get_dns_priority (config),
+	              nm_ip4_config_get_dns_priority (self),
 	              NULL);
 
 	return NM_SETTING (s_ip4);
@@ -685,11 +1086,16 @@ nm_ip4_config_create_setting (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFlags merge_flags)
+nm_ip4_config_merge (NMIP4Config *dst,
+                     const NMIP4Config *src,
+                     NMIPConfigMergeFlags merge_flags,
+                     guint32 default_route_metric_penalty)
 {
 	NMIP4ConfigPrivate *dst_priv;
 	const NMIP4ConfigPrivate *src_priv;
 	guint32 i;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address = NULL;
 
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
@@ -700,8 +1106,8 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (src); i++)
-		nm_ip4_config_add_address (dst, nm_ip4_config_get_address (src, i));
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &address)
+		_add_address (dst, NMP_OBJECT_UP_CAST (address), NULL);
 
 	/* nameservers */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
@@ -709,20 +1115,25 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 			nm_ip4_config_add_nameserver (dst, nm_ip4_config_get_nameserver (src, i));
 	}
 
-	/* default gateway */
-	if (nm_ip4_config_has_gateway (src))
-		nm_ip4_config_set_gateway (dst, nm_ip4_config_get_gateway (src));
-
 	/* routes */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_ROUTES)) {
-		for (i = 0; i < nm_ip4_config_get_num_routes (src); i++)
-			nm_ip4_config_add_route (dst, nm_ip4_config_get_route (src, i));
-	}
+		const NMPlatformIP4Route *r_src;
 
-	if (dst_priv->route_metric == -1)
-		dst_priv->route_metric = src_priv->route_metric;
-	else if (src_priv->route_metric != -1)
-		dst_priv->route_metric = MIN (dst_priv->route_metric, src_priv->route_metric);
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r_src) {
+			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r_src)) {
+				if (NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES))
+					continue;
+				if (default_route_metric_penalty) {
+					NMPlatformIP4Route r = *r_src;
+
+					r.metric = nm_utils_ip_route_metric_penalize (AF_INET, r.metric, default_route_metric_penalty);
+					_add_route (dst, NULL, &r, NULL);
+					continue;
+				}
+			}
+			_add_route (dst, ipconf_iter.current->obj, NULL, NULL);
+		}
+	}
 
 	/* domains */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
@@ -742,10 +1153,6 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 			nm_ip4_config_add_dns_option (dst, nm_ip4_config_get_dns_option (src, i));
 	}
 
-	/* MSS */
-	if (nm_ip4_config_get_mss (src))
-		nm_ip4_config_set_mss (dst, nm_ip4_config_get_mss (src));
-
 	/* MTU */
 	if (   src_priv->mtu_source > dst_priv->mtu_source
 	    || (   src_priv->mtu_source == dst_priv->mtu_source
@@ -782,21 +1189,6 @@ nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFl
 /*****************************************************************************/
 
 static int
-_addresses_get_index (const NMIP4Config *self, const NMPlatformIP4Address *addr)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
-	guint i;
-
-	for (i = 0; i < priv->addresses->len; i++) {
-		const NMPlatformIP4Address *a = &g_array_index (priv->addresses, NMPlatformIP4Address, i);
-
-		if (addresses_are_duplicate (addr, a))
-			return (int) i;
-	}
-	return -1;
-}
-
-static int
 _nameservers_get_index (const NMIP4Config *self, guint32 ns)
 {
 	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
@@ -812,22 +1204,6 @@ _nameservers_get_index (const NMIP4Config *self, guint32 ns)
 }
 
 static int
-_routes_get_index (const NMIP4Config *self, const NMPlatformIP4Route *route)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
-	guint i;
-
-	for (i = 0; i < priv->routes->len; i++) {
-		const NMPlatformIP4Route *r = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-		if (   route->network == r->network
-		    && route->plen == r->plen)
-			return (int) i;
-	}
-	return -1;
-}
-
-static int
 _domains_get_index (const NMIP4Config *self, const char *domain)
 {
 	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
@@ -908,26 +1284,45 @@ _wins_get_index (const NMIP4Config *self, guint32 wins_server)
  * nm_ip4_config_subtract:
  * @dst: config from which to remove everything in @src
  * @src: config to remove from @dst
+ * @default_route_metric_penalty: pretend that on source we applied
+ *   a route penalty on the default-route. It means, for default routes
+ *   we don't remove routes that match exactly, but those with a lower
+ *   metric (with the penalty removed).
  *
  * Removes everything in @src from @dst.
  */
 void
-nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
+nm_ip4_config_subtract (NMIP4Config *dst,
+                        const NMIP4Config *src,
+                        guint32 default_route_metric_penalty)
 {
-	guint32 i;
+	NMIP4ConfigPrivate *dst_priv;
+	guint i;
 	gint idx;
+	const NMPlatformIP4Address *a;
+	const NMPlatformIP4Route *r;
+	NMDedupMultiIter ipconf_iter;
+	gboolean changed;
+	gboolean changed_default_route;
 
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
 
+	dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
+
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (src); i++) {
-		idx = _addresses_get_index (dst, nm_ip4_config_get_address (src, i));
-		if (idx >= 0)
-			nm_ip4_config_del_address (dst, idx);
+	changed = FALSE;
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, src, &a) {
+		if (nm_dedup_multi_index_remove_obj (dst_priv->multi_idx,
+		                                     &dst_priv->idx_ip4_addresses,
+		                                     NMP_OBJECT_UP_CAST (a),
+		                                     NULL))
+			changed = TRUE;
 	}
+	if (changed)
+		_notify_addresses (dst);
 
 	/* nameservers */
 	for (i = 0; i < nm_ip4_config_get_num_nameservers (src); i++) {
@@ -936,22 +1331,46 @@ nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 			nm_ip4_config_del_nameserver (dst, idx);
 	}
 
-	/* default gateway */
-	if (   (nm_ip4_config_has_gateway (src) == nm_ip4_config_has_gateway (dst))
-	    && (nm_ip4_config_get_gateway (src) == nm_ip4_config_get_gateway (dst)))
-		nm_ip4_config_unset_gateway (dst);
-
-	if (!nm_ip4_config_get_num_addresses (dst))
-		nm_ip4_config_unset_gateway (dst);
-
-	/* ignore route_metric */
-
 	/* routes */
-	for (i = 0; i < nm_ip4_config_get_num_routes (src); i++) {
-		idx = _routes_get_index (dst, nm_ip4_config_get_route (src, i));
-		if (idx >= 0)
-			nm_ip4_config_del_route (dst, idx);
+	changed = FALSE;
+	changed_default_route = FALSE;
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, src, &r) {
+		const NMPObject *o_src = NMP_OBJECT_UP_CAST (r);
+		NMPObject o_lookup_copy;
+		const NMPObject *o_lookup;
+		nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    && default_route_metric_penalty) {
+			NMPlatformIP4Route *rr;
+
+			/* the default route was penalized when merging it to the combined ip-config.
+			 * When subtracting the routes, we must re-do that process when comparing
+			 * the routes. */
+			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_src);
+			rr = NMP_OBJECT_CAST_IP4_ROUTE (&o_lookup_copy);
+			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET, rr->metric, default_route_metric_penalty);
+		} else
+			o_lookup = o_src;
+
+		if (nm_dedup_multi_index_remove_obj (dst_priv->multi_idx,
+		                                     &dst_priv->idx_ip4_routes,
+		                                     o_lookup,
+		                                     (gconstpointer *) &obj_old)) {
+			if (dst_priv->best_default_route == obj_old) {
+				nm_clear_nmp_object (&dst_priv->best_default_route);
+				changed_default_route = TRUE;
+			}
+			changed = TRUE;
+		}
+	}
+	if (changed_default_route) {
+		_nm_ip_config_best_default_route_set (&dst_priv->best_default_route,
+		                                      _nm_ip4_config_best_default_route_find (dst));
+		_notify (dst, PROP_GATEWAY);
 	}
+	if (changed)
+		_notify_routes (dst);
 
 	/* domains */
 	for (i = 0; i < nm_ip4_config_get_num_domains (src); i++) {
@@ -974,10 +1393,6 @@ nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 			nm_ip4_config_del_dns_option (dst, idx);
 	}
 
-	/* MSS */
-	if (nm_ip4_config_get_mss (src) == nm_ip4_config_get_mss (dst))
-		nm_ip4_config_set_mss (dst, 0);
-
 	/* MTU */
 	if (   nm_ip4_config_get_mtu (src) == nm_ip4_config_get_mtu (dst)
 	    && nm_ip4_config_get_mtu_source (src) == nm_ip4_config_get_mtu_source (dst))
@@ -1008,43 +1423,83 @@ nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src)
 }
 
 void
-nm_ip4_config_intersect (NMIP4Config *dst, const NMIP4Config *src)
+nm_ip4_config_intersect (NMIP4Config *dst,
+                         const NMIP4Config *src,
+                         guint32 default_route_metric_penalty)
 {
-	guint32 i;
-	gint idx;
+	NMIP4ConfigPrivate *dst_priv;
+	const NMIP4ConfigPrivate *src_priv;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *a;
+	const NMPlatformIP4Route *r;
+	const NMPObject *new_best_default_route;
+	gboolean changed;
 
-	g_return_if_fail (src != NULL);
-	g_return_if_fail (dst != NULL);
+	g_return_if_fail (src);
+	g_return_if_fail (dst);
+
+	dst_priv = NM_IP4_CONFIG_GET_PRIVATE (dst);
+	src_priv = NM_IP4_CONFIG_GET_PRIVATE (src);
 
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (dst); ) {
-		idx = _addresses_get_index (src, nm_ip4_config_get_address (dst, i));
-		if (idx < 0)
-			nm_ip4_config_del_address (dst, i);
-		else
-			i++;
+	changed = FALSE;
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, dst, &a) {
+		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+		                                     &src_priv->idx_ip4_addresses,
+		                                     NMP_OBJECT_UP_CAST (a)))
+			continue;
+
+		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+		                                       ipconf_iter.current) != 1)
+			nm_assert_not_reached ();
+		changed = TRUE;
 	}
+	if (changed)
+		_notify_addresses (dst);
 
-	/* ignore route_metric */
 	/* ignore nameservers */
 
-	/* default gateway */
-	if (   !nm_ip4_config_get_num_addresses (dst)
-	    || (nm_ip4_config_has_gateway (src) != nm_ip4_config_has_gateway (dst))
-	    || (nm_ip4_config_get_gateway (src) != nm_ip4_config_get_gateway (dst))) {
-		nm_ip4_config_unset_gateway (dst);
-	}
-
 	/* routes */
-	for (i = 0; i < nm_ip4_config_get_num_routes (dst); ) {
-		idx = _routes_get_index (src, nm_ip4_config_get_route (dst, i));
-		if (idx < 0)
-			nm_ip4_config_del_route (dst, i);
-		else
-			i++;
+	changed = FALSE;
+	new_best_default_route = NULL;
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, dst, &r) {
+		const NMPObject *o_dst = NMP_OBJECT_UP_CAST (r);
+		const NMPObject *o_lookup;
+		NMPObject o_lookup_copy;
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    && default_route_metric_penalty) {
+			NMPlatformIP4Route *rr;
+
+			/* the default route was penalized when merging it to the combined ip-config.
+			 * When intersecting the routes, we must re-do that process when comparing
+			 * the routes. */
+			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_dst);
+			rr = NMP_OBJECT_CAST_IP4_ROUTE (&o_lookup_copy);
+			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET, rr->metric, default_route_metric_penalty);
+		} else
+			o_lookup = o_dst;
+
+		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+		                                     &src_priv->idx_ip4_routes,
+		                                     o_lookup)) {
+			new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, o_dst);
+			continue;
+		}
+
+		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+		                                       ipconf_iter.current) != 1)
+			nm_assert_not_reached ();
+		changed = TRUE;
+	}
+	if (_nm_ip_config_best_default_route_set (&dst_priv->best_default_route, new_best_default_route)) {
+		nm_assert (changed);
+		_notify (dst, PROP_GATEWAY);
 	}
+	if (changed)
+		_notify_routes (dst);
 
 	/* ignore domains */
 	/* ignore dns searches */
@@ -1080,8 +1535,9 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 	guint i, num;
 	NMIP4ConfigPrivate *dst_priv;
 	const NMIP4ConfigPrivate *src_priv;
-	const NMPlatformIP4Address *dst_addr, *src_addr;
-	const NMPlatformIP4Route *dst_route, *src_route;
+	NMDedupMultiIter ipconf_iter_src, ipconf_iter_dst;
+	const NMDedupMultiHeadEntry *head_entry_src;
+	const NMPObject *new_best_default_route;
 
 	g_return_val_if_fail (src != NULL, FALSE);
 	g_return_val_if_fail (dst != NULL, FALSE);
@@ -1102,72 +1558,106 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_minor_changes = TRUE;
 	}
 
-	/* never_default */
-	if (src_priv->never_default != dst_priv->never_default) {
-		dst_priv->never_default = src_priv->never_default;
-		has_minor_changes = TRUE;
-	}
-
-	/* default gateway */
-	if (   src_priv->gateway != dst_priv->gateway
-	    || src_priv->has_gateway != dst_priv->has_gateway) {
-		if (src_priv->has_gateway)
-			nm_ip4_config_set_gateway (dst, src_priv->gateway);
-		else
-			nm_ip4_config_unset_gateway (dst);
-		has_relevant_changes = TRUE;
-	}
-
-	if (src_priv->route_metric != dst_priv->route_metric) {
-		dst_priv->route_metric = src_priv->route_metric;
-		has_minor_changes = TRUE;
-	}
-
 	/* addresses */
-	num = nm_ip4_config_get_num_addresses (src);
-	are_equal = num == nm_ip4_config_get_num_addresses (dst);
-	if (are_equal) {
-		for (i = 0; i < num; i++ ) {
-			if (nm_platform_ip4_address_cmp (src_addr = nm_ip4_config_get_address (src, i),
-			                                 dst_addr = nm_ip4_config_get_address (dst, i))) {
-				are_equal = FALSE;
-				if (   !addresses_are_duplicate (src_addr, dst_addr)
-				    || src_addr->peer_address != dst_addr->peer_address) {
-					has_relevant_changes = TRUE;
-					break;
-				}
+	head_entry_src = nm_ip4_config_lookup_addresses (src);
+	nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
+	nm_ip_config_iter_ip4_address_init (&ipconf_iter_dst, dst);
+	are_equal = TRUE;
+	while (TRUE) {
+		gboolean has;
+		const NMPlatformIP4Address *r_src = NULL;
+		const NMPlatformIP4Address *r_dst = NULL;
+
+		has = nm_ip_config_iter_ip4_address_next (&ipconf_iter_src, &r_src);
+		if (has != nm_ip_config_iter_ip4_address_next (&ipconf_iter_dst, &r_dst)) {
+			are_equal = FALSE;
+			has_relevant_changes = TRUE;
+			break;
+		}
+		if (!has)
+			break;
+
+		if (nm_platform_ip4_address_cmp (r_src, r_dst) != 0) {
+			are_equal = FALSE;
+			if (   r_src->address != r_dst->address
+			    || r_src->plen != r_dst->plen
+			    || r_src->peer_address != r_dst->peer_address) {
+				has_relevant_changes = TRUE;
+				break;
 			}
 		}
-	} else
-		has_relevant_changes = TRUE;
+	}
 	if (!are_equal) {
-		nm_ip4_config_reset_addresses (dst);
-		for (i = 0; i < num; i++)
-			nm_ip4_config_add_address (dst, nm_ip4_config_get_address (src, i));
 		has_minor_changes = TRUE;
+		nm_dedup_multi_index_dirty_set_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_addresses);
+		nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) {
+			_nm_ip_config_add_obj (dst_priv->multi_idx,
+			                       &dst_priv->idx_ip4_addresses_,
+			                       dst_priv->ifindex,
+			                       ipconf_iter_src.current->obj,
+			                       NULL,
+			                       FALSE,
+			                       TRUE,
+			                       NULL,
+			                       NULL);
+		}
+		nm_dedup_multi_index_dirty_remove_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_addresses, FALSE);
+		_notify_addresses (dst);
 	}
 
 	/* routes */
-	num = nm_ip4_config_get_num_routes (src);
-	are_equal = num == nm_ip4_config_get_num_routes (dst);
-	if (are_equal) {
-		for (i = 0; i < num; i++ ) {
-			if (nm_platform_ip4_route_cmp (src_route = nm_ip4_config_get_route (src, i),
-			                               dst_route = nm_ip4_config_get_route (dst, i))) {
-				are_equal = FALSE;
-				if (!routes_are_duplicate (src_route, dst_route, TRUE)) {
-					has_relevant_changes = TRUE;
-					break;
-				}
+	head_entry_src = nm_ip4_config_lookup_routes (src);
+	nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
+	nm_ip_config_iter_ip4_route_init (&ipconf_iter_dst, dst);
+	are_equal = TRUE;
+	while (TRUE) {
+		gboolean has;
+		const NMPlatformIP4Route *r_src = NULL;
+		const NMPlatformIP4Route *r_dst = NULL;
+
+		has = nm_ip_config_iter_ip4_route_next (&ipconf_iter_src, &r_src);
+		if (has != nm_ip_config_iter_ip4_route_next (&ipconf_iter_dst, &r_dst)) {
+			are_equal = FALSE;
+			has_relevant_changes = TRUE;
+			break;
+		}
+		if (!has)
+			break;
+
+		if (nm_platform_ip4_route_cmp_full (r_src, r_dst) != 0) {
+			are_equal = FALSE;
+			if (   r_src->plen != r_dst->plen
+			    || !nm_utils_ip4_address_same_prefix (r_src->network, r_dst->network, r_src->plen)
+			    || r_src->gateway != r_dst->gateway
+			    || r_src->metric != r_dst->metric) {
+				has_relevant_changes = TRUE;
+				break;
 			}
 		}
-	} else
-		has_relevant_changes = TRUE;
+	}
 	if (!are_equal) {
-		nm_ip4_config_reset_routes (dst);
-		for (i = 0; i < num; i++)
-			nm_ip4_config_add_route (dst, nm_ip4_config_get_route (src, i));
 		has_minor_changes = TRUE;
+		new_best_default_route = NULL;
+		nm_dedup_multi_index_dirty_set_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_routes);
+		nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) {
+			const NMPObject *o = ipconf_iter_src.current->obj;
+			const NMPObject *obj_new;
+
+			_nm_ip_config_add_obj (dst_priv->multi_idx,
+			                       &dst_priv->idx_ip4_routes_,
+			                       dst_priv->ifindex,
+			                       o,
+			                       NULL,
+			                       FALSE,
+			                       TRUE,
+			                       NULL,
+			                       &obj_new);
+			new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, obj_new);
+		}
+		nm_dedup_multi_index_dirty_remove_idx (dst_priv->multi_idx, &dst_priv->idx_ip4_routes, FALSE);
+		if (_nm_ip_config_best_default_route_set (&dst_priv->best_default_route, new_best_default_route))
+			_notify (dst, PROP_GATEWAY);
+		_notify_routes (dst);
 	}
 
 	/* nameservers */
@@ -1251,12 +1741,6 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 		has_minor_changes = TRUE;
 	}
 
-	/* mss */
-	if (src_priv->mss != dst_priv->mss) {
-		nm_ip4_config_set_mss (dst, src_priv->mss);
-		has_minor_changes = TRUE;
-	}
-
 	/* nis */
 	num = nm_ip4_config_get_num_nis_servers (src);
 	are_equal = num == nm_ip4_config_get_num_nis_servers (dst);
@@ -1327,386 +1811,344 @@ nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relev
 }
 
 void
-nm_ip4_config_dump (const NMIP4Config *config, const char *detail)
+nm_ip4_config_dump (const NMIP4Config *self, const char *detail)
 {
-	guint32 i, tmp;
+	guint32 tmp;
+	guint i;
 	const char *str;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
 
-	g_message ("--------- NMIP4Config %p (%s)", config, detail);
+	g_message ("--------- NMIP4Config %p (%s)", self, detail);
 
-	if (config == NULL) {
+	if (self == NULL) {
 		g_message (" (null)");
 		return;
 	}
 
-	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (config));
+	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
 	if (str)
 		g_message ("   path: %s", str);
 
 	/* addresses */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (config); i++)
-		g_message ("      a: %s", nm_platform_ip4_address_to_string (nm_ip4_config_get_address (config, i), NULL, 0));
-
-	/* default gateway */
-	if (nm_ip4_config_has_gateway (config)) {
-		tmp = nm_ip4_config_get_gateway (config);
-		g_message ("     gw: %s", nm_utils_inet4_ntop (tmp, NULL));
-	}
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address)
+		g_message ("      a: %s", nm_platform_ip4_address_to_string (address, NULL, 0));
 
 	/* nameservers */
-	for (i = 0; i < nm_ip4_config_get_num_nameservers (config); i++) {
-		tmp = nm_ip4_config_get_nameserver (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_nameservers (self); i++) {
+		tmp = nm_ip4_config_get_nameserver (self, i);
 		g_message ("     ns: %s", nm_utils_inet4_ntop (tmp, NULL));
 	}
 
 	/* routes */
-	for (i = 0; i < nm_ip4_config_get_num_routes (config); i++)
-		g_message ("     rt: %s", nm_platform_ip4_route_to_string (nm_ip4_config_get_route (config, i), NULL, 0));
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route)
+		g_message ("     rt: %s", nm_platform_ip4_route_to_string (route, NULL, 0));
 
 	/* domains */
-	for (i = 0; i < nm_ip4_config_get_num_domains (config); i++)
-		g_message (" domain: %s", nm_ip4_config_get_domain (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_domains (self); i++)
+		g_message (" domain: %s", nm_ip4_config_get_domain (self, i));
 
 	/* dns searches */
-	for (i = 0; i < nm_ip4_config_get_num_searches (config); i++)
-		g_message (" search: %s", nm_ip4_config_get_search (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_searches (self); i++)
+		g_message (" search: %s", nm_ip4_config_get_search (self, i));
 
 	/* dns options */
-	for (i = 0; i < nm_ip4_config_get_num_dns_options (config); i++)
-		g_message (" dnsopt: %s", nm_ip4_config_get_dns_option (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_dns_options (self); i++)
+		g_message (" dnsopt: %s", nm_ip4_config_get_dns_option (self, i));
 
-	g_message (" dnspri: %d", nm_ip4_config_get_dns_priority (config));
+	g_message (" dnspri: %d", nm_ip4_config_get_dns_priority (self));
 
-	g_message ("    mss: %"G_GUINT32_FORMAT, nm_ip4_config_get_mss (config));
-	g_message ("    mtu: %"G_GUINT32_FORMAT" (source: %d)", nm_ip4_config_get_mtu (config), (int) nm_ip4_config_get_mtu_source (config));
+	g_message ("    mtu: %"G_GUINT32_FORMAT" (source: %d)", nm_ip4_config_get_mtu (self), (int) nm_ip4_config_get_mtu_source (self));
 
 	/* NIS */
-	for (i = 0; i < nm_ip4_config_get_num_nis_servers (config); i++) {
-		tmp = nm_ip4_config_get_nis_server (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_nis_servers (self); i++) {
+		tmp = nm_ip4_config_get_nis_server (self, i);
 		g_message ("    nis: %s", nm_utils_inet4_ntop (tmp, NULL));
 	}
 
-	g_message (" nisdmn: %s", nm_ip4_config_get_nis_domain (config) ?: "(none)");
+	g_message (" nisdmn: %s", nm_ip4_config_get_nis_domain (self) ?: "(none)");
 
 	/* WINS */
-	for (i = 0; i < nm_ip4_config_get_num_wins (config); i++) {
-		tmp = nm_ip4_config_get_wins (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_wins (self); i++) {
+		tmp = nm_ip4_config_get_wins (self, i);
 		g_message ("   wins: %s", nm_utils_inet4_ntop (tmp, NULL));
 	}
 
-	g_message (" n-dflt: %d", nm_ip4_config_get_never_default (config));
-	g_message (" mtrd:   %d", (int) nm_ip4_config_get_metered (config));
-}
-
-gboolean
-nm_ip4_config_destination_is_direct (const NMIP4Config *config, guint32 network, guint8 plen)
-{
-	guint naddresses = nm_ip4_config_get_num_addresses (config);
-	guint i;
-	in_addr_t peer_network;
-
-	for (i = 0; i < naddresses; i++) {
-		const NMPlatformIP4Address *item = nm_ip4_config_get_address (config, i);
-
-		if (item->plen > plen)
-			continue;
-
-		peer_network = nm_utils_ip4_address_clear_host_address (item->peer_address, item->plen);
-		if (_ipv4_is_zeronet (peer_network))
-			continue;
-
-		if (peer_network != nm_utils_ip4_address_clear_host_address (network, item->plen))
-			continue;
-
-		return TRUE;
-	}
-
-	return FALSE;
+	g_message (" mtrd:   %d", (int) nm_ip4_config_get_metered (self));
 }
 
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_never_default (NMIP4Config *config, gboolean never_default)
+nm_ip4_config_reset_addresses (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	priv->never_default = never_default;
+	if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
+	                                     &priv->idx_ip4_addresses) > 0)
+		_notify_addresses (self);
 }
 
-gboolean
-nm_ip4_config_get_never_default (const NMIP4Config *config)
+static void
+_add_address (NMIP4Config *self, const NMPObject *obj_new, const NMPlatformIP4Address *new)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	return priv->never_default;
+	if (_nm_ip_config_add_obj (priv->multi_idx,
+	                           &priv->idx_ip4_addresses_,
+	                           priv->ifindex,
+	                           obj_new,
+	                           (const NMPlatformObject *) new,
+	                           TRUE,
+	                           FALSE,
+	                           NULL,
+	                           NULL))
+		_notify_addresses (self);
 }
 
+/**
+ * nm_ip4_config_add_address:
+ * @self: the #NMIP4Config
+ * @new: the new address to add to @self
+ *
+ * Adds the new address to @self.  If an address with the same basic properties
+ * (address, prefix) already exists in @self, it is overwritten with the
+ * lifetime and preferred of @new.  The source is also overwritten by the source
+ * from @new if that source is higher priority.
+ */
 void
-nm_ip4_config_set_gateway (NMIP4Config *config, guint32 gateway)
+nm_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	g_return_if_fail (self);
+	g_return_if_fail (new);
+	g_return_if_fail (new->plen > 0 && new->plen <= 32);
+	g_return_if_fail (NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
-	if (priv->gateway != gateway || !priv->has_gateway) {
-		priv->gateway = gateway;
-		priv->has_gateway = TRUE;
-		_notify (config, PROP_GATEWAY);
-	}
+	_add_address (self, NULL, new);
 }
 
 void
-nm_ip4_config_unset_gateway (NMIP4Config *config)
+_nmtst_ip4_config_del_address (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMPlatformIP4Address *a;
 
-	if (priv->has_gateway) {
-		priv->gateway = 0;
-		priv->has_gateway = FALSE;
-		_notify (config, PROP_GATEWAY);
-	}
+	a = _nmtst_ip4_config_get_address (self, i);
+	if (!nm_ip4_config_nmpobj_remove (self,
+	                                  NMP_OBJECT_UP_CAST (a)))
+		g_assert_not_reached ();
 }
 
-/**
- * nm_ip4_config_has_gateway:
- * @config: the #NMIP4Config object
- *
- * NetworkManager's handling of default-routes is limited and usually a default-route
- * cannot have gateway 0.0.0.0. For peer-to-peer routes, we still want to
- * support that, so we need to differenciate between no-default-route and a
- * on-link-default route. Hence nm_ip4_config_has_gateway().
- *
- * Returns: whether the object has a gateway explicitly set. */
-gboolean
-nm_ip4_config_has_gateway (const NMIP4Config *config)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	return priv->has_gateway;
-}
-
-guint32
-nm_ip4_config_get_gateway (const NMIP4Config *config)
+guint
+nm_ip4_config_get_num_addresses (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMDedupMultiHeadEntry *head_entry;
 
-	return priv->gateway;
+	head_entry = nm_ip4_config_lookup_addresses (self);
+	return head_entry ? head_entry->len : 0;
 }
 
-gint64
-nm_ip4_config_get_route_metric (const NMIP4Config *config)
+const NMPlatformIP4Address *
+nm_ip4_config_get_first_address (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP4Address *a = NULL;
 
-	return priv->route_metric;
+	nm_ip_config_iter_ip4_address_for_each (&iter, self, &a)
+		return a;
+	return NULL;
 }
 
-/*****************************************************************************/
-
-void
-nm_ip4_config_reset_addresses (NMIP4Config *config)
+const NMPlatformIP4Address *
+_nmtst_ip4_config_get_address (const NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP4Address *a = NULL;
+	guint j;
 
-	if (priv->addresses->len != 0) {
-		g_array_set_size (priv->addresses, 0);
-		notify_addresses (config);
+	j = 0;
+	nm_ip_config_iter_ip4_address_for_each (&iter, self, &a) {
+		if (i == j)
+			return a;
+		j++;
 	}
+	g_return_val_if_reached (NULL);
 }
 
-/**
- * nm_ip4_config_add_address:
- * @config: the #NMIP4Config
- * @new: the new address to add to @config
- *
- * Adds the new address to @config.  If an address with the same basic properties
- * (address, prefix) already exists in @config, it is overwritten with the
- * lifetime and preferred of @new.  The source is also overwritten by the source
- * from @new if that source is higher priority.
- */
-void
-nm_ip4_config_add_address (NMIP4Config *config, const NMPlatformIP4Address *new)
+gboolean
+nm_ip4_config_address_exists (const NMIP4Config *self,
+                              const NMPlatformIP4Address *needle)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	NMPlatformIP4Address item_old;
-	int i;
-
-	g_return_if_fail (new != NULL);
-
-	for (i = 0; i < priv->addresses->len; i++ ) {
-		NMPlatformIP4Address *item = &g_array_index (priv->addresses, NMPlatformIP4Address, i);
-
-		if (addresses_are_duplicate (item, new)) {
-			if (nm_platform_ip4_address_cmp (item, new) == 0)
-				return;
-
-			/* remember the old values. */
-			item_old = *item;
-			/* Copy over old item to get new lifetime, timestamp, preferred */
-			*item = *new;
-
-			/* But restore highest priority source */
-			item->addr_source = MAX (item_old.addr_source, new->addr_source);
-
-			/* for addresses that we read from the kernel, we keep the timestamps as defined
-			 * by the previous source (item_old). The reason is, that the other source configured the lifetimes
-			 * with "what should be" and the kernel values are "what turned out after configuring it".
-			 *
-			 * For other sources, the longer lifetime wins. */
-			if (   (new->addr_source == NM_IP_CONFIG_SOURCE_KERNEL && new->addr_source != item_old.addr_source)
-			    || nm_platform_ip_address_cmp_expiry ((const NMPlatformIPAddress *) &item_old, (const NMPlatformIPAddress *) new) > 0) {
-				item->timestamp = item_old.timestamp;
-				item->lifetime = item_old.lifetime;
-				item->preferred = item_old.preferred;
-			}
-			if (nm_platform_ip4_address_cmp (&item_old, item) == 0)
-				return;
-			goto NOTIFY;
-		}
-	}
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	NMPObject obj_stack;
 
-	g_array_append_val (priv->addresses, *new);
-NOTIFY:
-	notify_addresses (config);
+	nmp_object_stackinit_id_ip4_address (&obj_stack,
+	                                     priv->ifindex,
+	                                     needle->address,
+	                                     needle->plen,
+	                                     needle->peer_address);
+	return !!nm_dedup_multi_index_lookup_obj (priv->multi_idx,
+	                                          &priv->idx_ip4_addresses,
+	                                          &obj_stack);
 }
 
-void
-nm_ip4_config_del_address (NMIP4Config *config, guint i)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	g_return_if_fail (i < priv->addresses->len);
-
-	g_array_remove_index (priv->addresses, i);
-
-	notify_addresses (config);
-}
+/*****************************************************************************/
 
-guint
-nm_ip4_config_get_num_addresses (const NMIP4Config *config)
+static const NMDedupMultiEntry *
+_lookup_route (const NMIP4Config *self,
+               const NMPObject *needle,
+               NMPlatformIPRouteCmpType cmp_type)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv;
 
-	return priv->addresses->len;
-}
+	nm_assert (NM_IS_IP4_CONFIG (self));
+	nm_assert (NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP4_ROUTE);
 
-const NMPlatformIP4Address *
-nm_ip4_config_get_address (const NMIP4Config *config, guint i)
-{
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-	return &g_array_index (priv->addresses, NMPlatformIP4Address, i);
+	return _nm_ip_config_lookup_ip_route (priv->multi_idx,
+	                                      &priv->idx_ip4_routes_,
+	                                      needle,
+	                                      cmp_type);
 }
 
-gboolean
-nm_ip4_config_address_exists (const NMIP4Config *config,
-                              const NMPlatformIP4Address *needle)
+void
+nm_ip4_config_reset_routes (NMIP4Config *self)
 {
-	return _addresses_get_index (config, needle) >= 0;
-}
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
-/*****************************************************************************/
+	if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
+	                                     &priv->idx_ip4_routes) > 0) {
+		if (nm_clear_nmp_object (&priv->best_default_route))
+			_notify (self, PROP_GATEWAY);
+		_notify_routes (self);
+	}
+}
 
-void
-nm_ip4_config_reset_routes (NMIP4Config *config)
+static void
+_add_route (NMIP4Config *self,
+            const NMPObject *obj_new,
+            const NMPlatformIP4Route *new,
+            const NMPObject **out_obj_new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	const NMPObject *obj_new_2;
+
+	nm_assert ((!new) != (!obj_new));
+	nm_assert (!new || _route_valid (new));
+	nm_assert (!obj_new || _route_valid (NMP_OBJECT_CAST_IP4_ROUTE (obj_new)));
+
+	if (_nm_ip_config_add_obj (priv->multi_idx,
+	                           &priv->idx_ip4_routes_,
+	                           priv->ifindex,
+	                           obj_new,
+	                           (const NMPlatformObject *) new,
+	                           TRUE,
+	                           FALSE,
+	                           &obj_old,
+	                           &obj_new_2)) {
+		gboolean changed_default_route = FALSE;
+
+		if (   priv->best_default_route == obj_old
+		    && obj_old != obj_new_2) {
+			changed_default_route = TRUE;
+			nm_clear_nmp_object (&priv->best_default_route);
+		}
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_new_2));
+		if (_nm_ip_config_best_default_route_merge (&priv->best_default_route, obj_new_2))
+			changed_default_route = TRUE;
 
-	if (priv->routes->len != 0) {
-		g_array_set_size (priv->routes, 0);
-		_notify (config, PROP_ROUTE_DATA);
-		_notify (config, PROP_ROUTES);
-	}
+		if (changed_default_route)
+			_notify (self, PROP_GATEWAY);
+		_notify_routes (self);
+	} else
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_new_2));
 }
 
 /**
  * nm_ip4_config_add_route:
- * @config: the #NMIP4Config
- * @new: the new route to add to @config
+ * @self: the #NMIP4Config
+ * @new: the new route to add to @self
+ * @out_obj_new: (allow-none): (out): the added route object. Must be unrefed
+ *   by caller.
  *
- * Adds the new route to @config.  If a route with the same basic properties
- * (network, prefix) already exists in @config, it is overwritten including the
+ * Adds the new route to @self.  If a route with the same basic properties
+ * (network, prefix) already exists in @self, it is overwritten including the
  * gateway and metric of @new.  The source is also overwritten by the source
  * from @new if that source is higher priority.
  */
 void
-nm_ip4_config_add_route (NMIP4Config *config, const NMPlatformIP4Route *new)
+nm_ip4_config_add_route (NMIP4Config *self,
+                         const NMPlatformIP4Route *new,
+                         const NMPObject **out_obj_new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	NMIPConfigSource old_source;
-	int i;
-
-	g_return_if_fail (new != NULL);
-	g_return_if_fail (new->plen > 0 && new->plen <= 32);
-	g_return_if_fail (priv->ifindex > 0);
-
-	for (i = 0; i < priv->routes->len; i++ ) {
-		NMPlatformIP4Route *item = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
-		if (routes_are_duplicate (item, new, FALSE)) {
-			if (nm_platform_ip4_route_cmp (item, new) == 0)
-				return;
-			old_source = item->rt_source;
-			memcpy (item, new, sizeof (*item));
-			/* Restore highest priority source */
-			item->rt_source = MAX (old_source, new->rt_source);
-			item->ifindex = priv->ifindex;
-			goto NOTIFY;
-		}
-	}
+	g_return_if_fail (self);
+	g_return_if_fail (new);
+	g_return_if_fail (new->plen <= 32);
+	g_return_if_fail (NM_IP4_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
-	g_array_append_val (priv->routes, *new);
-	g_array_index (priv->routes, NMPlatformIP4Route, priv->routes->len - 1).ifindex = priv->ifindex;
-NOTIFY:
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ROUTES);
+	_add_route (self, NULL, new, out_obj_new);
 }
 
 void
-nm_ip4_config_del_route (NMIP4Config *config, guint i)
+_nmtst_ip4_config_del_route (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	g_return_if_fail (i < priv->routes->len);
+	const NMPlatformIP4Route *r;
 
-	g_array_remove_index (priv->routes, i);
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ROUTES);
+	r = _nmtst_ip4_config_get_route (self, i);
+	if (!nm_ip4_config_nmpobj_remove (self,
+	                                  NMP_OBJECT_UP_CAST (r)))
+		g_assert_not_reached ();
 }
 
 guint
-nm_ip4_config_get_num_routes (const NMIP4Config *config)
+nm_ip4_config_get_num_routes (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMDedupMultiHeadEntry *head_entry;
 
-	return priv->routes->len;
+	head_entry = nm_ip4_config_lookup_routes (self);
+	nm_assert (!head_entry || head_entry->len == c_list_length (&head_entry->lst_entries_head));
+	return head_entry ? head_entry->len : 0;
 }
 
 const NMPlatformIP4Route *
-nm_ip4_config_get_route (const NMIP4Config *config, guint i)
+_nmtst_ip4_config_get_route (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP4Route *r = NULL;
+	guint j;
 
-	return &g_array_index (priv->routes, NMPlatformIP4Route, i);
+	j = 0;
+	nm_ip_config_iter_ip4_route_for_each (&iter, self, &r) {
+		if (i == j)
+			return r;
+		j++;
+	}
+	g_return_val_if_reached (NULL);
 }
 
 const NMPlatformIP4Route *
-nm_ip4_config_get_direct_route_for_host (const NMIP4Config *config, guint32 host)
+nm_ip4_config_get_direct_route_for_host (const NMIP4Config *self,
+                                         in_addr_t host,
+                                         guint32 route_table)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-	guint i;
-	NMPlatformIP4Route *best_route = NULL;
+	const NMPlatformIP4Route *best_route = NULL;
+	const NMPlatformIP4Route *item;
+	NMDedupMultiIter ipconf_iter;
 
 	g_return_val_if_fail (host, NULL);
 
-	for (i = 0; i < priv->routes->len; i++) {
-		NMPlatformIP4Route *item = &g_array_index (priv->routes, NMPlatformIP4Route, i);
-
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &item) {
 		if (item->gateway != 0)
 			continue;
 
 		if (best_route && best_route->plen > item->plen)
 			continue;
 
+		if (nm_platform_route_table_uncoerce (item->table_coerced, TRUE) != route_table)
+			continue;
+
 		if (nm_utils_ip4_address_clear_host_address (host, item->plen) != nm_utils_ip4_address_clear_host_address (item->network, item->plen))
 			continue;
 
@@ -1715,27 +2157,26 @@ nm_ip4_config_get_direct_route_for_host (const NMIP4Config *config, guint32 host
 
 		best_route = item;
 	}
-
 	return best_route;
 }
 
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_nameservers (NMIP4Config *config)
+nm_ip4_config_reset_nameservers (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->nameservers->len != 0) {
 		g_array_set_size (priv->nameservers, 0);
-		_notify (config, PROP_NAMESERVERS);
+		_notify (self, PROP_NAMESERVERS);
 	}
 }
 
 void
-nm_ip4_config_add_nameserver (NMIP4Config *config, guint32 new)
+nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (new != 0);
@@ -1745,32 +2186,32 @@ nm_ip4_config_add_nameserver (NMIP4Config *config, guint32 new)
 			return;
 
 	g_array_append_val (priv->nameservers, new);
-	_notify (config, PROP_NAMESERVERS);
+	_notify (self, PROP_NAMESERVERS);
 }
 
 void
-nm_ip4_config_del_nameserver (NMIP4Config *config, guint i)
+nm_ip4_config_del_nameserver (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->nameservers->len);
 
 	g_array_remove_index (priv->nameservers, i);
-	_notify (config, PROP_NAMESERVERS);
+	_notify (self, PROP_NAMESERVERS);
 }
 
 guint
-nm_ip4_config_get_num_nameservers (const NMIP4Config *config)
+nm_ip4_config_get_num_nameservers (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->nameservers->len;
 }
 
 guint32
-nm_ip4_config_get_nameserver (const NMIP4Config *config, guint i)
+nm_ip4_config_get_nameserver (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_array_index (priv->nameservers, guint32, i);
 }
@@ -1778,20 +2219,20 @@ nm_ip4_config_get_nameserver (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_domains (NMIP4Config *config)
+nm_ip4_config_reset_domains (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->domains->len != 0) {
 		g_ptr_array_set_size (priv->domains, 0);
-		_notify (config, PROP_DOMAINS);
+		_notify (self, PROP_DOMAINS);
 	}
 }
 
 void
-nm_ip4_config_add_domain (NMIP4Config *config, const char *domain)
+nm_ip4_config_add_domain (NMIP4Config *self, const char *domain)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (domain != NULL);
@@ -1802,32 +2243,32 @@ nm_ip4_config_add_domain (NMIP4Config *config, const char *domain)
 			return;
 
 	g_ptr_array_add (priv->domains, g_strdup (domain));
-	_notify (config, PROP_DOMAINS);
+	_notify (self, PROP_DOMAINS);
 }
 
 void
-nm_ip4_config_del_domain (NMIP4Config *config, guint i)
+nm_ip4_config_del_domain (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->domains->len);
 
 	g_ptr_array_remove_index (priv->domains, i);
-	_notify (config, PROP_DOMAINS);
+	_notify (self, PROP_DOMAINS);
 }
 
 guint
-nm_ip4_config_get_num_domains (const NMIP4Config *config)
+nm_ip4_config_get_num_domains (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->domains->len;
 }
 
 const char *
-nm_ip4_config_get_domain (const NMIP4Config *config, guint i)
+nm_ip4_config_get_domain (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->domains, i);
 }
@@ -1835,20 +2276,20 @@ nm_ip4_config_get_domain (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_searches (NMIP4Config *config)
+nm_ip4_config_reset_searches (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->searches->len != 0) {
 		g_ptr_array_set_size (priv->searches, 0);
-		_notify (config, PROP_SEARCHES);
+		_notify (self, PROP_SEARCHES);
 	}
 }
 
 void
-nm_ip4_config_add_search (NMIP4Config *config, const char *new)
+nm_ip4_config_add_search (NMIP4Config *self, const char *new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	char *search;
 	size_t len;
 
@@ -1874,32 +2315,32 @@ nm_ip4_config_add_search (NMIP4Config *config, const char *new)
 	}
 
 	g_ptr_array_add (priv->searches, search);
-	_notify (config, PROP_SEARCHES);
+	_notify (self, PROP_SEARCHES);
 }
 
 void
-nm_ip4_config_del_search (NMIP4Config *config, guint i)
+nm_ip4_config_del_search (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->searches->len);
 
 	g_ptr_array_remove_index (priv->searches, i);
-	_notify (config, PROP_SEARCHES);
+	_notify (self, PROP_SEARCHES);
 }
 
 guint
-nm_ip4_config_get_num_searches (const NMIP4Config *config)
+nm_ip4_config_get_num_searches (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->searches->len;
 }
 
 const char *
-nm_ip4_config_get_search (const NMIP4Config *config, guint i)
+nm_ip4_config_get_search (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->searches, i);
 }
@@ -1907,20 +2348,20 @@ nm_ip4_config_get_search (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_dns_options (NMIP4Config *config)
+nm_ip4_config_reset_dns_options (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->dns_options->len != 0) {
 		g_ptr_array_set_size (priv->dns_options, 0);
-		_notify (config, PROP_DNS_OPTIONS);
+		_notify (self, PROP_DNS_OPTIONS);
 	}
 }
 
 void
-nm_ip4_config_add_dns_option (NMIP4Config *config, const char *new)
+nm_ip4_config_add_dns_option (NMIP4Config *self, const char *new)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (new != NULL);
@@ -1931,32 +2372,32 @@ nm_ip4_config_add_dns_option (NMIP4Config *config, const char *new)
 			return;
 
 	g_ptr_array_add (priv->dns_options, g_strdup (new));
-	_notify (config, PROP_DNS_OPTIONS);
+	_notify (self, PROP_DNS_OPTIONS);
 }
 
 void
-nm_ip4_config_del_dns_option(NMIP4Config *config, guint i)
+nm_ip4_config_del_dns_option(NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->dns_options->len);
 
 	g_ptr_array_remove_index (priv->dns_options, i);
-	_notify (config, PROP_DNS_OPTIONS);
+	_notify (self, PROP_DNS_OPTIONS);
 }
 
 guint
-nm_ip4_config_get_num_dns_options (const NMIP4Config *config)
+nm_ip4_config_get_num_dns_options (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->dns_options->len;
 }
 
 const char *
-nm_ip4_config_get_dns_option (const NMIP4Config *config, guint i)
+nm_ip4_config_get_dns_option (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->dns_options, i);
 }
@@ -1964,20 +2405,20 @@ nm_ip4_config_get_dns_option (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_dns_priority (NMIP4Config *config, gint priority)
+nm_ip4_config_set_dns_priority (NMIP4Config *self, gint priority)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priority != priv->dns_priority) {
 		priv->dns_priority = priority;
-		_notify (config, PROP_DNS_PRIORITY);
+		_notify (self, PROP_DNS_PRIORITY);
 	}
 }
 
 gint
-nm_ip4_config_get_dns_priority (const NMIP4Config *config)
+nm_ip4_config_get_dns_priority (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->dns_priority;
 }
@@ -1985,35 +2426,17 @@ nm_ip4_config_get_dns_priority (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_mss (NMIP4Config *config, guint32 mss)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	priv->mss = mss;
-}
-
-guint32
-nm_ip4_config_get_mss (const NMIP4Config *config)
+nm_ip4_config_reset_nis_servers (NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
-
-	return priv->mss;
-}
-
-/*****************************************************************************/
-
-void
-nm_ip4_config_reset_nis_servers (NMIP4Config *config)
-{
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_array_set_size (priv->nis, 0);
 }
 
 void
-nm_ip4_config_add_nis_server (NMIP4Config *config, guint32 nis)
+nm_ip4_config_add_nis_server (NMIP4Config *self, guint32 nis)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	for (i = 0; i < priv->nis->len; i++)
@@ -2024,9 +2447,9 @@ nm_ip4_config_add_nis_server (NMIP4Config *config, guint32 nis)
 }
 
 void
-nm_ip4_config_del_nis_server (NMIP4Config *config, guint i)
+nm_ip4_config_del_nis_server (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->nis->len);
 
@@ -2034,34 +2457,34 @@ nm_ip4_config_del_nis_server (NMIP4Config *config, guint i)
 }
 
 guint
-nm_ip4_config_get_num_nis_servers (const NMIP4Config *config)
+nm_ip4_config_get_num_nis_servers (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->nis->len;
 }
 
 guint32
-nm_ip4_config_get_nis_server (const NMIP4Config *config, guint i)
+nm_ip4_config_get_nis_server (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_array_index (priv->nis, guint32, i);
 }
 
 void
-nm_ip4_config_set_nis_domain (NMIP4Config *config, const char *domain)
+nm_ip4_config_set_nis_domain (NMIP4Config *self, const char *domain)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_free (priv->nis_domain);
 	priv->nis_domain = g_strdup (domain);
 }
 
 const char *
-nm_ip4_config_get_nis_domain (const NMIP4Config *config)
+nm_ip4_config_get_nis_domain (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->nis_domain;
 }
@@ -2069,20 +2492,20 @@ nm_ip4_config_get_nis_domain (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_reset_wins (NMIP4Config *config)
+nm_ip4_config_reset_wins (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (priv->wins->len != 0) {
 		g_array_set_size (priv->wins, 0);
-		_notify (config, PROP_WINS_SERVERS);
+		_notify (self, PROP_WINS_SERVERS);
 	}
 }
 
 void
-nm_ip4_config_add_wins (NMIP4Config *config, guint32 wins)
+nm_ip4_config_add_wins (NMIP4Config *self, guint32 wins)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (wins != 0);
@@ -2092,32 +2515,32 @@ nm_ip4_config_add_wins (NMIP4Config *config, guint32 wins)
 			return;
 
 	g_array_append_val (priv->wins, wins);
-	_notify (config, PROP_WINS_SERVERS);
+	_notify (self, PROP_WINS_SERVERS);
 }
 
 void
-nm_ip4_config_del_wins (NMIP4Config *config, guint i)
+nm_ip4_config_del_wins (NMIP4Config *self, guint i)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->wins->len);
 
 	g_array_remove_index (priv->wins, i);
-	_notify (config, PROP_WINS_SERVERS);
+	_notify (self, PROP_WINS_SERVERS);
 }
 
 guint
-nm_ip4_config_get_num_wins (const NMIP4Config *config)
+nm_ip4_config_get_num_wins (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->wins->len;
 }
 
 guint32
-nm_ip4_config_get_wins (const NMIP4Config *config, guint i)
+nm_ip4_config_get_wins (const NMIP4Config *self, guint i)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return g_array_index (priv->wins, guint32, i);
 }
@@ -2125,9 +2548,9 @@ nm_ip4_config_get_wins (const NMIP4Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu, NMIPConfigSource source)
+nm_ip4_config_set_mtu (NMIP4Config *self, guint32 mtu, NMIPConfigSource source)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	if (!mtu)
 		source = NM_IP_CONFIG_SOURCE_UNKNOWN;
@@ -2137,17 +2560,17 @@ nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu, NMIPConfigSource source
 }
 
 guint32
-nm_ip4_config_get_mtu (const NMIP4Config *config)
+nm_ip4_config_get_mtu (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->mtu;
 }
 
 NMIPConfigSource
-nm_ip4_config_get_mtu_source (const NMIP4Config *config)
+nm_ip4_config_get_mtu_source (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->mtu_source;
 }
@@ -2155,23 +2578,102 @@ nm_ip4_config_get_mtu_source (const NMIP4Config *config)
 /*****************************************************************************/
 
 void
-nm_ip4_config_set_metered (NMIP4Config *config, gboolean metered)
+nm_ip4_config_set_metered (NMIP4Config *self, gboolean metered)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	priv->metered = metered;
 }
 
 gboolean
-nm_ip4_config_get_metered (const NMIP4Config *config)
+nm_ip4_config_get_metered (const NMIP4Config *self)
 {
-	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	const NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	return priv->metered;
 }
 
 /*****************************************************************************/
 
+const NMPObject *
+nm_ip4_config_nmpobj_lookup (const NMIP4Config *self, const NMPObject *needle)
+{
+	const NMIP4ConfigPrivate *priv;
+	const NMDedupMultiIdxType *idx_type;
+
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), NULL);
+
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	switch (NMP_OBJECT_GET_TYPE (needle)) {
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+		idx_type = &priv->idx_ip4_addresses;
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		idx_type = &priv->idx_ip4_routes;
+		break;
+	default:
+		g_return_val_if_reached (NULL);
+	}
+
+	return nm_dedup_multi_entry_get_obj (nm_dedup_multi_index_lookup_obj (priv->multi_idx,
+	                                                                      idx_type,
+	                                                                      needle));
+}
+
+gboolean
+nm_ip4_config_nmpobj_remove (NMIP4Config *self,
+                             const NMPObject *needle)
+{
+	NMIP4ConfigPrivate *priv;
+	NMDedupMultiIdxType *idx_type;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	guint n;
+
+	g_return_val_if_fail (NM_IS_IP4_CONFIG (self), FALSE);
+
+	priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	switch (NMP_OBJECT_GET_TYPE (needle)) {
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+		idx_type = &priv->idx_ip4_addresses;
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		idx_type = &priv->idx_ip4_routes;
+		break;
+	default:
+		g_return_val_if_reached (FALSE);
+	}
+
+	n = nm_dedup_multi_index_remove_obj (priv->multi_idx,
+	                                     idx_type,
+	                                     needle,
+	                                     (gconstpointer *) &obj_old);
+	if (n != 1) {
+		nm_assert (n == 0);
+		return FALSE;
+	}
+
+	nm_assert (NMP_OBJECT_GET_TYPE (obj_old) == NMP_OBJECT_GET_TYPE (needle));
+
+	switch (NMP_OBJECT_GET_TYPE (obj_old)) {
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+		_notify_addresses (self);
+		break;
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		if (priv->best_default_route == obj_old) {
+			if (_nm_ip_config_best_default_route_set (&priv->best_default_route,
+			                                          _nm_ip4_config_best_default_route_find (self)))
+				_notify (self, PROP_GATEWAY);
+		}
+		_notify_routes (self);
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 static inline void
 hash_u32 (GChecksum *sum, guint32 n)
 {
@@ -2179,60 +2681,57 @@ hash_u32 (GChecksum *sum, guint32 n)
 }
 
 void
-nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only)
+nm_ip4_config_hash (const NMIP4Config *self, GChecksum *sum, gboolean dns_only)
 {
 	guint i;
 	const char *s;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *route;
 
-	g_return_if_fail (config);
+	g_return_if_fail (self);
 	g_return_if_fail (sum);
 
 	if (!dns_only) {
-		hash_u32 (sum, nm_ip4_config_has_gateway (config));
-		hash_u32 (sum, nm_ip4_config_get_gateway (config));
-
-		for (i = 0; i < nm_ip4_config_get_num_addresses (config); i++) {
-			const NMPlatformIP4Address *address = nm_ip4_config_get_address (config, i);
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, self, &address) {
 			hash_u32 (sum, address->address);
 			hash_u32 (sum, address->plen);
-			hash_u32 (sum, address->peer_address & nm_utils_ip4_prefix_to_netmask (address->plen));
+			hash_u32 (sum, address->peer_address & _nm_utils_ip4_prefix_to_netmask (address->plen));
 		}
 
-		for (i = 0; i < nm_ip4_config_get_num_routes (config); i++) {
-			const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
-
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
 			hash_u32 (sum, route->network);
 			hash_u32 (sum, route->plen);
 			hash_u32 (sum, route->gateway);
 			hash_u32 (sum, route->metric);
 		}
 
-		for (i = 0; i < nm_ip4_config_get_num_nis_servers (config); i++)
-			hash_u32 (sum, nm_ip4_config_get_nis_server (config, i));
+		for (i = 0; i < nm_ip4_config_get_num_nis_servers (self); i++)
+			hash_u32 (sum, nm_ip4_config_get_nis_server (self, i));
 
-		s = nm_ip4_config_get_nis_domain (config);
+		s = nm_ip4_config_get_nis_domain (self);
 		if (s)
 			g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_nameservers (config); i++)
-		hash_u32 (sum, nm_ip4_config_get_nameserver (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_nameservers (self); i++)
+		hash_u32 (sum, nm_ip4_config_get_nameserver (self, i));
 
-	for (i = 0; i < nm_ip4_config_get_num_wins (config); i++)
-		hash_u32 (sum, nm_ip4_config_get_wins (config, i));
+	for (i = 0; i < nm_ip4_config_get_num_wins (self); i++)
+		hash_u32 (sum, nm_ip4_config_get_wins (self, i));
 
-	for (i = 0; i < nm_ip4_config_get_num_domains (config); i++) {
-		s = nm_ip4_config_get_domain (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_domains (self); i++) {
+		s = nm_ip4_config_get_domain (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_searches (config); i++) {
-		s = nm_ip4_config_get_search (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_searches (self); i++) {
+		s = nm_ip4_config_get_search (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_dns_options (config); i++) {
-		s = nm_ip4_config_get_dns_option (config, i);
+	for (i = 0; i < nm_ip4_config_get_num_dns_options (self); i++) {
+		s = nm_ip4_config_get_dns_option (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 }
@@ -2283,8 +2782,12 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMIP4Config *config = NM_IP4_CONFIG (object);
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4Config *self = NM_IP4_CONFIG (object);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP4Route *route;
+	GVariantBuilder builder_data, builder_legacy;
 
 	switch (prop_id) {
 	case PROP_IFINDEX:
@@ -2292,25 +2795,32 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_ADDRESS_DATA:
 	case PROP_ADDRESSES:
-		{
-			GVariantBuilder array_builder, addr_builder;
-			gs_unref_array GArray *new = NULL;
-			guint naddr, i;
+		nm_assert (!!priv->address_data_variant == !!priv->addresses_variant);
 
-			g_return_if_fail (!!priv->address_data_variant == !!priv->addresses_variant);
+		if (priv->address_data_variant)
+			goto out_addresses_cached;
 
-			if (priv->address_data_variant)
-				goto return_cached;
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
+		g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("aau"));
 
-			naddr = nm_ip4_config_get_num_addresses (config);
-			new = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformIP4Address), naddr);
-			g_array_append_vals (new, priv->addresses->data, priv->addresses->len);
-			g_array_sort (new, _addresses_sort_cmp);
+		head_entry = nm_ip4_config_lookup_addresses (self);
+		if (head_entry) {
+			gs_free const NMPObject **addresses = NULL;
+			guint naddr, i;
+
+			addresses = (const NMPObject **) nm_dedup_multi_objs_to_array_head (head_entry, NULL, NULL, &naddr);
+			nm_assert (addresses && naddr);
+
+			g_qsort_with_data (addresses,
+			                   naddr,
+			                   sizeof (addresses[0]),
+			                   _addresses_sort_cmp,
+			                   NULL);
 
 			/* Build address data variant */
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
 			for (i = 0; i < naddr; i++) {
-				const NMPlatformIP4Address *address = &g_array_index (new, NMPlatformIP4Address, i);
+				GVariantBuilder addr_builder;
+				const NMPlatformIP4Address *address = NMP_OBJECT_CAST_IP4_ADDRESS (addresses[i]);
 
 				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
 				g_variant_builder_add (&addr_builder, "{sv}",
@@ -2331,98 +2841,105 @@ get_property (GObject *object, guint prop_id,
 					                       g_variant_new_string (address->label));
 				}
 
-				g_variant_builder_add (&array_builder, "a{sv}", &addr_builder);
+				g_variant_builder_add (&builder_data, "a{sv}", &addr_builder);
+
+				{
+					const guint32 dbus_addr[3] = {
+					    address->address,
+					    address->plen,
+					    (   i == 0
+					     && priv->best_default_route)
+					       ? NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway
+					       : (guint32) 0,
+					};
+
+					g_variant_builder_add (&builder_legacy, "@au",
+					                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
+					                                                  dbus_addr, 3, sizeof (guint32)));
+				}
 			}
-			priv->address_data_variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
-
-			/* Build addresses variant */
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aau"));
-			for (i = 0; i < naddr; i++) {
-				const NMPlatformIP4Address *address = &g_array_index (new, NMPlatformIP4Address, i);
-				guint32 dbus_addr[3];
+		}
 
-				dbus_addr[0] = address->address;
-				dbus_addr[1] = address->plen;
-				dbus_addr[2] = i == 0 ? priv->gateway : 0;
+		priv->address_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
+		priv->addresses_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
 
-				g_variant_builder_add (&array_builder, "@au",
-				                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
-				                                                  dbus_addr, 3, sizeof (guint32)));
-			}
-			priv->addresses_variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
-
-return_cached:
-			g_value_set_variant (value,
-			                     prop_id == PROP_ADDRESS_DATA ?
-			                     priv->address_data_variant :
-			                     priv->addresses_variant);
-		}
+out_addresses_cached:
+		g_value_set_variant (value,
+		                     prop_id == PROP_ADDRESS_DATA ?
+		                     priv->address_data_variant :
+		                     priv->addresses_variant);
 		break;
 	case PROP_ROUTE_DATA:
-		{
-			GVariantBuilder array_builder, route_builder;
-			guint nroutes = nm_ip4_config_get_num_routes (config);
-			guint i;
+	case PROP_ROUTES:
+		nm_assert (!!priv->route_data_variant == !!priv->routes_variant);
 
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
-			for (i = 0; i < nroutes; i++) {
-				const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
+		if (priv->route_data_variant)
+			goto out_routes_cached;
 
-				g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
-				g_variant_builder_add (&route_builder, "{sv}",
-				                       "dest",
-				                       g_variant_new_string (nm_utils_inet4_ntop (route->network, NULL)));
-				g_variant_builder_add (&route_builder, "{sv}",
-				                       "prefix",
-				                       g_variant_new_uint32 (route->plen));
-				if (route->gateway) {
-					g_variant_builder_add (&route_builder, "{sv}",
-					                       "next-hop",
-					                       g_variant_new_string (nm_utils_inet4_ntop (route->gateway, NULL)));
-				}
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
+		g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("aau"));
+
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, self, &route) {
+			GVariantBuilder route_builder;
+
+			nm_assert (_route_valid (route));
+
+			g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "dest",
+			                       g_variant_new_string (nm_utils_inet4_ntop (route->network, NULL)));
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "prefix",
+			                       g_variant_new_uint32 (route->plen));
+			if (route->gateway) {
 				g_variant_builder_add (&route_builder, "{sv}",
-				                       "metric",
-				                       g_variant_new_uint32 (route->metric));
+				                       "next-hop",
+				                       g_variant_new_string (nm_utils_inet4_ntop (route->gateway, NULL)));
+			}
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "metric",
+			                       g_variant_new_uint32 (route->metric));
 
-				g_variant_builder_add (&array_builder, "a{sv}", &route_builder);
+			if (!nm_platform_route_table_is_main (route->table_coerced)) {
+				g_variant_builder_add (&route_builder, "{sv}",
+				                       "table",
+				                       g_variant_new_uint32 (nm_platform_route_table_uncoerce (route->table_coerced, TRUE)));
 			}
 
-			g_value_take_variant (value, g_variant_builder_end (&array_builder));
-		}
-		break;
-	case PROP_ROUTES:
-		{
-			GVariantBuilder array_builder;
-			guint nroutes = nm_ip4_config_get_num_routes (config);
-			guint i;
-
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aau"));
-			for (i = 0; i < nroutes; i++) {
-				const NMPlatformIP4Route *route = nm_ip4_config_get_route (config, i);
-				guint32 dbus_route[4];
-
-				/* legacy versions of nm_ip4_route_set_prefix() in libnm-util assert that the
-				 * plen is positive. Skip the default routes not to break older clients. */
-				if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
-					continue;
+			g_variant_builder_add (&builder_data, "a{sv}", &route_builder);
 
-				dbus_route[0] = route->network;
-				dbus_route[1] = route->plen;
-				dbus_route[2] = route->gateway;
-				dbus_route[3] = route->metric;
+			/* legacy versions of nm_ip4_route_set_prefix() in libnm-util assert that the
+			 * plen is positive. Skip the default routes not to break older clients. */
+			if (   nm_platform_route_table_is_main (route->table_coerced)
+			    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
+				const guint32 dbus_route[4] = {
+				    route->network,
+				    route->plen,
+				    route->gateway,
+				    route->metric,
+				};
 
-				g_variant_builder_add (&array_builder, "@au",
+				g_variant_builder_add (&builder_legacy, "@au",
 				                       g_variant_new_fixed_array (G_VARIANT_TYPE_UINT32,
 				                                                  dbus_route, 4, sizeof (guint32)));
 			}
-
-			g_value_take_variant (value, g_variant_builder_end (&array_builder));
 		}
+
+		priv->route_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
+		priv->routes_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
+
+out_routes_cached:
+		g_value_set_variant (value,
+		                     prop_id == PROP_ROUTE_DATA ?
+		                     priv->route_data_variant :
+		                     priv->routes_variant);
 		break;
 	case PROP_GATEWAY:
-		if (priv->has_gateway)
-			g_value_set_string (value, nm_utils_inet4_ntop (priv->gateway, NULL));
-		else
+		if (priv->best_default_route) {
+			g_value_set_string (value,
+			                    nm_utils_inet4_ntop (NMP_OBJECT_CAST_IP4_ROUTE (priv->best_default_route)->gateway,
+			                                         NULL));
+		} else
 			g_value_set_string (value, NULL);
 		break;
 	case PROP_NAMESERVERS:
@@ -2467,6 +2984,13 @@ set_property (GObject *object,
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
 	switch (prop_id) {
+	case PROP_MULTI_IDX:
+		/* construct-only */
+		priv->multi_idx = g_value_get_pointer (value);
+		if (!priv->multi_idx)
+			g_return_if_reached ();
+		nm_dedup_multi_index_ref (priv->multi_idx);
+		break;
 	case PROP_IFINDEX:
 		/* construct-only */
 		priv->ifindex = g_value_get_int (value);
@@ -2480,26 +3004,29 @@ set_property (GObject *object,
 /*****************************************************************************/
 
 static void
-nm_ip4_config_init (NMIP4Config *config)
+nm_ip4_config_init (NMIP4Config *self)
 {
-	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (config);
+	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
+
+	nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_addresses,
+	                                        NMP_OBJECT_TYPE_IP4_ADDRESS);
+	nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_routes,
+	                                        NMP_OBJECT_TYPE_IP4_ROUTE);
 
-	priv->addresses = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Address));
-	priv->routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
 	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);
 	priv->dns_options = g_ptr_array_new_with_free_func (g_free);
 	priv->nis = g_array_new (FALSE, TRUE, sizeof (guint32));
 	priv->wins = g_array_new (FALSE, TRUE, sizeof (guint32));
-	priv->route_metric = -1;
 }
 
 NMIP4Config *
-nm_ip4_config_new (int ifindex)
+nm_ip4_config_new (NMDedupMultiIndex *multi_idx, int ifindex)
 {
 	g_return_val_if_fail (ifindex >= -1, NULL);
 	return (NMIP4Config *) g_object_new (NM_TYPE_IP4_CONFIG,
+	                                     NM_IP4_CONFIG_MULTI_IDX, multi_idx,
 	                                     NM_IP4_CONFIG_IFINDEX, ifindex,
 	                                     NULL);
 }
@@ -2510,10 +3037,16 @@ finalize (GObject *object)
 	NMIP4Config *self = NM_IP4_CONFIG (object);
 	NMIP4ConfigPrivate *priv = NM_IP4_CONFIG_GET_PRIVATE (self);
 
+	nm_clear_nmp_object (&priv->best_default_route);
+
+	nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip4_addresses);
+	nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip4_routes);
+
 	nm_clear_g_variant (&priv->address_data_variant);
 	nm_clear_g_variant (&priv->addresses_variant);
-	g_array_unref (priv->addresses);
-	g_array_unref (priv->routes);
+	nm_clear_g_variant (&priv->route_data_variant);
+	nm_clear_g_variant (&priv->routes_variant);
+
 	g_array_unref (priv->nameservers);
 	g_ptr_array_unref (priv->domains);
 	g_ptr_array_unref (priv->searches);
@@ -2523,6 +3056,8 @@ finalize (GObject *object)
 	g_array_unref (priv->wins);
 
 	G_OBJECT_CLASS (nm_ip4_config_parent_class)->finalize (object);
+
+	nm_dedup_multi_index_unref (priv->multi_idx);
 }
 
 static void
@@ -2537,6 +3072,11 @@ nm_ip4_config_class_init (NMIP4ConfigClass *config_class)
 	object_class->set_property = set_property;
 	object_class->finalize = finalize;
 
+	obj_properties[PROP_MULTI_IDX] =
+	    g_param_spec_pointer (NM_IP4_CONFIG_MULTI_IDX, "", "",
+	                            G_PARAM_WRITABLE
+	                          | G_PARAM_CONSTRUCT_ONLY
+	                          | G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_IFINDEX] =
 	    g_param_spec_int (NM_IP4_CONFIG_IFINDEX, "", "",
 	                      -1, G_MAXINT, -1,
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index ceb52ac5..978471df 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -24,6 +24,97 @@
 #include "nm-exported-object.h"
 #include "nm-setting-ip4-config.h"
 
+#include "nm-utils/nm-dedup-multi.h"
+#include "platform/nmp-object.h"
+
+/*****************************************************************************/
+
+typedef struct {
+	NMDedupMultiIdxType parent;
+	NMPObjectType obj_type;
+} NMIPConfigDedupMultiIdxType;
+
+void nm_ip_config_dedup_multi_idx_type_init (NMIPConfigDedupMultiIdxType *idx_type, NMPObjectType obj_type);
+
+/*****************************************************************************/
+
+void nm_ip_config_iter_ip4_address_init (NMDedupMultiIter *iter, const NMIP4Config *self);
+void nm_ip_config_iter_ip4_route_init (NMDedupMultiIter *iter, const NMIP4Config *self);
+
+static inline gboolean
+nm_ip_config_iter_ip4_address_next (NMDedupMultiIter *ipconf_iter, const NMPlatformIP4Address **out_address)
+{
+	gboolean has_next;
+
+	has_next = nm_dedup_multi_iter_next (ipconf_iter);
+	if (out_address)
+		*out_address = has_next ? NMP_OBJECT_CAST_IP4_ADDRESS (ipconf_iter->current->obj) : NULL;
+	return has_next;
+}
+
+static inline gboolean
+nm_ip_config_iter_ip4_route_next (NMDedupMultiIter *ipconf_iter, const NMPlatformIP4Route **out_route)
+{
+	gboolean has_next;
+
+	has_next = nm_dedup_multi_iter_next (ipconf_iter);
+	if (out_route)
+		*out_route = has_next ? NMP_OBJECT_CAST_IP4_ROUTE (ipconf_iter->current->obj) : NULL;
+	return has_next;
+}
+
+#define nm_ip_config_iter_ip4_address_for_each(iter, self, address) \
+    for (nm_ip_config_iter_ip4_address_init ((iter), (self)); \
+         nm_ip_config_iter_ip4_address_next ((iter), (address)); \
+         )
+
+#define nm_ip_config_iter_ip4_route_for_each(iter, self, route) \
+    for (nm_ip_config_iter_ip4_route_init ((iter), (self)); \
+         nm_ip_config_iter_ip4_route_next ((iter), (route)); \
+         )
+
+/*****************************************************************************/
+
+static inline gboolean
+nm_ip_config_best_default_route_is (const NMPObject *obj)
+{
+	const NMPlatformIPRoute *r = NMP_OBJECT_CAST_IP_ROUTE (obj);
+
+	/* return whether @obj is considered a default-route.
+	 *
+	 * NMIP4Config/NMIP6Config tracks the (best) default-route explicitly, because
+	 * at various places we act differently depending on whether there is a default-route
+	 * configured.
+	 *
+	 * Note that this only considers the main routing table. */
+	return    r
+	       && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+	       && nm_platform_route_table_is_main (r->table_coerced);
+}
+
+const NMPObject *_nm_ip_config_best_default_route_find_better (const NMPObject *obj_cur, const NMPObject *obj_cmp);
+gboolean _nm_ip_config_best_default_route_set (const NMPObject **best_default_route, const NMPObject *new_candidate);
+gboolean _nm_ip_config_best_default_route_merge (const NMPObject **best_default_route, const NMPObject *new_candidate);
+
+/*****************************************************************************/
+
+gboolean _nm_ip_config_add_obj (NMDedupMultiIndex *multi_idx,
+                                NMIPConfigDedupMultiIdxType *idx_type,
+                                int ifindex,
+                                const NMPObject *obj_new,
+                                const NMPlatformObject *pl_new,
+                                gboolean merge,
+                                gboolean append_force,
+                                const NMPObject **out_obj_old,
+                                const NMPObject **out_obj_new);
+
+const NMDedupMultiEntry *_nm_ip_config_lookup_ip_route (const NMDedupMultiIndex *multi_idx,
+                                                        const NMIPConfigDedupMultiIdxType *idx_type,
+                                                        const NMPObject *needle,
+                                                        NMPlatformIPRouteCmpType cmp_type);
+
+/*****************************************************************************/
+
 #define NM_TYPE_IP4_CONFIG (nm_ip4_config_get_type ())
 #define NM_IP4_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IP4_CONFIG, NMIP4Config))
 #define NM_IP4_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IP4_CONFIG, NMIP4ConfigClass))
@@ -34,6 +125,7 @@
 typedef struct _NMIP4ConfigClass NMIP4ConfigClass;
 
 /* internal */
+#define NM_IP4_CONFIG_MULTI_IDX "multi-idx"
 #define NM_IP4_CONFIG_IFINDEX "ifindex"
 
 /* public*/
@@ -54,106 +146,125 @@ typedef struct _NMIP4ConfigClass NMIP4ConfigClass;
 GType nm_ip4_config_get_type (void);
 
 
-NMIP4Config * nm_ip4_config_new (int ifindex);
+NMIP4Config * nm_ip4_config_new (NMDedupMultiIndex *multi_idx,
+                                 int ifindex);
 
-int nm_ip4_config_get_ifindex (const NMIP4Config *config);
+int nm_ip4_config_get_ifindex (const NMIP4Config *self);
 
+NMDedupMultiIndex *nm_ip4_config_get_multi_idx (const NMIP4Config *self);
 
-NMIP4Config *nm_ip4_config_capture (NMPlatform *platform, int ifindex, gboolean capture_resolv_conf);
-gboolean nm_ip4_config_commit (const NMIP4Config *config, NMPlatform *platform, NMRouteManager *route_manager, int ifindex, gboolean routes_full_sync, gint64 default_route_metric);
-void nm_ip4_config_merge_setting (NMIP4Config *config, NMSettingIPConfig *setting, guint32 default_route_metric);
-NMSetting *nm_ip4_config_create_setting (const NMIP4Config *config);
+NMIP4Config *nm_ip4_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf);
 
+void nm_ip4_config_add_dependent_routes (NMIP4Config *self,
+                                         guint32 route_table,
+                                         guint32 route_metric,
+                                         GPtrArray **out_ip4_dev_route_blacklist);
 
-void nm_ip4_config_merge (NMIP4Config *dst, const NMIP4Config *src, NMIPConfigMergeFlags merge_flags);
-void nm_ip4_config_subtract (NMIP4Config *dst, const NMIP4Config *src);
-void nm_ip4_config_intersect (NMIP4Config *dst, const NMIP4Config *src);
-gboolean nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes);
-gboolean nm_ip4_config_destination_is_direct (const NMIP4Config *config, guint32 dest, guint8 plen);
-void nm_ip4_config_dump (const NMIP4Config *config, const char *detail);
-
-
-void nm_ip4_config_set_never_default (NMIP4Config *config, gboolean never_default);
-gboolean nm_ip4_config_get_never_default (const NMIP4Config *config);
-void nm_ip4_config_set_gateway (NMIP4Config *config, guint32 gateway);
-void nm_ip4_config_unset_gateway (NMIP4Config *config);
-gboolean nm_ip4_config_has_gateway (const NMIP4Config *config);
-guint32 nm_ip4_config_get_gateway (const NMIP4Config *config);
-gint64 nm_ip4_config_get_route_metric (const NMIP4Config *config);
-
-void nm_ip4_config_reset_addresses (NMIP4Config *config);
-void nm_ip4_config_add_address (NMIP4Config *config, const NMPlatformIP4Address *address);
-void nm_ip4_config_del_address (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_addresses (const NMIP4Config *config);
-const NMPlatformIP4Address *nm_ip4_config_get_address (const NMIP4Config *config, guint i);
-gboolean nm_ip4_config_address_exists (const NMIP4Config *config, const NMPlatformIP4Address *address);
-
-void nm_ip4_config_reset_routes (NMIP4Config *config);
-void nm_ip4_config_add_route (NMIP4Config *config, const NMPlatformIP4Route *route);
-void nm_ip4_config_del_route (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_routes (const NMIP4Config *config);
-const NMPlatformIP4Route *nm_ip4_config_get_route (const NMIP4Config *config, guint i);
-
-const NMPlatformIP4Route *nm_ip4_config_get_direct_route_for_host (const NMIP4Config *config, guint32 host);
-
-void nm_ip4_config_reset_nameservers (NMIP4Config *config);
-void nm_ip4_config_add_nameserver (NMIP4Config *config, guint32 nameserver);
-void nm_ip4_config_del_nameserver (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_nameservers (const NMIP4Config *config);
-guint32 nm_ip4_config_get_nameserver (const NMIP4Config *config, guint i);
-
-void nm_ip4_config_reset_domains (NMIP4Config *config);
-void nm_ip4_config_add_domain (NMIP4Config *config, const char *domain);
-void nm_ip4_config_del_domain (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_domains (const NMIP4Config *config);
-const char * nm_ip4_config_get_domain (const NMIP4Config *config, guint i);
-
-void nm_ip4_config_reset_searches (NMIP4Config *config);
-void nm_ip4_config_add_search (NMIP4Config *config, const char *search);
-void nm_ip4_config_del_search (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_searches (const NMIP4Config *config);
-const char * nm_ip4_config_get_search (const NMIP4Config *config, guint i);
-
-void nm_ip4_config_reset_dns_options (NMIP4Config *config);
-void nm_ip4_config_add_dns_option (NMIP4Config *config, const char *option);
-void nm_ip4_config_del_dns_option (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_dns_options (const NMIP4Config *config);
-const char * nm_ip4_config_get_dns_option (const NMIP4Config *config, guint i);
-
-void nm_ip4_config_set_dns_priority (NMIP4Config *config, gint priority);
-gint nm_ip4_config_get_dns_priority (const NMIP4Config *config);
-
-void nm_ip4_config_set_mss (NMIP4Config *config, guint32 mss);
-guint32 nm_ip4_config_get_mss (const NMIP4Config *config);
-
-void nm_ip4_config_reset_nis_servers (NMIP4Config *config);
-void nm_ip4_config_add_nis_server (NMIP4Config *config, guint32 nis);
-void nm_ip4_config_del_nis_server (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_nis_servers (const NMIP4Config *config);
-guint32 nm_ip4_config_get_nis_server (const NMIP4Config *config, guint i);
-void nm_ip4_config_set_nis_domain (NMIP4Config *config, const char *domain);
-const char * nm_ip4_config_get_nis_domain (const NMIP4Config *config);
-
-void nm_ip4_config_reset_wins (NMIP4Config *config);
-void nm_ip4_config_add_wins (NMIP4Config *config, guint32 wins);
-void nm_ip4_config_del_wins (NMIP4Config *config, guint i);
-guint nm_ip4_config_get_num_wins (const NMIP4Config *config);
-guint32 nm_ip4_config_get_wins (const NMIP4Config *config, guint i);
-
-void nm_ip4_config_set_mtu (NMIP4Config *config, guint32 mtu, NMIPConfigSource source);
-guint32 nm_ip4_config_get_mtu (const NMIP4Config *config);
-NMIPConfigSource nm_ip4_config_get_mtu_source (const NMIP4Config *config);
-
-void nm_ip4_config_set_metered (NMIP4Config *config, gboolean metered);
-gboolean nm_ip4_config_get_metered (const NMIP4Config *config);
-
-void nm_ip4_config_hash (const NMIP4Config *config, GChecksum *sum, gboolean dns_only);
-gboolean nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b);
+gboolean nm_ip4_config_commit (const NMIP4Config *self,
+                               NMPlatform *platform,
+                               NMIPRouteTableSyncMode route_table_sync);
+
+void nm_ip4_config_merge_setting (NMIP4Config *self,
+                                  NMSettingIPConfig *setting,
+                                  guint32 route_table,
+                                  guint32 route_metric);
+NMSetting *nm_ip4_config_create_setting (const NMIP4Config *self);
 
-/*****************************************************************************/
-/* Testing-only functions */
 
-gboolean nm_ip4_config_capture_resolv_conf (GArray *nameservers, GPtrArray *dns_options,
-                                            const char *rc_contents);
+void nm_ip4_config_merge (NMIP4Config *dst,
+                          const NMIP4Config *src,
+                          NMIPConfigMergeFlags merge_flags,
+                          guint32 default_route_metric_penalty);
+void nm_ip4_config_subtract (NMIP4Config *dst,
+                             const NMIP4Config *src,
+                             guint32 default_route_metric_penalty);
+void nm_ip4_config_intersect (NMIP4Config *dst,
+                              const NMIP4Config *src,
+                              guint32 default_route_metric_penalty);
+gboolean nm_ip4_config_replace (NMIP4Config *dst, const NMIP4Config *src, gboolean *relevant_changes);
+void nm_ip4_config_dump (const NMIP4Config *self, const char *detail);
+
+const NMPObject *nm_ip4_config_best_default_route_get (const NMIP4Config *self);
+const NMPObject *_nm_ip4_config_best_default_route_find (const NMIP4Config *self);
+
+in_addr_t nmtst_ip4_config_get_gateway (NMIP4Config *config);
+
+const NMDedupMultiHeadEntry *nm_ip4_config_lookup_addresses (const NMIP4Config *self);
+void nm_ip4_config_reset_addresses (NMIP4Config *self);
+void nm_ip4_config_add_address (NMIP4Config *self, const NMPlatformIP4Address *address);
+void _nmtst_ip4_config_del_address (NMIP4Config *self, guint i);
+guint nm_ip4_config_get_num_addresses (const NMIP4Config *self);
+const NMPlatformIP4Address *nm_ip4_config_get_first_address (const NMIP4Config *self);
+const NMPlatformIP4Address *_nmtst_ip4_config_get_address (const NMIP4Config *self, guint i);
+gboolean nm_ip4_config_address_exists (const NMIP4Config *self, const NMPlatformIP4Address *address);
+
+const NMDedupMultiHeadEntry *nm_ip4_config_lookup_routes (const NMIP4Config *self);
+void nm_ip4_config_reset_routes (NMIP4Config *self);
+void nm_ip4_config_add_route (NMIP4Config *self,
+                              const NMPlatformIP4Route *route,
+                              const NMPObject **out_obj_new);
+void _nmtst_ip4_config_del_route (NMIP4Config *self, guint i);
+guint nm_ip4_config_get_num_routes (const NMIP4Config *self);
+const NMPlatformIP4Route *_nmtst_ip4_config_get_route (const NMIP4Config *self, guint i);
+
+const NMPlatformIP4Route *nm_ip4_config_get_direct_route_for_host (const NMIP4Config *self,
+                                                                   in_addr_t host,
+                                                                   guint32 route_table);
+
+void nm_ip4_config_reset_nameservers (NMIP4Config *self);
+void nm_ip4_config_add_nameserver (NMIP4Config *self, guint32 nameserver);
+void nm_ip4_config_del_nameserver (NMIP4Config *self, guint i);
+guint nm_ip4_config_get_num_nameservers (const NMIP4Config *self);
+guint32 nm_ip4_config_get_nameserver (const NMIP4Config *self, guint i);
+
+void nm_ip4_config_reset_domains (NMIP4Config *self);
+void nm_ip4_config_add_domain (NMIP4Config *self, const char *domain);
+void nm_ip4_config_del_domain (NMIP4Config *self, guint i);
+guint nm_ip4_config_get_num_domains (const NMIP4Config *self);
+const char * nm_ip4_config_get_domain (const NMIP4Config *self, guint i);
+
+void nm_ip4_config_reset_searches (NMIP4Config *self);
+void nm_ip4_config_add_search (NMIP4Config *self, const char *search);
+void nm_ip4_config_del_search (NMIP4Config *self, guint i);
+guint nm_ip4_config_get_num_searches (const NMIP4Config *self);
+const char * nm_ip4_config_get_search (const NMIP4Config *self, guint i);
+
+void nm_ip4_config_reset_dns_options (NMIP4Config *self);
+void nm_ip4_config_add_dns_option (NMIP4Config *self, const char *option);
+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_reset_nis_servers (NMIP4Config *self);
+void nm_ip4_config_add_nis_server (NMIP4Config *self, guint32 nis);
+void nm_ip4_config_del_nis_server (NMIP4Config *self, guint i);
+guint nm_ip4_config_get_num_nis_servers (const NMIP4Config *self);
+guint32 nm_ip4_config_get_nis_server (const NMIP4Config *self, guint i);
+void nm_ip4_config_set_nis_domain (NMIP4Config *self, const char *domain);
+const char * nm_ip4_config_get_nis_domain (const NMIP4Config *self);
+
+void nm_ip4_config_reset_wins (NMIP4Config *self);
+void nm_ip4_config_add_wins (NMIP4Config *self, guint32 wins);
+void nm_ip4_config_del_wins (NMIP4Config *self, guint i);
+guint nm_ip4_config_get_num_wins (const NMIP4Config *self);
+guint32 nm_ip4_config_get_wins (const NMIP4Config *self, guint i);
+
+void nm_ip4_config_set_mtu (NMIP4Config *self, guint32 mtu, NMIPConfigSource source);
+guint32 nm_ip4_config_get_mtu (const NMIP4Config *self);
+NMIPConfigSource nm_ip4_config_get_mtu_source (const NMIP4Config *self);
+
+void nm_ip4_config_set_metered (NMIP4Config *self, gboolean metered);
+gboolean nm_ip4_config_get_metered (const NMIP4Config *self);
+
+const NMPObject *nm_ip4_config_nmpobj_lookup (const NMIP4Config *self,
+                                              const NMPObject *needle);
+gboolean nm_ip4_config_nmpobj_remove (NMIP4Config *self,
+                                      const NMPObject *needle);
+
+void nm_ip4_config_hash (const NMIP4Config *self, GChecksum *sum, gboolean dns_only);
+gboolean nm_ip4_config_equal (const NMIP4Config *a, const NMIP4Config *b);
 
 #endif /* __NETWORKMANAGER_IP4_CONFIG_H__ */
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index af88b21c..3f0bbfe7 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -25,34 +25,60 @@
 
 #include <string.h>
 #include <arpa/inet.h>
+#include <resolv.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-utils/nm-dedup-multi.h"
 
 #include "nm-utils.h"
+#include "platform/nmp-object.h"
 #include "platform/nm-platform.h"
 #include "platform/nm-platform-utils.h"
-#include "nm-route-manager.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
+#include "nm-ip4-config.h"
+#include "ndisc/nm-ndisc.h"
 
 #include "introspection/org.freedesktop.NetworkManager.IP6Config.h"
 
 /*****************************************************************************/
 
+static gboolean
+_route_valid (const NMPlatformIP6Route *r)
+{
+	struct in6_addr n;
+
+	return    r
+	       && r->plen <= 128
+	       && (memcmp (&r->network,
+	                   nm_utils_ip6_address_clear_host_address (&n, &r->network, r->plen),
+	                   sizeof (n)) == 0);
+}
+
+/*****************************************************************************/
+
 typedef struct {
-	bool never_default:1;
-	guint32 mss;
 	int ifindex;
 	int dns_priority;
 	NMSettingIP6ConfigPrivacy privacy;
-	gint64 route_metric;
-	struct in6_addr gateway;
-	GArray *addresses;
-	GArray *routes;
 	GArray *nameservers;
 	GPtrArray *domains;
 	GPtrArray *searches;
 	GPtrArray *dns_options;
 	GVariant *address_data_variant;
 	GVariant *addresses_variant;
+	GVariant *route_data_variant;
+	GVariant *routes_variant;
+	NMDedupMultiIndex *multi_idx;
+	const NMPObject *best_default_route;
+	union {
+		NMIPConfigDedupMultiIdxType idx_ip6_addresses_;
+		NMDedupMultiIdxType idx_ip6_addresses;
+	};
+	union {
+		NMIPConfigDedupMultiIdxType idx_ip6_routes_;
+		NMDedupMultiIdxType idx_ip6_routes;
+	};
 } NMIP6ConfigPrivate;
 
 struct _NMIP6Config {
@@ -69,6 +95,7 @@ G_DEFINE_TYPE (NMIP6Config, nm_ip6_config, NM_TYPE_EXPORTED_OBJECT)
 #define NM_IP6_CONFIG_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMIP6Config, NM_IS_IP6_CONFIG)
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMIP6Config,
+	PROP_MULTI_IDX,
 	PROP_IFINDEX,
 	PROP_ADDRESS_DATA,
 	PROP_ADDRESSES,
@@ -84,114 +111,122 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMIP6Config,
 
 /*****************************************************************************/
 
+static void _add_address (NMIP6Config *self, const NMPObject *obj_new, const NMPlatformIP6Address *new);
+static void _add_route (NMIP6Config *self, const NMPObject *obj_new, const NMPlatformIP6Route *new, const NMPObject **out_obj_new);
+static const NMDedupMultiEntry *_lookup_route (const NMIP6Config *self,
+                                               const NMPObject *needle,
+                                               NMPlatformIPRouteCmpType cmp_type);
+
+/*****************************************************************************/
+
 int
-nm_ip6_config_get_ifindex (const NMIP6Config *config)
+nm_ip6_config_get_ifindex (const NMIP6Config *self)
 {
-	return NM_IP6_CONFIG_GET_PRIVATE (config)->ifindex;
+	return NM_IP6_CONFIG_GET_PRIVATE (self)->ifindex;
+}
+
+NMDedupMultiIndex *
+nm_ip6_config_get_multi_idx (const NMIP6Config *self)
+{
+	return NM_IP6_CONFIG_GET_PRIVATE (self)->multi_idx;
 }
 
 void
-nm_ip6_config_set_privacy (NMIP6Config *config, NMSettingIP6ConfigPrivacy privacy)
+nm_ip6_config_set_privacy (NMIP6Config *self, NMSettingIP6ConfigPrivacy privacy)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	priv->privacy = privacy;
 }
 
 /*****************************************************************************/
 
-static void
-notify_addresses (NMIP6Config *self)
+const NMDedupMultiHeadEntry *
+nm_ip6_config_lookup_addresses (const NMIP6Config *self)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (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);
+	return nm_dedup_multi_index_lookup_head (priv->multi_idx,
+	                                         &priv->idx_ip6_addresses,
+	                                         NULL);
 }
 
-/**
- * nm_ip6_config_capture_resolv_conf():
- * @nameservers: array of struct in6_addr
- * @rc_contents: the contents of a resolv.conf or %NULL to read /etc/resolv.conf
- *
- * Reads all resolv.conf IPv6 nameservers and adds them to @nameservers.
- *
- * Returns: %TRUE if nameservers were added, %FALSE if @nameservers is unchanged
- */
-gboolean
-nm_ip6_config_capture_resolv_conf (GArray *nameservers,
-                                   GPtrArray *dns_options,
-                                   const char *rc_contents)
+void
+nm_ip_config_iter_ip6_address_init (NMDedupMultiIter *ipconf_iter, const NMIP6Config *self)
 {
-	GPtrArray *read_ns, *read_options;
-	guint i, j;
-	gboolean changed = FALSE;
-
-	g_return_val_if_fail (nameservers != NULL, FALSE);
+	g_return_if_fail (NM_IS_IP6_CONFIG (self));
+	nm_dedup_multi_iter_init (ipconf_iter, nm_ip6_config_lookup_addresses (self));
+}
 
-	read_ns = nm_utils_read_resolv_conf_nameservers (rc_contents);
-	if (!read_ns)
-		return FALSE;
+/*****************************************************************************/
 
-	for (i = 0; i < read_ns->len; i++) {
-		const char *s = g_ptr_array_index (read_ns, i);
-		struct in6_addr ns = IN6ADDR_ANY_INIT;
+const NMDedupMultiHeadEntry *
+nm_ip6_config_lookup_routes (const NMIP6Config *self)
+{
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-		if (!inet_pton (AF_INET6, s, (void *) &ns) || IN6_IS_ADDR_UNSPECIFIED (&ns))
-			continue;
+	return nm_dedup_multi_index_lookup_head (priv->multi_idx,
+	                                         &priv->idx_ip6_routes,
+	                                         NULL);
+}
 
-		/* Ignore duplicates */
-		for (j = 0; j < nameservers->len; j++) {
-			struct in6_addr *t = &g_array_index (nameservers, struct in6_addr, j);
+void
+nm_ip_config_iter_ip6_route_init (NMDedupMultiIter *ipconf_iter, const NMIP6Config *self)
+{
+	g_return_if_fail (NM_IS_IP6_CONFIG (self));
+	nm_dedup_multi_iter_init (ipconf_iter, nm_ip6_config_lookup_routes (self));
+}
 
-			if (IN6_ARE_ADDR_EQUAL (t, &ns))
-				break;
-		}
+/*****************************************************************************/
 
-		if (j == nameservers->len) {
-			g_array_append_val (nameservers, ns);
-			changed = TRUE;
-		}
-	}
-	g_ptr_array_unref (read_ns);
+const NMPObject *
+nm_ip6_config_best_default_route_get (const NMIP6Config *self)
+{
+	g_return_val_if_fail (NM_IS_IP6_CONFIG (self), NULL);
 
-	if (dns_options) {
-		read_options = nm_utils_read_resolv_conf_dns_options (rc_contents);
-		if (!read_options)
-			return changed;
+	return NM_IP6_CONFIG_GET_PRIVATE (self)->best_default_route;
+}
 
-		for (i = 0; i < read_options->len; i++) {
-			const char *s = g_ptr_array_index (read_options, i);
+const NMPObject *
+_nm_ip6_config_best_default_route_find (const NMIP6Config *self)
+{
+	NMDedupMultiIter ipconf_iter;
+	const NMPObject *new_best_default_route = NULL;
 
-			if (_nm_utils_dns_option_validate (s, NULL, NULL, TRUE, _nm_utils_dns_option_descs) &&
-				_nm_utils_dns_option_find_idx (dns_options, s) < 0) {
-				g_ptr_array_add (dns_options, g_strdup (s));
-				changed = TRUE;
-			}
-		}
-		g_ptr_array_unref (read_options);
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, NULL) {
+		new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route,
+		                                                                       ipconf_iter.current->obj);
 	}
-
-	return changed;
+	return new_best_default_route;
 }
 
-static gboolean
-addresses_are_duplicate (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
+/*****************************************************************************/
+
+static void
+_notify_addresses (NMIP6Config *self)
 {
-	return IN6_ARE_ADDR_EQUAL (&a->address, &b->address);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (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);
 }
 
-static gboolean
-routes_are_duplicate (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b, gboolean consider_gateway_and_metric)
+static void
+_notify_routes (NMIP6Config *self)
 {
-	return IN6_ARE_ADDR_EQUAL (&a->network, &b->network) && a->plen == b->plen &&
-	       (   !consider_gateway_and_metric
-	        || (   IN6_ARE_ADDR_EQUAL (&a->gateway, &b->gateway)
-	            && nm_utils_ip6_route_metric_normalize (a->metric) == nm_utils_ip6_route_metric_normalize (b->metric)));
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (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);
 }
 
+/*****************************************************************************/
+
 static gint
 _addresses_sort_cmp_get_prio (const struct in6_addr *addr)
 {
@@ -210,13 +245,14 @@ _addresses_sort_cmp_get_prio (const struct in6_addr *addr)
 	return 6;
 }
 
-static gint
-_addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
+static int
+_addresses_sort_cmp (const NMPlatformIP6Address *a1,
+                     const NMPlatformIP6Address *a2,
+                     gboolean prefer_temp)
 {
 	gint p1, p2, c;
 	gboolean perm1, perm2, tent1, tent2;
 	gboolean ipv6_privacy1, ipv6_privacy2;
-	const NMPlatformIP6Address *a1 = a, *a2 = b;
 
 	/* tentative addresses are always sorted back... */
 	/* sort tentative addresses after non-tentative. */
@@ -235,7 +271,6 @@ _addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
 	ipv6_privacy1 = !!(a1->n_ifa_flags & (IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY));
 	ipv6_privacy2 = !!(a2->n_ifa_flags & (IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY));
 	if (ipv6_privacy1 || ipv6_privacy2) {
-		gboolean prefer_temp = ((NMSettingIP6ConfigPrivacy) GPOINTER_TO_INT (user_data)) == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR;
 		gboolean public1 = TRUE, public2 = TRUE;
 
 		if (ipv6_privacy1) {
@@ -270,30 +305,60 @@ _addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
 	return c != 0 ? c : memcmp (a1, a2, sizeof (*a1));
 }
 
+static int
+_addresses_sort_cmp_prop (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	return _addresses_sort_cmp (NMP_OBJECT_CAST_IP6_ADDRESS (*((const NMPObject **) a)),
+	                            NMP_OBJECT_CAST_IP6_ADDRESS (*((const NMPObject **) b)),
+	                            ((NMSettingIP6ConfigPrivacy) GPOINTER_TO_INT (user_data)) == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR);
+}
+
+static int
+sort_captured_addresses (const CList *lst_a, const CList *lst_b, gconstpointer user_data)
+{
+	const NMPlatformIP6Address *addr_a = NMP_OBJECT_CAST_IP6_ADDRESS (c_list_entry (lst_a, NMDedupMultiEntry, lst_entries)->obj);
+	const NMPlatformIP6Address *addr_b = NMP_OBJECT_CAST_IP6_ADDRESS (c_list_entry (lst_b, NMDedupMultiEntry, lst_entries)->obj);
+
+	return _addresses_sort_cmp (addr_a, addr_b,
+	                            ((NMSettingIP6ConfigPrivacy) GPOINTER_TO_INT (user_data)) == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR);
+}
+
 gboolean
-nm_ip6_config_addresses_sort (NMIP6Config *self)
+_nmtst_ip6_config_addresses_sort (NMIP6Config *self)
 {
 	NMIP6ConfigPrivate *priv;
-	size_t data_len = 0;
-	char *data_pre = NULL;
-	gboolean changed;
+	const NMDedupMultiHeadEntry *head_entry;
 
 	g_return_val_if_fail (NM_IS_IP6_CONFIG (self), FALSE);
 
-	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
-	if (priv->addresses->len > 1) {
-		data_len = priv->addresses->len * g_array_get_element_size (priv->addresses);
-		data_pre = g_new (char, data_len);
-		memcpy (data_pre, priv->addresses->data, data_len);
+	head_entry = nm_ip6_config_lookup_addresses (self);
+	if (head_entry && head_entry->len > 1) {
+		gboolean changed;
+		gs_free gconstpointer *addresses_old = NULL;
+		guint naddr, j;
+		NMDedupMultiIter iter;
 
-		g_array_sort_with_data (priv->addresses, _addresses_sort_cmp,
-		                        GINT_TO_POINTER (priv->privacy));
+		priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-		changed = memcmp (data_pre, priv->addresses->data, data_len) != 0;
-		g_free (data_pre);
+		addresses_old = nm_dedup_multi_objs_to_array_head (head_entry, NULL, NULL, &naddr);
+		nm_assert (addresses_old);
+		nm_assert (naddr > 0 && naddr == head_entry->len);
+
+		nm_dedup_multi_head_entry_sort (head_entry,
+		                                sort_captured_addresses,
+		                                GINT_TO_POINTER (priv->privacy));
+
+		changed = FALSE;
+		j = 0;
+		nm_dedup_multi_iter_for_each (&iter, head_entry) {
+			nm_assert (j < naddr);
+			if (iter.current->obj != addresses_old[j++])
+				changed = TRUE;
+		}
+		nm_assert (j == naddr);
 
 		if (changed) {
-			notify_addresses (self);
+			_notify_addresses (self);
 			return TRUE;
 		}
 	}
@@ -301,129 +366,218 @@ nm_ip6_config_addresses_sort (NMIP6Config *self)
 }
 
 NMIP6Config *
-nm_ip6_config_capture (NMPlatform *platform, int ifindex, gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary)
+nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary)
 {
-	NMIP6Config *config;
+	NMIP6Config *self;
 	NMIP6ConfigPrivate *priv;
-	guint i;
-	guint32 lowest_metric = G_MAXUINT32;
-	struct in6_addr old_gateway = IN6ADDR_ANY_INIT;
-	gboolean has_gateway = FALSE;
-	gboolean notify_nameservers = FALSE;
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *plobj = NULL;
+	gboolean has_addresses = FALSE;
+
+	nm_assert (ifindex > 0);
 
 	/* Slaves have no IP configuration */
 	if (nm_platform_link_get_master (platform, ifindex) > 0)
 		return NULL;
 
-	config = nm_ip6_config_new (ifindex);
-	priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	self = nm_ip6_config_new (multi_idx, ifindex);
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-	g_array_unref (priv->addresses);
-	g_array_unref (priv->routes);
+	head_entry = nm_platform_lookup_addrroute (platform,
+	                                           NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                           ifindex);
+	if (head_entry) {
+		nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+			if (!_nm_ip_config_add_obj (priv->multi_idx,
+			                            &priv->idx_ip6_addresses_,
+			                            ifindex,
+			                            plobj,
+			                            NULL,
+			                            FALSE,
+			                            TRUE,
+			                            NULL,
+			                            NULL))
+				nm_assert_not_reached ();
+			has_addresses = TRUE;
+		}
+		head_entry = nm_ip6_config_lookup_addresses (self);
+		nm_assert (head_entry);
+		nm_dedup_multi_head_entry_sort (head_entry,
+		                                sort_captured_addresses,
+		                                GINT_TO_POINTER (use_temporary));
+		_notify_addresses (self);
+	}
 
-	priv->addresses = nm_platform_ip6_address_get_all (platform, ifindex);
-	priv->routes = nm_platform_ip6_route_get_all (platform, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	head_entry = nm_platform_lookup_addrroute (platform,
+	                                           NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                           ifindex);
 
-	/* Extract gateway from default route */
-	old_gateway = priv->gateway;
-	for (i = 0; i < priv->routes->len; ) {
-		const NMPlatformIP6Route *route = &g_array_index (priv->routes, NMPlatformIP6Route, i);
+	nmp_cache_iter_for_each (&iter, head_entry, &plobj)
+		_add_route (self, plobj, NULL, NULL);
 
-		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
-			if (route->metric < lowest_metric) {
-				priv->gateway = route->gateway;
-				lowest_metric = route->metric;
-			}
-			has_gateway = TRUE;
-			/* Remove the default route from the list */
-			g_array_remove_index_fast (priv->routes, i);
-			continue;
+	/* If the interface has the default route, and has IPv6 addresses, capture
+	 * nameservers from /etc/resolv.conf.
+	 */
+	if (   has_addresses
+	    && priv->best_default_route
+	    && capture_resolv_conf) {
+		gs_free char *rc_contents = NULL;
+
+		if (g_file_get_contents (_PATH_RESCONF, &rc_contents, NULL, NULL)) {
+			if (nm_utils_resolve_conf_parse (AF_INET6,
+			                                 rc_contents,
+			                                 priv->nameservers,
+			                                 priv->dns_options))
+				_notify (self, PROP_NAMESERVERS);
 		}
-		i++;
 	}
 
-	/* we detect the route metric based on the default route. All non-default
-	 * routes have their route metrics explicitly set. */
-	priv->route_metric = has_gateway ? (gint64) lowest_metric : (gint64) -1;
+	return self;
+}
 
-	/* If there is a host route to the gateway, ignore that route.  It is
-	 * automatically added by NetworkManager when needed.
-	 */
-	if (has_gateway) {
-		for (i = 0; i < priv->routes->len; i++) {
-			const NMPlatformIP6Route *route = &g_array_index (priv->routes, NMPlatformIP6Route, i);
-
-			if (   route->plen == 128
-			    && IN6_ARE_ADDR_EQUAL (&route->network, &priv->gateway)
-			    && IN6_IS_ADDR_UNSPECIFIED (&route->gateway)) {
-				g_array_remove_index (priv->routes, i);
-				i--;
+void
+nm_ip6_config_add_dependent_routes (NMIP6Config *self,
+                                    guint32 route_table,
+                                    guint32 route_metric)
+{
+	const NMIP6ConfigPrivate *priv;
+	const NMPlatformIP6Address *my_addr;
+	const NMPlatformIP6Route *my_route;
+	int ifindex;
+	NMDedupMultiIter iter;
+
+	g_return_if_fail (NM_IS_IP6_CONFIG (self));
+
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+
+	ifindex = nm_ip6_config_get_ifindex (self);
+	g_return_if_fail (ifindex > 0);
+
+	/* For IPv6 addresses received via SLAAC/autoconf, we explicitly add the
+	 * device-routes (onlink) to NMIP6Config.
+	 *
+	 * For manually added IPv6 routes, add the device routes explicitly. */
+
+	nm_ip_config_iter_ip6_address_for_each (&iter, self, &my_addr) {
+		NMPlatformIP6Route *route;
+		gboolean has_peer;
+		int routes_n, routes_i;
+
+		if (NM_FLAGS_HAS (my_addr->n_ifa_flags, IFA_F_NOPREFIXROUTE))
+			continue;
+
+		has_peer = !IN6_IS_ADDR_UNSPECIFIED (&my_addr->peer_address);
+
+		/* If we have an IPv6 peer, we add two /128 routes
+		 * (unless, both addresses are identical). */
+		routes_n = (   has_peer
+		            && !IN6_ARE_ADDR_EQUAL (&my_addr->address, &my_addr->peer_address))
+		           ? 2 : 1;
+
+		for (routes_i = 0; routes_i < routes_n; routes_i++) {
+			nm_auto_nmpobj NMPObject *r = NULL;
+
+			r = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
+			route = NMP_OBJECT_CAST_IP6_ROUTE (r);
+
+			route->ifindex = ifindex;
+			route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
+			route->table_coerced = nm_platform_route_table_coerce (route_table);
+			route->metric = route_metric;
+
+			if (has_peer) {
+				if (routes_i == 0)
+					route->network = my_addr->address;
+				else
+					route->network = my_addr->peer_address;
+				route->plen = 128;
+			} else {
+				nm_utils_ip6_address_clear_host_address (&route->network, &my_addr->address, my_addr->plen);
+				route->plen = my_addr->plen;
 			}
+
+			nm_platform_ip_route_normalize (AF_INET6, (NMPlatformIPRoute *) route);
+
+			if (_lookup_route (self,
+			                   r,
+			                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID)) {
+				/* we already track this route. Don't add it again. */
+			} else
+				_add_route (self, r, NULL, NULL);
 		}
 	}
 
-	/* If the interface has the default route, and has IPv6 addresses, capture
-	 * nameservers from /etc/resolv.conf.
-	 */
-	if (priv->addresses->len && has_gateway && capture_resolv_conf)
-		notify_nameservers = nm_ip6_config_capture_resolv_conf (priv->nameservers,
-		                                                        priv->dns_options,
-		                                                        NULL);
-
-	g_array_sort_with_data (priv->addresses, _addresses_sort_cmp, GINT_TO_POINTER (use_temporary));
+again:
+	nm_ip_config_iter_ip6_route_for_each (&iter, self, &my_route) {
+		NMPlatformIP6Route rt;
 
-	/* actually, nobody should be connected to the signal, just to be sure, notify */
-	if (notify_nameservers)
-		_notify (config, PROP_NAMESERVERS);
-	_notify (config, PROP_ADDRESS_DATA);
-	_notify (config, PROP_ADDRESSES);
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ROUTES);
-	if (!IN6_ARE_ADDR_EQUAL (&priv->gateway, &old_gateway))
-		_notify (config, PROP_GATEWAY);
+		if (   !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (my_route)
+		    || IN6_IS_ADDR_UNSPECIFIED (&my_route->gateway)
+		    || NM_IS_IP_CONFIG_SOURCE_RTPROT (my_route->rt_source)
+		    || nm_ip6_config_get_direct_route_for_host (self,
+		                                                &my_route->gateway,
+		                                                nm_platform_route_table_uncoerce (my_route->table_coerced, TRUE)))
+			continue;
 
-	return config;
+		rt = *my_route;
+		rt.network = my_route->gateway;
+		rt.plen = 128;
+		rt.gateway = in6addr_any;
+		_add_route (self, NULL, &rt, NULL);
+		/* adding the route might have invalidated the iteration. Start again. */
+		goto again;
+	}
 }
 
 gboolean
-nm_ip6_config_commit (const NMIP6Config *config,
+nm_ip6_config_commit (const NMIP6Config *self,
                       NMPlatform *platform,
-                      NMRouteManager *route_manager,
-                      int ifindex,
-                      gboolean routes_full_sync)
+                      NMIPRouteTableSyncMode route_table_sync,
+                      GPtrArray **out_temporary_not_available)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
-	gboolean success;
+	gs_unref_ptrarray GPtrArray *addresses = NULL;
+	gs_unref_ptrarray GPtrArray *routes = NULL;
+	gs_unref_ptrarray GPtrArray *routes_prune = NULL;
+	int ifindex;
+	gboolean success = TRUE;
+
+	g_return_val_if_fail (NM_IS_IP6_CONFIG (self), FALSE);
 
+	ifindex = nm_ip6_config_get_ifindex (self);
 	g_return_val_if_fail (ifindex > 0, FALSE);
-	g_return_val_if_fail (config != NULL, FALSE);
 
-	/* Addresses */
-	nm_platform_ip6_address_sync (platform, ifindex, priv->addresses, TRUE);
+	addresses = nm_dedup_multi_objs_to_ptr_array_head (nm_ip6_config_lookup_addresses (self),
+	                                                   NULL, NULL);
 
-	/* Routes */
-	{
-		guint i;
-		guint count = nm_ip6_config_get_num_routes (config);
-		GArray *routes = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformIP6Route), count);
-		const NMPlatformIP6Route *route;
-
-		for (i = 0; i < count; i++) {
-			route = nm_ip6_config_get_route (config, i);
-			g_array_append_vals (routes, route, 1);
-		}
+	routes = nm_dedup_multi_objs_to_ptr_array_head (nm_ip6_config_lookup_routes (self),
+	                                                NULL, NULL);
 
-		success = nm_route_manager_ip6_route_sync (route_manager, ifindex, routes, TRUE, routes_full_sync);
-		g_array_unref (routes);
-	}
+	routes_prune = nm_platform_ip_route_get_prune_list (platform,
+	                                                    AF_INET6,
+	                                                    ifindex,
+	                                                    route_table_sync);
+
+	nm_platform_ip6_address_sync (platform, ifindex, addresses, TRUE);
+
+	if (!nm_platform_ip_route_sync (platform,
+	                                AF_INET6,
+	                                ifindex,
+	                                routes,
+	                                routes_prune,
+	                                out_temporary_not_available))
+		success = FALSE;
 
 	return success;
 }
 
 static void
-merge_route_attributes (NMIPRoute *s_route, NMPlatformIP6Route *r)
+merge_route_attributes (NMIPRoute *s_route,
+                        NMPlatformIP6Route *r,
+                        guint32 route_table)
 {
 	GVariant *variant;
+	guint32 u32;
 	struct in6_addr addr;
 
 #define GET_ATTR(name, field, variant_type, type) \
@@ -431,7 +585,12 @@ merge_route_attributes (NMIPRoute *s_route, NMPlatformIP6Route *r)
 	if (variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_ ## variant_type)) \
 		r->field = g_variant_get_ ## type (variant);
 
-	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_TOS,            tos,            BYTE,     byte);
+	variant = nm_ip_route_get_attribute (s_route, NM_IP_ROUTE_ATTRIBUTE_TABLE);
+	u32 =   variant && g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32)
+	      ? g_variant_get_uint32 (variant)
+	      : 0;
+	r->table_coerced = nm_platform_route_table_coerce (u32 ?: (route_table ?: RT_TABLE_MAIN));
+
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_WINDOW,         window,         UINT32,   uint32);
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_CWND,           cwnd,           UINT32,   uint32);
 	GET_ATTR (NM_IP_ROUTE_ATTRIBUTE_INITCWND,       initcwnd,       UINT32,   uint32);
@@ -472,11 +631,15 @@ merge_route_attributes (NMIPRoute *s_route, NMPlatformIP6Route *r)
 }
 
 void
-nm_ip6_config_merge_setting (NMIP6Config *config, NMSettingIPConfig *setting, guint32 default_route_metric)
+nm_ip6_config_merge_setting (NMIP6Config *self,
+                             NMSettingIPConfig *setting,
+                             guint32 route_table,
+                             guint32 route_metric)
 {
 	NMIP6ConfigPrivate *priv;
 	guint naddresses, nroutes, nnameservers, nsearches;
 	const char *gateway_str;
+	struct in6_addr gateway_bin;
 	int i, priority;
 
 	if (!setting)
@@ -484,31 +647,30 @@ nm_ip6_config_merge_setting (NMIP6Config *config, NMSettingIPConfig *setting, gu
 
 	g_return_if_fail (NM_IS_SETTING_IP6_CONFIG (setting));
 
-	priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	naddresses = nm_setting_ip_config_get_num_addresses (setting);
 	nroutes = nm_setting_ip_config_get_num_routes (setting);
 	nnameservers = nm_setting_ip_config_get_num_dns (setting);
 	nsearches = nm_setting_ip_config_get_num_dns_searches (setting);
 
-	g_object_freeze_notify (G_OBJECT (config));
+	g_object_freeze_notify (G_OBJECT (self));
 
 	/* Gateway */
-	if (nm_setting_ip_config_get_never_default (setting))
-		nm_ip6_config_set_never_default (config, TRUE);
-	else if (nm_setting_ip_config_get_ignore_auto_routes (setting))
-		nm_ip6_config_set_never_default (config, FALSE);
-	gateway_str = nm_setting_ip_config_get_gateway (setting);
-	if (gateway_str) {
-		struct in6_addr gateway;
+	if (   !nm_setting_ip_config_get_never_default (setting)
+	    && (gateway_str = nm_setting_ip_config_get_gateway (setting))
+	    && inet_pton (AF_INET6, gateway_str, &gateway_bin) == 1
+	    && !IN6_IS_ADDR_UNSPECIFIED (&gateway_bin)) {
+		const NMPlatformIP6Route r = {
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.gateway = gateway_bin,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric = route_metric,
+		};
 
-		inet_pton (AF_INET6, gateway_str, &gateway);
-		nm_ip6_config_set_gateway (config, &gateway);
+		_add_route (self, NULL, &r, NULL);
 	}
 
-	if (priv->route_metric  == -1)
-		priv->route_metric = nm_setting_ip_config_get_route_metric (setting);
-
 	/* Addresses */
 	for (i = 0; i < naddresses; i++) {
 		NMIPAddress *s_addr = nm_setting_ip_config_get_address (setting, i);
@@ -522,16 +684,21 @@ nm_ip6_config_merge_setting (NMIP6Config *config, NMSettingIPConfig *setting, gu
 		address.preferred = NM_PLATFORM_LIFETIME_PERMANENT;
 		address.addr_source = NM_IP_CONFIG_SOURCE_USER;
 
-		nm_ip6_config_add_address (config, &address);
+		_add_address (self, NULL, &address);
 	}
 
 	/* Routes */
 	if (nm_setting_ip_config_get_ignore_auto_routes (setting))
-		nm_ip6_config_reset_routes (config);
+		nm_ip6_config_reset_routes (self);
 	for (i = 0; i < nroutes; i++) {
 		NMIPRoute *s_route = nm_setting_ip_config_get_route (setting, i);
 		NMPlatformIP6Route route;
 
+		if (nm_ip_route_get_family (s_route) != AF_INET6) {
+			nm_assert_not_reached ();
+			continue;
+		}
+
 		memset (&route, 0, sizeof (route));
 		nm_ip_route_get_dest_binary (s_route, &route.network);
 
@@ -542,73 +709,74 @@ nm_ip6_config_merge_setting (NMIP6Config *config, NMSettingIPConfig *setting, gu
 
 		nm_ip_route_get_next_hop_binary (s_route, &route.gateway);
 		if (nm_ip_route_get_metric (s_route) == -1)
-			route.metric = default_route_metric;
+			route.metric = route_metric;
 		else
 			route.metric = nm_ip_route_get_metric (s_route);
 		route.rt_source = NM_IP_CONFIG_SOURCE_USER;
 
-		merge_route_attributes (s_route, &route);
-		nm_ip6_config_add_route (config, &route);
+		nm_utils_ip6_address_clear_host_address (&route.network, &route.network, route.plen);
+
+		merge_route_attributes (s_route, &route, route_table);
+		_add_route (self, NULL, &route, NULL);
 	}
 
 	/* DNS */
 	if (nm_setting_ip_config_get_ignore_auto_dns (setting)) {
-		nm_ip6_config_reset_nameservers (config);
-		nm_ip6_config_reset_domains (config);
-		nm_ip6_config_reset_searches (config);
+		nm_ip6_config_reset_nameservers (self);
+		nm_ip6_config_reset_domains (self);
+		nm_ip6_config_reset_searches (self);
 	}
 	for (i = 0; i < nnameservers; i++) {
 		 struct in6_addr ip;
 
 		if (inet_pton (AF_INET6, nm_setting_ip_config_get_dns (setting, i), &ip) == 1)
-			nm_ip6_config_add_nameserver (config, &ip);
+			nm_ip6_config_add_nameserver (self, &ip);
 	}
 	for (i = 0; i < nsearches; i++)
-		nm_ip6_config_add_search (config, nm_setting_ip_config_get_dns_search (setting, i));
+		nm_ip6_config_add_search (self, nm_setting_ip_config_get_dns_search (setting, i));
 
 	i = 0;
 	while ((i = nm_setting_ip_config_next_valid_dns_option (setting, i)) >= 0) {
-		nm_ip6_config_add_dns_option (config, nm_setting_ip_config_get_dns_option (setting, i));
+		nm_ip6_config_add_dns_option (self, nm_setting_ip_config_get_dns_option (setting, i));
 		i++;
 	}
 
 	priority = nm_setting_ip_config_get_dns_priority (setting);
 	if (priority)
-		nm_ip6_config_set_dns_priority (config, priority);
+		nm_ip6_config_set_dns_priority (self, priority);
 
-	g_object_thaw_notify (G_OBJECT (config));
+	g_object_thaw_notify (G_OBJECT (self));
 }
 
 NMSetting *
-nm_ip6_config_create_setting (const NMIP6Config *config)
+nm_ip6_config_create_setting (const NMIP6Config *self)
 {
+	const NMIP6ConfigPrivate *priv;
 	NMSettingIPConfig *s_ip6;
-	const struct in6_addr *gateway;
-	guint naddresses, nroutes, nnameservers, nsearches, noptions;
+	guint nnameservers, nsearches, noptions;
 	const char *method = NULL;
 	int i;
-	gint64 route_metric;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *address;
+	const NMPlatformIP6Route *route;
 
 	s_ip6 = NM_SETTING_IP_CONFIG (nm_setting_ip6_config_new ());
 
-	if (!config) {
+	if (!self) {
 		g_object_set (s_ip6,
 		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
 		              NULL);
 		return NM_SETTING (s_ip6);
 	}
 
-	gateway = nm_ip6_config_get_gateway (config);
-	naddresses = nm_ip6_config_get_num_addresses (config);
-	nroutes = nm_ip6_config_get_num_routes (config);
-	nnameservers = nm_ip6_config_get_num_nameservers (config);
-	nsearches = nm_ip6_config_get_num_searches (config);
-	noptions = nm_ip6_config_get_num_dns_options (config);
-	route_metric = nm_ip6_config_get_route_metric (config);
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+
+	nnameservers = nm_ip6_config_get_num_nameservers (self);
+	nsearches = nm_ip6_config_get_num_searches (self);
+	noptions = nm_ip6_config_get_num_dns_options (self);
 
 	/* Addresses */
-	for (i = 0; i < naddresses; i++) {
-		const NMPlatformIP6Address *address = nm_ip6_config_get_address (config, i);
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &address) {
 		NMIPAddress *s_addr;
 
 		/* Ignore link-local address. */
@@ -634,10 +802,12 @@ nm_ip6_config_create_setting (const NMIP6Config *config)
 	}
 
 	/* Gateway */
-	if (   gateway
+	if (   priv->best_default_route
 	    && nm_setting_ip_config_get_num_addresses (s_ip6) > 0) {
 		g_object_set (s_ip6,
-		              NM_SETTING_IP_CONFIG_GATEWAY, nm_utils_inet6_ntop (gateway, NULL),
+		              NM_SETTING_IP_CONFIG_GATEWAY,
+		              nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway,
+		                                   NULL),
 		              NULL);
 	}
 
@@ -647,20 +817,17 @@ nm_ip6_config_create_setting (const NMIP6Config *config)
 
 	g_object_set (s_ip6,
 	              NM_SETTING_IP_CONFIG_METHOD, method,
-	              NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64) route_metric,
 	              NULL);
 
 	/* Routes */
-	for (i = 0; i < nroutes; i++) {
-		const NMPlatformIP6Route *route = nm_ip6_config_get_route (config, i);
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route) {
 		NMIPRoute *s_route;
 
 		/* Ignore link-local route. */
 		if (IN6_IS_ADDR_LINKLOCAL (&route->network))
 			continue;
 
-		/* Ignore default route. */
-		if (!route->plen)
+		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
 			continue;
 
 		/* Ignore routes provided by external sources */
@@ -677,24 +844,24 @@ nm_ip6_config_create_setting (const NMIP6Config *config)
 
 	/* DNS */
 	for (i = 0; i < nnameservers; i++) {
-		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (config, i);
+		const struct in6_addr *nameserver = nm_ip6_config_get_nameserver (self, i);
 
 		nm_setting_ip_config_add_dns (s_ip6, nm_utils_inet6_ntop (nameserver, NULL));
 	}
 	for (i = 0; i < nsearches; i++) {
-		const char *search = nm_ip6_config_get_search (config, i);
+		const char *search = nm_ip6_config_get_search (self, i);
 
 		nm_setting_ip_config_add_dns_search (s_ip6, search);
 	}
 	for (i = 0; i < noptions; i++) {
-		const char *option = nm_ip6_config_get_dns_option (config, i);
+		const char *option = nm_ip6_config_get_dns_option (self, i);
 
 		nm_setting_ip_config_add_dns_option (s_ip6, option);
 	}
 
 	g_object_set (s_ip6,
 	              NM_SETTING_IP_CONFIG_DNS_PRIORITY,
-	              nm_ip6_config_get_dns_priority (config),
+	              nm_ip6_config_get_dns_priority (self),
 	              NULL);
 
 	return NM_SETTING (s_ip6);
@@ -703,11 +870,16 @@ nm_ip6_config_create_setting (const NMIP6Config *config)
 /*****************************************************************************/
 
 void
-nm_ip6_config_merge (NMIP6Config *dst, const NMIP6Config *src, NMIPConfigMergeFlags merge_flags)
+nm_ip6_config_merge (NMIP6Config *dst,
+                     const NMIP6Config *src,
+                     NMIPConfigMergeFlags merge_flags,
+                     guint32 default_route_metric_penalty)
 {
 	NMIP6ConfigPrivate *dst_priv;
 	const NMIP6ConfigPrivate *src_priv;
 	guint32 i;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *address = NULL;
 
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
@@ -718,8 +890,8 @@ nm_ip6_config_merge (NMIP6Config *dst, const NMIP6Config *src, NMIPConfigMergeFl
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip6_config_get_num_addresses (src); i++)
-		nm_ip6_config_add_address (dst, nm_ip6_config_get_address (src, i));
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, src, &address)
+		_add_address (dst, NMP_OBJECT_UP_CAST (address), NULL);
 
 	/* nameservers */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
@@ -727,20 +899,25 @@ nm_ip6_config_merge (NMIP6Config *dst, const NMIP6Config *src, NMIPConfigMergeFl
 			nm_ip6_config_add_nameserver (dst, nm_ip6_config_get_nameserver (src, i));
 	}
 
-	/* default gateway */
-	if (nm_ip6_config_get_gateway (src))
-		nm_ip6_config_set_gateway (dst, nm_ip6_config_get_gateway (src));
-
 	/* routes */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_ROUTES)) {
-		for (i = 0; i < nm_ip6_config_get_num_routes (src); i++)
-			nm_ip6_config_add_route (dst, nm_ip6_config_get_route (src, i));
-	}
+		const NMPlatformIP6Route *r_src;
+
+		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r_src) {
+			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r_src)) {
+				if (NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES))
+					continue;
+				if (default_route_metric_penalty) {
+					NMPlatformIP6Route r = *r_src;
 
-	if (dst_priv->route_metric == -1)
-		dst_priv->route_metric = src_priv->route_metric;
-	else if (src_priv->route_metric != -1)
-		dst_priv->route_metric = MIN (dst_priv->route_metric, src_priv->route_metric);
+					r.metric = nm_utils_ip_route_metric_penalize (AF_INET6, r.metric, default_route_metric_penalty);
+					_add_route (dst, NULL, &r, NULL);
+					continue;
+				}
+			}
+			_add_route (dst, ipconf_iter.current->obj, NULL, NULL);
+		}
+	}
 
 	/* domains */
 	if (!NM_FLAGS_HAS (merge_flags, NM_IP_CONFIG_MERGE_NO_DNS)) {
@@ -760,9 +937,6 @@ nm_ip6_config_merge (NMIP6Config *dst, const NMIP6Config *src, NMIPConfigMergeFl
 			nm_ip6_config_add_dns_option (dst, nm_ip6_config_get_dns_option (src, i));
 	}
 
-	if (nm_ip6_config_get_mss (src))
-		nm_ip6_config_set_mss (dst, nm_ip6_config_get_mss (src));
-
 	/* DNS priority */
 	if (nm_ip6_config_get_dns_priority (src))
 		nm_ip6_config_set_dns_priority (dst, nm_ip6_config_get_dns_priority (src));
@@ -770,45 +944,9 @@ nm_ip6_config_merge (NMIP6Config *dst, const NMIP6Config *src, NMIPConfigMergeFl
 	g_object_thaw_notify (G_OBJECT (dst));
 }
 
-gboolean
-nm_ip6_config_destination_is_direct (const NMIP6Config *config, const struct in6_addr *network, guint8 plen)
-{
-	guint num = nm_ip6_config_get_num_addresses (config);
-	guint i;
-
-	nm_assert (network);
-	nm_assert (plen <= 128);
-
-	for (i = 0; i < num; i++) {
-		const NMPlatformIP6Address *item = nm_ip6_config_get_address (config, i);
-
-		if (   item->plen <= plen
-		    && !NM_FLAGS_HAS (item->n_ifa_flags, IFA_F_NOPREFIXROUTE)
-		    && nm_utils_ip6_address_same_prefix (&item->address, network, item->plen))
-			return TRUE;
-	}
-
-	return FALSE;
-}
-
 /*****************************************************************************/
 
 static int
-_addresses_get_index (const NMIP6Config *self, const NMPlatformIP6Address *addr)
-{
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
-	guint i;
-
-	for (i = 0; i < priv->addresses->len; i++) {
-		const NMPlatformIP6Address *a = &g_array_index (priv->addresses, NMPlatformIP6Address, i);
-
-		if (addresses_are_duplicate (a, addr))
-			return (int) i;
-	}
-	return -1;
-}
-
-static int
 _nameservers_get_index (const NMIP6Config *self, const struct in6_addr *ns)
 {
 	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
@@ -824,21 +962,6 @@ _nameservers_get_index (const NMIP6Config *self, const struct in6_addr *ns)
 }
 
 static int
-_routes_get_index (const NMIP6Config *self, const NMPlatformIP6Route *route)
-{
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
-	guint i;
-
-	for (i = 0; i < priv->routes->len; i++) {
-		const NMPlatformIP6Route *r = &g_array_index (priv->routes, NMPlatformIP6Route, i);
-
-		if (routes_are_duplicate (route, r, FALSE))
-			return (int) i;
-	}
-	return -1;
-}
-
-static int
 _domains_get_index (const NMIP6Config *self, const char *domain)
 {
 	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
@@ -889,27 +1012,45 @@ _dns_options_get_index (const NMIP6Config *self, const char *option)
  * nm_ip6_config_subtract:
  * @dst: config from which to remove everything in @src
  * @src: config to remove from @dst
- *
+  * @default_route_metric_penalty: pretend that on source we applied
+ *   a route penalty on the default-route. It means, for default routes
+ *   we don't remove routes that match exactly, but those with a lower
+ *   metric (with the penalty removed).
+*
  * Removes everything in @src from @dst.
  */
 void
-nm_ip6_config_subtract (NMIP6Config *dst, const NMIP6Config *src)
+nm_ip6_config_subtract (NMIP6Config *dst,
+                        const NMIP6Config *src,
+                        guint32 default_route_metric_penalty)
 {
+	NMIP6ConfigPrivate *dst_priv;
 	guint i;
 	gint idx;
-	const struct in6_addr *dst_tmp, *src_tmp;
+	const NMPlatformIP6Address *a;
+	const NMPlatformIP6Route *r;
+	NMDedupMultiIter ipconf_iter;
+	gboolean changed;
+	gboolean changed_default_route;
 
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
 
+	dst_priv = NM_IP6_CONFIG_GET_PRIVATE (dst);
+
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip6_config_get_num_addresses (src); i++) {
-		idx = _addresses_get_index (dst, nm_ip6_config_get_address (src, i));
-		if (idx >= 0)
-			nm_ip6_config_del_address (dst, idx);
+	changed = FALSE;
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, src, &a) {
+		if (nm_dedup_multi_index_remove_obj (dst_priv->multi_idx,
+		                                     &dst_priv->idx_ip6_addresses,
+		                                     NMP_OBJECT_UP_CAST (a),
+		                                     NULL))
+			changed = TRUE;
 	}
+	if (changed)
+		_notify_addresses (dst);
 
 	/* nameservers */
 	for (i = 0; i < nm_ip6_config_get_num_nameservers (src); i++) {
@@ -918,23 +1059,46 @@ nm_ip6_config_subtract (NMIP6Config *dst, const NMIP6Config *src)
 			nm_ip6_config_del_nameserver (dst, idx);
 	}
 
-	/* default gateway */
-	src_tmp = nm_ip6_config_get_gateway (src);
-	dst_tmp = nm_ip6_config_get_gateway (dst);
-	if (src_tmp && dst_tmp && IN6_ARE_ADDR_EQUAL (src_tmp, dst_tmp))
-		nm_ip6_config_set_gateway (dst, NULL);
-
-	if (!nm_ip6_config_get_num_addresses (dst))
-		nm_ip6_config_set_gateway (dst, NULL);
-
-	/* ignore route_metric */
-
 	/* routes */
-	for (i = 0; i < nm_ip6_config_get_num_routes (src); i++) {
-		idx = _routes_get_index (dst, nm_ip6_config_get_route (src, i));
-		if (idx >= 0)
-			nm_ip6_config_del_route (dst, idx);
+	changed = FALSE;
+	changed_default_route = FALSE;
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, src, &r) {
+		const NMPObject *o_src = NMP_OBJECT_UP_CAST (r);
+		NMPObject o_lookup_copy;
+		const NMPObject *o_lookup;
+		nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    && default_route_metric_penalty) {
+			NMPlatformIP6Route *rr;
+
+			/* the default route was penalized when merging it to the combined ip-config.
+			 * When subtracting the routes, we must re-do that process when comparing
+			 * the routes. */
+			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_src);
+			rr = NMP_OBJECT_CAST_IP6_ROUTE (&o_lookup_copy);
+			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET6, rr->metric, default_route_metric_penalty);
+		} else
+			o_lookup = o_src;
+
+		if (nm_dedup_multi_index_remove_obj (dst_priv->multi_idx,
+		                                     &dst_priv->idx_ip6_routes,
+		                                     o_lookup,
+		                                     (gconstpointer *) &obj_old)) {
+			if (dst_priv->best_default_route == obj_old) {
+				nm_clear_nmp_object (&dst_priv->best_default_route);
+				changed_default_route = TRUE;
+			}
+			changed = TRUE;
+		}
+	}
+	if (changed_default_route) {
+		_nm_ip_config_best_default_route_set (&dst_priv->best_default_route,
+		                                      _nm_ip6_config_best_default_route_find (dst));
+		_notify (dst, PROP_GATEWAY);
 	}
+	if (changed)
+		_notify_routes (dst);
 
 	/* domains */
 	for (i = 0; i < nm_ip6_config_get_num_domains (src); i++) {
@@ -957,9 +1121,6 @@ nm_ip6_config_subtract (NMIP6Config *dst, const NMIP6Config *src)
 			nm_ip6_config_del_dns_option (dst, idx);
 	}
 
-	if (nm_ip6_config_get_mss (src) == nm_ip6_config_get_mss (dst))
-		nm_ip6_config_set_mss (dst, 0);
-
 	/* DNS priority */
 	if (nm_ip6_config_get_dns_priority (src) == nm_ip6_config_get_dns_priority (dst))
 		nm_ip6_config_set_dns_priority (dst, 0);
@@ -968,51 +1129,87 @@ nm_ip6_config_subtract (NMIP6Config *dst, const NMIP6Config *src)
 }
 
 void
-nm_ip6_config_intersect (NMIP6Config *dst, const NMIP6Config *src)
+nm_ip6_config_intersect (NMIP6Config *dst,
+                         const NMIP6Config *src,
+                         guint32 default_route_metric_penalty)
 {
-	guint i;
-	gint idx;
-	const struct in6_addr *dst_tmp, *src_tmp;
+	NMIP6ConfigPrivate *dst_priv;
+	const NMIP6ConfigPrivate *src_priv;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *a;
+	const NMPlatformIP6Route *r;
+	gboolean changed;
+	const NMPObject *new_best_default_route;
 
-	g_return_if_fail (src != NULL);
-	g_return_if_fail (dst != NULL);
+	g_return_if_fail (src);
+	g_return_if_fail (dst);
+
+	dst_priv = NM_IP6_CONFIG_GET_PRIVATE (dst);
+	src_priv = NM_IP6_CONFIG_GET_PRIVATE (src);
 
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
-	for (i = 0; i < nm_ip6_config_get_num_addresses (dst); ) {
-		idx = _addresses_get_index (src, nm_ip6_config_get_address (dst, i));
-		if (idx < 0)
-			nm_ip6_config_del_address (dst, i);
-		else
-			i++;
+	changed = FALSE;
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, dst, &a) {
+		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+		                                     &src_priv->idx_ip6_addresses,
+		                                     NMP_OBJECT_UP_CAST (a)))
+			continue;
+
+		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+		                                       ipconf_iter.current) != 1)
+			nm_assert_not_reached ();
+		changed = TRUE;
 	}
+	if (changed)
+		_notify_addresses (dst);
 
-	/* ignore route_metric */
 	/* ignore nameservers */
 
-	/* default gateway */
-	dst_tmp = nm_ip6_config_get_gateway (dst);
-	if (dst_tmp) {
-		src_tmp = nm_ip6_config_get_gateway (src);
-		if (   !nm_ip6_config_get_num_addresses (dst)
-		    || !src_tmp
-		    || !IN6_ARE_ADDR_EQUAL (src_tmp, dst_tmp))
-			nm_ip6_config_set_gateway (dst, NULL);
-	}
-
 	/* routes */
-	for (i = 0; i < nm_ip6_config_get_num_routes (dst); ) {
-		idx = _routes_get_index (src, nm_ip6_config_get_route (dst, i));
-		if (idx < 0)
-			nm_ip6_config_del_route (dst, i);
-		else
-			i++;
+	changed = FALSE;
+	new_best_default_route = NULL;
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, dst, &r) {
+		const NMPObject *o_dst = NMP_OBJECT_UP_CAST (r);
+		const NMPObject *o_lookup;
+		NMPObject o_lookup_copy;
+
+		if (   NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r)
+		    && default_route_metric_penalty) {
+			NMPlatformIP6Route *rr;
+
+			/* the default route was penalized when merging it to the combined ip-config.
+			 * When intersecting the routes, we must re-do that process when comparing
+			 * the routes. */
+			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_dst);
+			rr = NMP_OBJECT_CAST_IP6_ROUTE (&o_lookup_copy);
+			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET6, rr->metric, default_route_metric_penalty);
+		} else
+			o_lookup = o_dst;
+
+		if (nm_dedup_multi_index_lookup_obj (src_priv->multi_idx,
+		                                     &src_priv->idx_ip6_routes,
+		                                     o_lookup)) {
+			new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, o_dst);
+			continue;
+		}
+
+		if (nm_dedup_multi_index_remove_entry (dst_priv->multi_idx,
+		                                       ipconf_iter.current) != 1)
+			nm_assert_not_reached ();
+		changed = TRUE;
+	}
+	if (_nm_ip_config_best_default_route_set (&dst_priv->best_default_route, new_best_default_route)) {
+		nm_assert (changed);
+		_notify (dst, PROP_GATEWAY);
 	}
+	if (changed)
+		_notify_routes (dst);
 
 	/* ignore domains */
 	/* ignore dns searches */
-	/* ignome dns options */
+	/* ignore dns options */
 
 	g_object_thaw_notify (G_OBJECT (dst));
 }
@@ -1041,8 +1238,9 @@ nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relev
 	guint i, num;
 	NMIP6ConfigPrivate *dst_priv;
 	const NMIP6ConfigPrivate *src_priv;
-	const NMPlatformIP6Address *dst_addr, *src_addr;
-	const NMPlatformIP6Route *dst_route, *src_route;
+	NMDedupMultiIter ipconf_iter_src, ipconf_iter_dst;
+	const NMDedupMultiHeadEntry *head_entry_src;
+	const NMPObject *new_best_default_route;
 
 	g_return_val_if_fail (NM_IS_IP6_CONFIG (src), FALSE);
 	g_return_val_if_fail (NM_IS_IP6_CONFIG (dst), FALSE);
@@ -1055,6 +1253,8 @@ nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relev
 	dst_priv = NM_IP6_CONFIG_GET_PRIVATE (dst);
 	src_priv = NM_IP6_CONFIG_GET_PRIVATE (src);
 
+	g_return_val_if_fail (src_priv->ifindex > 0, FALSE);
+
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* ifindex */
@@ -1063,70 +1263,107 @@ nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relev
 		has_minor_changes = TRUE;
 	}
 
-	/* never_default */
-	if (src_priv->never_default != dst_priv->never_default) {
-		dst_priv->never_default = src_priv->never_default;
-		has_minor_changes = TRUE;
-	}
-
-	/* default gateway */
-	if (!IN6_ARE_ADDR_EQUAL (&src_priv->gateway, &dst_priv->gateway)) {
-		nm_ip6_config_set_gateway (dst, &src_priv->gateway);
-		has_relevant_changes = TRUE;
-	}
-
-	if (src_priv->route_metric != dst_priv->route_metric) {
-		dst_priv->route_metric = src_priv->route_metric;
-		has_minor_changes = TRUE;
-	}
-
 	/* addresses */
-	num = nm_ip6_config_get_num_addresses (src);
-	are_equal = num == nm_ip6_config_get_num_addresses (dst);
-	if (are_equal) {
-		for (i = 0; i < num; i++ ) {
-			if (nm_platform_ip6_address_cmp (src_addr = nm_ip6_config_get_address (src, i),
-			                                 dst_addr = nm_ip6_config_get_address (dst, i))) {
-				are_equal = FALSE;
-				if (   !addresses_are_duplicate (src_addr, dst_addr)
-				    || src_addr->plen != dst_addr->plen
-				    || !IN6_ARE_ADDR_EQUAL (nm_platform_ip6_address_get_peer (src_addr),
-				                            nm_platform_ip6_address_get_peer (dst_addr)))  {
-					has_relevant_changes = TRUE;
-					break;
-				}
+	head_entry_src = nm_ip6_config_lookup_addresses (src);
+	nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
+	nm_ip_config_iter_ip6_address_init (&ipconf_iter_dst, dst);
+	are_equal = TRUE;
+	while (TRUE) {
+		gboolean has;
+		const NMPlatformIP6Address *r_src = NULL;
+		const NMPlatformIP6Address *r_dst = NULL;
+
+		has = nm_ip_config_iter_ip6_address_next (&ipconf_iter_src, &r_src);
+		if (has != nm_ip_config_iter_ip6_address_next (&ipconf_iter_dst, &r_dst)) {
+			are_equal = FALSE;
+			has_relevant_changes = TRUE;
+			break;
+		}
+		if (!has)
+			break;
+
+		if (nm_platform_ip6_address_cmp (r_src, r_dst) != 0) {
+			are_equal = FALSE;
+			if (   !IN6_ARE_ADDR_EQUAL (&r_src->address, &r_dst->address)
+			    || r_src->plen != r_dst->plen
+			    || !IN6_ARE_ADDR_EQUAL (nm_platform_ip6_address_get_peer (r_src),
+			                            nm_platform_ip6_address_get_peer (r_dst))) {
+				has_relevant_changes = TRUE;
+				break;
 			}
 		}
-	} else
-		has_relevant_changes = TRUE;
+	}
 	if (!are_equal) {
-		nm_ip6_config_reset_addresses (dst);
-		for (i = 0; i < num; i++)
-			nm_ip6_config_add_address (dst, nm_ip6_config_get_address (src, i));
 		has_minor_changes = TRUE;
+		nm_dedup_multi_index_dirty_set_idx (dst_priv->multi_idx, &dst_priv->idx_ip6_addresses);
+		nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) {
+			_nm_ip_config_add_obj (dst_priv->multi_idx,
+			                       &dst_priv->idx_ip6_addresses_,
+			                       dst_priv->ifindex,
+			                       ipconf_iter_src.current->obj,
+			                       NULL,
+			                       FALSE,
+			                       TRUE,
+			                       NULL,
+			                       NULL);
+		}
+		nm_dedup_multi_index_dirty_remove_idx (dst_priv->multi_idx, &dst_priv->idx_ip6_addresses, FALSE);
+		_notify_addresses (dst);
 	}
 
 	/* routes */
-	num = nm_ip6_config_get_num_routes (src);
-	are_equal = num == nm_ip6_config_get_num_routes (dst);
-	if (are_equal) {
-		for (i = 0; i < num; i++ ) {
-			if (nm_platform_ip6_route_cmp (src_route = nm_ip6_config_get_route (src, i),
-			                               dst_route = nm_ip6_config_get_route (dst, i))) {
-				are_equal = FALSE;
-				if (!routes_are_duplicate (src_route, dst_route, TRUE)) {
-					has_relevant_changes = TRUE;
-					break;
-				}
+	head_entry_src = nm_ip6_config_lookup_routes (src);
+	nm_dedup_multi_iter_init (&ipconf_iter_src, head_entry_src);
+	nm_ip_config_iter_ip6_route_init (&ipconf_iter_dst, dst);
+	are_equal = TRUE;
+	while (TRUE) {
+		gboolean has;
+		const NMPlatformIP6Route *r_src = NULL;
+		const NMPlatformIP6Route *r_dst = NULL;
+
+		has = nm_ip_config_iter_ip6_route_next (&ipconf_iter_src, &r_src);
+		if (has != nm_ip_config_iter_ip6_route_next (&ipconf_iter_dst, &r_dst)) {
+			are_equal = FALSE;
+			has_relevant_changes = TRUE;
+			break;
+		}
+		if (!has)
+			break;
+
+		if (nm_platform_ip6_route_cmp_full (r_src, r_dst) != 0) {
+			are_equal = FALSE;
+			if (   r_src->plen != r_dst->plen
+			    || !nm_utils_ip6_address_same_prefix (&r_src->network, &r_dst->network, r_src->plen)
+			    || r_src->metric != r_dst->metric
+			    || !IN6_ARE_ADDR_EQUAL (&r_src->gateway, &r_dst->gateway)) {
+				has_relevant_changes = TRUE;
+				break;
 			}
 		}
-	} else
-		has_relevant_changes = TRUE;
+	}
 	if (!are_equal) {
-		nm_ip6_config_reset_routes (dst);
-		for (i = 0; i < num; i++)
-			nm_ip6_config_add_route (dst, nm_ip6_config_get_route (src, i));
 		has_minor_changes = TRUE;
+		new_best_default_route = NULL;
+		nm_dedup_multi_index_dirty_set_idx (dst_priv->multi_idx, &dst_priv->idx_ip6_routes);
+		nm_dedup_multi_iter_for_each (&ipconf_iter_src, head_entry_src) {
+			const NMPObject *o = ipconf_iter_src.current->obj;
+			const NMPObject *obj_new;
+
+			_nm_ip_config_add_obj (dst_priv->multi_idx,
+			                       &dst_priv->idx_ip6_routes_,
+			                       dst_priv->ifindex,
+			                       o,
+			                       NULL,
+			                       FALSE,
+			                       TRUE,
+			                       NULL,
+			                       &obj_new);
+			new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, obj_new);
+		}
+		nm_dedup_multi_index_dirty_remove_idx (dst_priv->multi_idx, &dst_priv->idx_ip6_routes, FALSE);
+		if (_nm_ip_config_best_default_route_set (&dst_priv->best_default_route, new_best_default_route))
+			_notify (dst, PROP_GATEWAY);
+		_notify_routes (dst);
 	}
 
 	/* nameservers */
@@ -1205,12 +1442,6 @@ nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relev
 		has_relevant_changes = TRUE;
 	}
 
-	/* mss */
-	if (src_priv->mss != dst_priv->mss) {
-		nm_ip6_config_set_mss (dst, src_priv->mss);
-		has_minor_changes = TRUE;
-	}
-
 	/* DNS priority */
 	if (src_priv->dns_priority != dst_priv->dns_priority) {
 		nm_ip6_config_set_dns_priority (dst, src_priv->dns_priority);
@@ -1237,227 +1468,238 @@ nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relev
 }
 
 void
-nm_ip6_config_dump (const NMIP6Config *config, const char *detail)
+nm_ip6_config_dump (const NMIP6Config *self, const char *detail)
 {
 	const struct in6_addr *tmp;
 	guint32 i;
 	const char *str;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *address;
+	const NMPlatformIP6Route *route;
 
-	g_return_if_fail (config != NULL);
+	g_return_if_fail (self != NULL);
 
-	g_message ("--------- NMIP6Config %p (%s)", config, detail);
+	g_message ("--------- NMIP6Config %p (%s)", self, detail);
 
-	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (config));
+	str = nm_exported_object_get_path (NM_EXPORTED_OBJECT (self));
 	if (str)
 		g_message ("   path: %s", str);
 
 	/* addresses */
-	for (i = 0; i < nm_ip6_config_get_num_addresses (config); i++)
-		g_message ("      a: %s", nm_platform_ip6_address_to_string (nm_ip6_config_get_address (config, i), NULL, 0));
-
-	/* default gateway */
-	tmp = nm_ip6_config_get_gateway (config);
-	if (tmp)
-		g_message ("     gw: %s", nm_utils_inet6_ntop (tmp, NULL));
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &address)
+		g_message ("      a: %s", nm_platform_ip6_address_to_string (address, NULL, 0));
 
 	/* nameservers */
-	for (i = 0; i < nm_ip6_config_get_num_nameservers (config); i++) {
-		tmp = nm_ip6_config_get_nameserver (config, i);
+	for (i = 0; i < nm_ip6_config_get_num_nameservers (self); i++) {
+		tmp = nm_ip6_config_get_nameserver (self, i);
 		g_message ("     ns: %s", nm_utils_inet6_ntop (tmp, NULL));
 	}
 
 	/* routes */
-	for (i = 0; i < nm_ip6_config_get_num_routes (config); i++)
-		g_message ("     rt: %s", nm_platform_ip6_route_to_string (nm_ip6_config_get_route (config, i), NULL, 0));
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route)
+		g_message ("     rt: %s", nm_platform_ip6_route_to_string (route, NULL, 0));
 
 	/* domains */
-	for (i = 0; i < nm_ip6_config_get_num_domains (config); i++)
-		g_message (" domain: %s", nm_ip6_config_get_domain (config, i));
+	for (i = 0; i < nm_ip6_config_get_num_domains (self); i++)
+		g_message (" domain: %s", nm_ip6_config_get_domain (self, i));
 
 	/* dns searches */
-	for (i = 0; i < nm_ip6_config_get_num_searches (config); i++)
-		g_message (" search: %s", nm_ip6_config_get_search (config, i));
+	for (i = 0; i < nm_ip6_config_get_num_searches (self); i++)
+		g_message (" search: %s", nm_ip6_config_get_search (self, i));
 
 	/* dns options */
-	for (i = 0; i < nm_ip6_config_get_num_dns_options (config); i++)
-		g_message (" dnsopt: %s", nm_ip6_config_get_dns_option (config, i));
+	for (i = 0; i < nm_ip6_config_get_num_dns_options (self); i++)
+		g_message (" dnsopt: %s", nm_ip6_config_get_dns_option (self, i));
 
-	g_message (" dnspri: %d", nm_ip6_config_get_dns_priority (config));
-
-	g_message ("    mss: %"G_GUINT32_FORMAT, nm_ip6_config_get_mss (config));
-	g_message (" n-dflt: %d", nm_ip6_config_get_never_default (config));
+	g_message (" dnspri: %d", nm_ip6_config_get_dns_priority (self));
 }
 
 /*****************************************************************************/
 
 void
-nm_ip6_config_set_never_default (NMIP6Config *config, gboolean never_default)
+nm_ip6_config_reset_addresses_ndisc (NMIP6Config *self,
+                                     const NMNDiscAddress *addresses,
+                                     guint addresses_n,
+                                     guint8 plen,
+                                     guint32 ifa_flags)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
-
-	priv->never_default = never_default;
-}
+	NMIP6ConfigPrivate *priv;
+	guint i;
+	gboolean changed = FALSE;
 
-gboolean
-nm_ip6_config_get_never_default (const NMIP6Config *config)
-{
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	g_return_if_fail (NM_IS_IP6_CONFIG (self));
 
-	return priv->never_default;
-}
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-void
-nm_ip6_config_set_gateway (NMIP6Config *config, const struct in6_addr *gateway)
-{
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	g_return_if_fail (priv->ifindex > 0);
 
-	if (gateway) {
-		if (IN6_ARE_ADDR_EQUAL (&priv->gateway, gateway))
-			return;
-		priv->gateway = *gateway;
-	} else {
-		if (IN6_IS_ADDR_UNSPECIFIED (&priv->gateway))
-			return;
-		memset (&priv->gateway, 0, sizeof (priv->gateway));
+	nm_dedup_multi_index_dirty_set_idx (priv->multi_idx, &priv->idx_ip6_addresses);
+
+	for (i = 0; i < addresses_n; i++) {
+		const NMNDiscAddress *ndisc_addr = &addresses[i];
+		NMPObject obj;
+		NMPlatformIP6Address *a;
+
+		nmp_object_stackinit (&obj, NMP_OBJECT_TYPE_IP6_ADDRESS, NULL);
+		a = NMP_OBJECT_CAST_IP6_ADDRESS (&obj);
+		a->ifindex     = priv->ifindex;
+		a->address     = ndisc_addr->address;
+		a->plen        = plen;
+		a->timestamp   = ndisc_addr->timestamp;
+		a->lifetime    = ndisc_addr->lifetime;
+		a->preferred   = MIN (ndisc_addr->lifetime, ndisc_addr->preferred);
+		a->addr_source = NM_IP_CONFIG_SOURCE_NDISC;
+		a->n_ifa_flags = ifa_flags;
+
+		if (_nm_ip_config_add_obj (priv->multi_idx,
+		                           &priv->idx_ip6_addresses_,
+		                           priv->ifindex,
+		                           &obj,
+		                           NULL,
+		                           FALSE,
+		                           TRUE,
+		                           NULL,
+		                           NULL))
+			changed = TRUE;
 	}
-	_notify (config, PROP_GATEWAY);
-}
 
-const struct in6_addr *
-nm_ip6_config_get_gateway (const NMIP6Config *config)
-{
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	if (nm_dedup_multi_index_dirty_remove_idx (priv->multi_idx, &priv->idx_ip6_addresses, FALSE) > 0)
+		changed = TRUE;
 
-	return IN6_IS_ADDR_UNSPECIFIED (&priv->gateway) ? NULL : &priv->gateway;
+	if (changed)
+		_notify_addresses (self);
 }
 
-gint64
-nm_ip6_config_get_route_metric (const NMIP6Config *config)
+void
+nm_ip6_config_reset_addresses (NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-	return priv->route_metric;
+	if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
+	                                     &priv->idx_ip6_addresses) > 0)
+		_notify_addresses (self);
 }
 
-/*****************************************************************************/
-
-void
-nm_ip6_config_reset_addresses (NMIP6Config *config)
+static void
+_add_address (NMIP6Config *self,
+              const NMPObject *obj_new,
+              const NMPlatformIP6Address *new)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-	if (priv->addresses->len != 0) {
-		g_array_set_size (priv->addresses, 0);
-		notify_addresses (config);
-	}
+	if (_nm_ip_config_add_obj (priv->multi_idx,
+	                           &priv->idx_ip6_addresses_,
+	                           priv->ifindex,
+	                           obj_new,
+	                           (const NMPlatformObject *) new,
+	                           TRUE,
+	                           FALSE,
+	                           NULL,
+	                           NULL))
+		_notify_addresses (self);
 }
 
 /**
  * nm_ip6_config_add_address:
- * @config: the #NMIP6Config
- * @new: the new address to add to @config
+ * @self: the #NMIP6Config
+ * @new: the new address to add to @self
  *
- * Adds the new address to @config.  If an address with the same basic properties
- * (address, prefix) already exists in @config, it is overwritten with the
+ * Adds the new address to @self.  If an address with the same basic properties
+ * (address, prefix) already exists in @self, it is overwritten with the
  * lifetime and preferred of @new.  The source is also overwritten by the source
  * from @new if that source is higher priority.
  */
 void
-nm_ip6_config_add_address (NMIP6Config *config, const NMPlatformIP6Address *new)
+nm_ip6_config_add_address (NMIP6Config *self, const NMPlatformIP6Address *new)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
-	NMPlatformIP6Address item_old;
-	int i;
-
-	g_return_if_fail (new != NULL);
-
-	for (i = 0; i < priv->addresses->len; i++ ) {
-		NMPlatformIP6Address *item = &g_array_index (priv->addresses, NMPlatformIP6Address, i);
-
-		if (addresses_are_duplicate (item, new)) {
-			if (nm_platform_ip6_address_cmp (item, new) == 0)
-				return;
-
-			/* remember the old values. */
-			item_old = *item;
-			/* Copy over old item to get new lifetime, timestamp, preferred */
-			*item = *new;
-
-			/* But restore highest priority source */
-			item->addr_source = MAX (item_old.addr_source, new->addr_source);
-
-			/* for addresses that we read from the kernel, we keep the timestamps as defined
-			 * by the previous source (item_old). The reason is, that the other source configured the lifetimes
-			 * with "what should be" and the kernel values are "what turned out after configuring it".
-			 *
-			 * For other sources, the longer lifetime wins. */
-			if (   (new->addr_source == NM_IP_CONFIG_SOURCE_KERNEL && new->addr_source != item_old.addr_source)
-			    || nm_platform_ip_address_cmp_expiry ((const NMPlatformIPAddress *) &item_old, (const NMPlatformIPAddress *) new) > 0) {
-				item->timestamp = item_old.timestamp;
-				item->lifetime = item_old.lifetime;
-				item->preferred = item_old.preferred;
-			}
-			if (nm_platform_ip6_address_cmp (&item_old, item) == 0)
-				return;
-			goto NOTIFY;
-		}
-	}
+	g_return_if_fail (self);
+	g_return_if_fail (new);
+	g_return_if_fail (new->plen > 0 && new->plen <= 128);
+	g_return_if_fail (NM_IP6_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
-	g_array_append_val (priv->addresses, *new);
-NOTIFY:
-notify_addresses (config);
+	_add_address (self, NULL, new);
 }
 
 void
-nm_ip6_config_del_address (NMIP6Config *config, guint i)
+_nmtst_ip6_config_del_address (NMIP6Config *self, guint i)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMPlatformIP6Address *a;
 
-	g_return_if_fail (i < priv->addresses->len);
+	a = _nmtst_ip6_config_get_address (self, i);
+	if (!nm_ip6_config_nmpobj_remove (self,
+	                                  NMP_OBJECT_UP_CAST (a)))
+		g_assert_not_reached ();
+}
 
-	g_array_remove_index (priv->addresses, i);
+guint
+nm_ip6_config_get_num_addresses (const NMIP6Config *self)
+{
+	const NMDedupMultiHeadEntry *head_entry;
 
-	notify_addresses (config);
+	head_entry = nm_ip6_config_lookup_addresses (self);
+	return head_entry ? head_entry->len : 0;
 }
 
-guint
-nm_ip6_config_get_num_addresses (const NMIP6Config *config)
+const NMPlatformIP6Address *
+nm_ip6_config_get_first_address (const NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP6Address *a = NULL;
 
-	return priv->addresses->len;
+	nm_ip_config_iter_ip6_address_for_each (&iter, self, &a)
+		return a;
+	return NULL;
 }
 
 const NMPlatformIP6Address *
-nm_ip6_config_get_address (const NMIP6Config *config, guint i)
+_nmtst_ip6_config_get_address (const NMIP6Config *self, guint i)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP6Address *a = NULL;
+	guint j;
 
-	return &g_array_index (priv->addresses, NMPlatformIP6Address, i);
+	j = 0;
+	nm_ip_config_iter_ip6_address_for_each (&iter, self, &a) {
+		if (i == j)
+			return a;
+		j++;
+	}
+	g_return_val_if_reached (NULL);
 }
 
-gboolean
-nm_ip6_config_address_exists (const NMIP6Config *config,
-                              const NMPlatformIP6Address *needle)
+const NMPlatformIP6Address *
+nm_ip6_config_lookup_address (const NMIP6Config *self,
+                              const struct in6_addr *addr)
 {
-	return _addresses_get_index (config, needle) >= 0;
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+	NMPObject obj_stack;
+	const NMDedupMultiEntry *entry;
+
+	nmp_object_stackinit_id_ip6_address (&obj_stack,
+	                                     priv->ifindex,
+	                                     addr);
+	entry = nm_dedup_multi_index_lookup_obj (priv->multi_idx,
+	                                         &priv->idx_ip6_addresses,
+	                                         &obj_stack);
+	return entry
+	       ? NMP_OBJECT_CAST_IP6_ADDRESS (entry->obj)
+	       : NULL;
 }
 
 const NMPlatformIP6Address *
-nm_ip6_config_get_address_first_nontentative (const NMIP6Config *config, gboolean linklocal)
+nm_ip6_config_get_address_first_nontentative (const NMIP6Config *self, gboolean linklocal)
 {
 	const NMIP6ConfigPrivate *priv;
-	guint i;
+	const NMPlatformIP6Address *addr;
+	NMDedupMultiIter iter;
 
-	g_return_val_if_fail (NM_IS_IP6_CONFIG (config), NULL);
+	g_return_val_if_fail (NM_IS_IP6_CONFIG (self), NULL);
 
-	priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	linklocal = !!linklocal;
 
-	for (i = 0; i < priv->addresses->len; i++) {
-		const NMPlatformIP6Address *addr = &g_array_index (priv->addresses, NMPlatformIP6Address, i);
-
+	nm_ip_config_iter_ip6_address_for_each (&iter, self, &addr) {
 		if (   ((!!IN6_IS_ADDR_LINKLOCAL (&addr->address)) == linklocal)
 		    && !(addr->n_ifa_flags & IFA_F_TENTATIVE))
 			return addr;
@@ -1482,23 +1724,16 @@ gboolean
 nm_ip6_config_has_any_dad_pending (const NMIP6Config *self,
                                    const NMIP6Config *candidates)
 {
+	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP6Address *addr, *addr_c;
-	guint i, j, num, num_c;
 
-	num = nm_ip6_config_get_num_addresses (self);
-
-	for (i = 0; i < num; i++) {
-		addr = nm_ip6_config_get_address (self, i);
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &addr) {
 		if (   NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_TENTATIVE)
 		    && !NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_DADFAILED)
 		    && !NM_FLAGS_HAS (addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
-
-			num_c = nm_ip6_config_get_num_addresses (candidates);
-
-			for (j = 0; j < num_c; j++) {
-				addr_c = nm_ip6_config_get_address (candidates, j);
-				if (   addresses_are_duplicate (addr, addr_c)
-				    && addr->plen == addr_c->plen)
+			addr_c = nm_ip6_config_lookup_address (candidates, &addr->address);
+			if (addr_c) {
+				if (addr->plen == addr_c->plen)
 					return TRUE;
 			}
 		}
@@ -1509,107 +1744,264 @@ nm_ip6_config_has_any_dad_pending (const NMIP6Config *self,
 
 /*****************************************************************************/
 
+static const NMDedupMultiEntry *
+_lookup_route (const NMIP6Config *self,
+               const NMPObject *needle,
+               NMPlatformIPRouteCmpType cmp_type)
+{
+	const NMIP6ConfigPrivate *priv;
+
+	nm_assert (NM_IS_IP6_CONFIG (self));
+	nm_assert (NMP_OBJECT_GET_TYPE (needle) == NMP_OBJECT_TYPE_IP6_ROUTE);
+
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+
+	return _nm_ip_config_lookup_ip_route (priv->multi_idx,
+	                                      &priv->idx_ip6_routes_,
+	                                      needle,
+	                                      cmp_type);
+}
+
+void
+nm_ip6_config_reset_routes_ndisc (NMIP6Config *self,
+                                  const NMNDiscGateway *gateways,
+                                  guint gateways_n,
+                                  const NMNDiscRoute *routes,
+                                  guint routes_n,
+                                  guint32 route_table,
+                                  guint32 route_metric,
+                                  gboolean kernel_support_rta_pref)
+{
+	NMIP6ConfigPrivate *priv;
+	guint i;
+	gboolean changed = FALSE;
+	const NMPObject *new_best_default_route;
+
+	g_return_if_fail (NM_IS_IP6_CONFIG (self));
+
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+
+	g_return_if_fail (priv->ifindex > 0);
+
+	nm_dedup_multi_index_dirty_set_idx (priv->multi_idx, &priv->idx_ip6_routes);
+
+	new_best_default_route = NULL;
+	for (i = 0; i < routes_n; i++) {
+		const NMNDiscRoute *ndisc_route = &routes[i];
+		NMPObject obj;
+		const NMPObject *obj_new;
+		NMPlatformIP6Route *r;
+
+		nmp_object_stackinit (&obj, NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
+		r = NMP_OBJECT_CAST_IP6_ROUTE (&obj);
+		r->ifindex    = priv->ifindex;
+		r->network    = ndisc_route->network;
+		r->plen       = ndisc_route->plen;
+		r->gateway    = ndisc_route->gateway;
+		r->rt_source  = NM_IP_CONFIG_SOURCE_NDISC;
+		r->table_coerced = nm_platform_route_table_coerce (route_table);
+		r->metric     = route_metric;
+		r->rt_pref    = ndisc_route->preference;
+		nm_assert ((NMIcmpv6RouterPref) r->rt_pref == ndisc_route->preference);
+
+		if (_nm_ip_config_add_obj (priv->multi_idx,
+		                           &priv->idx_ip6_routes_,
+		                           priv->ifindex,
+		                           &obj,
+		                           NULL,
+		                           FALSE,
+		                           TRUE,
+		                           NULL,
+		                           &obj_new))
+			changed = TRUE;
+		new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, obj_new);
+	}
+
+	if (gateways_n) {
+		const NMPObject *obj_new;
+		NMPlatformIP6Route r = {
+			.rt_source     = NM_IP_CONFIG_SOURCE_NDISC,
+			.ifindex       = priv->ifindex,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric        = route_metric,
+		};
+		const NMIcmpv6RouterPref first_pref = gateways[0].preference;
+
+		for (i = 0; i < gateways_n; i++) {
+			r.gateway = gateways[i].address;
+			r.rt_pref = gateways[i].preference;
+			nm_assert ((NMIcmpv6RouterPref) r.rt_pref == gateways[i].preference);
+			if (_nm_ip_config_add_obj (priv->multi_idx,
+			                           &priv->idx_ip6_routes_,
+			                           priv->ifindex,
+			                           NULL,
+			                           (const NMPlatformObject *) &r,
+			                           FALSE,
+			                           TRUE,
+			                           NULL,
+			                           &obj_new))
+				changed = TRUE;
+			new_best_default_route = _nm_ip_config_best_default_route_find_better (new_best_default_route, obj_new);
+
+			if (   first_pref != gateways[i].preference
+			    && !kernel_support_rta_pref) {
+				/* We are unable to configure a router preference. Hence, we skip all gateways
+				 * with a different preference from the first gateway. Note, that the gateways
+				 * are sorted in order of highest to lowest preference. */
+				break;
+			}
+		}
+	}
+
+	if (nm_dedup_multi_index_dirty_remove_idx (priv->multi_idx, &priv->idx_ip6_routes, FALSE) > 0)
+		changed = TRUE;
+
+	if (_nm_ip_config_best_default_route_set (&priv->best_default_route, new_best_default_route)) {
+		changed = TRUE;
+		_notify (self, PROP_GATEWAY);
+	}
+
+	if (changed)
+		_notify_routes (self);
+}
+
 void
-nm_ip6_config_reset_routes (NMIP6Config *config)
+nm_ip6_config_reset_routes (NMIP6Config *self)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-	if (priv->routes->len != 0) {
-		g_array_set_size (priv->routes, 0);
-		_notify (config, PROP_ROUTE_DATA);
-		_notify (config, PROP_ROUTES);
+	if (nm_dedup_multi_index_remove_idx (priv->multi_idx,
+	                                     &priv->idx_ip6_routes) > 0) {
+		if (nm_clear_nmp_object (&priv->best_default_route))
+			_notify (self, PROP_GATEWAY);
+		_notify_routes (self);
 	}
 }
 
+static void
+_add_route (NMIP6Config *self,
+            const NMPObject *obj_new,
+            const NMPlatformIP6Route *new,
+            const NMPObject **out_obj_new)
+{
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	const NMPObject *obj_new_2;
+
+	nm_assert ((!new) != (!obj_new));
+	nm_assert (!new || _route_valid (new));
+	nm_assert (!obj_new || _route_valid (NMP_OBJECT_CAST_IP6_ROUTE (obj_new)));
+
+	if (_nm_ip_config_add_obj (priv->multi_idx,
+	                           &priv->idx_ip6_routes_,
+	                           priv->ifindex,
+	                           obj_new,
+	                           (const NMPlatformObject *) new,
+	                           TRUE,
+	                           FALSE,
+	                           &obj_old,
+	                           &obj_new_2)) {
+		gboolean changed_default_route = FALSE;
+
+		if (   priv->best_default_route == obj_old
+		    && obj_old != obj_new_2) {
+			changed_default_route = TRUE;
+			nm_clear_nmp_object (&priv->best_default_route);
+		}
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_new_2));
+		if (_nm_ip_config_best_default_route_merge (&priv->best_default_route, obj_new_2))
+			changed_default_route = TRUE;
+
+		if (changed_default_route)
+			_notify (self, PROP_GATEWAY);
+		_notify_routes (self);
+	} else
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_new_2));
+}
+
 /**
  * nm_ip6_config_add_route:
- * @config: the #NMIP6Config
- * @new: the new route to add to @config
+ * @self: the #NMIP6Config
+ * @new: the new route to add to @self
+ * @out_obj_new: (allow-none): (out): the added route object. Must be unrefed
+ *   by caller.
  *
- * Adds the new route to @config.  If a route with the same basic properties
- * (network, prefix) already exists in @config, it is overwritten including the
+ * Adds the new route to @self.  If a route with the same basic properties
+ * (network, prefix) already exists in @self, it is overwritten including the
  * gateway and metric of @new.  The source is also overwritten by the source
  * from @new if that source is higher priority.
  */
 void
-nm_ip6_config_add_route (NMIP6Config *config, const NMPlatformIP6Route *new)
+nm_ip6_config_add_route (NMIP6Config *self,
+                         const NMPlatformIP6Route *new,
+                         const NMPObject **out_obj_new)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
-	NMIPConfigSource old_source;
-	int i;
-
-	g_return_if_fail (new != NULL);
-	g_return_if_fail (new->plen > 0 && new->plen <= 128);
-	g_return_if_fail (priv->ifindex > 0);
+	g_return_if_fail (self);
+	g_return_if_fail (new);
+	g_return_if_fail (new->plen <= 128);
+	g_return_if_fail (NM_IP6_CONFIG_GET_PRIVATE (self)->ifindex > 0);
 
-	for (i = 0; i < priv->routes->len; i++ ) {
-		NMPlatformIP6Route *item = &g_array_index (priv->routes, NMPlatformIP6Route, i);
-
-		if (routes_are_duplicate (item, new, FALSE)) {
-			if (nm_platform_ip6_route_cmp (item, new) == 0)
-				return;
-			old_source = item->rt_source;
-			*item = *new;
-			/* Restore highest priority source */
-			item->rt_source = MAX (old_source, new->rt_source);
-			item->ifindex = priv->ifindex;
-			goto NOTIFY;
-		}
-	}
-
-	g_array_append_val (priv->routes, *new);
-	g_array_index (priv->routes, NMPlatformIP6Route, priv->routes->len - 1).ifindex = priv->ifindex;
-NOTIFY:
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ROUTES);
+	_add_route (self, NULL, new, out_obj_new);
 }
 
 void
-nm_ip6_config_del_route (NMIP6Config *config, guint i)
+_nmtst_ip6_config_del_route (NMIP6Config *self, guint i)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
-
-	g_return_if_fail (i < priv->routes->len);
+	const NMPlatformIP6Route *r;
 
-	g_array_remove_index (priv->routes, i);
-	_notify (config, PROP_ROUTE_DATA);
-	_notify (config, PROP_ROUTES);
+	r = _nmtst_ip6_config_get_route (self, i);
+	if (!nm_ip6_config_nmpobj_remove (self,
+	                                  NMP_OBJECT_UP_CAST (r)))
+		g_assert_not_reached ();
 }
 
 guint
-nm_ip6_config_get_num_routes (const NMIP6Config *config)
+nm_ip6_config_get_num_routes (const NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMDedupMultiHeadEntry *head_entry;
 
-	return priv->routes->len;
+	head_entry = nm_ip6_config_lookup_routes (self);
+	nm_assert (!head_entry || head_entry->len == c_list_length (&head_entry->lst_entries_head));
+	return head_entry ? head_entry->len : 0;
 }
 
 const NMPlatformIP6Route *
-nm_ip6_config_get_route (const NMIP6Config *config, guint i)
+_nmtst_ip6_config_get_route (const NMIP6Config *self, guint i)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMDedupMultiIter iter;
+	const NMPlatformIP6Route *r = NULL;
+	guint j;
 
-	return &g_array_index (priv->routes, NMPlatformIP6Route, i);
+	j = 0;
+	nm_ip_config_iter_ip6_route_for_each (&iter, self, &r) {
+		if (i == j)
+			return r;
+		j++;
+	}
+	g_return_val_if_reached (NULL);
 }
 
 const NMPlatformIP6Route *
-nm_ip6_config_get_direct_route_for_host (const NMIP6Config *config, const struct in6_addr *host)
+nm_ip6_config_get_direct_route_for_host (const NMIP6Config *self,
+                                         const struct in6_addr *host,
+                                         guint32 route_table)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
-	guint i;
-	NMPlatformIP6Route *best_route = NULL;
+	const NMPlatformIP6Route *best_route = NULL;
+	const NMPlatformIP6Route *item;
+	NMDedupMultiIter ipconf_iter;
 
 	g_return_val_if_fail (host && !IN6_IS_ADDR_UNSPECIFIED (host), NULL);
 
-	for (i = 0; i < priv->routes->len; i++) {
-		NMPlatformIP6Route *item = &g_array_index (priv->routes, NMPlatformIP6Route, i);
-
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &item) {
 		if (!IN6_IS_ADDR_UNSPECIFIED (&item->gateway))
 			continue;
 
 		if (best_route && best_route->plen > item->plen)
 			continue;
 
+		if (nm_platform_route_table_uncoerce (item->table_coerced, TRUE) != route_table)
+			continue;
+
 		if (!nm_utils_ip6_address_same_prefix (host, &item->network, item->plen))
 			continue;
 
@@ -1619,23 +2011,20 @@ nm_ip6_config_get_direct_route_for_host (const NMIP6Config *config, const struct
 
 		best_route = item;
 	}
-
 	return best_route;
 }
 
 const NMPlatformIP6Address *
-nm_ip6_config_get_subnet_for_host (const NMIP6Config *config, const struct in6_addr *host)
+nm_ip6_config_get_subnet_for_host (const NMIP6Config *self, const struct in6_addr *host)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
-	guint i;
-	NMPlatformIP6Address *subnet = NULL;
+	NMDedupMultiIter iter;
+	const NMPlatformIP6Address *item;
+	const NMPlatformIP6Address *subnet = NULL;
 	struct in6_addr subnet2, host2;
 
 	g_return_val_if_fail (host && !IN6_IS_ADDR_UNSPECIFIED (host), NULL);
 
-	for (i = 0; i < priv->addresses->len; i++) {
-		NMPlatformIP6Address *item = &g_array_index (priv->addresses, NMPlatformIP6Address, i);
-
+	nm_ip_config_iter_ip6_address_for_each (&iter, self, &item) {
 		if (subnet && subnet->plen >= item->plen)
 			continue;
 
@@ -1653,20 +2042,20 @@ nm_ip6_config_get_subnet_for_host (const NMIP6Config *config, const struct in6_a
 /*****************************************************************************/
 
 void
-nm_ip6_config_reset_nameservers (NMIP6Config *config)
+nm_ip6_config_reset_nameservers (NMIP6Config *self)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	if (priv->nameservers->len != 0) {
 		g_array_set_size (priv->nameservers, 0);
-		_notify (config, PROP_NAMESERVERS);
+		_notify (self, PROP_NAMESERVERS);
 	}
 }
 
 void
-nm_ip6_config_add_nameserver (NMIP6Config *config, const struct in6_addr *new)
+nm_ip6_config_add_nameserver (NMIP6Config *self, const struct in6_addr *new)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (new != NULL);
@@ -1676,32 +2065,32 @@ nm_ip6_config_add_nameserver (NMIP6Config *config, const struct in6_addr *new)
 			return;
 
 	g_array_append_val (priv->nameservers, *new);
-	_notify (config, PROP_NAMESERVERS);
+	_notify (self, PROP_NAMESERVERS);
 }
 
 void
-nm_ip6_config_del_nameserver (NMIP6Config *config, guint i)
+nm_ip6_config_del_nameserver (NMIP6Config *self, guint i)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->nameservers->len);
 
 	g_array_remove_index (priv->nameservers, i);
-	_notify (config, PROP_NAMESERVERS);
+	_notify (self, PROP_NAMESERVERS);
 }
 
 guint
-nm_ip6_config_get_num_nameservers (const NMIP6Config *config)
+nm_ip6_config_get_num_nameservers (const NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return priv->nameservers->len;
 }
 
 const struct in6_addr *
-nm_ip6_config_get_nameserver (const NMIP6Config *config, guint i)
+nm_ip6_config_get_nameserver (const NMIP6Config *self, guint i)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return &g_array_index (priv->nameservers, struct in6_addr, i);
 }
@@ -1709,20 +2098,20 @@ nm_ip6_config_get_nameserver (const NMIP6Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip6_config_reset_domains (NMIP6Config *config)
+nm_ip6_config_reset_domains (NMIP6Config *self)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	if (priv->domains->len != 0) {
 		g_ptr_array_set_size (priv->domains, 0);
-		_notify (config, PROP_DOMAINS);
+		_notify (self, PROP_DOMAINS);
 	}
 }
 
 void
-nm_ip6_config_add_domain (NMIP6Config *config, const char *domain)
+nm_ip6_config_add_domain (NMIP6Config *self, const char *domain)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (domain != NULL);
@@ -1733,32 +2122,32 @@ nm_ip6_config_add_domain (NMIP6Config *config, const char *domain)
 			return;
 
 	g_ptr_array_add (priv->domains, g_strdup (domain));
-	_notify (config, PROP_DOMAINS);
+	_notify (self, PROP_DOMAINS);
 }
 
 void
-nm_ip6_config_del_domain (NMIP6Config *config, guint i)
+nm_ip6_config_del_domain (NMIP6Config *self, guint i)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->domains->len);
 
 	g_ptr_array_remove_index (priv->domains, i);
-	_notify (config, PROP_DOMAINS);
+	_notify (self, PROP_DOMAINS);
 }
 
 guint
-nm_ip6_config_get_num_domains (const NMIP6Config *config)
+nm_ip6_config_get_num_domains (const NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return priv->domains->len;
 }
 
 const char *
-nm_ip6_config_get_domain (const NMIP6Config *config, guint i)
+nm_ip6_config_get_domain (const NMIP6Config *self, guint i)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->domains, i);
 }
@@ -1766,20 +2155,20 @@ nm_ip6_config_get_domain (const NMIP6Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip6_config_reset_searches (NMIP6Config *config)
+nm_ip6_config_reset_searches (NMIP6Config *self)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	if (priv->searches->len != 0) {
 		g_ptr_array_set_size (priv->searches, 0);
-		_notify (config, PROP_SEARCHES);
+		_notify (self, PROP_SEARCHES);
 	}
 }
 
 void
-nm_ip6_config_add_search (NMIP6Config *config, const char *new)
+nm_ip6_config_add_search (NMIP6Config *self, const char *new)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 	char *search;
 	size_t len;
 
@@ -1805,32 +2194,32 @@ nm_ip6_config_add_search (NMIP6Config *config, const char *new)
 	}
 
 	g_ptr_array_add (priv->searches, search);
-	_notify (config, PROP_SEARCHES);
+	_notify (self, PROP_SEARCHES);
 }
 
 void
-nm_ip6_config_del_search (NMIP6Config *config, guint i)
+nm_ip6_config_del_search (NMIP6Config *self, guint i)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->searches->len);
 
 	g_ptr_array_remove_index (priv->searches, i);
-	_notify (config, PROP_SEARCHES);
+	_notify (self, PROP_SEARCHES);
 }
 
 guint
-nm_ip6_config_get_num_searches (const NMIP6Config *config)
+nm_ip6_config_get_num_searches (const NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return priv->searches->len;
 }
 
 const char *
-nm_ip6_config_get_search (const NMIP6Config *config, guint i)
+nm_ip6_config_get_search (const NMIP6Config *self, guint i)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->searches, i);
 }
@@ -1838,20 +2227,20 @@ nm_ip6_config_get_search (const NMIP6Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip6_config_reset_dns_options (NMIP6Config *config)
+nm_ip6_config_reset_dns_options (NMIP6Config *self)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	if (priv->dns_options->len != 0) {
 		g_ptr_array_set_size (priv->dns_options, 0);
-		_notify (config, PROP_DNS_OPTIONS);
+		_notify (self, PROP_DNS_OPTIONS);
 	}
 }
 
 void
-nm_ip6_config_add_dns_option (NMIP6Config *config, const char *new)
+nm_ip6_config_add_dns_option (NMIP6Config *self, const char *new)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 	int i;
 
 	g_return_if_fail (new != NULL);
@@ -1862,32 +2251,32 @@ nm_ip6_config_add_dns_option (NMIP6Config *config, const char *new)
 			return;
 
 	g_ptr_array_add (priv->dns_options, g_strdup (new));
-	_notify (config, PROP_DNS_OPTIONS);
+	_notify (self, PROP_DNS_OPTIONS);
 }
 
 void
-nm_ip6_config_del_dns_option (NMIP6Config *config, guint i)
+nm_ip6_config_del_dns_option (NMIP6Config *self, guint i)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	g_return_if_fail (i < priv->dns_options->len);
 
 	g_ptr_array_remove_index (priv->dns_options, i);
-	_notify (config, PROP_DNS_OPTIONS);
+	_notify (self, PROP_DNS_OPTIONS);
 }
 
 guint
-nm_ip6_config_get_num_dns_options (const NMIP6Config *config)
+nm_ip6_config_get_num_dns_options (const NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return priv->dns_options->len;
 }
 
 const char *
-nm_ip6_config_get_dns_option (const NMIP6Config *config, guint i)
+nm_ip6_config_get_dns_option (const NMIP6Config *self, guint i)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return g_ptr_array_index (priv->dns_options, i);
 }
@@ -1895,40 +2284,101 @@ nm_ip6_config_get_dns_option (const NMIP6Config *config, guint i)
 /*****************************************************************************/
 
 void
-nm_ip6_config_set_dns_priority (NMIP6Config *config, gint priority)
+nm_ip6_config_set_dns_priority (NMIP6Config *self, gint priority)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	if (priority != priv->dns_priority) {
 		priv->dns_priority = priority;
-		_notify (config, PROP_DNS_PRIORITY);
+		_notify (self, PROP_DNS_PRIORITY);
 	}
 }
 
 gint
-nm_ip6_config_get_dns_priority (const NMIP6Config *config)
+nm_ip6_config_get_dns_priority (const NMIP6Config *self)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	return priv->dns_priority;
 }
 
 /*****************************************************************************/
 
-void
-nm_ip6_config_set_mss (NMIP6Config *config, guint32 mss)
+const NMPObject *
+nm_ip6_config_nmpobj_lookup (const NMIP6Config *self, const NMPObject *needle)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	const NMIP6ConfigPrivate *priv;
+	const NMDedupMultiIdxType *idx_type;
 
-	priv->mss = mss;
+	g_return_val_if_fail (NM_IS_IP6_CONFIG (self), NULL);
+
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+	switch (NMP_OBJECT_GET_TYPE (needle)) {
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+		idx_type = &priv->idx_ip6_addresses;
+		break;
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		idx_type = &priv->idx_ip6_routes;
+		break;
+	default:
+		g_return_val_if_reached (NULL);
+	}
+
+	return nm_dedup_multi_entry_get_obj (nm_dedup_multi_index_lookup_obj (priv->multi_idx,
+	                                                                      idx_type,
+	                                                                      needle));
 }
 
-guint32
-nm_ip6_config_get_mss (const NMIP6Config *config)
+gboolean
+nm_ip6_config_nmpobj_remove (NMIP6Config *self,
+                             const NMPObject *needle)
 {
-	const NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv;
+	NMDedupMultiIdxType *idx_type;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	guint n;
+
+	g_return_val_if_fail (NM_IS_IP6_CONFIG (self), FALSE);
 
-	return priv->mss;
+	priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+	switch (NMP_OBJECT_GET_TYPE (needle)) {
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+		idx_type = &priv->idx_ip6_addresses;
+		break;
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		idx_type = &priv->idx_ip6_routes;
+		break;
+	default:
+		g_return_val_if_reached (FALSE);
+	}
+
+	n = nm_dedup_multi_index_remove_obj (priv->multi_idx,
+	                                     idx_type,
+	                                     needle,
+	                                     (gconstpointer *) &obj_old);
+	if (n != 1) {
+		nm_assert (n == 0);
+		return FALSE;
+	}
+
+	nm_assert (NMP_OBJECT_GET_TYPE (obj_old) == NMP_OBJECT_GET_TYPE (needle));
+
+	switch (NMP_OBJECT_GET_TYPE (obj_old)) {
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
+		_notify_addresses (self);
+		break;
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		if (priv->best_default_route == obj_old) {
+			if (_nm_ip_config_best_default_route_set (&priv->best_default_route,
+			                                          _nm_ip6_config_best_default_route_find (self)))
+				_notify (self, PROP_GATEWAY);
+		}
+		_notify_routes (self);
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -1949,27 +2399,24 @@ hash_in6addr (GChecksum *sum, const struct in6_addr *a)
 }
 
 void
-nm_ip6_config_hash (const NMIP6Config *config, GChecksum *sum, gboolean dns_only)
+nm_ip6_config_hash (const NMIP6Config *self, GChecksum *sum, gboolean dns_only)
 {
 	guint32 i;
 	const char *s;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Address *address;
+	const NMPlatformIP6Route *route;
 
-	g_return_if_fail (config);
+	g_return_if_fail (self);
 	g_return_if_fail (sum);
 
 	if (dns_only == FALSE) {
-		hash_in6addr (sum, nm_ip6_config_get_gateway (config));
-
-		for (i = 0; i < nm_ip6_config_get_num_addresses (config); i++) {
-			const NMPlatformIP6Address *address = nm_ip6_config_get_address (config, i);
-
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, self, &address) {
 			hash_in6addr (sum, &address->address);
 			hash_u32 (sum, address->plen);
 		}
 
-		for (i = 0; i < nm_ip6_config_get_num_routes (config); i++) {
-			const NMPlatformIP6Route *route = nm_ip6_config_get_route (config, i);
-
+		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route) {
 			hash_in6addr (sum, &route->network);
 			hash_u32 (sum, route->plen);
 			hash_in6addr (sum, &route->gateway);
@@ -1977,21 +2424,21 @@ nm_ip6_config_hash (const NMIP6Config *config, GChecksum *sum, gboolean dns_only
 		}
 	}
 
-	for (i = 0; i < nm_ip6_config_get_num_nameservers (config); i++)
-		hash_in6addr (sum, nm_ip6_config_get_nameserver (config, i));
+	for (i = 0; i < nm_ip6_config_get_num_nameservers (self); i++)
+		hash_in6addr (sum, nm_ip6_config_get_nameserver (self, i));
 
-	for (i = 0; i < nm_ip6_config_get_num_domains (config); i++) {
-		s = nm_ip6_config_get_domain (config, i);
+	for (i = 0; i < nm_ip6_config_get_num_domains (self); i++) {
+		s = nm_ip6_config_get_domain (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip6_config_get_num_searches (config); i++) {
-		s = nm_ip6_config_get_search (config, i);
+	for (i = 0; i < nm_ip6_config_get_num_searches (self); i++) {
+		s = nm_ip6_config_get_search (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 
-	for (i = 0; i < nm_ip6_config_get_num_dns_options (config); i++) {
-		s = nm_ip6_config_get_dns_option (config, i);
+	for (i = 0; i < nm_ip6_config_get_num_dns_options (self); i++) {
+		s = nm_ip6_config_get_dns_option (self, i);
 		g_checksum_update (sum, (const guint8 *) s, strlen (s));
 	}
 }
@@ -2062,8 +2509,12 @@ static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
 {
-	NMIP6Config *config = NM_IP6_CONFIG (object);
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6Config *self = NM_IP6_CONFIG (object);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter ipconf_iter;
+	const NMPlatformIP6Route *route;
+	GVariantBuilder builder_data, builder_legacy;
 
 	switch (prop_id) {
 	case PROP_IFINDEX:
@@ -2071,27 +2522,31 @@ get_property (GObject *object, guint prop_id,
 		break;
 	case PROP_ADDRESS_DATA:
 	case PROP_ADDRESSES:
-		{
-			GVariantBuilder array_builder, addr_builder;
-			gs_unref_array GArray *new = NULL;
-			const struct in6_addr *gateway;
-			guint naddr, i;
+		nm_assert (!!priv->address_data_variant == !!priv->addresses_variant);
 
-			g_return_if_fail (!!priv->address_data_variant == !!priv->addresses_variant);
+		if (priv->address_data_variant)
+			goto out_addresses_cached;
 
-			if (priv->address_data_variant)
-				goto return_cached;
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
+		g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("a(ayuay)"));
 
-			naddr = nm_ip6_config_get_num_addresses (config);
-			gateway = nm_ip6_config_get_gateway (config);
-			new = g_array_sized_new (FALSE, FALSE, sizeof (NMPlatformIP6Address), naddr);
-			g_array_append_vals (new, priv->addresses->data, naddr);
-			g_array_sort_with_data (new, _addresses_sort_cmp,
-			                        GINT_TO_POINTER (priv->privacy));
+		head_entry = nm_ip6_config_lookup_addresses (self);
+		if (head_entry) {
+			gs_free const NMPObject **addresses = NULL;
+			guint naddr, i;
+
+			addresses = (const NMPObject **) nm_dedup_multi_objs_to_array_head (head_entry, NULL, NULL, &naddr);
+			nm_assert (addresses && naddr);
+
+			g_qsort_with_data (addresses,
+			                   naddr,
+			                   sizeof (addresses[0]),
+			                   _addresses_sort_cmp_prop,
+			                   GINT_TO_POINTER (priv->privacy));
 
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
 			for (i = 0; i < naddr; i++) {
-				const NMPlatformIP6Address *address = &g_array_index (new, NMPlatformIP6Address, i);
+				GVariantBuilder addr_builder;
+				const NMPlatformIP6Address *address = NMP_OBJECT_CAST_IP6_ADDRESS (addresses[i]);
 
 				g_variant_builder_init (&addr_builder, G_VARIANT_TYPE ("a{sv}"));
 				g_variant_builder_add (&addr_builder, "{sv}",
@@ -2107,80 +2562,75 @@ get_property (GObject *object, guint prop_id,
 					                       g_variant_new_string (nm_utils_inet6_ntop (&address->peer_address, NULL)));
 				}
 
-				g_variant_builder_add (&array_builder, "a{sv}", &addr_builder);
-			}
-			priv->address_data_variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
-
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("a(ayuay)"));
-			for (i = 0; i < naddr; i++) {
-				const NMPlatformIP6Address *address = &g_array_index (new, NMPlatformIP6Address, i);
+				g_variant_builder_add (&builder_data, "a{sv}", &addr_builder);
 
-				g_variant_builder_add (&array_builder, "(@ayu@ay)",
+				g_variant_builder_add (&builder_legacy, "(@ayu@ay)",
 				                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
 				                                                  &address->address, 16, 1),
 				                       address->plen,
 				                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
-				                                                  (i == 0 && gateway ? gateway : &in6addr_any),
+				                                                  (   i == 0
+				                                                   && priv->best_default_route)
+				                                                     ? &NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway
+				                                                     : &in6addr_any,
 				                                                  16, 1));
 			}
-
-			priv->addresses_variant = g_variant_ref_sink (g_variant_builder_end (&array_builder));
-return_cached:
-			g_value_set_variant (value,
-			                     prop_id == PROP_ADDRESS_DATA ?
-			                     priv->address_data_variant :
-			                     priv->addresses_variant);
 		}
+
+		priv->address_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
+		priv->addresses_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
+out_addresses_cached:
+		g_value_set_variant (value,
+		                     prop_id == PROP_ADDRESS_DATA ?
+		                     priv->address_data_variant :
+		                     priv->addresses_variant);
 		break;
+
 	case PROP_ROUTE_DATA:
-		{
-			GVariantBuilder array_builder, route_builder;
-			guint nroutes = nm_ip6_config_get_num_routes (config);
-			int i;
+	case PROP_ROUTES:
+		nm_assert (!!priv->route_data_variant == !!priv->routes_variant);
 
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("aa{sv}"));
-			for (i = 0; i < nroutes; i++) {
-				const NMPlatformIP6Route *route = nm_ip6_config_get_route (config, i);
+		if (priv->route_data_variant)
+			goto out_routes_cached;
 
-				g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
-				g_variant_builder_add (&route_builder, "{sv}",
-				                       "dest",
-				                       g_variant_new_string (nm_utils_inet6_ntop (&route->network, NULL)));
-				g_variant_builder_add (&route_builder, "{sv}",
-				                       "prefix",
-				                       g_variant_new_uint32 (route->plen));
-				if (!IN6_IS_ADDR_UNSPECIFIED (&route->gateway)) {
-					g_variant_builder_add (&route_builder, "{sv}",
-					                       "next-hop",
-					                       g_variant_new_string (nm_utils_inet6_ntop (&route->gateway, NULL)));
-				}
+		g_variant_builder_init (&builder_data, G_VARIANT_TYPE ("aa{sv}"));
+		g_variant_builder_init (&builder_legacy, G_VARIANT_TYPE ("a(ayuayu)"));
+
+		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, self, &route) {
+			GVariantBuilder route_builder;
 
+			nm_assert (_route_valid (route));
+
+			g_variant_builder_init (&route_builder, G_VARIANT_TYPE ("a{sv}"));
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "dest",
+			                       g_variant_new_string (nm_utils_inet6_ntop (&route->network, NULL)));
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "prefix",
+			                       g_variant_new_uint32 (route->plen));
+			if (!IN6_IS_ADDR_UNSPECIFIED (&route->gateway)) {
 				g_variant_builder_add (&route_builder, "{sv}",
-				                       "metric",
-				                       g_variant_new_uint32 (route->metric));
+				                       "next-hop",
+				                       g_variant_new_string (nm_utils_inet6_ntop (&route->gateway, NULL)));
+			}
+
+			g_variant_builder_add (&route_builder, "{sv}",
+			                       "metric",
+			                       g_variant_new_uint32 (route->metric));
 
-				g_variant_builder_add (&array_builder, "a{sv}", &route_builder);
+			if (!nm_platform_route_table_is_main (route->table_coerced)) {
+				g_variant_builder_add (&route_builder, "{sv}",
+				                       "table",
+				                       g_variant_new_uint32 (nm_platform_route_table_uncoerce (route->table_coerced, TRUE)));
 			}
 
-			g_value_take_variant (value, g_variant_builder_end (&array_builder));
-		}
-		break;
-	case PROP_ROUTES:
-		{
-			GVariantBuilder array_builder;
-			int nroutes = nm_ip6_config_get_num_routes (config);
-			int i;
-
-			g_variant_builder_init (&array_builder, G_VARIANT_TYPE ("a(ayuayu)"));
-			for (i = 0; i < nroutes; i++) {
-				const NMPlatformIP6Route *route = nm_ip6_config_get_route (config, i);
-
-				/* legacy versions of nm_ip6_route_set_prefix() in libnm-util assert that the
-				 * plen is positive. Skip the default routes not to break older clients. */
-				if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route))
-					continue;
+			g_variant_builder_add (&builder_data, "a{sv}", &route_builder);
 
-				g_variant_builder_add (&array_builder, "(@ayu@ayu)",
+			/* legacy versions of nm_ip6_route_set_prefix() in libnm-util assert that the
+			 * plen is positive. Skip the default routes not to break older clients. */
+			if (   nm_platform_route_table_is_main (route->table_coerced)
+			    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
+				g_variant_builder_add (&builder_legacy, "(@ayu@ayu)",
 				                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
 				                                                  &route->network, 16, 1),
 				                       (guint32) route->plen,
@@ -2188,14 +2638,21 @@ return_cached:
 				                                                  &route->gateway, 16, 1),
 				                       (guint32) route->metric);
 			}
-
-			g_value_take_variant (value, g_variant_builder_end (&array_builder));
 		}
+		priv->route_data_variant = g_variant_ref_sink (g_variant_builder_end (&builder_data));
+		priv->routes_variant = g_variant_ref_sink (g_variant_builder_end (&builder_legacy));
+out_routes_cached:
+		g_value_set_variant (value,
+		                     prop_id == PROP_ROUTE_DATA ?
+		                     priv->route_data_variant :
+		                     priv->routes_variant);
 		break;
 	case PROP_GATEWAY:
-		if (!IN6_IS_ADDR_UNSPECIFIED (&priv->gateway))
-			g_value_set_string (value, nm_utils_inet6_ntop (&priv->gateway, NULL));
-		else
+		if (priv->best_default_route) {
+			g_value_set_string (value,
+			                    nm_utils_inet6_ntop (&NMP_OBJECT_CAST_IP6_ROUTE (priv->best_default_route)->gateway,
+			                                         NULL));
+		} else
 			g_value_set_string (value, NULL);
 		break;
 	case PROP_NAMESERVERS:
@@ -2225,10 +2682,17 @@ set_property (GObject *object,
               const GValue *value,
               GParamSpec *pspec)
 {
-	NMIP6Config *config = NM_IP6_CONFIG (object);
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6Config *self = NM_IP6_CONFIG (object);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
 	switch (prop_id) {
+	case PROP_MULTI_IDX:
+		/* construct-only */
+		priv->multi_idx = g_value_get_pointer (value);
+		if (!priv->multi_idx)
+			g_return_if_reached ();
+		nm_dedup_multi_index_ref (priv->multi_idx);
+		break;
 	case PROP_IFINDEX:
 		/* construct-only */
 		priv->ifindex = g_value_get_int (value);
@@ -2242,24 +2706,27 @@ set_property (GObject *object,
 /*****************************************************************************/
 
 static void
-nm_ip6_config_init (NMIP6Config *config)
+nm_ip6_config_init (NMIP6Config *self)
 {
-	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (config);
+	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+
+	nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip6_addresses,
+	                                        NMP_OBJECT_TYPE_IP6_ADDRESS);
+	nm_ip_config_dedup_multi_idx_type_init ((NMIPConfigDedupMultiIdxType *) &priv->idx_ip6_routes,
+	                                        NMP_OBJECT_TYPE_IP6_ROUTE);
 
-	priv->addresses = g_array_new (FALSE, TRUE, sizeof (NMPlatformIP6Address));
-	priv->routes = g_array_new (FALSE, TRUE, sizeof (NMPlatformIP6Route));
 	priv->nameservers = g_array_new (FALSE, TRUE, sizeof (struct in6_addr));
 	priv->domains = g_ptr_array_new_with_free_func (g_free);
 	priv->searches = g_ptr_array_new_with_free_func (g_free);
 	priv->dns_options = g_ptr_array_new_with_free_func (g_free);
-	priv->route_metric = -1;
 }
 
 NMIP6Config *
-nm_ip6_config_new (int ifindex)
+nm_ip6_config_new (NMDedupMultiIndex *multi_idx, int ifindex)
 {
 	g_return_val_if_fail (ifindex >= -1, NULL);
 	return (NMIP6Config *) g_object_new (NM_TYPE_IP6_CONFIG,
+	                                     NM_IP6_CONFIG_MULTI_IDX, multi_idx,
 	                                     NM_IP6_CONFIG_IFINDEX, ifindex,
 	                                     NULL);
 }
@@ -2271,7 +2738,8 @@ nm_ip6_config_new_cloned (const NMIP6Config *src)
 
 	g_return_val_if_fail (NM_IS_IP6_CONFIG (src), NULL);
 
-	new = nm_ip6_config_new (nm_ip6_config_get_ifindex (src));
+	new = nm_ip6_config_new (nm_ip6_config_get_multi_idx (src),
+	                         nm_ip6_config_get_ifindex (src));
 	nm_ip6_config_replace (new, src, NULL);
 	return new;
 }
@@ -2282,16 +2750,24 @@ finalize (GObject *object)
 	NMIP6Config *self = NM_IP6_CONFIG (object);
 	NMIP6ConfigPrivate *priv = NM_IP6_CONFIG_GET_PRIVATE (self);
 
-	g_array_unref (priv->addresses);
-	g_array_unref (priv->routes);
+	nm_clear_nmp_object (&priv->best_default_route);
+
+	nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip6_addresses);
+	nm_dedup_multi_index_remove_idx (priv->multi_idx, &priv->idx_ip6_routes);
+
+	nm_clear_g_variant (&priv->address_data_variant);
+	nm_clear_g_variant (&priv->addresses_variant);
+	nm_clear_g_variant (&priv->route_data_variant);
+	nm_clear_g_variant (&priv->routes_variant);
+
 	g_array_unref (priv->nameservers);
 	g_ptr_array_unref (priv->domains);
 	g_ptr_array_unref (priv->searches);
 	g_ptr_array_unref (priv->dns_options);
-	nm_clear_g_variant (&priv->address_data_variant);
-	nm_clear_g_variant (&priv->addresses_variant);
 
 	G_OBJECT_CLASS (nm_ip6_config_parent_class)->finalize (object);
+
+	nm_dedup_multi_index_unref (priv->multi_idx);
 }
 
 static void
@@ -2306,6 +2782,11 @@ nm_ip6_config_class_init (NMIP6ConfigClass *config_class)
 	object_class->set_property = set_property;
 	object_class->finalize = finalize;
 
+	obj_properties[PROP_MULTI_IDX] =
+	    g_param_spec_pointer (NM_IP6_CONFIG_MULTI_IDX, "", "",
+	                            G_PARAM_WRITABLE
+	                          | G_PARAM_CONSTRUCT_ONLY
+	                          | G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_IFINDEX] =
 	    g_param_spec_int (NM_IP6_CONFIG_IFINDEX, "", "",
 	                      -1, G_MAXINT, -1,
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 557041c9..2fb8b8a4 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -26,6 +26,48 @@
 #include "nm-exported-object.h"
 #include "nm-setting-ip6-config.h"
 
+#include "nm-utils/nm-dedup-multi.h"
+#include "platform/nmp-object.h"
+
+/*****************************************************************************/
+
+void nm_ip_config_iter_ip6_address_init (NMDedupMultiIter *iter, const NMIP6Config *self);
+void nm_ip_config_iter_ip6_route_init (NMDedupMultiIter *iter, const NMIP6Config *self);
+
+static inline gboolean
+nm_ip_config_iter_ip6_address_next (NMDedupMultiIter *ipconf_iter, const NMPlatformIP6Address **out_address)
+{
+	gboolean has_next;
+
+	has_next = nm_dedup_multi_iter_next (ipconf_iter);
+	if (out_address)
+		*out_address = has_next ? NMP_OBJECT_CAST_IP6_ADDRESS (ipconf_iter->current->obj) : NULL;
+	return has_next;
+}
+
+static inline gboolean
+nm_ip_config_iter_ip6_route_next (NMDedupMultiIter *ipconf_iter, const NMPlatformIP6Route **out_route)
+{
+	gboolean has_next;
+
+	has_next = nm_dedup_multi_iter_next (ipconf_iter);
+	if (out_route)
+		*out_route = has_next ? NMP_OBJECT_CAST_IP6_ROUTE (ipconf_iter->current->obj) : NULL;
+	return has_next;
+}
+
+#define nm_ip_config_iter_ip6_address_for_each(iter, self, address) \
+    for (nm_ip_config_iter_ip6_address_init ((iter), (self)); \
+         nm_ip_config_iter_ip6_address_next ((iter), (address)); \
+         )
+
+#define nm_ip_config_iter_ip6_route_for_each(iter, self, route) \
+    for (nm_ip_config_iter_ip6_route_init ((iter), (self)); \
+         nm_ip_config_iter_ip6_route_next ((iter), (route)); \
+         )
+
+/*****************************************************************************/
+
 #define NM_TYPE_IP6_CONFIG (nm_ip6_config_get_type ())
 #define NM_IP6_CONFIG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IP6_CONFIG, NMIP6Config))
 #define NM_IP6_CONFIG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IP6_CONFIG, NMIP6ConfigClass))
@@ -36,6 +78,7 @@
 typedef struct _NMIP6ConfigClass NMIP6ConfigClass;
 
 /* internal */
+#define NM_IP6_CONFIG_MULTI_IDX "multi-idx"
 #define NM_IP6_CONFIG_IFINDEX "ifindex"
 
 /* public */
@@ -55,96 +98,128 @@ typedef struct _NMIP6ConfigClass NMIP6ConfigClass;
 GType nm_ip6_config_get_type (void);
 
 
-NMIP6Config * nm_ip6_config_new (int ifindex);
+NMIP6Config * nm_ip6_config_new (struct _NMDedupMultiIndex *multi_idx, int ifindex);
 NMIP6Config * nm_ip6_config_new_cloned (const NMIP6Config *src);
 
-int nm_ip6_config_get_ifindex (const NMIP6Config *config);
+int nm_ip6_config_get_ifindex (const NMIP6Config *self);
 
+struct _NMDedupMultiIndex *nm_ip6_config_get_multi_idx (const NMIP6Config *self);
 
-NMIP6Config *nm_ip6_config_capture (NMPlatform *platform, int ifindex, gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary);
-gboolean nm_ip6_config_commit (const NMIP6Config *config,
-                               NMPlatform *platform,
-                               NMRouteManager *route_manager,
-                               int ifindex,
-                               gboolean routes_full_sync);
-void nm_ip6_config_merge_setting (NMIP6Config *config, NMSettingIPConfig *setting, guint32 default_route_metric);
-NMSetting *nm_ip6_config_create_setting (const NMIP6Config *config);
+NMIP6Config *nm_ip6_config_capture (struct _NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex,
+                                    gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary);
 
+void nm_ip6_config_add_dependent_routes (NMIP6Config *self,
+                                         guint32 route_table,
+                                         guint32 route_metric);
 
-void nm_ip6_config_merge (NMIP6Config *dst, const NMIP6Config *src, NMIPConfigMergeFlags merge_flags);
-void nm_ip6_config_subtract (NMIP6Config *dst, const NMIP6Config *src);
-void nm_ip6_config_intersect (NMIP6Config *dst, const NMIP6Config *src);
+gboolean nm_ip6_config_commit (const NMIP6Config *self,
+                               NMPlatform *platform,
+                               NMIPRouteTableSyncMode route_table_sync,
+                               GPtrArray **out_temporary_not_available);
+void nm_ip6_config_merge_setting (NMIP6Config *self,
+                                  NMSettingIPConfig *setting,
+                                  guint32 route_table,
+                                  guint32 route_metric);
+NMSetting *nm_ip6_config_create_setting (const NMIP6Config *self);
+
+
+void nm_ip6_config_merge (NMIP6Config *dst,
+                          const NMIP6Config *src,
+                          NMIPConfigMergeFlags merge_flags,
+                          guint32 default_route_metric_penalty);
+void nm_ip6_config_subtract (NMIP6Config *dst,
+                             const NMIP6Config *src,
+                             guint32 default_route_metric_penalty);
+void nm_ip6_config_intersect (NMIP6Config *dst,
+                              const NMIP6Config *src,
+                              guint32 default_route_metric_penalty);
 gboolean nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relevant_changes);
-int nm_ip6_config_destination_is_direct (const NMIP6Config *config, const struct in6_addr *dest, guint8 plen);
-void nm_ip6_config_dump (const NMIP6Config *config, const char *detail);
-
-
-void nm_ip6_config_set_never_default (NMIP6Config *config, gboolean never_default);
-gboolean nm_ip6_config_get_never_default (const NMIP6Config *config);
-void nm_ip6_config_set_gateway (NMIP6Config *config, const struct in6_addr *);
-const struct in6_addr *nm_ip6_config_get_gateway (const NMIP6Config *config);
-gint64 nm_ip6_config_get_route_metric (const NMIP6Config *config);
-
-void nm_ip6_config_reset_addresses (NMIP6Config *config);
-void nm_ip6_config_add_address (NMIP6Config *config, const NMPlatformIP6Address *address);
-void nm_ip6_config_del_address (NMIP6Config *config, guint i);
-guint nm_ip6_config_get_num_addresses (const NMIP6Config *config);
-const NMPlatformIP6Address *nm_ip6_config_get_address (const NMIP6Config *config, guint i);
-const NMPlatformIP6Address *nm_ip6_config_get_address_first_nontentative (const NMIP6Config *config, gboolean linklocal);
-gboolean nm_ip6_config_address_exists (const NMIP6Config *config, const NMPlatformIP6Address *address);
-gboolean nm_ip6_config_addresses_sort (NMIP6Config *config);
+void nm_ip6_config_dump (const NMIP6Config *self, const char *detail);
+
+const NMPObject *nm_ip6_config_best_default_route_get (const NMIP6Config *self);
+const NMPObject *_nm_ip6_config_best_default_route_find (const NMIP6Config *self);
+
+const NMDedupMultiHeadEntry *nm_ip6_config_lookup_addresses (const NMIP6Config *self);
+void nm_ip6_config_reset_addresses (NMIP6Config *self);
+void nm_ip6_config_add_address (NMIP6Config *self, const NMPlatformIP6Address *address);
+void _nmtst_ip6_config_del_address (NMIP6Config *self, guint i);
+guint nm_ip6_config_get_num_addresses (const NMIP6Config *self);
+const NMPlatformIP6Address *nm_ip6_config_get_first_address (const NMIP6Config *self);
+const NMPlatformIP6Address *_nmtst_ip6_config_get_address (const NMIP6Config *self, guint i);
+const NMPlatformIP6Address *nm_ip6_config_get_address_first_nontentative (const NMIP6Config *self, gboolean linklocal);
+gboolean nm_ip6_config_address_exists (const NMIP6Config *self, const NMPlatformIP6Address *address);
+const NMPlatformIP6Address *nm_ip6_config_lookup_address (const NMIP6Config *self,
+                                                          const struct in6_addr *addr);
+gboolean _nmtst_ip6_config_addresses_sort (NMIP6Config *self);
 gboolean nm_ip6_config_has_any_dad_pending (const NMIP6Config *self,
                                             const NMIP6Config *candidates);
 
-void nm_ip6_config_reset_routes (NMIP6Config *config);
-void nm_ip6_config_add_route (NMIP6Config *config, const NMPlatformIP6Route *route);
-void nm_ip6_config_del_route (NMIP6Config *config, guint i);
-guint nm_ip6_config_get_num_routes (const NMIP6Config *config);
-const NMPlatformIP6Route *nm_ip6_config_get_route (const NMIP6Config *config, guint i);
-
-const NMPlatformIP6Route *nm_ip6_config_get_direct_route_for_host (const NMIP6Config *config, const struct in6_addr *host);
-const NMPlatformIP6Address *nm_ip6_config_get_subnet_for_host (const NMIP6Config *config, const struct in6_addr *host);
-
-void nm_ip6_config_reset_nameservers (NMIP6Config *config);
-void nm_ip6_config_add_nameserver (NMIP6Config *config, const struct in6_addr *nameserver);
-void nm_ip6_config_del_nameserver (NMIP6Config *config, guint i);
-guint nm_ip6_config_get_num_nameservers (const NMIP6Config *config);
-const struct in6_addr *nm_ip6_config_get_nameserver (const NMIP6Config *config, guint i);
-
-void nm_ip6_config_reset_domains (NMIP6Config *config);
-void nm_ip6_config_add_domain (NMIP6Config *config, const char *domain);
-void nm_ip6_config_del_domain (NMIP6Config *config, guint i);
-guint nm_ip6_config_get_num_domains (const NMIP6Config *config);
-const char * nm_ip6_config_get_domain (const NMIP6Config *config, guint i);
-
-void nm_ip6_config_reset_searches (NMIP6Config *config);
-void nm_ip6_config_add_search (NMIP6Config *config, const char *search);
-void nm_ip6_config_del_search (NMIP6Config *config, guint i);
-guint nm_ip6_config_get_num_searches (const NMIP6Config *config);
-const char * nm_ip6_config_get_search (const NMIP6Config *config, guint i);
-
-void nm_ip6_config_reset_dns_options (NMIP6Config *config);
-void nm_ip6_config_add_dns_option (NMIP6Config *config, const char *option);
-void nm_ip6_config_del_dns_option (NMIP6Config *config, guint i);
-guint nm_ip6_config_get_num_dns_options (const NMIP6Config *config);
-const char * nm_ip6_config_get_dns_option (const NMIP6Config *config, guint i);
-
-void nm_ip6_config_set_dns_priority (NMIP6Config *config, gint priority);
-gint nm_ip6_config_get_dns_priority (const NMIP6Config *config);
-
-void nm_ip6_config_set_mss (NMIP6Config *config, guint32 mss);
-guint32 nm_ip6_config_get_mss (const NMIP6Config *config);
-
-void nm_ip6_config_hash (const NMIP6Config *config, GChecksum *sum, gboolean dns_only);
+const NMDedupMultiHeadEntry *nm_ip6_config_lookup_routes (const NMIP6Config *self);
+void nm_ip6_config_reset_routes (NMIP6Config *self);
+void nm_ip6_config_add_route (NMIP6Config *self,
+                              const NMPlatformIP6Route *route,
+                              const NMPObject **out_obj_new);
+void _nmtst_ip6_config_del_route (NMIP6Config *self, guint i);
+guint nm_ip6_config_get_num_routes (const NMIP6Config *self);
+const NMPlatformIP6Route *_nmtst_ip6_config_get_route (const NMIP6Config *self, guint i);
+
+const NMPlatformIP6Route *nm_ip6_config_get_direct_route_for_host (const NMIP6Config *self,
+                                                                   const struct in6_addr *host,
+                                                                   guint32 route_table);
+const NMPlatformIP6Address *nm_ip6_config_get_subnet_for_host (const NMIP6Config *self, const struct in6_addr *host);
+
+void nm_ip6_config_reset_nameservers (NMIP6Config *self);
+void nm_ip6_config_add_nameserver (NMIP6Config *self, const struct in6_addr *nameserver);
+void nm_ip6_config_del_nameserver (NMIP6Config *self, guint i);
+guint nm_ip6_config_get_num_nameservers (const NMIP6Config *self);
+const struct in6_addr *nm_ip6_config_get_nameserver (const NMIP6Config *self, guint i);
+
+void nm_ip6_config_reset_domains (NMIP6Config *self);
+void nm_ip6_config_add_domain (NMIP6Config *self, const char *domain);
+void nm_ip6_config_del_domain (NMIP6Config *self, guint i);
+guint nm_ip6_config_get_num_domains (const NMIP6Config *self);
+const char * nm_ip6_config_get_domain (const NMIP6Config *self, guint i);
+
+void nm_ip6_config_reset_searches (NMIP6Config *self);
+void nm_ip6_config_add_search (NMIP6Config *self, const char *search);
+void nm_ip6_config_del_search (NMIP6Config *self, guint i);
+guint nm_ip6_config_get_num_searches (const NMIP6Config *self);
+const char * nm_ip6_config_get_search (const NMIP6Config *self, guint i);
+
+void nm_ip6_config_reset_dns_options (NMIP6Config *self);
+void nm_ip6_config_add_dns_option (NMIP6Config *self, const char *option);
+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);
+
+const NMPObject *nm_ip6_config_nmpobj_lookup (const NMIP6Config *self,
+                                              const NMPObject *needle);
+gboolean nm_ip6_config_nmpobj_remove (NMIP6Config *self,
+                                      const NMPObject *needle);
+
+void nm_ip6_config_hash (const NMIP6Config *self, GChecksum *sum, gboolean dns_only);
 gboolean nm_ip6_config_equal (const NMIP6Config *a, const NMIP6Config *b);
 
-void nm_ip6_config_set_privacy (NMIP6Config *config, NMSettingIP6ConfigPrivacy privacy);
-
-/*****************************************************************************/
-/* Testing-only functions */
-
-gboolean nm_ip6_config_capture_resolv_conf (GArray *nameservers,
-                                            GPtrArray *dns_options,
-                                            const char *rc_contents);
+void nm_ip6_config_set_privacy (NMIP6Config *self, NMSettingIP6ConfigPrivacy privacy);
+
+struct _NMNDiscAddress;
+void nm_ip6_config_reset_addresses_ndisc (NMIP6Config *self,
+                                          const struct _NMNDiscAddress *addresses,
+                                          guint addresses_n,
+                                          guint8 plen,
+                                          guint32 ifa_flags);
+struct _NMNDiscRoute;
+struct _NMNDiscGateway;
+void nm_ip6_config_reset_routes_ndisc (NMIP6Config *self,
+                                       const struct _NMNDiscGateway *gateways,
+                                       guint gateways_n,
+                                       const struct _NMNDiscRoute *routes,
+                                       guint routes_n,
+                                       guint32 route_table,
+                                       guint32 route_metric,
+                                       gboolean kernel_support_rta_pref);
 
 #endif /* __NETWORKMANAGER_IP6_CONFIG_H__ */
diff --git a/src/nm-logging.h b/src/nm-logging.h
index 91a41412..8fcbc8cc 100644
--- a/src/nm-logging.h
+++ b/src/nm-logging.h
@@ -142,11 +142,16 @@ typedef enum  { /*< skip >*/
            (prefix) ?: "", \
            self _NM_UTILS_MACRO_REST(__VA_ARGS__))
 
+static inline gboolean
+_nm_log_ptr_is_debug (NMLogLevel level)
+{
+	return level <= LOGL_DEBUG;
+}
+
 /* log a message for an object (with providing a generic @self pointer) */
 #define nm_log_ptr(level, domain, ifname, con_uuid, self, prefix, ...) \
     G_STMT_START { \
-        NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare") \
-        if ((level) <= LOGL_DEBUG) { \
+        if (_nm_log_ptr_is_debug (level)) { \
             _nm_log_ptr ((level), \
                          (domain), \
                          (ifname), \
@@ -165,7 +170,6 @@ typedef enum  { /*< skip >*/
                     __prefix ?: "", \
                     __prefix ? " " : "" _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
         } \
-        NM_PRAGMA_WARNING_REENABLE \
     } G_STMT_END
 
 
@@ -269,7 +273,7 @@ gboolean nm_logging_syslog_enabled (void);
 /*****************************************************************************/
 
 /* Some implementation define a second set of logging macros, for a separate
- * use. As with the _LOGD() macro familiy above, the exact implementation
+ * use. As with the _LOGD() macro family above, the exact implementation
  * depends on the file that uses them.
  * Still, it encourages a common pattern to have the common set of macros
  * like _LOG2D(), _LOG2I(), etc. and have _LOG2t() which by default
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 7662c2e3..3b2b4861 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -35,6 +35,8 @@
 #include "devices/nm-device.h"
 #include "devices/nm-device-generic.h"
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
+#include "nm-hostname-manager.h"
 #include "nm-rfkill-manager.h"
 #include "dhcp/nm-dhcp-manager.h"
 #include "settings/nm-settings.h"
@@ -92,6 +94,8 @@ static void settings_startup_complete_changed (NMSettings *settings,
                                                GParamSpec *pspec,
                                                NMManager *self);
 
+static void retry_connections_for_parent_device (NMManager *self, NMDevice *device);
+
 static NM_CACHED_QUARK_FCN ("active-connection-add-and-activate", active_connection_add_and_activate_quark)
 
 typedef struct {
@@ -106,6 +110,8 @@ typedef struct {
 } RadioState;
 
 typedef struct {
+	NMPlatform *platform;
+
 	GArray *capabilities;
 
 	GSList *active_connections;
@@ -122,6 +128,8 @@ typedef struct {
 
 	NMPolicy *policy;
 
+	NMHostnameManager *hostname_manager;
+
 	NMBusManager  *dbus_mgr;
 	struct {
 		GDBusConnection *connection;
@@ -129,10 +137,11 @@ typedef struct {
 	} prop_filter;
 	NMRfkillManager *rfkill_mgr;
 
+	CList link_cb_lst;
+
 	NMCheckpointManager *checkpoint_mgr;
 
 	NMSettings *settings;
-	char *hostname;
 
 	RadioState radio_states[RFKILL_TYPE_MAX];
 	NMVpnManager *vpn_manager;
@@ -202,6 +211,8 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMManager,
 	PROP_WIMAX_HARDWARE_ENABLED,
 	PROP_ACTIVE_CONNECTIONS,
 	PROP_CONNECTIVITY,
+	PROP_CONNECTIVITY_CHECK_AVAILABLE,
+	PROP_CONNECTIVITY_CHECK_ENABLED,
 	PROP_PRIMARY_CONNECTION,
 	PROP_PRIMARY_CONNECTION_TYPE,
 	PROP_ACTIVATING_CONNECTION,
@@ -211,7 +222,6 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMManager,
 	PROP_ALL_DEVICES,
 
 	/* Not exported */
-	PROP_HOSTNAME,
 	PROP_SLEEPING,
 );
 
@@ -329,7 +339,7 @@ active_connection_remove (NMManager *self, NMActiveConnection *active)
 			if (nm_settings_has_connection (priv->settings, connection)) {
 				_LOGD (LOGD_DEVICE, "assumed connection disconnected. Deleting generated connection '%s' (%s)",
 				       nm_settings_connection_get_id (connection), nm_settings_connection_get_uuid (connection));
-				nm_settings_connection_delete (connection, NULL, NULL);
+				nm_settings_connection_delete (connection, NULL);
 			}
 			g_object_unref (connection);
 		}
@@ -827,7 +837,7 @@ find_best_device_state (NMManager *manager)
 		case NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
 			if (   nm_active_connection_get_default (ac)
 			    || nm_active_connection_get_default6 (ac)) {
-				if (priv->connectivity_state)
+				if (priv->connectivity_state == NM_CONNECTIVITY_FULL)
 					return NM_STATE_CONNECTED_GLOBAL;
 
 				best_state = NM_STATE_CONNECTED_SITE;
@@ -1149,6 +1159,10 @@ find_parent_device_for_connection (NMManager *self, NMConnection *connection, NM
 	for (iter = priv->devices; iter; iter = iter->next) {
 		NMDevice *candidate = iter->data;
 
+		/* Unmanaged devices are not compatible with any connection */
+		if (!nm_device_get_managed (candidate, FALSE))
+			continue;
+
 		if (nm_device_get_settings_connection (candidate) == parent_connection)
 			return candidate;
 
@@ -1256,6 +1270,40 @@ nm_manager_iface_for_uuid (NMManager *self, const char *uuid)
 	return nm_connection_get_interface_name (NM_CONNECTION (connection));
 }
 
+NMDevice *
+nm_manager_get_device (NMManager *self, const char *ifname, NMDeviceType device_type)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	GSList *iter;
+	NMDevice *d;
+
+	g_return_val_if_fail (ifname, NULL);
+	g_return_val_if_fail (device_type != NM_DEVICE_TYPE_UNKNOWN, NULL);
+
+	for (iter = priv->devices; iter; iter = iter->next) {
+		d = iter->data;
+
+		if (   nm_device_get_device_type (d) == device_type
+		    && nm_streq0 (nm_device_get_iface (d), ifname))
+			return d;
+	}
+
+	return NULL;
+}
+
+gboolean
+nm_manager_remove_device (NMManager *self, const char *ifname, NMDeviceType device_type)
+{
+	NMDevice *d;
+
+	d = nm_manager_get_device (self, ifname, device_type);
+	if (!d)
+		return FALSE;
+
+	remove_device (self, d, FALSE, FALSE);
+	return TRUE;
+}
+
 /**
  * system_create_virtual_device:
  * @self: the #NMManager
@@ -1277,6 +1325,7 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 	gs_free char *iface = NULL;
 	NMDevice *device = NULL, *parent = NULL;
 	GError *error = NULL;
+	NMLogLevel log_level;
 
 	g_return_val_if_fail (NM_IS_MANAGER (self), NULL);
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
@@ -1357,12 +1406,20 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 
 		/* Create any backing resources the device needs */
 		if (!nm_device_create_and_realize (device, connection, parent, &error)) {
-			_LOG3W (LOGD_DEVICE, connection, "couldn't create the device: %s",
-			        error->message);
+			log_level = g_error_matches (error,
+			                             NM_DEVICE_ERROR,
+			                             NM_DEVICE_ERROR_MISSING_DEPENDENCIES)
+			            ? LOGL_DEBUG
+			            : LOGL_ERR;
+			_NMLOG3 (log_level, LOGD_DEVICE, connection,
+			         "couldn't create the device: %s",
+			         error->message);
 			g_error_free (error);
 			remove_device (self, device, FALSE, TRUE);
 			return NULL;
 		}
+
+		retry_connections_for_parent_device (self, device);
 		break;
 	}
 
@@ -1448,35 +1505,17 @@ system_unmanaged_devices_changed_cb (NMSettings *settings,
 }
 
 static void
-system_hostname_changed_cb (NMSettings *settings,
-                            GParamSpec *pspec,
-                            gpointer user_data)
+hostname_changed_cb (NMHostnameManager *hostname_manager,
+                     GParamSpec *pspec,
+                     NMManager *self)
 {
-	NMManager *self = NM_MANAGER (user_data);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	char *hostname;
-
-	hostname = nm_settings_get_hostname (priv->settings);
-
-	/* nm_settings_get_hostname() does not return an empty hostname. */
-	nm_assert (!hostname || *hostname);
-
-	if (!hostname && !priv->hostname)
-		return;
-	if (hostname && priv->hostname && !strcmp (hostname, priv->hostname)) {
-		g_free (hostname);
-		return;
-	}
+	const char *hostname;
 
-	/* realloc, to free possibly trailing data after NUL. */
-	if (hostname)
-		hostname = g_realloc (hostname, strlen (hostname) + 1);
+	hostname = nm_hostname_manager_get_hostname (priv->hostname_manager);
 
-	g_free (priv->hostname);
-	priv->hostname = hostname;
-	_notify (self, PROP_HOSTNAME);
-
-	nm_dhcp_manager_set_default_hostname (nm_dhcp_manager_get (), priv->hostname);
+	nm_dispatcher_call_hostname (NULL, NULL, NULL);
+	nm_dhcp_manager_set_default_hostname (nm_dhcp_manager_get (), hostname);
 }
 
 /*****************************************************************************/
@@ -1761,14 +1800,14 @@ get_existing_connection (NMManager *self,
 	nm_device_capture_initial_config (device);
 
 	if (ifindex) {
-		int master_ifindex = nm_platform_link_get_master (NM_PLATFORM_GET, ifindex);
+		int master_ifindex = nm_platform_link_get_master (priv->platform, ifindex);
 
 		if (master_ifindex) {
 			master = nm_manager_get_device_by_ifindex (self, master_ifindex);
 			if (!master) {
 				_LOG2D (LOGD_DEVICE, device, "assume: don't assume because "
 				        "cannot generate connection for slave before its master (%s/%d)",
-				        nm_platform_link_get_name (NM_PLATFORM_GET, master_ifindex), master_ifindex);
+				        nm_platform_link_get_name (priv->platform, master_ifindex), master_ifindex);
 				return NULL;
 			}
 			if (!nm_device_get_act_request (master)) {
@@ -1822,8 +1861,8 @@ get_existing_connection (NMManager *self,
 		matched = NM_SETTINGS_CONNECTION (nm_utils_match_connection (connections,
 		                                                             connection,
 		                                                             nm_device_has_carrier (device),
-		                                                             nm_device_get_ip4_route_metric (device),
-		                                                             nm_device_get_ip6_route_metric (device),
+		                                                             nm_device_get_route_metric (device, AF_INET),
+		                                                             nm_device_get_route_metric (device, AF_INET6),
 		                                                             NULL, NULL));
 	} else
 		matched = NULL;
@@ -1851,8 +1890,8 @@ get_existing_connection (NMManager *self,
 			matched = NM_SETTINGS_CONNECTION (nm_utils_match_connection ((NMConnection *const*) connections,
 			                                                             connection,
 			                                                             nm_device_has_carrier (device),
-			                                                             nm_device_get_ip4_route_metric (device),
-			                                                             nm_device_get_ip6_route_metric (device),
+			                                                             nm_device_get_route_metric (device, AF_INET),
+			                                                             nm_device_get_route_metric (device, AF_INET6),
 			                                                             NULL, NULL));
 		}
 	}
@@ -1967,7 +2006,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, NULL);
+				nm_settings_connection_delete (connection, 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);
@@ -2008,6 +2047,7 @@ device_ip_iface_changed (NMDevice *device,
                          NMManager *self)
 {
 	const char *ip_iface = nm_device_get_ip_iface (device);
+	NMDeviceType device_type = nm_device_get_device_type (device);
 	GSList *iter;
 
 	/* Remove NMDevice objects that are actually child devices of others,
@@ -2020,6 +2060,7 @@ device_ip_iface_changed (NMDevice *device,
 
 		if (   candidate != device
 		    && g_strcmp0 (nm_device_get_iface (candidate), ip_iface) == 0
+		    && nm_device_get_device_type (candidate) == device_type
 		    && nm_device_is_real (candidate)) {
 			remove_device (self, candidate, FALSE, FALSE);
 			break;
@@ -2228,11 +2269,6 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 
 	_parent_notify_changed (self, device, FALSE);
 
-	/* Virtual connections may refer to the new device as
-	 * parent device, retry to activate them.
-	 */
-	retry_connections_for_parent_device (self, device);
-
 	return TRUE;
 }
 
@@ -2258,6 +2294,7 @@ factory_device_added_cb (NMDeviceFactory *factory,
 	                             &error)) {
 		add_device (self, device, NULL);
 		_device_realize_finish (self, device, NULL);
+		retry_connections_for_parent_device (self, device);
 	} else {
 		_LOG2W (LOGD_DEVICE, device, "failed to realize device: %s", error->message);
 		g_error_free (error);
@@ -2320,6 +2357,9 @@ platform_link_added (NMManager *self,
 		gboolean compatible = TRUE;
 		gs_free_error GError *error = NULL;
 
+		if (nm_device_get_link_type (candidate) != plink->type)
+			continue;
+
 		if (strcmp (nm_device_get_iface (candidate), plink->name))
 			continue;
 
@@ -2327,6 +2367,7 @@ platform_link_added (NMManager *self,
 			/* Ignore the link added event since there's already a realized
 			 * device with the link's name.
 			 */
+			nm_device_update_from_platform_link (candidate, plink);
 			return;
 		} else if (nm_device_realize_start (candidate,
 		                                    plink,
@@ -2411,6 +2452,7 @@ platform_link_added (NMManager *self,
 		                             &error)) {
 			add_device (self, device, NULL);
 			_device_realize_finish (self, device, plink);
+			retry_connections_for_parent_device (self, device);
 		} else {
 			_LOGW (LOGD_DEVICE, "%s: failed to realize device: %s",
 			       plink->name, error->message);
@@ -2420,32 +2462,34 @@ platform_link_added (NMManager *self,
 }
 
 typedef struct {
+	CList lst;
 	NMManager *self;
 	int ifindex;
+	guint idle_id;
 } PlatformLinkCbData;
 
 static gboolean
 _platform_link_cb_idle (PlatformLinkCbData *data)
 {
+	int ifindex = data->ifindex;
 	NMManager *self = data->self;
-	const NMPlatformLink *l;
-
-	if (!self)
-		goto out;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	const NMPlatformLink *plink;
 
-	g_object_remove_weak_pointer (G_OBJECT (self), (gpointer *) &data->self);
+	c_list_unlink (&data->lst);
+	g_slice_free (PlatformLinkCbData, data);
 
-	l = nm_platform_link_get (NM_PLATFORM_GET, data->ifindex);
-	if (l) {
-		NMPlatformLink pllink;
+	plink = nm_platform_link_get (priv->platform, ifindex);
+	if (plink) {
+		const NMPObject *plink_keep_alive = nmp_object_ref (NMP_OBJECT_UP_CAST (plink));
 
-		pllink = *l; /* make a copy of the link instance */
-		platform_link_added (self, data->ifindex, &pllink, FALSE, NULL);
+		platform_link_added (self, ifindex, plink, FALSE, NULL);
+		nmp_object_unref (plink_keep_alive);
 	} else {
 		NMDevice *device;
 		GError *error = NULL;
 
-		device = nm_manager_get_device_by_ifindex (self, data->ifindex);
+		device = nm_manager_get_device_by_ifindex (self, ifindex);
 		if (device) {
 			if (nm_device_is_software (device)) {
 				nm_device_sys_iface_state_set (device, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
@@ -2454,6 +2498,8 @@ _platform_link_cb_idle (PlatformLinkCbData *data)
 					_LOG2W (LOGD_DEVICE, device, "failed to unrealize: %s", error->message);
 					g_clear_error (&error);
 					remove_device (self, device, FALSE, TRUE);
+				} else {
+					nm_device_update_from_platform_link (device, NULL);
 				}
 			} else {
 				/* Hardware and external devices always get removed when their kernel link is gone */
@@ -2462,8 +2508,6 @@ _platform_link_cb_idle (PlatformLinkCbData *data)
 		}
 	}
 
-out:
-	g_slice_free (PlatformLinkCbData, data);
 	return G_SOURCE_REMOVE;
 }
 
@@ -2475,17 +2519,22 @@ platform_link_cb (NMPlatform *platform,
                   int change_type_i,
                   gpointer user_data)
 {
+	NMManager *self;
+	NMManagerPrivate *priv;
 	const NMPlatformSignalChangeType change_type = change_type_i;
 	PlatformLinkCbData *data;
 
 	switch (change_type) {
 	case NM_PLATFORM_SIGNAL_ADDED:
 	case NM_PLATFORM_SIGNAL_REMOVED:
+		self = NM_MANAGER (user_data);
+		priv = NM_MANAGER_GET_PRIVATE (self);
+
 		data = g_slice_new (PlatformLinkCbData);
-		data->self = NM_MANAGER (user_data);
+		data->self = self;
 		data->ifindex = ifindex;
-		g_object_add_weak_pointer (G_OBJECT (data->self), (gpointer *) &data->self);
-		g_idle_add ((GSourceFunc) _platform_link_cb_idle, data);
+		c_list_link_tail (&priv->link_cb_lst, &data->lst);
+		data->idle_id = g_idle_add ((GSourceFunc) _platform_link_cb_idle, data);
 		break;
 	default:
 		break;
@@ -2495,32 +2544,32 @@ platform_link_cb (NMPlatform *platform,
 static void
 platform_query_devices (NMManager *self)
 {
-	GArray *links_array;
-	NMPlatformLink *links;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	gs_unref_ptrarray GPtrArray *links = NULL;
 	int i;
 	gboolean guess_assume;
-	const char *order;
+	gs_free char *order = NULL;
 
 	guess_assume = nm_config_get_first_start (nm_config_get ());
-	order = nm_config_data_get_value_cached (NM_CONFIG_GET_DATA,
-	                                         NM_CONFIG_KEYFILE_GROUP_MAIN,
-	                                         NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
-	                                         NM_CONFIG_GET_VALUE_STRIP);
-	links_array = nm_platform_link_get_all (NM_PLATFORM_GET, !nm_streq0 (order, "index"));
-	links = (NMPlatformLink *) links_array->data;
-	for (i = 0; i < links_array->len; i++) {
+	order = nm_config_data_get_value (NM_CONFIG_GET_DATA,
+	                                  NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                                  NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
+	                                  NM_CONFIG_GET_VALUE_STRIP);
+	links = nm_platform_link_get_all (priv->platform, !nm_streq0 (order, "index"));
+	if (!links)
+		return;
+	for (i = 0; i < links->len; i++) {
+		const NMPlatformLink *link = NMP_OBJECT_CAST_LINK (links->pdata[i]);
 		gs_free NMConfigDeviceStateData *dev_state = NULL;
 
-		dev_state = nm_config_device_state_load (links[i].ifindex);
+		dev_state = nm_config_device_state_load (link->ifindex);
 
 		platform_link_added (self,
-		                     links[i].ifindex,
-		                     &links[i],
+		                     link->ifindex,
+		                     link,
 		                     guess_assume && (!dev_state || !dev_state->connection_uuid),
 		                     dev_state);
 	}
-
-	g_array_unref (links_array);
 }
 
 static void
@@ -3090,14 +3139,15 @@ autoconnect_slaves (NMManager *self,
 	if (should_connect_slaves (NM_CONNECTION (master_connection), master_device)) {
 		gs_free SlaveConnectionInfo *slaves = NULL;
 		guint i, n_slaves = 0;
-		const char *value;
 
 		slaves = find_slaves (self, master_connection, master_device, &n_slaves);
 		if (n_slaves > 1) {
-			value = nm_config_data_get_value_cached (NM_CONFIG_GET_DATA,
-			                                         NM_CONFIG_KEYFILE_GROUP_MAIN,
-			                                         NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
-			                                         NM_CONFIG_GET_VALUE_STRIP);
+			gs_free char *value = NULL;
+
+			value = nm_config_data_get_value (NM_CONFIG_GET_DATA,
+			                                  NM_CONFIG_KEYFILE_GROUP_MAIN,
+			                                  NM_CONFIG_KEYFILE_KEY_MAIN_SLAVES_ORDER,
+			                                  NM_CONFIG_GET_VALUE_STRIP);
 			g_qsort_with_data (slaves, n_slaves, sizeof (slaves[0]),
 			                   compare_slaves,
 			                   GINT_TO_POINTER (!nm_streq0 (value, "index")));
@@ -3197,7 +3247,10 @@ unmanaged_to_disconnected (NMDevice *device)
 
 	nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_USER_EXPLICIT, FALSE, NM_DEVICE_STATE_REASON_USER_REQUESTED);
 
-	g_return_if_fail (nm_device_get_managed (device, FALSE));
+	if (!nm_device_get_managed (device, FALSE)) {
+		/* the device is still marked as unmanaged. Nothing to do. */
+		return;
+	}
 
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_UNMANAGED) {
 		nm_device_state_changed (device,
@@ -3806,10 +3859,11 @@ validate_activation_request (NMManager *self,
 		device = nm_manager_get_best_device_for_connection (self, connection, TRUE, NULL);
 
 	if (!device && !vpn) {
-		gboolean is_software = nm_connection_is_virtual (connection);
+		gs_free char *iface = NULL;
 
-		/* VPN and software-device connections don't need a device yet */
-		if (!is_software) {
+		/* 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,
@@ -3817,17 +3871,12 @@ validate_activation_request (NMManager *self,
 			goto error;
 		}
 
-		if (is_software) {
-			char *iface;
-
-			/* Look for an existing device with the connection's interface name */
-			iface = nm_manager_get_connection_iface (self, connection, NULL, error);
-			if (!iface)
-				goto error;
+		/* Look for an existing device with the connection's interface name */
+		iface = nm_manager_get_connection_iface (self, connection, NULL, error);
+		if (!iface)
+			goto error;
 
-			device = find_device_by_iface (self, iface, connection, NULL);
-			g_free (iface);
-		}
+		device = find_device_by_iface (self, iface, connection, NULL);
 	}
 
 	if ((!vpn || device_path) && !device) {
@@ -3922,8 +3971,8 @@ impl_manager_activate_connection (NMManager *self,
 		connection = nm_settings_get_connection_by_path (priv->settings, connection_path);
 		if (!connection) {
 			error = g_error_new_literal (NM_MANAGER_ERROR,
-						     NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
-						     "Connection could not be found.");
+			                             NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
+			                             "Connection could not be found.");
 			goto error;
 		}
 	} else {
@@ -4011,8 +4060,9 @@ activation_add_done (NMSettings *settings,
 
 		if (_internal_activate_generic (self, active, &local)) {
 			nm_settings_connection_commit_changes (new_connection,
+			                                       NULL,
 			                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED,
-			                                       NULL, NULL);
+			                                       NULL);
 			g_dbus_method_invocation_return_value (
 			    context,
 			    g_variant_new ("(oo)",
@@ -4032,7 +4082,7 @@ activation_add_done (NMSettings *settings,
 	g_assert (error);
 	_internal_activation_failed (self, active, error->message);
 	if (new_connection)
-		nm_settings_connection_delete (new_connection, NULL, NULL);
+		nm_settings_connection_delete (new_connection, NULL);
 	g_dbus_method_invocation_return_gerror (context, error);
 	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
 	                            NULL,
@@ -4155,7 +4205,7 @@ impl_manager_add_and_activate_connection (NMManager *self,
 			goto error;
 		}
 
-		nm_utils_complete_generic (NM_PLATFORM_GET,
+		nm_utils_complete_generic (priv->platform,
 		                           connection,
 		                           NM_SETTING_VPN_SETTING_NAME,
 		                           all_connections,
@@ -4372,9 +4422,9 @@ done:
 }
 
 static gboolean
-device_is_wake_on_lan (NMDevice *device)
+device_is_wake_on_lan (NMPlatform *platform, NMDevice *device)
 {
-	return nm_platform_link_get_wake_on_lan (NM_PLATFORM_GET, nm_device_get_ip_ifindex (device));
+	return nm_platform_link_get_wake_on_lan (platform, nm_device_get_ip_ifindex (device));
 }
 
 static gboolean
@@ -4492,7 +4542,8 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 			if (nm_device_is_software (device))
 				continue;
 			/* Wake-on-LAN devices will be taken down post-suspend rather than pre- */
-			if (suspending && device_is_wake_on_lan (device)) {
+			if (   suspending
+			    && device_is_wake_on_lan (priv->platform, device)) {
 				_LOGD (LOGD_SUSPEND, "sleep: device %s has wake-on-lan, skipping",
 				       nm_device_get_ip_iface (device));
 				continue;
@@ -4523,7 +4574,7 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 				/* Belatedly take down Wake-on-LAN devices; ideally we wouldn't have to do this
 				 * but for now it's the only way to make sure we re-check their connectivity.
 				 */
-				if (device_is_wake_on_lan (device))
+				if (device_is_wake_on_lan (priv->platform, device))
 					nm_device_set_unmanaged_by_flags (device, NM_UNMANAGED_SLEEPING, TRUE, NM_DEVICE_STATE_REASON_SLEEPING);
 
 				/* Check if the device is unmanaged but the state transition is still pending.
@@ -4871,6 +4922,7 @@ get_permissions_done_cb (NMAuthChain *chain,
 		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_RELOAD);
 		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);
 		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS);
+		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK);
 
 		g_dbus_method_invocation_return_value (context,
 		                                       g_variant_new ("(a{ss})", &results));
@@ -4912,6 +4964,7 @@ impl_manager_get_permissions (NMManager *self,
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_RELOAD, FALSE);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, FALSE);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS, FALSE);
+	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, FALSE);
 }
 
 static void
@@ -5095,7 +5148,7 @@ nm_manager_write_device_state (NMManager *self)
 			continue;
 		}
 
-		if (!nm_platform_link_get (NM_PLATFORM_GET, ifindex))
+		if (!nm_platform_link_get (priv->platform, ifindex))
 			continue;
 
 		managed = nm_device_get_managed (device, FALSE);
@@ -5174,15 +5227,15 @@ nm_manager_start (NMManager *self, GError **error)
 	       priv->net_enabled ? "enabled" : "disabled");
 
 	system_unmanaged_devices_changed_cb (priv->settings, NULL, self);
-	system_hostname_changed_cb (priv->settings, NULL, self);
+	hostname_changed_cb (priv->hostname_manager, NULL, self);
 
 	/* Start device factories */
 	nm_device_factory_manager_load_factories (_register_device_factory, self);
 	nm_device_factory_manager_for_each_factory (start_factory, NULL);
 
-	nm_platform_process_events (NM_PLATFORM_GET);
+	nm_platform_process_events (priv->platform);
 
-	g_signal_connect (NM_PLATFORM_GET,
+	g_signal_connect (priv->platform,
 	                  NM_PLATFORM_SIGNAL_LINK_CHANGED,
 	                  G_CALLBACK (platform_link_cb),
 	                  self);
@@ -5567,6 +5620,10 @@ prop_filter (GDBusConnection *connection,
 			permission = NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS;
 			audit_op = NM_AUDIT_OP_NET_CONTROL;
 			expected_type = G_VARIANT_TYPE ("a{sv}");
+		} else if (!strcmp (propname, "ConnectivityCheckEnabled")) {
+			glib_propname = NM_MANAGER_CONNECTIVITY_CHECK_ENABLED;
+			permission = NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK;
+			audit_op = NM_AUDIT_OP_NET_CONTROL;
 		} else
 			return message;
 		interface_type = NMDBUS_TYPE_MANAGER_SKELETON;
@@ -6069,12 +6126,15 @@ constructed (GObject *object)
 	                  G_CALLBACK (settings_startup_complete_changed), self);
 	g_signal_connect (priv->settings, "notify::" NM_SETTINGS_UNMANAGED_SPECS,
 	                  G_CALLBACK (system_unmanaged_devices_changed_cb), self);
-	g_signal_connect (priv->settings, "notify::" NM_SETTINGS_HOSTNAME,
-	                  G_CALLBACK (system_hostname_changed_cb), self);
 	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
 	                  G_CALLBACK (connection_added_cb), self);
 	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
 	                  G_CALLBACK (connection_updated_cb), self);
+
+	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
+	g_signal_connect (priv->hostname_manager, "notify::" NM_HOSTNAME_MANAGER_HOSTNAME,
+	                  G_CALLBACK (hostname_changed_cb), self);
+
 	/*
 	 * Do not delete existing virtual devices to keep connectivity up.
 	 * Virtual devices are reused when NetworkManager is restarted.
@@ -6126,6 +6186,10 @@ nm_manager_init (NMManager *self)
 	guint i;
 	GFile *file;
 
+	c_list_init (&priv->link_cb_lst);
+
+	priv->platform = g_object_ref (NM_PLATFORM_GET);
+
 	priv->capabilities = g_array_new (FALSE, FALSE, sizeof (guint32));
 
 	/* Initialize rfkill structures and states */
@@ -6210,6 +6274,7 @@ get_property (GObject *object, guint prop_id,
 	NMConfigData *config_data;
 	const NMGlobalDnsConfig *dns_config;
 	const char *type;
+	NMConnectivity *connectivity;
 
 	switch (prop_id) {
 	case PROP_VERSION:
@@ -6254,6 +6319,14 @@ get_property (GObject *object, guint prop_id,
 	case PROP_CONNECTIVITY:
 		g_value_set_uint (value, priv->connectivity_state);
 		break;
+	case PROP_CONNECTIVITY_CHECK_AVAILABLE:
+		config_data = nm_config_get_data (priv->config);
+		g_value_set_boolean (value, nm_config_data_get_connectivity_uri (config_data) != NULL);
+		break;
+	case PROP_CONNECTIVITY_CHECK_ENABLED:
+		connectivity = nm_connectivity_get ();
+		g_value_set_boolean (value, nm_connectivity_check_enabled (connectivity));
+		break;
 	case PROP_PRIMARY_CONNECTION:
 		nm_utils_g_value_set_object_path (value, priv->primary_connection);
 		break;
@@ -6271,9 +6344,6 @@ get_property (GObject *object, guint prop_id,
 	case PROP_ACTIVATING_CONNECTION:
 		nm_utils_g_value_set_object_path (value, priv->activating_connection);
 		break;
-	case PROP_HOSTNAME:
-		g_value_set_string (value, priv->hostname);
-		break;
 	case PROP_SLEEPING:
 		g_value_set_boolean (value, priv->sleeping);
 		break;
@@ -6320,6 +6390,10 @@ set_property (GObject *object, guint prop_id,
 	case PROP_WIMAX_ENABLED:
 		/* WIMAX is depreacted. This does nothing. */
 		break;
+	case PROP_CONNECTIVITY_CHECK_ENABLED:
+		nm_config_set_connectivity_check_enabled (priv->config,
+		                                          g_value_get_boolean (value));
+		break;
 	case PROP_GLOBAL_DNS_CONFIGURATION:
 		dns_config = nm_global_dns_config_from_dbus (value, &error);
 		if (!error)
@@ -6347,8 +6421,20 @@ _deinit_device_factory (NMDeviceFactory *factory, gpointer user_data)
 static void
 dispose (GObject *object)
 {
-	NMManager *manager = NM_MANAGER (object);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
+	NMManager *self = NM_MANAGER (object);
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	CList *iter, *iter_safe;
+
+	g_signal_handlers_disconnect_by_func (priv->platform,
+	                                      G_CALLBACK (platform_link_cb),
+	                                      self);
+	c_list_for_each_safe (iter, iter_safe, &priv->link_cb_lst) {
+		PlatformLinkCbData *data = c_list_entry (iter, PlatformLinkCbData, lst);
+
+		g_source_remove (data->idle_id);
+		c_list_unlink (iter);
+		g_slice_free (PlatformLinkCbData, data);
+	}
 
 	g_slist_free_full (priv->auth_chains, (GDestroyNotify) nm_auth_chain_unref);
 	priv->auth_chains = NULL;
@@ -6363,7 +6449,7 @@ dispose (GObject *object)
 	if (priv->auth_mgr) {
 		g_signal_handlers_disconnect_by_func (priv->auth_mgr,
 		                                      G_CALLBACK (auth_mgr_changed),
-		                                      manager);
+		                                      self);
 		g_clear_object (&priv->auth_mgr);
 	}
 
@@ -6372,52 +6458,54 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->ac_cleanup_id);
 
 	while (priv->active_connections)
-		active_connection_remove (manager, NM_ACTIVE_CONNECTION (priv->active_connections->data));
+		active_connection_remove (self, NM_ACTIVE_CONNECTION (priv->active_connections->data));
 	g_clear_pointer (&priv->active_connections, g_slist_free);
 	g_clear_object (&priv->primary_connection);
 	g_clear_object (&priv->activating_connection);
 
 	if (priv->config) {
-		g_signal_handlers_disconnect_by_func (priv->config, _config_changed_cb, manager);
+		g_signal_handlers_disconnect_by_func (priv->config, _config_changed_cb, self);
 		g_clear_object (&priv->config);
 	}
 
-	g_free (priv->hostname);
-
 	if (priv->policy) {
-		g_signal_handlers_disconnect_by_func (priv->policy, policy_default_device_changed, manager);
-		g_signal_handlers_disconnect_by_func (priv->policy, policy_activating_device_changed, manager);
+		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_clear_object (&priv->policy);
 	}
 
 	if (priv->settings) {
-		g_signal_handlers_disconnect_by_func (priv->settings, settings_startup_complete_changed, manager);
-		g_signal_handlers_disconnect_by_func (priv->settings, system_unmanaged_devices_changed_cb, manager);
-		g_signal_handlers_disconnect_by_func (priv->settings, system_hostname_changed_cb, manager);
-		g_signal_handlers_disconnect_by_func (priv->settings, connection_added_cb, manager);
-		g_signal_handlers_disconnect_by_func (priv->settings, connection_updated_cb, manager);
+		g_signal_handlers_disconnect_by_func (priv->settings, settings_startup_complete_changed, self);
+		g_signal_handlers_disconnect_by_func (priv->settings, system_unmanaged_devices_changed_cb, self);
+		g_signal_handlers_disconnect_by_func (priv->settings, connection_added_cb, self);
+		g_signal_handlers_disconnect_by_func (priv->settings, connection_updated_cb, self);
 		g_clear_object (&priv->settings);
 	}
 
+	if (priv->hostname_manager) {
+		g_signal_handlers_disconnect_by_func (priv->hostname_manager, hostname_changed_cb, self);
+		g_clear_object (&priv->hostname_manager);
+	}
+
 	g_clear_object (&priv->vpn_manager);
 
 	/* Unregister property filter */
 	if (priv->dbus_mgr) {
-		g_signal_handlers_disconnect_by_func (priv->dbus_mgr, dbus_connection_changed_cb, manager);
+		g_signal_handlers_disconnect_by_func (priv->dbus_mgr, dbus_connection_changed_cb, self);
 		g_clear_object (&priv->dbus_mgr);
 	}
-	_set_prop_filter (manager, NULL);
+	_set_prop_filter (self, NULL);
 
-	sleep_devices_clear (manager);
+	sleep_devices_clear (self);
 	g_clear_pointer (&priv->sleep_devices, g_hash_table_unref);
 
 	if (priv->sleep_monitor) {
-		g_signal_handlers_disconnect_by_func (priv->sleep_monitor, sleeping_cb, manager);
+		g_signal_handlers_disconnect_by_func (priv->sleep_monitor, sleeping_cb, self);
 		g_clear_object (&priv->sleep_monitor);
 	}
 
 	if (priv->fw_monitor) {
-		g_signal_handlers_disconnect_by_func (priv->fw_monitor, firmware_dir_changed, manager);
+		g_signal_handlers_disconnect_by_func (priv->fw_monitor, firmware_dir_changed, self);
 
 		nm_clear_g_source (&priv->fw_changed_id);
 
@@ -6426,11 +6514,11 @@ dispose (GObject *object)
 	}
 
 	if (priv->rfkill_mgr) {
-		g_signal_handlers_disconnect_by_func (priv->rfkill_mgr, rfkill_manager_rfkill_changed_cb, manager);
+		g_signal_handlers_disconnect_by_func (priv->rfkill_mgr, rfkill_manager_rfkill_changed_cb, self);
 		g_clear_object (&priv->rfkill_mgr);
 	}
 
-	nm_device_factory_manager_for_each_factory (_deinit_device_factory, manager);
+	nm_device_factory_manager_for_each_factory (_deinit_device_factory, self);
 
 	nm_clear_g_source (&priv->timestamp_update_id);
 
@@ -6445,6 +6533,8 @@ finalize (GObject *object)
 	g_array_free (priv->capabilities, TRUE);
 
 	G_OBJECT_CLASS (nm_manager_parent_class)->finalize (object);
+
+	g_object_unref (priv->platform);
 }
 
 static void
@@ -6542,6 +6632,18 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                       G_PARAM_READABLE |
 	                       G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_CONNECTIVITY_CHECK_AVAILABLE] =
+	    g_param_spec_boolean (NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE, "", "",
+	                          TRUE,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_CONNECTIVITY_CHECK_ENABLED] =
+	    g_param_spec_boolean (NM_MANAGER_CONNECTIVITY_CHECK_ENABLED, "", "",
+	                          TRUE,
+	                          G_PARAM_READWRITE |
+	                          G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_PRIMARY_CONNECTION] =
 	    g_param_spec_string (NM_MANAGER_PRIMARY_CONNECTION, "", "",
 	                         NULL,
@@ -6560,13 +6662,6 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                         G_PARAM_READABLE |
 	                         G_PARAM_STATIC_STRINGS);
 
-	/* Hostname is not exported over D-Bus */
-	obj_properties[PROP_HOSTNAME] =
-	    g_param_spec_string (NM_MANAGER_HOSTNAME, "", "",
-	                         NULL,
-	                         G_PARAM_READABLE |
-	                         G_PARAM_STATIC_STRINGS);
-
 	/* Sleeping is not exported over D-Bus */
 	obj_properties[PROP_SLEEPING] =
 	    g_param_spec_boolean (NM_MANAGER_SLEEPING, "", "",
diff --git a/src/nm-manager.h b/src/nm-manager.h
index 676fa995..622edb5b 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -45,6 +45,8 @@
 #define NM_MANAGER_WIMAX_HARDWARE_ENABLED "wimax-hardware-enabled"
 #define NM_MANAGER_ACTIVE_CONNECTIONS "active-connections"
 #define NM_MANAGER_CONNECTIVITY "connectivity"
+#define NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE "connectivity-check-available"
+#define NM_MANAGER_CONNECTIVITY_CHECK_ENABLED "connectivity-check-enabled"
 #define NM_MANAGER_PRIMARY_CONNECTION "primary-connection"
 #define NM_MANAGER_PRIMARY_CONNECTION_TYPE "primary-connection-type"
 #define NM_MANAGER_ACTIVATING_CONNECTION "activating-connection"
@@ -54,7 +56,6 @@
 #define NM_MANAGER_ALL_DEVICES "all-devices"
 
 /* Not exported */
-#define NM_MANAGER_HOSTNAME "hostname"
 #define NM_MANAGER_SLEEPING "sleeping"
 
 /* signals */
@@ -125,4 +126,11 @@ gboolean            nm_manager_deactivate_connection   (NMManager *manager,
 
 void                nm_manager_set_capability   (NMManager *self, NMCapability cap);
 
+NMDevice *          nm_manager_get_device    (NMManager *self,
+                                              const char *ifname,
+                                              NMDeviceType device_type);
+gboolean            nm_manager_remove_device (NMManager *self,
+                                              const char *ifname,
+                                              NMDeviceType device_type);
+
 #endif /* __NETWORKMANAGER_MANAGER_H__ */
diff --git a/src/nm-multi-index.c b/src/nm-multi-index.c
deleted file mode 100644
index 6ae4c21f..00000000
--- a/src/nm-multi-index.c
+++ /dev/null
@@ -1,473 +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) 2015 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-multi-index.h"
-
-#include <string.h>
-
-struct NMMultiIndex {
-	NMMultiIndexFuncEqual equal_fcn;
-	NMMultiIndexFuncClone clone_fcn;
-	GHashTable *hash;
-};
-
-typedef struct {
-	/* when storing the first item for a multi-index id, we don't yet create
-	 * the hashtable @index. Instead we store it inplace to @value0. Note that
-	 * &values_data->value0 is a NULL terminated array with one item that is
-	 * suitable to be returned directly from nm_multi_index_lookup(). */
-	union {
-		gpointer value0;
-		gpointer *values;
-	};
-	GHashTable *index;
-} ValuesData;
-
-/*****************************************************************************/
-
-static void
-_values_data_destroy (ValuesData *values_data)
-{
-	if (values_data->index) {
-		g_free (values_data->values);
-		g_hash_table_unref (values_data->index);
-	}
-	g_slice_free (ValuesData, values_data);
-}
-
-static gboolean
-_values_data_contains (ValuesData *values_data, gconstpointer value)
-{
-	return values_data->index
-	       ? g_hash_table_contains (values_data->index, value)
-	       : value == values_data->value0;
-}
-
-static void
-_values_data_get_data (ValuesData *values_data,
-                       void *const**out_data,
-                       guint *out_len)
-{
-	guint i, len;
-	gpointer *values;
-	GHashTableIter iter;
-
-	nm_assert (values_data);
-
-	if (!values_data->index) {
-		NM_SET_OUT (out_data, &values_data->value0);
-		NM_SET_OUT (out_len, 1);
-		return;
-	}
-
-	nm_assert (values_data->index && g_hash_table_size (values_data->index) > 0);
-
-	if (!values_data->values) {
-		len = g_hash_table_size (values_data->index);
-		values = g_new (gpointer, len + 1);
-
-		g_hash_table_iter_init (&iter, values_data->index);
-		for (i = 0; g_hash_table_iter_next (&iter, &values[i], NULL); i++)
-			nm_assert (i < len);
-		nm_assert (i == len);
-		values[i] = NULL;
-
-		values_data->values = values;
-		NM_SET_OUT (out_len, len);
-	} else if (out_len)
-		NM_SET_OUT (out_len, g_hash_table_size (values_data->index));
-
-	NM_SET_OUT (out_data, values_data->values);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_multi_index_lookup():
- * @index:
- * @id:
- * @out_len: (allow-none): output the number of values
- *   that are returned.
- *
- * Returns: (transfer none): %NULL if there are no values
- *   or a %NULL terminated array of pointers.
- */
-void *const*
-nm_multi_index_lookup (const NMMultiIndex *index,
-                       const NMMultiIndexId *id,
-                       guint *out_len)
-{
-	ValuesData *values_data;
-	void *const*values;
-
-	g_return_val_if_fail (index, NULL);
-	g_return_val_if_fail (id, NULL);
-
-	values_data = g_hash_table_lookup (index->hash, id);
-	if (!values_data) {
-		if (out_len)
-			*out_len = 0;
-		return NULL;
-	}
-	_values_data_get_data (values_data, &values, out_len);
-	return values;
-}
-
-gboolean
-nm_multi_index_contains (const NMMultiIndex *index,
-                         const NMMultiIndexId *id,
-                         gconstpointer value)
-{
-	ValuesData *values_data;
-
-	g_return_val_if_fail (index, FALSE);
-	g_return_val_if_fail (id, FALSE);
-	g_return_val_if_fail (value, FALSE);
-
-	values_data = g_hash_table_lookup (index->hash, id);
-	return values_data && _values_data_contains (values_data, value);
-}
-
-const NMMultiIndexId *
-nm_multi_index_lookup_first_by_value (const NMMultiIndex *index,
-                                      gconstpointer value)
-{
-	GHashTableIter iter;
-	const NMMultiIndexId *id;
-	ValuesData *values_data;
-
-	g_return_val_if_fail (index, NULL);
-	g_return_val_if_fail (value, NULL);
-
-	/* reverse-lookup needs to iterate over all hash tables. It should
-	 * still be fairly quick, if the number of hash tables is small.
-	 * There is no O(1) reverse lookup implemented, because this access
-	 * pattern is not what NMMultiIndex is here for.
-	 * You are supposed to use NMMultiIndex by always knowing which @id
-	 * a @value has.
-	 */
-
-	g_hash_table_iter_init (&iter, index->hash);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &values_data)) {
-		if (_values_data_contains (values_data, value))
-			return id;
-	}
-	return NULL;
-}
-
-void
-nm_multi_index_foreach (const NMMultiIndex *index,
-                        gconstpointer value,
-                        NMMultiIndexFuncForeach foreach_func,
-                        gpointer user_data)
-{
-	GHashTableIter iter;
-	const NMMultiIndexId *id;
-	ValuesData *values_data;
-	guint len;
-	void *const*values;
-
-	g_return_if_fail (index);
-	g_return_if_fail (foreach_func);
-
-	g_hash_table_iter_init (&iter, index->hash);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &values_data)) {
-		if (value && !_values_data_contains (values_data, value))
-			continue;
-
-		_values_data_get_data (values_data, &values, &len);
-		if (!foreach_func (id, values, len, user_data))
-			return;
-	}
-}
-
-void
-nm_multi_index_iter_init (NMMultiIndexIter *iter,
-                          const NMMultiIndex *index,
-                          gconstpointer value)
-{
-	g_return_if_fail (index);
-	g_return_if_fail (iter);
-
-	g_hash_table_iter_init (&iter->_iter, index->hash);
-	iter->_index = index;
-	iter->_value = value;
-}
-
-gboolean
-nm_multi_index_iter_next (NMMultiIndexIter *iter,
-                          const NMMultiIndexId **out_id,
-                          void *const**out_values,
-                          guint *out_len)
-{
-	const NMMultiIndexId *id;
-	ValuesData *values_data;
-
-	g_return_val_if_fail (iter, FALSE);
-
-	while (g_hash_table_iter_next (&iter->_iter, (gpointer *) &id, (gpointer *) &values_data)) {
-		if (   !iter->_value
-		    || _values_data_contains (values_data, iter->_value)) {
-			if (out_values || out_len)
-				_values_data_get_data (values_data, out_values, out_len);
-			if (out_id)
-				*out_id = id;
-			return TRUE;
-		}
-	}
-	return FALSE;
-}
-
-/*****************************************************************************/
-
-void
-nm_multi_index_id_iter_init (NMMultiIndexIdIter *iter,
-                             const NMMultiIndex *index,
-                             const NMMultiIndexId *id)
-{
-	ValuesData *values_data;
-
-	g_return_if_fail (index);
-	g_return_if_fail (iter);
-	g_return_if_fail (id);
-
-	values_data = g_hash_table_lookup (index->hash, id);
-	if (!values_data)
-		iter->_state = 2;
-	else if (values_data->index) {
-		iter->_state = 1;
-		g_hash_table_iter_init (&iter->_iter, values_data->index);
-	} else {
-		iter->_state = 0;
-		iter->_value = values_data->value0;
-	}
-}
-
-gboolean
-nm_multi_index_id_iter_next (NMMultiIndexIdIter *iter,
-                             void **out_value)
-{
-	g_return_val_if_fail (iter, FALSE);
-
-	switch (iter->_state) {
-	case 0:
-		iter->_state = 2;
-		NM_SET_OUT (out_value, iter->_value);
-		return TRUE;
-	case 1:
-		return g_hash_table_iter_next (&iter->_iter, out_value, NULL);
-	case 2:
-		iter->_state = 3;
-		return FALSE;
-	default:
-		g_return_val_if_reached (FALSE);
-	}
-}
-
-/*****************************************************************************/
-
-static gboolean
-_do_add (NMMultiIndex *index,
-         const NMMultiIndexId *id,
-         gconstpointer value)
-{
-	ValuesData *values_data;
-
-	values_data = g_hash_table_lookup (index->hash, id);
-	if (!values_data) {
-		NMMultiIndexId *id_new;
-
-		/* Contrary to GHashTable, we don't take ownership of the @id that was
-		 * provided to nm_multi_index_add(). Instead we clone it via @clone_fcn
-		 * when needed.
-		 *
-		 * The reason is, that we expect in most cases that there exists
-		 * already a @id so that we don't need ownership of it (or clone it).
-		 * By doing this, the caller can pass a stack allocated @id or
-		 * reuse the @id for other insertions.
-		 */
-		id_new = index->clone_fcn (id);
-		if (!id_new)
-			g_return_val_if_reached (FALSE);
-
-		values_data = g_slice_new0 (ValuesData);
-		values_data->value0 = (gpointer) value;
-
-		g_hash_table_insert (index->hash, id_new, values_data);
-	} else {
-		if (!values_data->index) {
-			if (values_data->value0 == value)
-				return FALSE;
-			values_data->index = g_hash_table_new (NULL, NULL);
-			g_hash_table_replace (values_data->index, (gpointer) value, (gpointer) value);
-			g_hash_table_replace (values_data->index, values_data->value0, values_data->value0);
-			values_data->values = NULL;
-		} else {
-			if (!nm_g_hash_table_replace (values_data->index, (gpointer) value, (gpointer) value))
-				return FALSE;
-			g_clear_pointer (&values_data->values, g_free);
-		}
-	}
-	return TRUE;
-}
-
-static gboolean
-_do_remove (NMMultiIndex *index,
-            const NMMultiIndexId *id,
-            gconstpointer value)
-{
-	ValuesData *values_data;
-
-	values_data = g_hash_table_lookup (index->hash, id);
-	if (!values_data)
-		return FALSE;
-
-	if (values_data->index) {
-		if (!g_hash_table_remove (values_data->index, value))
-			return FALSE;
-		if (g_hash_table_size (values_data->index) == 0)
-			g_hash_table_remove (index->hash, id);
-		else
-			g_clear_pointer (&values_data->values, g_free);
-	} else {
-		if (values_data->value0 != value)
-			return FALSE;
-		g_hash_table_remove (index->hash, id);
-	}
-
-	return TRUE;
-}
-
-gboolean
-nm_multi_index_add (NMMultiIndex *index,
-                    const NMMultiIndexId *id,
-                    gconstpointer value)
-{
-	g_return_val_if_fail (index, FALSE);
-	g_return_val_if_fail (id, FALSE);
-	g_return_val_if_fail (value, FALSE);
-
-	return _do_add (index, id, value);
-}
-
-gboolean
-nm_multi_index_remove (NMMultiIndex *index,
-                       const NMMultiIndexId *id,
-                       gconstpointer value)
-{
-	g_return_val_if_fail (index, FALSE);
-	g_return_val_if_fail (value, FALSE);
-
-	if (!id)
-		g_return_val_if_reached (FALSE);
-	return _do_remove (index, id, value);
-}
-
-/**
- * nm_multi_index_move:
- * @index:
- * @id_old: (allow-none): remove @value at @id_old
- * @id_new: (allow-none): add @value under @id_new
- * @value: the value to add
- *
- * Similar to a remove(), followed by an add(). The difference
- * is, that we allow %NULL for both @id_old and @id_new.
- * And the return value indicates whether @value was successfully
- * removed *and* added.
- *
- * Returns: %TRUE, if the value was removed from @id_old and added
- *   as %id_new. %FALSE could mean, that @value was not added to @id_old
- *   before, or that that @value was already part of @id_new. */
-gboolean
-nm_multi_index_move (NMMultiIndex *index,
-                     const NMMultiIndexId *id_old,
-                     const NMMultiIndexId *id_new,
-                     gconstpointer value)
-{
-	g_return_val_if_fail (index, FALSE);
-	g_return_val_if_fail (value, FALSE);
-
-	if (!id_old && !id_new) {
-		/* nothing to do, @value was and is not in @index. */
-		return TRUE;
-	} if (!id_old) {
-		/* add @value to @index with @id_new */
-		return _do_add (index, id_new, value);
-	} else if (!id_new) {
-		/* remove @value from @index with @id_old */
-		return _do_remove (index, id_old, value);
-	} else if (index->equal_fcn (id_old, id_new)) {
-		if (_do_add (index, id_new, value)) {
-			/* we would expect, that @value is already in @index,
-			 * Return %FALSE, if it wasn't. */
-			return FALSE;
-		}
-		return TRUE;
-	} else {
-		gboolean did_remove;
-
-		did_remove = _do_remove (index, id_old, value);
-		return _do_add (index, id_new, value) && did_remove;
-	}
-}
-
-/*****************************************************************************/
-
-guint
-nm_multi_index_get_num_groups (const NMMultiIndex *index)
-{
-	g_return_val_if_fail (index, 0);
-	return g_hash_table_size (index->hash);
-}
-
-NMMultiIndex *
-nm_multi_index_new (NMMultiIndexFuncHash hash_fcn,
-                    NMMultiIndexFuncEqual equal_fcn,
-                    NMMultiIndexFuncClone clone_fcn,
-                    NMMultiIndexFuncDestroy destroy_fcn)
-{
-	NMMultiIndex *index;
-
-	g_return_val_if_fail (hash_fcn, NULL);
-	g_return_val_if_fail (equal_fcn, NULL);
-	g_return_val_if_fail (clone_fcn, NULL);
-	g_return_val_if_fail (destroy_fcn, NULL);
-
-	index = g_new (NMMultiIndex, 1);
-	index->equal_fcn = equal_fcn;
-	index->clone_fcn = clone_fcn;
-
-	index->hash = g_hash_table_new_full ((GHashFunc) hash_fcn,
-	                                     (GEqualFunc) equal_fcn,
-	                                     (GDestroyNotify) destroy_fcn,
-	                                     (GDestroyNotify) _values_data_destroy);
-	return index;
-}
-
-void
-nm_multi_index_free (NMMultiIndex *index)
-{
-	g_return_if_fail (index);
-	g_hash_table_unref (index->hash);
-	g_free (index);
-}
-
diff --git a/src/nm-multi-index.h b/src/nm-multi-index.h
deleted file mode 100644
index fb102574..00000000
--- a/src/nm-multi-index.h
+++ /dev/null
@@ -1,105 +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) 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_MULTI_INDEX__
-#define __NM_MULTI_INDEX__
-
-typedef struct {
-	char _dummy;
-} NMMultiIndexId;
-
-typedef struct NMMultiIndex NMMultiIndex;
-
-typedef struct {
-	GHashTableIter _iter;
-	const NMMultiIndex *_index;
-	gconstpointer _value;
-} NMMultiIndexIter;
-
-typedef struct {
-	union {
-		GHashTableIter _iter;
-		gpointer _value;
-	};
-	guint _state;
-} NMMultiIndexIdIter;
-
-typedef gboolean (*NMMultiIndexFuncEqual) (const NMMultiIndexId *id_a, const NMMultiIndexId *id_b);
-typedef guint (*NMMultiIndexFuncHash) (const NMMultiIndexId *id);
-typedef NMMultiIndexId *(*NMMultiIndexFuncClone) (const NMMultiIndexId *id);
-typedef void (*NMMultiIndexFuncDestroy) (NMMultiIndexId *id);
-
-typedef gboolean (*NMMultiIndexFuncForeach) (const NMMultiIndexId *id, void *const* values, guint len, gpointer user_data);
-
-
-NMMultiIndex *nm_multi_index_new (NMMultiIndexFuncHash hash_fcn,
-                                  NMMultiIndexFuncEqual equal_fcn,
-                                  NMMultiIndexFuncClone clone_fcn,
-                                  NMMultiIndexFuncDestroy destroy_fcn);
-
-void nm_multi_index_free (NMMultiIndex *index);
-
-gboolean nm_multi_index_add (NMMultiIndex *index,
-                             const NMMultiIndexId *id,
-                             gconstpointer value);
-
-gboolean nm_multi_index_remove (NMMultiIndex *index,
-                                const NMMultiIndexId *id,
-                                gconstpointer value);
-
-gboolean nm_multi_index_move (NMMultiIndex *index,
-                              const NMMultiIndexId *id_old,
-                              const NMMultiIndexId *id_new,
-                              gconstpointer value);
-
-guint nm_multi_index_get_num_groups (const NMMultiIndex *index);
-
-void *const*nm_multi_index_lookup (const NMMultiIndex *index,
-                                   const NMMultiIndexId *id,
-                                   guint *out_len);
-
-gboolean nm_multi_index_contains (const NMMultiIndex *index,
-                                  const NMMultiIndexId *id,
-                                  gconstpointer value);
-
-const NMMultiIndexId *nm_multi_index_lookup_first_by_value (const NMMultiIndex *index,
-                                                             gconstpointer value);
-
-void nm_multi_index_foreach (const NMMultiIndex *index,
-                             gconstpointer value,
-                             NMMultiIndexFuncForeach foreach_func,
-                             gpointer user_data);
-
-void nm_multi_index_iter_init (NMMultiIndexIter *iter,
-                               const NMMultiIndex *index,
-                               gconstpointer value);
-gboolean nm_multi_index_iter_next (NMMultiIndexIter *iter,
-                                   const NMMultiIndexId **out_id,
-                                   void *const**out_values,
-                                   guint *out_len);
-
-void nm_multi_index_id_iter_init (NMMultiIndexIdIter *iter,
-                                  const NMMultiIndex *index,
-                                  const NMMultiIndexId *id);
-gboolean nm_multi_index_id_iter_next (NMMultiIndexIdIter *iter,
-                                      void **out_value);
-
-#endif /* __NM_MULTI_INDEX__ */
-
diff --git a/src/nm-netns.c b/src/nm-netns.c
index a81aa696..96ab2b35 100644
--- a/src/nm-netns.c
+++ b/src/nm-netns.c
@@ -22,10 +22,10 @@
 
 #include "nm-netns.h"
 
+#include "nm-utils/nm-dedup-multi.h"
+
 #include "platform/nm-platform.h"
 #include "platform/nmp-netns.h"
-#include "nm-route-manager.h"
-#include "nm-default-route-manager.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 
@@ -38,8 +38,6 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE (
 typedef struct {
 	NMPlatform *platform;
 	NMPNetns *platform_netns;
-	NMRouteManager *route_manager;
-	NMDefaultRouteManager *default_route_manager;
 	bool log_with_ptr;
 } NMNetnsPrivate;
 
@@ -74,16 +72,10 @@ nm_netns_get_platform (NMNetns *self)
 	return NM_NETNS_GET_PRIVATE (self)->platform;
 }
 
-NMDefaultRouteManager *
-nm_netns_get_default_route_manager (NMNetns *self)
-{
-	return NM_NETNS_GET_PRIVATE (self)->default_route_manager;
-}
-
-NMRouteManager *
-nm_netns_get_route_manager (NMNetns *self)
+NMDedupMultiIndex *
+nm_netns_get_multi_idx (NMNetns *self)
 {
-	return NM_NETNS_GET_PRIVATE (self)->route_manager;
+	return nm_platform_get_multi_idx (NM_NETNS_GET_PRIVATE (self)->platform);
 }
 
 /*****************************************************************************/
@@ -129,8 +121,6 @@ constructed (GObject *object)
 	log_with_ptr = nm_platform_get_log_with_ptr (priv->platform);
 
 	priv->platform_netns = nm_platform_netns_get (priv->platform);
-	priv->route_manager = nm_route_manager_new (log_with_ptr, priv->platform);
-	priv->default_route_manager = nm_default_route_manager_new (log_with_ptr, priv->platform);
 
 	G_OBJECT_CLASS (nm_netns_parent_class)->constructed (object);
 }
@@ -149,8 +139,6 @@ dispose (GObject *object)
 	NMNetns *self = NM_NETNS (object);
 	NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE (self);
 
-	g_clear_object (&priv->route_manager);
-	g_clear_object (&priv->default_route_manager);
 	g_clear_object (&priv->platform);
 
 	G_OBJECT_CLASS (nm_netns_parent_class)->dispose (object);
diff --git a/src/nm-netns.h b/src/nm-netns.h
index fd5daf47..ae343cce 100644
--- a/src/nm-netns.h
+++ b/src/nm-netns.h
@@ -39,8 +39,8 @@ NMNetns *nm_netns_new (NMPlatform *platform);
 
 NMPlatform *nm_netns_get_platform (NMNetns *self);
 NMPNetns *nm_netns_get_platform_netns (NMNetns *self);
-NMRouteManager *nm_netns_get_route_manager (NMNetns *self);
-NMDefaultRouteManager *nm_netns_get_default_route_manager (NMNetns *self);
+
+struct _NMDedupMultiIndex *nm_netns_get_multi_idx (NMNetns *self);
 
 #define NM_NETNS_GET (nm_netns_get ())
 
diff --git a/src/nm-pacrunner-manager.c b/src/nm-pacrunner-manager.c
index 87e0a364..08e10e40 100644
--- a/src/nm-pacrunner-manager.c
+++ b/src/nm-pacrunner-manager.c
@@ -27,8 +27,7 @@
 #include "nm-proxy-config.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
-
-static void pacrunner_remove_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data);
+#include "nm-utils/c-list.h"
 
 #define PACRUNNER_DBUS_SERVICE "org.pacrunner"
 #define PACRUNNER_DBUS_INTERFACE "org.pacrunner.Manager"
@@ -37,11 +36,15 @@ static void pacrunner_remove_done (GDBusProxy *proxy, GAsyncResult *res, gpointe
 /*****************************************************************************/
 
 struct _NMPacrunnerCallId {
-	NMPacrunnerManager *manager;
+	CList lst;
+
+	/* this might be a dangling pointer after the async operation
+	 * is cancelled. */
+	NMPacrunnerManager *manager_maybe_dangling;
+
 	GVariant *args;
 	char *path;
 	guint refcount;
-	bool removed;
 };
 
 typedef struct _NMPacrunnerCallId Config;
@@ -49,8 +52,8 @@ typedef struct _NMPacrunnerCallId Config;
 typedef struct {
 	char *iface;
 	GDBusProxy *pacrunner;
-	GCancellable *pacrunner_cancellable;
-	GList *configs;
+	GCancellable *cancellable;
+	CList configs;
 } NMPacrunnerManagerPrivate;
 
 struct _NMPacrunnerManager {
@@ -80,49 +83,59 @@ NM_DEFINE_SINGLETON_GETTER (NMPacrunnerManager, nm_pacrunner_manager_get, NM_TYP
 	G_STMT_START { \
 		nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
 		        "%s%p]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-		        "pacrunner: call[", \
+		        _NMLOG2_PREFIX_NAME": call[", \
 		        (config) \
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
 /*****************************************************************************/
 
+static void pacrunner_remove_done (GObject *source, GAsyncResult *res, gpointer user_data);
+
+/*****************************************************************************/
+
 static Config *
 config_new (NMPacrunnerManager *manager, GVariant *args)
 {
 	Config *config;
 
 	config = g_slice_new0 (Config);
-	config->manager = manager;
+	config->manager_maybe_dangling = manager;
 	config->args = g_variant_ref_sink (args);
 	config->refcount = 1;
+	c_list_link_tail (&NM_PACRUNNER_MANAGER_GET_PRIVATE (manager)->configs,
+	                  &config->lst);
 
 	return config;
 }
 
-static void
+static Config *
 config_ref (Config *config)
 {
-	g_assert (config);
-	g_assert (config->refcount > 0);
+	nm_assert (config);
+	nm_assert (config->refcount > 0);
 
 	config->refcount++;
+	return config;
 }
 
 static void
 config_unref (Config *config)
 {
-	g_assert (config);
-	g_assert (config->refcount > 0);
+	nm_assert (config);
+	nm_assert (config->refcount > 0);
 
 	if (config->refcount == 1) {
 		g_variant_unref (config->args);
 		g_free (config->path);
+		c_list_unlink (&config->lst);
 		g_slice_free (Config, config);
 	} else
 		config->refcount--;
 }
 
+/*****************************************************************************/
+
 static void
 add_proxy_config (GVariantBuilder *proxy_data, const NMProxyConfig *proxy_config)
 {
@@ -155,8 +168,11 @@ add_proxy_config (GVariantBuilder *proxy_data, const NMProxyConfig *proxy_config
 static void
 get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
 {
+	NMDedupMultiIter ipconf_iter;
 	char *cidr;
-	int i;
+	const NMPlatformIP4Address *address;
+	const NMPlatformIP4Route *routes;
+	guint i;
 
 	/* Extract searches */
 	for (i = 0; i < nm_ip4_config_get_num_searches (ip4); i++)
@@ -167,18 +183,17 @@ get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
 		g_ptr_array_add (domains, g_strdup (nm_ip4_config_get_domain (ip4, i)));
 
 	/* Add addresses and routes in CIDR form */
-	for (i = 0; i < nm_ip4_config_get_num_addresses (ip4); i++) {
-		const NMPlatformIP4Address *address = nm_ip4_config_get_address (ip4, i);
 
+	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address) {
 		cidr = g_strdup_printf ("%s/%u",
 		                        nm_utils_inet4_ntop (address->address, NULL),
 		                        address->plen);
 		g_ptr_array_add (domains, cidr);
 	}
 
-	for (i = 0; i < nm_ip4_config_get_num_routes (ip4); i++) {
-		const NMPlatformIP4Route *routes = nm_ip4_config_get_route (ip4, i);
-
+	nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, ip4, &routes) {
+		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
+			continue;
 		cidr = g_strdup_printf ("%s/%u",
 		                        nm_utils_inet4_ntop (routes->network, NULL),
 		                        routes->plen);
@@ -189,8 +204,11 @@ get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
 static void
 get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
 {
+	NMDedupMultiIter ipconf_iter;
 	char *cidr;
-	int i;
+	const NMPlatformIP6Address *address;
+	const NMPlatformIP6Route *routes;
+	guint i;
 
 	/* Extract searches */
 	for (i = 0; i < nm_ip6_config_get_num_searches (ip6); i++)
@@ -201,18 +219,16 @@ get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
 		g_ptr_array_add (domains, g_strdup (nm_ip6_config_get_domain (ip6, i)));
 
 	/* Add addresses and routes in CIDR form */
-	for (i = 0; i < nm_ip6_config_get_num_addresses (ip6); i++) {
-		const NMPlatformIP6Address *address = nm_ip6_config_get_address (ip6, i);
-
+	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address) {
 		cidr = g_strdup_printf ("%s/%u",
 		                        nm_utils_inet6_ntop (&address->address, NULL),
 		                        address->plen);
 		g_ptr_array_add (domains, cidr);
 	}
 
-	for (i = 0; i < nm_ip6_config_get_num_routes (ip6); i++) {
-		const NMPlatformIP6Route *routes = nm_ip6_config_get_route (ip6, i);
-
+	nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, ip6, &routes) {
+		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
+			continue;
 		cidr = g_strdup_printf ("%s/%u",
 		                        nm_utils_inet6_ntop (&routes->network, NULL),
 		                        routes->plen);
@@ -220,8 +236,18 @@ get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
 	}
 }
 
+/*****************************************************************************/
+
+static GCancellable *
+_ensure_cancellable (NMPacrunnerManagerPrivate *priv)
+{
+	if (G_UNLIKELY (!priv->cancellable))
+		priv->cancellable = g_cancellable_new ();
+	return priv->cancellable;
+}
+
 static void
-pacrunner_send_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+pacrunner_send_done (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	Config *config = user_data;
 	NMPacrunnerManager *self;
@@ -230,15 +256,13 @@ pacrunner_send_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
 	gs_unref_variant GVariant *variant = NULL;
 	const char *path = NULL;
 
-	g_return_if_fail (!config->path);
+	nm_assert (!config->path);
 
-	variant = g_dbus_proxy_call_finish (proxy, res, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-		config_unref (config);
-		return;
-	}
+	variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		goto out;
 
-	self = NM_PACRUNNER_MANAGER (config->manager);
+	self = NM_PACRUNNER_MANAGER (config->manager_maybe_dangling);
 	priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
 	if (!variant)
@@ -246,21 +270,23 @@ pacrunner_send_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
 	else {
 		g_variant_get (variant, "(&o)", &path);
 
-		config->path = g_strdup (path);
-		_LOG2D (config, "sent");
-
-		if (config->removed) {
-			config_ref (config);
+		if (c_list_is_empty (&config->lst)) {
+			_LOG2D (config, "sent (%s), but destory it right away", path);
 			g_dbus_proxy_call (priv->pacrunner,
 			                   "DestroyProxyConfiguration",
-			                   g_variant_new ("(o)", config->path),
+			                   g_variant_new ("(o)", path),
 			                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
 			                   -1,
-			                   priv->pacrunner_cancellable,
-			                   (GAsyncReadyCallback) pacrunner_remove_done,
-			                   config);
+			                   _ensure_cancellable (priv),
+			                   pacrunner_remove_done,
+			                   config_ref (config));
+		} else {
+			_LOG2D (config, "sent (%s)", path);
+			config->path = g_strdup (path);
 		}
 	}
+
+out:
 	config_unref (config);
 }
 
@@ -272,17 +298,15 @@ pacrunner_send_config (NMPacrunnerManager *self, Config *config)
 	if (priv->pacrunner) {
 		_LOG2T (config, "sending...");
 
-		config_ref (config);
-		g_clear_pointer (&config->path, g_free);
-
+		nm_assert (!config->path);
 		g_dbus_proxy_call (priv->pacrunner,
 		                   "CreateProxyConfiguration",
 		                   config->args,
 		                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
 		                   -1,
-		                   priv->pacrunner_cancellable,
-		                   (GAsyncReadyCallback) pacrunner_send_done,
-		                   config);
+		                   _ensure_cancellable (priv),
+		                   pacrunner_send_done,
+		                   config_ref (config));
 	}
 }
 
@@ -291,15 +315,18 @@ name_owner_changed (NMPacrunnerManager *self)
 {
 	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 	gs_free char *owner = NULL;
-	GList *iter = NULL;
+	CList *iter;
 
 	owner = g_dbus_proxy_get_name_owner (priv->pacrunner);
 	if (owner) {
 		_LOGD ("name owner appeared (%s)", owner);
-		for (iter = g_list_first (priv->configs); iter; iter = g_list_next (iter))
-			pacrunner_send_config (self, iter->data);
+		c_list_for_each (iter, &priv->configs)
+			pacrunner_send_config (self, c_list_entry (iter, Config, lst));
 	} else {
 		_LOGD ("name owner disappeared");
+		nm_clear_g_cancellable (&priv->cancellable);
+		c_list_for_each (iter, &priv->configs)
+			nm_clear_g_free (&c_list_entry (iter, Config, lst)->path);
 	}
 }
 
@@ -316,21 +343,19 @@ pacrunner_proxy_cb (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	NMPacrunnerManager *self = user_data;
 	NMPacrunnerManagerPrivate *priv;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
 	GDBusProxy *proxy;
 
 	proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
 	if (!proxy) {
 		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			_LOGW ("failed to connect to pacrunner via DBus: %s", error->message);
-		g_error_free (error);
+			_LOGE ("failed to create D-Bus proxy for pacrunner: %s", error->message);
 		return;
 	}
 
 	priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
 	priv->pacrunner = proxy;
-
 	g_signal_connect (priv->pacrunner, "notify::g-name-owner",
 	                  G_CALLBACK (name_owner_changed_cb), self);
 	name_owner_changed (self);
@@ -420,7 +445,6 @@ nm_pacrunner_manager_send (NMPacrunnerManager *self,
 	}
 
 	config = config_new (self, g_variant_new ("(a{sv})", &proxy_data));
-	priv->configs = g_list_append (priv->configs, config);
 
 	{
 		gs_free char *args_str = NULL;
@@ -439,26 +463,24 @@ nm_pacrunner_manager_send (NMPacrunnerManager *self,
 }
 
 static void
-pacrunner_remove_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+pacrunner_remove_done (GObject *source, GAsyncResult *res, gpointer user_data)
 {
 	Config *config = user_data;
 	NMPacrunnerManager *self;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *ret = NULL;
 
-	ret = g_dbus_proxy_call_finish (proxy, res, &error);
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
-		config_unref (config);
-		return;
-	}
-
-	self = NM_PACRUNNER_MANAGER (config->manager);
+	ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
+	if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		goto out;
 
+	self = NM_PACRUNNER_MANAGER (config->manager_maybe_dangling);
 	if (!ret)
 		_LOG2D (config, "remove failed: %s", error->message);
 	else
 		_LOG2D (config, "removed");
 
+out:
 	config_unref (config);
 }
 
@@ -472,7 +494,6 @@ nm_pacrunner_manager_remove (NMPacrunnerManager *self, NMPacrunnerCallId *call_i
 {
 	NMPacrunnerManagerPrivate *priv;
 	Config *config;
-	GList *list;
 
 	g_return_if_fail (NM_IS_PACRUNNER_MANAGER (self));
 	g_return_if_fail (call_id);
@@ -482,31 +503,29 @@ nm_pacrunner_manager_remove (NMPacrunnerManager *self, NMPacrunnerCallId *call_i
 
 	_LOG2T (config, "removing...");
 
-	list = g_list_find (priv->configs, config);
-	if (!list)
-		g_return_if_reached ();
+	nm_assert (c_list_contains (&priv->configs, &config->lst));
 
 	if (priv->pacrunner) {
 		if (!config->path) {
-			/* send() failed or is still pending. Mark the item as
-			 * removed, so that we ask pacrunner to drop it when the
-			 * send() completes.
+			/* send() failed or is still pending. The item is unlinked from
+			 * priv->configs, so pacrunner_send_done() knows to call
+			 * DestroyProxyConfiguration right away.
 			 */
-			config->removed = TRUE;
-			config_unref (config);
 		} else {
 			g_dbus_proxy_call (priv->pacrunner,
 			                   "DestroyProxyConfiguration",
 			                   g_variant_new ("(o)", config->path),
 			                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
 			                   -1,
-			                   priv->pacrunner_cancellable,
-			                   (GAsyncReadyCallback) pacrunner_remove_done,
-			                   config);
+			                   _ensure_cancellable (priv),
+			                   pacrunner_remove_done,
+			                   config_ref (config));
+			nm_clear_g_free (&config->path);
 		}
-	} else
-		config_unref (config);
-	priv->configs = g_list_delete_link (priv->configs, list);
+	}
+
+	c_list_unlink_init (&config->lst);
+	config_unref (config);
 }
 
 gboolean
@@ -532,16 +551,15 @@ nm_pacrunner_manager_init (NMPacrunnerManager *self)
 {
 	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
-	priv->pacrunner_cancellable = g_cancellable_new ();
-
+	c_list_init (&priv->configs);
 	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
 	                          G_DBUS_PROXY_FLAGS_NONE,
 	                          NULL,
 	                          PACRUNNER_DBUS_SERVICE,
 	                          PACRUNNER_DBUS_PATH,
 	                          PACRUNNER_DBUS_INTERFACE,
-	                          priv->pacrunner_cancellable,
-	                          (GAsyncReadyCallback) pacrunner_proxy_cb,
+	                          _ensure_cancellable (priv),
+	                          pacrunner_proxy_cb,
 	                          self);
 }
 
@@ -549,14 +567,22 @@ static void
 dispose (GObject *object)
 {
 	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE ((NMPacrunnerManager *) object);
+	CList *iter, *safe;
+
+	c_list_for_each_safe (iter, safe, &priv->configs) {
+		c_list_unlink_init (iter);
+		config_unref (c_list_entry (iter, Config, lst));
+	}
+
+	/* we cancel all pending operations. Note that pacrunner automatically
+	 * removes all configuration once NetworkManager disconnects from
+	 * the bus -- which happens soon after we destroy the pacrunner manager.
+	 */
+	nm_clear_g_cancellable (&priv->cancellable);
 
 	g_clear_pointer (&priv->iface, g_free);
-	nm_clear_g_cancellable (&priv->pacrunner_cancellable);
 	g_clear_object (&priv->pacrunner);
 
-	g_list_free_full (priv->configs, (GDestroyNotify) config_unref);
-	priv->configs = NULL;
-
 	G_OBJECT_CLASS (nm_pacrunner_manager_parent_class)->dispose (object);
 }
 
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 7c74a6b4..3cfb1f7c 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -31,7 +31,6 @@
 #include "NetworkManagerUtils.h"
 #include "nm-act-request.h"
 #include "devices/nm-device.h"
-#include "nm-default-route-manager.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-connection.h"
 #include "platform/nm-platform.h"
@@ -49,6 +48,7 @@
 #include "nm-dhcp6-config.h"
 #include "nm-config.h"
 #include "nm-netns.h"
+#include "nm-hostname-manager.h"
 
 /*****************************************************************************/
 
@@ -68,17 +68,23 @@ typedef struct {
 	GSList *pending_activation_checks;
 
 	GHashTable *devices;
+	GHashTable *pending_active_connections;
 
 	GSList *pending_secondaries;
 
 	NMSettings *settings;
 
+	NMHostnameManager *hostname_manager;
+
 	NMDevice *default_device4, *activating_device4;
 	NMDevice *default_device6, *activating_device6;
 
-	GResolver *resolver;
-	GInetAddress *lookup_addr;
-	GCancellable *lookup_cancellable;
+	struct {
+		GInetAddress *addr;
+		GResolver *resolver;
+		GCancellable *cancellable;
+	} lookup;
+
 	NMDnsManager *dns_manager;
 	gulong config_changed_id;
 
@@ -136,6 +142,7 @@ _PRIV_TO_SELF (NMPolicyPrivate *priv)
 /*****************************************************************************/
 
 static void schedule_activate_all (NMPolicy *self);
+static void schedule_activate_check (NMPolicy *self, NMDevice *device);
 
 /*****************************************************************************/
 
@@ -369,25 +376,81 @@ device_ip6_subnet_needed (NMDevice *device,
 /*****************************************************************************/
 
 static NMDevice *
-get_best_ip4_device (NMPolicy *self, gboolean fully_activated)
+get_best_ip_device (NMPolicy *self,
+                    int addr_family,
+                    gboolean fully_activated)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+	const GSList *iter;
+	NMDevice *best_device;
+	NMDevice *prev_device;
+	guint32 best_metric = G_MAXUINT32;
+	gboolean best_is_fully_activated = FALSE;
+
+	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);
+
+	for (iter = nm_manager_get_devices (priv->manager); iter; iter = iter->next) {
+		NMDevice *device = NM_DEVICE (iter->data);
+		NMDeviceState state;
+		const NMPObject *r;
+		NMConnection *connection;
+		guint32 metric;
+		gboolean is_fully_activated;
 
-	return nm_default_route_manager_ip4_get_best_device (nm_netns_get_default_route_manager (priv->netns),
-	                                                     nm_manager_get_devices (priv->manager),
-	                                                     fully_activated,
-	                                                     priv->default_device4);
-}
+		state = nm_device_get_state (device);
+		if (   state <= NM_DEVICE_STATE_DISCONNECTED
+		    || state >= NM_DEVICE_STATE_DEACTIVATING)
+			continue;
 
-static NMDevice *
-get_best_ip6_device (NMPolicy *self, gboolean fully_activated)
-{
-	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+		if (nm_device_sys_iface_state_is_external (device))
+			continue;
+
+		r = nm_device_get_best_default_route (device, addr_family);
+		if (r) {
+			/* XXX: the best route might have rt_source NM_IP_CONFIG_SOURCE_VPN,
+			 * which means it was injected by a VPN, not added by device.
+			 *
+			 * In this case, is it really the best device? Why do we even need the best
+			 * device?? */
+			metric = nm_utils_ip_route_metric_normalize (addr_family,
+			                                             NMP_OBJECT_CAST_IP_ROUTE (r)->metric);
+			is_fully_activated = TRUE;
+		} else if (   !fully_activated
+		           && (connection = nm_device_get_applied_connection (device))
+		           && nm_utils_connection_has_default_route (connection, addr_family, NULL)) {
+			metric = nm_utils_ip_route_metric_normalize (addr_family,
+			                                             nm_device_get_route_metric (device, addr_family));
+			is_fully_activated = FALSE;
+		} else
+			continue;
+
+		if (   !best_device
+		    || (!best_is_fully_activated && is_fully_activated)
+		    || (   metric < best_metric
+		        || (metric == best_metric && device == prev_device))) {
+			best_device = device;
+			best_metric = metric;
+			best_is_fully_activated = is_fully_activated;
+		}
+	}
 
-	return nm_default_route_manager_ip6_get_best_device (nm_netns_get_default_route_manager (priv->netns),
-	                                                     nm_manager_get_devices (priv->manager),
-	                                                     fully_activated,
-	                                                     priv->default_device6);
+	if (   !fully_activated
+	    && best_device
+	    && best_is_fully_activated) {
+		/* There's only a best activating device if the best device
+		 * among all activating and already-activated devices is a
+		 * still-activating one. */
+		return NULL;
+	}
+
+	return best_device;
 }
 
 static gboolean
@@ -443,50 +506,46 @@ settings_set_hostname_cb (const char *hostname,
 #define HOST_NAME_BUFSIZE (HOST_NAME_MAX + 2)
 
 static char *
-_get_hostname (NMPolicy *self, char **hostname)
+_get_hostname (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	char *buf;
-
-	g_assert (hostname && *hostname == NULL);
+	char *hostname = NULL;
 
 	/* If there is an in-progress hostname change, return
 	 * the last hostname set as would be set soon...
 	 */
 	if (priv->changing_hostname) {
 		_LOGT (LOGD_DNS, "get-hostname: \"%s\" (last on set)", priv->last_hostname);
-		*hostname = g_strdup (priv->last_hostname);
-		return *hostname;
+		return g_strdup (priv->last_hostname);
 	}
 
 	/* try to get the hostname via dbus... */
-	if (nm_settings_get_transient_hostname (priv->settings, hostname)) {
-		_LOGT (LOGD_DNS, "get-hostname: \"%s\" (from dbus)", *hostname);
-		return *hostname;
+	if (nm_hostname_manager_get_transient_hostname (priv->hostname_manager, &hostname)) {
+		_LOGT (LOGD_DNS, "get-hostname: \"%s\" (from dbus)", hostname);
+		return hostname;
 	}
 
 	/* ...or retrieve it by yourself */
-	buf = g_malloc (HOST_NAME_BUFSIZE);
-	if (gethostname (buf, HOST_NAME_BUFSIZE -1) != 0) {
+	hostname = g_malloc (HOST_NAME_BUFSIZE);
+	if (gethostname (hostname, HOST_NAME_BUFSIZE -1) != 0) {
 		int errsv = errno;
 
 		_LOGT (LOGD_DNS, "get-hostname: couldn't get the system hostname: (%d) %s",
 		       errsv, g_strerror (errsv));
-		g_free (buf);
+		g_free (hostname);
 		return NULL;
 	}
 
 	/* the name may be truncated... */
-	buf[HOST_NAME_BUFSIZE - 1] = '\0';
-	if (strlen (buf) >= HOST_NAME_BUFSIZE -1) {
-		_LOGT (LOGD_DNS, "get-hostname: system hostname too long: \"%s\"", buf);
-		g_free (buf);
+	hostname[HOST_NAME_BUFSIZE - 1] = '\0';
+	if (strlen (hostname) >= HOST_NAME_BUFSIZE -1) {
+		_LOGT (LOGD_DNS, "get-hostname: system hostname too long: \"%s\"", hostname);
+		g_free (hostname);
 		return NULL;
 	}
 
-	_LOGT (LOGD_DNS, "get-hostname: \"%s\"", buf);
-	*hostname = buf;
-	return *hostname;
+	_LOGT (LOGD_DNS, "get-hostname: \"%s\"", hostname);
+	return hostname;
 }
 
 static void
@@ -508,7 +567,7 @@ _set_hostname (NMPolicy *self,
 	 * restart the reverse lookup thread later.
 	 */
 	if (new_hostname)
-		g_clear_object (&priv->lookup_addr);
+		g_clear_object (&priv->lookup.addr);
 
 	/* Update the DNS only if the hostname is actually
 	 * going to change.
@@ -534,7 +593,7 @@ _set_hostname (NMPolicy *self,
 		name = new_hostname;
 
 	/* Don't set the hostname if it isn't actually changing */
-	if (   _get_hostname (self, &old_hostname)
+	if (   (old_hostname = _get_hostname (self))
 	    && (nm_streq (name, old_hostname))) {
 		_LOGT (LOGD_DNS, "set-hostname: hostname already set to '%s' (%s)", name, msg);
 		return;
@@ -549,10 +608,10 @@ _set_hostname (NMPolicy *self,
 
 	/* Ask NMSettings to update the transient hostname using its
 	 * systemd-hostnamed proxy */
-	nm_settings_set_transient_hostname (priv->settings,
-	                                    name,
-	                                    settings_set_hostname_cb,
-	                                    g_object_ref (self));
+	nm_hostname_manager_set_transient_hostname (priv->hostname_manager,
+	                                            name,
+	                                            settings_set_hostname_cb,
+	                                            g_object_ref (self));
 }
 
 static void
@@ -572,7 +631,7 @@ lookup_callback (GObject *source,
 	self = user_data;
 	priv = NM_POLICY_GET_PRIVATE (self);
 
-	g_clear_object (&priv->lookup_cancellable);
+	g_clear_object (&priv->lookup.cancellable);
 
 	if (hostname)
 		_set_hostname (self, hostname, "from address lookup");
@@ -581,15 +640,30 @@ lookup_callback (GObject *source,
 }
 
 static void
-update_system_hostname (NMPolicy *self, NMDevice *best4, NMDevice *best6, const char *msg)
+lookup_by_address (NMPolicy *self)
+{
+	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+
+	nm_clear_g_cancellable (&priv->lookup.cancellable);
+	priv->lookup.cancellable = g_cancellable_new ();
+	g_resolver_lookup_by_address_async (priv->lookup.resolver,
+	                                    priv->lookup.addr,
+	                                    priv->lookup.cancellable,
+	                                    lookup_callback, self);
+}
+
+static void
+update_system_hostname (NMPolicy *self, const char *msg)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	char *configured_hostname = NULL;
+	const char *configured_hostname;
 	gs_free char *temp_hostname = NULL;
 	const char *dhcp_hostname, *p;
 	NMIP4Config *ip4_config;
 	NMIP6Config *ip6_config;
 	gboolean external_hostname = FALSE;
+	const NMPlatformIP4Address *addr4;
+	const NMPlatformIP6Address *addr6;
 
 	g_return_if_fail (self != NULL);
 
@@ -600,12 +674,12 @@ update_system_hostname (NMPolicy *self, NMDevice *best4, NMDevice *best6, const
 
 	_LOGT (LOGD_DNS, "set-hostname: updating hostname (%s)", msg);
 
-	nm_clear_g_cancellable (&priv->lookup_cancellable);
+	nm_clear_g_cancellable (&priv->lookup.cancellable);
 
 	/* Check if the hostname was set externally to NM, so that in that case
 	 * we can avoid to fallback to the one we got when we started.
 	 * Consider "not specific" hostnames as equal. */
-	if (   _get_hostname (self, &temp_hostname)
+	if (   (temp_hostname = _get_hostname (self))
 	    && !nm_streq0 (temp_hostname, priv->last_hostname)
 	    && (   nm_utils_is_specific_hostname (temp_hostname)
 	        || nm_utils_is_specific_hostname (priv->last_hostname))) {
@@ -614,14 +688,14 @@ update_system_hostname (NMPolicy *self, NMDevice *best4, NMDevice *best6, const
 		       temp_hostname);
 		priv->dhcp_hostname = FALSE;
 
+		if (!nm_utils_is_specific_hostname (temp_hostname))
+			nm_clear_g_free (&temp_hostname);
 		if (!nm_streq0 (temp_hostname, priv->orig_hostname)) {
 			/* Update original (fallback) hostname */
 			g_free (priv->orig_hostname);
-			if (nm_utils_is_specific_hostname (temp_hostname)) {
-				priv->orig_hostname = temp_hostname;
-				temp_hostname = NULL;
-			} else
-				priv->orig_hostname = NULL;
+			priv->orig_hostname = g_steal_pointer (&temp_hostname);
+			_LOGT (LOGD_DNS, "hostname-original: update to %s%s%s",
+			       NM_PRINT_FMT_QUOTE_STRING (priv->orig_hostname));
 		}
 	}
 
@@ -635,56 +709,46 @@ update_system_hostname (NMPolicy *self, NMDevice *best4, NMDevice *best6, const
 	 */
 
 	/* Try a persistent hostname first */
-	g_object_get (G_OBJECT (priv->manager), NM_MANAGER_HOSTNAME, &configured_hostname, NULL);
+	configured_hostname = nm_hostname_manager_get_hostname (priv->hostname_manager);
 	if (configured_hostname && nm_utils_is_specific_hostname (configured_hostname)) {
 		_set_hostname (self, configured_hostname, "from system configuration");
 		priv->dhcp_hostname = FALSE;
-		g_free (configured_hostname);
 		return;
 	}
-	g_free (configured_hostname);
 
-	/* Try automatically determined hostname from the best device's IP config */
-	if (!best4)
-		best4 = get_best_ip4_device (self, TRUE);
-	if (!best6)
-		best6 = get_best_ip6_device (self, TRUE);
-
-	if (best4) {
+	if (priv->default_device4) {
 		NMDhcp4Config *dhcp4_config;
 
 		/* Grab a hostname out of the device's DHCP4 config */
-		dhcp4_config = nm_device_get_dhcp4_config (best4);
+		dhcp4_config = nm_device_get_dhcp4_config (priv->default_device4);
 		if (dhcp4_config) {
-			p = dhcp_hostname = nm_dhcp4_config_get_option (dhcp4_config, "host_name");
-			if (dhcp_hostname && strlen (dhcp_hostname)) {
-				/* Sanity check; strip leading spaces */
-				while (*p) {
-					if (!g_ascii_isspace (*p++)) {
-						_set_hostname (self, p-1, "from DHCPv4");
-						priv->dhcp_hostname = TRUE;
-						return;
-					}
+			dhcp_hostname = nm_dhcp4_config_get_option (dhcp4_config, "host_name");
+			if (dhcp_hostname && dhcp_hostname[0]) {
+				p = nm_str_skip_leading_spaces (dhcp_hostname);
+				if (p[0]) {
+					_set_hostname (self, p, "from DHCPv4");
+					priv->dhcp_hostname = TRUE;
+					return;
 				}
 				_LOGW (LOGD_DNS, "set-hostname: DHCPv4-provided hostname '%s' looks invalid; ignoring it",
 				       dhcp_hostname);
 			}
 		}
-	} else if (best6) {
+	}
+
+	if (priv->default_device6) {
 		NMDhcp6Config *dhcp6_config;
 
 		/* Grab a hostname out of the device's DHCP6 config */
-		dhcp6_config = nm_device_get_dhcp6_config (best6);
+		dhcp6_config = nm_device_get_dhcp6_config (priv->default_device6);
 		if (dhcp6_config) {
-			p = dhcp_hostname = nm_dhcp6_config_get_option (dhcp6_config, "host_name");
-			if (dhcp_hostname && strlen (dhcp_hostname)) {
-				/* Sanity check; strip leading spaces */
-				while (*p) {
-					if (!g_ascii_isspace (*p++)) {
-						_set_hostname (self, p-1, "from DHCPv6");
-						priv->dhcp_hostname = TRUE;
-						return;
-					}
+			dhcp_hostname = nm_dhcp6_config_get_option (dhcp6_config, "host_name");
+			if (dhcp_hostname && dhcp_hostname[0]) {
+				p = nm_str_skip_leading_spaces (dhcp_hostname);
+				if (p[0]) {
+					_set_hostname (self, p, "from DHCPv6");
+					priv->dhcp_hostname = TRUE;
+					return;
 				}
 				_LOGW (LOGD_DNS, "set-hostname: DHCPv6-provided hostname '%s' looks invalid; ignoring it",
 				       dhcp_hostname);
@@ -711,7 +775,7 @@ update_system_hostname (NMPolicy *self, NMDevice *best4, NMDevice *best6, const
 
 	priv->dhcp_hostname = FALSE;
 
-	if (!best4 && !best6) {
+	if (!priv->default_device4 && !priv->default_device6) {
 		/* No best device; fall back to the last hostname set externally
 		 * to NM or if there wasn't one, 'localhost.localdomain'
 		 */
@@ -730,22 +794,18 @@ update_system_hostname (NMPolicy *self, NMDevice *best4, NMDevice *best6, const
 	/* No configured hostname, no automatically determined hostname, and no
 	 * bootup hostname. Start reverse DNS of the current IPv4 or IPv6 address.
 	 */
-	ip4_config = best4 ? nm_device_get_ip4_config (best4) : NULL;
-	ip6_config = best6 ? nm_device_get_ip6_config (best6) : NULL;
-
-	if (ip4_config && nm_ip4_config_get_num_addresses (ip4_config) > 0) {
-		const NMPlatformIP4Address *addr4;
+	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;
 
-		addr4 = nm_ip4_config_get_address (ip4_config, 0);
-		g_clear_object (&priv->lookup_addr);
-		priv->lookup_addr = g_inet_address_new_from_bytes ((guint8 *) &addr4->address,
+	if (   ip4_config
+	    && (addr4 = nm_ip4_config_get_first_address (ip4_config))) {
+		g_clear_object (&priv->lookup.addr);
+		priv->lookup.addr = g_inet_address_new_from_bytes ((guint8 *) &addr4->address,
 		                                                   G_SOCKET_FAMILY_IPV4);
-	} else if (ip6_config && nm_ip6_config_get_num_addresses (ip6_config) > 0) {
-		const NMPlatformIP6Address *addr6;
-
-		addr6 = nm_ip6_config_get_address (ip6_config, 0);
-		g_clear_object (&priv->lookup_addr);
-		priv->lookup_addr = g_inet_address_new_from_bytes ((guint8 *) &addr6->address,
+	} else if (   ip6_config
+	           && (addr6 = nm_ip6_config_get_first_address (ip6_config))) {
+		g_clear_object (&priv->lookup.addr);
+		priv->lookup.addr = g_inet_address_new_from_bytes ((guint8 *) &addr6->address,
 		                                                   G_SOCKET_FAMILY_IPV6);
 	} else {
 		/* No valid IP config; fall back to localhost.localdomain */
@@ -753,11 +813,7 @@ update_system_hostname (NMPolicy *self, NMDevice *best4, NMDevice *best6, const
 		return;
 	}
 
-	priv->lookup_cancellable = g_cancellable_new ();
-	g_resolver_lookup_by_address_async (priv->resolver,
-	                                    priv->lookup_addr,
-	                                    priv->lookup_cancellable,
-	                                    lookup_callback, self);
+	lookup_by_address (self);
 }
 
 static void
@@ -783,20 +839,83 @@ update_default_ac (NMPolicy *self,
 		set_active_func (best, TRUE);
 }
 
-static NMIP4Config *
-get_best_ip4_config (NMPolicy *self,
-                     gboolean ignore_never_default,
-                     const char **out_ip_iface,
-                     NMActiveConnection **out_ac,
-                     NMDevice **out_device,
-                     NMVpnConnection **out_vpn)
+static gpointer
+get_best_ip_config (NMPolicy *self,
+                    int addr_family,
+                    const char **out_ip_iface,
+                    NMActiveConnection **out_ac,
+                    NMDevice **out_device,
+                    NMVpnConnection **out_vpn)
 {
-	return nm_default_route_manager_ip4_get_best_config (nm_netns_get_default_route_manager (NM_POLICY_GET_PRIVATE (self)->netns),
-	                                                     ignore_never_default,
-	                                                     out_ip_iface,
-	                                                     out_ac,
-	                                                     out_device,
-	                                                     out_vpn);
+	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+	NMDevice *device;
+	gpointer conf;
+	const GSList *iter;
+
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	for (iter = nm_manager_get_active_connections (priv->manager); iter; iter = iter->next) {
+		NMActiveConnection *active = NM_ACTIVE_CONNECTION (iter->data);
+		NMVpnConnection *candidate;
+		NMVpnConnectionState vpn_state;
+
+		if (!NM_IS_VPN_CONNECTION (active))
+			continue;
+
+		candidate = NM_VPN_CONNECTION (active);
+
+		vpn_state = nm_vpn_connection_get_vpn_state (candidate);
+		if (vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED)
+			continue;
+
+		if (addr_family == AF_INET)
+			conf = nm_vpn_connection_get_ip4_config (candidate);
+		else
+			conf = nm_vpn_connection_get_ip6_config (candidate);
+		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;
+		}
+
+		/* FIXME: in case of multiple VPN candidates, choose the one with the
+		 * best metric. */
+		NM_SET_OUT (out_device, NULL);
+		NM_SET_OUT (out_vpn, candidate);
+		NM_SET_OUT (out_ac, active);
+		NM_SET_OUT (out_ip_iface, nm_vpn_connection_get_ip_iface (candidate, TRUE));
+		return conf;
+	}
+
+	device = get_best_ip_device (self, addr_family, TRUE);
+	if (device) {
+		NMActRequest *req;
+
+		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, NULL);
+	NM_SET_OUT (out_vpn, NULL);
+	NM_SET_OUT (out_ac, NULL);
+	NM_SET_OUT (out_ip_iface, NULL);
+	return NULL;
 }
 
 static void
@@ -807,7 +926,7 @@ update_ip4_dns (NMPolicy *self, NMDnsManager *dns_mgr)
 	NMVpnConnection *vpn = NULL;
 	NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
 
-	ip4_config = get_best_ip4_config (self, TRUE, &ip_iface, NULL, NULL, &vpn);
+	ip4_config = get_best_ip_config (self, AF_INET, &ip_iface, NULL, NULL, &vpn);
 	if (ip4_config) {
 		if (vpn)
 			dns_type = NM_DNS_IP_CONFIG_TYPE_VPN;
@@ -823,8 +942,7 @@ static void
 update_ip4_routing (NMPolicy *self, gboolean force_update)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	NMDevice *best = NULL, *default_device;
-	NMConnection *connection = NULL;
+	NMDevice *best = NULL;
 	NMVpnConnection *vpn = NULL;
 	NMActiveConnection *best_ac = NULL;
 	const char *ip_iface = NULL;
@@ -832,19 +950,18 @@ update_ip4_routing (NMPolicy *self, gboolean force_update)
 	/* Note that we might have an IPv4 VPN tunneled over an IPv6-only device,
 	 * so we can get (vpn != NULL && best == NULL).
 	 */
-	if (!get_best_ip4_config (self, FALSE, &ip_iface, &best_ac, &best, &vpn)) {
-		gboolean changed;
-
-		changed = (priv->default_device4 != NULL);
-		priv->default_device4 = NULL;
-		if (changed)
+	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);
-
+		}
 		return;
 	}
 	g_assert ((best || vpn) && best_ac);
 
-	if (!force_update && best && (best == priv->default_device4))
+	if (   !force_update
+	    && best
+	    && best == priv->default_device4)
 		return;
 
 	if (best) {
@@ -862,38 +979,20 @@ update_ip4_routing (NMPolicy *self, gboolean force_update)
 	}
 
 	if (vpn)
-		default_device = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
-	else
-		default_device = best;
+		best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
 
 	update_default_ac (self, best_ac, nm_active_connection_set_default);
 
-	if (default_device == priv->default_device4)
+	if (!nm_g_object_ref_set (&priv->default_device4, best))
 		return;
+	_LOGt (LOGD_DNS, "set-default-device-4: %p", priv->default_device4);
 
-	priv->default_device4 = default_device;
-	connection = nm_active_connection_get_applied_connection (best_ac);
 	_LOGI (LOGD_CORE, "set '%s' (%s) as default for IPv4 routing and DNS",
-	       nm_connection_get_id (connection), ip_iface);
+	       nm_connection_get_id (nm_active_connection_get_applied_connection (best_ac)),
+	       ip_iface);
 	_notify (self, PROP_DEFAULT_IP4_DEVICE);
 }
 
-static NMIP6Config *
-get_best_ip6_config (NMPolicy *self,
-                     gboolean ignore_never_default,
-                     const char **out_ip_iface,
-                     NMActiveConnection **out_ac,
-                     NMDevice **out_device,
-                     NMVpnConnection **out_vpn)
-{
-	return nm_default_route_manager_ip6_get_best_config (nm_netns_get_default_route_manager (NM_POLICY_GET_PRIVATE (self)->netns),
-	                                                     ignore_never_default,
-	                                                     out_ip_iface,
-	                                                     out_ac,
-	                                                     out_device,
-	                                                     out_vpn);
-}
-
 static void
 update_ip6_dns_delegation (NMPolicy *self)
 {
@@ -917,7 +1016,7 @@ update_ip6_dns (NMPolicy *self, NMDnsManager *dns_mgr)
 	NMVpnConnection *vpn = NULL;
 	NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
 
-	ip6_config = get_best_ip6_config (self, TRUE, &ip_iface, NULL, NULL, &vpn);
+	ip6_config = get_best_ip_config (self, AF_INET6, &ip_iface, NULL, NULL, &vpn);
 	if (ip6_config) {
 		if (vpn)
 			dns_type = NM_DNS_IP_CONFIG_TYPE_VPN;
@@ -951,8 +1050,7 @@ static void
 update_ip6_routing (NMPolicy *self, gboolean force_update)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	NMDevice *best = NULL, *default_device6;
-	NMConnection *connection = NULL;
+	NMDevice *best = NULL;
 	NMVpnConnection *vpn = NULL;
 	NMActiveConnection *best_ac = NULL;
 	const char *ip_iface = NULL;
@@ -960,19 +1058,18 @@ update_ip6_routing (NMPolicy *self, gboolean force_update)
 	/* Note that we might have an IPv6 VPN tunneled over an IPv4-only device,
 	 * so we can get (vpn != NULL && best == NULL).
 	 */
-	if (!get_best_ip6_config (self, FALSE, &ip_iface, &best_ac, &best, &vpn)) {
-		gboolean changed;
-
-		changed = (priv->default_device6 != NULL);
-		priv->default_device6 = NULL;
-		if (changed)
+	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);
-
+		}
 		return;
 	}
 	g_assert ((best || vpn) && best_ac);
 
-	if (!force_update && best && (best == priv->default_device6))
+	if (   !force_update
+	    && best
+	    && best == priv->default_device6)
 		return;
 
 	if (best) {
@@ -990,21 +1087,19 @@ update_ip6_routing (NMPolicy *self, gboolean force_update)
 	}
 
 	if (vpn)
-		default_device6 = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
-	else
-		default_device6 = best;
+		best = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
 
 	update_default_ac (self, best_ac, nm_active_connection_set_default6);
 
-	if (default_device6 == priv->default_device6)
+	if (!nm_g_object_ref_set (&priv->default_device6, best))
 		return;
-	priv->default_device6 = default_device6;
+	_LOGt (LOGD_DNS, "set-default-device-6: %p", priv->default_device6);
 
 	update_ip6_prefix_delegation (self);
 
-	connection = nm_active_connection_get_applied_connection (best_ac);
 	_LOGI (LOGD_CORE, "set '%s' (%s) as default for IPv6 routing and DNS",
-	       nm_connection_get_id (connection), ip_iface);
+	       nm_connection_get_id (nm_active_connection_get_applied_connection (best_ac)),
+	       ip_iface);
 	_notify (self, PROP_DEFAULT_IP6_DEVICE);
 }
 
@@ -1022,7 +1117,7 @@ update_routing_and_dns (NMPolicy *self, gboolean force_update)
 	update_ip6_routing (self, force_update);
 
 	/* Update the system hostname */
-	update_system_hostname (self, priv->default_device4, priv->default_device6, "routing and dns");
+	update_system_hostname (self, "routing and dns");
 
 	nm_dns_manager_end_updates (priv->dns_manager, __func__);
 }
@@ -1031,24 +1126,23 @@ static void
 check_activating_devices (NMPolicy *self)
 {
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
-	GObject *object = G_OBJECT (self);
 	NMDevice *best4, *best6 = NULL;
 
-	best4 = get_best_ip4_device (self, FALSE);
-	best6 = get_best_ip6_device (self, FALSE);
+	best4 = get_best_ip_device (self, AF_INET, FALSE);
+	best6 = get_best_ip_device (self, AF_INET6, FALSE);
 
-	g_object_freeze_notify (object);
+	g_object_freeze_notify (G_OBJECT (self));
 
-	if (best4 != priv->activating_device4) {
-		priv->activating_device4 = best4;
+	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 (best6 != priv->activating_device6) {
-		priv->activating_device6 = best6;
+	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);
 	}
 
-	g_object_thaw_notify (object);
+	g_object_thaw_notify (G_OBJECT (self));
 }
 
 typedef struct {
@@ -1073,6 +1167,45 @@ activate_data_free (ActivateData *data)
 }
 
 static void
+pending_ac_gone (gpointer data, GObject *where_the_object_was)
+{
+	NMPolicy *self = NM_POLICY (data);
+	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+
+	/* Active connections should reach the DEACTIVATED state
+	 * before disappearing. */
+	nm_assert_not_reached();
+
+	if (g_hash_table_remove (priv->pending_active_connections, where_the_object_was))
+		g_object_unref (self);
+}
+
+static void
+pending_ac_state_changed (NMActiveConnection *ac, guint state, guint reason, NMPolicy *self)
+{
+	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
+	NMSettingsConnection *con;
+
+	if (state >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
+		/* The AC is being deactivated before the device had a chance
+		 * to move to PREPARE. Schedule a new auto-activation on the
+		 * device, but block the current connection to avoid an activation
+		 * loop.
+		 */
+		con = nm_active_connection_get_settings_connection (ac);
+		nm_settings_connection_autoconnect_blocked_reason_set (con, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED);
+		schedule_activate_check (self, nm_active_connection_get_device (ac));
+
+		/* Cleanup */
+		g_signal_handlers_disconnect_by_func (ac, pending_ac_state_changed, self);
+		if (!g_hash_table_remove (priv->pending_active_connections, ac))
+			nm_assert_not_reached ();
+		g_object_weak_unref (G_OBJECT (ac), pending_ac_gone, self);
+		g_object_unref (self);
+	}
+}
+
+static void
 auto_activate_device (NMPolicy *self,
                       NMDevice *device)
 {
@@ -1102,9 +1235,23 @@ auto_activate_device (NMPolicy *self,
 	best_connection = NULL;
 	for (i = 0; i < len; i++) {
 		NMSettingsConnection *candidate = NM_SETTINGS_CONNECTION (connections[i]);
+		NMSettingConnection *s_con;
+		const char *permission;
 
-		if (!nm_settings_connection_can_autoconnect (candidate))
+		if (   !nm_settings_connection_is_visible (candidate)
+		    || nm_settings_connection_autoconnect_retries_get (candidate) == 0
+		    || nm_settings_connection_autoconnect_blocked_reason_get (candidate) != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE)
 			continue;
+
+		s_con = nm_connection_get_setting_connection (NM_CONNECTION (candidate));
+		if (!nm_setting_connection_get_autoconnect (s_con))
+			continue;
+
+		permission = nm_utils_get_shared_wifi_permission (NM_CONNECTION (candidate));
+		if (   permission
+		    && !nm_settings_connection_check_permission (candidate, permission))
+			continue;
+
 		if (nm_device_can_auto_connect (device, (NMConnection *) candidate, &specific_object)) {
 			best_connection = candidate;
 			break;
@@ -1114,24 +1261,41 @@ auto_activate_device (NMPolicy *self,
 	if (best_connection) {
 		GError *error = NULL;
 		NMAuthSubject *subject;
+		NMActiveConnection *ac;
 
 		_LOGI (LOGD_DEVICE, "auto-activating connection '%s'",
 		       nm_settings_connection_get_id (best_connection));
 		subject = nm_auth_subject_new_internal ();
-		if (!nm_manager_activate_connection (priv->manager,
+		ac = nm_manager_activate_connection (priv->manager,
 		                                     best_connection,
 		                                     NULL,
 		                                     specific_object,
 		                                     device,
 		                                     subject,
 		                                     NM_ACTIVATION_TYPE_MANAGED,
-		                                     &error)) {
+		                                     &error);
+		if (!ac) {
 			_LOGI (LOGD_DEVICE, "connection '%s' auto-activation failed: (%d) %s",
 			       nm_settings_connection_get_id (best_connection),
 			       error->code,
 			       error->message);
 			g_error_free (error);
+			nm_settings_connection_autoconnect_blocked_reason_set (best_connection,
+			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED);
+			schedule_activate_check (self, device);
+			return;
 		}
+
+		/* Subscribe to AC state-changed signal to detect when the
+		 * activation fails in early stages without changing device
+		 * state.
+		 */
+		if (nm_g_hash_table_add (priv->pending_active_connections, ac)) {
+			g_signal_connect (ac, NM_ACTIVE_CONNECTION_STATE_CHANGED,
+			                  G_CALLBACK (pending_ac_state_changed), g_object_ref (self));
+			g_object_weak_ref (G_OBJECT (ac), (GWeakNotify) pending_ac_gone, self);
+		}
+
 		g_object_unref (subject);
 	}
 }
@@ -1248,12 +1412,12 @@ process_secondaries (NMPolicy *self,
 }
 
 static void
-hostname_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data)
+hostname_changed (NMHostnameManager *hostname_manager, GParamSpec *pspec, gpointer user_data)
 {
 	NMPolicyPrivate *priv = user_data;
 	NMPolicy *self = _PRIV_TO_SELF (priv);
 
-	update_system_hostname (self, NULL, NULL, "hostname changed");
+	update_system_hostname (self, "hostname changed");
 }
 
 static void
@@ -1274,8 +1438,8 @@ reset_autoconnect_all (NMPolicy *self, NMDevice *device)
 		NMSettingsConnection *connection = connections[i];
 
 		if (!device || nm_device_check_connection_compatible (device, NM_CONNECTION (connection))) {
-			nm_settings_connection_reset_autoconnect_retries (connection);
-			nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_UNBLOCKED);
+			nm_settings_connection_autoconnect_retries_reset (connection);
+			nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE);
 		}
 	}
 }
@@ -1293,9 +1457,9 @@ reset_autoconnect_for_failed_secrets (NMPolicy *self)
 	for (i = 0; connections[i]; i++) {
 		NMSettingsConnection *connection = connections[i];
 
-		if (nm_settings_connection_get_autoconnect_blocked_reason (connection) == NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS) {
-			nm_settings_connection_reset_autoconnect_retries (connection);
-			nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_UNBLOCKED);
+		if (nm_settings_connection_autoconnect_blocked_reason_get (connection) == NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS) {
+			nm_settings_connection_autoconnect_retries_reset (connection);
+			nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE);
 		}
 	}
 }
@@ -1322,8 +1486,8 @@ block_autoconnect_for_device (NMPolicy *self, NMDevice *device)
 		NMSettingsConnection *connection = connections[i];
 
 		if (nm_device_check_connection_compatible (device, NM_CONNECTION (connection))) {
-			nm_settings_connection_set_autoconnect_blocked_reason (connection,
-			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_BLOCKED);
+			nm_settings_connection_autoconnect_blocked_reason_set (connection,
+			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST);
 		}
 	}
 }
@@ -1406,12 +1570,12 @@ reset_connections_retries (gpointer user_data)
 	for (i = 0; connections[i]; i++) {
 		NMSettingsConnection *connection = connections[i];
 
-		con_stamp = nm_settings_connection_get_autoconnect_retry_time (connection);
+		con_stamp = nm_settings_connection_autoconnect_blocked_until_get (connection);
 		if (con_stamp == 0)
 			continue;
 
 		if (con_stamp <= now) {
-			nm_settings_connection_reset_autoconnect_retries (connection);
+			nm_settings_connection_autoconnect_retries_reset (connection);
 			changed = TRUE;
 		} else if (min_stamp == 0 || min_stamp > con_stamp)
 			min_stamp = con_stamp;
@@ -1436,6 +1600,7 @@ activate_slave_connections (NMPolicy *self, NMDevice *device)
 	guint i;
 	NMActRequest *req;
 	gboolean internal_activation = FALSE;
+	gs_free NMSettingsConnection **connections = NULL;
 
 	master_device = nm_device_get_iface (device);
 	g_assert (master_device);
@@ -1459,28 +1624,34 @@ activate_slave_connections (NMPolicy *self, NMDevice *device)
 		internal_activation = subject && nm_auth_subject_is_internal (subject);
 	}
 
-	if (!internal_activation) {
-		gs_free NMSettingsConnection **connections = NULL;
+	connections = nm_settings_get_connections_sorted (priv->settings, NULL);
+	for (i = 0; connections[i]; i++) {
+		NMConnection *slave;
+		NMSettingConnection *s_slave_con;
+		const char *slave_master;
 
-		connections = nm_settings_get_connections_sorted (priv->settings, NULL);
+		slave = NM_CONNECTION (connections[i]);
 
-		for (i = 0; connections[i]; i++) {
-			NMConnection *slave;
-			NMSettingConnection *s_slave_con;
-			const char *slave_master;
+		s_slave_con = nm_connection_get_setting_connection (slave);
+		g_assert (s_slave_con);
+		slave_master = nm_setting_connection_get_master (s_slave_con);
+		if (!slave_master)
+			continue;
 
-			slave = NM_CONNECTION (connections[i]);
+		if (   nm_streq0 (slave_master, master_device)
+		    || nm_streq0 (slave_master, master_uuid_applied)
+		    || nm_streq0 (slave_master, master_uuid_settings)) {
+			NMSettingsConnection *settings = NM_SETTINGS_CONNECTION (slave);
+			NMSettingsAutoconnectBlockedReason reason;
 
-			s_slave_con = nm_connection_get_setting_connection (slave);
-			g_assert (s_slave_con);
-			slave_master = nm_setting_connection_get_master (s_slave_con);
-			if (!slave_master)
-				continue;
+			if (!internal_activation)
+				nm_settings_connection_autoconnect_retries_reset (settings);
 
-			if (   !g_strcmp0 (slave_master, master_device)
-			    || !g_strcmp0 (slave_master, master_uuid_applied)
-			    || !g_strcmp0 (slave_master, master_uuid_settings))
-				nm_settings_connection_reset_autoconnect_retries (NM_SETTINGS_CONNECTION (slave));
+			reason = nm_settings_connection_autoconnect_blocked_reason_get (settings);
+			if (reason == NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED) {
+				reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE;
+				nm_settings_connection_autoconnect_blocked_reason_set (settings, reason);
+			}
 		}
 	}
 
@@ -1568,6 +1739,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);
 
@@ -1576,6 +1748,28 @@ device_state_changed (NMDevice *device,
 	NMIP6Config *ip6_config;
 	NMSettingConnection *s_con = NULL;
 
+	switch (nm_device_state_reason_check (reason)) {
+	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED:
+	case NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING:
+	case NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED:
+	case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
+	case NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED:
+	case NM_DEVICE_STATE_REASON_GSM_SIM_WRONG:
+	case NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT:
+	case NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED:
+	case NM_DEVICE_STATE_REASON_GSM_APN_FAILED:
+		/* 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,
+			                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED);
+		}
+		break;
+	default:
+		break;
+	}
+
 	switch (new_state) {
 	case NM_DEVICE_STATE_FAILED:
 		/* Mark the connection invalid if it failed during activation so that
@@ -1584,26 +1778,31 @@ device_state_changed (NMDevice *device,
 		if (   connection
 		    && old_state >= NM_DEVICE_STATE_PREPARE
 		    && old_state <= NM_DEVICE_STATE_ACTIVATED) {
-			int tries = nm_settings_connection_get_autoconnect_retries (connection);
+			int tries;
 
+			tries = nm_settings_connection_autoconnect_retries_get (connection);
 			if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_NO_SECRETS) {
 				_LOGD (LOGD_DEVICE, "connection '%s' now blocked from autoconnect due to no secrets",
 				       nm_settings_connection_get_id (connection));
 
-				nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS);
+				nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS);
 			} else if (tries != 0) {
-				_LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; %d tries left",
-				       nm_settings_connection_get_id (connection), tries);
-				if (tries > 0)
-					nm_settings_connection_set_autoconnect_retries (connection, tries - 1);
+				if (tries > 0) {
+					_LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; %d tries left",
+					       nm_settings_connection_get_id (connection), tries);
+					nm_settings_connection_autoconnect_retries_set (connection, --tries);
+				} else {
+					_LOGD (LOGD_DEVICE, "connection '%s' failed to autoconnect; infinite tries left",
+					       nm_settings_connection_get_id (connection));
+				}
 			}
 
-			if (nm_settings_connection_get_autoconnect_retries (connection) == 0) {
+			if (nm_settings_connection_autoconnect_retries_get (connection) == 0) {
 				_LOGI (LOGD_DEVICE, "disabling autoconnect for connection '%s'.",
 				       nm_settings_connection_get_id (connection));
 				/* Schedule a handler to reset retries count */
 				if (!priv->reset_retries_id) {
-					gint32 retry_time = nm_settings_connection_get_autoconnect_retry_time (connection);
+					gint32 retry_time = nm_settings_connection_autoconnect_blocked_until_get (connection);
 
 					g_warn_if_fail (retry_time != 0);
 					priv->reset_retries_id = g_timeout_add_seconds (MAX (0, retry_time - nm_utils_get_monotonic_timestamp_s ()), reset_connections_retries, self);
@@ -1615,7 +1814,7 @@ device_state_changed (NMDevice *device,
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (connection) {
 			/* Reset auto retries back to default since connection was successful */
-			nm_settings_connection_reset_autoconnect_retries (connection);
+			nm_settings_connection_autoconnect_retries_reset (connection);
 
 			/* And clear secrets so they will always be requested from the
 			 * settings service when the next connection is made.
@@ -1654,8 +1853,8 @@ device_state_changed (NMDevice *device,
 					/* The connection was deactivated, so block just this connection */
 					_LOGD (LOGD_DEVICE, "blocking autoconnect of connection '%s' by user request",
 					       nm_settings_connection_get_id (connection));
-					nm_settings_connection_set_autoconnect_blocked_reason (connection,
-					                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_BLOCKED);
+					nm_settings_connection_autoconnect_blocked_reason_set (connection,
+					                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST);
 				}
 			}
 		}
@@ -1680,11 +1879,20 @@ device_state_changed (NMDevice *device,
 		/* Reset auto-connect retries of all slaves and schedule them for
 		 * activation. */
 		activate_slave_connections (self, device);
+
+		/* Now that the device state is progressing, we don't care
+		 * anymore for the AC state. */
+		ac = (NMActiveConnection *) nm_device_get_act_request (device);
+		if (ac && g_hash_table_remove (priv->pending_active_connections, ac)) {
+			g_signal_handlers_disconnect_by_func (ac, pending_ac_state_changed, self);
+			g_object_weak_unref (G_OBJECT (ac), pending_ac_gone, self);
+			g_object_unref (self);
+		}
 		break;
 	case NM_DEVICE_STATE_IP_CONFIG:
 		/* We must have secrets if we got here. */
 		if (connection)
-			nm_settings_connection_set_autoconnect_blocked_reason (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_UNBLOCKED);
+			nm_settings_connection_autoconnect_blocked_reason_set (connection, NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE);
 		break;
 	case NM_DEVICE_STATE_SECONDARIES:
 		if (connection)
@@ -1735,7 +1943,7 @@ device_ip4_config_changed (NMDevice *device,
 		}
 		update_ip4_dns (self, priv->dns_manager);
 		update_ip4_routing (self, TRUE);
-		update_system_hostname (self, priv->default_device4, priv->default_device6, "ip4 conf");
+		update_system_hostname (self, "ip4 conf");
 	} else {
 		/* Old configs get removed immediately */
 		if (old_config)
@@ -1771,7 +1979,7 @@ device_ip6_config_changed (NMDevice *device,
 		}
 		update_ip6_dns (self, priv->dns_manager);
 		update_ip6_routing (self, TRUE);
-		update_system_hostname (self, priv->default_device4, priv->default_device6, "ip6 conf");
+		update_system_hostname (self, "ip6 conf");
 	} else {
 		/* Old configs get removed immediately */
 		if (old_config)
@@ -2088,30 +2296,26 @@ dns_config_changed (NMDnsManager *dns_manager, gpointer user_data)
 	 * (race in updating DNS and doing the reverse lookup).
 	 */
 
-	nm_clear_g_cancellable (&priv->lookup_cancellable);
+	nm_clear_g_cancellable (&priv->lookup.cancellable);
 
 	/* Re-start the hostname lookup thread if we don't have hostname yet. */
-	if (priv->lookup_addr) {
+	if (priv->lookup.addr) {
 		char *str = NULL;
 		gs_free char *hostname = NULL;
 
 		/* Check if the hostname was externally set */
-		if (   _get_hostname (self, &hostname)
+		if (   (hostname = _get_hostname (self))
 		    && nm_utils_is_specific_hostname (hostname)
 		    && !nm_streq0 (hostname, priv->last_hostname)) {
-			g_clear_object (&priv->lookup_addr);
+			g_clear_object (&priv->lookup.addr);
 			return;
 		}
 
 		_LOGD (LOGD_DNS, "restarting reverse-lookup thread for address %s",
-		       (str = g_inet_address_to_string (priv->lookup_addr)));
+		       (str = g_inet_address_to_string (priv->lookup.addr)));
 		g_free (str);
 
-		priv->lookup_cancellable = g_cancellable_new ();
-		g_resolver_lookup_by_address_async (priv->resolver,
-		                                    priv->lookup_addr,
-		                                    priv->lookup_cancellable,
-		                                    lookup_callback, self);
+		lookup_by_address (self);
 	}
 }
 
@@ -2141,7 +2345,7 @@ connection_updated (NMSettings *settings,
 			nm_device_reapply_settings_immediately (device);
 
 		/* Reset auto retries back to default since connection was updated */
-		nm_settings_connection_reset_autoconnect_retries (connection);
+		nm_settings_connection_autoconnect_retries_reset (connection);
 	}
 
 	schedule_activate_all (self);
@@ -2240,6 +2444,15 @@ nm_policy_get_activating_ip6_device (NMPolicy *self)
 
 /*****************************************************************************/
 
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_hostname_mode_to_string, NMPolicyHostnameMode,
+	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_NONE,  "none"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_DHCP,  "dhcp"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_POLICY_HOSTNAME_MODE_FULL,  "full"),
+);
+
+/*****************************************************************************/
+
 static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
@@ -2300,6 +2513,8 @@ nm_policy_init (NMPolicy *self)
 
 	priv->netns = g_object_ref (nm_netns_get ());
 
+	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
+
 	hostname_mode = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
 	                                          NM_CONFIG_KEYFILE_GROUP_MAIN,
 	                                          NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE,
@@ -2311,8 +2526,8 @@ nm_policy_init (NMPolicy *self)
 	else /* default - full mode */
 		priv->hostname_mode = NM_POLICY_HOSTNAME_MODE_FULL;
 
-	_LOGI (LOGD_DNS, "hostname management mode: %s", hostname_mode ? hostname_mode : "default");
 	priv->devices = g_hash_table_new (NULL, NULL);
+	priv->pending_active_connections = g_hash_table_new (NULL, NULL);
 	priv->ip6_prefix_delegations = g_array_new (FALSE, FALSE, sizeof (IP6PrefixDelegation));
 	g_array_set_clear_func (priv->ip6_prefix_delegations, clear_ip6_prefix_delegation);
 }
@@ -2325,7 +2540,7 @@ constructed (GObject *object)
 	char *hostname = NULL;
 
 	/* Grab hostname on startup and use that if nothing provides one */
-	if (_get_hostname (self, &hostname)) {
+	if ((hostname = _get_hostname (self))) {
 		/* init last_hostname */
 		priv->last_hostname = hostname;
 
@@ -2333,6 +2548,8 @@ constructed (GObject *object)
 		if (nm_utils_is_specific_hostname (hostname))
 			priv->orig_hostname = g_strdup (hostname);
 	}
+	_LOGT (LOGD_DNS, "hostname-original: set to %s%s%s",
+	       NM_PRINT_FMT_QUOTE_STRING (priv->orig_hostname));
 
 	priv->firewall_manager = g_object_ref (nm_firewall_manager_get ());
 	g_signal_connect (priv->firewall_manager, NM_FIREWALL_MANAGER_STATE_CHANGED,
@@ -2343,9 +2560,10 @@ constructed (GObject *object)
 	priv->config_changed_id = g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED,
 	                                            G_CALLBACK (dns_config_changed), self);
 
-	priv->resolver = g_resolver_get_default ();
+	priv->lookup.resolver = g_resolver_get_default ();
+
+	g_signal_connect (priv->hostname_manager, "notify::" NM_HOSTNAME_MANAGER_HOSTNAME, (GCallback) hostname_changed, priv);
 
-	g_signal_connect (priv->manager, "notify::" NM_MANAGER_HOSTNAME,           (GCallback) hostname_changed, priv);
 	g_signal_connect (priv->manager, "notify::" NM_MANAGER_SLEEPING,           (GCallback) sleeping_changed, priv);
 	g_signal_connect (priv->manager, "notify::" NM_MANAGER_NETWORKING_ENABLED, (GCallback) sleeping_changed, priv);
 	g_signal_connect (priv->manager, NM_MANAGER_INTERNAL_DEVICE_ADDED,         (GCallback) device_added, priv);
@@ -2360,6 +2578,8 @@ constructed (GObject *object)
 	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_AGENT_REGISTERED,              (GCallback) secret_agent_registered, priv);
 
 	G_OBJECT_CLASS (nm_policy_parent_class)->constructed (object);
+
+	_LOGD (LOGD_DNS, "hostname-mode: %s", _hostname_mode_to_string (priv->hostname_mode));
 }
 
 NMPolicy *
@@ -2383,10 +2603,15 @@ dispose (GObject *object)
 	GHashTableIter h_iter;
 	NMDevice *device;
 
-	nm_clear_g_cancellable (&priv->lookup_cancellable);
+	nm_clear_g_cancellable (&priv->lookup.cancellable);
+	g_clear_object (&priv->lookup.addr);
+	g_clear_object (&priv->lookup.resolver);
 
-	g_clear_object (&priv->lookup_addr);
-	g_clear_object (&priv->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);
+	g_clear_pointer (&priv->pending_active_connections, g_hash_table_unref);
 
 	while (priv->pending_activation_checks)
 		activate_data_free (priv->pending_activation_checks->data);
@@ -2424,6 +2649,11 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->cur_hostname, g_free);
 	g_clear_pointer (&priv->last_hostname, g_free);
 
+	if (priv->hostname_manager) {
+		g_signal_handlers_disconnect_by_data (priv->hostname_manager, priv);
+		g_clear_object (&priv->hostname_manager);
+	}
+
 	if (priv->settings) {
 		g_signal_handlers_disconnect_by_data (priv->settings, priv);
 		g_clear_object (&priv->settings);
diff --git a/src/nm-route-manager.c b/src/nm-route-manager.c
deleted file mode 100644
index f293f130..00000000
--- a/src/nm-route-manager.c
+++ /dev/null
@@ -1,1321 +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) 2015 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-route-manager.h"
-
-#include <string.h>
-
-#include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
-#include "platform/nmp-object.h"
-#include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
-
-/* if within half a second after adding an IP address a matching device-route shows
- * up, we delete it. */
-#define IP4_DEVICE_ROUTES_WAIT_TIME_NS                 (NM_UTILS_NS_PER_SECOND / 2)
-
-#define IP4_DEVICE_ROUTES_GC_INTERVAL_SEC              (IP4_DEVICE_ROUTES_WAIT_TIME_NS * 2)
-
-/*****************************************************************************/
-
-typedef struct {
-	guint len;
-	NMPlatformIPXRoute *entries[1];
-} RouteIndex;
-
-typedef struct {
-	GArray *entries;
-	RouteIndex *index;
-
-	/* list of effective metrics. The indexes of the array correspond to @index, not @entries. */
-	GArray *effective_metrics;
-
-	/* this array contains the effective metrics but using the reversed index that corresponds
-	 * to @entries, instead of @index. */
-	GArray *effective_metrics_reverse;
-} RouteEntries;
-
-typedef struct {
-	NMRouteManager *self;
-	gint64 scheduled_at_ns;
-	guint idle_id;
-	NMPObject *obj;
-} IP4DeviceRoutePurgeEntry;
-
-/*****************************************************************************/
-
-enum {
-	IP4_ROUTES_CHANGED,
-	LAST_SIGNAL,
-};
-static guint signals[LAST_SIGNAL] = { 0 };
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_LOG_WITH_PTR,
-	PROP_PLATFORM,
-);
-
-typedef struct {
-	NMPlatform *platform;
-
-	RouteEntries ip4_routes;
-	RouteEntries ip6_routes;
-	struct {
-		GHashTable *entries;
-		guint gc_id;
-	} ip4_device_routes;
-
-	bool log_with_ptr;
-} NMRouteManagerPrivate;
-
-struct _NMRouteManager {
-	GObject parent;
-	NMRouteManagerPrivate _priv;
-};
-
-struct _NMRouteManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMRouteManager, nm_route_manager, G_TYPE_OBJECT);
-
-#define NM_ROUTE_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMRouteManager, NM_IS_ROUTE_MANAGER)
-
-/*****************************************************************************/
-
-typedef struct {
-	const NMPlatformVTableRoute *vt;
-
-	/* a compare function for two routes that considers only the destination fields network/plen.
-	 * It is a looser comparisong then @route_id_cmp(), that means that if @route_dest_cmp()
-	 * returns non-zero, also @route_id_cmp() returns the same value. It also means, that
-	 * sorting by @route_id_cmp() implicitly sorts by @route_dest_cmp() as well. */
-	int (*route_dest_cmp) (const NMPlatformIPXRoute *r1, const NMPlatformIPXRoute *r2);
-
-	/* a compare function for two routes that considers only the fields network/plen,metric. */
-	int (*route_id_cmp) (const NMPlatformIPXRoute *r1, const NMPlatformIPXRoute *r2);
-} VTableIP;
-
-static const VTableIP vtable_v4, vtable_v6;
-
-#define VTABLE_ROUTE_INDEX(vtable, garray, idx) ((NMPlatformIPXRoute *) &((garray)->data[(idx) * (vtable)->vt->sizeof_route]))
-
-#define VTABLE_IS_DEVICE_ROUTE(vtable, route) ((vtable)->vt->is_ip4 \
-                                                ? ((route)->r4.gateway == 0) \
-                                                : IN6_IS_ADDR_UNSPECIFIED (&(route)->r6.gateway) )
-
-#define CMP_AND_RETURN_INT(a, b) \
-	G_STMT_START { \
-		typeof(a) _a = (a), _b = (b); \
-		\
-		if (_a < _b) \
-			return -1; \
-		if (_a > _b) \
-			return 1; \
-	} G_STMT_END
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME   "route-mgr"
-#undef  _NMLOG_ENABLED
-#define _NMLOG_ENABLED(level, addr_family) \
-    ({ \
-        const int __addr_family = (addr_family); \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        nm_logging_enabled (__level, __domain); \
-    })
-#define _NMLOG(level, addr_family, ...) \
-    G_STMT_START { \
-        const int __addr_family = (addr_family); \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            char __ch = __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'); \
-            char __prefix[30] = _NMLOG_PREFIX_NAME; \
-            \
-            if (NM_ROUTE_MANAGER_GET_PRIVATE (self)->log_with_ptr) \
-                g_snprintf (__prefix, sizeof (__prefix), "%s%c[%p]", _NMLOG_PREFIX_NAME, __ch, (self)); \
-            else \
-                __prefix[NM_STRLEN (_NMLOG_PREFIX_NAME)] = __ch; \
-            _nm_log ((level), (__domain), 0, NULL, NULL, \
-                     "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                     __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-        } \
-    } G_STMT_END
-
-/*****************************************************************************/
-
-static gboolean _ip4_device_routes_cancel (NMRouteManager *self);
-
-/*****************************************************************************/
-
-#if NM_MORE_ASSERTS && !defined (G_DISABLE_ASSERT)
-static inline void
-ASSERT_route_index_valid (const VTableIP *vtable, const GArray *entries, const RouteIndex *index, gboolean unique_ifindexes)
-{
-	guint i, j;
-	int c;
-	const NMPlatformIPXRoute *r1, *r2;
-	gs_unref_hashtable GHashTable *ptrs = g_hash_table_new (NULL, NULL);
-	const NMPlatformIPXRoute *r_first = NULL, *r_last = NULL;
-
-	g_assert (index);
-
-	if (entries)
-		g_assert_cmpint (entries->len, ==, index->len);
-	else
-		g_assert (index->len == 0);
-
-	if (index->len > 0) {
-		r_first = VTABLE_ROUTE_INDEX (vtable, entries, 0);
-		r_last = VTABLE_ROUTE_INDEX (vtable, entries, index->len - 1);
-	}
-
-	/* assert that the @index is valid for the @entries. */
-
-	g_assert (!index->entries[index->len]);
-	for (i = 0; i < index->len; i++) {
-		r1 = index->entries[i];
-
-		g_assert (r1);
-		g_assert (r1 >= r_first);
-		g_assert (r1 <= r_last);
-		g_assert_cmpint ((((char *) r1) - ((char *) entries->data)) % vtable->vt->sizeof_route, ==, 0);
-
-		g_assert (!g_hash_table_contains (ptrs, (gpointer) r1));
-		g_hash_table_add (ptrs, (gpointer) r1);
-
-		for (j = i; j > 0; ) {
-			r2 = index->entries[--j];
-
-			c = vtable->route_id_cmp (r1, r2);
-			g_assert (c >= 0);
-			if (c != 0)
-				break;
-			if (unique_ifindexes)
-				g_assert_cmpint (r1->rx.ifindex, !=, r2->rx.ifindex);
-		}
-	}
-}
-#else
-#define ASSERT_route_index_valid(vtable, entries, index, unique_ifindexes) G_STMT_START { (void) 0; } G_STMT_END
-#endif
-
-/*****************************************************************************/
-
-static int
-_v4_route_dest_cmp (const NMPlatformIP4Route *r1, const NMPlatformIP4Route *r2)
-{
-	CMP_AND_RETURN_INT (r1->plen, r2->plen);
-	CMP_AND_RETURN_INT (nm_utils_ip4_address_clear_host_address (r1->network, r1->plen),
-	                    nm_utils_ip4_address_clear_host_address (r2->network, r2->plen));
-	return 0;
-}
-
-static int
-_v6_route_dest_cmp (const NMPlatformIP6Route *r1, const NMPlatformIP6Route *r2)
-{
-	struct in6_addr n1, n2;
-
-	CMP_AND_RETURN_INT (r1->plen, r2->plen);
-
-	nm_utils_ip6_address_clear_host_address (&n1, &r1->network, r1->plen);
-	nm_utils_ip6_address_clear_host_address (&n2, &r2->network, r2->plen );
-	return memcmp (&n1, &n2, sizeof (n1));
-}
-
-static int
-_v4_route_id_cmp (const NMPlatformIP4Route *r1, const NMPlatformIP4Route *r2)
-{
-	CMP_AND_RETURN_INT (r1->plen, r2->plen);
-	CMP_AND_RETURN_INT (nm_utils_ip4_address_clear_host_address (r1->network, r1->plen),
-	                    nm_utils_ip4_address_clear_host_address (r2->network, r2->plen));
-	CMP_AND_RETURN_INT (r1->metric, r2->metric);
-	return 0;
-}
-
-static int
-_v6_route_id_cmp (const NMPlatformIP6Route *r1, const NMPlatformIP6Route *r2)
-{
-	struct in6_addr n1, n2;
-	int c;
-
-	CMP_AND_RETURN_INT (r1->plen, r2->plen);
-
-	nm_utils_ip6_address_clear_host_address (&n1, &r1->network, r1->plen);
-	nm_utils_ip6_address_clear_host_address (&n2, &r2->network, r2->plen);
-	c = memcmp (&n1, &n2, sizeof (n1));
-	if (c != 0)
-		return c;
-
-	CMP_AND_RETURN_INT (nm_utils_ip6_route_metric_normalize (r1->metric),
-	                    nm_utils_ip6_route_metric_normalize (r2->metric));
-	return 0;
-}
-
-/*****************************************************************************/
-
-static int
-_route_index_create_sort (const NMPlatformIPXRoute **p1, const NMPlatformIPXRoute ** p2, const VTableIP *vtable)
-{
-	return vtable->route_id_cmp (*p1, *p2);
-}
-
-static RouteIndex *
-_route_index_create (const VTableIP *vtable, const GArray *routes)
-{
-	RouteIndex *index;
-	guint i;
-	guint len = routes ? routes->len : 0;
-
-	index = g_malloc (sizeof (RouteIndex) + len * sizeof (NMPlatformIPXRoute *));
-
-	index->len = len;
-	for (i = 0; i < len; i++)
-		index->entries[i] = VTABLE_ROUTE_INDEX (vtable, routes, i);
-	index->entries[i] = NULL;
-
-	/* this is a stable sort, which is very important at this point. */
-	g_qsort_with_data (index->entries,
-	                   len,
-	                   sizeof (NMPlatformIPXRoute *),
-	                   (GCompareDataFunc) _route_index_create_sort,
-	                   (gpointer) vtable);
-	return index;
-}
-
-static int
-_vx_route_id_cmp_full (const NMPlatformIPXRoute *r1, const NMPlatformIPXRoute *r2, const VTableIP *vtable)
-{
-	return vtable->route_id_cmp (r1, r2);
-}
-
-static gssize
-_route_index_find (const VTableIP *vtable, const RouteIndex *index, const NMPlatformIPXRoute *needle)
-{
-	gssize idx, idx2;
-
-	idx = _nm_utils_ptrarray_find_binary_search ((gconstpointer *) index->entries, index->len, needle, (GCompareDataFunc) _vx_route_id_cmp_full, (gpointer) vtable);
-	if (idx < 0)
-		return idx;
-
-	/* we only know that the route at index @idx has matching destination. Also find the one with the right
-	 * ifindex by searching the neighbours */
-
-	idx2 = idx;
-	do {
-		if (index->entries[idx2]->rx.ifindex == needle->rx.ifindex)
-			return idx2;
-	} while (   idx2 > 0
-	         && vtable->route_id_cmp (index->entries[--idx2], needle) != 0);
-
-	for (idx++; idx < index->len; idx++ ){
-		if (vtable->route_id_cmp (index->entries[idx], needle) != 0)
-			break;
-		if (index->entries[idx]->rx.ifindex == needle->rx.ifindex)
-			return idx;
-	}
-
-	return ~idx;
-}
-
-static guint
-_route_index_reverse_idx (const VTableIP *vtable, const RouteIndex *index, guint idx_idx, const GArray *routes)
-{
-	const NMPlatformIPXRoute *r, *r0;
-	gssize offset;
-
-	/* reverse the @idx_idx that points into @index, to the corresponding index into the unsorted @routes array. */
-
-	r = index->entries[idx_idx];
-	r0 = VTABLE_ROUTE_INDEX (vtable, routes, 0);
-
-	if (vtable->vt->is_ip4)
-		offset = &r->r4 - &r0->r4;
-	else
-		offset = &r->r6 - &r0->r6;
-	g_assert (offset >= 0 && offset < index->len);
-	g_assert (VTABLE_ROUTE_INDEX (vtable, routes, offset) == r);
-	return offset;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_route_equals_ignoring_ifindex (const VTableIP *vtable,
-                                const NMPlatformIPXRoute *plat_rt,
-                                const NMPlatformIPXRoute *rt,
-                                gint64 rt_metric)
-{
-	NMPlatformIPXRoute rt_backup;
-
-	memcpy (&rt_backup, rt, vtable->vt->sizeof_route);
-	rt_backup.rx.ifindex = plat_rt->rx.ifindex;
-	if (rt_metric >= 0)
-		rt_backup.rx.metric = (guint32) rt_metric;
-	rt_backup.rx.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt_backup.rx.rt_source);
-
-	return vtable->vt->route_cmp (plat_rt, &rt_backup, FALSE) == 0;
-}
-
-static NMPlatformIPXRoute *
-_get_next_ipx_route (const RouteIndex *index, gboolean start_at_zero, guint *cur_idx, int ifindex)
-{
-	guint i;
-
-	if (start_at_zero)
-		i = 0;
-	else
-		i = *cur_idx + 1;
-	/* Find the next route with matching @ifindex. */
-	for (; i < index->len; i++) {
-		if (index->entries[i]->rx.ifindex == ifindex) {
-			*cur_idx = i;
-			return index->entries[i];
-		}
-	}
-	*cur_idx = index->len;
-	return NULL;
-}
-
-static const NMPlatformIPXRoute *
-_get_next_known_route (const VTableIP *vtable, const RouteIndex *index, gboolean start_at_zero, guint *cur_idx)
-{
-	guint i = 0;
-	const NMPlatformIPXRoute *cur = NULL;
-
-	if (!start_at_zero) {
-		i = *cur_idx;
-		cur = index->entries[i];
-		i++;
-	}
-	/* For @known_routes we expect that all routes have the same @ifindex. This is not enforced however,
-	 * the ifindex value of these routes is ignored. */
-	for (; i < index->len; i++) {
-		const NMPlatformIPXRoute *r = index->entries[i];
-
-		/* skip over default routes. */
-		if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r))
-			continue;
-
-		/* @known_routes should not, but could contain duplicate routes. Skip over them. */
-		if (cur && vtable->route_id_cmp (cur, r) == 0)
-			continue;
-
-		*cur_idx = i;
-		return r;
-	}
-	*cur_idx = index->len;
-	return NULL;
-}
-
-static const NMPlatformIPXRoute *
-_get_next_plat_route (const RouteIndex *index, gboolean start_at_zero, guint *cur_idx)
-{
-	if (start_at_zero)
-		*cur_idx = 0;
-	else
-		++*cur_idx;
-
-	/* get next route from the platform index. */
-	if (*cur_idx < index->len)
-		return index->entries[*cur_idx];
-	*cur_idx = index->len;
-	return NULL;
-}
-
-static int
-_sort_indexes_cmp (guint *a, guint *b)
-{
-	CMP_AND_RETURN_INT (*a, *b);
-	g_return_val_if_reached (0);
-}
-
-/*****************************************************************************/
-
-static gboolean
-_vx_route_sync (const VTableIP *vtable, NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync)
-{
-	NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-	GArray *plat_routes;
-	RouteEntries *ipx_routes;
-	RouteIndex *plat_routes_idx, *known_routes_idx;
-	gboolean success = TRUE;
-	guint i, i_type;
-	GArray *to_delete_indexes = NULL;
-	GPtrArray *to_add_routes = NULL;
-	guint i_known_routes, i_plat_routes, i_ipx_routes;
-	const NMPlatformIPXRoute *cur_known_route, *cur_plat_route;
-	NMPlatformIPXRoute *cur_ipx_route;
-	gint64 *p_effective_metric = NULL;
-	gboolean ipx_routes_changed = FALSE;
-	gint64 *effective_metrics = NULL;
-
-	nm_platform_process_events (priv->platform);
-
-	ipx_routes = vtable->vt->is_ip4 ? &priv->ip4_routes : &priv->ip6_routes;
-	plat_routes = vtable->vt->route_get_all (priv->platform, ifindex,
-	                                         ignore_kernel_routes
-	                                             ? NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT
-	                                             : NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_RTPROT_KERNEL);
-	plat_routes_idx = _route_index_create (vtable, plat_routes);
-	known_routes_idx = _route_index_create (vtable, known_routes);
-
-	effective_metrics = &g_array_index (ipx_routes->effective_metrics, gint64, 0);
-
-	ASSERT_route_index_valid (vtable, plat_routes, plat_routes_idx, TRUE);
-	ASSERT_route_index_valid (vtable, known_routes, known_routes_idx, FALSE);
-
-	_LOGD (vtable->vt->addr_family, "%3d: sync %u IPv%c routes", ifindex, known_routes_idx->len, vtable->vt->is_ip4 ? '4' : '6');
-	if (_LOGt_ENABLED (vtable->vt->addr_family)) {
-		for (i = 0; i < known_routes_idx->len; i++) {
-			_LOGt (vtable->vt->addr_family, "%3d: sync new route #%u: %s",
-			       ifindex, i, vtable->vt->route_to_string (VTABLE_ROUTE_INDEX (vtable, known_routes, i), NULL, 0));
-		}
-		for (i = 0; i < ipx_routes->index->len; i++)
-			_LOGt (vtable->vt->addr_family, "%3d: STATE: has     #%u - %s (%lld)",
-			       ifindex, i,
-			       vtable->vt->route_to_string (ipx_routes->index->entries[i], NULL, 0),
-			       (long long) g_array_index (ipx_routes->effective_metrics, gint64, i));
-	}
-
-	/***************************************************************************
-	 * Check which routes are in @known_routes, and update @ipx_routes.
-	 *
-	 * This first part only updates @ipx_routes to find out what routes must
-	 * be added/deleted.
-	 **************************************************************************/
-
-	/* iterate over @ipx_routes and @known_routes */
-	cur_ipx_route = _get_next_ipx_route (ipx_routes->index, TRUE, &i_ipx_routes, ifindex);
-	cur_known_route = _get_next_known_route (vtable, known_routes_idx, TRUE, &i_known_routes);
-	while (cur_ipx_route || cur_known_route) {
-		int route_id_cmp_result = -1;
-
-		while (   cur_ipx_route
-		       && (   !cur_known_route
-		           || ((route_id_cmp_result = vtable->route_id_cmp (cur_ipx_route, cur_known_route)) < 0))) {
-			/* we have @cur_ipx_route, which is less then @cur_known_route. Hence,
-			 * the route does no longer exist in @known_routes */
-			if (!to_delete_indexes)
-				to_delete_indexes = g_array_new (FALSE, FALSE, sizeof (guint));
-			g_array_append_val (to_delete_indexes, i_ipx_routes);
-
-			/* find the next @cur_ipx_route with matching ifindex. */
-			cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex);
-		}
-		if (   cur_ipx_route
-		    && cur_known_route
-		    && route_id_cmp_result == 0) {
-			if (!_route_equals_ignoring_ifindex (vtable, cur_ipx_route, cur_known_route, -1)) {
-				/* The routes match. Update the entry in place. As this is an exact match of primary
-				 * fields, this only updates possibly modified fields such as @gateway or @mss.
-				 * Modifiying @cur_ipx_route this way does not invalidate @ipx_routes->index. */
-				memcpy (cur_ipx_route, cur_known_route, vtable->vt->sizeof_route);
-				cur_ipx_route->rx.ifindex = ifindex;
-				cur_ipx_route->rx.metric = vtable->vt->metric_normalize (cur_ipx_route->rx.metric);
-				nm_utils_ipx_address_clear_host_address (vtable->vt->addr_family, cur_ipx_route->rx.network_ptr,
-				                                         cur_ipx_route->rx.network_ptr, cur_ipx_route->rx.plen);
-				ipx_routes_changed = TRUE;
-				_LOGt (vtable->vt->addr_family, "%3d: STATE: update  #%u - %s", ifindex, i_ipx_routes,
-				       vtable->vt->route_to_string (cur_ipx_route, NULL, 0));
-			}
-		} else if (cur_known_route) {
-			g_assert (!cur_ipx_route || route_id_cmp_result > 0);
-			/* @cur_known_route is new. We cannot immediately add @cur_known_route to @ipx_routes, because
-			 * it would invalidate @ipx_routes->index. Instead remember to add it later. */
-			if (!to_add_routes)
-				to_add_routes = g_ptr_array_new ();
-			g_ptr_array_add (to_add_routes, (gpointer) cur_known_route);
-		}
-
-		if (cur_ipx_route && (!cur_known_route || route_id_cmp_result == 0))
-			cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex);
-		if (cur_known_route)
-			cur_known_route = _get_next_known_route (vtable, known_routes_idx, FALSE, &i_known_routes);
-	}
-
-	if (!full_sync && to_delete_indexes) {
-		/***************************************************************************
-		 * Delete routes in platform, that we are about to remove from @ipx_routes
-		 *
-		 * When doing a non-full_sync, we delete routes from platform that were previously
-		 * known by route-manager, and are now deleted.
-		 ***************************************************************************/
-
-		/* iterate over @to_delete_indexes and @plat_routes.
-		 * @to_delete_indexes contains the indexes (relative to ipx_routes->index) of items
-		 * we are about to delete. */
-		cur_plat_route = _get_next_plat_route (plat_routes_idx, TRUE, &i_plat_routes);
-		for (i = 0; i < to_delete_indexes->len; i++) {
-			int route_dest_cmp_result = 0;
-			i_ipx_routes = g_array_index (to_delete_indexes, guint, i);
-			cur_ipx_route = ipx_routes->index->entries[i_ipx_routes];
-			p_effective_metric = &effective_metrics[i_ipx_routes];
-
-			nm_assert (cur_ipx_route->rx.ifindex == ifindex);
-
-			if (*p_effective_metric == -1)
-				continue;
-
-			/* skip over @plat_routes that are ordered before our @cur_ipx_route. */
-			while (   cur_plat_route
-			       && (route_dest_cmp_result = vtable->route_dest_cmp (cur_plat_route, cur_ipx_route)) <= 0) {
-				if (   route_dest_cmp_result == 0
-				    && cur_plat_route->rx.metric >= *p_effective_metric)
-					break;
-				cur_plat_route = _get_next_plat_route (plat_routes_idx, FALSE, &i_plat_routes);
-			}
-
-			if (!cur_plat_route) {
-				/* no more platform routes. Break the loop. */
-				break;
-			}
-
-			if (   route_dest_cmp_result == 0
-			    && cur_plat_route->rx.metric == *p_effective_metric) {
-				/* we are about to delete cur_ipx_route and we have a matching route
-				 * in platform. Delete it. */
-				_LOGt (vtable->vt->addr_family, "%3d: platform rt-rm #%u - %s", ifindex, i_plat_routes,
-				       vtable->vt->route_to_string (cur_plat_route, NULL, 0));
-				vtable->vt->route_delete (priv->platform, ifindex, cur_plat_route);
-			}
-		}
-	}
-
-	/* Update @ipx_routes with the just learned changes. */
-	if (to_delete_indexes || to_add_routes) {
-		if (to_delete_indexes) {
-			for (i = 0; i < to_delete_indexes->len; i++) {
-				guint idx = g_array_index (to_delete_indexes, guint, i);
-
-				_LOGt (vtable->vt->addr_family, "%3d: STATE: delete  #%u - %s", ifindex, idx,
-				       vtable->vt->route_to_string (ipx_routes->index->entries[idx], NULL, 0));
-				g_array_index (to_delete_indexes, guint, i) = _route_index_reverse_idx (vtable, ipx_routes->index, idx, ipx_routes->entries);
-			}
-			g_array_sort (to_delete_indexes, (GCompareFunc) _sort_indexes_cmp);
-			nm_utils_array_remove_at_indexes (ipx_routes->entries, &g_array_index (to_delete_indexes, guint, 0), to_delete_indexes->len);
-			nm_utils_array_remove_at_indexes (ipx_routes->effective_metrics_reverse, &g_array_index (to_delete_indexes, guint, 0), to_delete_indexes->len);
-			g_array_unref (to_delete_indexes);
-		}
-		if (to_add_routes) {
-			guint j = ipx_routes->effective_metrics_reverse->len;
-
-			g_array_set_size (ipx_routes->effective_metrics_reverse, j + to_add_routes->len);
-
-			for (i = 0; i < to_add_routes->len; i++) {
-				NMPlatformIPXRoute *ipx_route;
-
-				g_array_append_vals (ipx_routes->entries, g_ptr_array_index (to_add_routes, i), 1);
-
-				ipx_route = VTABLE_ROUTE_INDEX (vtable, ipx_routes->entries, ipx_routes->entries->len - 1);
-				ipx_route->rx.ifindex = ifindex;
-				ipx_route->rx.metric = vtable->vt->metric_normalize (ipx_route->rx.metric);
-				nm_utils_ipx_address_clear_host_address (vtable->vt->addr_family, ipx_route->rx.network_ptr,
-				                                         ipx_route->rx.network_ptr, ipx_route->rx.plen);
-
-				g_array_index (ipx_routes->effective_metrics_reverse, gint64, j++) = -1;
-
-				_LOGt (vtable->vt->addr_family, "%3d: STATE: added   #%u - %s", ifindex, ipx_routes->entries->len - 1,
-				       vtable->vt->route_to_string (ipx_route, NULL, 0));
-			}
-			g_ptr_array_unref (to_add_routes);
-		}
-		g_free (ipx_routes->index);
-		ipx_routes->index = _route_index_create (vtable, ipx_routes->entries);
-		ipx_routes_changed = TRUE;
-		ASSERT_route_index_valid (vtable, ipx_routes->entries, ipx_routes->index, TRUE);
-	}
-
-	if (ipx_routes_changed) {
-		/***************************************************************************
-		 * Rebuild the list of effective metrics. In case of conflicting routes,
-		 * we configure device routes with a bumped metric. We do this, because non-direct
-		 * routes might require this direct route to reach the gateway (e.g. the default
-		 * route).
-		 *
-		 * We determine the effective metrics only based on our internal list @ipx_routes
-		 * and don't consider @plat_routes. That means, we might bump the metric of a route
-		 * and thereby cause a conflict with an existing route on an unmanaged device (which
-		 * causes the route on the unmanaged device to be replaced).
-		 * Still, that is not much different then from messing with unmanaged routes when
-		 * the effective and the intended metrics equal. The rules is: NM will leave routes
-		 * on unmanaged devices alone, unless they conflict with what NM wants to configure.
-		 ***************************************************************************/
-
-		g_array_set_size (ipx_routes->effective_metrics, ipx_routes->entries->len);
-		effective_metrics = &g_array_index (ipx_routes->effective_metrics, gint64, 0);
-
-		/* Completely regenerate the list of effective metrics by walking through
-		 * ipx_routes->index and determining the effective metric. */
-
-		for (i_ipx_routes = 0; i_ipx_routes < ipx_routes->index->len; i_ipx_routes++) {
-			gint64 *p_effective_metric_before;
-			gboolean is_shadowed;
-			guint i_ipx_routes_before;
-
-			cur_ipx_route = ipx_routes->index->entries[i_ipx_routes];
-			p_effective_metric = &effective_metrics[i_ipx_routes];
-
-			is_shadowed =    i_ipx_routes > 0
-			              && vtable->route_dest_cmp (cur_ipx_route, ipx_routes->index->entries[i_ipx_routes - 1]) == 0;
-
-			if (!is_shadowed) {
-				/* the route is not shadowed, the effective metric is just as specified. */
-				*p_effective_metric = cur_ipx_route->rx.metric;
-				goto next;
-			}
-			if (!VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route)) {
-				/* The route is not a device route. We want to add redundant device routes, because
-				 * we might need the direct routes to the gateway. For non-direct routes, there is not much
-				 * reason to do the metric increment. */
-				*p_effective_metric = -1;
-				goto next;
-			}
-
-			/* The current route might be shadowed by several other routes. Find the one with the highest metric,
-			 * i.e. the one with an effecive metric set and in the index before the current index. */
-			i_ipx_routes_before = i_ipx_routes;
-			while (TRUE) {
-				nm_assert (i_ipx_routes_before > 0);
-
-				i_ipx_routes_before--;
-
-				p_effective_metric_before = &effective_metrics[i_ipx_routes_before];
-
-				if (*p_effective_metric_before == -1) {
-					/* this route is also shadowed, continue search. */
-					continue;
-				}
-
-				if (*p_effective_metric_before < cur_ipx_route->rx.metric) {
-					/* the previous route has a lower metric. There is no conflict,
-					 * just use the original metric. */
-					*p_effective_metric = cur_ipx_route->rx.metric;
-				} else if (*p_effective_metric_before == G_MAXUINT32) {
-					/* we cannot bump the metric. Don't configure this route. */
-					*p_effective_metric = -1;
-				} else {
-					/* bump the metric by one. */
-					*p_effective_metric = *p_effective_metric_before + 1;
-				}
-				break;
-			}
-next:
-			_LOGt (vtable->vt->addr_family, "%3d: new metric     #%u - %s (%lld)",
-			       ifindex, i_ipx_routes,
-			       vtable->vt->route_to_string (cur_ipx_route, NULL, 0),
-			       (long long) *p_effective_metric);
-		}
-	}
-
-	if (full_sync) {
-		/***************************************************************************
-		 * Delete all routes in platform, that no longer exist in @ipx_routes
-		 *
-		 * Different from the delete action above, we delete every unknown route on
-		 * the interface.
-		 ***************************************************************************/
-
-		/* iterate over @plat_routes and @ipx_routes */
-		cur_plat_route = _get_next_plat_route (plat_routes_idx, TRUE, &i_plat_routes);
-		cur_ipx_route = _get_next_ipx_route (ipx_routes->index, TRUE, &i_ipx_routes, ifindex);
-		if (cur_ipx_route)
-			p_effective_metric = &effective_metrics[i_ipx_routes];
-		while (cur_plat_route) {
-			int route_dest_cmp_result = 0;
-
-			g_assert (cur_plat_route->rx.ifindex == ifindex);
-
-			_LOGt (vtable->vt->addr_family, "%3d: platform rt    #%u - %s", ifindex, i_plat_routes, vtable->vt->route_to_string (cur_plat_route, NULL, 0));
-
-			/* skip over @cur_ipx_route that are ordered before @cur_plat_route */
-			while (   cur_ipx_route
-			       && ((route_dest_cmp_result = vtable->route_dest_cmp (cur_ipx_route, cur_plat_route)) <= 0)) {
-				if (   route_dest_cmp_result == 0
-				    && *p_effective_metric != -1
-				    && *p_effective_metric >= cur_plat_route->rx.metric) {
-					break;
-				}
-				cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex);
-				if (cur_ipx_route)
-					p_effective_metric = &effective_metrics[i_ipx_routes];
-			}
-
-			/* if @cur_ipx_route is not equal to @plat_route, the route must be deleted. */
-			if (   !cur_ipx_route
-			    || route_dest_cmp_result != 0
-			    || *p_effective_metric != cur_plat_route->rx.metric)
-				vtable->vt->route_delete (priv->platform, ifindex, cur_plat_route);
-
-			cur_plat_route = _get_next_plat_route (plat_routes_idx, FALSE, &i_plat_routes);
-		}
-	}
-
-	/***************************************************************************
-	 * Restore shadowed routes. These routes are on an other @ifindex then what
-	 * we are syncing now. But the current changes make it necessary to add those
-	 * routes.
-	 *
-	 * Only add some routes that might be necessary. We don't delete any routes
-	 * on other ifindexes here. I.e. we don't do a full sync, but only ~add~ routes
-	 * that were shadowed previously, but should be now present with a different
-	 * metric.
-	 **************************************************************************/
-
-	if (ipx_routes_changed) {
-		GArray *gateway_routes = NULL;
-
-		/* @effective_metrics_reverse contains the list of assigned metrics from the last
-		 * sync. Walk through it and see what changes there are (and possibly restore a
-		 * shadowed route).
-		 * Thereby also update @effective_metrics_reverse to be up-to-date again. */
-		for (i_ipx_routes = 0; i_ipx_routes < ipx_routes->entries->len; i_ipx_routes++) {
-			guint i_ipx_routes_reverse;
-			gint64 *p_effective_metric_reversed;
-
-			p_effective_metric = &effective_metrics[i_ipx_routes];
-
-			i_ipx_routes_reverse = _route_index_reverse_idx (vtable, ipx_routes->index, i_ipx_routes, ipx_routes->entries);
-			p_effective_metric_reversed = &g_array_index (ipx_routes->effective_metrics_reverse, gint64, i_ipx_routes_reverse);
-
-			if (*p_effective_metric_reversed == *p_effective_metric) {
-				/* The entry is up to date. No change, continue with the next one. */
-				continue;
-			}
-			*p_effective_metric_reversed = *p_effective_metric;
-
-			if (*p_effective_metric == -1) {
-				/* the entry is shadowed. Nothing to do. */
-				continue;
-			}
-
-			cur_ipx_route = ipx_routes->index->entries[i_ipx_routes];
-			if (cur_ipx_route->rx.ifindex == ifindex) {
-				/* @cur_ipx_route is on the current @ifindex. No need to special handling them
-				 * because we are about to do a full sync of the ifindex. */
-				continue;
-			}
-
-			/* the effective metric from previous sync changed. While @cur_ipx_route is not on the
-			 * ifindex we are about to sync, we still must add this route. Possibly it was shadowed
-			 * before, and now we want to restore it.
-			 *
-			 * Note that we don't do a full sync on the other ifindex. Especially, we don't delete
-			 * or add any further routes then this. That means there might be some stale routes
-			 * (with a higher metric!). They will only be removed on the next sync of that other
-			 * ifindex. */
-
-			if (!VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route)) {
-				/* the route to restore has a gateway. We can only restore the route
-				 * when we also have a direct route to the gateway. There can be cases
-				 * where the direct route is shadowed too, and we cannot restore the gateway
-				 * route.
-				 *
-				 * Restore first the direct-routes, and gateway-routes afterwards.
-				 * This can avoid some cases where we would fail to add the
-				 * gateway route. */
-				if (!gateway_routes)
-					gateway_routes = g_array_new (FALSE, FALSE, sizeof (guint));
-				g_array_append_val (gateway_routes, i_ipx_routes);
-			} else
-				vtable->vt->route_add (priv->platform, 0, cur_ipx_route, *p_effective_metric);
-		}
-
-		if (gateway_routes) {
-			for (i = 0; i < gateway_routes->len; i++) {
-				i_ipx_routes = g_array_index (gateway_routes, guint, i);
-				vtable->vt->route_add (priv->platform, 0,
-				                       ipx_routes->index->entries[i_ipx_routes],
-				                       effective_metrics[i_ipx_routes]);
-			}
-			g_array_unref (gateway_routes);
-		}
-	}
-
-	/***************************************************************************
-	 * Sync @ipx_routes for @ifindex to platform
-	 **************************************************************************/
-
-	for (i_type = 0; i_type < 2; i_type++) {
-		/* iterate (twice) over @ipx_routes and @plat_routes */
-		cur_plat_route = _get_next_plat_route (plat_routes_idx, TRUE, &i_plat_routes);
-		cur_ipx_route = _get_next_ipx_route (ipx_routes->index, TRUE, &i_ipx_routes, ifindex);
-		/* Iterate here over @ipx_routes instead of @known_routes. That is done because
-		 * we need to know whether a route is shadowed by another route, and that
-		 * requires to look at @ipx_routes. */
-		for (; cur_ipx_route; cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex)) {
-			int route_dest_cmp_result = -1;
-
-			if (   (i_type == 0 && !VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route))
-			    || (i_type == 1 && VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route))) {
-				/* Make two runs over the list of @ipx_routes. On the first, only add
-				 * device routes, on the second the others (gateway routes). */
-				continue;
-			}
-
-			p_effective_metric = &effective_metrics[i_ipx_routes];
-
-			if (*p_effective_metric == -1) {
-				/* @cur_ipx_route is shadewed by another route. */
-				continue;
-			}
-
-			/* skip over @plat_routes that are ordered before our @cur_ipx_route. */
-			while (   cur_plat_route
-			       && (route_dest_cmp_result = vtable->route_dest_cmp (cur_plat_route, cur_ipx_route)) <= 0) {
-				if (   route_dest_cmp_result == 0
-				    && cur_plat_route->rx.metric >= *p_effective_metric)
-					break;
-				cur_plat_route = _get_next_plat_route (plat_routes_idx, FALSE, &i_plat_routes);
-			}
-
-			/* only add the route if we don't have an identical route in @plat_routes,
-			 * i.e. if @cur_plat_route is different from @cur_ipx_route. */
-			if (   !cur_plat_route
-			    || route_dest_cmp_result != 0
-			    || !_route_equals_ignoring_ifindex (vtable, cur_plat_route, cur_ipx_route, *p_effective_metric)) {
-
-				if (!vtable->vt->route_add (priv->platform, ifindex, cur_ipx_route, *p_effective_metric)) {
-					if (cur_ipx_route->rx.rt_source < NM_IP_CONFIG_SOURCE_USER) {
-						_LOGD (vtable->vt->addr_family,
-						       "ignore error adding IPv%c route to kernel: %s",
-						       vtable->vt->is_ip4 ? '4' : '6',
-						       vtable->vt->route_to_string (cur_ipx_route, NULL, 0));
-					} else {
-						/* Remember that there was a failure, but for now continue trying
-						 * to sync the remaining routes. */
-						success = FALSE;
-					}
-				}
-			}
-		}
-	}
-
-	if (vtable->vt->is_ip4 && ipx_routes_changed)
-		g_signal_emit (self, signals[IP4_ROUTES_CHANGED], 0);
-
-	g_free (known_routes_idx);
-	g_free (plat_routes_idx);
-	g_array_unref (plat_routes);
-
-	return success;
-}
-
-/**
- * nm_route_manager_ip4_route_sync:
- * @ifindex: Interface index
- * @known_routes: List of routes
- * @ignore_kernel_routes: if %TRUE, ignore kernel routes.
- * @full_sync: whether to do a full sync and delete routes
- *   that are configured on the interface but not currently
- *   tracked by route-manager.
- *
- * A convenience function to synchronize routes for a specific interface
- * with the least possible disturbance. It simply removes routes that are
- * not listed and adds routes that are.
- * Default routes are ignored (both in @known_routes and those already
- * configured on the device).
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_route_manager_ip4_route_sync (NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync)
-{
-	return _vx_route_sync (&vtable_v4, self, ifindex, known_routes, ignore_kernel_routes, full_sync);
-}
-
-/**
- * nm_route_manager_ip6_route_sync:
- * @ifindex: Interface index
- * @known_routes: List of routes
- * @ignore_kernel_routes: if %TRUE, ignore kernel routes.
- * @full_sync: whether to do a full sync and delete routes
- *   that are configured on the interface but not currently
- *   tracked by route-manager.
- *
- * A convenience function to synchronize routes for a specific interface
- * with the least possible disturbance. It simply removes routes that are
- * not listed and adds routes that are.
- * Default routes are ignored (both in @known_routes and those already
- * configured on the device).
- *
- * Returns: %TRUE on success.
- */
-gboolean
-nm_route_manager_ip6_route_sync (NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync)
-{
-	return _vx_route_sync (&vtable_v6, self, ifindex, known_routes, ignore_kernel_routes, full_sync);
-}
-
-gboolean
-nm_route_manager_route_flush (NMRouteManager *self, int ifindex)
-{
-	bool success = TRUE;
-
-	success &= (bool) nm_route_manager_ip4_route_sync (self, ifindex, NULL, FALSE, TRUE);
-	success &= (bool) nm_route_manager_ip6_route_sync (self, ifindex, NULL, FALSE, TRUE);
-	return success;
-}
-
-/**
- * nm_route_manager_ip4_routes_shadowed:
- * @ifindex: Interface index
- *
- * Returns: %TRUE if some other link has a route to the same destination
- *   with a lower metric.
- */
-gboolean
-nm_route_manager_ip4_routes_shadowed (NMRouteManager *self, int ifindex)
-{
-	NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-	RouteIndex *index = priv->ip4_routes.index;
-	const NMPlatformIP4Route *route;
-	guint i;
-
-	for (i = 1; i < index->len; i++) {
-		route = (const NMPlatformIP4Route *) index->entries[i];
-
-		if (route->ifindex != ifindex)
-			continue;
-		if (_v4_route_dest_cmp (route, (const NMPlatformIP4Route *) index->entries[i - 1]) == 0)
-			return TRUE;
-	}
-
-	return FALSE;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_ip4_device_routes_entry_expired (const IP4DeviceRoutePurgeEntry *entry, gint64 now)
-{
-	return entry->scheduled_at_ns + IP4_DEVICE_ROUTES_WAIT_TIME_NS < now;
-}
-
-static IP4DeviceRoutePurgeEntry *
-_ip4_device_routes_purge_entry_create (NMRouteManager *self, const NMPlatformIP4Route *route, gint64 now_ns)
-{
-	IP4DeviceRoutePurgeEntry *entry;
-
-	entry = g_slice_new (IP4DeviceRoutePurgeEntry);
-
-	entry->self = self;
-	entry->scheduled_at_ns = now_ns;
-	entry->idle_id = 0;
-	entry->obj = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, (NMPlatformObject *) route);
-	return entry;
-}
-
-static void
-_ip4_device_routes_purge_entry_free (IP4DeviceRoutePurgeEntry *entry)
-{
-	nmp_object_unref (entry->obj);
-	nm_clear_g_source (&entry->idle_id);
-	g_slice_free (IP4DeviceRoutePurgeEntry, entry);
-}
-
-static gboolean
-_ip4_device_routes_idle_cb (IP4DeviceRoutePurgeEntry *entry)
-{
-	NMRouteManager *self;
-	NMRouteManagerPrivate *priv;
-
-	nm_clear_g_source (&entry->idle_id);
-
-	self = entry->self;
-	priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (_route_index_find (&vtable_v4, priv->ip4_routes.index, &entry->obj->ipx_route) >= 0) {
-		/* we have an identical route in our list. Don't delete it. */
-		return G_SOURCE_REMOVE;
-	}
-
-	_LOGt (vtable_v4.vt->addr_family, "device-route: delete %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-
-	nm_platform_ip4_route_delete (priv->platform,
-	                              entry->obj->ip4_route.ifindex,
-	                              entry->obj->ip4_route.network,
-	                              entry->obj->ip4_route.plen,
-	                              entry->obj->ip4_route.metric);
-
-	g_hash_table_remove (priv->ip4_device_routes.entries, entry->obj);
-	_ip4_device_routes_cancel (self);
-	return G_SOURCE_REMOVE;
-}
-
-static void
-_ip4_device_routes_ip4_route_changed (NMPlatform *platform,
-                                      int obj_type_i,
-                                      int ifindex,
-                                      const NMPlatformIP4Route *route,
-                                      int change_type_i,
-                                      NMRouteManager *self)
-{
-	const NMPlatformSignalChangeType change_type = change_type_i;
-	NMRouteManagerPrivate *priv;
-	NMPObject obj_needle;
-	IP4DeviceRoutePurgeEntry *entry;
-
-	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
-		return;
-
-	if (   route->rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL
-	    || route->metric != 0) {
-		/* we don't have an automatically created device route at hand. Bail out early. */
-		return;
-	}
-
-	priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	entry = g_hash_table_lookup (priv->ip4_device_routes.entries,
-	                             nmp_object_stackinit (&obj_needle, NMP_OBJECT_TYPE_IP4_ROUTE, (NMPlatformObject *) route));
-	if (!entry)
-		return;
-
-	if (_ip4_device_routes_entry_expired (entry, nm_utils_get_monotonic_timestamp_ns ())) {
-		_LOGt (vtable_v4.vt->addr_family, "device-route: cleanup-ch %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-		g_hash_table_remove (priv->ip4_device_routes.entries, entry->obj);
-		_ip4_device_routes_cancel (self);
-		return;
-	}
-
-	if (entry->idle_id == 0) {
-		_LOGt (vtable_v4.vt->addr_family, "device-route: schedule %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-		entry->idle_id = g_idle_add ((GSourceFunc) _ip4_device_routes_idle_cb, entry);
-	}
-}
-
-static gboolean
-_ip4_device_routes_cancel (NMRouteManager *self)
-{
-	NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	if (priv->ip4_device_routes.gc_id) {
-		if (g_hash_table_size (priv->ip4_device_routes.entries) > 0)
-			return G_SOURCE_CONTINUE;
-		_LOGt (vtable_v4.vt->addr_family, "device-route: cancel");
-		if (priv->platform)
-			g_signal_handlers_disconnect_by_func (priv->platform, G_CALLBACK (_ip4_device_routes_ip4_route_changed), self);
-		nm_clear_g_source (&priv->ip4_device_routes.gc_id);
-	}
-	return G_SOURCE_REMOVE;
-}
-
-static gboolean
-_ip4_device_routes_gc (NMRouteManager *self)
-{
-	NMRouteManagerPrivate *priv;
-	GHashTableIter iter;
-	IP4DeviceRoutePurgeEntry *entry;
-	gint64 now = nm_utils_get_monotonic_timestamp_ns ();
-
-	priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	g_hash_table_iter_init (&iter, priv->ip4_device_routes.entries);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &entry)) {
-		if (_ip4_device_routes_entry_expired (entry, now)) {
-			_LOGt (vtable_v4.vt->addr_family, "device-route: cleanup-gc %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-			g_hash_table_iter_remove (&iter);
-		}
-	}
-
-	return _ip4_device_routes_cancel (self);
-}
-
-/**
- * nm_route_manager_ip4_route_register_device_route_purge_list:
- *
- * When adding an IPv4 address, kernel will automatically add a device route with
- * metric zero. We don't want that route and want to delete it. However, the route
- * by kernel immediately, but some time after. That means during nm_route_manager_ip4_route_sync()
- * such a route doesn't exist yet. We must remember that we expect such a route to appear later
- * and to remove it. */
-void
-nm_route_manager_ip4_route_register_device_route_purge_list (NMRouteManager *self, GArray *device_route_purge_list)
-{
-	NMRouteManagerPrivate *priv;
-	guint i;
-	gint64 now_ns;
-
-	if (!device_route_purge_list || device_route_purge_list->len == 0)
-		return;
-
-	priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	now_ns = nm_utils_get_monotonic_timestamp_ns ();
-	for (i = 0; i < device_route_purge_list->len; i++) {
-		IP4DeviceRoutePurgeEntry *entry;
-
-		entry = _ip4_device_routes_purge_entry_create (self, &g_array_index (device_route_purge_list, NMPlatformIP4Route, i), now_ns);
-		_LOGt (vtable_v4.vt->addr_family, "device-route: watch (%s) %s",
-		                                  g_hash_table_contains (priv->ip4_device_routes.entries, entry->obj)
-		                                      ? "update" : "new",
-		                                  nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-		g_hash_table_replace (priv->ip4_device_routes.entries,
-		                      nmp_object_ref (entry->obj),
-		                      entry);
-	}
-	if (priv->ip4_device_routes.gc_id == 0) {
-		g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_ip4_device_routes_ip4_route_changed), self);
-		priv->ip4_device_routes.gc_id = g_timeout_add (IP4_DEVICE_ROUTES_GC_INTERVAL_SEC, (GSourceFunc) _ip4_device_routes_gc, self);
-	}
-}
-
-/*****************************************************************************/
-
-static const VTableIP vtable_v4 = {
-	.vt                             = &nm_platform_vtable_route_v4,
-	.route_dest_cmp                 = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v4_route_dest_cmp,
-	.route_id_cmp                   = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v4_route_id_cmp,
-};
-
-static const VTableIP vtable_v6 = {
-	.vt                             = &nm_platform_vtable_route_v6,
-	.route_dest_cmp                 = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v6_route_dest_cmp,
-	.route_id_cmp                   = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v6_route_id_cmp,
-};
-
-/*****************************************************************************/
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMRouteManager *self = NM_ROUTE_MANAGER (object);
-	NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	switch (prop_id) {
-	case PROP_LOG_WITH_PTR:
-		/* construct-only */
-		priv->log_with_ptr = g_value_get_boolean (value);
-		break;
-	case PROP_PLATFORM:
-		/* construct-only */
-		priv->platform = g_value_get_object (value) ? : NM_PLATFORM_GET;
-		if (!priv->platform)
-			g_return_if_reached ();
-		g_object_ref (priv->platform);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_route_manager_init (NMRouteManager *self)
-{
-	NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	priv->ip4_routes.entries = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-	priv->ip6_routes.entries = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
-	priv->ip4_routes.effective_metrics = g_array_new (FALSE, FALSE, sizeof (gint64));
-	priv->ip6_routes.effective_metrics = g_array_new (FALSE, FALSE, sizeof (gint64));
-	priv->ip4_routes.effective_metrics_reverse = g_array_new (FALSE, FALSE, sizeof (gint64));
-	priv->ip6_routes.effective_metrics_reverse = g_array_new (FALSE, FALSE, sizeof (gint64));
-	priv->ip4_routes.index = _route_index_create (&vtable_v4, priv->ip4_routes.entries);
-	priv->ip6_routes.index = _route_index_create (&vtable_v6, priv->ip6_routes.entries);
-	priv->ip4_device_routes.entries = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
-	                                                         (GEqualFunc) nmp_object_id_equal,
-	                                                         (GDestroyNotify) nmp_object_unref,
-	                                                         (GDestroyNotify) _ip4_device_routes_purge_entry_free);
-}
-
-NMRouteManager *
-nm_route_manager_new (gboolean log_with_ptr, NMPlatform *platform)
-{
-	return g_object_new (NM_TYPE_ROUTE_MANAGER,
-	                     NM_ROUTE_MANAGER_LOG_WITH_PTR, log_with_ptr,
-	                     NM_ROUTE_MANAGER_PLATFORM, platform,
-	                     NULL);
-}
-
-static void
-dispose (GObject *object)
-{
-	NMRouteManager *self = NM_ROUTE_MANAGER (object);
-	NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	g_hash_table_remove_all (priv->ip4_device_routes.entries);
-	_ip4_device_routes_cancel (self);
-
-	G_OBJECT_CLASS (nm_route_manager_parent_class)->dispose (object);
-}
-
-static void
-finalize (GObject *object)
-{
-	NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE ((NMRouteManager *) object);
-
-	g_array_free (priv->ip4_routes.entries, TRUE);
-	g_array_free (priv->ip6_routes.entries, TRUE);
-	g_array_free (priv->ip4_routes.effective_metrics, TRUE);
-	g_array_free (priv->ip6_routes.effective_metrics, TRUE);
-	g_array_free (priv->ip4_routes.effective_metrics_reverse, TRUE);
-	g_array_free (priv->ip6_routes.effective_metrics_reverse, TRUE);
-	g_free (priv->ip4_routes.index);
-	g_free (priv->ip6_routes.index);
-
-	g_hash_table_unref (priv->ip4_device_routes.entries);
-
-	g_clear_object (&priv->platform);
-
-	G_OBJECT_CLASS (nm_route_manager_parent_class)->finalize (object);
-}
-
-static void
-nm_route_manager_class_init (NMRouteManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->set_property = set_property;
-	object_class->dispose = dispose;
-	object_class->finalize = finalize;
-
-	obj_properties[PROP_LOG_WITH_PTR] =
-	    g_param_spec_boolean (NM_ROUTE_MANAGER_LOG_WITH_PTR, "", "",
-	                          TRUE,
-	                          G_PARAM_WRITABLE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_PLATFORM] =
-	    g_param_spec_object (NM_ROUTE_MANAGER_PLATFORM, "", "",
-	                         NM_TYPE_PLATFORM,
-	                         G_PARAM_WRITABLE |
-	                         G_PARAM_CONSTRUCT_ONLY |
-	                         G_PARAM_STATIC_STRINGS);
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[IP4_ROUTES_CHANGED] =
-	    g_signal_new (NM_ROUTE_MANAGER_IP4_ROUTES_CHANGED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL, NULL,
-	                  G_TYPE_NONE, 0);
-}
diff --git a/src/nm-route-manager.h b/src/nm-route-manager.h
deleted file mode 100644
index bdf79a09..00000000
--- a/src/nm-route-manager.h
+++ /dev/null
@@ -1,49 +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) 2015 Red Hat, Inc.
- */
-
-#ifndef __NM_ROUTE_MANAGER_H__
-#define __NM_ROUTE_MANAGER_H__
-
-#define NM_TYPE_ROUTE_MANAGER            (nm_route_manager_get_type ())
-#define NM_ROUTE_MANAGER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_ROUTE_MANAGER, NMRouteManager))
-#define NM_ROUTE_MANAGER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_ROUTE_MANAGER, NMRouteManagerClass))
-#define NM_IS_ROUTE_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_ROUTE_MANAGER))
-#define NM_IS_ROUTE_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_ROUTE_MANAGER))
-#define NM_ROUTE_MANAGER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_ROUTE_MANAGER, NMRouteManagerClass))
-
-#define NM_ROUTE_MANAGER_LOG_WITH_PTR "log-with-ptr"
-#define NM_ROUTE_MANAGER_PLATFORM     "platform"
-
-#define NM_ROUTE_MANAGER_IP4_ROUTES_CHANGED "ip4-routes-changed"
-
-typedef struct _NMRouteManagerClass NMRouteManagerClass;
-
-GType nm_route_manager_get_type (void);
-
-gboolean nm_route_manager_ip4_route_sync (NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync);
-gboolean nm_route_manager_ip6_route_sync (NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync);
-gboolean nm_route_manager_route_flush (NMRouteManager *self, int ifindex);
-
-gboolean nm_route_manager_ip4_routes_shadowed (NMRouteManager *self, int ifindex);
-void nm_route_manager_ip4_route_register_device_route_purge_list (NMRouteManager *self, GArray *device_route_purge_list);
-
-NMRouteManager *nm_route_manager_new (gboolean log_with_ptr, NMPlatform *platform);
-
-#endif /* __NM_ROUTE_MANAGER_H__ */
diff --git a/src/nm-session-monitor.c b/src/nm-session-monitor.c
index 151deec8..20781bd4 100644
--- a/src/nm-session-monitor.c
+++ b/src/nm-session-monitor.c
@@ -260,9 +260,9 @@ ck_init (NMSessionMonitor *monitor)
 		if ((monitor->ck.monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error))) {
 			monitor->ck.cache = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
 			g_signal_connect (monitor->ck.monitor,
-							  "changed",
-							  G_CALLBACK (ck_changed),
-							  monitor);
+			                  "changed",
+			                  G_CALLBACK (ck_changed),
+			                  monitor);
 		} else {
 			_LOGE ("error monitoring " CKDB_PATH ": %s", error->message);
 			g_clear_error (&error);
diff --git a/src/nm-test-utils-core.h b/src/nm-test-utils-core.h
index 4e8e2f98..58beadcd 100644
--- a/src/nm-test-utils-core.h
+++ b/src/nm-test-utils-core.h
@@ -195,7 +195,7 @@ nmtst_platform_ip6_route_full (const char *network, guint plen, const char *gate
 static inline int
 _nmtst_platform_ip4_routes_equal_sort (gconstpointer a, gconstpointer b, gpointer user_data)
 {
-	return nm_platform_ip4_route_cmp ((const NMPlatformIP4Route *) a, (const NMPlatformIP4Route *) b);
+	return nm_platform_ip4_route_cmp_full ((const NMPlatformIP4Route *) a, (const NMPlatformIP4Route *) b);
 }
 
 static inline void
@@ -215,7 +215,7 @@ nmtst_platform_ip4_routes_equal (const NMPlatformIP4Route *a, const NMPlatformIP
 	}
 
 	for (i = 0; i < len; i++) {
-		if (nm_platform_ip4_route_cmp (&a[i], &b[i]) != 0) {
+		if (nm_platform_ip4_route_cmp_full (&a[i], &b[i]) != 0) {
 			char buf[sizeof (_nm_utils_to_string_buffer)];
 
 			g_error ("Error comparing IPv4 route[%lu]: %s vs %s", (unsigned long) i,
@@ -226,10 +226,29 @@ nmtst_platform_ip4_routes_equal (const NMPlatformIP4Route *a, const NMPlatformIP
 	}
 }
 
+#ifdef __NMP_OBJECT_H__
+
+static inline void
+nmtst_platform_ip4_routes_equal_aptr (const NMPObject *const*a, const NMPlatformIP4Route *b, gsize len, gboolean ignore_order)
+{
+	gsize i;
+	gs_free NMPlatformIP4Route *c_a = NULL;
+
+	g_assert (len > 0);
+	g_assert (a);
+
+	c_a = g_new (NMPlatformIP4Route, len);
+	for (i = 0; i < len; i++)
+		c_a[i] = *NMP_OBJECT_CAST_IP4_ROUTE (a[i]);
+	nmtst_platform_ip4_routes_equal (c_a, b, len, ignore_order);
+}
+
+#endif
+
 static inline int
 _nmtst_platform_ip6_routes_equal_sort (gconstpointer a, gconstpointer b, gpointer user_data)
 {
-	return nm_platform_ip6_route_cmp ((const NMPlatformIP6Route *) a, (const NMPlatformIP6Route *) b);
+	return nm_platform_ip6_route_cmp_full ((const NMPlatformIP6Route *) a, (const NMPlatformIP6Route *) b);
 }
 
 static inline void
@@ -249,7 +268,7 @@ nmtst_platform_ip6_routes_equal (const NMPlatformIP6Route *a, const NMPlatformIP
 	}
 
 	for (i = 0; i < len; i++) {
-		if (nm_platform_ip6_route_cmp (&a[i], &b[i]) != 0) {
+		if (nm_platform_ip6_route_cmp_full (&a[i], &b[i]) != 0) {
 			char buf[sizeof (_nm_utils_to_string_buffer)];
 
 			g_error ("Error comparing IPv6 route[%lu]: %s vs %s", (unsigned long) i,
@@ -260,18 +279,48 @@ nmtst_platform_ip6_routes_equal (const NMPlatformIP6Route *a, const NMPlatformIP
 	}
 }
 
+#ifdef __NMP_OBJECT_H__
+
+static inline void
+nmtst_platform_ip6_routes_equal_aptr (const NMPObject *const*a, const NMPlatformIP6Route *b, gsize len, gboolean ignore_order)
+{
+	gsize i;
+	gs_free NMPlatformIP6Route *c_a = NULL;
+
+	g_assert (len > 0);
+	g_assert (a);
+
+	c_a = g_new (NMPlatformIP6Route, len);
+	for (i = 0; i < len; i++)
+		c_a[i] = *NMP_OBJECT_CAST_IP6_ROUTE (a[i]);
+	nmtst_platform_ip6_routes_equal (c_a, b, len, ignore_order);
+}
+
+#endif
+
 #endif
 
 
 #ifdef __NETWORKMANAGER_IP4_CONFIG_H__
 
+#include "nm-utils/nm-dedup-multi.h"
+
+static inline NMIP4Config *
+nmtst_ip4_config_new (int ifindex)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+
+	return nm_ip4_config_new (multi_idx, ifindex);
+}
+
 static inline NMIP4Config *
 nmtst_ip4_config_clone (NMIP4Config *config)
 {
-	NMIP4Config *copy = nm_ip4_config_new (-1);
+	NMIP4Config *copy;
 
-	g_assert (copy);
 	g_assert (config);
+	copy = nm_ip4_config_new (nm_ip4_config_get_multi_idx (config), -1);
+	g_assert (copy);
 	nm_ip4_config_replace (copy, config, NULL);
 	return copy;
 }
@@ -281,13 +330,24 @@ nmtst_ip4_config_clone (NMIP4Config *config)
 
 #ifdef __NETWORKMANAGER_IP6_CONFIG_H__
 
+#include "nm-utils/nm-dedup-multi.h"
+
+static inline NMIP6Config *
+nmtst_ip6_config_new (int ifindex)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+
+	return nm_ip6_config_new (multi_idx, ifindex);
+}
+
 static inline NMIP6Config *
 nmtst_ip6_config_clone (NMIP6Config *config)
 {
-	NMIP6Config *copy = nm_ip6_config_new (-1);
+	NMIP6Config *copy;
 
-	g_assert (copy);
 	g_assert (config);
+	copy = nm_ip6_config_new (nm_ip6_config_get_multi_idx (config), -1);
+	g_assert (copy);
 	nm_ip6_config_replace (copy, config, NULL);
 	return copy;
 }
diff --git a/src/nm-types.h b/src/nm-types.h
index 44b4fecb..e01c48b0 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -38,7 +38,6 @@ typedef struct _NMConfigData         NMConfigData;
 typedef struct _NMArpingManager      NMArpingManager;
 typedef struct _NMConnectionProvider NMConnectionProvider;
 typedef struct _NMConnectivity       NMConnectivity;
-typedef struct _NMDefaultRouteManager NMDefaultRouteManager;
 typedef struct _NMDevice             NMDevice;
 typedef struct _NMDhcp4Config        NMDhcp4Config;
 typedef struct _NMDhcp6Config        NMDhcp6Config;
@@ -50,12 +49,13 @@ typedef struct _NMNetns              NMNetns;
 typedef struct _NMPolicy             NMPolicy;
 typedef struct _NMRfkillManager      NMRfkillManager;
 typedef struct _NMPacrunnerManager   NMPacrunnerManager;
-typedef struct _NMRouteManager       NMRouteManager;
 typedef struct _NMSessionMonitor     NMSessionMonitor;
 typedef struct _NMSleepMonitor       NMSleepMonitor;
 typedef struct _NMLldpListener       NMLldpListener;
 typedef struct _NMConfigDeviceStateData NMConfigDeviceStateData;
 
+struct _NMDedupMultiIndex;
+
 /*****************************************************************************/
 
 typedef enum {
@@ -108,6 +108,7 @@ NM_IS_IP_CONFIG_SOURCE_RTPROT (NMIPConfigSource source)
 
 /* platform */
 typedef struct _NMPlatform           NMPlatform;
+typedef struct _NMPlatformObject     NMPlatformObject;
 typedef struct _NMPlatformIP4Address NMPlatformIP4Address;
 typedef struct _NMPlatformIP4Route   NMPlatformIP4Route;
 typedef struct _NMPlatformIP6Address NMPlatformIP6Address;
@@ -139,7 +140,8 @@ typedef enum {
 	NM_LINK_TYPE_WIMAX,
 
 	/* Software types */
-	NM_LINK_TYPE_DUMMY = 0x10000,
+	NM_LINK_TYPE_BNEP = 0x10000,   /* Bluetooth Ethernet emulation */
+	NM_LINK_TYPE_DUMMY,
 	NM_LINK_TYPE_GRE,
 	NM_LINK_TYPE_GRETAP,
 	NM_LINK_TYPE_IFB,
@@ -150,13 +152,13 @@ typedef enum {
 	NM_LINK_TYPE_MACVLAN,
 	NM_LINK_TYPE_MACVTAP,
 	NM_LINK_TYPE_OPENVSWITCH,
+	NM_LINK_TYPE_PPP,
 	NM_LINK_TYPE_SIT,
 	NM_LINK_TYPE_TAP,
 	NM_LINK_TYPE_TUN,
 	NM_LINK_TYPE_VETH,
 	NM_LINK_TYPE_VLAN,
 	NM_LINK_TYPE_VXLAN,
-	NM_LINK_TYPE_BNEP,   /* Bluetooth Ethernet emulation */
 
 	/* Software types with slaves */
 	NM_LINK_TYPE_BRIDGE = 0x10000 | 0x20000,
@@ -192,9 +194,27 @@ typedef enum {
 typedef enum {
 	NM_IP_CONFIG_MERGE_DEFAULT                  = 0,
 	NM_IP_CONFIG_MERGE_NO_ROUTES                = (1LL << 0),
-	NM_IP_CONFIG_MERGE_NO_DNS                   = (1LL << 1),
+	NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES        = (1LL << 1),
+	NM_IP_CONFIG_MERGE_NO_DNS                   = (1LL << 2),
 } NMIPConfigMergeFlags;
 
+
+/**
+ * NMIPRouteTableSyncMode:
+ * @NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN: only the main table is synced. For all
+ *   other tables, NM won't delete any extra routes.
+ * @NM_IP_ROUTE_TABLE_SYNC_MODE_FULL: NM will sync all tables, except the
+ *   local table (255).
+ * @NM_IP_ROUTE_TABLE_SYNC_MODE_ALL: NM will sync all tables, including the
+ *   local table (255).
+ */
+typedef enum {
+	NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN        = 1,
+	NM_IP_ROUTE_TABLE_SYNC_MODE_FULL        = 2,
+	NM_IP_ROUTE_TABLE_SYNC_MODE_ALL         = 3,
+} NMIPRouteTableSyncMode;
+
+
 /* settings */
 typedef struct _NMAgentManager       NMAgentManager;
 typedef struct _NMSecretAgent        NMSecretAgent;
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 38706f37..c199c5ed 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -32,6 +32,7 @@
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
+#include "nm-platform-private.h"
 #include "nmp-object.h"
 
 #include "nm-test-utils-core.h"
@@ -39,20 +40,14 @@
 /*****************************************************************************/
 
 typedef struct {
-	NMPlatformLink link;
-
+	const NMPObject *obj;
 	char *udi;
-	NMPObject *lnk;
 	struct in6_addr ip6_lladdr;
 } NMFakePlatformLink;
 
 typedef struct {
 	GHashTable *options;
 	GArray *links;
-	GArray *ip4_addresses;
-	GArray *ip6_addresses;
-	GArray *ip4_routes;
-	GArray *ip6_routes;
 } NMFakePlatformPrivate;
 
 struct _NMFakePlatform {
@@ -96,7 +91,22 @@ G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM)
 
 /*****************************************************************************/
 
-static void link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal);
+static void link_changed (NMPlatform *platform,
+                          NMFakePlatformLink *device,
+                          NMPCacheOpsType cache_op,
+                          const NMPObject *obj_old);
+
+static gboolean ipx_address_delete (NMPlatform *platform,
+                                    int addr_family,
+                                    int ifindex,
+                                    gconstpointer addr,
+                                    const guint8 *plen,
+                                    gconstpointer peer_addr);
+
+static gboolean ipx_route_delete (NMPlatform *platform,
+                                  int addr_family,
+                                  int ifindex,
+                                  const NMPObject *obj);
 
 static gboolean ip6_address_add (NMPlatform *platform,
                                  int ifindex,
@@ -110,14 +120,6 @@ static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in
 
 /*****************************************************************************/
 
-static gboolean
-_ip4_address_equal_peer_net (in_addr_t peer1, in_addr_t peer2, guint8 plen)
-{
-	return ((peer1 ^ peer2) & nm_utils_ip4_prefix_to_netmask (plen)) == 0;
-}
-
-/*****************************************************************************/
-
 #define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \
 	G_STMT_START { \
 		const char *const _pathid = (pathid); \
@@ -158,288 +160,338 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	return g_strdup (g_hash_table_lookup (priv->options, path));
 }
 
-static const char *
-type_to_type_name (NMLinkType type)
-{
-	switch (type) {
-	case NM_LINK_TYPE_UNKNOWN:
-		return "unknown";
-	case NM_LINK_TYPE_LOOPBACK:
-		return "loopback";
-	case NM_LINK_TYPE_ETHERNET:
-		return "ethernet";
-	case NM_LINK_TYPE_DUMMY:
-		return "dummy";
-	case NM_LINK_TYPE_BRIDGE:
-		return "bridge";
-	case NM_LINK_TYPE_BOND:
-		return "bond";
-	case NM_LINK_TYPE_TEAM:
-		return "team";
-	case NM_LINK_TYPE_VLAN:
-		return "vlan";
-	case NM_LINK_TYPE_NONE:
-	default:
-		return NULL;
-	}
-}
-
-static void
-link_init (NMFakePlatformLink *device, int ifindex, int type, const char *name)
-{
-	gs_free char *ip6_lladdr = NULL;
-
-	g_assert (!name || strlen (name) < sizeof(device->link.name));
-
-	memset (device, 0, sizeof (*device));
-
-	ip6_lladdr = ifindex > 0 ? g_strdup_printf ("fe80::fa1e:%0x:%0x", ifindex / 256, ifindex % 256) : NULL;
-
-	device->link.ifindex = name ? ifindex : 0;
-	device->link.type = type;
-	device->link.kind = type_to_type_name (type);
-	device->link.driver = type_to_type_name (type);
-	device->udi = g_strdup_printf ("fake:%d", ifindex);
-	device->link.initialized = TRUE;
-	device->ip6_lladdr = *nmtst_inet6_from_string (ip6_lladdr);
-	if (name)
-		strcpy (device->link.name, name);
-	switch (device->link.type) {
-	case NM_LINK_TYPE_DUMMY:
-		device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP);
-		break;
-	default:
-		device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP);
-		break;
-	}
-}
-
 static NMFakePlatformLink *
 link_get (NMPlatform *platform, int ifindex)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMFakePlatformLink *device;
+	int idx;
+
+	if (ifindex <= 0)
+		g_return_val_if_reached (NULL);
 
-	if (ifindex >= priv->links->len)
+	idx = ifindex - 1;
+	if (idx >= priv->links->len)
 		goto not_found;
-	device = &g_array_index (priv->links, NMFakePlatformLink, ifindex);
-	if (!device->link.ifindex)
+
+	device = &g_array_index (priv->links, NMFakePlatformLink, idx);
+	if (!device->obj)
 		goto not_found;
 
+	g_assert (ifindex == NMP_OBJECT_CAST_LINK (device->obj)->ifindex);
+	g_assert (device->obj == nm_platform_link_get_obj (platform, ifindex, FALSE));
+
 	return device;
 not_found:
 	_LOGD ("link not found: %d", ifindex);
 	return NULL;
 }
 
-static GArray *
-link_get_all (NMPlatform *platform)
+static void
+link_add_prepare (NMPlatform *platform,
+                  NMFakePlatformLink *device,
+                  NMPObject *obj_tmp)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *links = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), priv->links->len);
-	int i;
+	gboolean connected;
 
-	for (i = 0; i < priv->links->len; i++)
-		if (g_array_index (priv->links, NMFakePlatformLink, i).link.ifindex)
-			g_array_append_val (links, g_array_index (priv->links, NMFakePlatformLink, i).link);
+	/* we must clear the driver, because platform cache want's to set it */
+	g_assert (obj_tmp->link.driver == g_intern_string (obj_tmp->link.driver));
+	obj_tmp->link.driver = NULL;
 
-	return links;
-}
-
-static const NMPlatformLink *
-_nm_platform_link_get (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
+	if (NM_IN_SET (obj_tmp->link.type, NM_LINK_TYPE_BRIDGE,
+	                                   NM_LINK_TYPE_BOND)) {
+		connected = FALSE;
+		if (NM_FLAGS_HAS (obj_tmp->link.n_ifi_flags, IFF_UP)) {
+			NMPLookup lookup;
+			NMDedupMultiIter iter;
+			const NMPObject *slave_candidate = NULL;
+
+			nmp_cache_iter_for_each (&iter,
+			                         nmp_cache_lookup (nm_platform_get_cache (platform),
+			                                           nmp_lookup_init_obj_type (&lookup,
+			                                                                     NMP_OBJECT_TYPE_LINK)),
+			                         &slave_candidate) {
+				if (nmp_cache_link_connected_for_slave (obj_tmp->link.ifindex, slave_candidate)) {
+					connected = TRUE;
+					break;
+				}
+			}
+		}
+	} else
+		connected = NM_FLAGS_HAS (obj_tmp->link.n_ifi_flags, IFF_UP);
 
-	return device ? &device->link : NULL;
+	obj_tmp->link.n_ifi_flags = NM_FLAGS_ASSIGN (obj_tmp->link.n_ifi_flags, IFF_LOWER_UP, connected);
+	obj_tmp->link.connected = connected;
 }
 
-static const NMPlatformLink *
-_nm_platform_link_get_by_ifname (NMPlatform *platform, const char *ifname)
+static NMFakePlatformLink *
+link_add_pre (NMPlatform *platform,
+              const char *name,
+              NMLinkType type,
+              const void *address,
+              size_t address_len)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	guint i;
+	NMFakePlatformLink *device;
+	int ifindex;
+	NMPObject *o;
+	NMPlatformLink *link;
+	gs_free char *ip6_lladdr = NULL;
 
-	for (i = 0; i < priv->links->len; i++) {
-		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+	g_assert (!name || strlen (name) < IFNAMSIZ);
 
-		if (!strcmp (device->link.name, ifname))
-			return &device->link;
-	}
-	return NULL;
-}
+	g_array_set_size (priv->links, priv->links->len + 1);
+	device = &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1);
+	ifindex = priv->links->len;
 
-static const NMPlatformLink *
-_nm_platform_link_get_by_address (NMPlatform *platform,
-                                  gconstpointer address,
-                                  size_t length)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	guint i;
+	memset (device, 0, sizeof (*device));
 
-	if (   length == 0
-	    || length > NM_UTILS_HWADDR_LEN_MAX
-	    || !address)
-		g_return_val_if_reached (NULL);
+	o = nmp_object_new_link (ifindex);
+	link = NMP_OBJECT_CAST_LINK (o);
 
-	for (i = 0; i < priv->links->len; i++) {
-		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
+	ip6_lladdr = ifindex > 0 ? g_strdup_printf ("fe80::fa1e:%0x:%0x", ifindex / 256, ifindex % 256) : NULL;
 
-		if (   device->link.addr.len == length
-		    && memcmp (device->link.addr.data, address, length) == 0) {
-			return &device->link;
-		}
+	link->ifindex = name ? ifindex : 0;
+	link->type = type;
+	link->kind = g_intern_string (nm_link_type_to_string (type));
+	link->initialized = TRUE;
+	if (name)
+		strcpy (link->name, name);
+	switch (link->type) {
+	case NM_LINK_TYPE_DUMMY:
+		link->n_ifi_flags = NM_FLAGS_SET (link->n_ifi_flags, IFF_NOARP);
+		break;
+	default:
+		link->n_ifi_flags = NM_FLAGS_UNSET (link->n_ifi_flags, IFF_NOARP);
+		break;
 	}
-	return NULL;
-}
 
-static const NMPObject *
-link_get_lnk (NMPlatform *platform,
-              int ifindex,
-              NMLinkType link_type,
-              const NMPlatformLink **out_link)
-{
-	NMFakePlatformLink *device = link_get (platform, ifindex);
+	o->_link.netlink.is_in_netlink = TRUE;
 
-	if (!device)
-		return NULL;
-
-	NM_SET_OUT (out_link, &device->link);
-
-	if (!device->lnk)
-		return NULL;
-
-	if (link_type == NM_LINK_TYPE_NONE)
-		return device->lnk;
+	if (address) {
+		g_assert (address_len > 0 && address_len <= sizeof (link->addr.data));
+		memcpy (link->addr.data, address, address_len);
+		link->addr.len = address_len;
+	} else
+		g_assert (address_len == 0);
 
-	if (   link_type != device->link.type
-	    || link_type != NMP_OBJECT_GET_CLASS (device->lnk)->lnk_link_type)
-		return NULL;
+	device->obj = o;
+	device->udi = g_strdup_printf ("fake:%d", ifindex);
+	device->ip6_lladdr = *nmtst_inet6_from_string (ip6_lladdr);
 
-	return device->lnk;
+	return device;
 }
 
 static gboolean
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
+          const char *veth_peer,
           const void *address,
           size_t address_len,
           const NMPlatformLink **out_link)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	NMFakePlatformLink device;
-	NMFakePlatformLink *new_device;
+	NMFakePlatformLink *device;
+	NMFakePlatformLink *device_veth = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old_veth = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new_veth = NULL;
+	NMPCacheOpsType cache_op;
+	NMPCacheOpsType cache_op_veth = NMP_CACHE_OPS_UNCHANGED;
+
+	device = link_add_pre (platform, name, type, address, address_len);
+
+	if (veth_peer) {
+		g_assert (type == NM_LINK_TYPE_VETH);
+		device_veth = link_add_pre (platform, veth_peer, type, NULL, 0);
+	} else
+		g_assert (type != NM_LINK_TYPE_VETH);
+
+	link_add_prepare (platform, device, (NMPObject *) device->obj);
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     (NMPObject *) device->obj,
+	                                     FALSE,
+	                                     &obj_old, &obj_new);
+	g_assert (cache_op == NMP_CACHE_OPS_ADDED);
+	nmp_object_unref (device->obj);
+	device->obj = nmp_object_ref (obj_new);
+	if (veth_peer) {
+		link_add_prepare (platform, device_veth, (NMPObject *) device_veth->obj);
+		cache_op_veth = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+		                                          (NMPObject *) device_veth->obj,
+		                                          FALSE,
+		                                          &obj_old_veth, &obj_new_veth);
+		g_assert (cache_op == NMP_CACHE_OPS_ADDED);
+		nmp_object_unref (device->obj);
+		device->obj = nmp_object_ref (obj_new);
+	}
 
-	link_init (&device, priv->links->len, type, name);
+	if (out_link)
+		*out_link = NMP_OBJECT_CAST_LINK (device->obj);
 
-	if (address) {
-		g_return_val_if_fail (address_len > 0 && address_len <= sizeof (device.link.addr.data), FALSE);
-		memcpy (device.link.addr.data, address, address_len);
-		device.link.addr.len = address_len;
-	}
+	link_changed (platform, device, cache_op, NULL);
+	if (veth_peer)
+		link_changed (platform, device_veth, cache_op_veth, NULL);
+
+	return TRUE;
+}
+
+static NMFakePlatformLink *
+link_add_one (NMPlatform *platform,
+              const char *name,
+              NMLinkType link_type,
+              void (*prepare_fcn) (NMPlatform *platform, NMFakePlatformLink *device, gconstpointer user_data),
+              gconstpointer user_data,
+              const NMPlatformLink **out_link)
+{
+	NMFakePlatformLink *device;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	NMPCacheOpsType cache_op;
+	int ifindex;
 
-	g_array_append_val (priv->links, device);
-	new_device = &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1);
+	device = link_add_pre (platform, name, NM_LINK_TYPE_VLAN, NULL, 0);
 
-	if (device.link.ifindex) {
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, device.link.ifindex, &device, (int) NM_PLATFORM_SIGNAL_ADDED);
+	ifindex = NMP_OBJECT_CAST_LINK (device->obj)->ifindex;
 
-		link_changed (platform, &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1), FALSE);
-	}
+	if (prepare_fcn)
+		prepare_fcn (platform, device, user_data);
 
-	if (out_link)
-		*out_link = &new_device->link;
-	return TRUE;
+	link_add_prepare (platform, device, (NMPObject *) device->obj);
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     (NMPObject *) device->obj,
+	                                     FALSE,
+	                                     &obj_old, &obj_new);
+	g_assert (cache_op == NMP_CACHE_OPS_ADDED);
+	nmp_object_unref (device->obj);
+	device->obj = nmp_object_ref (obj_new);
+
+	link_changed (platform, device, cache_op, obj_old);
+
+	device = link_get (platform, ifindex);
+	if (!device)
+		g_assert_not_reached ();
+
+	NM_SET_OUT (out_link, NMP_OBJECT_CAST_LINK (device->obj));
+	return device;
 }
 
 static gboolean
 link_delete (NMPlatform *platform, int ifindex)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMFakePlatformLink *device = link_get (platform, ifindex);
-	NMPlatformLink deleted_device;
-	int i;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old2 = NULL;
+	NMPCacheOpsType cache_op;
 
-	if (!device || !device->link.ifindex)
+	if (!device)
 		return FALSE;
 
-	memcpy (&deleted_device, &device->link, sizeof (deleted_device));
-	memset (&device->link, 0, sizeof (device->link));
-	g_clear_pointer (&device->lnk, nmp_object_unref);
+	obj_old = g_steal_pointer (&device->obj);
 	g_clear_pointer (&device->udi, g_free);
 
+	cache_op = nmp_cache_remove (nm_platform_get_cache (platform),
+	                             obj_old,
+	                             FALSE,
+	                             FALSE,
+	                             &obj_old2);
+	g_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+	g_assert (obj_old2);
+	g_assert (obj_old == obj_old2);
+
 	/* Remove addresses and routes which belong to the deleted interface */
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
+	ipx_address_delete (platform, AF_INET, ifindex, NULL, NULL, NULL);
+	ipx_address_delete (platform, AF_INET6, ifindex, NULL, NULL, NULL);
+	ipx_route_delete (platform, AF_INET, ifindex, NULL);
+	ipx_route_delete (platform, AF_INET6, ifindex, NULL);
+
+	nm_platform_cache_update_emit_signal (platform,
+	                                      cache_op,
+	                                      obj_old2,
+	                                      NULL);
+	return TRUE;
+}
 
-		if (address->ifindex == ifindex)
-			memset (address, 0, sizeof (*address));
-	}
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
+static void
+link_set_obj (NMPlatform *platform,
+              NMFakePlatformLink *device,
+              NMPObject *obj_tmp)
+{
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj NMPObject *obj_tmp_tmp = NULL;
+	NMPCacheOpsType cache_op;
 
-		if (address->ifindex == ifindex)
-			memset (address, 0, sizeof (*address));
-	}
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
+	g_assert (device);
+	g_assert (NMP_OBJECT_GET_TYPE (device->obj) == NMP_OBJECT_TYPE_LINK);
 
-		if (route->ifindex == ifindex)
-			memset (route, 0, sizeof (*route));
+	if (!obj_tmp) {
+		obj_tmp_tmp = nmp_object_clone (device->obj, FALSE);
+		obj_tmp = obj_tmp_tmp;
 	}
-	for (i = 0; i < priv->ip6_routes->len; i++) {
-		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
 
-		if (route->ifindex == ifindex)
-			memset (route, 0, sizeof (*route));
-	}
+	g_assert (NMP_OBJECT_GET_TYPE (obj_tmp) == NMP_OBJECT_TYPE_LINK);
 
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, ifindex, &deleted_device, (int) NM_PLATFORM_SIGNAL_REMOVED);
+	link_add_prepare (platform, device, obj_tmp);
+	cache_op = nmp_cache_update_netlink (nm_platform_get_cache (platform),
+	                                     obj_tmp,
+	                                     FALSE,
+	                                     &obj_old, &obj_new);
+	g_assert (NM_IN_SET (cache_op, NMP_CACHE_OPS_UNCHANGED,
+	                               NMP_CACHE_OPS_UPDATED));
+	g_assert (obj_old == device->obj);
+	g_assert (obj_new);
 
-	return TRUE;
-}
+	nmp_object_unref (device->obj);
+	device->obj = nmp_object_ref (obj_new);
 
-static const char *
-link_get_type_name (NMPlatform *platform, int ifindex)
-{
-	return type_to_type_name (nm_platform_link_get_type (platform, ifindex));
+	link_changed (platform, device, cache_op, obj_old);
 }
 
 static void
-link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal)
+link_set_flags (NMPlatform *platform,
+                NMFakePlatformLink *device,
+                guint n_ifi_flags)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-	if (raise_signal)
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, (int) NMP_OBJECT_TYPE_LINK, device->link.ifindex, &device->link, (int) NM_PLATFORM_SIGNAL_CHANGED);
+	g_assert (device);
+	g_assert (NMP_OBJECT_GET_TYPE (device->obj) == NMP_OBJECT_TYPE_LINK);
 
-	if (device->link.ifindex && !IN6_IS_ADDR_UNSPECIFIED (&device->ip6_lladdr)) {
-		if (device->link.connected)
-			ip6_address_add (platform, device->link.ifindex, in6addr_any, 64, device->ip6_lladdr, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0);
-		else
-			ip6_address_delete (platform, device->link.ifindex, device->ip6_lladdr, 64);
-	}
+	obj_tmp = nmp_object_clone (device->obj, FALSE);
+	obj_tmp->link.n_ifi_flags = n_ifi_flags;
+	link_set_obj (platform, device, obj_tmp);
+}
 
-	if (device->link.master) {
-		gboolean connected = FALSE;
+static void
+link_changed (NMPlatform *platform,
+              NMFakePlatformLink *device,
+              NMPCacheOpsType cache_op,
+              const NMPObject *obj_old)
+{
+	g_assert (device->obj);
 
-		NMFakePlatformLink *master = link_get (platform, device->link.master);
+	g_assert (!nmp_cache_link_connected_needs_toggle (nm_platform_get_cache (platform),
+	                                                  device->obj, NULL, NULL));
 
-		g_return_if_fail (master && master != device);
+	nm_platform_cache_update_emit_signal (platform,
+	                                      cache_op,
+	                                      obj_old,
+	                                      device->obj);
 
-		for (i = 0; i < priv->links->len; i++) {
-			NMFakePlatformLink *slave = &g_array_index (priv->links, NMFakePlatformLink, i);
+	if (!IN6_IS_ADDR_UNSPECIFIED (&device->ip6_lladdr)) {
+		if (device->obj->link.connected)
+			ip6_address_add (platform, device->obj->link.ifindex, in6addr_any, 64, device->ip6_lladdr, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0);
+		else
+			ip6_address_delete (platform, device->obj->link.ifindex, device->ip6_lladdr, 64);
+	}
 
-			if (slave && slave->link.master == master->link.ifindex && slave->link.connected)
-				connected = TRUE;
-		}
+	if (device->obj->link.master) {
+		NMFakePlatformLink *master;
 
-		if (master->link.connected != connected) {
-			master->link.connected = connected;
-			link_changed (platform, master, TRUE);
-		}
+		master = link_get (platform, device->obj->link.master);
+		link_set_obj (platform, master, NULL);
 	}
 }
 
@@ -447,7 +499,6 @@ static gboolean
 link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
-	gboolean up, connected;
 
 	if (out_no_firmware)
 		*out_no_firmware = FALSE;
@@ -457,29 +508,9 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 		return FALSE;
 	}
 
-	up = TRUE;
-	connected = TRUE;
-	switch (device->link.type) {
-	case NM_LINK_TYPE_DUMMY:
-	case NM_LINK_TYPE_VLAN:
-		break;
-	case NM_LINK_TYPE_BRIDGE:
-	case NM_LINK_TYPE_BOND:
-	case NM_LINK_TYPE_TEAM:
-		connected = FALSE;
-		break;
-	default:
-		connected = FALSE;
-		g_error ("Unexpected device type: %d", device->link.type);
-	}
-
-	if (   NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) != !!up
-	    || device->link.connected != connected) {
-		device->link.n_ifi_flags = NM_FLAGS_ASSIGN (device->link.n_ifi_flags, IFF_UP, up);
-		device->link.connected = connected;
-		link_changed (platform, device, TRUE);
-	}
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_ASSIGN (device->obj->link.n_ifi_flags, IFF_UP, TRUE));
 	return TRUE;
 }
 
@@ -493,13 +524,9 @@ link_set_down (NMPlatform *platform, int ifindex)
 		return FALSE;
 	}
 
-	if (NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) || device->link.connected) {
-		device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_UP);
-		device->link.connected = FALSE;
-
-		link_changed (platform, device, TRUE);
-	}
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_UNSET (device->obj->link.n_ifi_flags, IFF_UP));
 	return TRUE;
 }
 
@@ -513,10 +540,9 @@ link_set_arp (NMPlatform *platform, int ifindex)
 		return FALSE;
 	}
 
-	device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP);
-
-	link_changed (platform, device, TRUE);
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_UNSET (device->obj->link.n_ifi_flags, IFF_NOARP));
 	return TRUE;
 }
 
@@ -530,10 +556,9 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 		return FALSE;
 	}
 
-	device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP);
-
-	link_changed (platform, device, TRUE);
-
+	link_set_flags (platform,
+	                device,
+	                NM_FLAGS_SET (device->obj->link.n_ifi_flags, IFF_NOARP));
 	return TRUE;
 }
 
@@ -541,36 +566,40 @@ static NMPlatformError
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-	if (   !device
-	    || len == 0
+	if (   len == 0
 	    || len > NM_UTILS_HWADDR_LEN_MAX
 	    || !addr)
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
-	if (   device->link.addr.len != len
-	    || (   len > 0
-	        && memcmp (device->link.addr.data, addr, len) != 0)) {
-		memcpy (device->link.addr.data, addr, len);
-		device->link.addr.len = len;
-		link_changed (platform, link_get (platform, ifindex), TRUE);
-	}
+	if (!device)
+		return NM_PLATFORM_ERROR_EXISTS;
 
+	obj_tmp = nmp_object_clone (device->obj, FALSE);
+	obj_tmp->link.addr.len = len;
+	memset (obj_tmp->link.addr.data, 0, sizeof (obj_tmp->link.addr.data));
+	memcpy (obj_tmp->link.addr.data, addr, len);
+
+	link_set_obj (platform, device, obj_tmp);
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
-static gboolean
+static NMPlatformError
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	NMFakePlatformLink *device = link_get (platform, ifindex);
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-	if (device) {
-		device->link.mtu = mtu;
-		link_changed (platform, device, TRUE);
-	} else
+	if (!device) {
 		_LOGE ("failure changing link: netlink error (No such device)");
+		return NM_PLATFORM_ERROR_EXISTS;
+	}
 
-	return !!device;
+	obj_tmp = nmp_object_clone (device->obj, FALSE);
+	obj_tmp->link.mtu = mtu;
+	link_set_obj (platform, device, obj_tmp);
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -614,7 +643,7 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	switch (device->link.type) {
+	switch (device->obj->link.type) {
 	case NM_LINK_TYPE_DUMMY:
 		return FALSE;
 	default:
@@ -630,7 +659,7 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	switch (device->link.type) {
+	switch (device->obj->link.type) {
 	case NM_LINK_TYPE_LOOPBACK:
 		return FALSE;
 	default:
@@ -646,7 +675,7 @@ link_supports_sriov (NMPlatform *platform, int ifindex)
 	if (!device)
 		return FALSE;
 
-	switch (device->link.type) {
+	switch (device->obj->link.type) {
 	case NM_LINK_TYPE_LOOPBACK:
 		return FALSE;
 	default:
@@ -663,15 +692,14 @@ link_enslave (NMPlatform *platform, int master, int slave)
 	g_return_val_if_fail (device, FALSE);
 	g_return_val_if_fail (master_device, FALSE);
 
-	if (device->link.master != master) {
-		device->link.master = master;
-
-		if (NM_IN_SET (master_device->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) {
-			device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_UP);
-			device->link.connected = TRUE;
-		}
+	if (device->obj->link.master != master) {
+		nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
-		link_changed (platform, device, TRUE);
+		obj_tmp = nmp_object_clone (device->obj, FALSE);
+		obj_tmp->link.master = master;
+		if (NM_IN_SET (master_device->obj->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM))
+			obj_tmp->link.n_ifi_flags = NM_FLAGS_SET (device->obj->link.n_ifi_flags, IFF_UP);
+		link_set_obj (platform, device, obj_tmp);
 	}
 
 	return TRUE;
@@ -682,40 +710,56 @@ link_release (NMPlatform *platform, int master_idx, int slave_idx)
 {
 	NMFakePlatformLink *master = link_get (platform, master_idx);
 	NMFakePlatformLink *slave = link_get (platform, slave_idx);
+	nm_auto_nmpobj NMPObject *obj_tmp = NULL;
 
 	g_return_val_if_fail (master, FALSE);
 	g_return_val_if_fail (slave, FALSE);
 
-	if (slave->link.master != master->link.ifindex)
+	if (slave->obj->link.master != master->obj->link.ifindex)
 		return FALSE;
 
-	slave->link.master = 0;
-
-	link_changed (platform, slave, TRUE);
-	link_changed (platform, master, TRUE);
-
+	obj_tmp = nmp_object_clone (slave->obj, FALSE);
+	obj_tmp->link.master = 0;
+	link_set_obj (platform, slave, obj_tmp);
 	return TRUE;
 }
 
-static gboolean
-vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
+struct vlan_add_data {
+	guint32 vlan_flags;
+	int parent;
+	int vlan_id;
+};
+
+static void
+_vlan_add_prepare (NMPlatform *platform,
+                   NMFakePlatformLink *device,
+                   gconstpointer user_data)
 {
-	NMFakePlatformLink *device;
+	const struct vlan_add_data *d = user_data;
+	NMPObject *obj_tmp;
+	NMPObject *lnk;
 
-	if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0, out_link))
-		return FALSE;
+	obj_tmp = (NMPObject *) device->obj;
 
-	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
+	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
+	lnk->lnk_vlan.id = d->vlan_id;
+	lnk->lnk_vlan.flags = d->vlan_flags;
 
-	g_return_val_if_fail (device, FALSE);
-	g_return_val_if_fail (!device->lnk, FALSE);
+	obj_tmp->link.parent = d->parent;
+	obj_tmp->_link.netlink.lnk = lnk;
+}
 
-	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
-	device->lnk->lnk_vlan.id = vlan_id;
-	device->link.parent = parent;
+static gboolean
+vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link)
+{
+	const struct vlan_add_data d = {
+		.parent = parent,
+		.vlan_id = vlan_id,
+		.vlan_flags = vlan_flags,
+	};
 
-	if (out_link)
-		*out_link = &device->link;
+	link_add_one (platform, name, NM_LINK_TYPE_VLAN,
+	              _vlan_add_prepare, &d, out_link);
 	return TRUE;
 }
 
@@ -734,53 +778,76 @@ link_vlan_change (NMPlatform *platform,
 	return FALSE;
 }
 
+static void
+_vxlan_add_prepare (NMPlatform *platform,
+                    NMFakePlatformLink *device,
+                    gconstpointer user_data)
+{
+	const NMPlatformLnkVxlan *props = user_data;
+	NMPObject *obj_tmp;
+	NMPObject *lnk;
+
+	obj_tmp = (NMPObject *) device->obj;
+
+	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
+	lnk->lnk_vxlan = *props;
+
+	obj_tmp->link.parent = props->parent_ifindex;
+	obj_tmp->_link.netlink.lnk = lnk;
+}
+
 static gboolean
 link_vxlan_add (NMPlatform *platform,
                 const char *name,
                 const NMPlatformLnkVxlan *props,
                 const NMPlatformLink **out_link)
 {
-	NMFakePlatformLink *device;
+	link_add_one (platform, name, NM_LINK_TYPE_VXLAN,
+	              _vxlan_add_prepare, props, out_link);
+	return TRUE;
+}
 
-	if (!link_add (platform, name, NM_LINK_TYPE_VXLAN, NULL, 0, out_link))
-		return FALSE;
+struct infiniband_add_data {
+	int parent;
+	int p_key;
+};
 
-	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
+static void
+_infiniband_add_prepare (NMPlatform *platform,
+                         NMFakePlatformLink *device,
+                         gconstpointer user_data)
+{
+	const struct infiniband_add_data *d = user_data;
+	NMPObject *obj_tmp;
+	NMPObject *lnk;
 
-	g_return_val_if_fail (device, FALSE);
-	g_return_val_if_fail (!device->lnk, FALSE);
+	obj_tmp = (NMPObject *) device->obj;
 
-	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
-	device->lnk->lnk_vxlan = *props;
-	device->link.parent = props->parent_ifindex;
+	lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL);
+	lnk->lnk_infiniband.p_key = d->p_key;
+	lnk->lnk_infiniband.mode = "datagram";
 
-	if (out_link)
-		*out_link = &device->link;
-	return TRUE;
+	obj_tmp->link.parent = d->parent;
+	obj_tmp->_link.netlink.lnk = lnk;
 }
 
 static gboolean
 infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
 {
-	NMFakePlatformLink *device, *parent_device;
+	NMFakePlatformLink *parent_device;
 	char name[IFNAMSIZ];
+	const struct infiniband_add_data d = {
+		.parent = parent,
+		.p_key = p_key,
+	};
 
 	parent_device = link_get (platform, parent);
 	g_return_val_if_fail (parent_device != NULL, FALSE);
 
-	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);
-
-	if (!link_add (platform, name, NM_LINK_TYPE_INFINIBAND, NULL, 0, out_link))
-		return FALSE;
-
-	device = link_get (platform, nm_platform_link_get_ifindex (platform, name));
-	g_return_val_if_fail (device, FALSE);
-	g_return_val_if_fail (!device->lnk, FALSE);
+	nm_utils_new_infiniband_name (name, parent_device->obj->link.name, p_key);
 
-	device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
-	device->lnk->lnk_infiniband.p_key = p_key;
-	device->lnk->lnk_infiniband.mode = "datagram";
-	device->link.parent = parent;
+	link_add_one (platform, name, NM_LINK_TYPE_INFINIBAND,
+	              _infiniband_add_prepare, &d, out_link);
 	return TRUE;
 }
 
@@ -793,7 +860,7 @@ infiniband_partition_delete (NMPlatform *platform, int parent, int p_key)
 	parent_device = link_get (platform, parent);
 	g_return_val_if_fail (parent_device != NULL, FALSE);
 
-	nm_utils_new_infiniband_name (name, parent_device->link.name, p_key);
+	nm_utils_new_infiniband_name (name, parent_device->obj->link.name, p_key);
 	return link_delete (platform, nm_platform_link_get_ifindex (platform, name));
 }
 
@@ -804,7 +871,7 @@ wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabiliti
 
 	g_return_val_if_fail (device, FALSE);
 
-	if (device->link.type != NM_LINK_TYPE_WIFI)
+	if (device->obj->link.type != NM_LINK_TYPE_WIFI)
 		return FALSE;
 
 	if (caps) {
@@ -894,59 +961,25 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 
 /*****************************************************************************/
 
-static GArray *
-ip4_address_get_all (NMPlatform *platform, int ifindex)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *addresses;
-	NMPlatformIP4Address *address;
-	int count = 0, i;
-
-	/* Count addresses */
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-		if (address && address->ifindex == ifindex)
-			count++;
-	}
-
-	addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP4Address), count);
-
-	/* Fill addresses */
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-		if (address && address->ifindex == ifindex)
-			g_array_append_val (addresses, *address);
-	}
-
-	return addresses;
-}
-
-static GArray *
-ip6_address_get_all (NMPlatform *platform, int ifindex)
+static gboolean
+ipx_address_add (NMPlatform *platform, int addr_family, const NMPlatformObject *address)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *addresses;
-	NMPlatformIP6Address *address;
-	int count = 0, i;
-
-	/* Count addresses */
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-		if (address && address->ifindex == ifindex)
-			count++;
-	}
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPCacheOpsType cache_op;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	NMPCache *cache = nm_platform_get_cache (platform);
 
-	addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP6Address), count);
+	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
 
-	/* Fill addresses */
-	count = 0;
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-		if (address && address->ifindex == ifindex)
-			g_array_append_val (addresses, *address);
-	}
+	obj = nmp_object_new (addr_family == AF_INET
+	                        ? NMP_OBJECT_TYPE_IP4_ADDRESS
+	                        : NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                      address);
 
-	return addresses;
+	cache_op = nmp_cache_update_netlink (cache, obj, FALSE, &obj_old, &obj_new);
+	nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+	return TRUE;
 }
 
 static gboolean
@@ -960,9 +993,7 @@ ip4_address_add (NMPlatform *platform,
                  guint32 flags,
                  const char *label)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMPlatformIP4Address address;
-	int i;
 
 	memset (&address, 0, sizeof (address));
 	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
@@ -977,28 +1008,7 @@ ip4_address_add (NMPlatform *platform,
 	if (label)
 		g_strlcpy (address.label, label, sizeof (address.label));
 
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *item = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-		gboolean changed;
-
-		if (   item->ifindex != address.ifindex
-		    || item->address != address.address
-		    || item->plen != address.plen
-		    || !_ip4_address_equal_peer_net (item->peer_address, address.peer_address, address.plen))
-			continue;
-
-		changed = !nm_platform_ip4_address_cmp (item, &address);
-
-		memcpy (item, &address, sizeof (address));
-		if (changed)
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
-	}
-
-	g_array_append_val (priv->ip4_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_ADDED);
-
-	return TRUE;
+	return ipx_address_add (platform, AF_INET, (const NMPlatformObject *) &address);
 }
 
 static gboolean
@@ -1011,9 +1021,7 @@ ip6_address_add (NMPlatform *platform,
                  guint32 preferred,
                  guint32 flags)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
 	NMPlatformIP6Address address;
-	int i;
 
 	memset (&address, 0, sizeof (address));
 	address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
@@ -1026,380 +1034,321 @@ ip6_address_add (NMPlatform *platform,
 	address.preferred = preferred;
 	address.n_ifa_flags = flags;
 
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *item = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-		gboolean changed;
-
-		if (   item->ifindex != address.ifindex
-		    || !IN6_ARE_ADDR_EQUAL (&item->address, &address.address))
-			continue;
+	return ipx_address_add (platform, AF_INET6, (const NMPlatformObject *) &address);
+}
 
-		changed = !nm_platform_ip6_address_cmp (item, &address);
+static gboolean
+ipx_address_delete (NMPlatform *platform,
+                    int addr_family,
+                    int ifindex,
+                    gconstpointer addr,
+                    const guint8 *plen,
+                    gconstpointer peer_addr)
+{
+	gs_unref_ptrarray GPtrArray *objs = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	NMDedupMultiIter iter;
+	const NMPObject *o = NULL;
+	guint i;
+	guint32 peer_addr_i;
+
+	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	peer_addr_i = peer_addr ? *((guint32 *) peer_addr) : 0;
+
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup_addrroute (platform,
+	                                                       addr_family == AF_INET
+	                                                         ? NMP_OBJECT_TYPE_IP4_ADDRESS
+	                                                         : NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                                       0),
+	                         &o) {
+		const NMPObject *obj_old = NULL;
+
+		if (addr_family == AF_INET) {
+			const NMPlatformIP4Address *address = NMP_OBJECT_CAST_IP4_ADDRESS (o);
+
+			if (   address->ifindex != ifindex
+			    || (addr && address->address != *((guint32 *) addr))
+			    || (plen && address->plen != *plen)
+			    || (   peer_addr
+			        && (((peer_addr_i ^ address->peer_address) & _nm_utils_ip4_prefix_to_netmask (address->plen)) != 0)))
+				continue;
+		} else {
+			const NMPlatformIP6Address *address = NMP_OBJECT_CAST_IP6_ADDRESS (o);
+
+			g_assert (!peer_addr);
+			if (   address->ifindex != ifindex
+			    || (addr && !IN6_ARE_ADDR_EQUAL (&address->address, addr))
+			    || (plen && address->plen != *plen))
+				continue;
+		}
 
-		memcpy (item, &address, sizeof (address));
-		if (changed)
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
+		if (nmp_cache_remove (nm_platform_get_cache (platform),
+		                      o,
+		                      TRUE,
+		                      FALSE,
+		                      &obj_old) != NMP_CACHE_OPS_REMOVED)
+			g_assert_not_reached ();
+		g_assert (obj_old);
+		g_ptr_array_add (objs, (gpointer) obj_old);
 	}
 
-	g_array_append_val (priv->ip6_addresses, address);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, (int) NM_PLATFORM_SIGNAL_ADDED);
-
+	for (i = 0; i < objs->len; i++) {
+		nm_platform_cache_update_emit_signal (platform,
+		                                      NMP_CACHE_OPS_REMOVED,
+		                                      objs->pdata[i],
+		                                      NULL);
+	}
 	return TRUE;
 }
 
 static gboolean
 ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && address->address == addr
-		    && ((peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (plen)) == 0) {
-			NMPlatformIP4Address deleted_address;
-
-			memcpy (&deleted_address, address, sizeof (deleted_address));
-			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &deleted_address, (int) NM_PLATFORM_SIGNAL_REMOVED);
-			return TRUE;
-		}
-	}
-
-	return TRUE;
+	return ipx_address_delete (platform, AF_INET, ifindex, &addr, &plen, &peer_address);
 }
 
 static gboolean
 ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && IN6_ARE_ADDR_EQUAL (&address->address, &addr)) {
-			NMPlatformIP6Address deleted_address;
-
-			memcpy (&deleted_address, address, sizeof (deleted_address));
-			memset (address, 0, sizeof (*address));
-			g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &deleted_address, (int) NM_PLATFORM_SIGNAL_REMOVED);
-			return TRUE;
-		}
-	}
-
-	return TRUE;
-}
-
-static const NMPlatformIP4Address *
-ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip4_addresses->len; i++) {
-		NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && address->address == addr
-		    && _ip4_address_equal_peer_net (address->peer_address, peer_address, plen))
-			return address;
-	}
-
-	return NULL;
-}
-
-static const NMPlatformIP6Address *
-ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip6_addresses->len; i++) {
-		NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i);
-
-		if (   address->ifindex == ifindex
-		    && address->plen == plen
-		    && IN6_ARE_ADDR_EQUAL (&address->address, &addr))
-			return address;
-	}
-
-	return NULL;
+	return ipx_address_delete (platform, AF_INET6, ifindex, &addr, &plen, NULL);
 }
 
 /*****************************************************************************/
 
-static GArray *
-ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
+static gboolean
+ipx_route_delete (NMPlatform *platform,
+                  int addr_family,
+                  int ifindex,
+                  const NMPObject *obj)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *routes;
-	NMPlatformIP4Route *route;
+	gs_unref_ptrarray GPtrArray *objs = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+	NMDedupMultiIter iter;
+	const NMPObject *o = NULL;
 	guint i;
-
-	routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route));
-
-	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
-		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;
-
-	/* Fill routes */
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
-		if (route && (!ifindex || route->ifindex == ifindex)) {
-			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
-				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
-					g_array_append_val (routes, *route);
-			} else {
-				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
-					g_array_append_val (routes, *route);
-			}
-		}
+	NMPObjectType obj_type;
+
+	if (addr_family == AF_UNSPEC) {
+		g_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+		                                                NMP_OBJECT_TYPE_IP6_ROUTE));
+		g_assert (ifindex == -1);
+		ifindex = obj->object.ifindex;
+		obj_type = NMP_OBJECT_GET_TYPE (obj);
+	} else {
+		g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+		g_assert (!obj);
+		g_assert (ifindex > 0);
+		obj_type =   addr_family == AF_INET
+	               ? NMP_OBJECT_TYPE_IP4_ROUTE
+	               : NMP_OBJECT_TYPE_IP6_ROUTE;
 	}
 
-	return routes;
-}
-
-static GArray *
-ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	GArray *routes;
-	NMPlatformIP6Route *route;
-	guint i;
-
-	routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route));
-
-	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
-		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;
-
-	/* Fill routes */
-	for (i = 0; i < priv->ip6_routes->len; i++) {
-		route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
-		if (route && (!ifindex || route->ifindex == ifindex)) {
-			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
-				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT))
-					g_array_append_val (routes, *route);
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup_addrroute (platform,
+	                                                       obj_type,
+	                                                       ifindex),
+	                         &o) {
+		const NMPObject *obj_old = NULL;
+
+		if (obj) {
+			if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) {
+				const NMPlatformIP4Route *route = NMP_OBJECT_CAST_IP4_ROUTE (o);
+				const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (obj);
+
+				if (   route->network != r->network
+				    || route->plen != r->plen
+				    || route->metric != r->metric)
+					continue;
 			} else {
-				if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
-					g_array_append_val (routes, *route);
+				const NMPlatformIP6Route *route = NMP_OBJECT_CAST_IP6_ROUTE (o);
+				const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (obj);
+
+				if (   !IN6_ARE_ADDR_EQUAL (&route->network, &r->network)
+				    || route->plen != r->plen
+				    || route->metric != r->metric)
+					continue;
 			}
 		}
-	}
-
-	return routes;
-}
 
-static gboolean
-ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
-		NMPlatformIP4Route deleted_route;
-
-		if (   route->ifindex != ifindex
-		    || route->network != network
-		    || route->plen != plen
-		    || route->metric != metric)
-			continue;
-
-		memcpy (&deleted_route, route, sizeof (deleted_route));
-		g_array_remove_index (priv->ip4_routes, i);
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &deleted_route, (int) NM_PLATFORM_SIGNAL_REMOVED);
+		if (nmp_cache_remove (nm_platform_get_cache (platform),
+		                      o,
+		                      TRUE,
+		                      FALSE,
+		                      &obj_old) != NMP_CACHE_OPS_REMOVED)
+			g_assert_not_reached ();
+		g_assert (obj_old);
+		g_ptr_array_add (objs, (gpointer) obj_old);
 	}
 
+	for (i = 0; i < objs->len; i++) {
+		nm_platform_cache_update_emit_signal (platform,
+		                                      NMP_CACHE_OPS_REMOVED,
+		                                      objs->pdata[i],
+		                                      NULL);
+	}
 	return TRUE;
 }
 
 static gboolean
-ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
+ip_route_delete (NMPlatform *platform, const NMPObject *obj)
 {
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	metric = nm_utils_ip6_route_metric_normalize (metric);
-
-	for (i = 0; i < priv->ip6_routes->len; i++) {
-		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
-		NMPlatformIP6Route deleted_route;
-
-		if (   route->ifindex != ifindex
-		    || !IN6_ARE_ADDR_EQUAL (&route->network, &network)
-		    || route->plen != plen
-		    || route->metric != metric)
-			continue;
-
-		memcpy (&deleted_route, route, sizeof (deleted_route));
-		g_array_remove_index (priv->ip6_routes, i);
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &deleted_route, (int) NM_PLATFORM_SIGNAL_REMOVED);
-	}
+	g_assert (NM_IS_FAKE_PLATFORM (platform));
+	g_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	return TRUE;
+	return ipx_route_delete (platform, AF_UNSPEC, -1, obj);
 }
 
-static gboolean
-ip4_route_add (NMPlatform *platform, const NMPlatformIP4Route *route)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	NMPlatformIP4Route rt = *route;
-	guint i;
-
-	rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-	rt.network = nm_utils_ip4_address_clear_host_address (rt.network, rt.plen);
-	rt.scope_inv = nm_platform_route_scope_inv (rt.gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK);
+static NMPlatformError
+ip_route_add (NMPlatform *platform,
+              NMPNlmFlags flags,
+              int addr_family,
+              const NMPlatformIPRoute *route)
+{
+	NMDedupMultiIter iter;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPCacheOpsType cache_op;
+	const NMPObject *o = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
+	nm_auto_nmpobj const NMPObject *obj_replace = NULL;
+	NMPCache *cache = nm_platform_get_cache (platform);
+	gboolean has_gateway = FALSE;
+	NMPlatformIPRoute *r = NULL;
+	NMPlatformIP4Route *r4 = NULL;
+	NMPlatformIP6Route *r6 = NULL;
+	gboolean has_same_weak_id;
+	gboolean only_dirty;
+	guint16 nlmsgflags;
+
+	g_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	flags = NM_FLAGS_UNSET (flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE);
+
+	/* currently, only replace is implemented. */
+	g_assert (flags == NMP_NLM_FLAG_REPLACE);
+
+	obj = nmp_object_new (addr_family == AF_INET
+	                        ? NMP_OBJECT_TYPE_IP4_ROUTE
+	                        : NMP_OBJECT_TYPE_IP6_ROUTE,
+	                      (const NMPlatformObject *) route);
+	r = NMP_OBJECT_CAST_IP_ROUTE (obj);
+	nm_platform_ip_route_normalize (addr_family, r);
+
+	switch (addr_family) {
+	case AF_INET:
+		r4 = NMP_OBJECT_CAST_IP4_ROUTE (obj);
+		if (r4->gateway)
+			has_gateway = TRUE;
+		break;
+	case AF_INET6:
+		r6 = NMP_OBJECT_CAST_IP6_ROUTE (obj);
+		if (!IN6_IS_ADDR_UNSPECIFIED (&r6->gateway))
+			has_gateway = TRUE;
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
 
-	if (rt.gateway) {
-		for (i = 0; i < priv->ip4_routes->len; i++) {
-			NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes,
-			                                           NMPlatformIP4Route, i);
-			guint32 gate = ntohl (item->network) >> (32 - item->plen);
-			guint32 host = ntohl (rt.gateway) >> (32 - item->plen);
+	if (has_gateway) {
+		gboolean has_route_to_gw = FALSE;
+
+		nmp_cache_iter_for_each (&iter,
+		                         nm_platform_lookup_addrroute (platform,
+		                                                       NMP_OBJECT_GET_TYPE (obj),
+		                                                       0),
+		                         &o) {
+			if (addr_family == AF_INET) {
+				const NMPlatformIP4Route *item = NMP_OBJECT_CAST_IP4_ROUTE (o);
+				guint32 n = nm_utils_ip4_address_clear_host_address (item->network, item->plen);
+				guint32 g = nm_utils_ip4_address_clear_host_address (r4->gateway, item->plen);
+
+				if (   r->ifindex == item->ifindex
+				    && n == g) {
+					has_route_to_gw = TRUE;
+					break;
+				}
+			} else {
+				const NMPlatformIP6Route *item = NMP_OBJECT_CAST_IP6_ROUTE (o);
 
-			if (rt.ifindex == item->ifindex && gate == host)
-				break;
+				if (   r->ifindex == item->ifindex
+				    && nm_utils_ip6_address_same_prefix (&r6->gateway, &item->network, item->plen)) {
+					has_route_to_gw = TRUE;
+					break;
+				}
+			}
 		}
-		if (i == priv->ip4_routes->len) {
-			nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
-			             rt.ifindex, nm_utils_inet4_ntop (rt.network, NULL), rt.plen, rt.metric);
-			return FALSE;
+		if (!has_route_to_gw) {
+			if (addr_family == AF_INET) {
+				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable",
+				             r->ifindex, nm_utils_inet4_ntop (r4->network, NULL), r->plen, r->metric);
+			} else {
+				nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
+				             r->ifindex, nm_utils_inet6_ntop (&r6->network, NULL), r->plen, r->metric);
+			}
+			return NM_PLATFORM_ERROR_UNSPECIFIED;
 		}
 	}
 
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
-
-		if (item->network != rt.network)
-			continue;
-		if (item->plen != rt.plen)
-			continue;
-		if (item->metric != rt.metric)
-			continue;
-
-		if (item->ifindex != rt.ifindex) {
-			ip4_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
-			i--;
-			continue;
+	has_same_weak_id = FALSE;
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup_all (platform,
+	                                                 NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
+	                                                 obj),
+	                         &o) {
+		if (addr_family == AF_INET) {
+			if (nm_platform_ip4_route_cmp (NMP_OBJECT_CAST_IP4_ROUTE (o), r4, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0)
+				continue;
+		} else {
+			if (nm_platform_ip6_route_cmp (NMP_OBJECT_CAST_IP6_ROUTE (o), r6, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0)
+				continue;
 		}
-
-		memcpy (item, &rt, sizeof (rt));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE,
-		                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
+		has_same_weak_id = TRUE;
 	}
 
-	g_array_append_val (priv->ip4_routes, rt);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP4_ROUTE,
-	                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_ADDED);
-
-	return TRUE;
-}
-
-static gboolean
-ip6_route_add (NMPlatform *platform, const NMPlatformIP6Route *route)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	NMPlatformIP6Route rt = *route;
-	guint i;
-
-	rt.metric = nm_utils_ip6_route_metric_normalize (rt.metric);
-	rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-	nm_utils_ip6_address_clear_host_address (&rt.network, &rt.network, rt.plen);
-
-	if (!IN6_IS_ADDR_UNSPECIFIED (&rt.gateway)) {
-		for (i = 0; i < priv->ip6_routes->len; i++) {
-			NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes,
-			                                           NMPlatformIP6Route, i);
-			guint8 gate_bits = rt.gateway.s6_addr[item->plen / 8] >> (8 - item->plen % 8);
-			guint8 host_bits = item->network.s6_addr[item->plen / 8] >> (8 - item->plen % 8);
-
-			if (   rt.ifindex == item->ifindex
-			    && memcmp (&rt.gateway, &item->network, item->plen / 8) == 0
-			    && gate_bits == host_bits)
-				break;
-		}
-		if (i == priv->ip6_routes->len) {
-			nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable",
-			             rt.ifindex, nm_utils_inet6_ntop (&rt.network, NULL), rt.plen, rt.metric);
-			return FALSE;
+	nlmsgflags = 0;
+	if (has_same_weak_id) {
+		switch (flags) {
+		case NMP_NLM_FLAG_REPLACE:
+			nlmsgflags = NLM_F_REPLACE;
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
 		}
 	}
 
-	for (i = 0; i < priv->ip6_routes->len; i++) {
-		NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
-
-		if (!IN6_ARE_ADDR_EQUAL (&item->network, &rt.network))
-			continue;
-		if (item->plen != rt.plen)
-			continue;
-		if (item->metric != rt.metric)
-			continue;
-
-		if (item->ifindex != rt.ifindex) {
-			ip6_route_delete (platform, item->ifindex, item->network, item->plen, item->metric);
-			i--;
-			continue;
+	/* we manipulate the cache the same was as NMLinuxPlatform does it. */
+	cache_op = nmp_cache_update_netlink_route (cache,
+	                                           obj,
+	                                           FALSE,
+	                                           nlmsgflags,
+	                                           &obj_old,
+	                                           &obj_new,
+	                                           &obj_replace,
+	                                           NULL);
+	only_dirty = FALSE;
+	if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+		if (obj_replace) {
+			const NMDedupMultiEntry *entry_replace;
+
+			entry_replace = nmp_cache_lookup_entry (cache, obj_replace);
+			nm_assert (entry_replace && entry_replace->obj == obj_replace);
+			nm_dedup_multi_entry_set_dirty (entry_replace, TRUE);
+			only_dirty = TRUE;
 		}
-
-		memcpy (item, &rt, sizeof (rt));
-		g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE,
-		                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_CHANGED);
-		return TRUE;
+		nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
 	}
 
-	g_array_append_val (priv->ip6_routes, rt);
-	g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, (int) NMP_OBJECT_TYPE_IP6_ROUTE,
-	                       rt.ifindex, &rt, (int) NM_PLATFORM_SIGNAL_ADDED);
-
-	return TRUE;
-}
-
-static const NMPlatformIP4Route *
-ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	for (i = 0; i < priv->ip4_routes->len; i++) {
-		NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i);
-
-		if (route->ifindex == ifindex
-				&& route->network == network
-				&& route->plen == plen
-				&& route->metric == metric)
-			return route;
-	}
-
-	return NULL;
-}
-
-static const NMPlatformIP6Route *
-ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
-{
-	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform);
-	int i;
-
-	metric = nm_utils_ip6_route_metric_normalize (metric);
-
-	for (i = 0; i < priv->ip6_routes->len; i++) {
-		NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i);
-
-		if (route->ifindex == ifindex
-				&& IN6_ARE_ADDR_EQUAL (&route->network, &network)
-				&& route->plen == plen
-				&& route->metric == metric)
-			return route;
+	if (obj_replace) {
+		cache_op = nmp_cache_remove (cache, obj_replace, TRUE, only_dirty, NULL);
+		if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+			nm_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+			nm_platform_cache_update_emit_signal (platform, cache_op, obj_replace, NULL);
+		}
 	}
 
-	return NULL;
+	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
 /*****************************************************************************/
@@ -1409,12 +1358,8 @@ nm_fake_platform_init (NMFakePlatform *fake_platform)
 {
 	NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (fake_platform);
 
-	priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	priv->options = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 	priv->links = g_array_new (TRUE, TRUE, sizeof (NMFakePlatformLink));
-	priv->ip4_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Address));
-	priv->ip6_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Address));
-	priv->ip4_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route));
-	priv->ip6_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route));
 }
 
 void
@@ -1428,16 +1373,13 @@ nm_fake_platform_setup (void)
 
 	nm_platform_setup (platform);
 
-	/* skip zero element */
-	link_add (platform, NULL, NM_LINK_TYPE_NONE, NULL, 0, NULL);
-
 	/* add loopback interface */
-	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, 0, NULL);
+	link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, NULL, 0, NULL);
 
 	/* add some ethernets */
-	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
-	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
-	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL);
+	link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
+	link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
+	link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, NULL, 0, NULL);
 }
 
 static void
@@ -1451,13 +1393,9 @@ finalize (GObject *object)
 		NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i);
 
 		g_free (device->udi);
-		g_clear_pointer (&device->lnk, nmp_object_unref);
+		g_clear_pointer (&device->obj, nmp_object_unref);
 	}
 	g_array_unref (priv->links);
-	g_array_unref (priv->ip4_addresses);
-	g_array_unref (priv->ip6_addresses);
-	g_array_unref (priv->ip4_routes);
-	g_array_unref (priv->ip6_routes);
 
 	G_OBJECT_CLASS (nm_fake_platform_parent_class)->finalize (object);
 }
@@ -1473,15 +1411,8 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->sysctl_set = sysctl_set;
 	platform_class->sysctl_get = sysctl_get;
 
-	platform_class->link_get = _nm_platform_link_get;
-	platform_class->link_get_by_ifname = _nm_platform_link_get_by_ifname;
-	platform_class->link_get_by_address = _nm_platform_link_get_by_address;
-	platform_class->link_get_all = link_get_all;
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
-	platform_class->link_get_type_name = link_get_type_name;
-
-	platform_class->link_get_lnk = link_get_lnk;
 
 	platform_class->link_get_udi = link_get_udi;
 
@@ -1525,21 +1456,11 @@ nm_fake_platform_class_init (NMFakePlatformClass *klass)
 	platform_class->mesh_set_channel = mesh_set_channel;
 	platform_class->mesh_set_ssid = mesh_set_ssid;
 
-	platform_class->ip4_address_get = ip4_address_get;
-	platform_class->ip6_address_get = ip6_address_get;
-	platform_class->ip4_address_get_all = ip4_address_get_all;
-	platform_class->ip6_address_get_all = ip6_address_get_all;
 	platform_class->ip4_address_add = ip4_address_add;
 	platform_class->ip6_address_add = ip6_address_add;
 	platform_class->ip4_address_delete = ip4_address_delete;
 	platform_class->ip6_address_delete = ip6_address_delete;
 
-	platform_class->ip4_route_get = ip4_route_get;
-	platform_class->ip6_route_get = ip6_route_get;
-	platform_class->ip4_route_get_all = ip4_route_get_all;
-	platform_class->ip6_route_get_all = ip6_route_get_all;
-	platform_class->ip4_route_add = ip4_route_add;
-	platform_class->ip6_route_add = ip6_route_add;
-	platform_class->ip4_route_delete = ip4_route_delete;
-	platform_class->ip6_route_delete = ip6_route_delete;
+	platform_class->ip_route_add = ip_route_add;
+	platform_class->ip_route_delete = ip_route_delete;
 }
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 6b84c185..c4c93ed3 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -48,6 +48,7 @@
 #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 "nm-utils/unaligned.h"
@@ -103,6 +104,14 @@
 #define IFLA_IPTUN_MAX                  (__IFLA_IPTUN_MAX - 1)
 #endif
 
+
+static const gboolean RTA_PREF_SUPPORTED_AT_COMPILETIME = (RTA_MAX >= 20 /* RTA_PREF */);
+
+G_STATIC_ASSERT (RTA_MAX == (__RTA_MAX - 1));
+#define RTA_PREF                        20
+#undef  RTA_MAX
+#define RTA_MAX                        (MAX ((__RTA_MAX - 1), RTA_PREF))
+
 #ifndef MACVLAN_FLAG_NOPROMISC
 #define MACVLAN_FLAG_NOPROMISC          1
 #endif
@@ -197,6 +206,21 @@ typedef enum {
 	INFINIBAND_ACTION_DELETE_CHILD,
 } InfinibandAction;
 
+typedef enum {
+	CHANGE_LINK_TYPE_UNSPEC,
+	CHANGE_LINK_TYPE_SET_MTU,
+	CHANGE_LINK_TYPE_SET_ADDRESS,
+} ChangeLinkType;
+
+typedef struct {
+	union {
+		struct {
+			gconstpointer address;
+			gsize length;
+		} set_address;
+	};
+} ChangeLinkData;
+
 enum {
 	DELAYED_ACTION_IDX_REFRESH_ALL_LINKS,
 	DELAYED_ACTION_IDX_REFRESH_ALL_IP4_ADDRESSES,
@@ -255,13 +279,25 @@ static void delayed_action_schedule (NMPlatform *platform, DelayedActionType act
 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);
 static void do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type);
-static void cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
-static void cache_prune_candidates_prune (NMPlatform *platform);
+static void cache_on_change (NMPlatform *platform,
+                             NMPCacheOpsType cache_op,
+                             const NMPObject *obj_old,
+                             const NMPObject *obj_new);
+static void cache_prune_all (NMPlatform *platform);
 static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks);
-static void ASSERT_NETNS_CURRENT (NMPlatform *platform);
 
 /*****************************************************************************/
 
+static NMPlatformError
+wait_for_nl_response_to_plerr (WaitForNlResponseResult seq_result)
+{
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
+		return NM_PLATFORM_ERROR_SUCCESS;
+	if (seq_result < 0)
+		return (NMPlatformError) seq_result;
+	return NM_PLATFORM_ERROR_NETLINK;
+}
+
 static const char *
 wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size)
 {
@@ -287,35 +323,117 @@ wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, g
 	return buf0;
 }
 
-/******************************************************************
+/*****************************************************************************
  * Support IFLA_INET6_ADDR_GEN_MODE
- ******************************************************************/
+ *****************************************************************************/
 
 static int _support_user_ipv6ll = 0;
 #define _support_user_ipv6ll_still_undecided() (G_UNLIKELY (_support_user_ipv6ll == 0))
 
+static void
+_support_user_ipv6ll_detect (struct nlattr **tb)
+{
+	gboolean supported;
+
+	nm_assert (_support_user_ipv6ll_still_undecided ());
+
+	/* IFLA_INET6_ADDR_GEN_MODE was added in kernel 3.17, dated 5 October, 2014. */
+	supported = !!tb[IFLA_INET6_ADDR_GEN_MODE];
+	_support_user_ipv6ll = supported ? 1 : -1;
+	_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s",
+	        supported ? "detected" : "not detected");
+}
+
 static gboolean
 _support_user_ipv6ll_get (void)
 {
 	if (_support_user_ipv6ll_still_undecided ()) {
-		_support_user_ipv6ll = -1;
-		_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s", "failed to detect; assume no support");
-		return FALSE;
+		_support_user_ipv6ll = 1;
+		_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s", "failed to detect; assume support");
 	}
-	return _support_user_ipv6ll > 0;
+	return _support_user_ipv6ll >= 0;
+}
+
+/*****************************************************************************
+ * extended IFA_FLAGS support
+ *****************************************************************************/
 
+static int _support_kernel_extended_ifa_flags = 0;
+
+#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == 0))
+
+static void
+_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
+{
+	struct nlmsghdr *msg_hdr;
+	gboolean support;
+
+	nm_assert (_support_kernel_extended_ifa_flags_still_undecided ());
+	nm_assert (msg);
+
+	msg_hdr = nlmsg_hdr (msg);
+
+	nm_assert (msg_hdr && msg_hdr->nlmsg_type == RTM_NEWADDR);
+
+	/* IFA_FLAGS is set for IPv4 and IPv6 addresses. It was added first to IPv6,
+	 * but if we encounter an IPv4 address with IFA_FLAGS, we surely have support. */
+	if (NM_IN_SET (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family, AF_INET, AF_INET6))
+		return;
+
+	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
+	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
+	 * and IFA_F_NOPREFIXROUTE for IPv6. They were added together in kernel 3.14,
+	 * dated 30 March, 2014.
+	 *
+	 * For IPv4, IFA_F_NOPREFIXROUTE was added later, but there is no easy
+	 * way to detect kernel support. */
+	support = !!nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), IFA_FLAGS);
+	_support_kernel_extended_ifa_flags = support ? 1 : -1;
+	_LOG2D ("kernel-support: extended-ifa-flags: %s", support ? "detected" : "not detected");
 }
 
+static gboolean
+_support_kernel_extended_ifa_flags_get (void)
+{
+	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
+		_LOG2D ("kernel-support: extended-ifa-flags: %s", "unable to detect kernel support for handling IPv6 temporary addresses. Assume support");
+		_support_kernel_extended_ifa_flags = 1;
+	}
+	return _support_kernel_extended_ifa_flags >= 0;
+}
+
+/*****************************************************************************
+ * Support RTA_PREF
+ *****************************************************************************/
+
+static int _support_rta_pref = 0;
+#define _support_rta_pref_still_undecided() (G_UNLIKELY (_support_rta_pref == 0))
+
 static void
-_support_user_ipv6ll_detect (struct nlattr **tb)
+_support_rta_pref_detect (struct nlattr **tb)
 {
-	if (_support_user_ipv6ll_still_undecided ()) {
-		gboolean supported = !!tb[IFLA_INET6_ADDR_GEN_MODE];
+	gboolean supported;
+
+	nm_assert (_support_rta_pref_still_undecided ());
 
-		_support_user_ipv6ll = supported ? 1 : -1;
-		_LOG2D ("kernel-support: IFLA_INET6_ADDR_GEN_MODE: %s",
-		        supported ? "detected" : "not detected");
+	/* RTA_PREF was added in kernel 4.1, dated 21 June, 2015. */
+	supported = !!tb[RTA_PREF];
+	_support_rta_pref = supported ? 1 : -1;
+	_LOG2D ("kernel-support: RTA_PREF: ability to set router preference for IPv6 routes: %s",
+	        supported ? "detected" : "not detected");
+}
+
+static gboolean
+_support_rta_pref_get (void)
+{
+	if (_support_rta_pref_still_undecided ()) {
+		/* if we couldn't detect support, we fallback on compile-time check, whether
+		 * RTA_PREF is present in the kernel headers. */
+		_support_rta_pref = RTA_PREF_SUPPORTED_AT_COMPILETIME ? 1 : -1;
+		_LOG2D ("kernel-support: RTA_PREF: ability to set router preference for IPv6 routes: %s",
+		        RTA_PREF_SUPPORTED_AT_COMPILETIME ? "assume support" : "assume no support");
 	}
+	return _support_rta_pref >= 0;
 }
 
 /******************************************************************
@@ -377,6 +495,7 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_WWAN_NET,      "wwan",        NULL,          "wwan" },
 	{ NM_LINK_TYPE_WIMAX,         "wimax",       "wimax",       "wimax" },
 
+	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
 	{ NM_LINK_TYPE_DUMMY,         "dummy",       "dummy",       NULL },
 	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
 	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
@@ -388,13 +507,13 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_MACVLAN,       "macvlan",     "macvlan",     NULL },
 	{ NM_LINK_TYPE_MACVTAP,       "macvtap",     "macvtap",     NULL },
 	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
+	{ NM_LINK_TYPE_PPP,           "ppp",         NULL,          "ppp" },
 	{ NM_LINK_TYPE_SIT,           "sit",         "sit",         NULL },
 	{ NM_LINK_TYPE_TAP,           "tap",         NULL,          NULL },
 	{ NM_LINK_TYPE_TUN,           "tun",         NULL,          NULL },
 	{ NM_LINK_TYPE_VETH,          "veth",        "veth",        NULL },
 	{ NM_LINK_TYPE_VLAN,          "vlan",        "vlan",        "vlan" },
 	{ NM_LINK_TYPE_VXLAN,         "vxlan",       "vxlan",       "vxlan" },
-	{ NM_LINK_TYPE_BNEP,          "bluetooth",   NULL,          "bluetooth" },
 
 	{ NM_LINK_TYPE_BRIDGE,        "bridge",      "bridge",      "bridge" },
 	{ NM_LINK_TYPE_BOND,          "bond",        "bond",        "bond" },
@@ -628,7 +747,7 @@ _linktype_get_type (NMPlatform *platform,
 {
 	guint i;
 
-	ASSERT_NETNS_CURRENT (platform);
+	NMTST_ASSERT_PLATFORM_NETNS_CURRENT (platform);
 	nm_assert (ifname);
 
 	if (completed_from_cache) {
@@ -693,6 +812,8 @@ _linktype_get_type (NMPlatform *platform,
 		return NM_LINK_TYPE_SIT;
 	else if (arptype == ARPHRD_TUNNEL6)
 		return NM_LINK_TYPE_IP6TNL;
+	else if (arptype == ARPHRD_PPP)
+		return NM_LINK_TYPE_PPP;
 
 	{
 		NMPUtilsEthtoolDriverInfo driver_info;
@@ -762,6 +883,10 @@ _linktype_get_type (NMPlatform *platform,
 			 * aside from the DEVTYPE. */
 			if (!g_strcmp0 (devtype, "gadget"))
 				return NM_LINK_TYPE_ETHERNET;
+
+			/* Distributed Switch Architecture switch chips */
+			if (!g_strcmp0 (devtype, "dsa"))
+				return NM_LINK_TYPE_ETHERNET;
 		}
 	}
 
@@ -772,32 +897,149 @@ _linktype_get_type (NMPlatform *platform,
  * libnl unility functions and wrappers
  ******************************************************************/
 
-#define nm_auto_nlmsg __attribute__((cleanup(_nm_auto_nl_msg_cleanup)))
+#define NLMSG_TAIL(nmsg) \
+    ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/* copied from iproute2's addattr_l(). */
+static gboolean
+_nl_addattr_l (struct nlmsghdr *n,
+               int maxlen,
+               int type,
+               const void *data,
+               int alen)
+{
+	int len = RTA_LENGTH (alen);
+	struct rtattr *rta;
+
+	if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
+		return FALSE;
+
+	rta = NLMSG_TAIL (n);
+	rta->rta_type = type;
+	rta->rta_len = len;
+	memcpy (RTA_DATA (rta), data, alen);
+	n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);
+	return TRUE;
+}
+
 static void
 _nm_auto_nl_msg_cleanup (void *ptr)
 {
 	nlmsg_free (*((struct nl_msg **) ptr));
 }
+#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)
 
 static const char *
-_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
+_nl_nlmsghdr_to_str (const struct nlmsghdr *hdr, char *buf, gsize len)
 {
-	const char *str_type = NULL;
+	const char *b;
+	const char *s;
+	guint flags, flags_before;
+	const char *prefix;
 
-	switch (type) {
-	case RTM_NEWLINK:  str_type = "NEWLINK";  break;
-	case RTM_DELLINK:  str_type = "DELLINK";  break;
-	case RTM_NEWADDR:  str_type = "NEWADDR";  break;
-	case RTM_DELADDR:  str_type = "DELADDR";  break;
-	case RTM_NEWROUTE: str_type = "NEWROUTE"; break;
-	case RTM_DELROUTE: str_type = "DELROUTE"; break;
+	nm_utils_to_string_buffer_init (&buf, &len);
+	b = buf;
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:  s = "NEWLINK";  break;
+	case RTM_DELLINK:  s = "DELLINK";  break;
+	case RTM_NEWADDR:  s = "NEWADDR";  break;
+	case RTM_DELADDR:  s = "DELADDR";  break;
+	case RTM_NEWROUTE: s = "NEWROUTE"; break;
+	case RTM_DELROUTE: s = "DELROUTE"; break;
+	default:           s = NULL;       break;
 	}
-	if (str_type)
-		g_strlcpy (buf, str_type, len);
+
+	if (s)
+		nm_utils_strbuf_append (&buf, &len, "RTM_%s", s);
 	else
-		g_snprintf (buf, len, "(%d)", type);
-	return buf;
+		nm_utils_strbuf_append (&buf, &len, "(%u)", (unsigned) hdr->nlmsg_type);
+
+	flags = hdr->nlmsg_flags;
+
+	if (!flags) {
+		nm_utils_strbuf_append_str (&buf, &len, ", flags 0");
+		goto flags_done;
+	}
+
+#define _F(f, n) \
+	G_STMT_START { \
+		if (NM_FLAGS_ALL (flags, f)) { \
+			flags &= ~(f); \
+			nm_utils_strbuf_append (&buf, &len, "%s%s", prefix, n); \
+			if (!flags) \
+				goto flags_done; \
+			prefix = ","; \
+		} \
+	} G_STMT_END
+
+	prefix = ", flags ";
+	flags_before = flags;
+	_F (NLM_F_REQUEST, "request");
+	_F (NLM_F_MULTI, "multi");
+	_F (NLM_F_ACK, "ack");
+	_F (NLM_F_ECHO, "echo");
+	_F (NLM_F_DUMP_INTR, "dump_intr");
+	_F (0x20 /*NLM_F_DUMP_FILTERED*/, "dump_filtered");
+
+	if (flags_before != flags)
+		prefix = ";";
+
+	switch (hdr->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_NEWADDR:
+	case RTM_NEWROUTE:
+		_F (NLM_F_REPLACE, "replace");
+		_F (NLM_F_EXCL, "excl");
+		_F (NLM_F_CREATE, "create");
+		_F (NLM_F_APPEND, "append");
+		break;
+	case RTM_GETLINK:
+	case RTM_GETADDR:
+	case RTM_GETROUTE:
+		_F (NLM_F_DUMP, "dump");
+		_F (NLM_F_ROOT, "root");
+		_F (NLM_F_MATCH, "match");
+		_F (NLM_F_ATOMIC, "atomic");
+		break;
+	}
+
+#undef _F
+
+	if (flags_before != flags)
+		prefix = ";";
+	nm_utils_strbuf_append (&buf, &len, "%s0x%04x", prefix, flags);
+
+flags_done:
+
+	nm_utils_strbuf_append (&buf, &len, ", seq %u", (unsigned) hdr->nlmsg_seq);
+
+	return b;
+}
+
+static int
+_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+               const struct nla_policy *policy)
+{
+	return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy);
+}
+#define nla_parse(...) _nl_nla_parse(__VA_ARGS__)
+
+static int
+_nl_nlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
+                 int maxtype, const struct nla_policy *policy)
+{
+	return nlmsg_parse (nlh, hdrlen, tb, maxtype, (struct nla_policy *) policy);
 }
+#define nlmsg_parse(...) _nl_nlmsg_parse(__VA_ARGS__)
+
+static int
+_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+                      const struct nla_policy *policy)
+{
+	return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy);
+}
+#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__)
 
 /******************************************************************
  * NMPObject/netlink functions
@@ -826,7 +1068,7 @@ _parse_af_inet6 (NMPlatform *platform,
                  guint8 *out_addr_gen_mode_inv,
                  gboolean *out_addr_gen_mode_valid)
 {
-	static struct nla_policy policy[IFLA_INET6_MAX+1] = {
+	static const struct nla_policy policy[IFLA_INET6_MAX+1] = {
 		[IFLA_INET6_FLAGS]              = { .type = NLA_U32 },
 		[IFLA_INET6_CACHEINFO]          = { .minlen = nm_offsetofend (struct ifla_cacheinfo, retrans_time) },
 		[IFLA_INET6_CONF]               = { .minlen = 4 },
@@ -862,7 +1104,8 @@ _parse_af_inet6 (NMPlatform *platform,
 	/* Hack to detect support addrgenmode of the kernel. We only parse
 	 * netlink messages that we receive from kernel, hence this check
 	 * is valid. */
-	_support_user_ipv6ll_detect (tb);
+	if (_support_user_ipv6ll_still_undecided ())
+		_support_user_ipv6ll_detect (tb);
 
 	if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
 		i6_addr_gen_mode_inv = _nm_platform_uint8_inv (nla_get_u8 (tb[IFLA_INET6_ADDR_GEN_MODE]));
@@ -892,7 +1135,7 @@ errout:
 static NMPObject *
 _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_GRE_MAX + 1] = {
+	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 },
@@ -952,7 +1195,7 @@ _parse_lnk_gre (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPOIB_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPOIB_MAX + 1] = {
 		[IFLA_IPOIB_PKEY]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_MODE]   = { .type = NLA_U16 },
 		[IFLA_IPOIB_UMCAST] = { .type = NLA_U16 },
@@ -998,7 +1241,7 @@ _parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
 		[IFLA_IPTUN_LINK]        = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]       = { .type = NLA_UNSPEC,
 		                             .minlen = sizeof (struct in6_addr)},
@@ -1051,7 +1294,7 @@ _parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1089,7 +1332,7 @@ _parse_lnk_ipip (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
 		[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
 		[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
 	};
@@ -1132,7 +1375,7 @@ _parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[__IFLA_MACSEC_MAX] = {
+	static const struct nla_policy policy[__IFLA_MACSEC_MAX] = {
 		[IFLA_MACSEC_SCI]            = { .type = NLA_U64 },
 		[IFLA_MACSEC_ICV_LEN]        = { .type = NLA_U8 },
 		[IFLA_MACSEC_CIPHER_SUITE]   = { .type = NLA_U64 },
@@ -1182,7 +1425,7 @@ _parse_lnk_macsec (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _parse_lnk_sit (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_IPTUN_MAX + 1] = {
 		[IFLA_IPTUN_LINK]     = { .type = NLA_U32 },
 		[IFLA_IPTUN_LOCAL]    = { .type = NLA_U32 },
 		[IFLA_IPTUN_REMOTE]   = { .type = NLA_U32 },
@@ -1284,7 +1527,7 @@ _vlan_qos_mapping_from_nla (struct nlattr *nlattr,
 static NMPObject *
 _parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_VLAN_MAX+1] = {
+	static const struct nla_policy policy[IFLA_VLAN_MAX+1] = {
 		[IFLA_VLAN_ID]          = { .type = NLA_U16 },
 		[IFLA_VLAN_FLAGS]       = { .minlen = nm_offsetofend (struct ifla_vlan_flags, flags) },
 		[IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
@@ -1370,7 +1613,7 @@ struct nm_ifla_vxlan_port_range {
 static NMPObject *
 _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 {
-	static struct nla_policy policy[IFLA_VXLAN_MAX + 1] = {
+	static const struct nla_policy policy[IFLA_VXLAN_MAX + 1] = {
 		[IFLA_VXLAN_ID]         = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP]      = { .type = NLA_U32 },
 		[IFLA_VXLAN_GROUP6]     = { .type = NLA_UNSPEC,
@@ -1460,7 +1703,7 @@ _parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
 {
-	static struct nla_policy policy[IFLA_MAX+1] = {
+	static const struct nla_policy policy[IFLA_MAX+1] = {
 		[IFLA_IFNAME]           = { .type = NLA_STRING,
 		                            .maxlen = IFNAMSIZ },
 		[IFLA_MTU]              = { .type = NLA_U32 },
@@ -1488,7 +1731,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		[IFLA_NET_NS_PID]       = { .type = NLA_U32 },
 		[IFLA_NET_NS_FD]        = { .type = NLA_U32 },
 	};
-	static struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = {
+	static const struct nla_policy policy_link_info[IFLA_INFO_MAX+1] = {
 		[IFLA_INFO_KIND]        = { .type = NLA_STRING },
 		[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
 		[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
@@ -1514,6 +1757,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 		return NULL;
 	ifi = nlmsg_data(nlh);
 
+	if (ifi->ifi_family != AF_UNSPEC)
+		return NULL;
+
 	obj = nmp_object_new_link (ifi->ifi_index);
 
 	if (id_only)
@@ -1680,7 +1926,7 @@ _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 = nmp_object_ref (link_cached->_link.netlink.lnk);
+				lnk_data = (NMPObject *) nmp_object_ref (link_cached->_link.netlink.lnk);
 			}
 			if (address_complete_from_cache)
 				obj->link.addr = link_cached->link.addr;
@@ -1711,7 +1957,7 @@ errout:
 static NMPObject *
 _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static struct nla_policy policy[IFA_MAX+1] = {
+	static const struct nla_policy policy[IFA_MAX+1] = {
 		[IFA_LABEL]     = { .type = NLA_STRING,
 		                     .maxlen = IFNAMSIZ },
 		[IFA_CACHEINFO] = { .minlen = nm_offsetofend (struct ifa_cacheinfo, tstamp) },
@@ -1733,7 +1979,7 @@ _new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 		goto errout;
 	is_v4 = ifa->ifa_family == AF_INET;
 
-	err = nlmsg_parse(nlh, sizeof(*ifa), tb, IFA_MAX, policy);
+	err = nlmsg_parse (nlh, sizeof(*ifa), tb, IFA_MAX, policy);
 	if (err < 0)
 		goto errout;
 
@@ -1826,10 +2072,12 @@ errout:
 static NMPObject *
 _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 {
-	static struct nla_policy policy[RTA_MAX+1] = {
+	static const struct nla_policy policy[RTA_MAX+1] = {
+		[RTA_TABLE]     = { .type = NLA_U32 },
 		[RTA_IIF]       = { .type = NLA_U32 },
 		[RTA_OIF]       = { .type = NLA_U32 },
 		[RTA_PRIORITY]  = { .type = NLA_U32 },
+		[RTA_PREF]      = { .type = NLA_U8 },
 		[RTA_FLOW]      = { .type = NLA_U32 },
 		[RTA_CACHEINFO] = { .minlen = nm_offsetofend (struct rta_cacheinfo, rta_tsage) },
 		[RTA_METRICS]   = { .type = NLA_NESTED },
@@ -1849,7 +2097,6 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	} nh;
 	guint32 mss;
 	guint32 window = 0, cwnd = 0, initcwnd = 0, initrwnd = 0, mtu = 0, lock = 0;
-	guint32 table;
 
 	if (!nlmsg_valid_hdr (nlh, sizeof (*rtm)))
 		return NULL;
@@ -1869,12 +2116,6 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	if (err < 0)
 		goto errout;
 
-	table = tb[RTA_TABLE]
-	        ? nla_get_u32 (tb[RTA_TABLE])
-	        : (guint32) rtm->rtm_table;
-	if (table != RT_TABLE_MAIN)
-		goto errout;
-
 	/*****************************************************************/
 
 	is_v4 = rtm->rtm_family == AF_INET;
@@ -1928,7 +2169,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	    || tb[RTA_GATEWAY]
 	    || tb[RTA_FLOW]) {
 		int ifindex = 0;
-		NMIPAddr gateway = NMIPAddrInit;
+		NMIPAddr gateway = { };
 
 		if (tb[RTA_OIF])
 			ifindex = nla_get_u32 (tb[RTA_OIF]);
@@ -1956,7 +2197,7 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	mss = 0;
 	if (tb[RTA_METRICS]) {
 		struct nlattr *mtb[RTAX_MAX + 1];
-		static struct nla_policy rtax_policy[RTAX_MAX + 1] = {
+		static const struct nla_policy rtax_policy[RTAX_MAX + 1] = {
 			[RTAX_LOCK]        = { .type = NLA_U32 },
 			[RTAX_ADVMSS]      = { .type = NLA_U32 },
 			[RTAX_WINDOW]      = { .type = NLA_U32 },
@@ -1990,6 +2231,10 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 
 	obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
 
+	obj->ip_route.table_coerced = nm_platform_route_table_coerce (  tb[RTA_TABLE]
+	                                                              ? nla_get_u32 (tb[RTA_TABLE])
+	                                                              : (guint32) rtm->rtm_table);
+
 	obj->ip_route.ifindex = nh.ifindex;
 
 	if (_check_addr_or_errout (tb, RTA_DST, addr_len))
@@ -2015,9 +2260,13 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 			memcpy (&obj->ip6_route.pref_src, nla_data (tb[RTA_PREFSRC]), addr_len);
 	}
 
-	if (!is_v4 && tb[RTA_SRC]) {
-		_check_addr_or_errout (tb, RTA_SRC, addr_len);
-		memcpy (&obj->ip6_route.src, nla_data (tb[RTA_SRC]), addr_len);
+	if (is_v4)
+		obj->ip4_route.tos = rtm->rtm_tos;
+	else {
+		if (tb[RTA_SRC]) {
+			_check_addr_or_errout (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;
 	}
 
@@ -2027,12 +2276,20 @@ _new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
 	obj->ip_route.initcwnd = initcwnd;
 	obj->ip_route.initrwnd = initrwnd;
 	obj->ip_route.mtu = mtu;
-	obj->ip_route.tos = rtm->rtm_tos;
-	obj->ip_route.lock_window = NM_FLAGS_HAS (lock, 1 << RTAX_WINDOW);
-	obj->ip_route.lock_cwnd = NM_FLAGS_HAS (lock, 1 << RTAX_CWND);
+	obj->ip_route.lock_window   = NM_FLAGS_HAS (lock, 1 << RTAX_WINDOW);
+	obj->ip_route.lock_cwnd     = NM_FLAGS_HAS (lock, 1 << RTAX_CWND);
 	obj->ip_route.lock_initcwnd = NM_FLAGS_HAS (lock, 1 << RTAX_INITCWND);
 	obj->ip_route.lock_initrwnd = NM_FLAGS_HAS (lock, 1 << RTAX_INITRWND);
-	obj->ip_route.lock_mtu  = NM_FLAGS_HAS (lock, 1 << RTAX_MTU);
+	obj->ip_route.lock_mtu      = NM_FLAGS_HAS (lock, 1 << RTAX_MTU);
+
+	if (!is_v4) {
+		/* Detect support for RTA_PREF by inspecting the netlink message. */
+		if (_support_rta_pref_still_undecided ())
+			_support_rta_pref_detect (tb);
+
+		if (tb[RTA_PREF])
+			obj->ip6_route.rt_pref = nla_get_u8 (tb[RTA_PREF]);
+	}
 
 	if (NM_FLAGS_HAS (rtm->rtm_flags, RTM_F_CLONED)) {
 		/* we must not straight way reject cloned routes, because we might have cached
@@ -2135,12 +2392,14 @@ nla_put_failure:
 
 static gboolean
 _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
-                               NMLinkType link_type)
+                               NMLinkType link_type,
+                               const char *veth_peer)
 {
 	struct nlattr *info;
 	const char *kind;
 
 	nm_assert (msg);
+	nm_assert (!!veth_peer == (link_type == NM_LINK_TYPE_VETH));
 
 	kind = nm_link_type_to_rtnl_type_string (link_type);
 	if (!kind)
@@ -2151,11 +2410,26 @@ _nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
 
 	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
 
+	if (veth_peer) {
+		struct ifinfomsg ifi = { };
+		struct nlattr *data, *info_peer;
+
+		if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+			goto nla_put_failure;
+		if (!(info_peer = nla_nest_start (msg, 1 /*VETH_INFO_PEER*/)))
+			goto nla_put_failure;
+		if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+			goto nla_put_failure;
+		NLA_PUT_STRING (msg, IFLA_IFNAME, veth_peer);
+		nla_nest_end (msg, info_peer);
+		nla_nest_end (msg, data);
+	}
+
 	nla_nest_end (msg, info);
 
 	return TRUE;
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -2363,7 +2637,7 @@ _nl_msg_new_address (int nlmsg_type,
 	    && *((in_addr_t *) address) != 0) {
 		in_addr_t broadcast;
 
-		broadcast = *((in_addr_t *) address) | ~nm_utils_ip4_prefix_to_netmask (plen);
+		broadcast = *((in_addr_t *) address) | ~_nm_utils_ip4_prefix_to_netmask (plen);
 		NLA_PUT (msg, IFA_BROADCAST, addr_len, &broadcast);
 	}
 
@@ -2394,87 +2668,109 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
+static guint32
+ip_route_get_lock_flag (const NMPlatformIPRoute *route)
+{
+	return   (((guint32) route->lock_window)   << RTAX_WINDOW)
+	       | (((guint32) route->lock_cwnd)     << RTAX_CWND)
+	       | (((guint32) route->lock_initcwnd) << RTAX_INITCWND)
+	       | (((guint32) route->lock_initrwnd) << RTAX_INITRWND)
+	       | (((guint32) route->lock_mtu)      << RTAX_MTU);
+}
+
 /* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */
 static struct nl_msg *
 _nl_msg_new_route (int nlmsg_type,
-                   int nlmsg_flags,
-                   int family,
-                   int ifindex,
-                   NMIPConfigSource source,
-                   unsigned char scope,
-                   gconstpointer network,
-                   guint8 plen,
-                   gconstpointer gateway,
-                   guint32 metric,
-                   guint32 mss,
-                   gconstpointer pref_src,
-                   gconstpointer src,
-                   guint8 src_plen,
-                   guint8 tos,
-                   guint32 window,
-                   guint32 cwnd,
-                   guint32 initcwnd,
-                   guint32 initrwnd,
-                   guint32 mtu,
-                   guint32 lock)
+                   guint16 nlmsgflags,
+                   const NMPObject *obj)
 {
 	struct nl_msg *msg;
+	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+	gboolean is_v4 = klass->addr_family == AF_INET;
+	const guint32 lock = ip_route_get_lock_flag (NMP_OBJECT_CAST_IP_ROUTE (obj));
+	const guint32 table = nm_platform_route_table_uncoerce (NMP_OBJECT_CAST_IP_ROUTE (obj)->table_coerced, TRUE);
 	struct rtmsg rtmsg = {
-		.rtm_family = family,
-		.rtm_tos = tos,
-		.rtm_table = RT_TABLE_MAIN, /* omit setting RTA_TABLE attribute */
-		.rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot (source),
-		.rtm_scope = scope,
+		.rtm_family = klass->addr_family,
+		.rtm_tos = is_v4
+		           ? obj->ip4_route.tos
+		           : 0,
+		.rtm_table = table <= 0xFF ? table : RT_TABLE_UNSPEC,
+		.rtm_protocol = nmp_utils_ip_config_source_coerce_to_rtprot (obj->ip_route.rt_source),
+		.rtm_scope = is_v4
+		             ? nm_platform_route_scope_inv (obj->ip4_route.scope_inv)
+		             : RT_SCOPE_NOWHERE,
 		.rtm_type = RTN_UNICAST,
 		.rtm_flags = 0,
-		.rtm_dst_len = plen,
-		.rtm_src_len = src ? src_plen : 0,
+		.rtm_dst_len = obj->ip_route.plen,
+		.rtm_src_len = is_v4
+		               ? 0
+		               : NMP_OBJECT_CAST_IP6_ROUTE (obj)->src_plen,
 	};
 
 	gsize addr_len;
 
-	nm_assert (NM_IN_SET (family, AF_INET, AF_INET6));
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
-	nm_assert (network);
 
-	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	msg = nlmsg_alloc_simple (nlmsg_type, (int) nlmsgflags);
 	if (!msg)
 		g_return_val_if_reached (NULL);
 
 	if (nlmsg_append (msg, &rtmsg, sizeof (rtmsg), NLMSG_ALIGNTO) < 0)
 		goto nla_put_failure;
 
-	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
+	addr_len = is_v4
+	             ? sizeof (in_addr_t)
+	             : sizeof (struct in6_addr);
 
-	NLA_PUT (msg, RTA_DST, addr_len, network);
+	NLA_PUT (msg, RTA_DST, addr_len,
+	         is_v4
+	           ? (gconstpointer) &obj->ip4_route.network
+	           : (gconstpointer) &obj->ip6_route.network);
 
-	if (src)
-		NLA_PUT (msg, RTA_SRC, addr_len, src);
+	if (!is_v4) {
+		if (!IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (obj)->src))
+			NLA_PUT (msg, RTA_SRC, addr_len, &obj->ip6_route.src);
+	}
 
-	NLA_PUT_U32 (msg, RTA_PRIORITY, metric);
+	NLA_PUT_U32 (msg, RTA_PRIORITY, obj->ip_route.metric);
 
-	if (pref_src)
-		NLA_PUT (msg, RTA_PREFSRC, addr_len, pref_src);
+	if (table > 0xFF)
+		NLA_PUT_U32 (msg, RTA_TABLE, table);
+
+	if (is_v4) {
+		if (NMP_OBJECT_CAST_IP4_ROUTE (obj)->pref_src)
+			NLA_PUT (msg, RTA_PREFSRC, addr_len, &obj->ip4_route.pref_src);
+	} else {
+		if (!IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (obj)->pref_src))
+			NLA_PUT (msg, RTA_PREFSRC, addr_len, &obj->ip6_route.pref_src);
+	}
 
-	if (mss || window || cwnd || initcwnd || initrwnd || mtu || lock) {
+	if (   obj->ip_route.mss
+	    || obj->ip_route.window
+	    || obj->ip_route.cwnd
+	    || obj->ip_route.initcwnd
+	    || obj->ip_route.initrwnd
+	    || obj->ip_route.mtu
+	    || lock) {
 		struct nlattr *metrics;
 
 		metrics = nla_nest_start (msg, RTA_METRICS);
 		if (!metrics)
 			goto nla_put_failure;
 
-		if (mss)
-			NLA_PUT_U32 (msg, RTAX_ADVMSS, mss);
-		if (window)
-			NLA_PUT_U32 (msg, RTAX_WINDOW, window);
-		if (cwnd)
-			NLA_PUT_U32 (msg, RTAX_CWND, cwnd);
-		if (initcwnd)
-			NLA_PUT_U32 (msg, RTAX_INITCWND, initcwnd);
-		if (initrwnd)
-			NLA_PUT_U32 (msg, RTAX_INITRWND, initrwnd);
-		if (mtu)
-			NLA_PUT_U32 (msg, RTAX_MTU, mtu);
+		if (obj->ip_route.mss)
+			NLA_PUT_U32 (msg, RTAX_ADVMSS, obj->ip_route.mss);
+		if (obj->ip_route.window)
+			NLA_PUT_U32 (msg, RTAX_WINDOW, obj->ip_route.window);
+		if (obj->ip_route.cwnd)
+			NLA_PUT_U32 (msg, RTAX_CWND, obj->ip_route.cwnd);
+		if (obj->ip_route.initcwnd)
+			NLA_PUT_U32 (msg, RTAX_INITCWND, obj->ip_route.initcwnd);
+		if (obj->ip_route.initrwnd)
+			NLA_PUT_U32 (msg, RTAX_INITRWND, obj->ip_route.initrwnd);
+		if (obj->ip_route.mtu)
+			NLA_PUT_U32 (msg, RTAX_MTU, obj->ip_route.mtu);
 		if (lock)
 			NLA_PUT_U32 (msg, RTAX_LOCK, lock);
 
@@ -2482,10 +2778,17 @@ _nl_msg_new_route (int nlmsg_type,
 	}
 
 	/* We currently don't have need for multi-hop routes... */
-	if (   gateway
-	    && memcmp (gateway, &nm_ip_addr_zero, addr_len) != 0)
-		NLA_PUT (msg, RTA_GATEWAY, addr_len, gateway);
-	NLA_PUT_U32 (msg, RTA_OIF, ifindex);
+	if (is_v4) {
+		NLA_PUT (msg, RTA_GATEWAY, addr_len, &obj->ip4_route.gateway);
+	} else {
+		if (!IN6_IS_ADDR_UNSPECIFIED (&obj->ip6_route.gateway))
+			NLA_PUT (msg, RTA_GATEWAY, addr_len, &obj->ip6_route.gateway);
+	}
+	NLA_PUT_U32 (msg, RTA_OIF, obj->ip_route.ifindex);
+
+	if (   !is_v4
+	    && obj->ip6_route.rt_pref != NM_ICMPV6_ROUTER_PREF_MEDIUM)
+		NLA_PUT_U8 (msg, RTA_PREF, obj->ip6_route.rt_pref);
 
 	return msg;
 
@@ -2494,56 +2797,27 @@ nla_put_failure:
 	g_return_val_if_reached (NULL);
 }
 
-/*****************************************************************************/
-
-static int _support_kernel_extended_ifa_flags = -1;
-
-#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == -1))
-
-static void
-_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
-{
-	struct nlmsghdr *msg_hdr;
-
-	if (!_support_kernel_extended_ifa_flags_still_undecided ())
-		return;
-
-	msg_hdr = nlmsg_hdr (msg);
-	if (msg_hdr->nlmsg_type != RTM_NEWADDR)
-		return;
-
-	/* the extended address flags are only set for AF_INET6 */
-	if (((struct ifaddrmsg *) nlmsg_data (msg_hdr))->ifa_family != AF_INET6)
-		return;
-
-	/* see if the nl_msg contains the IFA_FLAGS attribute. If it does,
-	 * we assume, that the kernel supports extended flags, IFA_F_MANAGETEMPADDR
-	 * and IFA_F_NOPREFIXROUTE (they were added together).
-	 **/
-	_support_kernel_extended_ifa_flags = !!nlmsg_find_attr (msg_hdr, sizeof (struct ifaddrmsg), IFA_FLAGS);
-	_LOG2D ("kernel-support: extended-ifa-flags: %s", _support_kernel_extended_ifa_flags ? "detected" : "not detected");
-}
-
-static gboolean
-_support_kernel_extended_ifa_flags_get (void)
-{
-	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
-		_LOG2D ("kernel-support: extended-ifa-flags: %s", "unable to detect kernel support for handling IPv6 temporary addresses. Assume support");
-		_support_kernel_extended_ifa_flags = 1;
-	}
-	return _support_kernel_extended_ifa_flags;
-}
-
 /******************************************************************
  * 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;
-	gint *out_refresh_all_in_progess;
+	union {
+		gint *out_refresh_all_in_progess;
+		NMPObject **out_route_get;
+		gpointer out_data;
+	} response;
 } DelayedActionWaitForNlResponseData;
 
 typedef struct {
@@ -2553,11 +2827,12 @@ typedef struct {
 	guint32 nlh_seq_last_handled;
 #endif
 	guint32 nlh_seq_last_seen;
-	NMPCache *cache;
 	GIOChannel *event_channel;
 	guint event_id;
 
-	gboolean sysctl_get_warned;
+	bool pruning[_DELAYED_ACTION_IDX_REFRESH_ALL_NUM];
+
+	bool sysctl_get_warned;
 	GHashTable *sysctl_get_prev_values;
 
 	NMUdevClient *udev_client;
@@ -2578,8 +2853,6 @@ typedef struct {
 		gint is_handling;
 	} delayed_action;
 
-	GHashTable *prune_candidates;
-
 	GHashTable *wifi_data;
 } NMLinuxPlatformPrivate;
 
@@ -2599,8 +2872,15 @@ G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
 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);
 }
@@ -2611,13 +2891,6 @@ nm_linux_platform_setup (void)
 	nm_platform_setup (nm_linux_platform_new (FALSE, FALSE));
 }
 
-static void
-ASSERT_NETNS_CURRENT (NMPlatform *platform)
-{
-	nm_assert (NM_IS_LINUX_PLATFORM (platform));
-	nm_assert (NM_IN_SET (nm_platform_netns_get (platform), NULL, nmp_netns_get_current ()));
-}
-
 /*****************************************************************************/
 
 #define ASSERT_SYSCTL_ARGS(pathid, dirfd, path) \
@@ -2676,7 +2949,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	int fd, tries;
 	gssize nwrote;
-	gsize len;
+	gssize len;
 	char *actual;
 	gs_free char *actual_free = NULL;
 	int errsv;
@@ -2731,6 +3004,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	 * about to write.
 	 */
 	len = strlen (value) + 1;
+	nm_assert (len > 0);
 	if (len > 512)
 		actual = actual_free = g_malloc (len + 1);
 	else
@@ -2752,16 +3026,27 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			break;
 		}
 	}
-	if (nwrote == -1 && errsv != EEXIST) {
-		_LOGE ("sysctl: failed to set '%s' to '%s': (%d) %s",
-		       path, value, errsv, strerror (errsv));
+	if (nwrote == -1) {
+		NMLogLevel level = LOGL_ERR;
+
+		if (errsv == EEXIST) {
+			level = LOGL_DEBUG;
+		} else if (   errsv == EINVAL
+		           && nm_utils_sysctl_ip_conf_is_path (AF_INET6, path, NULL, "mtu")) {
+			/* setting the MTU can fail under regular conditions. Suppress
+			 * logging a warning. */
+			level = LOGL_DEBUG;
+		}
+
+		_NMLOG (level, "sysctl: failed to set '%s' to '%s': (%d) %s",
+		        path, value, errsv, strerror (errsv));
 	} else if (nwrote < len - 1) {
 		_LOGE ("sysctl: failed to set '%s' to '%s' after three attempts",
 		       path, value);
 	}
 
 	if (nwrote < len - 1) {
-		if (close (fd) != 0) {
+		if (nm_close (fd) != 0) {
 			if (errsv != 0)
 				errno = errsv;
 		} else if (errsv != 0)
@@ -2770,7 +3055,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 			errno = EIO;
 		return FALSE;
 	}
-	if (close (fd) != 0) {
+	if (nm_close (fd) != 0) {
 		/* errno is already properly set. */
 		return FALSE;
 	}
@@ -2804,7 +3089,7 @@ _log_dbg_sysctl_get_impl (NMPlatform *platform, const char *pathid, const char *
 	if (!priv->sysctl_get_prev_values) {
 		_nm_logging_clear_platform_logging_cache = _nm_logging_clear_platform_logging_cache_impl;
 		sysctl_clear_cache_list = g_slist_prepend (sysctl_clear_cache_list, platform);
-		priv->sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+		priv->sysctl_get_prev_values = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
 	} else
 		prev_value = g_hash_table_lookup (priv->sysctl_get_prev_values, pathid);
 
@@ -2872,20 +3157,30 @@ sysctl_get (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 
 /*****************************************************************************/
 
-static gboolean
-check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+static NMPlatformKernelSupportFlags
+check_kernel_support (NMPlatform *platform,
+                      NMPlatformKernelSupportFlags request_flags)
 {
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	NMPlatformKernelSupportFlags response = 0;
 
-	return _support_kernel_extended_ifa_flags_get ();
-}
+	nm_assert (NM_IS_LINUX_PLATFORM (platform));
 
-static gboolean
-check_support_user_ipv6ll (NMPlatform *platform)
-{
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)) {
+		if (_support_kernel_extended_ifa_flags_get ())
+			response |= NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS;
+	}
 
-	return _support_user_ipv6ll_get ();
+	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL)) {
+		if (_support_user_ipv6ll_get ())
+			response |= NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL;
+	}
+
+	if (NM_FLAGS_HAS (request_flags, NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF)) {
+		if (_support_rta_pref_get ())
+			response |= NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF;
+	}
+
+	return response;
 }
 
 static void
@@ -2896,86 +3191,6 @@ process_events (NMPlatform *platform)
 
 /*****************************************************************************/
 
-#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \
-	({ \
-		NMPCacheId _cache_id; \
-		\
-		((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \
-		                                              nmp_cache_id_init_object_type (&_cache_id, (obj_type), (visible_only)), \
-		                                              NULL)); \
-	})
-
-/*****************************************************************************/
-
-static void
-do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible)
-{
-	gboolean is_visible;
-	NMPObject obj_clone;
-	const NMPClass *klass;
-
-	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, (NMPlatformSignalChangeType) NMP_CACHE_OPS_UNCHANGED, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED));
-
-	nm_assert (obj || cache_op == NMP_CACHE_OPS_UNCHANGED);
-	nm_assert (!obj || cache_op == NMP_CACHE_OPS_REMOVED || obj == nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
-	nm_assert (!obj || cache_op != NMP_CACHE_OPS_REMOVED || obj != nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj));
-
-	ASSERT_NETNS_CURRENT (platform);
-
-	switch (cache_op) {
-	case NMP_CACHE_OPS_ADDED:
-		if (!nmp_object_is_visible (obj))
-			return;
-		break;
-	case NMP_CACHE_OPS_UPDATED:
-		is_visible = nmp_object_is_visible (obj);
-		if (!was_visible && is_visible)
-			cache_op = NMP_CACHE_OPS_ADDED;
-		else if (was_visible && !is_visible) {
-			/* This is a bit ugly. The object was visible and changed in a way that it became invisible.
-			 * We raise a removed signal, but contrary to a real 'remove', @obj is already changed to be
-			 * different from what it was when the user saw it the last time.
-			 *
-			 * The more correct solution would be to have cache_pre_hook() create a clone of the original
-			 * value before it was changed to become invisible.
-			 *
-			 * But, don't bother. Probably nobody depends on the original values and only cares about the
-			 * id properties (which are still correct).
-			 */
-			cache_op = NMP_CACHE_OPS_REMOVED;
-		} else if (!is_visible)
-			return;
-		break;
-	case NMP_CACHE_OPS_REMOVED:
-		if (!was_visible)
-			return;
-		break;
-	default:
-		g_assert (cache_op == NMP_CACHE_OPS_UNCHANGED);
-		return;
-	}
-
-	klass = NMP_OBJECT_GET_CLASS (obj);
-
-	_LOGt ("emit signal %s %s: %s",
-	       klass->signal_type,
-	       nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
-	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-
-	/* don't expose @obj directly, but clone the public fields. A signal handler might
-	 * call back into NMPlatform which could invalidate (or modify) @obj. */
-	memcpy (&obj_clone.object, &obj->object, klass->sizeof_public);
-	g_signal_emit (platform,
-	               _nm_platform_signal_id_get (klass->signal_type_id),
-	               0,
-	               (int) klass->obj_type,
-	               obj_clone.object.ifindex,
-	               &obj_clone.object,
-	               (int) cache_op);
-}
-
-/*****************************************************************************/
-
 _NM_UTILS_LOOKUP_DEFINE (static, delayed_action_refresh_from_object_type, NMPObjectType, DelayedActionType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT (DELAYED_ACTION_TYPE_NONE),
 	NM_UTILS_LOOKUP_ITEM (NMP_OBJECT_TYPE_LINK,        DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS),
@@ -3043,11 +3258,12 @@ delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data
 			gint64 timeout = data->timeout_abs_ns - nm_utils_get_monotonic_timestamp_ns ();
 			char b[255];
 
-			nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT"%s%s)",
+			nm_utils_strbuf_append (&buf, &buf_size, " (seq %u, timeout in %s%"G_GINT64_FORMAT".%09"G_GINT64_FORMAT", response-type %d%s%s)",
 			                        data->seq_number,
 			                        timeout < 0 ? "-" : "",
 			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NS_PER_SECOND,
 			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
+			                        (int) data->response_type,
 			                        data->seq_result ? ", " : "",
 			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, b, sizeof (b)) : "");
 		} else
@@ -3109,9 +3325,22 @@ delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
 		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE;
 	if (data->out_seq_result)
 		*data->out_seq_result = seq_result;
-	if (data->out_refresh_all_in_progess) {
-		nm_assert (*data->out_refresh_all_in_progess > 0);
-		*data->out_refresh_all_in_progess -= 1;
+	switch (data->response_type) {
+	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;
+		}
+		break;
+	case DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET:
+		if (data->response.out_route_get) {
+			nm_assert (!*data->response.out_route_get);
+			data->response.out_route_get = NULL;
+		}
+		break;
 	}
 
 	g_array_remove_index_fast (priv->delayed_action.list_wait_for_nl_response, idx);
@@ -3146,13 +3375,15 @@ delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
 static void
 delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	nm_auto_nmpobj NMPObject *obj_cache = NULL;
-	gboolean was_visible;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
 	NMPCacheOpsType cache_op;
 
-	cache_op = nmp_cache_update_link_master_connected (priv->cache, master_ifindex, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible);
+	cache_op = nmp_cache_update_link_master_connected (nm_platform_get_cache (platform), master_ifindex, &obj_old, &obj_new);
+	if (cache_op == NMP_CACHE_OPS_UNCHANGED)
+		return;
+	cache_on_change (platform, cache_op, obj_old, obj_new);
+	nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
 }
 
 static void
@@ -3273,7 +3504,7 @@ delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink)
 		any = TRUE;
 	priv->delayed_action.is_handling--;
 
-	cache_prune_candidates_prune (platform);
+	cache_prune_all (platform);
 
 	return any;
 }
@@ -3319,13 +3550,15 @@ static void
 delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
                                               guint32 seq_number,
                                               WaitForNlResponseResult *out_seq_result,
-                                              gint *out_refresh_all_in_progess)
+                                              DelayedActionWaitForNlResponseType response_type,
+                                              gpointer response_out_data)
 {
 	DelayedActionWaitForNlResponseData data = {
 		.seq_number = seq_number,
 		.timeout_abs_ns = nm_utils_get_monotonic_timestamp_ns () + (200 * (NM_UTILS_NS_PER_SECOND / 1000)),
 		.out_seq_result = out_seq_result,
-		.out_refresh_all_in_progess = out_refresh_all_in_progess,
+		.response_type = response_type,
+		.response.out_data = response_out_data,
 	};
 
 	delayed_action_schedule (platform,
@@ -3336,146 +3569,111 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
 /*****************************************************************************/
 
 static void
-cache_prune_candidates_record_all (NMPlatform *platform, NMPObjectType obj_type)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-
-	priv->prune_candidates = nmp_cache_lookup_all_to_hash (priv->cache,
-	                                                       nmp_cache_id_init_object_type (&cache_id, obj_type, FALSE),
-	                                                       priv->prune_candidates);
-	_LOGt ("cache-prune: record %s (now %u candidates)", nmp_class_from_type (obj_type)->obj_type_name,
-	       priv->prune_candidates ? g_hash_table_size (priv->prune_candidates) : 0);
-}
-
-static void
-cache_prune_candidates_record_one (NMPlatform *platform, NMPObject *obj)
-{
-	NMLinuxPlatformPrivate *priv;
-
-	if (!obj)
-		return;
-
-	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-
-	if (!priv->prune_candidates)
-		priv->prune_candidates = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
-
-	if (_LOGt_ENABLED () && !g_hash_table_contains (priv->prune_candidates, obj))
-		_LOGt ("cache-prune: record-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
-	g_hash_table_add (priv->prune_candidates, nmp_object_ref (obj));
-}
-
-static void
-cache_prune_candidates_drop (NMPlatform *platform, const NMPObject *obj)
+cache_prune_one_type (NMPlatform *platform, NMPObjectType obj_type)
 {
-	NMLinuxPlatformPrivate *priv;
-
-	if (!obj)
-		return;
-
-	priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	if (priv->prune_candidates) {
-		if (_LOGt_ENABLED () && g_hash_table_contains (priv->prune_candidates, obj))
-			_LOGt ("cache-prune: drop-one: %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
-		g_hash_table_remove (priv->prune_candidates, obj);
+	NMDedupMultiIter iter;
+	const NMPObject *obj;
+	NMPCacheOpsType cache_op;
+	NMPLookup lookup;
+	NMPCache *cache = nm_platform_get_cache (platform);
+
+	nmp_lookup_init_obj_type (&lookup,
+	                          obj_type);
+	nm_dedup_multi_iter_init (&iter,
+	                          nmp_cache_lookup (cache,
+	                                            &lookup));
+	while (nm_dedup_multi_iter_next (&iter)) {
+		if (iter.current->dirty) {
+			nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+			obj = iter.current->obj;
+			_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+			cache_op = nmp_cache_remove (cache, obj, TRUE, TRUE, &obj_old);
+			nm_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+			cache_on_change (platform, cache_op, obj_old, NULL);
+			nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, NULL);
+		}
 	}
 }
 
 static void
-cache_prune_candidates_prune (NMPlatform *platform)
+cache_prune_all (NMPlatform *platform)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	GHashTable *prune_candidates;
-	GHashTableIter iter;
-	const NMPObject *obj;
-	gboolean was_visible;
-	NMPCacheOpsType cache_op;
-
-	if (!priv->prune_candidates)
-		return;
-
-	prune_candidates = priv->prune_candidates;
-	priv->prune_candidates = NULL;
+	DelayedActionType iflags, action_type;
 
-	g_hash_table_iter_init (&iter, prune_candidates);
-	while (g_hash_table_iter_next (&iter, (gpointer *)&obj, NULL)) {
-		nm_auto_nmpobj NMPObject *obj_cache = NULL;
+	action_type = DELAYED_ACTION_TYPE_REFRESH_ALL;
+	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
+		bool *p = &priv->pruning[delayed_action_refresh_all_to_idx (iflags)];
 
-		_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
-		cache_op = nmp_cache_remove (priv->cache, obj, TRUE, &obj_cache, &was_visible, cache_pre_hook, platform);
-		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		if (*p) {
+			*p = FALSE;
+			cache_prune_one_type (platform, delayed_action_refresh_to_object_type (iflags));
+		}
 	}
-
-	g_hash_table_unref (prune_candidates);
 }
 
 static void
-cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+cache_on_change (NMPlatform *platform,
+                 NMPCacheOpsType cache_op,
+                 const NMPObject *obj_old,
+                 const NMPObject *obj_new)
 {
-	NMPlatform *platform = user_data;
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPClass *klass;
 	char str_buf[sizeof (_nm_utils_to_string_buffer)];
 	char str_buf2[sizeof (_nm_utils_to_string_buffer)];
+	NMPCache *cache = nm_platform_get_cache (platform);
 
-	nm_assert (old || new);
-	nm_assert (NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_REMOVED, NMP_CACHE_OPS_UPDATED));
-	nm_assert (ops_type != NMP_CACHE_OPS_ADDED   || (old == NULL && NMP_OBJECT_IS_VALID (new) && nmp_object_is_alive (new)));
-	nm_assert (ops_type != NMP_CACHE_OPS_REMOVED || (new == NULL && NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old)));
-	nm_assert (ops_type != NMP_CACHE_OPS_UPDATED || (NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old) && NMP_OBJECT_IS_VALID (new) && nmp_object_is_alive (new)));
-	nm_assert (new == NULL || old == NULL || nmp_object_id_equal (new, old));
-	nm_assert (!old || !new || NMP_OBJECT_GET_CLASS (old) == NMP_OBJECT_GET_CLASS (new));
+	ASSERT_nmp_cache_ops (cache, cache_op, obj_old, obj_new);
+	nm_assert (cache_op != NMP_CACHE_OPS_UNCHANGED);
 
-	klass = old ? NMP_OBJECT_GET_CLASS (old) : NMP_OBJECT_GET_CLASS (new);
-
-	nm_assert (klass == (new ? NMP_OBJECT_GET_CLASS (new) : NMP_OBJECT_GET_CLASS (old)));
+	klass = obj_old ? NMP_OBJECT_GET_CLASS (obj_old) : NMP_OBJECT_GET_CLASS (obj_new);
 
 	_LOGt ("update-cache-%s: %s: %s%s%s",
 	       klass->obj_type_name,
-	       (ops_type == NMP_CACHE_OPS_UPDATED
+	       (cache_op == NMP_CACHE_OPS_UPDATED
 	           ? "UPDATE"
-	           : (ops_type == NMP_CACHE_OPS_REMOVED
+	           : (cache_op == NMP_CACHE_OPS_REMOVED
 	                 ? "REMOVE"
-	                 : (ops_type == NMP_CACHE_OPS_ADDED) ? "ADD" : "???")),
-	       (ops_type != NMP_CACHE_OPS_ADDED
-	           ? nmp_object_to_string (old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))
-	           : nmp_object_to_string (new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))),
-	       (ops_type == NMP_CACHE_OPS_UPDATED) ? " -> " : "",
-	       (ops_type == NMP_CACHE_OPS_UPDATED
-	           ? nmp_object_to_string (new, NMP_OBJECT_TO_STRING_ALL, str_buf, sizeof (str_buf))
+	                 : (cache_op == NMP_CACHE_OPS_ADDED) ? "ADD" : "???")),
+	       (cache_op != NMP_CACHE_OPS_ADDED
+	           ? nmp_object_to_string (obj_old, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))
+	           : nmp_object_to_string (obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf2, sizeof (str_buf2))),
+	       (cache_op == NMP_CACHE_OPS_UPDATED) ? " -> " : "",
+	       (cache_op == NMP_CACHE_OPS_UPDATED
+	           ? nmp_object_to_string (obj_new, NMP_OBJECT_TO_STRING_ALL, str_buf, sizeof (str_buf))
 	           : ""));
 
 	switch (klass->obj_type) {
 	case NMP_OBJECT_TYPE_LINK:
 		{
 			/* check whether changing a slave link can cause a master link (bridge or bond) to go up/down */
-			if (   old
-			    && nmp_cache_link_connected_needs_toggle_by_ifindex (priv->cache, old->link.master, new, old))
-				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (old->link.master));
-			if (   new
-			    && (!old || old->link.master != new->link.master)
-			    && nmp_cache_link_connected_needs_toggle_by_ifindex (priv->cache, new->link.master, new, old))
-				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (new->link.master));
+			if (   obj_old
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (cache, obj_old->link.master, obj_new, obj_old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (obj_old->link.master));
+			if (   obj_new
+			    && (!obj_old || obj_old->link.master != obj_new->link.master)
+			    && nmp_cache_link_connected_needs_toggle_by_ifindex (cache, obj_new->link.master, obj_new, obj_old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (obj_new->link.master));
 		}
 		{
 			/* check whether we are about to change a master link that needs toggling connected state. */
-			if (   new /* <-- nonsensical, make coverity happy */
-			    && nmp_cache_link_connected_needs_toggle (cache, new, new, old))
-				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (new->link.ifindex));
+			if (   obj_new /* <-- nonsensical, make coverity happy */
+			    && nmp_cache_link_connected_needs_toggle (cache, obj_new, obj_new, obj_old))
+				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_MASTER_CONNECTED, GINT_TO_POINTER (obj_new->link.ifindex));
 		}
 		{
 			int ifindex = 0;
 
 			/* if we remove a link (from netlink), we must refresh the addresses and routes */
-			if (   ops_type == NMP_CACHE_OPS_REMOVED
-			    && old /* <-- nonsensical, make coverity happy */)
-				ifindex = old->link.ifindex;
-			else if (   ops_type == NMP_CACHE_OPS_UPDATED
-			         && old && new /* <-- nonsensical, make coverity happy */
-			         && !new->_link.netlink.is_in_netlink
-			         && new->_link.netlink.is_in_netlink != old->_link.netlink.is_in_netlink)
-				ifindex = new->link.ifindex;
+			if (   cache_op == NMP_CACHE_OPS_REMOVED
+			    && obj_old /* <-- nonsensical, make coverity happy */)
+				ifindex = obj_old->link.ifindex;
+			else if (   cache_op == NMP_CACHE_OPS_UPDATED
+			         && obj_old && obj_new /* <-- nonsensical, make coverity happy */
+			         && !obj_new->_link.netlink.is_in_netlink
+			         && obj_new->_link.netlink.is_in_netlink != obj_old->_link.netlink.is_in_netlink)
+				ifindex = obj_new->link.ifindex;
 
 			if (ifindex > 0) {
 				delayed_action_schedule (platform,
@@ -3494,40 +3692,40 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 			 * Currently, kernel misses to sent us a notification in this case
 			 * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */
 
-			if (   ops_type == NMP_CACHE_OPS_REMOVED
-			    && old /* <-- nonsensical, make coverity happy */
-			    && old->_link.netlink.is_in_netlink)
-				ifindex = old->link.ifindex;
-			else if (   ops_type == NMP_CACHE_OPS_UPDATED
-			         && old && new /* <-- nonsensical, make coverity happy */
-			         && old->_link.netlink.is_in_netlink
-			         && !new->_link.netlink.is_in_netlink)
-				ifindex = new->link.ifindex;
+			if (   cache_op == NMP_CACHE_OPS_REMOVED
+			    && obj_old /* <-- nonsensical, make coverity happy */
+			    && obj_old->_link.netlink.is_in_netlink)
+				ifindex = obj_old->link.ifindex;
+			else if (   cache_op == NMP_CACHE_OPS_UPDATED
+			         && obj_old && obj_new /* <-- nonsensical, make coverity happy */
+			         && obj_old->_link.netlink.is_in_netlink
+			         && !obj_new->_link.netlink.is_in_netlink)
+				ifindex = obj_new->link.ifindex;
 
 			if (ifindex > 0) {
-				const NMPlatformLink *const *links;
-
-				links = cache_lookup_all_objects (NMPlatformLink, platform, NMP_OBJECT_TYPE_LINK, FALSE);
-				if (links) {
-					for (; *links; links++) {
-						const NMPlatformLink *l = (*links);
-
-						if (l->parent == ifindex)
-							delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (l->ifindex));
-					}
+				NMPLookup lookup;
+				NMDedupMultiIter iter;
+				const NMPlatformLink *l;
+
+				nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
+				nmp_cache_iter_for_each_link (&iter,
+				                              nmp_cache_lookup (cache, &lookup),
+				                              &l) {
+					if (l->parent == ifindex)
+						delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (l->ifindex));
 				}
 			}
 		}
 		{
 			/* if a link goes down, we must refresh routes */
-			if (   ops_type == NMP_CACHE_OPS_UPDATED
-			    && old && new /* <-- nonsensical, make coverity happy */
-			    && old->_link.netlink.is_in_netlink
-			    && new->_link.netlink.is_in_netlink
-			    && (   (   NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_UP)
-			            && !NM_FLAGS_HAS (new->link.n_ifi_flags, IFF_UP))
-			        || (   NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_LOWER_UP)
-			            && !NM_FLAGS_HAS (new->link.n_ifi_flags, IFF_LOWER_UP)))) {
+			if (   cache_op == NMP_CACHE_OPS_UPDATED
+			    && obj_old && obj_new /* <-- nonsensical, make coverity happy */
+			    && obj_old->_link.netlink.is_in_netlink
+			    && obj_new->_link.netlink.is_in_netlink
+			    && (   (   NM_FLAGS_HAS (obj_old->link.n_ifi_flags, IFF_UP)
+			            && !NM_FLAGS_HAS (obj_new->link.n_ifi_flags, IFF_UP))
+			        || (   NM_FLAGS_HAS (obj_old->link.n_ifi_flags, IFF_LOWER_UP)
+			            && !NM_FLAGS_HAS (obj_new->link.n_ifi_flags, IFF_LOWER_UP)))) {
 				/* FIXME: I suspect that IFF_LOWER_UP must not be considered, and I
 				 * think kernel does send RTM_DELROUTE events for IPv6 routes, so
 				 * we might not need to refresh IPv6 routes. */
@@ -3537,17 +3735,17 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				                         NULL);
 			}
 		}
-		if (   NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)
-		    && (new && new->_link.netlink.is_in_netlink)
-		    && (!old || !old->_link.netlink.is_in_netlink))
+		if (   NM_IN_SET (cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED)
+		    && (obj_new && obj_new->_link.netlink.is_in_netlink)
+		    && (!obj_old || !obj_old->_link.netlink.is_in_netlink))
 		{
-			if (!new->_link.netlink.lnk) {
+			if (!obj_new->_link.netlink.lnk) {
 				/* certain link-types also come with a IFLA_INFO_DATA/lnk_data. It may happen that
 				 * kernel didn't send this notification, thus when we first learn about a link
 				 * that lacks an lnk_data we re-request it again.
 				 *
 				 * For example https://bugzilla.redhat.com/show_bug.cgi?id=1284001 */
-				switch (new->link.type) {
+				switch (obj_new->link.type) {
 				case NM_LINK_TYPE_GRE:
 				case NM_LINK_TYPE_IP6TNL:
 				case NM_LINK_TYPE_INFINIBAND:
@@ -3558,23 +3756,23 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				case NM_LINK_TYPE_VXLAN:
 					delayed_action_schedule (platform,
 					                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-					                         GINT_TO_POINTER (new->link.ifindex));
+					                         GINT_TO_POINTER (obj_new->link.ifindex));
 					break;
 				default:
 					break;
 				}
 			}
-			if (   new->link.type == NM_LINK_TYPE_VETH
-			    && new->link.parent == 0) {
+			if (   obj_new->link.type == NM_LINK_TYPE_VETH
+			    && obj_new->link.parent == 0) {
 				/* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
 				 * (https://bugzilla.redhat.com/show_bug.cgi?id=1285827).
 				 * Request it again. */
 				delayed_action_schedule (platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-				                         GINT_TO_POINTER (new->link.ifindex));
+				                         GINT_TO_POINTER (obj_new->link.ifindex));
 			}
-			if (   new->link.type == NM_LINK_TYPE_ETHERNET
-			    && new->link.addr.len == 0) {
+			if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
+			    && obj_new->link.addr.len == 0) {
 				/* Due to a kernel bug, we sometimes receive spurious NEWLINK
 				 * messages after a wifi interface has disappeared. Since the
 				 * link is not present anymore we can't determine its type and
@@ -3584,7 +3782,7 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 				 */
 				delayed_action_schedule (platform,
 				                         DELAYED_ACTION_TYPE_REFRESH_LINK,
-				                         GINT_TO_POINTER (new->link.ifindex));
+				                         GINT_TO_POINTER (obj_new->link.ifindex));
 			}
 		}
 		{
@@ -3592,14 +3790,14 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 			int ifindex1 = 0, ifindex2 = 0;
 			gboolean changed_master, changed_connected;
 
-			changed_master =    (new && new->_link.netlink.is_in_netlink && new->link.master > 0 ? new->link.master : 0)
-			                 != (old && old->_link.netlink.is_in_netlink && old->link.master > 0 ? old->link.master : 0);
-			changed_connected =    (new && new->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (new->link.n_ifi_flags, IFF_LOWER_UP) : 2)
-			                    != (old && old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (old->link.n_ifi_flags, IFF_LOWER_UP) : 2);
+			changed_master =    (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0 ? obj_new->link.master : 0)
+			                 != (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0 ? obj_old->link.master : 0);
+			changed_connected =    (obj_new && obj_new->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (obj_new->link.n_ifi_flags, IFF_LOWER_UP) : 2)
+			                    != (obj_old && obj_old->_link.netlink.is_in_netlink ? NM_FLAGS_HAS (obj_old->link.n_ifi_flags, IFF_LOWER_UP) : 2);
 
 			if (changed_master || changed_connected) {
-				ifindex1 = (old && old->_link.netlink.is_in_netlink && old->link.master > 0) ? old->link.master : 0;
-				ifindex2 = (new && new->_link.netlink.is_in_netlink && new->link.master > 0) ? new->link.master : 0;
+				ifindex1 = (obj_old && obj_old->_link.netlink.is_in_netlink && obj_old->link.master > 0) ? obj_old->link.master : 0;
+				ifindex2 = (obj_new && obj_new->_link.netlink.is_in_netlink && obj_new->link.master > 0) ? obj_new->link.master : 0;
 
 				if (ifindex1 > 0)
 					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex1));
@@ -3607,29 +3805,13 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 					delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex2));
 			}
 		}
-		{
-			if (   (       (ops_type == NMP_CACHE_OPS_REMOVED)
-			        || (   (ops_type == NMP_CACHE_OPS_UPDATED)
-			            && new
-			            && !new->_link.netlink.is_in_netlink))
-			    && old
-			    && old->_link.netlink.is_in_netlink
-			    && old->link.master) {
-				/* sometimes we receive a wrong RTM_DELLINK message when unslaving
-				 * a device. Refetch the link again to check whether the device
-				 * is really gone.
-				 *
-				 * https://bugzilla.redhat.com/show_bug.cgi?id=1285719#c2 */
-				delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (old->link.ifindex));
-			}
-		}
 		break;
 	case NMP_OBJECT_TYPE_IP4_ADDRESS:
 	case NMP_OBJECT_TYPE_IP6_ADDRESS:
 		{
 			/* Address deletion is sometimes accompanied by route deletion. We need to
 			 * check all routes belonging to the same interface. */
-			if (ops_type == NMP_CACHE_OPS_REMOVED) {
+			if (cache_op == NMP_CACHE_OPS_REMOVED) {
 				delayed_action_schedule (platform,
 				                         (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS)
 				                             ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
@@ -3643,69 +3825,113 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 	}
 }
 
-static void
-cache_post (NMPlatform *platform,
-            struct nlmsghdr *msghdr,
-            NMPCacheOpsType cache_op,
-            NMPObject *obj,
-            NMPObject *obj_cache)
+/*****************************************************************************/
+
+static guint32
+_nlh_seq_next_get (NMLinuxPlatformPrivate *priv)
+{
+	/* generate a new sequence number, but skip zero. */
+	return priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
+}
+
+/**
+ * _nl_send_nlmsghdr:
+ * @platform:
+ * @nlhdr:
+ * @out_seq_result:
+ * @response_type:
+ * @response_out_data:
+ *
+ * Returns: 0 on success or a negative errno. Beware, it's an errno, not nlerror.
+ */
+static int
+_nl_send_nlmsghdr (NMPlatform *platform,
+                   struct nlmsghdr *nlhdr,
+                   WaitForNlResponseResult *out_seq_result,
+                   DelayedActionWaitForNlResponseType response_type,
+                   gpointer response_out_data)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	guint32 seq;
+	int nle;
 
-	nm_assert (NMP_OBJECT_IS_VALID (obj));
-	nm_assert (!obj_cache || nmp_object_id_equal (obj, obj_cache));
-
-	if (msghdr->nlmsg_type == RTM_NEWROUTE) {
-		DelayedActionType action_type;
-
-		action_type = NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_IP4_ROUTE
-		                  ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-		                  : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES;
-		if (   !delayed_action_refresh_all_in_progress (platform, action_type)
-		    && nmp_cache_find_other_route_for_same_destination (priv->cache, obj)) {
-			/* via `iproute route change` the user can update an existing route which effectively
-			 * means that a new object (with a different ID) comes into existance, replacing the
-			 * old on. In other words, as the ID of the object changes, we really see a new
-			 * object with the old one deleted.
-			 * However, kernel decides not to send a RTM_DELROUTE event for that.
-			 *
-			 * To hack around that, check if the update leaves us with multiple routes for the
-			 * same network/plen,metric part. In that case, we cannot do better then requesting
-			 * all routes anew, which sucks.
-			 *
-			 * One mitigation to avoid a dump is only to request a new dump, if we are not in
-			 * the middle of an ongoing dump (delayed_action_refresh_all_in_progress). */
-			delayed_action_schedule (platform, action_type, NULL);
+	nm_assert (nlhdr);
+
+	seq = _nlh_seq_next_get (priv);
+	nlhdr->nlmsg_seq = seq;
+
+	{
+		struct sockaddr_nl nladdr = {
+			.nl_family = AF_NETLINK,
+		};
+		struct iovec iov = {
+			.iov_base = nlhdr,
+			.iov_len = nlhdr->nlmsg_len
+		};
+		struct msghdr msg = {
+			.msg_name = &nladdr,
+			.msg_namelen = sizeof(nladdr),
+			.msg_iov = &iov,
+			.msg_iovlen = 1,
+		};
+		int try_count;
+
+		if (!nlhdr->nlmsg_pid)
+			nlhdr->nlmsg_pid = nl_socket_get_local_port (priv->nlh);
+		nlhdr->nlmsg_flags |= (NLM_F_REQUEST | NLM_F_ACK);
+
+		try_count = 0;
+again:
+		nle = sendmsg (nl_socket_get_fd (priv->nlh), &msg, 0);
+		if (nle < 0) {
+			nle = errno;
+			if (nle == EINTR && try_count++ < 100)
+				goto again;
+			_LOGD ("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)", g_strerror (nle), nle);
+			return -nle;
 		}
 	}
-}
 
-/*****************************************************************************/
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
+	                                              response_type, response_out_data);
+	return 0;
+}
 
+/**
+ * _nl_send_nlmsg:
+ * @platform:
+ * @nlmsg:
+ * @out_seq_result:
+ * @response_type:
+ * @response_out_data:
+ *
+ * Returns: 0 on success, or a negative libnl3 error code (beware, it's not an errno).
+ */
 static int
-_nl_send_auto_with_seq (NMPlatform *platform,
-                        struct nl_msg *nlmsg,
-                        WaitForNlResponseResult *out_seq_result,
-                        gint *out_refresh_all_in_progess)
+_nl_send_nlmsg (NMPlatform *platform,
+                struct nl_msg *nlmsg,
+                WaitForNlResponseResult *out_seq_result,
+                DelayedActionWaitForNlResponseType response_type,
+                gpointer response_out_data)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	struct nlmsghdr *nlhdr;
 	guint32 seq;
 	int nle;
 
-	/* complete the message with a sequence number (ensuring it's not zero). */
-	seq = priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
-
-	nlmsg_hdr (nlmsg)->nlmsg_seq = seq;
+	nlhdr = nlmsg_hdr (nlmsg);
+	seq = _nlh_seq_next_get (priv);
+	nlhdr->nlmsg_seq = seq;
 
 	nle = nl_send_auto (priv->nlh, nlmsg);
+	if (nle < 0) {
+		_LOGD ("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nl_geterror (nle), nle);
+		return nle;
+	}
 
-	if (nle >= 0) {
-		nle = 0;
-		delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result, out_refresh_all_in_progess);
-	} else
-		_LOGD ("netlink: send: failed sending message: %s (%d)", nl_geterror (nle), nle);
-
-	return nle;
+	delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result,
+	                                              response_type, response_out_data);
+	return 0;
 }
 
 static void
@@ -3713,17 +3939,23 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	int nle;
 
 	if (name && !name[0])
 		name = NULL;
 
 	g_return_if_fail (ifindex > 0 || name);
 
-	_LOGD ("do-request-link: %d %s", ifindex, name ? name : "");
+	_LOGD ("do-request-link: %d %s", ifindex, name ?: "");
 
 	if (ifindex > 0) {
-		cache_prune_candidates_record_one (platform,
-		                                   (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex));
+		const NMDedupMultiEntry *entry;
+
+		entry = nmp_cache_lookup_entry_link (nm_platform_get_cache (platform), ifindex);
+		if (entry) {
+			priv->pruning[DELAYED_ACTION_IDX_REFRESH_ALL_LINKS] = TRUE;
+			nm_dedup_multi_entry_set_dirty (entry, TRUE);
+		}
 	}
 
 	event_handler_read_netlink (platform, FALSE);
@@ -3734,8 +3966,15 @@ do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const cha
 	                          name,
 	                          0,
 	                          0);
-	if (nlmsg)
-		_nl_send_auto_with_seq (platform, nlmsg, NULL, NULL);
+	if (nlmsg) {
+		nle = _nl_send_nlmsg (platform, nlmsg, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+		if (nle < 0) {
+			_LOGE ("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
+			       ifindex, name ?: "",
+			       nl_geterror (nle), -nle);
+			return;
+		}
+	}
 }
 
 static void
@@ -3755,7 +3994,9 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 	action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
-		cache_prune_candidates_record_all (platform, delayed_action_refresh_to_object_type (iflags));
+		priv->pruning[delayed_action_refresh_all_to_idx (iflags)] = TRUE;
+		nmp_cache_dirty_set_all (nm_platform_get_cache (platform),
+		                         delayed_action_refresh_to_object_type (iflags));
 	}
 
 	FOR_EACH_DELAYED_ACTION (iflags, action_type) {
@@ -3795,7 +4036,7 @@ do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType actio
 		if (nle < 0)
 			continue;
 
-		if (_nl_send_auto_with_seq (platform, nlmsg, NULL, out_refresh_all_in_progess) < 0) {
+		if (_nl_send_nlmsg (platform, nlmsg, 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;
 		}
@@ -3825,13 +4066,12 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number)
 		for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
 			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
 
-			if (data->seq_number == priv->nlh_seq_last_seen) {
-				if (data->out_refresh_all_in_progess) {
-					nm_assert (*data->out_refresh_all_in_progess > 0);
-					*data->out_refresh_all_in_progess -= 1;
-					data->out_refresh_all_in_progess = NULL;
-					break;
-				}
+			if (   data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS
+			    && data->response.out_refresh_all_in_progess
+			    && data->seq_number == priv->nlh_seq_last_seen) {
+				*data->response.out_refresh_all_in_progess -= 1;
+				data->response.out_refresh_all_in_progess = NULL;
+				break;
 			}
 		}
 	}
@@ -3879,18 +4119,19 @@ event_seq_check (NMPlatform *platform, guint32 seq_number, WaitForNlResponseResu
 static void
 event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_events)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	NMLinuxPlatformPrivate *priv;
 	nm_auto_nmpobj NMPObject *obj = NULL;
-	nm_auto_nmpobj NMPObject *obj_cache = NULL;
 	NMPCacheOpsType cache_op;
 	struct nlmsghdr *msghdr;
-	char buf_nlmsg_type[16];
+	char buf_nlmsghdr[400];
 	gboolean id_only = FALSE;
-	gboolean was_visible;
+	NMPCache *cache = nm_platform_get_cache (platform);
+	gboolean is_dump;
 
 	msghdr = nlmsg_hdr (msg);
 
-	if (_support_kernel_extended_ifa_flags_still_undecided () && msghdr->nlmsg_type == RTM_NEWADDR)
+	if (   _support_kernel_extended_ifa_flags_still_undecided ()
+	    && msghdr->nlmsg_type == RTM_NEWADDR)
 		_support_kernel_extended_ifa_flags_detect (msg);
 
 	if (!handle_events)
@@ -3902,156 +4143,144 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event
 		id_only = TRUE;
 	}
 
-	obj = nmp_object_new_from_nl (platform, priv->cache, msg, id_only);
+	obj = nmp_object_new_from_nl (platform, cache, msg, id_only);
 	if (!obj) {
-		_LOGT ("event-notification: %s, seq %u: ignore",
-		       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
-		       msghdr->nlmsg_seq);
+		_LOGT ("event-notification: %s: ignore",
+		       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 		return;
 	}
 
-	_LOGT ("event-notification: %s, seq %u: %s",
-	       _nl_nlmsg_type_to_str (msghdr->nlmsg_type, buf_nlmsg_type, sizeof (buf_nlmsg_type)),
-	       msghdr->nlmsg_seq, nmp_object_to_string (obj,
-	           id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-
 	switch (msghdr->nlmsg_type) {
-
-	case RTM_NEWLINK:
 	case RTM_NEWADDR:
+	case RTM_NEWLINK:
 	case RTM_NEWROUTE:
-	case RTM_GETLINK:
-		cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
-
-		cache_post (platform, msghdr, cache_op, obj, obj_cache);
-
-		do_emit_signal (platform, obj_cache, cache_op, was_visible);
-		break;
-
-	case RTM_DELLINK:
-	case RTM_DELADDR:
-	case RTM_DELROUTE:
-		cache_op = nmp_cache_remove_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
-		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		is_dump = delayed_action_refresh_all_in_progress (platform,
+		                                                  delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)));
 		break;
-
 	default:
-		break;
+		is_dump = FALSE;
 	}
 
-	cache_prune_candidates_drop (platform, obj_cache);
-}
-
-/*****************************************************************************/
-
-static const NMPObject *
-cache_lookup_link (NMPlatform *platform, int ifindex)
-{
-	const NMPObject *obj_cache;
-
-	obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
-	if (!nmp_object_is_visible (obj_cache))
-		return NULL;
-
-	return obj_cache;
-}
-
-const NMPlatformObject *const*
-nm_linux_platform_lookup (NMPlatform *platform, const NMPCacheId *cache_id, guint *out_len)
-{
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), NULL);
-	g_return_val_if_fail (cache_id, NULL);
+	_LOGT ("event-notification: %s%s: %s",
+	       _nl_nlmsghdr_to_str (msghdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)),
+	       is_dump ? ", in-dump" : "",
+	       nmp_object_to_string (obj,
+	                             id_only ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
+	                             NULL, 0));
 
-	return nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                               cache_id, out_len);
-}
+	{
+		nm_auto_nmpobj const NMPObject *obj_old = NULL;
+		nm_auto_nmpobj const NMPObject *obj_new = NULL;
+
+		switch (msghdr->nlmsg_type) {
+
+		case RTM_NEWLINK:
+		case RTM_NEWADDR:
+		case RTM_GETLINK:
+			cache_op = nmp_cache_update_netlink (cache, obj, is_dump, &obj_old, &obj_new);
+			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+				cache_on_change (platform, cache_op, obj_old, obj_new);
+				nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+			}
+			break;
 
-static GArray *
-link_get_all (NMPlatform *platform)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
+		case RTM_NEWROUTE: {
+			nm_auto_nmpobj const NMPObject *obj_replace = NULL;
+			gboolean resync_required = FALSE;
+			gboolean only_dirty = FALSE;
+
+			if (obj->ip_route.rt_cloned) {
+				/* a cloned route might be a response for RTM_GETROUTE. Check, whether it is. */
+				nm_assert (!nmp_object_is_alive (obj));
+				priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+				if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+					guint i;
+
+					nm_assert (priv->delayed_action.list_wait_for_nl_response->len > 0);
+					for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
+						DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+						if (   data->response_type == DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET
+						    && data->response.out_route_get) {
+							nm_assert (!*data->response.out_route_get);
+							if (data->seq_number == nlmsg_hdr (msg)->nlmsg_seq) {
+								*data->response.out_route_get = nmp_object_clone (obj, FALSE);
+								data->response.out_route_get = NULL;
+								break;
+							}
+						}
+					}
+				}
+			}
 
-	return nmp_cache_lookup_multi_to_array (priv->cache,
-	                                        NMP_OBJECT_TYPE_LINK,
-	                                        nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, TRUE));
-}
+			cache_op = nmp_cache_update_netlink_route (cache,
+			                                           obj,
+			                                           is_dump,
+			                                           msghdr->nlmsg_flags,
+			                                           &obj_old,
+			                                           &obj_new,
+			                                           &obj_replace,
+			                                           &resync_required);
+			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+				if (obj_replace) {
+					const NMDedupMultiEntry *entry_replace;
+
+					/* we found an object that is to be replaced by the RTM_NEWROUTE message.
+					 * While we invoke the signal, the platform cache might change and invalidate
+					 * the findings. Mitigate that (for the most part), by marking the entry as
+					 * dirty and only delete @obj_replace if it is still dirty afterwards.
+					 *
+					 * Yes, there is a tiny tiny chance for still getting it wrong. But in practice,
+					 * the signal handlers do not cause to call the platform again, so the cache
+					 * is not really changing. -- if they would, it would anyway be dangerous to overflow
+					 * the stack and it's not ensured that the processing of netlink messages is
+					 * reentrant (maybe it is).
+					 */
+					entry_replace = nmp_cache_lookup_entry (cache, obj_replace);
+					nm_assert (entry_replace && entry_replace->obj == obj_replace);
+					nm_dedup_multi_entry_set_dirty (entry_replace, TRUE);
+					only_dirty = TRUE;
+				}
+				cache_on_change (platform, cache_op, obj_old, obj_new);
+				nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+			}
 
-static const NMPlatformLink *
-_nm_platform_link_get (NMPlatform *platform, int ifindex)
-{
-	const NMPObject *obj;
+			if (obj_replace) {
+				/* the RTM_NEWROUTE message indicates that another route was replaced.
+				 * Remove it now. */
+				cache_op = nmp_cache_remove (cache, obj_replace, TRUE, only_dirty, NULL);
+				if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+					nm_assert (cache_op == NMP_CACHE_OPS_REMOVED);
+					cache_on_change (platform, cache_op, obj_replace, NULL);
+					nm_platform_cache_update_emit_signal (platform, cache_op, obj_replace, NULL);
+				}
+			}
 
-	obj = cache_lookup_link (platform, ifindex);
-	return obj ? &obj->link : NULL;
-}
+			if (resync_required) {
+				/* we'd like to avoid such resyncs as they are expensive and we should only rely on the
+				 * netlink events. This needs investigation. */
+				_LOGT ("schedule resync of routes after RTM_NEWROUTE");
+				delayed_action_schedule (platform,
+				                         delayed_action_refresh_from_object_type (NMP_OBJECT_GET_TYPE (obj)),
+				                         NULL);
+			}
+			break;
+		}
 
-static const NMPlatformLink *
-_nm_platform_link_get_by_ifname (NMPlatform *platform,
-                                 const char *ifname)
-{
-	const NMPObject *obj = NULL;
+		case RTM_DELLINK:
+		case RTM_DELADDR:
+		case RTM_DELROUTE:
+			cache_op = nmp_cache_remove_netlink (cache, obj, &obj_old, &obj_new);
+			if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
+				cache_on_change (platform, cache_op, obj_old, obj_new);
+				nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
+			}
+			break;
 
-	if (ifname && *ifname) {
-		obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-		                                  0, ifname, TRUE, NM_LINK_TYPE_NONE, NULL, NULL);
+		default:
+			break;
+		}
 	}
-	return obj ? &obj->link : NULL;
-}
-
-struct _nm_platform_link_get_by_address_data {
-	gconstpointer address;
-	guint8 length;
-};
-
-static gboolean
-_nm_platform_link_get_by_address_match_link (const NMPObject *obj, struct _nm_platform_link_get_by_address_data *d)
-{
-	return obj->link.addr.len == d->length && !memcmp (obj->link.addr.data, d->address, d->length);
-}
-
-static const NMPlatformLink *
-_nm_platform_link_get_by_address (NMPlatform *platform,
-                                  gconstpointer address,
-                                  size_t length)
-{
-	const NMPObject *obj;
-	struct _nm_platform_link_get_by_address_data d = {
-		.address = address,
-		.length = length,
-	};
-
-	if (length <= 0 || length > NM_UTILS_HWADDR_LEN_MAX)
-		return NULL;
-	if (!address)
-		return NULL;
-
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                                  0, NULL, TRUE, NM_LINK_TYPE_NONE,
-	                                  (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, &d);
-	return obj ? &obj->link : NULL;
-}
-
-/*****************************************************************************/
-
-static const NMPObject *
-link_get_lnk (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
-{
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
-
-	if (!obj)
-		return NULL;
-
-	NM_SET_OUT (out_link, &obj->link);
-
-	if (!obj->_link.netlink.lnk)
-		return NULL;
-	if (   link_type != NM_LINK_TYPE_NONE
-	    && (   link_type != obj->link.type
-	        || link_type != NMP_OBJECT_GET_CLASS (obj->_link.netlink.lnk)->lnk_link_type))
-		return NULL;
-
-	return obj->_link.netlink.lnk;
 }
 
 /*****************************************************************************/
@@ -4063,34 +4292,21 @@ do_add_link_with_lookup (NMPlatform *platform,
                          struct nl_msg *nlmsg,
                          const NMPlatformLink **out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPObject *obj = NULL;
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
 	char s_buf[256];
+	NMPCache *cache = nm_platform_get_cache (platform);
 
 	event_handler_read_netlink (platform, FALSE);
 
-	if (nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) {
-		/* hm, a link with such a name already exists. Try reloading first. */
-		do_request_link (platform, 0, name);
-
-		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL);
-		if (obj) {
-			_LOGE ("do-add-link[%s/%s]: link already exists: %s",
-			       name,
-			       nm_link_type_to_string (link_type),
-			       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-			return FALSE;
-		}
-	}
-
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
 		       name,
 		       nm_link_type_to_string (link_type),
 		       nl_geterror (nle), -nle);
+		NM_SET_OUT (out_link, NULL);
 		return FALSE;
 	}
 
@@ -4100,35 +4316,29 @@ do_add_link_with_lookup (NMPlatform *platform,
 
 	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
 	            ? LOGL_DEBUG
-	            : LOGL_ERR,
+	            : LOGL_WARN,
 	        "do-add-link[%s/%s]: %s",
 	        name,
 	        nm_link_type_to_string (link_type),
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
-	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
-		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL);
-
-	if (!obj) {
-		/* either kernel signaled failure, or it signaled success and the link object
-		 * is not (yet) in the cache. Try to reload it... */
-		do_request_link (platform, 0, name);
-		obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE, link_type, NULL, NULL);
+	if (out_link) {
+		obj = nmp_cache_lookup_link_full (cache, 0, name, FALSE, link_type, NULL, NULL);
+		*out_link = NMP_OBJECT_CAST_LINK (obj);
 	}
 
-	if (out_link)
-		*out_link = obj ? &obj->link : NULL;
-	return !!obj;
+	return seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
 }
 
-static gboolean
-do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
+static NMPlatformError
+do_add_addrroute (NMPlatform *platform,
+                  const NMPObject *obj_id,
+                  struct nl_msg *nlmsg,
+                  gboolean suppress_netlink_failure)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
 	char s_buf[256];
-	const NMPObject *obj;
 
 	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
 	                      NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS,
@@ -4136,72 +4346,69 @@ do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 		       nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
 		       nl_geterror (nle), -nle);
-		return FALSE;
+		return NM_PLATFORM_ERROR_NETLINK;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
 
 	nm_assert (seq_result);
 
-	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	_NMLOG ((   seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	         || (   suppress_netlink_failure
+	             && seq_result < 0))
 	            ? LOGL_DEBUG
-	            : LOGL_ERR,
+	            : LOGL_WARN,
 	        "do-add-%s[%s]: %s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)));
 
-	/* In rare cases, the object is not yet ready as we received the ACK from
-	 * kernel. Need to refetch.
-	 *
-	 * We want to safe the expensive refetch, thus we look first into the cache
-	 * whether the object exists.
-	 *
-	 * FIXME: if the object already existed previously, we might not notice a
-	 * missing update. It's not clear how to fix that reliably without refechting
-	 * all the time. */
-	obj = nmp_cache_lookup_obj (priv->cache, obj_id);
-	if (!obj) {
-		do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
-		obj = nmp_cache_lookup_obj (priv->cache, obj_id);
+	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
+		/* In rare cases, the object is not yet ready as we received the ACK from
+		 * kernel. Need to refetch.
+		 *
+		 * We want to safe the expensive refetch, thus we look first into the cache
+		 * whether the object exists.
+		 *
+		 * rh#1484434 */
+		if (!nmp_cache_lookup_obj (nm_platform_get_cache (platform), obj_id))
+			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
 	}
 
-	/* Adding is only successful, if kernel reported success *and* we have the
-	 * expected object in cache afterwards. */
-	return obj && seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+	return wait_for_nl_response_to_plerr (seq_result);
 }
 
 static gboolean
 do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
 	char s_buf[256];
-	gboolean success = TRUE;
+	gboolean success;
 	const char *log_detail = "";
 
 	event_handler_read_netlink (platform, FALSE);
 
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
 		_LOGE ("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
 		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 		       nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
 		       nl_geterror (nle), -nle);
-		goto out;
+		return FALSE;
 	}
 
 	delayed_action_handle_all (platform, FALSE);
 
 	nm_assert (seq_result);
 
+	success = TRUE;
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
 		/* ok */
 	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT))
@@ -4216,42 +4423,60 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 	else
 		success = FALSE;
 
-	_NMLOG (success ? LOGL_DEBUG : LOGL_ERR,
+	_NMLOG (success ? LOGL_DEBUG : LOGL_WARN,
 	        "do-delete-%s[%s]: %s%s",
 	        NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
 	        nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0),
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
 	        log_detail);
 
-out:
-	if (!nmp_cache_lookup_obj (priv->cache, obj_id))
-		return TRUE;
+	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
+		/* In rare cases, the object is still there after we receive the ACK from
+		 * kernel. Need to refetch.
+		 *
+		 * We want to safe the expensive refetch, thus we look first into the cache
+		 * whether the object exists.
+		 *
+		 * rh#1484434 */
+		if (nmp_cache_lookup_obj (nm_platform_get_cache (platform), obj_id))
+			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
+	}
 
-	/* such an object still exists in the cache. To be sure, refetch it (and
-	 * hope it's gone) */
-	do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id));
-	return !nmp_cache_lookup_obj (priv->cache, obj_id);
+	return success;
 }
 
-static WaitForNlResponseResult
-do_change_link_request (NMPlatform *platform,
-                        int ifindex,
-                        struct nl_msg *nlmsg)
+static NMPlatformError
+do_change_link (NMPlatform *platform,
+                ChangeLinkType change_link_type,
+                int ifindex,
+                struct nl_msg *nlmsg,
+                const ChangeLinkData *data)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
-	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	char s_buf[256];
+	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
+	NMLogLevel log_level = LOGL_DEBUG;
+	const char *log_result = "failure";
+	const char *log_detail = "";
+	gs_free char *log_detail_free = NULL;
+	const NMPObject *obj_cache;
 
-	if (!nm_platform_netns_push (platform, &netns))
-		return WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+	if (!nm_platform_netns_push (platform, &netns)) {
+		log_level = LOGL_ERR;
+		log_detail = ", failure to change network namespace";
+		goto out;
+	}
 
 retry:
-	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result, NULL);
+	nle = _nl_send_nlmsg (platform, nlmsg, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
 	if (nle < 0) {
-		_LOGE ("do-change-link[%d]: failure sending netlink request \"%s\" (%d)",
-		       ifindex,
-		       nl_geterror (nle), -nle);
-		return WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+		log_level = LOGL_ERR;
+		log_detail_free = g_strdup_printf (", failure sending netlink request: %s (%d)",
+		                                   nl_geterror (nle), -nle);
+		log_detail = log_detail_free;
+		goto out;
 	}
 
 	/* always refetch the link after changing it. There seems to be issues
@@ -4267,18 +4492,6 @@ retry:
 		nlmsg_hdr (nlmsg)->nlmsg_type = RTM_SETLINK;
 		goto retry;
 	}
-	return seq_result;
-}
-
-static NMPlatformError
-do_change_link_result (NMPlatform *platform,
-                       int ifindex,
-                       WaitForNlResponseResult seq_result)
-{
-	char s_buf[256];
-	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
-	NMLogLevel log_level = LOGL_DEBUG;
-	const char *log_result = "failure", *log_detail = "";
 
 	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
 		log_result = "success";
@@ -4287,38 +4500,43 @@ do_change_link_result (NMPlatform *platform,
 	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT)) {
 		log_detail = ", firmware not found";
 		result = NM_PLATFORM_ERROR_NO_FIRMWARE;
+	} else if (   NM_IN_SET (-((int) seq_result), ERANGE)
+	           && change_link_type == CHANGE_LINK_TYPE_SET_MTU) {
+		log_detail = ", setting MTU to requested size is not possible";
+		result = NM_PLATFORM_ERROR_CANT_SET_MTU;
+	} else if (   NM_IN_SET (-((int) seq_result), ENFILE)
+	           && change_link_type == CHANGE_LINK_TYPE_SET_ADDRESS
+	           && (obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex))
+	           && obj_cache->link.addr.len == data->set_address.length
+	           && memcmp (obj_cache->link.addr.data, data->set_address.address, data->set_address.length) == 0) {
+		/* workaround ENFILE which may be wrongly returned (bgo #770456).
+		 * If the MAC address is as expected, assume success? */
+		log_result = "success";
+		log_detail = " (assume success changing address)";
+		result = NM_PLATFORM_ERROR_SUCCESS;
 	} else if (NM_IN_SET (-((int) seq_result), ENODEV)) {
 		log_level = LOGL_DEBUG;
 		result = NM_PLATFORM_ERROR_NOT_FOUND;
 	} else {
-		log_level = LOGL_ERR;
+		log_level = LOGL_WARN;
 		result = NM_PLATFORM_ERROR_UNSPECIFIED;
 	}
+
+out:
 	_NMLOG (log_level,
 	        "do-change-link[%d]: %s changing link: %s%s",
 	        ifindex,
 	        log_result,
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
 	        log_detail);
-
 	return result;
 }
 
-static NMPlatformError
-do_change_link (NMPlatform *platform,
-                int ifindex,
-                struct nl_msg *nlmsg)
-{
-	WaitForNlResponseResult seq_result;
-
-	seq_result = do_change_link_request (platform, ifindex, nlmsg);
-	return do_change_link_result (platform, ifindex, seq_result);
-}
-
 static gboolean
 link_add (NMPlatform *platform,
           const char *name,
           NMLinkType type,
+          const char *veth_peer,
           const void *address,
           size_t address_len,
           const NMPlatformLink **out_link)
@@ -4337,9 +4555,6 @@ link_add (NMPlatform *platform,
 			(void) nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
 	}
 
-	_LOGD ("link: add link '%s' of type '%s' (%d)",
-	       name, nm_link_type_to_string (type), (int) type);
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          NLM_F_CREATE | NLM_F_EXCL,
 	                          0,
@@ -4352,7 +4567,7 @@ link_add (NMPlatform *platform,
 	if (address && address_len)
 		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
-	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type))
+	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type, veth_peer))
 		return FALSE;
 
 	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
@@ -4364,11 +4579,10 @@ static gboolean
 link_delete (NMPlatform *platform, int ifindex)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	NMPObject obj_id;
 	const NMPObject *obj;
 
-	obj = nmp_cache_lookup_link (priv->cache, ifindex);
+	obj = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
 	if (!obj || !obj->_link.netlink.is_in_netlink)
 		return FALSE;
 
@@ -4383,56 +4597,11 @@ link_delete (NMPlatform *platform, int ifindex)
 	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
-static const char *
-link_get_type_name (NMPlatform *platform, int ifindex)
-{
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
-
-	if (!obj)
-		return NULL;
-
-	if (obj->link.type != NM_LINK_TYPE_UNKNOWN) {
-		/* We could detect the @link_type. In this case the function returns
-		 * our internel module names, which differs from rtnl_link_get_type():
-		 *   - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib")
-		 *   - NM_LINK_TYPE_TAP (gives "tap", instead of "tun").
-		 * Note that this functions is only used by NMDeviceGeneric to
-		 * set type_description. */
-		return nm_link_type_to_string (obj->link.type);
-	}
-	/* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */
-	return obj->link.kind ?: "unknown";
-}
-
-static gboolean
-link_get_unmanaged (NMPlatform *platform, int ifindex, gboolean *unmanaged)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPObject *link;
-	struct udev_device *udevice = NULL;
-	const char *uproperty;
-
-	link = nmp_cache_lookup_link (priv->cache, ifindex);
-	if (!link)
-		return FALSE;
-
-	udevice = link->_link.udev.device;
-	if (!udevice)
-		return FALSE;
-
-	uproperty = udev_device_get_property_value (udevice, "NM_UNMANAGED");
-	if (!uproperty)
-		return FALSE;
-
-	*unmanaged = nm_udev_utils_property_as_boolean (uproperty);
-	return TRUE;
-}
-
 static gboolean
 link_refresh (NMPlatform *platform, int ifindex)
 {
 	do_request_link (platform, ifindex, NULL);
-	return !!cache_lookup_link (platform, ifindex);
+	return !!nm_platform_link_get_obj (platform, ifindex, TRUE);
 }
 
 static gboolean
@@ -4454,7 +4623,7 @@ link_set_netns (NMPlatform *platform,
 		return FALSE;
 
 	NLA_PUT (nlmsg, IFLA_NET_NS_FD, 4, &netns_fd);
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	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);
@@ -4484,7 +4653,7 @@ link_change_flags (NMPlatform *platform,
 	                          flags_set);
 	if (!nlmsg)
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
-	return do_change_link (platform, ifindex, nlmsg);
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
 
 static gboolean
@@ -4519,7 +4688,7 @@ link_set_noarp (NMPlatform *platform, int ifindex)
 static const char *
 link_get_udi (NMPlatform *platform, int ifindex)
 {
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
+	const NMPObject *obj = nm_platform_link_get_obj (platform, ifindex, TRUE);
 
 	if (   !obj
 	    || !obj->_link.netlink.is_in_netlink
@@ -4528,35 +4697,21 @@ link_get_udi (NMPlatform *platform, int ifindex)
 	return udev_device_get_syspath (obj->_link.udev.device);
 }
 
-static struct udev_device *
-link_get_udev_device (NMPlatform *platform, int ifindex)
-{
-	const NMPObject *obj_cache;
-
-	/* we don't use cache_lookup_link() because this would return NULL
-	 * if the link is not visible in libnl. For link_get_udev_device()
-	 * we want to return whatever we have, even if the link itself
-	 * appears invisible via other platform functions. */
-
-	obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
-	return obj_cache ? obj_cache->_link.udev.device : NULL;
-}
-
 static NMPlatformError
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64;
 
+	_LOGD ("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s",
+	       ifindex,
+	       nm_platform_link_inet6_addrgenmode2str (mode, NULL, 0));
+
 	if (!_support_user_ipv6ll_get ()) {
 		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
 		return NM_PLATFORM_ERROR_OPNOTSUPP;
 	}
 
-	_LOGD ("link: change %d: user-ipv6ll: set IPv6 address generation mode to %s",
-	       ifindex,
-	       nm_platform_link_inet6_addrgenmode2str (mode, NULL, 0));
-
 	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
 	                          0,
 	                          ifindex,
@@ -4567,7 +4722,7 @@ link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enable
 	    || !_nl_msg_new_link_set_afspec (nlmsg, mode, NULL))
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
 
-	return do_change_link (platform, ifindex, nlmsg);
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL);
 }
 
 static gboolean
@@ -4582,7 +4737,7 @@ link_set_token (NMPlatform *platform, int ifindex, NMUtilsIPv6IfaceId iid)
 	if (!nlmsg || !_nl_msg_new_link_set_afspec (nlmsg, -1, &iid))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -4606,7 +4761,7 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	const NMPObject *obj;
 
-	obj = cache_lookup_link (platform, ifindex);
+	obj = nm_platform_link_get_obj (platform, ifindex, TRUE);
 
 	/* Only ARPHRD_ETHER links can possibly support VLANs. */
 	if (!obj || obj->link.arptype != ARPHRD_ETHER)
@@ -4647,8 +4802,12 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	gs_free char *mac = NULL;
-	WaitForNlResponseResult seq_result;
-	char s_buf[256];
+	const ChangeLinkData d = {
+		.set_address = {
+			.address = address,
+			.length = length,
+		},
+	};
 
 	if (!address || !length)
 		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
@@ -4668,32 +4827,32 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size
 
 	NLA_PUT (nlmsg, IFLA_ADDRESS, length, address);
 
-	seq_result = do_change_link_request (platform, ifindex, nlmsg);
+	return do_change_link (platform, CHANGE_LINK_TYPE_SET_ADDRESS, ifindex, nlmsg, &d);
+nla_put_failure:
+	g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+}
 
-	if (NM_IN_SET (-((int) seq_result), ENFILE)) {
-		const NMPObject *obj_cache;
+static NMPlatformError
+link_set_name (NMPlatform *platform, int ifindex, const char *name)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-		/* workaround ENFILE which may be wrongly returned (bgo #770456).
-		 * If the MAC address is as expected, assume success? */
+	_LOGD ("link: change %d: name: %s", ifindex, name);
 
-		obj_cache = nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex);
-		if (   obj_cache
-		    && obj_cache->link.addr.len == length
-		    && memcmp (obj_cache->link.addr.data, address, length) == 0) {
-			_NMLOG (LOGL_DEBUG,
-			        "do-change-link[%d]: %s changing link: %s%s",
-			        ifindex,
-			        "success",
-			        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
-			        " (assume success changing address)");
-			return NM_PLATFORM_ERROR_SUCCESS;
-		}
-	}
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
 
-	return do_change_link_result (platform, ifindex, seq_result);
+	NLA_PUT (nlmsg, IFLA_IFNAME, strlen (name) + 1, name);
 
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 nla_put_failure:
-	g_return_val_if_reached (NM_PLATFORM_ERROR_UNSPECIFIED);
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -4710,7 +4869,7 @@ link_get_permanent_address (NMPlatform *platform,
 	return nmp_utils_ethtool_get_permanent_address (ifindex, buf, length);
 }
 
-static gboolean
+static NMPlatformError
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -4728,7 +4887,7 @@ link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 
 	NLA_PUT_U32 (nlmsg, IFLA_MTU, mtu);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_SET_MTU, ifindex, nlmsg, NULL);
 nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
@@ -5359,7 +5518,6 @@ link_vlan_change (NMPlatform *platform,
                   const NMVlanQosMapping *egress_map,
                   gsize n_egress_map)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPObject *obj_cache;
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	const NMPObjectLnkVlan *lnk;
@@ -5371,7 +5529,7 @@ link_vlan_change (NMPlatform *platform,
 	char s_ingress[256];
 	char s_egress[256];
 
-	obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
+	obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (platform), ifindex);
 	if (   !obj_cache
 	    || !obj_cache->_link.netlink.is_in_netlink) {
 		_LOGD ("link: change %d: %s: link does not exist", ifindex, "vlan");
@@ -5437,7 +5595,7 @@ link_vlan_change (NMPlatform *platform,
 	                                            new_n_egress_map))
 		g_return_val_if_reached (FALSE);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_change_link (platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static int
@@ -5449,9 +5607,6 @@ tun_add (NMPlatform *platform, const char *name, gboolean tap,
 	struct ifreq ifr = { };
 	int fd;
 
-	_LOGD ("link: add %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
-	       tap ? "tap" : "tun", name, owner, group);
-
 	fd = open ("/dev/net/tun", O_RDWR | O_CLOEXEC);
 	if (fd < 0)
 		return FALSE;
@@ -5467,30 +5622,30 @@ tun_add (NMPlatform *platform, const char *name, gboolean tap,
 		ifr.ifr_flags |= NM_IFF_MULTI_QUEUE;
 
 	if (ioctl (fd, TUNSETIFF, &ifr)) {
-		close (fd);
+		nm_close (fd);
 		return FALSE;
 	}
 
 	if (owner >= 0 && owner < G_MAXINT32) {
 		if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) {
-			close (fd);
+			nm_close (fd);
 			return FALSE;
 		}
 	}
 
 	if (group >= 0 && group < G_MAXINT32) {
 		if (ioctl (fd, TUNSETGROUP, (gid_t) group)) {
-			close (fd);
+			nm_close (fd);
 			return FALSE;
 		}
 	}
 
 	if (ioctl (fd, TUNSETPERSIST, 1)) {
-		close (fd);
+		nm_close (fd);
 		return FALSE;
 	}
 	do_request_link (platform, 0, name);
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
 	                                  0, name, FALSE,
 	                                  tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN,
 	                                  NULL, NULL);
@@ -5520,7 +5675,7 @@ link_enslave (NMPlatform *platform, int master, int slave)
 
 	NLA_PUT_U32 (nlmsg, IFLA_MASTER, master);
 
-	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+	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);
 }
@@ -5540,7 +5695,6 @@ _infiniband_partition_action (NMPlatform *platform,
                               int p_key,
                               const NMPlatformLink **out_link)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	nm_auto_close int dirfd = -1;
 	char ifname_parent[IFNAMSIZ];
 	const NMPObject *obj;
@@ -5576,7 +5730,7 @@ _infiniband_partition_action (NMPlatform *platform,
 	if (action == INFINIBAND_ACTION_DELETE_CHILD)
 		return TRUE;
 
-	obj = nmp_cache_lookup_link_full (priv->cache, 0, name, FALSE,
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform), 0, name, FALSE,
 	                                  NM_LINK_TYPE_INFINIBAND, NULL, NULL);
 	if (out_link)
 		*out_link = obj ? &obj->link : NULL;
@@ -5714,16 +5868,15 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru
 static gboolean
 link_can_assume (NMPlatform *platform, int ifindex)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-	const NMPlatformObject *const *objs;
-	guint i, len;
-	const NMPObject *link;
+	NMPLookup lookup;
+	const NMPObject *link, *o;
+	NMDedupMultiIter iter;
+	NMPCache *cache = nm_platform_get_cache (platform);
 
 	if (ifindex <= 0)
 		return FALSE;
 
-	link = cache_lookup_link (platform, ifindex);
+	link = nm_platform_link_get_obj (platform, ifindex, TRUE);
 	if (!link)
 		return FALSE;
 
@@ -5733,21 +5886,21 @@ link_can_assume (NMPlatform *platform, int ifindex)
 	if (link->link.master > 0)
 		return TRUE;
 
-	if (nmp_cache_lookup_multi (priv->cache,
-	                            nmp_cache_id_init_addrroute_visible_by_ifindex (&cache_id, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex),
-	                            NULL))
+	nmp_lookup_init_addrroute (&lookup,
+	                           NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                           ifindex);
+	if (nmp_cache_lookup (cache, &lookup))
 		return TRUE;
 
-	objs = nmp_cache_lookup_multi (priv->cache,
-	                               nmp_cache_id_init_addrroute_visible_by_ifindex (&cache_id, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex),
-	                               &len);
-	if (objs) {
-		for (i = 0; i < len; i++) {
-			const NMPlatformIP6Address *a = (NMPlatformIP6Address *) objs[i];
-
-			if (!IN6_IS_ADDR_LINKLOCAL (&a->address))
-				return TRUE;
-		}
+	nmp_lookup_init_addrroute (&lookup,
+	                           NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                           ifindex);
+	nmp_cache_iter_for_each (&iter,
+	                         nmp_cache_lookup (cache, &lookup),
+	                         &o) {
+		nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_TYPE_IP6_ADDRESS);
+		if (!IN6_IS_ADDR_LINKLOCAL (&o->ip6_address.address))
+			return TRUE;
 	}
 	return FALSE;
 }
@@ -5822,33 +5975,6 @@ link_get_driver_info (NMPlatform *platform,
 
 /*****************************************************************************/
 
-static GArray *
-ipx_address_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-
-	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS));
-
-	return nmp_cache_lookup_multi_to_array (priv->cache,
-	                                        obj_type,
-	                                        nmp_cache_id_init_addrroute_visible_by_ifindex (&cache_id,
-	                                                                                        obj_type,
-	                                                                                        ifindex));
-}
-
-static GArray *
-ip4_address_get_all (NMPlatform *platform, int ifindex)
-{
-	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP4_ADDRESS);
-}
-
-static GArray *
-ip6_address_get_all (NMPlatform *platform, int ifindex)
-{
-	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
-}
-
 static gboolean
 ip4_address_add (NMPlatform *platform,
                  int ifindex,
@@ -5877,7 +6003,7 @@ ip4_address_add (NMPlatform *platform,
 	                             label);
 
 	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
+	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -5906,8 +6032,8 @@ ip6_address_add (NMPlatform *platform,
 	                             preferred,
 	                             NULL);
 
-	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
+	return do_add_addrroute (platform, &obj_id, nlmsg, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -5956,303 +6082,139 @@ ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, gui
 	if (!nlmsg)
 		g_return_val_if_reached (FALSE);
 
-	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr);
 	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
-static const NMPlatformIP4Address *
-ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
-
-	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_address);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip4_address;
-	return NULL;
-}
-
-static const NMPlatformIP6Address *
-ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
-
-	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip6_address;
-	return NULL;
-}
-
 /*****************************************************************************/
 
-static GArray *
-ipx_route_get_all (NMPlatform *platform, int ifindex, NMPObjectType obj_type, NMPlatformGetRouteFlags flags)
+static NMPlatformError
+ip_route_add (NMPlatform *platform,
+              NMPNlmFlags flags,
+              int addr_family,
+              const NMPlatformIPRoute *route)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPCacheId cache_id;
-	const NMPlatformIPRoute *const* routes;
-	GArray *array;
-	const NMPClass *klass;
-	gboolean with_rtprot_kernel;
-	guint i, len;
-
-	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
-
-	if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT))
-		flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT;
-
-	klass = nmp_class_from_type (obj_type);
-
-	nmp_cache_id_init_routes_visible (&cache_id,
-	                                  obj_type,
-	                                  NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT),
-	                                  NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT),
-	                                  ifindex);
-
-	routes = (const NMPlatformIPRoute *const*) nmp_cache_lookup_multi (priv->cache, &cache_id, &len);
-
-	array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len);
-
-	with_rtprot_kernel = NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_RTPROT_KERNEL);
-	for (i = 0; i < len; i++) {
-		nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (routes[i])) == klass);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj;
 
-		if (   with_rtprot_kernel
-		    || routes[i]->rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL)
-			g_array_append_vals (array, routes[i], 1);
+	switch (addr_family) {
+	case AF_INET:
+		nmp_object_stackinit (&obj, NMP_OBJECT_TYPE_IP4_ROUTE, (const NMPlatformObject *) route);
+		break;
+	case AF_INET6:
+		nmp_object_stackinit (&obj, NMP_OBJECT_TYPE_IP6_ROUTE, (const NMPlatformObject *) route);
+		break;
+	default:
+		nm_assert_not_reached ();
 	}
-	return array;
-}
 
-static GArray *
-ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
-{
-	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP4_ROUTE, flags);
-}
+	nm_platform_ip_route_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (&obj));
 
-static GArray *
-ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags)
-{
-	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ROUTE, flags);
-}
-
-static guint32
-ip_route_get_lock_flag (NMPlatformIPRoute *route)
-{
-	return   (((guint32) route->lock_window) << RTAX_WINDOW)
-	       | (((guint32) route->lock_cwnd) << RTAX_CWND)
-	       | (((guint32) route->lock_initcwnd) << RTAX_INITCWND)
-	       | (((guint32) route->lock_initrwnd) << RTAX_INITRWND)
-	       | (((guint32) route->lock_mtu) << RTAX_MTU);
-}
-
-static gboolean
-ip4_route_add (NMPlatform *platform, const NMPlatformIP4Route *route)
-{
-	NMPObject obj_id;
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	in_addr_t network;
-
-	network = nm_utils_ip4_address_clear_host_address (route->network, route->plen);
-
-	/* FIXME: take the scope from route into account */
-	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
-	                           NLM_F_CREATE | NLM_F_REPLACE,
-	                           AF_INET,
-	                           route->ifindex,
-	                           route->rt_source,
-	                           route->gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK,
-	                           &network,
-	                           route->plen,
-	                           &route->gateway,
-	                           route->metric,
-	                           route->mss,
-	                           route->pref_src ? &route->pref_src : NULL,
-	                           NULL,
-	                           0,
-	                           route->tos,
-	                           route->window,
-	                           route->cwnd,
-	                           route->initcwnd,
-	                           route->initrwnd,
-	                           route->mtu,
-	                           ip_route_get_lock_flag ((NMPlatformIPRoute *) route));
-
-	nmp_object_stackinit_id_ip4_route (&obj_id, route->ifindex, network, route->plen, route->metric);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
+	nlmsg = _nl_msg_new_route (RTM_NEWROUTE, flags & NMP_NLM_FLAG_FMASK, &obj);
+	if (!nlmsg)
+		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+	return do_add_addrroute (platform,
+	                         &obj,
+	                         nlmsg,
+	                         NM_FLAGS_HAS (flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE));
 }
 
 static gboolean
-ip6_route_add (NMPlatform *platform, const NMPlatformIP6Route *route)
+ip_route_delete (NMPlatform *platform,
+                 const NMPObject *obj)
 {
-	NMPObject obj_id;
+	nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL;
 	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	struct in6_addr network;
-
-	nm_utils_ip6_address_clear_host_address (&network, &route->network, route->plen);
-
-	/* FIXME: take the scope from route into account */
-	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
-	                           NLM_F_CREATE | NLM_F_REPLACE,
-	                           AF_INET6,
-	                           route->ifindex,
-	                           route->rt_source,
-	                           IN6_IS_ADDR_UNSPECIFIED (&route->gateway) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
-	                           &network,
-	                           route->plen,
-	                           &route->gateway,
-	                           route->metric,
-	                           route->mss,
-	                           !IN6_IS_ADDR_UNSPECIFIED (&route->pref_src) ? &route->pref_src : NULL,
-	                           !IN6_IS_ADDR_UNSPECIFIED (&route->src) ? &route->src : NULL,
-	                           route->src_plen,
-	                           route->tos,
-	                           route->window,
-	                           route->cwnd,
-	                           route->initcwnd,
-	                           route->initrwnd,
-	                           route->mtu,
-	                           ip_route_get_lock_flag ((NMPlatformIPRoute *) route));
-
-	nmp_object_stackinit_id_ip6_route (&obj_id, route->ifindex, &network, route->plen, route->metric);
-	return do_add_addrroute (platform, &obj_id, nlmsg);
-}
 
-static gboolean
-ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	NMPObject obj_id;
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                 NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	network = nm_utils_ip4_address_clear_host_address (network, plen);
+	if (!NMP_OBJECT_IS_STACKINIT (obj))
+		obj_keep_alive = nmp_object_ref (obj);
 
-	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
+	nlmsg = _nl_msg_new_route (RTM_DELROUTE, 0, obj);
+	if (!nlmsg)
+		g_return_val_if_reached (FALSE);
+	return do_delete_object (platform, obj, nlmsg);
+}
 
-	if (metric == 0) {
-		/* Deleting an IPv4 route with metric 0 does not only delete an exectly matching route.
-		 * If no route with metric 0 exists, it might delete another route to the same destination.
-		 * For nm_platform_ip4_route_delete() we don't want this semantic.
-		 *
-		 * Instead, make sure that we have the most recent state and process all
-		 * delayed actions (including re-reading data from netlink). */
-		delayed_action_handle_all (platform, TRUE);
-
-		if (!nmp_cache_lookup_obj (priv->cache, &obj_id)) {
-			/* hmm... we are about to delete an IP4 route with metric 0. We must only
-			 * send the delete request if such a route really exists. Above we refreshed
-			 * the platform cache, still no such route exists.
-			 *
-			 * Be extra careful and reload the routes. We must be sure that such a
-			 * route doesn't exists, because when we add an IPv4 address, we immediately
-			 * afterwards try to delete the kernel-added device route with metric 0.
-			 * It might be, that we didn't yet get the notification about that route.
-			 *
-			 * FIXME: once our ip4_address_add() is sure that upon return we have
-			 * the latest state from in the platform cache, we might save this
-			 * additional expensive cache-resync. */
-			do_request_one_type (platform, NMP_OBJECT_TYPE_IP4_ROUTE);
+/*****************************************************************************/
 
-			if (!nmp_cache_lookup_obj (priv->cache, &obj_id))
-				return TRUE;
-		}
-	}
+static NMPlatformError
+ip_route_get (NMPlatform *platform,
+              int addr_family,
+              gconstpointer address,
+              int oif_ifindex,
+              NMPObject **out_route)
+{
+	const gboolean is_v4 = (addr_family == AF_INET);
+	const int addr_len = is_v4 ? 4 : 16;
+	int try_count = 0;
+	WaitForNlResponseResult seq_result;
+	int nle;
+	nm_auto_nlmsg NMPObject *route = NULL;
 
-	nlmsg = _nl_msg_new_route (RTM_DELROUTE,
-	                           0,
-	                           AF_INET,
-	                           ifindex,
-	                           NM_IP_CONFIG_SOURCE_UNKNOWN,
-	                           RT_SCOPE_NOWHERE,
-	                           &network,
-	                           plen,
-	                           NULL,
-	                           metric,
-	                           0,
-	                           NULL,
-	                           NULL,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0);
-	if (!nlmsg)
-		return FALSE;
+	nm_assert (NM_IS_LINUX_PLATFORM (platform));
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert (address);
+
+	do {
+		struct {
+			struct nlmsghdr n;
+			struct rtmsg r;
+			char buf[64];
+		} req = {
+			.n.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg)),
+			.n.nlmsg_flags = NLM_F_REQUEST,
+			.n.nlmsg_type = RTM_GETROUTE,
+			.r.rtm_family = addr_family,
+			.r.rtm_tos = 0,
+			.r.rtm_dst_len = is_v4 ? 32 : 128,
+			.r.rtm_flags = 0x1000 /* RTM_F_LOOKUP_TABLE */,
+		};
 
-	return do_delete_object (platform, &obj_id, nlmsg);
-}
+		g_clear_pointer (&route, nmp_object_unref);
 
-static gboolean
-ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
-{
-	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
-	NMPObject obj_id;
+		if (!_nl_addattr_l (&req.n, sizeof (req), RTA_DST, address, addr_len))
+			nm_assert_not_reached ();
 
-	metric = nm_utils_ip6_route_metric_normalize (metric);
-
-	nm_utils_ip6_address_clear_host_address (&network, &network, plen);
-
-	nlmsg = _nl_msg_new_route (RTM_DELROUTE,
-	                           0,
-	                           AF_INET6,
-	                           ifindex,
-	                           NM_IP_CONFIG_SOURCE_UNKNOWN,
-	                           RT_SCOPE_NOWHERE,
-	                           &network,
-	                           plen,
-	                           NULL,
-	                           metric,
-	                           0,
-	                           NULL,
-	                           NULL,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0,
-	                           0);
-	if (!nlmsg)
-		return FALSE;
+		if (oif_ifindex > 0) {
+			gint32 ii = oif_ifindex;
 
-	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
+			if (!_nl_addattr_l (&req.n, sizeof (req), RTA_OIF, &ii, sizeof (ii)))
+				nm_assert_not_reached ();
+		}
 
-	return do_delete_object (platform, &obj_id, nlmsg);
-}
+		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+		nle = _nl_send_nlmsghdr (platform, &req.n, &seq_result, DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET, &route);
+		if (nle < 0) {
+			_LOGE ("get-route: failure sending netlink request \"%s\" (%d)",
+			       g_strerror (-nle), -nle);
+			return NM_PLATFORM_ERROR_UNSPECIFIED;
+		}
 
-static const NMPlatformIP4Route *
-ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
+		delayed_action_handle_all (platform, FALSE);
 
-	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip4_route;
-	return NULL;
-}
+		/* Retry, if we failed due to a cache resync. That can happen when the netlink
+		 * socket fills up and we lost the response. */
+	} while (   seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+	         && ++try_count < 10);
 
-static const NMPlatformIP6Route *
-ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
-{
-	NMPObject obj_id;
-	const NMPObject *obj;
+	if (seq_result < 0) {
+		/* negative seq_result is an errno from kernel. Map it to negative
+		 * NMPlatformError (which are also errno). */
+		return (NMPlatformError) seq_result;
+	}
 
-	metric = nm_utils_ip6_route_metric_normalize (metric);
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
+		if (route) {
+			NM_SET_OUT (out_route, g_steal_pointer (&route));
+			return NM_PLATFORM_ERROR_SUCCESS;
+		}
+		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
+	}
 
-	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_id);
-	if (nmp_object_is_visible (obj))
-		return &obj->ip6_route;
-	return NULL;
+	return NM_PLATFORM_ERROR_UNSPECIFIED;
 }
 
 /*****************************************************************************/
@@ -6351,6 +6313,7 @@ continue_reading:
 		gboolean abort_parsing = FALSE;
 		gboolean process_valid_msg = FALSE;
 		guint32 seq_number;
+		char buf_nlmsghdr[400];
 
 		msg = nlmsg_convert (hdr);
 		if (!msg) {
@@ -6370,8 +6333,8 @@ continue_reading:
 			goto stop;
 		}
 
-		_LOGt ("netlink: recvmsg: new message type %d, seq %u",
-		       hdr->nlmsg_type, hdr->nlmsg_seq);
+		_LOGt ("netlink: recvmsg: new message %s",
+		       _nl_nlmsghdr_to_str (hdr, buf_nlmsghdr, sizeof (buf_nlmsghdr)));
 
 		if (creds)
 			nlmsg_set_creds (msg, creds);
@@ -6512,7 +6475,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 
 			nle = event_handler_recvmsgs (platform, TRUE);
 
-			if (nle < 0)
+			if (nle < 0) {
 				switch (nle) {
 				case -NLE_AGAIN:
 					goto after_read;
@@ -6543,6 +6506,7 @@ event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 				default:
 					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
 					break;
+				}
 			}
 			any = TRUE;
 		}
@@ -6615,19 +6579,19 @@ cache_update_link_udev (NMPlatform *platform,
                         int ifindex,
                         struct udev_device *udevice)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	nm_auto_nmpobj NMPObject *obj_cache = NULL;
-	gboolean was_visible;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+	nm_auto_nmpobj const NMPObject *obj_new = NULL;
 	NMPCacheOpsType cache_op;
 
-	cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udevice, &obj_cache, &was_visible, cache_pre_hook, platform);
+	cache_op = nmp_cache_update_link_udev (nm_platform_get_cache (platform), ifindex, udevice, &obj_old, &obj_new);
 
 	if (cache_op != NMP_CACHE_OPS_UNCHANGED) {
 		nm_auto_pop_netns NMPNetns *netns = NULL;
 
+		cache_on_change (platform, cache_op, obj_old, obj_new);
 		if (!nm_platform_netns_push (platform, &netns))
 			return;
-		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		nm_platform_cache_update_emit_signal (platform, cache_op, obj_old, obj_new);
 	}
 }
 
@@ -6683,7 +6647,7 @@ udev_device_removed (NMPlatform *platform,
 	if (ifindex <= 0) {
 		const NMPObject *obj;
 
-		obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+		obj = nmp_cache_lookup_link_full (nm_platform_get_cache (platform),
 		                                  0, NULL, FALSE, NM_LINK_TYPE_NONE, _udev_device_removed_match_link, udevice);
 		if (obj)
 			ifindex = obj->link.ifindex;
@@ -6735,22 +6699,12 @@ static void
 nm_linux_platform_init (NMLinuxPlatform *self)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (self);
-	gboolean use_udev;
-
-	use_udev =    nmp_netns_is_initial ()
-	           && access ("/sys", W_OK) == 0;
 
 	priv->nlh_seq_next = 1;
-	priv->cache = nmp_cache_new (use_udev);
 	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 (NULL, NULL, NULL, (GDestroyNotify) wifi_utils_deinit);
-
-	if (use_udev) {
-		priv->udev_client = nm_udev_client_new ((const char *[]) { "net", NULL },
-		                                        handle_udev_event, self);
-	}
 }
 
 static void
@@ -6764,6 +6718,11 @@ constructed (GObject *_object)
 
 	nm_assert (!platform->_netns || platform->_netns == nmp_netns_get_current ());
 
+	if (nm_platform_get_use_udev (platform)) {
+		priv->udev_client = nm_udev_client_new ((const char *[]) { "net", NULL },
+		                                        handle_udev_event, platform);
+	}
+
 	_LOGD ("create (%s netns, %s, %s udev)",
 	       !platform->_netns ? "ignore" : "use",
 	       !platform->_netns && nmp_netns_is_initial ()
@@ -6773,7 +6732,7 @@ constructed (GObject *_object)
 	                : nm_sprintf_bufa (100, "in netns[%p]%s",
 	                                   nmp_netns_get_current (),
 	                                   nmp_netns_get_current () == nmp_netns_get_initial () ? "/main" : "")),
-	       nmp_cache_use_udev_get (priv->cache) ? "use" : "no");
+	       nm_platform_get_use_udev (platform) ? "use" : "no");
 
 	priv->nlh = nl_socket_alloc ();
 	g_assert (priv->nlh);
@@ -6874,8 +6833,6 @@ dispose (GObject *object)
 	g_ptr_array_set_size (priv->delayed_action.list_master_connected, 0);
 	g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
 
-	g_clear_pointer (&priv->prune_candidates, g_hash_table_unref);
-
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
 }
 
@@ -6884,8 +6841,6 @@ finalize (GObject *object)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (object);
 
-	nmp_cache_free (priv->cache);
-
 	g_ptr_array_unref (priv->delayed_action.list_master_connected);
 	g_ptr_array_unref (priv->delayed_action.list_refresh_link);
 	g_array_unref (priv->delayed_action.list_wait_for_nl_response);
@@ -6919,16 +6874,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->sysctl_set = sysctl_set;
 	platform_class->sysctl_get = sysctl_get;
 
-	platform_class->link_get = _nm_platform_link_get;
-	platform_class->link_get_by_ifname = _nm_platform_link_get_by_ifname;
-	platform_class->link_get_by_address = _nm_platform_link_get_by_address;
-	platform_class->link_get_all = link_get_all;
 	platform_class->link_add = link_add;
 	platform_class->link_delete = link_delete;
-	platform_class->link_get_type_name = link_get_type_name;
-	platform_class->link_get_unmanaged = link_get_unmanaged;
-
-	platform_class->link_get_lnk = link_get_lnk;
 
 	platform_class->link_refresh = link_refresh;
 
@@ -6940,7 +6887,6 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_noarp = link_set_noarp;
 
 	platform_class->link_get_udi = link_get_udi;
-	platform_class->link_get_udev_device = link_get_udev_device;
 
 	platform_class->link_set_user_ipv6ll_enabled = link_set_user_ipv6ll_enabled;
 	platform_class->link_set_token = link_set_token;
@@ -6948,6 +6894,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_set_address = link_set_address;
 	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_get_physical_port_id = link_get_physical_port_id;
@@ -6995,26 +6942,16 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_ipip_add = link_ipip_add;
 	platform_class->link_sit_add = link_sit_add;
 
-	platform_class->ip4_address_get = ip4_address_get;
-	platform_class->ip6_address_get = ip6_address_get;
-	platform_class->ip4_address_get_all = ip4_address_get_all;
-	platform_class->ip6_address_get_all = ip6_address_get_all;
 	platform_class->ip4_address_add = ip4_address_add;
 	platform_class->ip6_address_add = ip6_address_add;
 	platform_class->ip4_address_delete = ip4_address_delete;
 	platform_class->ip6_address_delete = ip6_address_delete;
 
-	platform_class->ip4_route_get = ip4_route_get;
-	platform_class->ip6_route_get = ip6_route_get;
-	platform_class->ip4_route_get_all = ip4_route_get_all;
-	platform_class->ip6_route_get_all = ip6_route_get_all;
-	platform_class->ip4_route_add = ip4_route_add;
-	platform_class->ip6_route_add = ip6_route_add;
-	platform_class->ip4_route_delete = ip4_route_delete;
-	platform_class->ip6_route_delete = ip6_route_delete;
-
-	platform_class->check_support_kernel_extended_ifa_flags = check_support_kernel_extended_ifa_flags;
-	platform_class->check_support_user_ipv6ll = check_support_user_ipv6ll;
+	platform_class->ip_route_add = ip_route_add;
+	platform_class->ip_route_delete = ip_route_delete;
+	platform_class->ip_route_get = ip_route_get;
+
+	platform_class->check_kernel_support = check_kernel_support;
 
 	platform_class->process_events = process_events;
 }
diff --git a/src/platform/nm-linux-platform.h b/src/platform/nm-linux-platform.h
index 6b66ea69..bff6c00c 100644
--- a/src/platform/nm-linux-platform.h
+++ b/src/platform/nm-linux-platform.h
@@ -39,10 +39,4 @@ NMPlatform *nm_linux_platform_new (gboolean log_with_ptr, gboolean netns_support
 
 void nm_linux_platform_setup (void);
 
-struct _NMPCacheId;
-
-const NMPlatformObject *const *nm_linux_platform_lookup (NMPlatform *platform,
-                                                         const struct _NMPCacheId *cache_id,
-                                                         guint *out_len);
-
 #endif /* __NETWORKMANAGER_LINUX_PLATFORM_H__ */
diff --git a/src/platform/nm-platform-private.h b/src/platform/nm-platform-private.h
new file mode 100644
index 00000000..b6c94baa
--- /dev/null
+++ b/src/platform/nm-platform-private.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* nm-platform.c - Handle runtime kernel networking configuration
+ *
+ * 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, 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) 2017 Red Hat, Inc.
+ */
+
+#ifndef __NM_PLATFORM_PRIVATE_H__
+#define __NM_PLATFORM_PRIVATE_H__
+
+#include "nm-platform.h"
+#include "nmp-object.h"
+
+NMPCache *nm_platform_get_cache (NMPlatform *self);
+
+#define NMTST_ASSERT_PLATFORM_NETNS_CURRENT(platform) \
+	G_STMT_START { \
+		NMPlatform *_platform = (platform); \
+		\
+		nm_assert (NM_IS_PLATFORM (_platform)); \
+		nm_assert (NM_IN_SET (nm_platform_netns_get (_platform), NULL, nmp_netns_get_current ())); \
+	} G_STMT_END
+
+void nm_platform_cache_update_emit_signal (NMPlatform *platform,
+                                           NMPCacheOpsType cache_op,
+                                           const NMPObject *obj_old,
+                                           const NMPObject *obj_new);
+
+#endif /* __NM_PLATFORM_PRIVATE_H__ */
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index a244ff39..ffc4b395 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -32,12 +32,16 @@
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/rtnetlink.h>
+#include <libudev.h>
 
 #include "nm-utils.h"
 #include "nm-core-internal.h"
+#include "nm-utils/nm-dedup-multi.h"
+#include "nm-utils/nm-udev-utils.h"
 
 #include "nm-core-utils.h"
 #include "nm-platform-utils.h"
+#include "nm-platform-private.h"
 #include "nmp-object.h"
 #include "nmp-netns.h"
 
@@ -79,12 +83,23 @@ static guint signals[_NM_PLATFORM_SIGNAL_ID_LAST] = { 0 };
 enum {
 	PROP_0,
 	PROP_NETNS_SUPPORT,
+	PROP_USE_UDEV,
 	PROP_LOG_WITH_PTR,
 	LAST_PROP,
 };
 
 typedef struct _NMPlatformPrivate {
+	bool use_udev:1;
 	bool log_with_ptr:1;
+
+	NMPlatformKernelSupportFlags support_checked;
+	NMPlatformKernelSupportFlags support_present;
+
+	guint ip4_dev_route_blacklist_check_id;
+	guint ip4_dev_route_blacklist_gc_timeout_id;
+	GHashTable *ip4_dev_route_blacklist_hash;
+	NMDedupMultiIndex *multi_idx;
+	NMPCache *cache;
 } NMPlatformPrivate;
 
 G_DEFINE_TYPE (NMPlatform, nm_platform, G_TYPE_OBJECT)
@@ -93,6 +108,16 @@ G_DEFINE_TYPE (NMPlatform, nm_platform, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
+static void _ip4_dev_route_blacklist_schedule (NMPlatform *self);
+
+/*****************************************************************************/
+
+gboolean
+nm_platform_get_use_udev (NMPlatform *self)
+{
+	return NM_PLATFORM_GET_PRIVATE (self)->use_udev;
+}
+
 gboolean
 nm_platform_get_log_with_ptr (NMPlatform *self)
 {
@@ -193,17 +218,18 @@ nm_platform_get ()
 
 /*****************************************************************************/
 
-/**
- * _nm_platform_error_to_string:
- * @error_code: the error code to stringify.
- *
- * Returns: A string representation of the error.
- * For negative numbers, this function interprets
- * the code as -errno.
- * For invalid (positive) numbers it returns NULL.
- */
-NM_UTILS_LOOKUP_STR_DEFINE (_nm_platform_error_to_string, NMPlatformError,
-	NM_UTILS_LOOKUP_DEFAULT ( val < 0 ? g_strerror (- ((int) val)) : NULL ),
+NMDedupMultiIndex *
+nm_platform_get_multi_idx (NMPlatform *self)
+{
+	g_return_val_if_fail (NM_IS_PLATFORM (self), NULL);
+
+	return NM_PLATFORM_GET_PRIVATE (self)->multi_idx;
+}
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nm_platform_error_to_string, NMPlatformError,
+	NM_UTILS_LOOKUP_DEFAULT (NULL),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_SUCCESS,     "success"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_BUG,         "bug"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_UNSPECIFIED, "unspecified"),
@@ -213,35 +239,101 @@ NM_UTILS_LOOKUP_STR_DEFINE (_nm_platform_error_to_string, NMPlatformError,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NOT_SLAVE,   "not-slave"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NO_FIRMWARE, "no-firmware"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_OPNOTSUPP,   "not-supported"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_NETLINK,     "netlink"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_PLATFORM_ERROR_CANT_SET_MTU, "cant-set-mtu"),
 	NM_UTILS_LOOKUP_ITEM_IGNORE (_NM_PLATFORM_ERROR_MININT),
 );
 
-/*****************************************************************************/
-
-gboolean
-nm_platform_check_support_kernel_extended_ifa_flags (NMPlatform *self)
+/**
+ * nm_platform_error_to_string:
+ * @error_code: the error code to stringify.
+ * @buf: (allow-none): buffer
+ * @buf_len: size of buffer
+ *
+ * Returns: A string representation of the error.
+ * For negative numbers, this function interprets
+ * the code as -errno.
+ * For invalid (positive) numbers it returns NULL.
+ */
+const char *
+nm_platform_error_to_string (NMPlatformError error_code, char *buf, gsize buf_len)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	const char *s;
 
-	if (!klass->check_support_kernel_extended_ifa_flags)
-		return FALSE;
+	if (error_code < 0) {
+		int errsv = -((int) error_code);
 
-	return klass->check_support_kernel_extended_ifa_flags (self);
+		nm_utils_to_string_buffer_init (&buf, &buf_len);
+		g_snprintf (buf, buf_len, "%s (%d)", g_strerror (errsv), errsv);
+	} else {
+		s = _nm_platform_error_to_string (error_code);
+		if (s) {
+			if (!buf)
+				return s;
+			g_strlcpy (buf, s, buf_len);
+		} else {
+			nm_utils_to_string_buffer_init (&buf, &buf_len);
+			g_snprintf (buf, buf_len, "(%d)", (int) error_code);
+		}
+	}
+
+	return buf;
 }
 
-gboolean
-nm_platform_check_support_user_ipv6ll (NMPlatform *self)
+NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_nmp_nlm_flag_to_string_lookup, NMPNlmFlags,
+	NM_UTILS_LOOKUP_DEFAULT (NULL),
+	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_ADD,     "add"),
+	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_CHANGE,  "change"),
+	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_REPLACE, "replace"),
+	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_PREPEND, "prepend"),
+	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_APPEND,  "append"),
+	NM_UTILS_LOOKUP_ITEM (NMP_NLM_FLAG_TEST,    "test"),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_NLM_FLAG_F_APPEND),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_NLM_FLAG_FMASK),
+	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE),
+);
+
+#define _nmp_nlm_flag_to_string(flags) \
+	({ \
+		NMPNlmFlags _flags = (flags); \
+		\
+		_nmp_nlm_flag_to_string_lookup (flags) ?: nm_sprintf_bufa (100, "new[0x%x]", (unsigned) _flags); \
+	})
+
+/*****************************************************************************/
+
+NMPlatformKernelSupportFlags
+nm_platform_check_kernel_support (NMPlatform *self,
+                                  NMPlatformKernelSupportFlags request_flags)
 {
-	static int supported = -1;
+	NMPlatformPrivate *priv;
 
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF (self, klass, TRUE);
 
-	if (!klass->check_support_user_ipv6ll)
-		return FALSE;
+	priv = NM_PLATFORM_GET_PRIVATE (self);
+
+	/* we cache the response from subclasses and only request it once.
+	 * This probably gives better performance, but more importantly,
+	 * we are guaranteed that the answer for a certain request_flag
+	 * is always the same. */
+	if (G_UNLIKELY (!NM_FLAGS_ALL (priv->support_checked, request_flags))) {
+		NMPlatformKernelSupportFlags checked, response;
 
-	if (supported < 0)
-		supported = klass->check_support_user_ipv6ll (self) ? 1 : 0;
-	return !!supported;
+		checked = request_flags & ~priv->support_checked;
+		nm_assert (checked);
+
+		if (klass->check_kernel_support)
+			response = klass->check_kernel_support (self, checked);
+		else {
+			/* fake platform. Pretend no support for anything. */
+			response = 0;
+		}
+
+		priv->support_checked |= checked;
+		priv->support_present = (priv->support_present & ~checked) | (response & checked);
+	}
+
+	return priv->support_present & request_flags;
 }
 
 /**
@@ -322,6 +414,7 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface,
 {
 	const char *path;
 	gint64 cur;
+	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
 	_CHECK_SELF (self, klass, FALSE);
 
@@ -333,7 +426,7 @@ nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char *iface,
 	if (value < 10)
 		return FALSE;
 
-	path = nm_utils_ip6_property_path (iface, "hop_limit");
+	path = nm_utils_sysctl_ip_conf_path (AF_INET6, buf, iface, "hop_limit");
 	cur = nm_platform_sysctl_get_int_checked (self, NMP_SYSCTL_PATHID_ABSOLUTE (path), 10, 1, G_MAXINT32, -1);
 
 	/* only allow increasing the hop-limit to avoid DOS by an attacker
@@ -440,8 +533,8 @@ _link_get_all_presort (gconstpointer  p_a,
                        gconstpointer  p_b,
                        gpointer       sort_by_name)
 {
-	const NMPlatformLink *a = p_a;
-	const NMPlatformLink *b = p_b;
+	const NMPlatformLink *a = NMP_OBJECT_CAST_LINK (*((const NMPObject **) p_a));
+	const NMPlatformLink *b = NMP_OBJECT_CAST_LINK (*((const NMPObject **) p_b));
 
 	/* Loopback always first */
 	if (a->ifindex == 1)
@@ -463,43 +556,56 @@ _link_get_all_presort (gconstpointer  p_a,
 
 /**
  * nm_platform_link_get_all:
- * self: platform instance
+ * @self: platform instance
+ * @sort_by_name: whether to sort by name or ifindex.
  *
  * Retrieve a snapshot of configuration for all links at once. The result is
- * owned by the caller and should be freed with g_array_unref().
+ * owned by the caller and should be freed with g_ptr_array_unref().
  */
-GArray *
+GPtrArray *
 nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name)
 {
-	GArray *links, *result;
-	guint i, j, nresult;
-	GHashTable *unseen;
-	NMPlatformLink *item;
+	gs_unref_ptrarray GPtrArray *links = NULL;
+	GPtrArray *result;
+	guint i, nresult;
+	gs_unref_hashtable GHashTable *unseen = NULL;
+	const NMPlatformLink *item;
+	NMPLookup lookup;
 
 	_CHECK_SELF (self, klass, NULL);
 
-	links = klass->link_get_all (self);
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
+	links = nm_dedup_multi_objs_to_ptr_array_head (nm_platform_lookup (self, &lookup),
+	                                               NULL, NULL);
+	if (!links)
+		return NULL;
+
+	for (i = 0; i < links->len; ) {
+		if (!nmp_object_is_visible (links->pdata[i]))
+			g_ptr_array_remove_index_fast (links, i);
+		else
+			i++;
+	}
 
-	if (!links || links->len == 0)
-		return links;
+	if (links->len == 0)
+		return NULL;
 
 	/* first sort the links by their ifindex or name. Below we will sort
 	 * further by moving children/slaves to the end. */
-	g_array_sort_with_data (links, _link_get_all_presort, GINT_TO_POINTER (sort_by_name));
+	g_ptr_array_sort_with_data (links, _link_get_all_presort, GINT_TO_POINTER (sort_by_name));
 
 	unseen = g_hash_table_new (g_direct_hash, g_direct_equal);
 	for (i = 0; i < links->len; i++) {
-		item = &g_array_index (links, NMPlatformLink, i);
-
+		item = NMP_OBJECT_CAST_LINK (links->pdata[i]);
 		nm_assert (item->ifindex > 0);
 		if (!nm_g_hash_table_insert (unseen, GINT_TO_POINTER (item->ifindex), NULL))
 			nm_assert_not_reached ();
 	}
 
-#ifndef G_DISABLE_ASSERT
+#if NM_MORE_ASSERTS
 	/* Ensure that link_get_all returns a consistent and valid result. */
 	for (i = 0; i < links->len; i++) {
-		item = &g_array_index (links, NMPlatformLink, i);
+		item = NMP_OBJECT_CAST_LINK (links->pdata[i]);
 
 		if (!item->ifindex)
 			continue;
@@ -519,54 +625,75 @@ nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name)
 #endif
 
 	/* Re-order the links list such that children/slaves come after all ancestors */
-	nresult = g_hash_table_size (unseen);
-	result = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), nresult);
-	g_array_set_size (result, nresult);
+	nm_assert (g_hash_table_size (unseen) == links->len);
+	nresult = links->len;
+	result = g_ptr_array_new_full (nresult, (GDestroyNotify) nmp_object_unref);
 
-	j = 0;
-	do {
+	while (TRUE) {
 		gboolean found_something = FALSE;
 		guint first_idx = G_MAXUINT;
 
 		for (i = 0; i < links->len; i++) {
-			item = &g_array_index (links, NMPlatformLink, i);
+			item = NMP_OBJECT_CAST_LINK (links->pdata[i]);
 
-			if (!item->ifindex)
+			if (!item)
 				continue;
 
-			if (first_idx == G_MAXUINT)
-				first_idx = i;
-
 			g_assert (g_hash_table_contains (unseen, GINT_TO_POINTER (item->ifindex)));
 
 			if (item->master > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->master)))
-				continue;
+				goto skip;
 			if (item->parent > 0 && g_hash_table_contains (unseen, GINT_TO_POINTER (item->parent)))
-				continue;
+				goto skip;
 
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
-			g_array_index (result, NMPlatformLink, j++) = *item;
-			item->ifindex = 0;
+			g_ptr_array_add (result, links->pdata[i]);
+			links->pdata[i] = NULL;
 			found_something = TRUE;
+			continue;
+skip:
+			if (first_idx == G_MAXUINT)
+				first_idx = i;
 		}
 
-		if (!found_something) {
+		if (found_something) {
+			if (first_idx == G_MAXUINT)
+				break;
+		} else {
+			nm_assert (first_idx != G_MAXUINT);
 			/* There is a loop, pop the first (remaining) element from the list.
 			 * This can happen for veth pairs where each peer is parent of the other end. */
-			item = &g_array_index (links, NMPlatformLink, first_idx);
-
+			item = NMP_OBJECT_CAST_LINK (links->pdata[first_idx]);
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
-			g_array_index (result, NMPlatformLink, j++) = *item;
-			item->ifindex = 0;
+			g_ptr_array_add (result, links->pdata[first_idx]);
+			links->pdata[first_idx] = NULL;
 		}
-	} while (j < nresult);
-
-	g_hash_table_destroy (unseen);
-	g_array_free (links, TRUE);
+		nm_assert (result->len < nresult);
+	}
+	nm_assert (result->len == nresult);
 
 	return result;
 }
 
+/*****************************************************************************/
+
+const NMPObject *
+nm_platform_link_get_obj (NMPlatform *self,
+                          int ifindex,
+                          gboolean visible_only)
+{
+	const NMPObject *obj_cache;
+
+	obj_cache = nmp_cache_lookup_link (nm_platform_get_cache (self), ifindex);
+	if (   !obj_cache
+	    || (   visible_only
+	        && !nmp_object_is_visible (obj_cache)))
+		return NULL;
+	return obj_cache;
+}
+
+/*****************************************************************************/
+
 /**
  * nm_platform_link_get:
  * @self: platform instance
@@ -577,16 +704,20 @@ nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name)
  * Returns: %NULL, if such a link exists or the internal
  * platform link object. Do not modify the returned value.
  * Also, be aware that any subsequent platform call might
- * invalidated/modify the returned instance.
+ * invalidate/modify the returned instance.
  **/
 const NMPlatformLink *
 nm_platform_link_get (NMPlatform *self, int ifindex)
 {
+	const NMPObject *obj;
+
 	_CHECK_SELF (self, klass, NULL);
 
-	if (ifindex > 0)
-		return klass->link_get (self, ifindex);
-	return NULL;
+	if (ifindex <= 0)
+		return NULL;
+
+	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
+	return NMP_OBJECT_CAST_LINK (obj);
 }
 
 /**
@@ -599,11 +730,27 @@ nm_platform_link_get (NMPlatform *self, int ifindex)
 const NMPlatformLink *
 nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname)
 {
+	const NMPObject *obj;
+
 	_CHECK_SELF (self, klass, NULL);
 
-	if (ifname && *ifname)
-		return klass->link_get_by_ifname (self, ifname);
-	return NULL;
+	if (!ifname || !*ifname)
+		return NULL;
+
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (self),
+	                                  0, ifname, TRUE, NM_LINK_TYPE_NONE, NULL, NULL);
+	return NMP_OBJECT_CAST_LINK (obj);
+}
+
+struct _nm_platform_link_get_by_address_data {
+	gconstpointer address;
+	guint8 length;
+};
+
+static gboolean
+_nm_platform_link_get_by_address_match_link (const NMPObject *obj, struct _nm_platform_link_get_by_address_data *d)
+{
+	return obj->link.addr.len == d->length && !memcmp (obj->link.addr.data, d->address, d->length);
 }
 
 /**
@@ -620,15 +767,26 @@ nm_platform_link_get_by_address (NMPlatform *self,
                                  gconstpointer address,
                                  size_t length)
 {
+	const NMPObject *obj;
+	struct _nm_platform_link_get_by_address_data d = {
+		.address = address,
+		.length = length,
+	};
+
 	_CHECK_SELF (self, klass, NULL);
 
-	g_return_val_if_fail (length == 0 || address, NULL);
-	if (length > 0) {
-		if (length > NM_UTILS_HWADDR_LEN_MAX)
-			g_return_val_if_reached (NULL);
-		return klass->link_get_by_address (self, address, length);
-	}
-	return NULL;
+	if (length == 0)
+		return NULL;
+
+	if (length > NM_UTILS_HWADDR_LEN_MAX)
+		g_return_val_if_reached (NULL);
+	if (!address)
+		g_return_val_if_reached (NULL);
+
+	obj = nmp_cache_lookup_link_full (nm_platform_get_cache (self),
+	                                  0, NULL, TRUE, NM_LINK_TYPE_NONE,
+	                                  (NMPObjectMatchFn) _nm_platform_link_get_by_address_match_link, &d);
+	return NMP_OBJECT_CAST_LINK (obj);
 }
 
 static NMPlatformError
@@ -662,6 +820,7 @@ _link_add_check_existing (NMPlatform *self, const char *name, NMLinkType type, c
  * @self: platform instance
  * @name: Interface name
  * @type: Interface type
+ * @veth_peer: For veths, the peer name
  * @address: (allow-none): set the mac address of the link
  * @address_len: the length of the @address
  * @out_link: on success, the link object
@@ -680,27 +839,51 @@ static NMPlatformError
 nm_platform_link_add (NMPlatform *self,
                       const char *name,
                       NMLinkType type,
+                      const char *veth_peer,
                       const void *address,
                       size_t address_len,
                       const NMPlatformLink **out_link)
 {
 	NMPlatformError plerr;
+	char addr_buf[NM_UTILS_HWADDR_LEN_MAX * 3];
 
 	_CHECK_SELF (self, klass, NM_PLATFORM_ERROR_BUG);
 
 	g_return_val_if_fail (name, NM_PLATFORM_ERROR_BUG);
-	g_return_val_if_fail ( (address != NULL) ^ (address_len == 0) , NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail ((address != NULL) ^ (address_len == 0) , NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (address_len <= NM_UTILS_HWADDR_LEN_MAX, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail ((!!veth_peer) == (type == NM_LINK_TYPE_VETH), NM_PLATFORM_ERROR_BUG);
 
 	plerr = _link_add_check_existing (self, name, type, out_link);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS)
 		return plerr;
 
-	_LOGD ("link: adding %s '%s'", nm_link_type_to_string (type), name);
-	if (!klass->link_add (self, name, type, address, address_len, out_link))
+	_LOGD ("link: adding link '%s' of type '%s' (%d)"
+	       "%s%s" /* address */
+	       "%s%s" /* veth peer */
+	       "",
+	       name,
+	       nm_link_type_to_string (type),
+	       (int) type,
+	       address ? ", address: " : "",
+	       address ? nm_utils_hwaddr_ntoa_buf (address, address_len, FALSE, addr_buf, sizeof (addr_buf)) : "",
+	       veth_peer ? ", veth-peer: " : "",
+	       veth_peer ?: "");
+
+	if (!klass->link_add (self, name, type, veth_peer, address, address_len, out_link))
 		return NM_PLATFORM_ERROR_UNSPECIFIED;
 	return NM_PLATFORM_ERROR_SUCCESS;
 }
 
+NMPlatformError
+nm_platform_link_veth_add (NMPlatform *self,
+                            const char *name,
+                            const char *peer,
+                            const NMPlatformLink **out_link)
+{
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_VETH, peer, NULL, 0, out_link);
+}
+
 /**
  * nm_platform_link_dummy_add:
  * @self: platform instance
@@ -714,7 +897,7 @@ nm_platform_link_dummy_add (NMPlatform *self,
                             const char *name,
                             const NMPlatformLink **out_link)
 {
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, 0, out_link);
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_DUMMY, NULL, NULL, 0, out_link);
 }
 
 /**
@@ -846,9 +1029,26 @@ nm_platform_link_get_type (NMPlatform *self, int ifindex)
 const char *
 nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 {
+	const NMPObject *obj;
+
 	_CHECK_SELF (self, klass, NULL);
 
-	return klass->link_get_type_name (self, ifindex);
+	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
+
+	if (!obj)
+		return NULL;
+
+	if (obj->link.type != NM_LINK_TYPE_UNKNOWN) {
+		/* We could detect the @link_type. In this case the function returns
+		 * our internel module names, which differs from rtnl_link_get_type():
+		 *   - NM_LINK_TYPE_INFINIBAND (gives "infiniband", instead of "ipoib")
+		 *   - NM_LINK_TYPE_TAP (gives "tap", instead of "tun").
+		 * Note that this functions is only used by NMDeviceGeneric to
+		 * set type_description. */
+		return nm_link_type_to_string (obj->link.type);
+	}
+	/* Link type not detected. Fallback to rtnl_link_get_type()/IFLA_INFO_KIND. */
+	return obj->link.kind ?: "unknown";
 }
 
 /**
@@ -863,11 +1063,26 @@ nm_platform_link_get_type_name (NMPlatform *self, int ifindex)
 gboolean
 nm_platform_link_get_unmanaged (NMPlatform *self, int ifindex, gboolean *unmanaged)
 {
+	const NMPObject *link;
+	struct udev_device *udevice = NULL;
+	const char *uproperty;
+
 	_CHECK_SELF (self, klass, FALSE);
 
-	if (klass->link_get_unmanaged)
-		return klass->link_get_unmanaged (self, ifindex, unmanaged);
-	return FALSE;
+	link = nmp_cache_lookup_link (nm_platform_get_cache (self), ifindex);
+	if (!link)
+		return FALSE;
+
+	udevice = link->_link.udev.device;
+	if (!udevice)
+		return FALSE;
+
+	uproperty = udev_device_get_property_value (udevice, "NM_UNMANAGED");
+	if (!uproperty)
+		return FALSE;
+
+	*unmanaged = nm_udev_utils_property_as_boolean (uproperty);
+	return TRUE;
 }
 
 /**
@@ -1013,13 +1228,14 @@ nm_platform_link_get_udi (NMPlatform *self, int ifindex)
 struct udev_device *
 nm_platform_link_get_udev_device (NMPlatform *self, int ifindex)
 {
+	const NMPObject *obj_cache;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	g_return_val_if_fail (ifindex >= 0, NULL);
 
-	if (klass->link_get_udev_device)
-		return klass->link_get_udev_device (self, ifindex);
-	return NULL;
+	obj_cache = nm_platform_link_get_obj (self, ifindex, FALSE);
+	return obj_cache ? obj_cache->_link.udev.device : NULL;
 }
 
 /**
@@ -1284,7 +1500,7 @@ nm_platform_link_set_noarp (NMPlatform *self, int ifindex)
  *
  * Set interface MTU.
  */
-gboolean
+NMPlatformError
 nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu)
 {
 	_CHECK_SELF (self, klass, FALSE);
@@ -1315,6 +1531,30 @@ nm_platform_link_get_mtu (NMPlatform *self, int ifindex)
 }
 
 /**
+ * nm_platform_link_set_name:
+ * @self: platform instance
+ * @ifindex: Interface index
+ * @name: The new interface name
+ *
+ * Set interface name.
+ */
+gboolean
+nm_platform_link_set_name (NMPlatform *self, int ifindex, const char *name)
+{
+	_CHECK_SELF (self, klass, FALSE);
+
+	g_return_val_if_fail (ifindex >= 0, FALSE);
+	g_return_val_if_fail (name, FALSE);
+
+	_LOGD ("link: setting '%s' (%d) name %s", nm_platform_link_get_name (self, ifindex), ifindex, name);
+
+	if (strlen (name) + 1 > IFNAMSIZ)
+		return FALSE;
+
+	return klass->link_set_name (self, ifindex, name);
+}
+
+/**
  * nm_platform_link_get_physical_port_id:
  * @self: platform instance
  * @ifindex: Interface index
@@ -1465,7 +1705,7 @@ nm_platform_link_release (NMPlatform *self, int master, int slave)
  * @self: platform instance
  * @slave: Interface index of the slave.
  *
- * Returns: Interfase index of the slave's master.
+ * Returns: Interface index of the slave's master.
  */
 int
 nm_platform_link_get_master (NMPlatform *self, int slave)
@@ -1519,13 +1759,28 @@ nm_platform_link_can_assume (NMPlatform *self, int ifindex)
 const NMPObject *
 nm_platform_link_get_lnk (NMPlatform *self, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
 {
+	const NMPObject *obj;
+
 	_CHECK_SELF (self, klass, FALSE);
 
 	NM_SET_OUT (out_link, NULL);
 
 	g_return_val_if_fail (ifindex > 0, NULL);
 
-	return klass->link_get_lnk (self, ifindex, link_type, out_link);
+	obj = nm_platform_link_get_obj (self, ifindex, TRUE);
+	if (!obj)
+		return NULL;
+
+	NM_SET_OUT (out_link, &obj->link);
+
+	if (!obj->_link.netlink.lnk)
+		return NULL;
+	if (   link_type != NM_LINK_TYPE_NONE
+	    && (   link_type != obj->link.type
+	        || link_type != NMP_OBJECT_GET_CLASS (obj->_link.netlink.lnk)->lnk_link_type))
+		return NULL;
+
+	return obj->_link.netlink.lnk;
 }
 
 static gconstpointer
@@ -1616,7 +1871,7 @@ nm_platform_link_bridge_add (NMPlatform *self,
                              size_t address_len,
                              const NMPlatformLink **out_link)
 {
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_BRIDGE, address, address_len, out_link);
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_BRIDGE, NULL, address, address_len, out_link);
 }
 
 /**
@@ -1632,7 +1887,7 @@ nm_platform_link_bond_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLink **out_link)
 {
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_BOND, NULL, 0, out_link);
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_BOND, NULL, NULL, 0, out_link);
 }
 
 /**
@@ -1648,7 +1903,7 @@ nm_platform_link_team_add (NMPlatform *self,
                            const char *name,
                            const NMPlatformLink **out_link)
 {
-	return nm_platform_link_add (self, name, NM_LINK_TYPE_TEAM, NULL, 0, out_link);
+	return nm_platform_link_add (self, name, NM_LINK_TYPE_TEAM, NULL, NULL, 0, out_link);
 }
 
 /**
@@ -2617,6 +2872,82 @@ nm_platform_ethtool_get_link_settings (NMPlatform *self, int ifindex, gboolean *
 
 /*****************************************************************************/
 
+const NMDedupMultiHeadEntry *
+nm_platform_lookup_all (NMPlatform *platform,
+                        NMPCacheIdType cache_id_type,
+                        const NMPObject *obj)
+{
+	return nmp_cache_lookup_all (nm_platform_get_cache (platform),
+	                             cache_id_type,
+	                             obj);
+}
+
+const NMDedupMultiEntry *
+nm_platform_lookup_entry (NMPlatform *platform,
+                          NMPCacheIdType cache_id_type,
+                          const NMPObject *obj)
+{
+	return nmp_cache_lookup_entry_with_idx_type (nm_platform_get_cache (platform),
+	                                             cache_id_type,
+	                                             obj);
+}
+
+const NMDedupMultiHeadEntry *
+nm_platform_lookup (NMPlatform *self,
+                    const NMPLookup *lookup)
+{
+	return nmp_cache_lookup (nm_platform_get_cache (self),
+	                         lookup);
+}
+
+gboolean
+nm_platform_lookup_predicate_routes_main (const NMPObject *obj,
+                                          gpointer user_data)
+{
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                 NMP_OBJECT_TYPE_IP6_ROUTE));
+	return nm_platform_route_table_is_main (obj->ip_route.table_coerced);
+}
+
+gboolean
+nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel (const NMPObject *obj,
+                                                             gpointer user_data)
+{
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                 NMP_OBJECT_TYPE_IP6_ROUTE));
+	return    nm_platform_route_table_is_main (obj->ip_route.table_coerced)
+	       && obj->ip_route.rt_source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL;
+}
+
+/**
+ * nm_platform_lookup_clone:
+ * @self:
+ * @lookup:
+ * @predicate: if given, only objects for which @predicate returns %TRUE are included
+ *   in the result.
+ * @user_data: user data for @predicate
+ *
+ * Returns the result of lookup in a GPtrArray. The result array contains
+ * references objects from the cache, it's destroy function will unref them.
+ *
+ * The user must unref the GPtrArray, which will also unref the NMPObject
+ * elements.
+ *
+ * The elements in the array *must* not be modified.
+ *
+ * Returns: the result of the lookup.
+ */
+GPtrArray *
+nm_platform_lookup_clone (NMPlatform *self,
+                          const NMPLookup *lookup,
+                          NMPObjectPredicateFunc predicate,
+                          gpointer user_data)
+{
+	return nm_dedup_multi_objs_to_ptr_array_head (nm_platform_lookup (self, lookup),
+	                                              (NMDedupMultiFcnSelectPredicate) predicate,
+	                                              user_data);
+}
+
 void
 nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, guint8 plen)
 {
@@ -2636,26 +2967,6 @@ nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr)
 	return &addr->peer_address;
 }
 
-GArray *
-nm_platform_ip4_address_get_all (NMPlatform *self, int ifindex)
-{
-	_CHECK_SELF (self, klass, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
-	return klass->ip4_address_get_all (self, ifindex);
-}
-
-GArray *
-nm_platform_ip6_address_get_all (NMPlatform *self, int ifindex)
-{
-	_CHECK_SELF (self, klass, NULL);
-
-	g_return_val_if_fail (ifindex > 0, NULL);
-
-	return klass->ip6_address_get_all (self, ifindex);
-}
-
 gboolean
 nm_platform_ip4_address_add (NMPlatform *self,
                              int ifindex,
@@ -2768,54 +3079,41 @@ nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr a
 const NMPlatformIP4Address *
 nm_platform_ip4_address_get (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, guint32 peer_address)
 {
+	NMPObject obj_id;
+	const NMPObject *obj;
+
 	_CHECK_SELF (self, klass, NULL);
 
 	g_return_val_if_fail (plen <= 32, NULL);
 
-	return klass->ip4_address_get (self, ifindex, address, plen, peer_address);
+	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, address, plen, peer_address);
+	obj = nmp_cache_lookup_obj (nm_platform_get_cache (self), &obj_id);
+	nm_assert (!obj || nmp_object_is_visible (obj));
+	return NMP_OBJECT_CAST_IP4_ADDRESS (obj);
 }
 
 const NMPlatformIP6Address *
-nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen)
+nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address)
 {
-	_CHECK_SELF (self, klass, NULL);
+	NMPObject obj_id;
+	const NMPObject *obj;
 
-	g_return_val_if_fail (plen <= 128, NULL);
-
-	return klass->ip6_address_get (self, ifindex, address, plen);
-}
-
-static const NMPlatformIP4Address *
-array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address, gint32 now)
-{
-	guint len = addresses ? addresses->len : 0;
-	guint i;
-
-	for (i = 0; i < len; i++) {
-		const NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
-
-		if (   candidate->address == address->address
-		    && candidate->plen == address->plen
-		    && ((candidate->peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (address->plen)) == 0) {
-			guint32 lifetime, preferred;
-
-			if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
-			                           now, &lifetime, &preferred))
-				return candidate;
-		}
-	}
+	_CHECK_SELF (self, klass, NULL);
 
-	return NULL;
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &address);
+	obj = nmp_cache_lookup_obj (nm_platform_get_cache (self), &obj_id);
+	nm_assert (!obj || nmp_object_is_visible (obj));
+	return NMP_OBJECT_CAST_IP6_ADDRESS (obj);
 }
 
 static gboolean
-array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address *address, gint32 now)
+array_contains_ip6_address (const GPtrArray *addresses, const NMPlatformIP6Address *address, gint32 now)
 {
 	guint len = addresses ? addresses->len : 0;
 	guint i;
 
 	for (i = 0; i < len; i++) {
-		NMPlatformIP6Address *candidate = &g_array_index (addresses, NMPlatformIP6Address, i);
+		NMPlatformIP6Address *candidate = NMP_OBJECT_CAST_IP6_ADDRESS (addresses->pdata[i]);
 
 		if (IN6_ARE_ADDR_EQUAL (&candidate->address, &address->address) && candidate->plen == address->plen) {
 			guint32 lifetime, preferred;
@@ -2830,69 +3128,100 @@ array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address
 }
 
 static gboolean
-_ptr_inside_ip4_addr_array (const GArray *array, gconstpointer needle)
+ip4_addr_subnets_is_plain_address (const GPtrArray *addresses, gconstpointer needle)
 {
-	return    needle >= (gconstpointer) &g_array_index (array, const NMPlatformIP4Address, 0)
-	       && needle <  (gconstpointer) &g_array_index (array, const NMPlatformIP4Address, array->len);
+	return    needle >= (gconstpointer) &addresses->pdata[0]
+	       && needle <  (gconstpointer) &addresses->pdata[addresses->len];
+}
+
+static const NMPObject **
+ip4_addr_subnets_addr_list_get (const GPtrArray *addr_list, guint idx)
+{
+	nm_assert (addr_list);
+	nm_assert (addr_list->len > 1);
+	nm_assert (idx < addr_list->len);
+	nm_assert (addr_list->pdata[idx]);
+	nm_assert (   !(*((gpointer *) addr_list->pdata[idx]))
+	           || NMP_OBJECT_CAST_IP4_ADDRESS (*((gpointer *) addr_list->pdata[idx])));
+	nm_assert (idx == 0 || ip4_addr_subnets_addr_list_get (addr_list, idx - 1));
+	return addr_list->pdata[idx];
 }
 
 static void
-ip4_addr_subnets_destroy_index (GHashTable *ht, const GArray *addresses)
+ip4_addr_subnets_destroy_index (GHashTable *subnets, const GPtrArray *addresses)
 {
 	GHashTableIter iter;
 	gpointer p;
 
-	g_hash_table_iter_init (&iter, ht);
+	if (!subnets)
+		return;
 
+	g_hash_table_iter_init (&iter, subnets);
 	while (g_hash_table_iter_next (&iter, NULL, &p)) {
-		if (!_ptr_inside_ip4_addr_array (addresses, p)) {
+		if (!ip4_addr_subnets_is_plain_address (addresses, p))
 			g_ptr_array_free ((GPtrArray *) p, TRUE);
-		}
 	}
 
-	g_hash_table_unref (ht);
+	g_hash_table_unref (subnets);
 }
 
 static GHashTable *
-ip4_addr_subnets_build_index (const GArray *addresses, gboolean consider_flags)
+ip4_addr_subnets_build_index (const GPtrArray *addresses,
+                              gboolean consider_flags,
+                              gboolean full_index)
 {
-	const NMPlatformIP4Address *address;
-	gpointer p;
 	GHashTable *subnets;
-	GPtrArray *ptr;
-	guint32 net;
 	guint i;
-	gint position;
 
-	if (!addresses)
-		return NULL;
+	nm_assert (addresses && addresses->len);
 
-	subnets = g_hash_table_new_full (g_direct_hash,
-	                                 g_direct_equal,
-	                                 NULL,
-	                                 NULL);
+	subnets = g_hash_table_new (NULL, NULL);
 
 	/* Build a hash table of all addresses per subnet */
 	for (i = 0; i < addresses->len; i++) {
-		address = &g_array_index (addresses, const NMPlatformIP4Address, i);
-		net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
-		if (!g_hash_table_lookup_extended (subnets, GUINT_TO_POINTER (net), NULL, &p)) {
-			g_hash_table_insert (subnets, GUINT_TO_POINTER (net), (gpointer) address);
+		const NMPlatformIP4Address *address;
+		gpointer p_address;
+		GPtrArray *addr_list;
+		guint32 net;
+		int position;
+		gpointer p;
+
+		if (!addresses->pdata[i])
 			continue;
-		}
-		if (_ptr_inside_ip4_addr_array (addresses, p)) {
-			ptr = g_ptr_array_new ();
-			g_hash_table_insert (subnets, GUINT_TO_POINTER (net), ptr);
-			g_ptr_array_add (ptr, p);
-		} else
-			ptr = p;
 
-		if (!consider_flags || NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY))
-			position = -1; /* append */
-		else
-			position = 0; /* prepend */
+		p_address = &addresses->pdata[i];
+		address = NMP_OBJECT_CAST_IP4_ADDRESS (addresses->pdata[i]);
 
-		g_ptr_array_insert (ptr, position, (gpointer) address);
+		net = address->address & _nm_utils_ip4_prefix_to_netmask (address->plen);
+		if (!g_hash_table_lookup_extended (subnets, GUINT_TO_POINTER (net), NULL, &p)) {
+			g_hash_table_insert (subnets, GUINT_TO_POINTER (net), p_address);
+			continue;
+		}
+		nm_assert (p);
+
+		if (full_index) {
+			if (ip4_addr_subnets_is_plain_address (addresses, p)) {
+				addr_list = g_ptr_array_new ();
+				g_hash_table_insert (subnets, GUINT_TO_POINTER (net), addr_list);
+				g_ptr_array_add (addr_list, p);
+			} else
+				addr_list = p;
+
+			if (   !consider_flags
+			    || NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY))
+				position = -1; /* append */
+			else
+				position = 0; /* prepend */
+			g_ptr_array_insert (addr_list, position, p_address);
+		} else {
+			/* we only care about the primary. No need to track the secondaries
+			 * as a GPtrArray. */
+			nm_assert (ip4_addr_subnets_is_plain_address (addresses, p));
+			if (   consider_flags
+			    && !NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY)) {
+				g_hash_table_insert (subnets, GUINT_TO_POINTER (net), p_address);
+			}
+		}
 	}
 
 	return subnets;
@@ -2911,23 +3240,33 @@ ip4_addr_subnets_build_index (const GArray *addresses, gboolean consider_flags)
  * Returns: %TRUE if the address is secondary, %FALSE otherwise
  */
 static gboolean
-ip4_addr_subnets_is_secondary (const NMPlatformIP4Address *address, GHashTable *subnets, const GArray *addresses, GPtrArray **out_addr_list)
-{
-	GPtrArray *addr_list;
-	gpointer p;
+ip4_addr_subnets_is_secondary (const NMPObject *address,
+                               GHashTable *subnets,
+                               const GPtrArray *addresses,
+                               const GPtrArray **out_addr_list)
+{
+	const NMPlatformIP4Address *a;
+	const GPtrArray *addr_list;
+	gconstpointer p;
 	guint32 net;
+	const NMPObject **o;
+
+	a = NMP_OBJECT_CAST_IP4_ADDRESS (address);
 
-	net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
+	net = a->address & _nm_utils_ip4_prefix_to_netmask (a->plen);
 	p = g_hash_table_lookup (subnets, GUINT_TO_POINTER (net));
 	nm_assert (p);
-	if (!_ptr_inside_ip4_addr_array (addresses, p)) {
+	if (!ip4_addr_subnets_is_plain_address (addresses, p)) {
 		addr_list = p;
+		nm_assert (addr_list->len > 1);
 		NM_SET_OUT (out_addr_list, addr_list);
-		if (addr_list->pdata[0] != address)
+		o = ip4_addr_subnets_addr_list_get (addr_list, 0);
+		nm_assert (o && *o);
+		if (*o != address)
 			return TRUE;
 	} else {
-		nm_assert ((gconstpointer) address == p);
 		NM_SET_OUT (out_addr_list, NULL);
+		return address != *((gconstpointer *) p);
 	}
 	return FALSE;
 }
@@ -2936,11 +3275,14 @@ ip4_addr_subnets_is_secondary (const NMPlatformIP4Address *address, GHashTable *
  * nm_platform_ip4_address_sync:
  * @self: platform instance
  * @ifindex: Interface index
- * @known_addresses: List of addresses
- * @out_added_addresses: (out): (allow-none): if not %NULL, return a #GPtrArray
- *   with the addresses added. The pointers point into @known_addresses.
- *   It possibly does not contain all addresses from @known_address because
- *   some addresses might be expired.
+ * @known_addresses: List of addresses. The list will be modified and only
+ *   addresses that were successfully added will be kept in the list.
+ *   That means, expired addresses and addresses that could not be added
+ *   will be dropped.
+ *   Hence, the input argument @known_addresses is also an output argument
+ *   telling which addresses were succesfully added.
+ *   Addresses are removed by unrefing the instance via nmp_object_unref()
+ *   and leaving a NULL tombstone.
  *
  * A convenience function to synchronize addresses for a specific interface
  * with the least possible disturbance. It simply removes addresses that are
@@ -2949,102 +3291,168 @@ ip4_addr_subnets_is_secondary (const NMPlatformIP4Address *address, GHashTable *
  * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, GPtrArray **out_added_addresses)
+nm_platform_ip4_address_sync (NMPlatform *self,
+                              int ifindex,
+                              GPtrArray *known_addresses)
 {
-	GArray *addresses;
-	NMPlatformIP4Address *address;
+	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
 	const NMPlatformIP4Address *known_address;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
-	GHashTable *plat_subnets;
-	GHashTable *known_subnets;
-	GPtrArray *ptr;
-	int i, j;
+	GHashTable *plat_subnets = NULL;
+	GHashTable *known_subnets = NULL;
+	gs_unref_hashtable GHashTable *known_addresses_idx = NULL;
+	guint i, j, len;
+	NMPLookup lookup;
+	guint32 lifetime, preferred;
+	guint32 ifa_flags;
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	addresses = nm_platform_ip4_address_get_all (self, ifindex);
-	plat_subnets = ip4_addr_subnets_build_index (addresses, TRUE);
-	known_subnets = ip4_addr_subnets_build_index (known_addresses, FALSE);
+	if (known_addresses) {
+		/* remove all addresses that are already expired. */
+		for (i = 0; i < known_addresses->len; i++) {
+			const NMPObject *o;
+
+			o = known_addresses->pdata[i];
+			nm_assert (o);
+
+			known_address = NMP_OBJECT_CAST_IP4_ADDRESS (known_addresses->pdata[i]);
+
+			if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
+			                            now, &lifetime, &preferred))
+				goto delete_and_next;
+
+			if (G_UNLIKELY (!known_addresses_idx)) {
+				known_addresses_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
+				                                        (GEqualFunc) nmp_object_id_equal);
+			}
+			if (!nm_g_hash_table_insert (known_addresses_idx, (gpointer) o, (gpointer) o)) {
+				/* duplicate? Keep only the first instance. */
+				goto delete_and_next;
+			}
+
+			continue;
+delete_and_next:
+			nmp_object_unref (o);
+			known_addresses->pdata[i] = NULL;
+		}
+
+		if (   !known_addresses_idx
+		    || g_hash_table_size (known_addresses_idx) == 0)
+			known_addresses = NULL;
+	}
+
+	plat_addresses = nm_platform_lookup_clone (self,
+	                                           nmp_lookup_init_addrroute (&lookup,
+	                                                                      NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                                                                      ifindex),
+	                                           NULL, NULL);
+	if (plat_addresses)
+		plat_subnets = ip4_addr_subnets_build_index (plat_addresses, TRUE, TRUE);
 
 	/* Delete unknown addresses */
-	for (i = 0; i < addresses->len; i++) {
-		address = &g_array_index (addresses, NMPlatformIP4Address, i);
+	len = plat_addresses ? plat_addresses->len : 0;
+	for (i = 0; i < len; i++) {
+		const NMPObject *plat_obj;
+		const NMPlatformIP4Address *plat_address;
+		const GPtrArray *addr_list;
 
-		if (!address->ifindex) {
+		plat_obj = plat_addresses->pdata[i];
+		if (!plat_obj) {
 			/* Already deleted */
 			continue;
 		}
 
-		known_address = array_contains_ip4_address (known_addresses, address, now);
-		if (known_address) {
-			gboolean secondary;
+		plat_address = NMP_OBJECT_CAST_IP4_ADDRESS (plat_obj);
+
+		if (known_addresses) {
+			const NMPObject *o;
+
+			o = g_hash_table_lookup (known_addresses_idx, plat_obj);
+			if (o) {
+				gboolean secondary;
+
+				if (!known_subnets)
+					known_subnets = ip4_addr_subnets_build_index (known_addresses, FALSE, FALSE);
 
-			secondary = ip4_addr_subnets_is_secondary (known_address, known_subnets, known_addresses, NULL);
-			/* Ignore the matching address if it has a different primary/slave
-			 * role. */
-			if (secondary != NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY))
-				known_address = NULL;
+				secondary = ip4_addr_subnets_is_secondary (o, known_subnets, known_addresses, NULL);
+				if (secondary == NM_FLAGS_HAS (plat_address->n_ifa_flags, IFA_F_SECONDARY)) {
+					/* if we have an existing known-address, with matching secondary role,
+					 * do not delete the platform-address. */
+					continue;
+				}
+			}
 		}
 
-		if (!known_address) {
-			nm_platform_ip4_address_delete (self, ifindex,
-			                                address->address,
-			                                address->plen,
-			                                address->peer_address);
-			if (   !ip4_addr_subnets_is_secondary (address, plat_subnets, addresses, &ptr)
-			    && ptr) {
-				/* If we just deleted a primary addresses and there were
-				 * secondary ones the kernel can do two things, depending on
-				 * version and sysctl setting: delete also secondary addresses
-				 * or promote a secondary to primary. Ensure that secondary
-				 * addresses are deleted, so that we can start with a clean
-				 * slate and add addresses in the right order. */
-				for (j = 1; j < ptr->len; j++) {
-					address = ptr->pdata[j];
+		nm_platform_ip4_address_delete (self, ifindex,
+		                                plat_address->address,
+		                                plat_address->plen,
+		                                plat_address->peer_address);
+
+		if (   !ip4_addr_subnets_is_secondary (plat_obj, plat_subnets, plat_addresses, &addr_list)
+		    && addr_list) {
+			/* If we just deleted a primary addresses and there were
+			 * secondary ones the kernel can do two things, depending on
+			 * version and sysctl setting: delete also secondary addresses
+			 * or promote a secondary to primary. Ensure that secondary
+			 * addresses are deleted, so that we can start with a clean
+			 * slate and add addresses in the right order. */
+			for (j = 1; j < addr_list->len; j++) {
+				const NMPObject **o;
+
+				o = ip4_addr_subnets_addr_list_get (addr_list, j);
+				nm_assert (o);
+
+				if (*o) {
+					const NMPlatformIP4Address *a;
+
+					a = NMP_OBJECT_CAST_IP4_ADDRESS (*o);
 					nm_platform_ip4_address_delete (self, ifindex,
-					                                address->address,
-					                                address->plen,
-					                                address->peer_address);
-					address->ifindex = 0;
+					                                a->address,
+					                                a->plen,
+					                                a->peer_address);
+					nmp_object_unref (*o);
+					*o = NULL;
 				}
 			}
 		}
 	}
-	ip4_addr_subnets_destroy_index (plat_subnets, addresses);
-	g_array_free (addresses, TRUE);
-
-	if (out_added_addresses)
-		*out_added_addresses = NULL;
+	ip4_addr_subnets_destroy_index (plat_subnets, plat_addresses);
+	ip4_addr_subnets_destroy_index (known_subnets, known_addresses);
 
 	if (!known_addresses)
 		return TRUE;
 
+	ifa_flags =   nm_platform_check_kernel_support (self, NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)
+	            ? IFA_F_NOPREFIXROUTE
+	            : 0;
+
 	/* Add missing addresses */
 	for (i = 0; i < known_addresses->len; i++) {
-		guint32 lifetime, preferred;
+		const NMPObject *o;
 
-		known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
+		o = known_addresses->pdata[i];
+		if (!o)
+			continue;
+
+		known_address = NMP_OBJECT_CAST_IP4_ADDRESS (o);
 
 		if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
 		                            now, &lifetime, &preferred))
-			continue;
+			goto delete_and_next2;
 
 		if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
 		                                  known_address->peer_address, lifetime, preferred,
-		                                  0, known_address->label)) {
-			ip4_addr_subnets_destroy_index (known_subnets, known_addresses);
-			return FALSE;
-		}
-
-		if (out_added_addresses) {
-			if (!*out_added_addresses)
-				*out_added_addresses = g_ptr_array_new ();
-			g_ptr_array_add (*out_added_addresses, (gpointer) known_address);
-		}
+		                                  ifa_flags,
+		                                  known_address->label))
+			goto delete_and_next2;
+
+		continue;
+delete_and_next2:
+		nmp_object_unref (o);
+		known_addresses->pdata[i] = NULL;
 	}
 
-	ip4_addr_subnets_destroy_index (known_subnets, known_addresses);
-
 	return TRUE;
 }
 
@@ -3052,7 +3460,8 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
  * nm_platform_ip6_address_sync:
  * @self: platform instance
  * @ifindex: Interface index
- * @known_addresses: List of addresses
+ * @known_addresses: List of IPv6 addresses, as NMPObject. The list
+ *   is not modified.
  * @keep_link_local: Don't remove link-local address
  *
  * A convenience function to synchronize addresses for a specific interface
@@ -3062,33 +3471,47 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
  * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, gboolean keep_link_local)
+nm_platform_ip6_address_sync (NMPlatform *self,
+                              int ifindex,
+                              const GPtrArray *known_addresses,
+                              gboolean keep_link_local)
 {
-	GArray *addresses;
+	gs_unref_ptrarray GPtrArray *plat_addresses = NULL;
 	NMPlatformIP6Address *address;
 	gint32 now = nm_utils_get_monotonic_timestamp_s ();
-	int i;
+	guint i;
+	NMPLookup lookup;
+	guint32 ifa_flags;
 
 	/* Delete unknown addresses */
-	addresses = nm_platform_ip6_address_get_all (self, ifindex);
-	for (i = 0; i < addresses->len; i++) {
-		address = &g_array_index (addresses, NMPlatformIP6Address, i);
-
-		/* Leave link local address management to the kernel */
-		if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address))
-			continue;
+	plat_addresses = nm_platform_lookup_clone (self,
+	                                           nmp_lookup_init_addrroute (&lookup,
+	                                                                      NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                                                      ifindex),
+	                                           NULL, NULL);
+	if (plat_addresses) {
+		for (i = 0; i < plat_addresses->len; i++) {
+			address = NMP_OBJECT_CAST_IP6_ADDRESS (plat_addresses->pdata[i]);
+
+			/* Leave link local address management to the kernel */
+			if (keep_link_local && IN6_IS_ADDR_LINKLOCAL (&address->address))
+				continue;
 
-		if (!array_contains_ip6_address (known_addresses, address, now))
-			nm_platform_ip6_address_delete (self, ifindex, address->address, address->plen);
+			if (!array_contains_ip6_address (known_addresses, address, now))
+				nm_platform_ip6_address_delete (self, ifindex, address->address, address->plen);
+		}
 	}
-	g_array_free (addresses, TRUE);
 
 	if (!known_addresses)
 		return TRUE;
 
+	ifa_flags =   nm_platform_check_kernel_support (self, NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS)
+	            ? IFA_F_NOPREFIXROUTE
+	            : 0;
+
 	/* Add missing addresses */
 	for (i = 0; i < known_addresses->len; i++) {
-		const NMPlatformIP6Address *known_address = &g_array_index (known_addresses, NMPlatformIP6Address, i);
+		const NMPlatformIP6Address *known_address = NMP_OBJECT_CAST_IP6_ADDRESS (known_addresses->pdata[i]);
 		guint32 lifetime, preferred;
 
 		if (NM_FLAGS_HAS (known_address->n_ifa_flags, IFA_F_TEMPORARY)) {
@@ -3102,7 +3525,8 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 
 		if (!nm_platform_ip6_address_add (self, ifindex, known_address->address,
 		                                  known_address->plen, known_address->peer_address,
-		                                  lifetime, preferred, known_address->n_ifa_flags))
+		                                  lifetime, preferred,
+		                                  ifa_flags | known_address->n_ifa_flags))
 			return FALSE;
 	}
 
@@ -3110,125 +3534,797 @@ nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known
 }
 
 gboolean
-nm_platform_address_flush (NMPlatform *self, int ifindex)
+nm_platform_ip_address_flush (NMPlatform *self,
+                              int addr_family,
+                              int ifindex)
 {
+	gboolean success = TRUE;
+
 	_CHECK_SELF (self, klass, FALSE);
 
-	return    nm_platform_ip4_address_sync (self, ifindex, NULL, NULL)
-	       && nm_platform_ip6_address_sync (self, ifindex, NULL, FALSE);
+	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC,
+	                                   AF_INET,
+	                                   AF_INET6));
+
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET))
+		success &= nm_platform_ip4_address_sync (self, ifindex, NULL);
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6))
+		success &= nm_platform_ip6_address_sync (self, ifindex, NULL, FALSE);
+	return success;
 }
 
 /*****************************************************************************/
 
-GArray *
-nm_platform_ip4_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags)
+static gboolean
+_err_inval_due_to_ipv6_tentative_pref_src (NMPlatform *self, const NMPObject *obj)
 {
-	_CHECK_SELF (self, klass, NULL);
+	const NMPlatformIP6Route *r;
+	const NMPlatformIP6Address *a;
 
-	g_return_val_if_fail (ifindex >= 0, NULL);
+	nm_assert (NM_IS_PLATFORM (self));
+	nm_assert (NMP_OBJECT_IS_VALID (obj));
 
-	return klass->ip4_route_get_all (self, ifindex, flags);
+	/* trying to add an IPv6 route with pref-src fails, if the address is
+	 * still tentative (rh#1452684). We need to hack around that.
+	 *
+	 * Detect it, by guessing whether that's the case. */
+
+	if (NMP_OBJECT_GET_TYPE (obj) != NMP_OBJECT_TYPE_IP6_ROUTE)
+		return FALSE;
+
+	r = NMP_OBJECT_CAST_IP6_ROUTE (obj);
+
+	/* we only allow this workaround for routes added manually by the user. */
+	if (r->rt_source != NM_IP_CONFIG_SOURCE_USER)
+		return FALSE;
+
+	if (IN6_IS_ADDR_UNSPECIFIED (&r->pref_src))
+		return FALSE;
+
+	a = nm_platform_ip6_address_get (self, r->ifindex, r->pref_src);
+	if (!a)
+		return FALSE;
+	if (   !NM_FLAGS_HAS (a->n_ifa_flags, IFA_F_TENTATIVE)
+	    || NM_FLAGS_HAS (a->n_ifa_flags, IFA_F_DADFAILED))
+		return FALSE;
+
+	return TRUE;
 }
 
-GArray *
-nm_platform_ip6_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags)
-{
-	_CHECK_SELF (self, klass, NULL);
+GPtrArray *
+nm_platform_ip_route_get_prune_list (NMPlatform *self,
+                                     int addr_family,
+                                     int ifindex,
+                                     NMIPRouteTableSyncMode route_table_sync)
+{
+	NMPLookup lookup;
+	GPtrArray *routes_prune;
+	const NMDedupMultiHeadEntry *head_entry;
+	CList *iter;
+
+	nm_assert (NM_IS_PLATFORM (self));
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert (NM_IN_SET (route_table_sync, NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
+	                                        NM_IP_ROUTE_TABLE_SYNC_MODE_FULL,
+	                                        NM_IP_ROUTE_TABLE_SYNC_MODE_ALL));
+
+	nmp_lookup_init_addrroute (&lookup,
+	                           addr_family == AF_INET
+	                             ? NMP_OBJECT_TYPE_IP4_ROUTE
+	                             : NMP_OBJECT_TYPE_IP6_ROUTE,
+	                           ifindex);
+	head_entry = nm_platform_lookup (self, &lookup);
+	if (!head_entry)
+		return NULL;
 
-	g_return_val_if_fail (ifindex >= 0, NULL);
+	routes_prune = g_ptr_array_new_full (head_entry->len,
+	                                     (GDestroyNotify) nm_dedup_multi_obj_unref);
+
+	c_list_for_each (iter, &head_entry->lst_entries_head) {
+		const NMPObject *obj = c_list_entry (iter, NMDedupMultiEntry, lst_entries)->obj;
+
+		if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_FULL) {
+			if (nm_platform_route_table_uncoerce (NMP_OBJECT_CAST_IP_ROUTE (obj)->table_coerced, TRUE) == RT_TABLE_LOCAL)
+				continue;
+		} else if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN) {
+			if (!nm_platform_route_table_is_main (NMP_OBJECT_CAST_IP_ROUTE (obj)->table_coerced))
+				continue;
+		} else
+			nm_assert (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_ALL);
+
+		g_ptr_array_add (routes_prune, (gpointer) nmp_object_ref (obj));
+	}
 
-	return klass->ip6_route_get_all (self, ifindex, flags);
+	if (routes_prune->len == 0) {
+		g_ptr_array_unref (routes_prune);
+		return NULL;
+	}
+	return routes_prune;
 }
 
 /**
- * nm_platform_ip4_route_add:
- * @self:
- * @route:
- *
- * For kernel, a gateway can be either explicitly set or left
- * at zero (0.0.0.0). In addition, there is the scope of the IPv4
- * route.
- * When adding a route with
- *   $ ip route add default dev $IFNAME
- * the resulting route will have gateway 0.0.0.0 and scope "link".
- * Contrary to
- *   $ ip route add default via 0.0.0.0 dev $IFNAME
- * which adds the route with scope "global".
- *
- * NetworkManager's Platform can currently only add on-link-routes with scope
- * "link" (and gateway 0.0.0.0) or gateway-routes with scope "global" (and
- * gateway not 0.0.0.0).
+ * nm_platform_ip_route_sync:
+ * @self: the #NMPlatform instance.
+ * @addr_family: AF_INET or AF_INET6.
+ * @ifindex: the @ifindex for which the routes are to be added.
+ * @routes: (allow-none): a list of routes to configure. Must contain
+ *   NMPObject instances of routes, according to @addr_family.
+ * @routes_prune: (allow-none): the list of routes to delete.
+ *   If platform has such a route configured, it will be deleted
+ *   at the end of the operation. Note that if @routes contains
+ *   the same route, then it will not be deleted. @routes overrules
+ *   @routes_prune list.
+ * @out_temporary_not_available: (allow-none): (out): routes that could
+ *   currently not be synced. The caller shall keep them and try later again.
  *
- * It does not support adding globally scoped routes via 0.0.0.0.
- *
- * Returns: %TRUE in case of success.
+ * Returns: %TRUE on success.
  */
 gboolean
-nm_platform_ip4_route_add (NMPlatform *self, const NMPlatformIP4Route *route)
-{
-	_CHECK_SELF (self, klass, FALSE);
+nm_platform_ip_route_sync (NMPlatform *self,
+                           int addr_family,
+                           int ifindex,
+                           GPtrArray *routes,
+                           GPtrArray *routes_prune,
+                           GPtrArray **out_temporary_not_available)
+{
+	const NMPlatformVTableRoute *vt;
+	gs_unref_hashtable GHashTable *routes_idx = NULL;
+	const NMPObject *conf_o;
+	const NMDedupMultiEntry *plat_entry;
+	guint i;
+	int i_type;
+	gboolean success = TRUE;
+	char sbuf1[sizeof (_nm_utils_to_string_buffer)];
+	char sbuf2[sizeof (_nm_utils_to_string_buffer)];
+	char sbuf_err[60];
+
+	nm_assert (NM_IS_PLATFORM (self));
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert (ifindex > 0);
+
+	vt = addr_family == AF_INET
+	     ? &nm_platform_vtable_route_v4
+	     : &nm_platform_vtable_route_v6;
+
+	for (i_type = 0; routes && i_type < 2; i_type++) {
+		for (i = 0; i < routes->len; i++) {
+			NMPlatformError plerr;
+
+			conf_o = routes->pdata[i];
+
+#define VTABLE_IS_DEVICE_ROUTE(vt, o) (vt->is_ip4 \
+                                         ? (NMP_OBJECT_CAST_IP4_ROUTE (o)->gateway == 0) \
+                                         : IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (o)->gateway) )
+
+			if (   (i_type == 0 && !VTABLE_IS_DEVICE_ROUTE (vt, conf_o))
+			    || (i_type == 1 &&  VTABLE_IS_DEVICE_ROUTE (vt, conf_o))) {
+				/* we add routes in two runs over @i_type.
+				 *
+				 * First device routes, then gateway routes. */
+				continue;
+			}
+
+			if (!routes_idx) {
+				routes_idx = g_hash_table_new ((GHashFunc) nmp_object_id_hash,
+				                               (GEqualFunc) nmp_object_id_equal);
+			}
+			if (!nm_g_hash_table_insert (routes_idx, (gpointer) conf_o, (gpointer) conf_o)) {
+				_LOGD ("route-sync: skip adding duplicate route %s",
+				       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
+				continue;
+			}
+
+			plat_entry = nm_platform_lookup_entry (self,
+			                                       NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+			                                       conf_o);
+			if (plat_entry) {
+				const NMPObject *plat_o;
+
+				plat_o = plat_entry->obj;
 
-	g_return_val_if_fail (route, FALSE);
-	g_return_val_if_fail (route->plen <= 32, FALSE);
+				if (vt->route_cmp (NMP_OBJECT_CAST_IPX_ROUTE (conf_o),
+				                   NMP_OBJECT_CAST_IPX_ROUTE (plat_o),
+				                   NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) == 0)
+					continue;
 
-	_LOGD ("route: adding or updating IPv4 route: %s", nm_platform_ip4_route_to_string (route, NULL, 0));
+				/* we need to replace the existing route with a (slightly) differnt
+				 * one. Delete it first. */
+				if (!nm_platform_ip_route_delete (self, plat_o)) {
+					/* ignore error. */
+				}
+			}
 
-	return klass->ip4_route_add (self, route);
+			plerr = nm_platform_ip_route_add (self,
+			                                    NMP_NLM_FLAG_APPEND
+			                                  | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
+			                                  conf_o);
+			if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
+				if (-((int) plerr) == EEXIST) {
+					/* Don't fail for EEXIST. It's not clear that the existing route
+					 * is identical to the one that we were about to add. However,
+					 * above we should have deleted conflicting (non-identical) routes. */
+					if (_LOGD_ENABLED ()) {
+						plat_entry = nm_platform_lookup_entry (self,
+						                                       NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+						                                       conf_o);
+						if (!plat_entry) {
+							_LOGD ("route-sync: adding route %s failed with EEXIST, however we cannot find such a route",
+							       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)));
+						} else if (vt->route_cmp (NMP_OBJECT_CAST_IPX_ROUTE (conf_o),
+						                          NMP_OBJECT_CAST_IPX_ROUTE (plat_entry->obj),
+						                          NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) != 0) {
+							_LOGD ("route-sync: adding route %s failed due to existing (different!) route %s",
+							       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+							       nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2)));
+						}
+					}
+				} else if (   -((int) plerr) == EINVAL
+				           && out_temporary_not_available
+				           && _err_inval_due_to_ipv6_tentative_pref_src (self, conf_o)) {
+					_LOGD ("route-sync: ignore failure to add IPv6 route with tentative IPv6 pref-src: %s: %s",
+					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+					if (!*out_temporary_not_available)
+						*out_temporary_not_available = g_ptr_array_new_full (0, (GDestroyNotify) nmp_object_unref);
+					g_ptr_array_add (*out_temporary_not_available, (gpointer) nmp_object_ref (conf_o));
+				} else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
+					_LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s",
+					       vt->is_ip4 ? '4' : '6',
+					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)));
+				} else {
+					const char *reason = "";
+
+					if (   -((int) plerr) == ENETUNREACH
+					    && (  vt->is_ip4
+					        ? !!NMP_OBJECT_CAST_IP4_ROUTE (conf_o)->gateway
+					        : !IN6_IS_ADDR_UNSPECIFIED (&NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->gateway)))
+						reason = "; is the gateway directly reachable?";
+
+					_LOGW ("route-sync: failure to add IPv%c route: %s: %s%s",
+					       vt->is_ip4 ? '4' : '6',
+					       nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)),
+					       nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err)),
+					       reason);
+					success = FALSE;
+				}
+			}
+		}
+	}
+
+	if (routes_prune) {
+		for (i = 0; i < routes_prune->len; i++) {
+			const NMPObject *prune_o;
+
+			prune_o = routes_prune->pdata[i];
+
+			nm_assert (   (addr_family == AF_INET  && NMP_OBJECT_GET_TYPE (prune_o) == NMP_OBJECT_TYPE_IP4_ROUTE)
+			           || (addr_family == AF_INET6 && NMP_OBJECT_GET_TYPE (prune_o) == NMP_OBJECT_TYPE_IP6_ROUTE));
+
+			if (   routes_idx
+			    && g_hash_table_lookup (routes_idx, prune_o))
+				continue;
+
+			if (!nm_platform_lookup_entry (self,
+			                               NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+			                               prune_o))
+				continue;
+
+			if (!nm_platform_ip_route_delete (self, prune_o)) {
+				/* ignore error... */
+			}
+		}
+	}
+
+	return success;
 }
 
 gboolean
-nm_platform_ip6_route_add (NMPlatform *self, const NMPlatformIP6Route *route)
+nm_platform_ip_route_flush (NMPlatform *self,
+                            int addr_family,
+                            int ifindex)
 {
+	gboolean success = TRUE;
+
 	_CHECK_SELF (self, klass, FALSE);
 
-	g_return_val_if_fail (route, FALSE);
-	g_return_val_if_fail (route->plen <= 128, FALSE);
+	nm_assert (NM_IN_SET (addr_family, AF_UNSPEC,
+	                                   AF_INET,
+	                                   AF_INET6));
 
-	_LOGD ("route: adding or updating IPv6 route: %s", nm_platform_ip6_route_to_string (route, NULL, 0));
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) {
+		gs_unref_ptrarray GPtrArray *routes_prune = NULL;
 
-	return klass->ip6_route_add (self, route);
+		routes_prune = nm_platform_ip_route_get_prune_list (self,
+		                                                    AF_INET,
+		                                                    ifindex,
+		                                                    NM_IP_ROUTE_TABLE_SYNC_MODE_ALL);
+		success &= nm_platform_ip_route_sync (self, AF_INET, ifindex, NULL, routes_prune, NULL);
+	}
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) {
+		gs_unref_ptrarray GPtrArray *routes_prune = NULL;
+
+		routes_prune = nm_platform_ip_route_get_prune_list (self,
+		                                                    AF_INET6,
+		                                                    ifindex,
+		                                                    NM_IP_ROUTE_TABLE_SYNC_MODE_ALL);
+		success &= nm_platform_ip_route_sync (self, AF_INET6, ifindex, NULL, routes_prune, NULL);
+	}
+	return success;
 }
 
-gboolean
-nm_platform_ip4_route_delete (NMPlatform *self, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
+/*****************************************************************************/
+
+static guint8
+_ip_route_scope_inv_get_normalized (const NMPlatformIP4Route *route)
+{
+	/* in kernel, you cannot set scope to RT_SCOPE_NOWHERE (255).
+	 * That means, in NM, we treat RT_SCOPE_NOWHERE as unset, and detect
+	 * it based on the presence of the gateway. In other words, when adding
+	 * a route with scope RT_SCOPE_NOWHERE (in NetworkManager) to kernel,
+	 * the resulting scope will be either "link" or "universe" (depending
+	 * on the gateway).
+	 *
+	 * Note that internally, we track @scope_inv is the inverse of scope,
+	 * so that the default equals zero (~(RT_SCOPE_NOWHERE)).
+	 **/
+	if (route->scope_inv == 0) {
+		return nm_platform_route_scope_inv (!route->gateway
+		                                    ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE);
+	}
+	return route->scope_inv;
+}
+
+static guint8
+_route_pref_normalize (guint8 pref)
 {
-	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	/* for kernel (and ICMPv6) pref can only have one of 3 values. Normalize. */
+	return NM_IN_SET (pref, NM_ICMPV6_ROUTER_PREF_LOW,
+	                        NM_ICMPV6_ROUTER_PREF_HIGH)
+	       ? pref
+	       : NM_ICMPV6_ROUTER_PREF_MEDIUM;
+}
+
+/**
+ * nm_platform_ip_route_normalize:
+ * @addr_family: AF_INET or AF_INET6
+ * @route: an NMPlatformIP4Route or NMPlatformIP6Route instance, depending on @addr_family.
+ *
+ * Adding a route to kernel via nm_platform_ip_route_add() will normalize/coerce some
+ * properties of the route. This function modifies (normalizes) the route like it
+ * would be done by adding the route in kernel.
+ *
+ * Note that this function is related to NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY
+ * in that if two routes compare semantically equal, after normalizing they also shall
+ * compare equal with NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL.
+ */
+void
+nm_platform_ip_route_normalize (int addr_family,
+                                NMPlatformIPRoute *route)
+{
+	NMPlatformIP4Route *r4;
+	NMPlatformIP6Route *r6;
+
+	switch (addr_family) {
+	case AF_INET:
+		r4 = (NMPlatformIP4Route *) route;
+		r4->table_coerced = nm_platform_route_table_coerce (nm_platform_route_table_uncoerce (r4->table_coerced, TRUE));
+		r4->network = nm_utils_ip4_address_clear_host_address (r4->network, r4->plen);
+		r4->rt_source = nmp_utils_ip_config_source_round_trip_rtprot (r4->rt_source);
+		r4->scope_inv = _ip_route_scope_inv_get_normalized (r4);
+		break;
+	case AF_INET6:
+		r6 = (NMPlatformIP6Route *) route;
+		r6->table_coerced = nm_platform_route_table_coerce (nm_platform_route_table_uncoerce (r6->table_coerced, TRUE));
+		nm_utils_ip6_address_clear_host_address (&r6->network, &r6->network, r6->plen);
+		r6->rt_source = nmp_utils_ip_config_source_round_trip_rtprot (r6->rt_source),
+		r6->metric = nm_utils_ip6_route_metric_normalize (r6->metric);
+		nm_utils_ip6_address_clear_host_address (&r6->src, &r6->src, r6->src_plen);
+		break;
+	default:
+		nm_assert_not_reached ();
+		break;
+	}
+}
+
+static NMPlatformError
+_ip_route_add (NMPlatform *self,
+               NMPNlmFlags flags,
+               int addr_family,
+               gconstpointer route)
+{
+	char sbuf[sizeof (_nm_utils_to_string_buffer)];
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	_LOGD ("route: deleting IPv4 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
-	       nm_utils_inet4_ntop (network, NULL), plen, metric, ifindex,
-	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
-	return klass->ip4_route_delete (self, ifindex, network, plen, metric);
+	nm_assert (route);
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	_LOGD ("route: %-10s IPv%c route: %s",
+	       _nmp_nlm_flag_to_string (flags & NMP_NLM_FLAG_FMASK),
+	       nm_utils_addr_family_to_char (addr_family),
+	       addr_family == AF_INET
+	         ? nm_platform_ip4_route_to_string (route, sbuf, sizeof (sbuf))
+	         : nm_platform_ip6_route_to_string (route, sbuf, sizeof (sbuf)));
+
+	return klass->ip_route_add (self, flags, addr_family, route);
+}
+
+NMPlatformError
+nm_platform_ip_route_add (NMPlatform *self,
+                          NMPNlmFlags flags,
+                          const NMPObject *route)
+{
+	int addr_family;
+
+	switch (NMP_OBJECT_GET_TYPE (route)) {
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		addr_family = AF_INET;
+		break;
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		addr_family = AF_INET6;
+		break;
+	default:
+		g_return_val_if_reached (FALSE);
+	}
+
+	return _ip_route_add (self, flags, addr_family, NMP_OBJECT_CAST_IP_ROUTE (route));
+}
+
+NMPlatformError
+nm_platform_ip4_route_add (NMPlatform *self,
+                           NMPNlmFlags flags,
+                           const NMPlatformIP4Route *route)
+{
+	return _ip_route_add (self, flags, AF_INET, route);
+}
+
+NMPlatformError
+nm_platform_ip6_route_add (NMPlatform *self,
+                           NMPNlmFlags flags,
+                           const NMPlatformIP6Route *route)
+{
+	return _ip_route_add (self, flags, AF_INET6, route);
 }
 
 gboolean
-nm_platform_ip6_route_delete (NMPlatform *self, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
+nm_platform_ip_route_delete (NMPlatform *self,
+                             const NMPObject *obj)
 {
-	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	_CHECK_SELF (self, klass, FALSE);
+
+	if (!NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                           NMP_OBJECT_TYPE_IP6_ROUTE))
+		g_return_val_if_reached (FALSE);
+
+	_LOGD ("route: delete     IPv%c route %s",
+	       NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_IP4_ROUTE ? '4' : '6',
+	       nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+
+	return klass->ip_route_delete (self, obj);
+}
+
+/*****************************************************************************/
+
+NMPlatformError
+nm_platform_ip_route_get (NMPlatform *self,
+                          int addr_family,
+                          gconstpointer address /* in_addr_t or struct in6_addr */,
+                          int oif_ifindex,
+                          NMPObject **out_route)
+{
+	nm_auto_nmpobj NMPObject *route = NULL;
+	NMPlatformError result;
+	char buf[NM_UTILS_INET_ADDRSTRLEN];
+	char buf_err[200];
+	char buf_oif[64];
 
 	_CHECK_SELF (self, klass, FALSE);
 
-	_LOGD ("route: deleting IPv6 route %s/%d, metric=%"G_GUINT32_FORMAT", ifindex %d%s",
-	       nm_utils_inet6_ntop (&network, NULL), plen, metric, ifindex,
-	       _to_string_dev (self, ifindex, str_dev, sizeof (str_dev)));
-	return klass->ip6_route_delete (self, ifindex, network, plen, metric);
+	g_return_val_if_fail (address, NM_PLATFORM_ERROR_BUG);
+	g_return_val_if_fail (NM_IN_SET (addr_family, AF_INET,
+	                                              AF_INET6), NM_PLATFORM_ERROR_BUG);
+
+	_LOGT ("route: get IPv%c route for: %s%s",
+	       nm_utils_addr_family_to_char (addr_family),
+	       inet_ntop (addr_family, address, buf, sizeof (buf)),
+	       oif_ifindex > 0 ? nm_sprintf_buf (buf_oif, " oif %d", oif_ifindex) : "");
+
+	if (!klass->ip_route_get)
+		result = NM_PLATFORM_ERROR_OPNOTSUPP;
+	else {
+		result = klass->ip_route_get (self,
+		                              addr_family,
+		                              address,
+		                              oif_ifindex,
+		                              &route);
+	}
+
+	if (result != NM_PLATFORM_ERROR_SUCCESS) {
+		nm_assert (!route);
+		_LOGW ("route: get IPv%c route for: %s failed with %s",
+		       nm_utils_addr_family_to_char (addr_family),
+		       inet_ntop (addr_family, address, buf, sizeof (buf)),
+		       nm_platform_error_to_string (result, buf_err, sizeof (buf_err)));
+	} else {
+		nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (route), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
+		nm_assert (!NMP_OBJECT_IS_STACKINIT (route));
+		nm_assert (route->parent._ref_count == 1);
+		_LOGD ("route: get IPv%c route for: %s succeeded: %s",
+		       nm_utils_addr_family_to_char (addr_family),
+		       inet_ntop (addr_family, address, buf, sizeof (buf)),
+		       nmp_object_to_string (route, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+		NM_SET_OUT (out_route, g_steal_pointer (&route));
+	}
+	return result;
 }
 
-const NMPlatformIP4Route *
-nm_platform_ip4_route_get (NMPlatform *self, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
+/*****************************************************************************/
+
+#define IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS   ((int) 1500)
+#define IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S ((int) (((IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS + 999) * 3) / 1000))
+
+static gint64
+_ip4_dev_route_blacklist_timeout_ms_get (gint64 timeout_ms)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	return timeout_ms >> 1;
+}
 
-	return klass->ip4_route_get (self ,ifindex, network, plen, metric);
+static gint64
+_ip4_dev_route_blacklist_timeout_ms_marked (gint64 timeout_ms)
+{
+	return !!(timeout_ms & ((gint64) 1));
 }
 
-const NMPlatformIP6Route *
-nm_platform_ip6_route_get (NMPlatform *self, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
+static gboolean
+_ip4_dev_route_blacklist_check_cb (gpointer user_data)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	NMPlatform *self = user_data;
+	NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (self);
+	GHashTableIter iter;
+	const NMPObject *p_obj;
+	gint64 *p_timeout_ms;
+	gint64 now_ms;
+
+	priv->ip4_dev_route_blacklist_check_id = 0;
+
+again:
+	if (!priv->ip4_dev_route_blacklist_hash)
+		goto out;
+
+	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+
+	g_hash_table_iter_init (&iter, priv->ip4_dev_route_blacklist_hash);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) {
+		if (!_ip4_dev_route_blacklist_timeout_ms_marked (*p_timeout_ms))
+			continue;
+
+		/* unmark because we checked it. */
+		*p_timeout_ms = *p_timeout_ms & ~((gint64) 1);
+
+		if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get (*p_timeout_ms))
+			continue;
+
+		if (!nm_platform_lookup_entry (self,
+		                               NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+		                               p_obj))
+			continue;
+
+		_LOGT ("ip4-dev-route: delete %s",
+		       nmp_object_to_string (p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+		nm_platform_ip_route_delete (self, p_obj);
+		goto again;
+	}
+
+out:
+	return G_SOURCE_REMOVE;
+}
+
+static void
+_ip4_dev_route_blacklist_check_schedule (NMPlatform *self)
+{
+	NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (self);
+
+	if (!priv->ip4_dev_route_blacklist_check_id) {
+		priv->ip4_dev_route_blacklist_check_id = g_idle_add_full (G_PRIORITY_HIGH,
+		                                                          _ip4_dev_route_blacklist_check_cb,
+		                                                          self,
+		                                                          NULL);
+	}
+}
+
+static void
+_ip4_dev_route_blacklist_notify_route (NMPlatform *self,
+                                       const NMPObject *obj)
+{
+	NMPlatformPrivate *priv;
+	const NMPObject *p_obj;
+	gint64 *p_timeout_ms;
+	gint64 now_ms;
+
+	nm_assert (NM_IS_PLATFORM (self));
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_IP4_ROUTE);
+
+	priv = NM_PLATFORM_GET_PRIVATE (self);
+
+	nm_assert (priv->ip4_dev_route_blacklist_gc_timeout_id);
+
+	if (!g_hash_table_lookup_extended (priv->ip4_dev_route_blacklist_hash,
+	                                   obj,
+	                                   (gpointer *) &p_obj,
+	                                   (gpointer *) &p_timeout_ms))
+		return;
+
+	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+	if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get (*p_timeout_ms)) {
+		/* already expired. Wait for gc. */
+		return;
+	}
+
+	if (_ip4_dev_route_blacklist_timeout_ms_marked (*p_timeout_ms)) {
+		nm_assert (priv->ip4_dev_route_blacklist_check_id);
+		return;
+	}
+
+	/* We cannot delete it right away because we are in the process of receiving netlink messages.
+	 * It may be possible to do so, but complicated and error prone.
+	 *
+	 * Instead, we mark the entry and schedule an idle action (with high priority). */
+	*p_timeout_ms = (*p_timeout_ms) | ((gint64) 1);
+	_ip4_dev_route_blacklist_check_schedule (self);
+}
+
+static gboolean
+_ip4_dev_route_blacklist_gc_timeout_handle (gpointer user_data)
+{
+	NMPlatform *self = user_data;
+	NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (self);
+	GHashTableIter iter;
+	const NMPObject *p_obj;
+	gint64 *p_timeout_ms;
+	gint64 now_ms;
+
+	nm_assert (priv->ip4_dev_route_blacklist_gc_timeout_id);
+
+	now_ms = nm_utils_get_monotonic_timestamp_ms ();
+
+	g_hash_table_iter_init (&iter, priv->ip4_dev_route_blacklist_hash);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) {
+		if (now_ms > _ip4_dev_route_blacklist_timeout_ms_get (*p_timeout_ms)) {
+			_LOGT ("ip4-dev-route: cleanup %s",
+			       nmp_object_to_string (p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+			g_hash_table_iter_remove (&iter);
+		}
+	}
+
+	_ip4_dev_route_blacklist_schedule (self);
+	return G_SOURCE_CONTINUE;
+}
+
+static void
+_ip4_dev_route_blacklist_schedule (NMPlatform *self)
+{
+	NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (self);
+
+	if (   !priv->ip4_dev_route_blacklist_hash
+	    || g_hash_table_size (priv->ip4_dev_route_blacklist_hash) == 0) {
+		g_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
+		nm_clear_g_source (&priv->ip4_dev_route_blacklist_gc_timeout_id);
+	} else {
+		if (!priv->ip4_dev_route_blacklist_gc_timeout_id) {
+			/* this timeout is only to garbage collect the expired entries from priv->ip4_dev_route_blacklist_hash.
+			 * It can run infrequently, and it doesn't hurt if expired entries linger around a bit
+			 * longer then necessary. */
+			priv->ip4_dev_route_blacklist_gc_timeout_id = g_timeout_add_seconds (IP4_DEV_ROUTE_BLACKLIST_GC_TIMEOUT_S,
+			                                                                     _ip4_dev_route_blacklist_gc_timeout_handle,
+			                                                                     self);
+		}
+	}
+}
+
+/**
+ * nm_platform_ip4_dev_route_blacklist_set:
+ * @self:
+ * @ifindex:
+ * @ip4_dev_route_blacklist:
+ *
+ * When adding an IP address, kernel automatically adds a device route.
+ * This can be suppressed via the IFA_F_NOPREFIXROUTE address flag. For proper
+ * IPv6 support, we require kernel support for IFA_F_NOPREFIXROUTE and always
+ * add the device route manually.
+ *
+ * For IPv4, this flag is rather new and we don't rely on it yet. We want to use
+ * it (but currently still don't). So, for IPv4, kernel possibly adds a device
+ * route, however it has a wrong metric of zero. We add our own device route (with
+ * proper metric), but need to delete the route that kernel adds.
+ *
+ * The problem is, that kernel does not immidiately add the route, when adding
+ * the address. It only shows up some time later. So, we register here a list
+ * of blacklisted routes, and when they show up within a time out, we assume it's
+ * the kernel generated one, and we delete it.
+ *
+ * Eventually, we want to get rid of this and use IFA_F_NOPREFIXROUTE for IPv4
+ * routes as well.
+ */
+void
+nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
+                                         int ifindex,
+                                         GPtrArray *ip4_dev_route_blacklist)
+{
+	NMPlatformPrivate *priv;
+	GHashTableIter iter;
+	const NMPObject *p_obj;
+	guint i;
+	gint64 timeout_ms;
+	gint64 timeout_ms_val;
+	gint64 *p_timeout_ms;
+	gboolean needs_check = FALSE;
+
+	nm_assert (NM_IS_PLATFORM (self));
+	nm_assert (ifindex > 0);
+
+	priv = NM_PLATFORM_GET_PRIVATE (self);
+
+	/* first, expire all for current ifindex... */
+	if (priv->ip4_dev_route_blacklist_hash) {
+		g_hash_table_iter_init (&iter, priv->ip4_dev_route_blacklist_hash);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &p_obj, (gpointer *) &p_timeout_ms)) {
+			if (NMP_OBJECT_CAST_IP4_ROUTE (p_obj)->ifindex == ifindex) {
+				/* we could g_hash_table_iter_remove(&iter) the current entry.
+				 * Instead, just expire it and let _ip4_dev_route_blacklist_gc_timeout_handle()
+				 * handle it.
+				 *
+				 * The assumption is, that ip4_dev_route_blacklist contains the very same entry
+				 * again, with a new timeout. So, we can un-expire it below. */
+				*p_timeout_ms = 0;
+			}
+		}
+	}
+
+	if (   ip4_dev_route_blacklist
+	    && ip4_dev_route_blacklist->len > 0) {
+
+		if (!priv->ip4_dev_route_blacklist_hash) {
+			priv->ip4_dev_route_blacklist_hash = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
+			                                                            (GEqualFunc) nmp_object_id_equal,
+			                                                            (GDestroyNotify) nmp_object_unref,
+			                                                            nm_g_slice_free_fcn_gint64);
+		}
+
+		timeout_ms = nm_utils_get_monotonic_timestamp_ms () + IP4_DEV_ROUTE_BLACKLIST_TIMEOUT_MS;
+		timeout_ms_val = (timeout_ms << 1) | ((gint64) 1);
+		for (i = 0; i < ip4_dev_route_blacklist->len; i++) {
+			const NMPObject *o;
+
+			needs_check = TRUE;
+			o = ip4_dev_route_blacklist->pdata[i];
+			if (g_hash_table_lookup_extended (priv->ip4_dev_route_blacklist_hash,
+			                                  o,
+			                                  (gpointer *) &p_obj,
+			                                  (gpointer *) &p_timeout_ms)) {
+				if (nmp_object_equal (p_obj, o)) {
+					/* un-expire and reuse the entry. */
+					_LOGT ("ip4-dev-route: register %s (update)",
+					       nmp_object_to_string (p_obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+					*p_timeout_ms = timeout_ms_val;
+					continue;
+				}
+			}
+
+			_LOGT ("ip4-dev-route: register %s",
+			       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+			p_timeout_ms = g_slice_new (gint64);
+			*p_timeout_ms = timeout_ms_val;
+			g_hash_table_replace (priv->ip4_dev_route_blacklist_hash,
+			                      (gpointer) nmp_object_ref (o),
+			                      p_timeout_ms);
+		}
+	}
+
+	_ip4_dev_route_blacklist_schedule (self);
 
-	return klass->ip6_route_get (self, ifindex, network, plen, metric);
+	if (needs_check)
+		_ip4_dev_route_blacklist_check_schedule (self);
 }
 
 /*****************************************************************************/
@@ -3898,6 +4994,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN];
 	char s_pref_src[INET_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	char str_table[30];
 	char str_scope[30], s_source[50];
 	char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
 
@@ -3909,20 +5006,9 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
-	if (route->tos)
-		nm_sprintf_buf (str_tos, " tos 0x%x", (unsigned) route->tos);
-	if (route->window)
-		nm_sprintf_buf (str_window, " window %s%"G_GUINT32_FORMAT, route->lock_window ? "lock " : "", route->window);
-	if (route->cwnd)
-		nm_sprintf_buf (str_cwnd, " cwnd %s%"G_GUINT32_FORMAT, route->lock_cwnd ? "lock " : "", route->cwnd);
-	if (route->initcwnd)
-		nm_sprintf_buf (str_initcwnd, " initcwnd %s%"G_GUINT32_FORMAT, route->lock_initcwnd ? "lock " : "", route->initcwnd);
-	if (route->initrwnd)
-		nm_sprintf_buf (str_initrwnd, " initrwnd %s%"G_GUINT32_FORMAT, route->lock_initrwnd ? "lock " : "", route->initrwnd);
-	if (route->mtu)
-		nm_sprintf_buf (str_mtu, " mtu %s%"G_GUINT32_FORMAT, route->lock_mtu ? "lock " : "", route->mtu);
 
 	g_snprintf (buf, len,
+	            "%s" /* table */
 	            "%s/%d"
 	            " via %s"
 	            "%s"
@@ -3939,6 +5025,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            "%s" /* initrwnd */
 	            "%s" /* mtu */
 	            "",
+	            route->table_coerced ? nm_sprintf_buf (str_table, "table %u ", nm_platform_route_table_uncoerce (route->table_coerced, FALSE)) : "",
 	            s_network,
 	            route->plen,
 	            s_gateway,
@@ -3951,12 +5038,12 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            route->scope_inv ? (nm_platform_route_scope2str (nm_platform_route_scope_inv (route->scope_inv), str_scope, sizeof (str_scope))) : "",
 	            route->pref_src ? " pref-src " : "",
 	            route->pref_src ? inet_ntop (AF_INET, &route->pref_src, s_pref_src, sizeof(s_pref_src)) : "",
-	            route->tos ? str_tos : "",
-	            route->window ? str_window : "",
-	            route->cwnd ? str_cwnd : "",
-	            route->initcwnd ? str_initcwnd : "",
-	            route->initrwnd ? str_initrwnd : "",
-	            route->mtu ? str_mtu : "");
+	            route->tos ? nm_sprintf_buf (str_tos, " tos 0x%x", (unsigned) route->tos) : "",
+	            route->window   || route->lock_window   ? nm_sprintf_buf (str_window,   " window %s%"G_GUINT32_FORMAT,   route->lock_window   ? "lock " : "", route->window)   : "",
+	            route->cwnd     || route->lock_cwnd     ? nm_sprintf_buf (str_cwnd,     " cwnd %s%"G_GUINT32_FORMAT,     route->lock_cwnd     ? "lock " : "", route->cwnd)     : "",
+	            route->initcwnd || route->lock_initcwnd ? nm_sprintf_buf (str_initcwnd, " initcwnd %s%"G_GUINT32_FORMAT, route->lock_initcwnd ? "lock " : "", route->initcwnd) : "",
+	            route->initrwnd || route->lock_initrwnd ? nm_sprintf_buf (str_initrwnd, " initrwnd %s%"G_GUINT32_FORMAT, route->lock_initrwnd ? "lock " : "", route->initrwnd) : "",
+	            route->mtu      || route->lock_mtu      ? nm_sprintf_buf (str_mtu,      " mtu %s%"G_GUINT32_FORMAT,      route->lock_mtu      ? "lock " : "", route->mtu)      : "");
 	return buf;
 }
 
@@ -3976,16 +5063,18 @@ const char *
 nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsize len)
 {
 	char s_network[INET6_ADDRSTRLEN], s_gateway[INET6_ADDRSTRLEN], s_pref_src[INET6_ADDRSTRLEN];
-	char s_src[INET6_ADDRSTRLEN];
+	char s_src_all[INET6_ADDRSTRLEN + 40], s_src[INET6_ADDRSTRLEN];
+	char str_table[30];
+	char str_pref[40];
+	char str_pref2[30];
 	char str_dev[TO_STRING_DEV_BUF_SIZE], s_source[50];
-	char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
+	char str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
 		return buf;
 
 	inet_ntop (AF_INET6, &route->network, s_network, sizeof (s_network));
 	inet_ntop (AF_INET6, &route->gateway, s_gateway, sizeof (s_gateway));
-	inet_ntop (AF_INET6, &route->src, s_src, sizeof (s_src));
 
 	if (IN6_IS_ADDR_UNSPECIFIED (&route->pref_src))
 		s_pref_src[0] = 0;
@@ -3994,36 +5083,25 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
-	if (route->tos)
-		nm_sprintf_buf (str_tos, " tos 0x%x", (unsigned) route->tos);
-	if (route->window)
-		nm_sprintf_buf (str_window, " window %s%"G_GUINT32_FORMAT, route->lock_window ? "lock " : "", route->window);
-	if (route->cwnd)
-		nm_sprintf_buf (str_cwnd, " cwnd %s%"G_GUINT32_FORMAT, route->lock_cwnd ? "lock " : "", route->cwnd);
-	if (route->initcwnd)
-		nm_sprintf_buf (str_initcwnd, " initcwnd %s%"G_GUINT32_FORMAT, route->lock_initcwnd ? "lock " : "", route->initcwnd);
-	if (route->initrwnd)
-		nm_sprintf_buf (str_initrwnd, " initrwnd %s%"G_GUINT32_FORMAT, route->lock_initrwnd ? "lock " : "", route->initrwnd);
-	if (route->mtu)
-		nm_sprintf_buf (str_mtu, " mtu %s%"G_GUINT32_FORMAT, route->lock_mtu ? "lock " : "", route->mtu);
-
 	g_snprintf (buf, len,
+	            "%s" /* table */
 	            "%s/%d"
 	            " via %s"
 	            "%s"
 	            " metric %"G_GUINT32_FORMAT
 	            " mss %"G_GUINT32_FORMAT
 	            " rt-src %s" /* protocol */
-	            " src %s/%u" /* source */
+	            "%s" /* source */
 	            "%s" /* cloned */
 	            "%s%s" /* pref-src */
-	            "%s" /* tos */
 	            "%s" /* window */
 	            "%s" /* cwnd */
 	            "%s" /* initcwnd */
 	            "%s" /* initrwnd */
 	            "%s" /* mtu */
+	            "%s" /* pref */
 	            "",
+	            route->table_coerced ? nm_sprintf_buf (str_table, "table %u ", nm_platform_route_table_uncoerce (route->table_coerced, FALSE)) : "",
 	            s_network,
 	            route->plen,
 	            s_gateway,
@@ -4031,358 +5109,724 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            route->metric,
 	            route->mss,
 	            nmp_utils_ip_config_source_to_string (route->rt_source, s_source, sizeof (s_source)),
-	            s_src, route->src_plen,
+	            route->src_plen || !IN6_IS_ADDR_UNSPECIFIED (&route->src)
+	              ? nm_sprintf_buf (s_src_all, " src %s/%u", nm_utils_inet6_ntop (&route->src, s_src), (unsigned) route->src_plen)
+	              : "",
 	            route->rt_cloned ? " cloned" : "",
 	            s_pref_src[0] ? " pref-src " : "",
 	            s_pref_src[0] ? s_pref_src : "",
-	            route->tos ? str_tos : "",
-	            route->window ? str_window : "",
-	            route->cwnd ? str_cwnd : "",
-	            route->initcwnd ? str_initcwnd : "",
-	            route->initrwnd ? str_initrwnd : "",
-	            route->mtu ? str_mtu : "");
+	            route->window   || route->lock_window   ? nm_sprintf_buf (str_window,   " window %s%"G_GUINT32_FORMAT,   route->lock_window   ? "lock " : "", route->window)   : "",
+	            route->cwnd     || route->lock_cwnd     ? nm_sprintf_buf (str_cwnd,     " cwnd %s%"G_GUINT32_FORMAT,     route->lock_cwnd     ? "lock " : "", route->cwnd)     : "",
+	            route->initcwnd || route->lock_initcwnd ? nm_sprintf_buf (str_initcwnd, " initcwnd %s%"G_GUINT32_FORMAT, route->lock_initcwnd ? "lock " : "", route->initcwnd) : "",
+	            route->initrwnd || route->lock_initrwnd ? nm_sprintf_buf (str_initrwnd, " initrwnd %s%"G_GUINT32_FORMAT, route->lock_initrwnd ? "lock " : "", route->initrwnd) : "",
+	            route->mtu      || route->lock_mtu      ? nm_sprintf_buf (str_mtu,      " mtu %s%"G_GUINT32_FORMAT,      route->lock_mtu      ? "lock " : "", route->mtu)      : "",
+	            route->rt_pref ? nm_sprintf_buf (str_pref, " pref %s", nm_icmpv6_router_pref_to_string (route->rt_pref, str_pref2, sizeof (str_pref2))) : "");
 
 	return buf;
 }
 
-#define _CMP_SELF(a, b)                                     \
-    G_STMT_START {                                          \
-        if ((a) == (b))                                     \
-            return 0;                                       \
-        if (!(a))                                           \
-            return -1;                                      \
-        if (!(b))                                           \
-            return 1;                                       \
-    } G_STMT_END
-
-#define _CMP_DIRECT(a, b)                                   \
-    G_STMT_START {                                          \
-        if ((a) != (b))                                     \
-            return ((a) < (b)) ? -1 : 1;                    \
-    } G_STMT_END
-
-#define _CMP_DIRECT_MEMCMP(a, b, size)                      \
-    G_STMT_START {                                          \
-        int c = memcmp ((a), (b), (size));                  \
-        if (c != 0)                                         \
-            return c < 0 ? -1 : 1;                          \
-    } G_STMT_END
-
-#define _CMP_FIELD(a, b, field)                             \
-    G_STMT_START {                                          \
-        if (((a)->field) != ((b)->field))                   \
-            return (((a)->field) < ((b)->field)) ? -1 : 1;  \
-    } G_STMT_END
-
-#define _CMP_FIELD_BOOL(a, b, field)                        \
-    G_STMT_START {                                          \
-        if ((!((a)->field)) != (!((b)->field)))                 \
-            return ((!((a)->field)) < (!((b)->field))) ? -1 : 1; \
-    } G_STMT_END
-
-#define _CMP_FIELD_STR(a, b, field)                         \
-    G_STMT_START {                                          \
-        int c = strcmp ((a)->field, (b)->field);            \
-        if (c != 0)                                         \
-            return c < 0 ? -1 : 1;                          \
-    } G_STMT_END
-
-#define _CMP_FIELD_STR_INTERNED(a, b, field)                \
-    G_STMT_START {                                          \
-        if (((a)->field) != ((b)->field)) {                 \
-            /* just to be sure, also do a strcmp() if the pointers don't match */ \
-            int c = g_strcmp0 ((a)->field, (b)->field);     \
-            if (c != 0)                                     \
-                return c < 0 ? -1 : 1;                      \
-        } \
-    } G_STMT_END
-
-#define _CMP_FIELD_STR0(a, b, field)                        \
-    G_STMT_START {                                          \
-        int c = g_strcmp0 ((a)->field, (b)->field);         \
-        if (c != 0)                                         \
-            return c < 0 ? -1 : 1;                          \
-    } G_STMT_END
-
-#define _CMP_FIELD_MEMCMP_LEN(a, b, field, len)             \
-    G_STMT_START {                                          \
-        int c = memcmp (&((a)->field), &((b)->field),       \
-                        MIN (len, sizeof ((a)->field)));    \
-        if (c != 0)                                         \
-            return c < 0 ? -1 : 1;                          \
-    } G_STMT_END
-
-#define _CMP_FIELD_MEMCMP(a, b, field)                      \
-    G_STMT_START {                                          \
-        int c = memcmp (&((a)->field), &((b)->field),       \
-                        sizeof ((a)->field));               \
-        if (c != 0)                                         \
-            return c < 0 ? -1 : 1;                          \
-    } G_STMT_END
+void
+nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->master,
+	                     obj->parent,
+	                     obj->n_ifi_flags,
+	                     obj->mtu,
+	                     obj->type,
+	                     obj->arptype,
+	                     obj->inet6_addr_gen_mode_inv,
+	                     obj->inet6_token,
+	                     obj->rx_packets,
+	                     obj->rx_bytes,
+	                     obj->tx_packets,
+	                     obj->tx_bytes,
+	                     NM_HASH_COMBINE_BOOLS (guint8,
+	                                            obj->connected,
+	                                            obj->initialized));
+	nm_hash_update_strarr (h, obj->name);
+	nm_hash_update_str0 (h, obj->kind);
+	nm_hash_update_str0 (h, obj->driver);
+	/* nm_hash_update_mem() also hashes the length obj->addr.len */
+	nm_hash_update_mem (h, obj->addr.data, obj->addr.len);
+}
 
 int
 nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, ifindex);
-	_CMP_FIELD (a, b, type);
-	_CMP_FIELD_STR (a, b, name);
-	_CMP_FIELD (a, b, master);
-	_CMP_FIELD (a, b, parent);
-	_CMP_FIELD (a, b, n_ifi_flags);
-	_CMP_FIELD (a, b, connected);
-	_CMP_FIELD (a, b, mtu);
-	_CMP_FIELD_BOOL (a, b, initialized);
-	_CMP_FIELD (a, b, arptype);
-	_CMP_FIELD (a, b, addr.len);
-	_CMP_FIELD (a, b, inet6_addr_gen_mode_inv);
-	_CMP_FIELD_STR_INTERNED (a, b, kind);
-	_CMP_FIELD_STR_INTERNED (a, b, driver);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, ifindex);
+	NM_CMP_FIELD (a, b, type);
+	NM_CMP_FIELD_STR (a, b, name);
+	NM_CMP_FIELD (a, b, master);
+	NM_CMP_FIELD (a, b, parent);
+	NM_CMP_FIELD (a, b, n_ifi_flags);
+	NM_CMP_FIELD_UNSAFE (a, b, connected);
+	NM_CMP_FIELD (a, b, mtu);
+	NM_CMP_FIELD_BOOL (a, b, initialized);
+	NM_CMP_FIELD (a, b, arptype);
+	NM_CMP_FIELD (a, b, addr.len);
+	NM_CMP_FIELD (a, b, inet6_addr_gen_mode_inv);
+	NM_CMP_FIELD_STR_INTERNED (a, b, kind);
+	NM_CMP_FIELD_STR_INTERNED (a, b, driver);
 	if (a->addr.len)
-		_CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len);
-	_CMP_FIELD_MEMCMP (a, b, inet6_token);
-	_CMP_FIELD (a, b, rx_packets);
-	_CMP_FIELD (a, b, rx_bytes);
-	_CMP_FIELD (a, b, tx_packets);
-	_CMP_FIELD (a, b, tx_bytes);
+		NM_CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len);
+	NM_CMP_FIELD_MEMCMP (a, b, inet6_token);
+	NM_CMP_FIELD (a, b, rx_packets);
+	NM_CMP_FIELD (a, b, rx_bytes);
+	NM_CMP_FIELD (a, b, tx_packets);
+	NM_CMP_FIELD (a, b, tx_bytes);
 	return 0;
 }
 
+void
+nm_platform_lnk_gre_hash_update (const NMPlatformLnkGre *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->local,
+	                     obj->remote,
+	                     obj->parent_ifindex,
+	                     obj->input_flags,
+	                     obj->output_flags,
+	                     obj->input_key,
+	                     obj->output_key,
+	                     obj->ttl,
+	                     obj->tos,
+	                     (bool) obj->path_mtu_discovery);
+}
+
 int
 nm_platform_lnk_gre_cmp (const NMPlatformLnkGre *a, const NMPlatformLnkGre *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, parent_ifindex);
-	_CMP_FIELD (a, b, input_flags);
-	_CMP_FIELD (a, b, output_flags);
-	_CMP_FIELD (a, b, input_key);
-	_CMP_FIELD (a, b, output_key);
-	_CMP_FIELD (a, b, local);
-	_CMP_FIELD (a, b, remote);
-	_CMP_FIELD (a, b, ttl);
-	_CMP_FIELD (a, b, tos);
-	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, parent_ifindex);
+	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 (a, b, local);
+	NM_CMP_FIELD (a, b, remote);
+	NM_CMP_FIELD (a, b, ttl);
+	NM_CMP_FIELD (a, b, tos);
+	NM_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
 	return 0;
 }
 
+void
+nm_platform_lnk_infiniband_hash_update (const NMPlatformLnkInfiniband *obj, NMHashState *h)
+{
+	nm_hash_update_val (h, obj->p_key);
+	nm_hash_update_str0 (h, obj->mode);
+}
+
 int
 nm_platform_lnk_infiniband_cmp (const NMPlatformLnkInfiniband *a, const NMPlatformLnkInfiniband *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, p_key);
-	_CMP_FIELD_STR_INTERNED (a, b, mode);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, p_key);
+	NM_CMP_FIELD_STR_INTERNED (a, b, mode);
 	return 0;
 }
 
+void
+nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->local,
+	                     obj->remote,
+	                     obj->parent_ifindex,
+	                     obj->ttl,
+	                     obj->tclass,
+	                     obj->encap_limit,
+	                     obj->proto,
+	                     obj->flow_label);
+}
+
 int
 nm_platform_lnk_ip6tnl_cmp (const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, parent_ifindex);
-	_CMP_FIELD_MEMCMP (a, b, local);
-	_CMP_FIELD_MEMCMP (a, b, remote);
-	_CMP_FIELD (a, b, ttl);
-	_CMP_FIELD (a, b, tclass);
-	_CMP_FIELD (a, b, encap_limit);
-	_CMP_FIELD (a, b, flow_label);
-	_CMP_FIELD (a, b, proto);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, parent_ifindex);
+	NM_CMP_FIELD_MEMCMP (a, b, local);
+	NM_CMP_FIELD_MEMCMP (a, b, remote);
+	NM_CMP_FIELD (a, b, ttl);
+	NM_CMP_FIELD (a, b, tclass);
+	NM_CMP_FIELD (a, b, encap_limit);
+	NM_CMP_FIELD (a, b, flow_label);
+	NM_CMP_FIELD (a, b, proto);
 	return 0;
 }
 
+void
+nm_platform_lnk_ipip_hash_update (const NMPlatformLnkIpIp *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->local,
+	                     obj->remote,
+	                     obj->parent_ifindex,
+	                     obj->ttl,
+	                     obj->tos,
+	                     (bool) obj->path_mtu_discovery);
+}
+
 int
 nm_platform_lnk_ipip_cmp (const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, parent_ifindex);
-	_CMP_FIELD (a, b, local);
-	_CMP_FIELD (a, b, remote);
-	_CMP_FIELD (a, b, ttl);
-	_CMP_FIELD (a, b, tos);
-	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, parent_ifindex);
+	NM_CMP_FIELD (a, b, local);
+	NM_CMP_FIELD (a, b, remote);
+	NM_CMP_FIELD (a, b, ttl);
+	NM_CMP_FIELD (a, b, tos);
+	NM_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
 	return 0;
 }
 
+void
+nm_platform_lnk_macsec_hash_update (const NMPlatformLnkMacsec *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->parent_ifindex,
+	                     obj->sci,
+	                     obj->cipher_suite,
+	                     obj->window,
+	                     obj->icv_length,
+	                     obj->encoding_sa,
+	                     obj->validation,
+	                     NM_HASH_COMBINE_BOOLS (guint8,
+	                                            obj->encrypt,
+	                                            obj->protect,
+	                                            obj->include_sci,
+	                                            obj->es,
+	                                            obj->scb,
+	                                            obj->replay_protect));
+}
+
 int
 nm_platform_lnk_macsec_cmp (const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, sci);
-	_CMP_FIELD (a, b, icv_length);
-	_CMP_FIELD (a, b, cipher_suite);
-	_CMP_FIELD (a, b, window);
-	_CMP_FIELD (a, b, encoding_sa);
-	_CMP_FIELD (a, b, validation);
-	_CMP_FIELD (a, b, encrypt);
-	_CMP_FIELD (a, b, protect);
-	_CMP_FIELD (a, b, include_sci);
-	_CMP_FIELD (a, b, es);
-	_CMP_FIELD (a, b, scb);
-	_CMP_FIELD (a, b, replay_protect);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, parent_ifindex);
+	NM_CMP_FIELD (a, b, sci);
+	NM_CMP_FIELD (a, b, icv_length);
+	NM_CMP_FIELD (a, b, cipher_suite);
+	NM_CMP_FIELD (a, b, window);
+	NM_CMP_FIELD (a, b, encoding_sa);
+	NM_CMP_FIELD (a, b, validation);
+	NM_CMP_FIELD_UNSAFE (a, b, encrypt);
+	NM_CMP_FIELD_UNSAFE (a, b, protect);
+	NM_CMP_FIELD_UNSAFE (a, b, include_sci);
+	NM_CMP_FIELD_UNSAFE (a, b, es);
+	NM_CMP_FIELD_UNSAFE (a, b, scb);
+	NM_CMP_FIELD_UNSAFE (a, b, replay_protect);
 	return 0;
 }
 
+void
+nm_platform_lnk_macvlan_hash_update (const NMPlatformLnkMacvlan *obj, NMHashState *h )
+{
+	nm_hash_update_vals (h,
+	                     obj->mode,
+	                     NM_HASH_COMBINE_BOOLS (guint8,
+	                                            obj->no_promisc,
+	                                            obj->tap));
+}
+
 int
 nm_platform_lnk_macvlan_cmp (const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, mode);
-	_CMP_FIELD_BOOL (a, b, no_promisc);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, mode);
+	NM_CMP_FIELD_UNSAFE (a, b, no_promisc);
+	NM_CMP_FIELD_UNSAFE (a, b, tap);
 	return 0;
 }
 
+void
+nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->local,
+	                     obj->remote,
+	                     obj->parent_ifindex,
+	                     obj->flags,
+	                     obj->ttl,
+	                     obj->tos,
+	                     obj->proto,
+	                     (bool) obj->path_mtu_discovery);
+}
+
 int
 nm_platform_lnk_sit_cmp (const NMPlatformLnkSit *a, const NMPlatformLnkSit *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, parent_ifindex);
-	_CMP_FIELD (a, b, local);
-	_CMP_FIELD (a, b, remote);
-	_CMP_FIELD (a, b, ttl);
-	_CMP_FIELD (a, b, tos);
-	_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
-	_CMP_FIELD (a, b, flags);
-	_CMP_FIELD (a, b, proto);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, parent_ifindex);
+	NM_CMP_FIELD (a, b, local);
+	NM_CMP_FIELD (a, b, remote);
+	NM_CMP_FIELD (a, b, ttl);
+	NM_CMP_FIELD (a, b, tos);
+	NM_CMP_FIELD_BOOL (a, b, path_mtu_discovery);
+	NM_CMP_FIELD (a, b, flags);
+	NM_CMP_FIELD (a, b, proto);
 	return 0;
 }
 
+void
+nm_platform_lnk_vlan_hash_update (const NMPlatformLnkVlan *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->id,
+	                     obj->flags);
+}
+
 int
 nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVlan *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, id);
-	_CMP_FIELD (a, b, flags);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, id);
+	NM_CMP_FIELD (a, b, flags);
 	return 0;
 }
 
+void
+nm_platform_lnk_vxlan_hash_update (const NMPlatformLnkVxlan *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->group6,
+	                     obj->local6,
+	                     obj->group,
+	                     obj->local,
+	                     obj->parent_ifindex,
+	                     obj->id,
+	                     obj->ageing,
+	                     obj->limit,
+	                     obj->dst_port,
+	                     obj->src_port_min,
+	                     obj->src_port_max,
+	                     obj->tos,
+	                     obj->ttl,
+	                     NM_HASH_COMBINE_BOOLS (guint8,
+	                                            obj->learning,
+	                                            obj->proxy,
+	                                            obj->rsc,
+	                                            obj->l2miss,
+	                                            obj->l3miss));
+}
+
 int
 nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, parent_ifindex);
-	_CMP_FIELD (a, b, id);
-	_CMP_FIELD (a, b, group);
-	_CMP_FIELD (a, b, local);
-	_CMP_FIELD_MEMCMP (a, b, group6);
-	_CMP_FIELD_MEMCMP (a, b, local6);
-	_CMP_FIELD (a, b, tos);
-	_CMP_FIELD (a, b, ttl);
-	_CMP_FIELD_BOOL (a, b, learning);
-	_CMP_FIELD (a, b, ageing);
-	_CMP_FIELD (a, b, limit);
-	_CMP_FIELD (a, b, dst_port);
-	_CMP_FIELD (a, b, src_port_min);
-	_CMP_FIELD (a, b, src_port_max);
-	_CMP_FIELD_BOOL (a, b, proxy);
-	_CMP_FIELD_BOOL (a, b, rsc);
-	_CMP_FIELD_BOOL (a, b, l2miss);
-	_CMP_FIELD_BOOL (a, b, l3miss);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, parent_ifindex);
+	NM_CMP_FIELD (a, b, id);
+	NM_CMP_FIELD (a, b, group);
+	NM_CMP_FIELD (a, b, local);
+	NM_CMP_FIELD_MEMCMP (a, b, group6);
+	NM_CMP_FIELD_MEMCMP (a, b, local6);
+	NM_CMP_FIELD (a, b, tos);
+	NM_CMP_FIELD (a, b, ttl);
+	NM_CMP_FIELD_BOOL (a, b, learning);
+	NM_CMP_FIELD (a, b, ageing);
+	NM_CMP_FIELD (a, b, limit);
+	NM_CMP_FIELD (a, b, dst_port);
+	NM_CMP_FIELD (a, b, src_port_min);
+	NM_CMP_FIELD (a, b, src_port_max);
+	NM_CMP_FIELD_BOOL (a, b, proxy);
+	NM_CMP_FIELD_BOOL (a, b, rsc);
+	NM_CMP_FIELD_BOOL (a, b, l2miss);
+	NM_CMP_FIELD_BOOL (a, b, l3miss);
 	return 0;
 }
 
+void
+nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->addr_source,
+	                     obj->timestamp,
+	                     obj->lifetime,
+	                     obj->preferred,
+	                     obj->n_ifa_flags,
+	                     obj->plen,
+	                     obj->address,
+	                     obj->peer_address);
+	nm_hash_update_strarr (h, obj->label);
+}
+
 int
 nm_platform_ip4_address_cmp (const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
 {
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, ifindex);
-	_CMP_FIELD (a, b, address);
-	_CMP_FIELD (a, b, plen);
-	_CMP_FIELD (a, b, peer_address);
-	_CMP_FIELD (a, b, addr_source);
-	_CMP_FIELD (a, b, timestamp);
-	_CMP_FIELD (a, b, lifetime);
-	_CMP_FIELD (a, b, preferred);
-	_CMP_FIELD (a, b, n_ifa_flags);
-	_CMP_FIELD_STR (a, b, label);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, ifindex);
+	NM_CMP_FIELD (a, b, address);
+	NM_CMP_FIELD (a, b, plen);
+	NM_CMP_FIELD (a, b, peer_address);
+	NM_CMP_FIELD (a, b, addr_source);
+	NM_CMP_FIELD (a, b, timestamp);
+	NM_CMP_FIELD (a, b, lifetime);
+	NM_CMP_FIELD (a, b, preferred);
+	NM_CMP_FIELD (a, b, n_ifa_flags);
+	NM_CMP_FIELD_STR (a, b, label);
 	return 0;
 }
 
+void
+nm_platform_ip6_address_hash_update (const NMPlatformIP6Address *obj, NMHashState *h)
+{
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->addr_source,
+	                     obj->timestamp,
+	                     obj->lifetime,
+	                     obj->preferred,
+	                     obj->n_ifa_flags,
+	                     obj->plen,
+	                     obj->address,
+	                     obj->peer_address);
+}
+
 int
 nm_platform_ip6_address_cmp (const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
 {
 	const struct in6_addr *p_a, *p_b;
 
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, ifindex);
-	_CMP_FIELD_MEMCMP (a, b, address);
-	_CMP_FIELD (a, b, plen);
+	NM_CMP_SELF (a, b);
+	NM_CMP_FIELD (a, b, ifindex);
+	NM_CMP_FIELD_MEMCMP (a, b, address);
+	NM_CMP_FIELD (a, b, plen);
 	p_a = nm_platform_ip6_address_get_peer (a);
 	p_b = nm_platform_ip6_address_get_peer (b);
-	_CMP_DIRECT_MEMCMP (p_a, p_b, sizeof (*p_a));
-	_CMP_FIELD (a, b, addr_source);
-	_CMP_FIELD (a, b, timestamp);
-	_CMP_FIELD (a, b, lifetime);
-	_CMP_FIELD (a, b, preferred);
-	_CMP_FIELD (a, b, n_ifa_flags);
+	NM_CMP_DIRECT_MEMCMP (p_a, p_b, sizeof (*p_a));
+	NM_CMP_FIELD (a, b, addr_source);
+	NM_CMP_FIELD (a, b, timestamp);
+	NM_CMP_FIELD (a, b, lifetime);
+	NM_CMP_FIELD (a, b, preferred);
+	NM_CMP_FIELD (a, b, n_ifa_flags);
 	return 0;
 }
 
+void
+nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRouteCmpType cmp_type, NMHashState *h)
+{
+	switch (cmp_type) {
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
+		nm_hash_update_vals (h,
+		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
+		                     nm_utils_ip4_address_clear_host_address (obj->network, obj->plen),
+		                     obj->plen,
+		                     obj->metric,
+		                     obj->tos);
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
+		nm_hash_update_vals (h,
+		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
+		                     nm_utils_ip4_address_clear_host_address (obj->network, obj->plen),
+		                     obj->plen,
+		                     obj->metric,
+		                     obj->tos,
+		                     /* on top of WEAK_ID: */
+		                     obj->ifindex,
+		                     nmp_utils_ip_config_source_round_trip_rtprot (obj->rt_source),
+		                     _ip_route_scope_inv_get_normalized (obj),
+		                     obj->gateway,
+		                     obj->mss,
+		                     obj->pref_src,
+		                     obj->window,
+		                     obj->cwnd,
+		                     obj->initcwnd,
+		                     obj->initrwnd,
+		                     obj->mtu,
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            obj->lock_window,
+		                                            obj->lock_cwnd,
+		                                            obj->lock_initcwnd,
+		                                            obj->lock_initrwnd,
+		                                            obj->lock_mtu));
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
+		nm_hash_update_vals (h,
+		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
+		                     obj->ifindex,
+		                     nm_utils_ip4_address_clear_host_address (obj->network, obj->plen),
+		                     obj->plen,
+		                     obj->metric,
+		                     obj->gateway,
+		                     nmp_utils_ip_config_source_round_trip_rtprot (obj->rt_source),
+		                     _ip_route_scope_inv_get_normalized (obj),
+		                     obj->tos,
+		                     obj->mss,
+		                     obj->pref_src,
+		                     obj->window,
+		                     obj->cwnd,
+		                     obj->initcwnd,
+		                     obj->initrwnd,
+		                     obj->mtu,
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            obj->rt_cloned,
+		                                            obj->lock_window,
+		                                            obj->lock_cwnd,
+		                                            obj->lock_initcwnd,
+		                                            obj->lock_initrwnd,
+		                                            obj->lock_mtu));
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
+		nm_hash_update_vals (h,
+		                     obj->table_coerced,
+		                     obj->ifindex,
+		                     obj->network,
+		                     obj->plen,
+		                     obj->metric,
+		                     obj->gateway,
+		                     obj->rt_source,
+		                     obj->scope_inv,
+		                     obj->tos,
+		                     obj->mss,
+		                     obj->pref_src,
+		                     obj->window,
+		                     obj->cwnd,
+		                     obj->initcwnd,
+		                     obj->initrwnd,
+		                     obj->mtu,
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            obj->rt_cloned,
+		                                            obj->lock_window,
+		                                            obj->lock_cwnd,
+		                                            obj->lock_initcwnd,
+		                                            obj->lock_initrwnd,
+		                                            obj->lock_mtu));
+		break;
+	}
+}
+
 int
-nm_platform_ip4_route_cmp_full (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, gboolean consider_host_part)
-{
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, ifindex);
-	if (consider_host_part)
-		_CMP_FIELD (a, b, network);
-	else {
-		_CMP_DIRECT (nm_utils_ip4_address_clear_host_address (a->network, a->plen),
-		             nm_utils_ip4_address_clear_host_address (b->network, b->plen));
+nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, NMPlatformIPRouteCmpType cmp_type)
+{
+	NM_CMP_SELF (a, b);
+	switch (cmp_type) {
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
+		NM_CMP_DIRECT (nm_platform_route_table_uncoerce (a->table_coerced, TRUE),
+		               nm_platform_route_table_uncoerce (b->table_coerced, TRUE));
+		NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX (a->network, b->network, MIN (a->plen, b->plen));
+		NM_CMP_FIELD (a, b, plen);
+		NM_CMP_FIELD (a, b, metric);
+		NM_CMP_FIELD (a, b, tos);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
+			NM_CMP_FIELD (a, b, ifindex);
+			NM_CMP_DIRECT (nmp_utils_ip_config_source_round_trip_rtprot (a->rt_source),
+			               nmp_utils_ip_config_source_round_trip_rtprot (b->rt_source));
+			NM_CMP_DIRECT (_ip_route_scope_inv_get_normalized (a),
+			               _ip_route_scope_inv_get_normalized (b));
+			NM_CMP_FIELD (a, b, gateway);
+			NM_CMP_FIELD (a, b, mss);
+			NM_CMP_FIELD (a, b, pref_src);
+			NM_CMP_FIELD (a, b, window);
+			NM_CMP_FIELD (a, b, cwnd);
+			NM_CMP_FIELD (a, b, initcwnd);
+			NM_CMP_FIELD (a, b, initrwnd);
+			NM_CMP_FIELD (a, b, mtu);
+			NM_CMP_FIELD_UNSAFE (a, b, lock_window);
+			NM_CMP_FIELD_UNSAFE (a, b, lock_cwnd);
+			NM_CMP_FIELD_UNSAFE (a, b, lock_initcwnd);
+			NM_CMP_FIELD_UNSAFE (a, b, lock_initrwnd);
+			NM_CMP_FIELD_UNSAFE (a, b, lock_mtu);
+		}
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
+			NM_CMP_DIRECT (nm_platform_route_table_uncoerce (a->table_coerced, TRUE),
+			               nm_platform_route_table_uncoerce (b->table_coerced, TRUE));
+		} else
+			NM_CMP_FIELD (a, b, table_coerced);
+		NM_CMP_FIELD (a, b, ifindex);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+			NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX (a->network, b->network, MIN (a->plen, b->plen));
+		else
+			NM_CMP_FIELD (a, b, network);
+		NM_CMP_FIELD (a, b, plen);
+		NM_CMP_FIELD (a, b, metric);
+		NM_CMP_FIELD (a, b, gateway);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
+			NM_CMP_DIRECT (nmp_utils_ip_config_source_round_trip_rtprot (a->rt_source),
+			               nmp_utils_ip_config_source_round_trip_rtprot (b->rt_source));
+			NM_CMP_DIRECT (_ip_route_scope_inv_get_normalized (a),
+			               _ip_route_scope_inv_get_normalized (b));
+		} else {
+			NM_CMP_FIELD (a, b, rt_source);
+			NM_CMP_FIELD (a, b, scope_inv);
+		}
+		NM_CMP_FIELD (a, b, mss);
+		NM_CMP_FIELD (a, b, pref_src);
+		NM_CMP_FIELD_UNSAFE (a, b, rt_cloned);
+		NM_CMP_FIELD (a, b, tos);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_window);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_cwnd);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_initcwnd);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_initrwnd);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_mtu);
+		NM_CMP_FIELD (a, b, window);
+		NM_CMP_FIELD (a, b, cwnd);
+		NM_CMP_FIELD (a, b, initcwnd);
+		NM_CMP_FIELD (a, b, initrwnd);
+		NM_CMP_FIELD (a, b, mtu);
+		break;
 	}
-	_CMP_FIELD (a, b, plen);
-	_CMP_FIELD (a, b, metric);
-	_CMP_FIELD (a, b, gateway);
-	_CMP_FIELD (a, b, rt_source);
-	_CMP_FIELD (a, b, mss);
-	_CMP_FIELD (a, b, scope_inv);
-	_CMP_FIELD (a, b, pref_src);
-	_CMP_FIELD (a, b, rt_cloned);
-	_CMP_FIELD (a, b, tos);
-	_CMP_FIELD (a, b, lock_window);
-	_CMP_FIELD (a, b, lock_cwnd);
-	_CMP_FIELD (a, b, lock_initcwnd);
-	_CMP_FIELD (a, b, lock_initrwnd);
-	_CMP_FIELD (a, b, lock_mtu);
-	_CMP_FIELD (a, b, window);
-	_CMP_FIELD (a, b, cwnd);
-	_CMP_FIELD (a, b, initcwnd);
-	_CMP_FIELD (a, b, initrwnd);
-	_CMP_FIELD (a, b, mtu);
 	return 0;
 }
 
-int
-nm_platform_ip6_route_cmp_full (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b, gboolean consider_host_part)
-{
-	_CMP_SELF (a, b);
-	_CMP_FIELD (a, b, ifindex);
-	if (consider_host_part)
-		_CMP_FIELD_MEMCMP (a, b, network);
-	else {
-		struct in6_addr n1, n2;
+void
+nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRouteCmpType cmp_type, NMHashState *h)
+{
+	struct in6_addr a1, a2;
+
+	switch (cmp_type) {
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
+		nm_hash_update_vals (h,
+		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
+		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
+		                     obj->plen,
+		                     nm_utils_ip6_route_metric_normalize (obj->metric),
+		                     *nm_utils_ip6_address_clear_host_address (&a2, &obj->src, obj->src_plen),
+		                     obj->src_plen);
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
+		nm_hash_update_vals (h,
+		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
+		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
+		                     obj->plen,
+		                     nm_utils_ip6_route_metric_normalize (obj->metric),
+		                     *nm_utils_ip6_address_clear_host_address (&a2, &obj->src, obj->src_plen),
+		                     obj->src_plen,
+		                     /* on top of WEAK_ID: */
+		                     obj->ifindex,
+		                     obj->gateway);
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
+		nm_hash_update_vals (h,
+		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
+		                     obj->ifindex,
+		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
+		                     obj->plen,
+		                     nm_utils_ip6_route_metric_normalize (obj->metric),
+		                     obj->gateway,
+		                     obj->pref_src,
+		                     *nm_utils_ip6_address_clear_host_address (&a2, &obj->src, obj->src_plen),
+		                     obj->src_plen,
+		                     nmp_utils_ip_config_source_round_trip_rtprot (obj->rt_source),
+		                     obj->mss,
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            obj->rt_cloned,
+		                                            obj->lock_window,
+		                                            obj->lock_cwnd,
+		                                            obj->lock_initcwnd,
+		                                            obj->lock_initrwnd,
+		                                            obj->lock_mtu),
+		                     obj->window,
+		                     obj->cwnd,
+		                     obj->initcwnd,
+		                     obj->initrwnd,
+		                     obj->mtu,
+		                     _route_pref_normalize (obj->rt_pref));
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
+		nm_hash_update_vals (h,
+		                     obj->table_coerced,
+		                     obj->ifindex,
+		                     obj->network,
+		                     obj->plen,
+		                     obj->metric,
+		                     obj->gateway,
+		                     obj->pref_src,
+		                     obj->src,
+		                     obj->src_plen,
+		                     obj->rt_source,
+		                     obj->mss,
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            obj->rt_cloned,
+		                                            obj->lock_window,
+		                                            obj->lock_cwnd,
+		                                            obj->lock_initcwnd,
+		                                            obj->lock_initrwnd,
+		                                            obj->lock_mtu),
+		                     obj->window,
+		                     obj->cwnd,
+		                     obj->initcwnd,
+		                     obj->initrwnd,
+		                     obj->mtu,
+		                     obj->rt_pref);
+		break;
+	}
+}
 
-		nm_utils_ip6_address_clear_host_address (&n1, &a->network, a->plen);
-		nm_utils_ip6_address_clear_host_address (&n2, &b->network, b->plen);
-		_CMP_DIRECT_MEMCMP (&n1, &n2, sizeof (struct in6_addr));
+int
+nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b, NMPlatformIPRouteCmpType cmp_type)
+{
+	NM_CMP_SELF (a, b);
+	switch (cmp_type) {
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID:
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID:
+		NM_CMP_DIRECT (nm_platform_route_table_uncoerce (a->table_coerced, TRUE),
+		               nm_platform_route_table_uncoerce (b->table_coerced, TRUE));
+		NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX (&a->network, &b->network, MIN (a->plen, b->plen));
+		NM_CMP_FIELD (a, b, plen);
+		NM_CMP_DIRECT (nm_utils_ip6_route_metric_normalize (a->metric), nm_utils_ip6_route_metric_normalize (b->metric));
+		NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX (&a->src, &b->src, MIN (a->src_plen, b->src_plen));
+		NM_CMP_FIELD (a, b, src_plen);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
+			NM_CMP_FIELD (a, b, ifindex);
+			NM_CMP_FIELD_IN6ADDR (a, b, gateway);
+		}
+		break;
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY:
+	case NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL:
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
+			NM_CMP_DIRECT (nm_platform_route_table_uncoerce (a->table_coerced, TRUE),
+			               nm_platform_route_table_uncoerce (b->table_coerced, TRUE));
+		} else
+			NM_CMP_FIELD (a, b, table_coerced);
+		NM_CMP_FIELD (a, b, ifindex);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+			NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX (&a->network, &b->network, MIN (a->plen, b->plen));
+		else
+			NM_CMP_FIELD_IN6ADDR (a, b, network);
+		NM_CMP_FIELD (a, b, plen);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+			NM_CMP_DIRECT (nm_utils_ip6_route_metric_normalize (a->metric), nm_utils_ip6_route_metric_normalize (b->metric));
+		else
+			NM_CMP_FIELD (a, b, metric);
+		NM_CMP_FIELD_IN6ADDR (a, b, gateway);
+		NM_CMP_FIELD_IN6ADDR (a, b, pref_src);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
+			NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX (&a->src, &b->src, MIN (a->src_plen, b->src_plen));
+			NM_CMP_FIELD (a, b, src_plen);
+			NM_CMP_DIRECT (nmp_utils_ip_config_source_round_trip_rtprot (a->rt_source),
+			               nmp_utils_ip_config_source_round_trip_rtprot (b->rt_source));
+		} else {
+			NM_CMP_FIELD_IN6ADDR (a, b, src);
+			NM_CMP_FIELD (a, b, src_plen);
+			NM_CMP_FIELD (a, b, rt_source);
+		}
+		NM_CMP_FIELD (a, b, mss);
+		NM_CMP_FIELD_UNSAFE (a, b, rt_cloned);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_window);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_cwnd);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_initcwnd);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_initrwnd);
+		NM_CMP_FIELD_UNSAFE (a, b, lock_mtu);
+		NM_CMP_FIELD (a, b, window);
+		NM_CMP_FIELD (a, b, cwnd);
+		NM_CMP_FIELD (a, b, initcwnd);
+		NM_CMP_FIELD (a, b, initrwnd);
+		NM_CMP_FIELD (a, b, mtu);
+		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+			NM_CMP_DIRECT (_route_pref_normalize (a->rt_pref), _route_pref_normalize (b->rt_pref));
+		else
+			NM_CMP_FIELD (a, b, rt_pref);
+		break;
 	}
-	_CMP_FIELD (a, b, plen);
-	_CMP_FIELD (a, b, metric);
-	_CMP_FIELD_MEMCMP (a, b, gateway);
-	_CMP_FIELD_MEMCMP (a, b, pref_src);
-	_CMP_FIELD_MEMCMP (a, b, src);
-	_CMP_FIELD (a, b, src_plen);
-	_CMP_FIELD (a, b, rt_source);
-	_CMP_FIELD (a, b, mss);
-	_CMP_FIELD (a, b, rt_cloned);
-	_CMP_FIELD (a, b, tos);
-	_CMP_FIELD (a, b, lock_window);
-	_CMP_FIELD (a, b, lock_cwnd);
-	_CMP_FIELD (a, b, lock_initcwnd);
-	_CMP_FIELD (a, b, lock_initrwnd);
-	_CMP_FIELD (a, b, lock_mtu);
-	_CMP_FIELD (a, b, window);
-	_CMP_FIELD (a, b, cwnd);
-	_CMP_FIELD (a, b, initcwnd);
-	_CMP_FIELD (a, b, initrwnd);
-	_CMP_FIELD (a, b, mtu);
 	return 0;
 }
 
@@ -4404,7 +5848,7 @@ nm_platform_ip_address_cmp_expiry (const NMPlatformIPAddress *a, const NMPlatfor
 {
 	gint64 ta = 0, tb = 0;
 
-	_CMP_SELF (a, b);
+	NM_CMP_SELF (a, b);
 
 	if (a->lifetime == NM_PLATFORM_LIFETIME_PERMANENT || a->lifetime == 0)
 		ta = G_MAXINT64;
@@ -4485,120 +5929,139 @@ log_ip6_route (NMPlatform *self, NMPObjectType obj_type, int ifindex, NMPlatform
 
 /*****************************************************************************/
 
-NMPNetns *
-nm_platform_netns_get (NMPlatform *self)
+void
+nm_platform_cache_update_emit_signal (NMPlatform *self,
+                                      NMPCacheOpsType cache_op,
+                                      const NMPObject *obj_old,
+                                      const NMPObject *obj_new)
 {
-	_CHECK_SELF (self, klass, NULL);
+	gboolean visible_new;
+	gboolean visible_old;
+	const NMPObject *o;
+	const NMPClass *klass;
 
-	return self->_netns;
-}
+	nm_assert (NM_IN_SET ((NMPlatformSignalChangeType) cache_op, (NMPlatformSignalChangeType) NMP_CACHE_OPS_UNCHANGED, NM_PLATFORM_SIGNAL_ADDED, NM_PLATFORM_SIGNAL_CHANGED, NM_PLATFORM_SIGNAL_REMOVED));
 
-gboolean
-nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns)
-{
-	g_return_val_if_fail (NM_IS_PLATFORM (platform), FALSE);
+	ASSERT_nmp_cache_ops (nm_platform_get_cache (self), cache_op, obj_old, obj_new);
 
-	if (   platform->_netns
-	    && !nmp_netns_push (platform->_netns)) {
-		NM_SET_OUT (netns, NULL);
-		return FALSE;
+	nm_assert (NM_IN_SET (nm_platform_netns_get (self),
+	                      NULL,
+	                      nmp_netns_get_current ()));
+
+	NMTST_ASSERT_PLATFORM_NETNS_CURRENT (self);
+
+	switch (cache_op) {
+	case NMP_CACHE_OPS_ADDED:
+		if (!nmp_object_is_visible (obj_new))
+			return;
+		o = obj_new;
+		break;
+	case NMP_CACHE_OPS_UPDATED:
+		visible_old = nmp_object_is_visible (obj_old);
+		visible_new = nmp_object_is_visible (obj_new);
+		if (!visible_old && visible_new) {
+			o = obj_new;
+			cache_op = NMP_CACHE_OPS_ADDED;
+		} else if (visible_old && !visible_new) {
+			o = obj_old;
+			cache_op = NMP_CACHE_OPS_REMOVED;
+		} else if (!visible_new) {
+			/* it was invisible and stayed invisible. Nothing to do. */
+			return;
+		} else
+			o = obj_new;
+		break;
+	case NMP_CACHE_OPS_REMOVED:
+		if (!nmp_object_is_visible (obj_old))
+			return;
+		o = obj_old;
+		break;
+	default:
+		nm_assert (cache_op == NMP_CACHE_OPS_UNCHANGED);
+		return;
 	}
 
-	NM_SET_OUT (netns, platform->_netns);
-	return TRUE;
+	klass = NMP_OBJECT_GET_CLASS (o);
+
+	if (   klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
+	    && NM_PLATFORM_GET_PRIVATE (self)->ip4_dev_route_blacklist_gc_timeout_id
+	    && NM_IN_SET (cache_op, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_UPDATED))
+		_ip4_dev_route_blacklist_notify_route (self, o);
+
+	_LOGt ("emit signal %s %s: %s",
+	       klass->signal_type,
+	       nm_platform_signal_change_type_to_string ((NMPlatformSignalChangeType) cache_op),
+	       nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
+
+	nmp_object_ref (o);
+	g_signal_emit (self,
+	               _nm_platform_signal_id_get (klass->signal_type_id),
+	               0,
+	               (int) klass->obj_type,
+	               o->object.ifindex,
+	               &o->object,
+	               (int) cache_op);
+	nmp_object_unref (o);
 }
 
 /*****************************************************************************/
 
-static gboolean
-_vtr_v4_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric)
+NMPCache *
+nm_platform_get_cache (NMPlatform *self)
 {
-	NMPlatformIP4Route rt = route->r4;
-
-	if (ifindex > 0)
-		rt.ifindex = ifindex;
-	if (metric >= 0)
-		rt.metric = metric;
-
-	return nm_platform_ip4_route_add (self, &rt);
+	return NM_PLATFORM_GET_PRIVATE (self)->cache;
 }
 
-static gboolean
-_vtr_v6_route_add (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric)
+NMPNetns *
+nm_platform_netns_get (NMPlatform *self)
 {
-	NMPlatformIP6Route rt = route->r6;
-
-	if (ifindex > 0)
-		rt.ifindex = ifindex;
-	if (metric >= 0)
-		rt.metric = metric;
+	_CHECK_SELF (self, klass, NULL);
 
-	return nm_platform_ip6_route_add (self, &rt);
+	return self->_netns;
 }
 
-static gboolean
-_vtr_v4_route_delete (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route)
+gboolean
+nm_platform_netns_push (NMPlatform *self, NMPNetns **netns)
 {
-	return nm_platform_ip4_route_delete (self,
-	                                     ifindex > 0 ? ifindex : route->rx.ifindex,
-	                                     route->r4.network,
-	                                     route->rx.plen,
-	                                     route->rx.metric);
-}
+	g_return_val_if_fail (NM_IS_PLATFORM (self), FALSE);
 
-static gboolean
-_vtr_v6_route_delete (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route)
-{
-	return nm_platform_ip6_route_delete (self,
-	                                     ifindex > 0 ? ifindex : route->rx.ifindex,
-	                                     route->r6.network,
-	                                     route->rx.plen,
-	                                     route->rx.metric);
+	if (   self->_netns
+	    && !nmp_netns_push (self->_netns)) {
+		NM_SET_OUT (netns, NULL);
+		return FALSE;
+	}
+
+	NM_SET_OUT (netns, self->_netns);
+	return TRUE;
 }
 
+/*****************************************************************************/
+
 static guint32
 _vtr_v4_metric_normalize (guint32 metric)
 {
 	return metric;
 }
 
-static gboolean
-_vtr_v4_route_delete_default (NMPlatform *self, int ifindex, guint32 metric)
-{
-	return nm_platform_ip4_route_delete (self, ifindex, 0, 0, metric);
-}
-
-static gboolean
-_vtr_v6_route_delete_default (NMPlatform *self, int ifindex, guint32 metric)
-{
-	return nm_platform_ip6_route_delete (self, ifindex, in6addr_any, 0, metric);
-}
-
 /*****************************************************************************/
 
 const NMPlatformVTableRoute nm_platform_vtable_route_v4 = {
 	.is_ip4                         = TRUE,
+	.obj_type                       = NMP_OBJECT_TYPE_IP4_ROUTE,
 	.addr_family                    = AF_INET,
 	.sizeof_route                   = sizeof (NMPlatformIP4Route),
-	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, gboolean consider_host_part)) nm_platform_ip4_route_cmp_full,
+	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip4_route_cmp,
 	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip4_route_to_string,
-	.route_get_all                  = nm_platform_ip4_route_get_all,
-	.route_add                      = _vtr_v4_route_add,
-	.route_delete                   = _vtr_v4_route_delete,
-	.route_delete_default           = _vtr_v4_route_delete_default,
 	.metric_normalize               = _vtr_v4_metric_normalize,
 };
 
 const NMPlatformVTableRoute nm_platform_vtable_route_v6 = {
 	.is_ip4                         = FALSE,
+	.obj_type                       = NMP_OBJECT_TYPE_IP6_ROUTE,
 	.addr_family                    = AF_INET6,
 	.sizeof_route                   = sizeof (NMPlatformIP6Route),
-	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, gboolean consider_host_part)) nm_platform_ip6_route_cmp_full,
+	.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip6_route_cmp,
 	.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip6_route_to_string,
-	.route_get_all                  = nm_platform_ip6_route_get_all,
-	.route_add                      = _vtr_v6_route_add,
-	.route_delete                   = _vtr_v6_route_delete,
-	.route_delete_default           = _vtr_v6_route_delete_default,
 	.metric_normalize               = nm_utils_ip6_route_metric_normalize,
 };
 
@@ -4622,6 +6085,10 @@ set_property (GObject *object, guint prop_id,
 				self->_netns = g_object_ref (netns);
 		}
 		break;
+	case PROP_USE_UDEV:
+		/* construct-only */
+		priv->use_udev = g_value_get_boolean (value);
+		break;
 	case PROP_LOG_WITH_PTR:
 		/* construct-only */
 		priv->log_with_ptr = g_value_get_boolean (value);
@@ -4638,12 +6105,40 @@ nm_platform_init (NMPlatform *self)
 	self->_priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_PLATFORM, NMPlatformPrivate);
 }
 
+static GObject *
+constructor (GType type,
+             guint n_construct_params,
+             GObjectConstructParam *construct_params)
+{
+	GObject *object;
+	NMPlatform *self;
+	NMPlatformPrivate *priv;
+
+	object = G_OBJECT_CLASS (nm_platform_parent_class)->constructor (type,
+	                                                                 n_construct_params,
+	                                                                 construct_params);
+	self = NM_PLATFORM (object);
+	priv = NM_PLATFORM_GET_PRIVATE (self);
+
+	priv->multi_idx = nm_dedup_multi_index_new ();
+
+	priv->cache = nmp_cache_new (nm_platform_get_multi_idx (self),
+	                             priv->use_udev);
+	return object;
+}
+
 static void
 finalize (GObject *object)
 {
 	NMPlatform *self = NM_PLATFORM (object);
+	NMPlatformPrivate *priv = NM_PLATFORM_GET_PRIVATE (self);
 
+	nm_clear_g_source (&priv->ip4_dev_route_blacklist_check_id);
+	nm_clear_g_source (&priv->ip4_dev_route_blacklist_gc_timeout_id);
+	g_clear_pointer (&priv->ip4_dev_route_blacklist_hash, g_hash_table_unref);
 	g_clear_object (&self->_netns);
+	nm_dedup_multi_index_unref (priv->multi_idx);
+	nmp_cache_free (priv->cache);
 }
 
 static void
@@ -4653,6 +6148,7 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 
 	g_type_class_add_private (object_class, sizeof (NMPlatformPrivate));
 
+	object_class->constructor = constructor;
 	object_class->set_property = set_property;
 	object_class->finalize = finalize;
 
@@ -4667,6 +6163,14 @@ nm_platform_class_init (NMPlatformClass *platform_class)
 	                           G_PARAM_STATIC_STRINGS));
 
 	g_object_class_install_property
+	 (object_class, PROP_USE_UDEV,
+	     g_param_spec_boolean (NM_PLATFORM_USE_UDEV, "", "",
+	                           FALSE,
+	                           G_PARAM_WRITABLE |
+	                           G_PARAM_CONSTRUCT_ONLY |
+	                           G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
 	 (object_class, PROP_LOG_WITH_PTR,
 	     g_param_spec_boolean (NM_PLATFORM_LOG_WITH_PTR, "", "",
 	                           TRUE,
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 1b8fa133..d155c109 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -45,12 +45,16 @@
 /*****************************************************************************/
 
 #define NM_PLATFORM_NETNS_SUPPORT      "netns-support"
+#define NM_PLATFORM_USE_UDEV           "use-udev"
 #define NM_PLATFORM_LOG_WITH_PTR       "log-with-ptr"
 
 /*****************************************************************************/
 
 struct udev_device;
 
+typedef gboolean (*NMPObjectPredicateFunc) (const NMPObject *obj,
+                                            gpointer user_data);
+
 /* workaround for older libnl version, that does not define these flags. */
 #ifndef IFA_F_MANAGETEMPADDR
 #define IFA_F_MANAGETEMPADDR 0x100
@@ -59,6 +63,8 @@ struct udev_device;
 #define IFA_F_NOPREFIXROUTE 0x200
 #endif
 
+#define NM_RT_SCOPE_LINK                       253  /* RT_SCOPE_LINK */
+
 /* Define of the IN6_ADDR_GEN_MODE_* values to workaround old kernel headers
  * that don't define it. */
 #define NM_IN6_ADDR_GEN_MODE_UNKNOWN           255  /* no corresponding value.  */
@@ -72,6 +78,74 @@ struct udev_device;
 /* Redefine this in host's endianness */
 #define NM_GRE_KEY      0x2000
 
+typedef enum {
+	/* use our own platform enum for the nlmsg-flags. Otherwise, we'd have
+	 * to include <linux/netlink.h> */
+	NMP_NLM_FLAG_F_REPLACE      = 0x100, /* NLM_F_REPLACE, Override existing */
+	NMP_NLM_FLAG_F_EXCL         = 0x200, /* NLM_F_EXCL, Do not touch, if it exists */
+	NMP_NLM_FLAG_F_CREATE       = 0x400, /* NLM_F_CREATE, Create, if it does not exist */
+	NMP_NLM_FLAG_F_APPEND       = 0x800, /* NLM_F_APPEND, Add to end of list */
+
+	NMP_NLM_FLAG_FMASK          = 0xFFFF, /* a mask for all NMP_NLM_FLAG_F_* flags */
+
+	/* instructs NM to suppress logging an error message for any failures
+	 * received from kernel.
+	 *
+	 * It will still log with debug-level, and it will still log
+	 * other failures aside the kernel response. */
+	NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE = 0x10000,
+
+	/* the following aliases correspond to iproute2's `ip route CMD` for
+	 * RTM_NEWROUTE, with CMD being one of add, change, replace, prepend,
+	 * append and test. */
+	NMP_NLM_FLAG_ADD            = NMP_NLM_FLAG_F_CREATE                          | NMP_NLM_FLAG_F_EXCL,
+	NMP_NLM_FLAG_CHANGE         =                         NMP_NLM_FLAG_F_REPLACE,
+	NMP_NLM_FLAG_REPLACE        = NMP_NLM_FLAG_F_CREATE | NMP_NLM_FLAG_F_REPLACE,
+	NMP_NLM_FLAG_PREPEND        = NMP_NLM_FLAG_F_CREATE,
+	NMP_NLM_FLAG_APPEND         = NMP_NLM_FLAG_F_CREATE                                                | NMP_NLM_FLAG_F_APPEND,
+	NMP_NLM_FLAG_TEST           =                                                  NMP_NLM_FLAG_F_EXCL,
+} NMPNlmFlags;
+
+typedef enum {
+	/* compare fields which kernel considers as similar routes.
+	 * It is a looser comparisong then NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID
+	 * and means that `ip route add` would fail to add two routes
+	 * that have the same NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID.
+	 * On the other hand, `ip route append` would allow that, as
+	 * long as NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID differs. */
+	NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID,
+
+	/* compare two routes as kernel would allow to add them with
+	 * `ip route append`. In other words, kernel does not allow you to
+	 * add two routes (at the same time) which compare equal according
+	 * to NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID.
+	 *
+	 * For the ID we can only recognize route fields that we actually implement.
+	 * However, kernel supports more routing options, some of them also part of
+	 * the ID. NetworkManager is oblivious to these options and will wrongly think
+	 * that two routes are idential, while they are not. That can lead to an
+	 * inconsistent platform cache. Not much what we can do about that, except
+	 * implementing all options that kernel supports *sigh*. See rh#1337860.
+	 */
+	NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID,
+
+	/* compare all fields as they make sense for kernel. For example,
+	 * a route destination 192.168.1.5/24 is not accepted by kernel and
+	 * we treat it identical to 192.168.1.0/24. Semantically these
+	 * routes are identical, but NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL will
+	 * report them as different.
+	 *
+	 * The result shall be identical to call first nm_platform_ip_route_normalize()
+	 * on both routes and then doing a full comparison. */
+	NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY,
+
+	/* compare all fields. This should have the same effect as memcmp(),
+	 * except allowing for undefined data in holes between field alignment.
+	 */
+	NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
+
+} NMPlatformIPRouteCmpType;
+
 typedef enum { /*< skip >*/
 
 	/* dummy value, to enforce that the enum type is signed and has a size
@@ -91,6 +165,8 @@ typedef enum { /*< skip >*/
 	NM_PLATFORM_ERROR_NOT_SLAVE,
 	NM_PLATFORM_ERROR_NO_FIRMWARE,
 	NM_PLATFORM_ERROR_OPNOTSUPP,
+	NM_PLATFORM_ERROR_NETLINK,
+	NM_PLATFORM_ERROR_CANT_SET_MTU,
 } NMPlatformError;
 
 #define NM_PLATFORM_LINK_OTHER_NETNS    (-1)
@@ -172,21 +248,9 @@ typedef enum {
 	NM_PLATFORM_SIGNAL_REMOVED,
 } NMPlatformSignalChangeType;
 
-typedef enum { /*< skip >*/
-	NM_PLATFORM_GET_ROUTE_FLAGS_NONE                            = 0,
-
-	/* Whether to include default-routes/non-default-routes. Omitting
-	 * both WITH_DEFAULT and WITH_NON_DEFAULT, is equal to specifying
-	 * both of them. */
-	NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT                    = (1LL << 0),
-	NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT                = (1LL << 1),
-
-	NM_PLATFORM_GET_ROUTE_FLAGS_WITH_RTPROT_KERNEL              = (1LL << 2),
-} NMPlatformGetRouteFlags;
-
-typedef struct {
+struct _NMPlatformObject {
 	__NMPlatformObject_COMMON;
-} NMPlatformObject;
+};
 
 
 #define __NMPlatformIPAddress_COMMON \
@@ -298,7 +362,15 @@ typedef union {
 	/* The NMIPConfigSource. For routes that we receive from cache this corresponds
 	 * to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values).
 	 * When adding a route, the source will be coerced to the protocol using
-	 * nmp_utils_ip_config_source_coerce_to_rtprot(). */ \
+	 * nmp_utils_ip_config_source_coerce_to_rtprot().
+	 *
+	 * rtm_protocol is part of the primary key of an IPv4 route (meaning, you can add
+	 * two IPv4 routes that only differ in their rtm_protocol. For IPv6, that is not
+	 * the case.
+	 *
+	 * When deleting an IPv4/IPv6 route, the rtm_protocol field must match (even
+	 * if it is not part of the primary key for IPv6) -- unless rtm_protocol is set
+	 * to zero, in which case the first matching route (with proto ignored) is deleted. */ \
 	NMIPConfigSource rt_source; \
 	\
 	guint8 plen; \
@@ -308,21 +380,58 @@ typedef union {
 	 * of platform users. This flag is internal to track those hidden
 	 * routes. Such a route is not alive, according to nmp_object_is_alive(). */ \
 	bool rt_cloned:1; \
+	\
+	\
+	/* RTA_METRICS:
+	 *
+	 * For IPv4 routes, these properties are part of their
+	 * ID (meaning: you can add otherwise idential IPv4 routes that
+	 * only differ by the metric property).
+	 * On the other hand, for IPv6 you cannot add two IPv6 routes that only differ
+	 * by an RTA_METRICS property.
+	 *
+	 * When deleting a route, kernel seems to ignore the RTA_METRICS propeties.
+	 * That is a problem/bug for IPv4 because you cannot explicitly select which
+	 * route to delete. Kernel just picks the first. See rh#1475642. */ \
+	\
+	/* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */ \
 	bool lock_window:1; \
 	bool lock_cwnd:1; \
 	bool lock_initcwnd:1; \
 	bool lock_initrwnd:1; \
 	bool lock_mtu:1; \
 	\
-	guint32 metric; \
+	/* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */ \
 	guint32 mss; \
-	guint32 tos; \
+	\
+	/* RTA_METRICS.RTAX_WINDOW (iproute2: window) */ \
 	guint32 window; \
+	\
+	/* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */ \
 	guint32 cwnd; \
+	\
+	/* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */ \
 	guint32 initcwnd; \
+	\
+	/* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */ \
 	guint32 initrwnd; \
+	\
+	/* RTA_METRICS.RTAX_MTU (iproute2: mtu) */ \
 	guint32 mtu; \
-	;
+	\
+	\
+	/* RTA_PRIORITY (iproute2: metric) */ \
+	guint32 metric; \
+	\
+	/* rtm_table, RTA_TABLE.
+	 *
+	 * This is not the original table ID. Instead, 254 (RT_TABLE_MAIN) and
+	 * zero (RT_TABLE_UNSPEC) are swapped, so that the default is the main
+	 * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */ \
+	guint32 table_coerced; \
+	\
+	/*end*/
+
 
 typedef struct {
 	__NMPlatformIPRoute_COMMON;
@@ -332,30 +441,89 @@ typedef struct {
 	};
 } NMPlatformIPRoute;
 
+#if _NM_CC_SUPPORT_GENERIC
+#define NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route) \
+	(_Generic ((route), \
+	           const NMPlatformIPRoute  *: ((const NMPlatformIPRoute *) (route))->plen, \
+	                 NMPlatformIPRoute  *: ((const NMPlatformIPRoute *) (route))->plen, \
+	           const NMPlatformIPXRoute *: ((const NMPlatformIPRoute *) (route))->plen, \
+	                 NMPlatformIPXRoute *: ((const NMPlatformIPRoute *) (route))->plen, \
+	           const NMPlatformIP4Route *: ((const NMPlatformIPRoute *) (route))->plen, \
+	                 NMPlatformIP4Route *: ((const NMPlatformIPRoute *) (route))->plen, \
+	           const NMPlatformIP6Route *: ((const NMPlatformIPRoute *) (route))->plen, \
+	                 NMPlatformIP6Route *: ((const NMPlatformIPRoute *) (route))->plen, \
+	           const void               *: ((const NMPlatformIPRoute *) (route))->plen, \
+	                 void               *: ((const NMPlatformIPRoute *) (route))->plen) == 0)
+#else
 #define NM_PLATFORM_IP_ROUTE_IS_DEFAULT(route) \
 	( ((const NMPlatformIPRoute *) (route))->plen <= 0 )
+#endif
 
 struct _NMPlatformIP4Route {
 	__NMPlatformIPRoute_COMMON;
 	in_addr_t network;
-	in_addr_t gateway;
 
-	/* The bitwise inverse of the route scope. It is inverted so that the
-	 * default value (RT_SCOPE_NOWHERE) is nul. */
-	guint8 scope_inv;
+	/* RTA_GATEWAY. The gateway is part of the primary key for a route */
+	in_addr_t gateway;
 
-	/* RTA_PREFSRC/rtnl_route_get_pref_src(). A value of zero means that
-	 * no pref-src is set.  */
+	/* RTA_PREFSRC (called "src" by iproute2).
+	 *
+	 * pref_src is part of the ID of an IPv4 route. When deleting a route,
+	 * pref_src must match, unless set to 0.0.0.0 to match any. */
 	in_addr_t pref_src;
+
+	/* rtm_tos (iproute2: tos)
+	 *
+	 * For IPv4, tos is part of the weak-id (like metric).
+	 *
+	 * For IPv6, tos is ignored by kernel.  */
+	guint8 tos;
+
+	/* The bitwise inverse of the route scope rtm_scope. It is inverted so that the
+	 * default value (RT_SCOPE_NOWHERE) is zero. Use nm_platform_route_scope_inv()
+	 * to convert back and forth between the inverese representation and the
+	 * real value.
+	 *
+	 * rtm_scope is part of the primary key for IPv4 routes. When deleting a route,
+	 * the scope must match, unless it is left at RT_SCOPE_NOWHERE, in which case the first
+	 * matching route is deleted.
+	 *
+	 * For IPv6 routes, the scope is ignored and kernel always assumes global scope.
+	 * Hence, this field is only in NMPlatformIP4Route. */
+	guint8 scope_inv;
 };
 
 struct _NMPlatformIP6Route {
 	__NMPlatformIPRoute_COMMON;
 	struct in6_addr network;
+
+	/* RTA_GATEWAY. The gateway is part of the primary key for a route */
 	struct in6_addr gateway;
+
+	/* RTA_PREFSRC (called "src" by iproute2).
+	 *
+	 * pref_src is not part of the ID for an IPv6 route. You cannot add two
+	 * routes that only differ by pref_src.
+	 *
+	 * When deleting a route, pref_src is ignored by kernel. */
 	struct in6_addr pref_src;
+
+	/* RTA_SRC and rtm_src_len (called "from" by iproute2).
+	 *
+	 * Kernel clears the host part of src/src_plen.
+	 *
+	 * src/src_plen is part of the ID of a route just like network/plen. That is,
+	 * Not only `ip route append`, but also `ip route add` allows to add routes that only
+	 * differ in their src/src_plen.
+	 */
 	struct in6_addr src;
 	guint8 src_plen;
+
+	/* RTA_PREF router preference.
+	 *
+	 * The type is guint8 to keep the struct size small. But the values are compatible with
+	 * the NMIcmpv6RouterPref enum. */
+	guint8 rt_pref;
 };
 
 typedef union {
@@ -372,14 +540,11 @@ typedef union {
 
 typedef struct {
 	gboolean is_ip4;
+	NMPObjectType obj_type;
 	int addr_family;
 	gsize sizeof_route;
-	int (*route_cmp) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, gboolean consider_host_part);
+	int (*route_cmp) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type);
 	const char *(*route_to_string) (const NMPlatformIPXRoute *route, char *buf, gsize len);
-	GArray *(*route_get_all) (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags);
-	gboolean (*route_add) (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route, gint64 metric);
-	gboolean (*route_delete) (NMPlatform *self, int ifindex, const NMPlatformIPXRoute *route);
-	gboolean (*route_delete_default) (NMPlatform *self, int ifindex, guint32 metric);
 	guint32 (*metric_normalize) (guint32 metric);
 } NMPlatformVTableRoute;
 
@@ -426,7 +591,7 @@ typedef struct {
 
 typedef struct {
 	int parent_ifindex;
-	guint64 sci;	/* host byte order */
+	guint64 sci;                    /* host byte order */
 	guint64 cipher_suite;
 	guint32 window;
 	guint8 icv_length;
@@ -452,11 +617,11 @@ typedef struct {
 	in_addr_t local;
 	in_addr_t remote;
 	int parent_ifindex;
+	guint16 flags;
 	guint8 ttl;
 	guint8 tos;
 	guint8 proto;
 	bool path_mtu_discovery:1;
-	guint16 flags;
 } NMPlatformLnkSit;
 
 typedef struct {
@@ -501,6 +666,12 @@ typedef enum {
 	NM_PLATFORM_LINK_DUPLEX_FULL,
 } NMPlatformLinkDuplexType;
 
+typedef enum {
+	NM_PLATFORM_KERNEL_SUPPORT_EXTENDED_IFA_FLAGS               = (1LL <<  0),
+	NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL                      = (1LL <<  1),
+	NM_PLATFORM_KERNEL_SUPPORT_RTA_PREF                         = (1LL <<  2),
+} NMPlatformKernelSupportFlags;
+
 /*****************************************************************************/
 
 struct _NMPlatformPrivate;
@@ -517,22 +688,14 @@ typedef struct {
 	gboolean (*sysctl_set) (NMPlatform *, const char *pathid, int dirfd, const char *path, const char *value);
 	char * (*sysctl_get) (NMPlatform *, const char *pathid, int dirfd, const char *path);
 
-	const NMPlatformLink *(*link_get) (NMPlatform *platform, int ifindex);
-	const NMPlatformLink *(*link_get_by_ifname) (NMPlatform *platform, const char *ifname);
-	const NMPlatformLink *(*link_get_by_address) (NMPlatform *platform, gconstpointer address, size_t length);
-
-	const NMPObject *(*link_get_lnk) (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link);
-
-	GArray *(*link_get_all) (NMPlatform *);
 	gboolean (*link_add) (NMPlatform *,
 	                      const char *name,
 	                      NMLinkType type,
+	                      const char *veth_peer,
 	                      const void *address,
 	                      size_t address_len,
 	                      const NMPlatformLink **out_link);
 	gboolean (*link_delete) (NMPlatform *, int ifindex);
-	const char *(*link_get_type_name) (NMPlatform *, int ifindex);
-	gboolean (*link_get_unmanaged) (NMPlatform *, int ifindex, gboolean *unmanaged);
 
 	gboolean (*link_refresh) (NMPlatform *, int ifindex);
 
@@ -556,7 +719,8 @@ typedef struct {
 	                                        guint8 *buf,
 	                                        size_t *length);
 	NMPlatformError (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
-	gboolean (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
+	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);
 
 	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
@@ -641,8 +805,6 @@ typedef struct {
 	gboolean    (*mesh_set_channel)      (NMPlatform *, int ifindex, guint32 channel);
 	gboolean    (*mesh_set_ssid)         (NMPlatform *, int ifindex, const guint8 *ssid, gsize len);
 
-	GArray * (*ip4_address_get_all) (NMPlatform *, int ifindex);
-	GArray * (*ip6_address_get_all) (NMPlatform *, int ifindex);
 	gboolean (*ip4_address_add) (NMPlatform *,
 	                             int ifindex,
 	                             in_addr_t address,
@@ -662,20 +824,21 @@ typedef struct {
 	                             guint32 flags);
 	gboolean (*ip4_address_delete) (NMPlatform *, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 	gboolean (*ip6_address_delete) (NMPlatform *, int ifindex, struct in6_addr address, guint8 plen);
-	const NMPlatformIP4Address *(*ip4_address_get) (NMPlatform *, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
-	const NMPlatformIP6Address *(*ip6_address_get) (NMPlatform *, int ifindex, struct in6_addr address, guint8 plen);
-
-	GArray * (*ip4_route_get_all) (NMPlatform *, int ifindex, NMPlatformGetRouteFlags flags);
-	GArray * (*ip6_route_get_all) (NMPlatform *, int ifindex, NMPlatformGetRouteFlags flags);
-	gboolean (*ip4_route_add) (NMPlatform *, const NMPlatformIP4Route *route);
-	gboolean (*ip6_route_add) (NMPlatform *, const NMPlatformIP6Route *route);
-	gboolean (*ip4_route_delete) (NMPlatform *, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
-	gboolean (*ip6_route_delete) (NMPlatform *, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
-	const NMPlatformIP4Route *(*ip4_route_get) (NMPlatform *, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
-	const NMPlatformIP6Route *(*ip6_route_get) (NMPlatform *, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
-
-	gboolean (*check_support_kernel_extended_ifa_flags) (NMPlatform *);
-	gboolean (*check_support_user_ipv6ll) (NMPlatform *);
+
+	NMPlatformError (*ip_route_add) (NMPlatform *,
+	                                 NMPNlmFlags flags,
+	                                 int addr_family,
+	                                 const NMPlatformIPRoute *route);
+	gboolean (*ip_route_delete) (NMPlatform *, const NMPObject *obj);
+
+	NMPlatformError (*ip_route_get) (NMPlatform *self,
+	                                 int addr_family,
+	                                 gconstpointer address,
+	                                 int oif_ifindex,
+	                                 NMPObject **out_route);
+
+	NMPlatformKernelSupportFlags (*check_kernel_support) (NMPlatform * self,
+	                                                      NMPlatformKernelSupportFlags request_flags);
 } NMPlatformClass;
 
 /* NMPlatform signals
@@ -709,6 +872,72 @@ NMPlatform *nm_platform_get (void);
 /*****************************************************************************/
 
 /**
+ * nm_platform_route_table_coerce:
+ * @table: the route table, in its original value as received
+ *   from rtm_table/RTA_TABLE.
+ *
+ * Returns: returns the coerced table id, that can be stored in
+ *   NMPlatformIPRoute.table_coerced.
+ */
+static inline guint32
+nm_platform_route_table_coerce (guint32 table)
+{
+	/* For kernel, the default table is RT_TABLE_MAIN (254).
+	 * We want that in NMPlatformIPRoute.table_coerced a numeric
+	 * zero is the default. Hence, @table_coerced swaps the
+	 * value 0 and 254. Use nm_platform_route_table_coerce()
+	 * and nm_platform_route_table_uncoerce() to convert between
+	 * the two domains. */
+	switch (table) {
+	case 0 /* RT_TABLE_UNSPEC */:
+		return 254;
+	case 254 /* RT_TABLE_MAIN */:
+		return 0;
+	default:
+		return table;
+	}
+}
+
+/**
+ * nm_platform_route_table_uncoerce:
+ * @table: the route table, in its coerced value
+ * @normalize: whether to normalize RT_TABLE_UNSPEC to
+ *   RT_TABLE_MAIN. For kernel, routes with a table id
+ *   RT_TABLE_UNSPEC do not exist and are treated like
+ *   RT_TABLE_MAIN.
+ *
+ * Returns: reverts the coerced table ID in NMPlatformIPRoute.table_coerced
+ *   to the original value as kernel understands it.
+ */
+static inline guint32
+nm_platform_route_table_uncoerce (guint32 table_coerced, gboolean normalize)
+{
+	/* this undoes nm_platform_route_table_coerce().  */
+	switch (table_coerced) {
+	case 0 /* RT_TABLE_UNSPEC */:
+		return 254;
+	case 254 /* RT_TABLE_MAIN */:
+		return normalize ? 254 : 0;
+	default:
+		return table_coerced;
+	}
+}
+
+static inline gboolean
+nm_platform_route_table_is_main (guint32 table)
+{
+	/* same as
+	 *   nm_platform_route_table_uncoerce (table, TRUE) == RT_TABLE_MAIN
+	 * and
+	 *   nm_platform_route_table_uncoerce (nm_platform_route_table_coerce (table), TRUE) == RT_TABLE_MAIN
+	 *
+	 * That is, the function operates the same on @table and its coerced
+	 * form.
+	 */
+	return table == 0 || table == 254;
+}
+
+/**
  * nm_platform_route_scope_inv:
  * @scope: the route scope, either its original value, or its inverse.
  *
@@ -725,6 +954,7 @@ _nm_platform_uint8_inv (guint8 scope)
 	return (guint8) ~scope;
 }
 
+gboolean nm_platform_get_use_udev (NMPlatform *self);
 gboolean nm_platform_get_log_with_ptr (NMPlatform *self);
 
 NMPNetns *nm_platform_netns_get (NMPlatform *self);
@@ -732,8 +962,11 @@ gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns);
 
 const char *nm_link_type_to_string (NMLinkType link_type);
 
-const char *_nm_platform_error_to_string (NMPlatformError error);
-#define nm_platform_error_to_string(error) NM_UTILS_LOOKUP_STR (_nm_platform_error_to_string, error)
+const char *nm_platform_error_to_string (NMPlatformError error,
+                                         char *buf,
+                                         gsize buf_len);
+#define nm_platform_error_to_string_a(error) \
+	(nm_platform_error_to_string ((error), g_alloca (30), 30))
 
 #define NMP_SYSCTL_PATHID_ABSOLUTE(path) \
 	((const char *) NULL), -1, (path)
@@ -759,19 +992,39 @@ gboolean nm_platform_sysctl_set_ip6_hop_limit_safe (NMPlatform *self, const char
 const char *nm_platform_if_indextoname (NMPlatform *self, int ifindex, char *out_ifname/* of size IFNAMSIZ */);
 int nm_platform_if_nametoindex (NMPlatform *self, const char *ifname);
 
+const NMPObject *nm_platform_link_get_obj (NMPlatform *self,
+                                           int ifindex,
+                                           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);
 
-GArray *nm_platform_link_get_all (NMPlatform *self, gboolean sort_by_name);
+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);
 NMPlatformError nm_platform_link_bridge_add (NMPlatform *self, const char *name, const void *address, size_t address_len, const NMPlatformLink **out_link);
 NMPlatformError nm_platform_link_bond_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
 NMPlatformError nm_platform_link_team_add (NMPlatform *self, const char *name, const NMPlatformLink **out_link);
+NMPlatformError nm_platform_link_veth_add (NMPlatform *self, const char *name, const char *peer, const NMPlatformLink **out_link);
+
 gboolean nm_platform_link_delete (NMPlatform *self, int ifindex);
 
 gboolean nm_platform_link_set_netns (NMPlatform *self, int ifindex, int netns_fd);
 
+struct _NMDedupMultiHeadEntry;
+struct _NMPLookup;
+const struct _NMDedupMultiHeadEntry *nm_platform_lookup (NMPlatform *platform,
+                                                         const struct _NMPLookup *lookup);
+
+gboolean nm_platform_lookup_predicate_routes_main (const NMPObject *obj,
+                                                   gpointer user_data);
+gboolean nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel (const NMPObject *obj,
+                                                                      gpointer user_data);
+
+GPtrArray *nm_platform_lookup_clone (NMPlatform *platform,
+                                     const struct _NMPLookup *lookup,
+                                     NMPObjectPredicateFunc predicate,
+                                     gpointer user_data);
+
 /* convienience methods to lookup the link and access fields of NMPlatformLink. */
 int nm_platform_link_get_ifindex (NMPlatform *self, const char *name);
 const char *nm_platform_link_get_name (NMPlatform *self, int ifindex);
@@ -808,7 +1061,8 @@ gboolean nm_platform_link_set_ipv6_token (NMPlatform *self, int ifindex, NMUtils
 
 gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex, guint8 *buf, size_t *length);
 NMPlatformError nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
-gboolean nm_platform_link_set_mtu (NMPlatform *self, int ifindex, guint32 mtu);
+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);
 
 char    *nm_platform_link_get_physical_port_id (NMPlatform *self, int ifindex);
@@ -938,9 +1192,8 @@ NMPlatformError nm_platform_link_sit_add (NMPlatform *self,
                                           const NMPlatformLnkSit *props,
                                           const NMPlatformLink **out_link);
 
-const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
-GArray *nm_platform_ip4_address_get_all (NMPlatform *self, int ifindex);
-GArray *nm_platform_ip6_address_get_all (NMPlatform *self, int ifindex);
+const NMPlatformIP6Address *nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr address);
+
 gboolean nm_platform_ip4_address_add (NMPlatform *self,
                                       int ifindex,
                                       in_addr_t address,
@@ -960,18 +1213,44 @@ gboolean nm_platform_ip6_address_add (NMPlatform *self,
                                       guint32 flags);
 gboolean nm_platform_ip4_address_delete (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
 gboolean nm_platform_ip6_address_delete (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
-gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, GPtrArray **out_added_addresses);
-gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GArray *known_addresses, gboolean keep_link_local);
-gboolean nm_platform_address_flush (NMPlatform *self, int ifindex);
-
-const NMPlatformIP4Route *nm_platform_ip4_route_get (NMPlatform *self, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
-const NMPlatformIP6Route *nm_platform_ip6_route_get (NMPlatform *self, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
-GArray *nm_platform_ip4_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags);
-GArray *nm_platform_ip6_route_get_all (NMPlatform *self, int ifindex, NMPlatformGetRouteFlags flags);
-gboolean nm_platform_ip4_route_add (NMPlatform *self, const NMPlatformIP4Route *route);
-gboolean nm_platform_ip6_route_add (NMPlatform *self, const NMPlatformIP6Route *route);
-gboolean nm_platform_ip4_route_delete (NMPlatform *self, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
-gboolean nm_platform_ip6_route_delete (NMPlatform *self, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
+gboolean nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, GPtrArray *known_addresse);
+gboolean nm_platform_ip6_address_sync (NMPlatform *self, int ifindex, const GPtrArray *known_addresses, gboolean keep_link_local);
+gboolean nm_platform_ip_address_flush (NMPlatform *self,
+                                       int addr_family,
+                                       int ifindex);
+
+void nm_platform_ip_route_normalize (int addr_family,
+                                     NMPlatformIPRoute *route);
+
+NMPlatformError nm_platform_ip_route_add (NMPlatform *self,
+                                          NMPNlmFlags flags,
+                                          const NMPObject *route);
+NMPlatformError nm_platform_ip4_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP4Route *route);
+NMPlatformError nm_platform_ip6_route_add (NMPlatform *self, NMPNlmFlags flags, const NMPlatformIP6Route *route);
+
+gboolean nm_platform_ip_route_delete (NMPlatform *self, const NMPObject *route);
+
+GPtrArray *nm_platform_ip_route_get_prune_list (NMPlatform *self,
+                                                int addr_family,
+                                                int ifindex,
+                                                NMIPRouteTableSyncMode route_table_sync);
+
+gboolean nm_platform_ip_route_sync (NMPlatform *self,
+                                    int addr_family,
+                                    int ifindex,
+                                    GPtrArray *routes,
+                                    GPtrArray *routes_prune,
+                                    GPtrArray **out_temporary_not_available);
+
+gboolean nm_platform_ip_route_flush (NMPlatform *self,
+                                     int addr_family,
+                                     int ifindex);
+
+NMPlatformError nm_platform_ip_route_get (NMPlatform *self,
+                                          int addr_family,
+                                          gconstpointer address,
+                                          int oif_ifindex,
+                                          NMPObject **out_route);
 
 const char *nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len);
 const char *nm_platform_lnk_gre_to_string (const NMPlatformLnkGre *lnk, char *buf, gsize len);
@@ -1006,23 +1285,39 @@ int nm_platform_lnk_vlan_cmp (const NMPlatformLnkVlan *a, const NMPlatformLnkVla
 int nm_platform_lnk_vxlan_cmp (const NMPlatformLnkVxlan *a, const NMPlatformLnkVxlan *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);
-int nm_platform_ip4_route_cmp_full (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, gboolean consider_host_part);
-int nm_platform_ip6_route_cmp_full (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b, gboolean consider_host_part);
+
+int nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b, NMPlatformIPRouteCmpType cmp_type);
+int nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b, NMPlatformIPRouteCmpType cmp_type);
 
 static inline int
-nm_platform_ip4_route_cmp (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b)
+nm_platform_ip4_route_cmp_full (const NMPlatformIP4Route *a, const NMPlatformIP4Route *b)
 {
-	return nm_platform_ip4_route_cmp_full (a, b, TRUE);
+	return nm_platform_ip4_route_cmp (a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
 }
 
 static inline int
-nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b)
+nm_platform_ip6_route_cmp_full (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b)
 {
-	return nm_platform_ip6_route_cmp_full (a, b, TRUE);
+	return nm_platform_ip6_route_cmp (a, b, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
 }
 
-gboolean nm_platform_check_support_kernel_extended_ifa_flags (NMPlatform *self);
-gboolean nm_platform_check_support_user_ipv6ll (NMPlatform *self);
+void nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h);
+void nm_platform_ip4_address_hash_update (const NMPlatformIP4Address *obj, NMHashState *h);
+void nm_platform_ip6_address_hash_update (const NMPlatformIP6Address *obj, NMHashState *h);
+void nm_platform_ip4_route_hash_update (const NMPlatformIP4Route *obj, NMPlatformIPRouteCmpType cmp_type, NMHashState *h);
+void nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRouteCmpType cmp_type, NMHashState *h);
+void nm_platform_lnk_gre_hash_update (const NMPlatformLnkGre *obj, NMHashState *h);
+void nm_platform_lnk_infiniband_hash_update (const NMPlatformLnkInfiniband *obj, NMHashState *h);
+void nm_platform_lnk_ip6tnl_hash_update (const NMPlatformLnkIp6Tnl *obj, NMHashState *h);
+void nm_platform_lnk_ipip_hash_update (const NMPlatformLnkIpIp *obj, NMHashState *h);
+void nm_platform_lnk_macsec_hash_update (const NMPlatformLnkMacsec *obj, NMHashState *h);
+void nm_platform_lnk_macvlan_hash_update (const NMPlatformLnkMacvlan *obj, NMHashState *h);
+void nm_platform_lnk_sit_hash_update (const NMPlatformLnkSit *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);
+
+NMPlatformKernelSupportFlags nm_platform_check_kernel_support (NMPlatform *self,
+                                                               NMPlatformKernelSupportFlags request_flags);
 
 const char *nm_platform_link_flags2str (unsigned flags, char *buf, gsize len);
 const char *nm_platform_link_inet6_addrgenmode2str (guint8 mode, char *buf, gsize len);
@@ -1035,4 +1330,10 @@ gboolean nm_platform_ethtool_set_wake_on_lan (NMPlatform *self, int ifindex, NMS
 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);
 
+void nm_platform_ip4_dev_route_blacklist_set (NMPlatform *self,
+                                              int ifindex,
+                                              GPtrArray *ip4_dev_route_blacklist);
+
+struct _NMDedupMultiIndex *nm_platform_get_multi_idx (NMPlatform *self);
+
 #endif /* __NETWORKMANAGER_PLATFORM_H__ */
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index 4acd4761..34215828 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -299,7 +299,7 @@ _netns_new (GError **error)
 		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
 		             "Failed opening mntns: %s",
 		             g_strerror (errsv));
-		close (fd_net);
+		nm_close (fd_net);
 		return NULL;
 	}
 
@@ -620,7 +620,7 @@ nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd)
 		       filename, g_strerror (errsv));
 		return FALSE;
 	}
-	close (fd);
+	nm_close (fd);
 
 	if (mount (PROC_SELF_NS_NET, filename, "none", MS_BIND, NULL) != 0) {
 		errsv = errno;
@@ -702,15 +702,11 @@ dispose (GObject *object)
 	NMPNetns *self = NMP_NETNS (object);
 	NMPNetnsPrivate *priv = NMP_NETNS_GET_PRIVATE (self);
 
-	if (priv->fd_net > 0) {
-		close (priv->fd_net);
-		priv->fd_net = 0;
-	}
+	nm_close (priv->fd_net);
+	priv->fd_net = -1;
 
-	if (priv->fd_mnt > 0) {
-		close (priv->fd_mnt);
-		priv->fd_mnt = 0;
-	}
+	nm_close (priv->fd_mnt);
+	priv->fd_mnt = -1;
 
 	G_OBJECT_CLASS (nmp_netns_parent_class)->dispose (object);
 }
diff --git a/src/platform/nmp-netns.h b/src/platform/nmp-netns.h
index 56c1e7e8..55a4b95f 100644
--- a/src/platform/nmp-netns.h
+++ b/src/platform/nmp-netns.h
@@ -60,8 +60,7 @@ _nm_auto_pop_netns (NMPNetns **p)
 		errno = errsv;
 	}
 }
-
-#define nm_auto_pop_netns __attribute__((cleanup(_nm_auto_pop_netns)))
+#define nm_auto_pop_netns nm_auto(_nm_auto_pop_netns)
 
 gboolean nmp_netns_bind_to_path (NMPNetns *self, const char *filename, int *out_fd);
 gboolean nmp_netns_bind_to_path_destroy (NMPNetns *self, const char *filename);
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index ecec8f0f..a8600705 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -51,6 +51,11 @@
 
 /*****************************************************************************/
 
+typedef struct {
+	NMDedupMultiIdxType parent;
+	NMPCacheIdType cache_id_type;
+} DedupMultiIdxType;
+
 struct _NMPCache {
 	/* the cache contains only one hash table for all object types, and similarly
 	 * it contains only one NMMultiIndex.
@@ -66,23 +71,238 @@ struct _NMPCache {
 	 * This effectively merges the udev-device cache into the NMPCache.
 	 */
 
-	GHashTable *idx_main;
-	NMMultiIndex *idx_multi;
+	NMDedupMultiIndex *multi_idx;
+
+	/* an idx_type entry for each NMP_CACHE_ID_TYPE. Note that NONE (zero)
+	 * is skipped, so the index is shifted by one: idx_type[cache_id_type - 1].
+	 *
+	 * Don't bother, use _idx_type_get() instead! */
+	DedupMultiIdxType idx_types[NMP_CACHE_ID_TYPE_MAX];
 
 	gboolean use_udev;
 };
 
 /*****************************************************************************/
 
-static inline guint
-_id_hash_ip6_addr (const struct in6_addr *addr)
+static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class;
+
+static void
+_idx_obj_id_hash_update (const NMDedupMultiIdxType *idx_type,
+                         const NMDedupMultiObj *obj,
+                         NMHashState *h)
 {
-	guint hash = (guint) 0x897da53981a13ULL;
-	int i;
+	const NMPObject *o = (NMPObject *) obj;
+
+	nm_assert (idx_type && idx_type->klass == &_dedup_multi_idx_type_class);
+	nm_assert (NMP_OBJECT_GET_TYPE (o) != NMP_OBJECT_TYPE_UNKNOWN);
+
+	nmp_object_id_hash_update (o, h);
+}
+
+static gboolean
+_idx_obj_id_equal (const NMDedupMultiIdxType *idx_type,
+                   const NMDedupMultiObj *obj_a,
+                   const NMDedupMultiObj *obj_b)
+{
+	const NMPObject *o_a = (NMPObject *) obj_a;
+	const NMPObject *o_b = (NMPObject *) obj_b;
+
+	nm_assert (idx_type && idx_type->klass == &_dedup_multi_idx_type_class);
+	nm_assert (NMP_OBJECT_GET_TYPE (o_a) != NMP_OBJECT_TYPE_UNKNOWN);
+	nm_assert (NMP_OBJECT_GET_TYPE (o_b) != NMP_OBJECT_TYPE_UNKNOWN);
 
-	for (i = 0; i < sizeof (*addr); i++)
-		hash = (hash * 33) + ((const guint8 *) addr)[i];
-	return hash;
+	return nmp_object_id_equal (o_a, o_b);
+}
+
+static guint
+_idx_obj_part (const DedupMultiIdxType *idx_type,
+               const NMPObject *obj_a,
+               const NMPObject *obj_b,
+               NMHashState *h)
+{
+	NMPObjectType obj_type;
+
+	/* the hash/equals functions are strongly related. So, keep them
+	 * side-by-side and do it all in _idx_obj_part(). */
+
+	nm_assert (idx_type);
+	nm_assert (idx_type->parent.klass == &_dedup_multi_idx_type_class);
+	nm_assert (obj_a);
+	nm_assert (NMP_OBJECT_GET_TYPE (obj_a) != NMP_OBJECT_TYPE_UNKNOWN);
+	nm_assert (!obj_b || (NMP_OBJECT_GET_TYPE (obj_b) != NMP_OBJECT_TYPE_UNKNOWN));
+	nm_assert (!h || !obj_b);
+
+	switch (idx_type->cache_id_type) {
+
+	case NMP_CACHE_ID_TYPE_OBJECT_TYPE:
+		if (obj_b)
+			return NMP_OBJECT_GET_TYPE (obj_a) == NMP_OBJECT_GET_TYPE (obj_b);
+		if (h) {
+			nm_hash_update_vals (h,
+			                     idx_type->cache_id_type,
+			                     NMP_OBJECT_GET_TYPE (obj_a));
+		}
+		return 1;
+
+	case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME:
+		if (NMP_OBJECT_GET_TYPE (obj_a) != NMP_OBJECT_TYPE_LINK) {
+			/* first check, whether obj_a is suitable for this idx_type.
+			 * If not, return 0 (which is correct for partitionable(), hash() and equal()
+			 * functions. */
+			if (h)
+				nm_hash_update_val (h, obj_a);
+			return 0;
+		}
+		if (obj_b) {
+			/* we are in equal() mode. Compare obj_b with obj_a. */
+			return    NMP_OBJECT_GET_TYPE (obj_b) == NMP_OBJECT_TYPE_LINK
+			       && nm_streq (obj_a->link.name, obj_b->link.name);
+		}
+		if (h) {
+			nm_hash_update_val (h, idx_type->cache_id_type);
+			nm_hash_update_strarr (h, obj_a->link.name);
+		}
+		/* just return 1, to indicate that obj_a is partitionable by this idx_type. */
+		return 1;
+
+	case NMP_CACHE_ID_TYPE_DEFAULT_ROUTES:
+		if (   !NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_a), NMP_OBJECT_TYPE_IP4_ROUTE,
+		                                                NMP_OBJECT_TYPE_IP6_ROUTE)
+		    || !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj_a->ip_route)
+		    || !nmp_object_is_visible (obj_a)) {
+			if (h)
+				nm_hash_update_val (h, obj_a);
+			return 0;
+		}
+		if (obj_b) {
+			return    NMP_OBJECT_GET_TYPE (obj_a) == NMP_OBJECT_GET_TYPE (obj_b)
+			       && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj_b->ip_route)
+			       && nmp_object_is_visible (obj_b);
+		}
+		if (h) {
+			nm_hash_update_vals (h,
+			                     idx_type->cache_id_type,
+			                     NMP_OBJECT_GET_TYPE (obj_a));
+		}
+		return 1;
+
+	case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX:
+		if (   !NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_a), NMP_OBJECT_TYPE_IP4_ADDRESS,
+		                                                NMP_OBJECT_TYPE_IP6_ADDRESS,
+		                                                NMP_OBJECT_TYPE_IP4_ROUTE,
+		                                                NMP_OBJECT_TYPE_IP6_ROUTE)
+		    || !nmp_object_is_visible (obj_a)) {
+			if (h)
+				nm_hash_update_val (h, obj_a);
+			return 0;
+		}
+		nm_assert (obj_a->object.ifindex > 0);
+		if (obj_b) {
+			return    NMP_OBJECT_GET_TYPE (obj_a) == NMP_OBJECT_GET_TYPE (obj_b)
+			       && obj_a->object.ifindex == obj_b->object.ifindex
+			       && nmp_object_is_visible (obj_b);
+		}
+		if (h) {
+			nm_hash_update_vals (h,
+			                     idx_type->cache_id_type,
+			                     obj_a->object.ifindex);
+		}
+		return 1;
+
+	case NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID:
+		obj_type = NMP_OBJECT_GET_TYPE (obj_a);
+		if (   !NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE,
+		                             NMP_OBJECT_TYPE_IP6_ROUTE)
+		    || obj_a->object.ifindex <= 0) {
+			if (h)
+				nm_hash_update_val (h, obj_a);
+			return 0;
+		}
+		if (obj_b) {
+			return    obj_type == NMP_OBJECT_GET_TYPE (obj_b)
+			       && obj_b->object.ifindex > 0
+			       && (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE
+			           ? (nm_platform_ip4_route_cmp (&obj_a->ip4_route, &obj_b->ip4_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) == 0)
+			           : (nm_platform_ip6_route_cmp (&obj_a->ip6_route, &obj_b->ip6_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) == 0));
+		}
+		if (h) {
+			nm_hash_update_val (h, idx_type->cache_id_type);
+			if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE)
+				nm_platform_ip4_route_hash_update (&obj_a->ip4_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, h);
+			else
+				nm_platform_ip6_route_hash_update (&obj_a->ip6_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, h);
+		}
+		return 1;
+
+	case NMP_CACHE_ID_TYPE_NONE:
+	case __NMP_CACHE_ID_TYPE_MAX:
+		break;
+	}
+	nm_assert_not_reached ();
+	return 0;
+}
+
+static gboolean
+_idx_obj_partitionable (const NMDedupMultiIdxType *idx_type,
+                        const NMDedupMultiObj *obj)
+{
+	return _idx_obj_part ((DedupMultiIdxType *) idx_type,
+	                      (NMPObject *) obj,
+	                      NULL,
+	                      NULL) != 0;
+}
+
+static void
+_idx_obj_partition_hash_update (const NMDedupMultiIdxType *idx_type,
+                                const NMDedupMultiObj *obj,
+                                NMHashState *h)
+{
+	_idx_obj_part ((DedupMultiIdxType *) idx_type,
+	               (NMPObject *) obj,
+	               NULL,
+	               h);
+}
+
+static gboolean
+_idx_obj_partition_equal (const NMDedupMultiIdxType *idx_type,
+                          const NMDedupMultiObj *obj_a,
+                          const NMDedupMultiObj *obj_b)
+{
+	return _idx_obj_part ((DedupMultiIdxType *) idx_type,
+	                      (NMPObject *) obj_a,
+	                      (NMPObject *) obj_b,
+	                      NULL);
+}
+
+static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class = {
+	.idx_obj_id_hash_update = _idx_obj_id_hash_update,
+	.idx_obj_id_equal = _idx_obj_id_equal,
+	.idx_obj_partitionable = _idx_obj_partitionable,
+	.idx_obj_partition_hash_update = _idx_obj_partition_hash_update,
+	.idx_obj_partition_equal = _idx_obj_partition_equal,
+};
+
+static void
+_dedup_multi_idx_type_init (DedupMultiIdxType *idx_type, NMPCacheIdType cache_id_type)
+{
+	nm_dedup_multi_idx_type_init ((NMDedupMultiIdxType *) idx_type,
+	                              &_dedup_multi_idx_type_class);
+	idx_type->cache_id_type = cache_id_type;
+}
+
+/*****************************************************************************/
+
+static void
+_vlan_xgress_qos_mappings_hash_update (guint n_map,
+                                       const NMVlanQosMapping *map,
+                                       NMHashState *h)
+{
+	/* ensure no padding. */
+	G_STATIC_ASSERT (sizeof (NMVlanQosMapping) == 2 * sizeof (guint32));
+
+	nm_hash_update_val (h, n_map);
+	if (n_map)
+		nm_hash_update (h, map, n_map * sizeof (*map));
 }
 
 static int
@@ -150,12 +370,18 @@ _link_get_driver (struct udev_device *udevice, const char *kind, int ifindex)
 }
 
 void
-_nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev)
+_nmp_object_fixup_link_udev_fields (NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev)
 {
 	const char *driver = NULL;
 	gboolean initialized = FALSE;
+	NMPObject *obj;
 
-	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK);
+	nm_assert (obj_orig || *obj_new);
+	nm_assert (obj_new);
+	nm_assert (!obj_orig || NMP_OBJECT_GET_TYPE (obj_orig) == NMP_OBJECT_TYPE_LINK);
+	nm_assert (!*obj_new || NMP_OBJECT_GET_TYPE (*obj_new) == NMP_OBJECT_TYPE_LINK);
+
+	obj = *obj_new ?: obj_orig;
 
 	/* The link contains internal fields that are combined by
 	 * properties from netlink and udev. Update those properties */
@@ -179,17 +405,34 @@ _nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev)
 		}
 	}
 
+	if (   nm_streq0 (obj->link.driver, driver)
+	    && obj->link.initialized == initialized)
+		return;
+
+	if (!*obj_new)
+		obj = *obj_new = nmp_object_clone (obj, FALSE);
+
 	obj->link.driver = driver;
 	obj->link.initialized = initialized;
 }
 
 static void
-_nmp_object_fixup_link_master_connected (NMPObject *obj, const NMPCache *cache)
+_nmp_object_fixup_link_master_connected (NMPObject **obj_new, NMPObject *obj_orig, const NMPCache *cache)
 {
-	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK);
+	NMPObject *obj;
+
+	nm_assert (obj_orig || *obj_new);
+	nm_assert (obj_new);
+	nm_assert (!obj_orig || NMP_OBJECT_GET_TYPE (obj_orig) == NMP_OBJECT_TYPE_LINK);
+	nm_assert (!*obj_new || NMP_OBJECT_GET_TYPE (*obj_new) == NMP_OBJECT_TYPE_LINK);
+
+	obj = *obj_new ?: obj_orig;
 
-	if (nmp_cache_link_connected_needs_toggle (cache, obj, NULL, NULL))
+	if (nmp_cache_link_connected_needs_toggle (cache, obj, NULL, NULL)) {
+		if (!*obj_new)
+			obj = *obj_new = nmp_object_clone (obj, FALSE);
 		obj->link.connected = !obj->link.connected;
+	}
 }
 
 /*****************************************************************************/
@@ -204,33 +447,6 @@ nmp_class_from_type (NMPObjectType obj_type)
 
 /*****************************************************************************/
 
-NMPObject *
-nmp_object_ref (NMPObject *obj)
-{
-	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
-	g_return_val_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT, NULL);
-	obj->_ref_count++;
-
-	return obj;
-}
-
-void
-nmp_object_unref (NMPObject *obj)
-{
-	if (obj) {
-		g_return_if_fail (obj->_ref_count > 0);
-		g_return_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT);
-		if (--obj->_ref_count <= 0) {
-			const NMPClass *klass = obj->_class;
-
-			nm_assert (!obj->is_cached);
-			if (klass->cmd_obj_dispose)
-				klass->cmd_obj_dispose (obj);
-			g_slice_free1 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object), obj);
-		}
-	}
-}
-
 static void
 _vt_cmd_obj_dispose_link (NMPObject *obj)
 {
@@ -259,7 +475,7 @@ _nmp_object_new_from_class (const NMPClass *klass)
 
 	obj = g_slice_alloc0 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object));
 	obj->_class = klass;
-	obj->_ref_count = 1;
+	obj->parent._ref_count = 1;
 	return obj;
 }
 
@@ -287,14 +503,29 @@ nmp_object_new_link (int ifindex)
 
 /*****************************************************************************/
 
-static const NMPObject *
+static void
 _nmp_object_stackinit_from_class (NMPObject *obj, const NMPClass *klass)
 {
+	nm_assert (obj);
+	nm_assert (klass);
+
+	memset (obj, 0, sizeof (NMPObject));
+	obj->_class = klass;
+	obj->parent._ref_count = NM_OBJ_REF_COUNT_STACKINIT;
+}
+
+static NMPObject *
+_nmp_object_stackinit_from_type (NMPObject *obj, NMPObjectType obj_type)
+{
+	const NMPClass *klass;
+
+	nm_assert (obj);
+	klass = nmp_class_from_type (obj_type);
 	nm_assert (klass);
 
 	memset (obj, 0, sizeof (NMPObject));
 	obj->_class = klass;
-	obj->_ref_count = NMP_REF_COUNT_STACKINIT;
+	obj->parent._ref_count = NM_OBJ_REF_COUNT_STACKINIT;
 	return obj;
 }
 
@@ -318,31 +549,24 @@ nmp_object_stackinit_id  (NMPObject *obj, const NMPObject *src)
 	nm_assert (obj);
 
 	klass = NMP_OBJECT_GET_CLASS (src);
-	if (!klass->cmd_obj_stackinit_id)
-		nmp_object_stackinit (obj, klass->obj_type, NULL);
-	else
-		klass->cmd_obj_stackinit_id (obj, src);
+	_nmp_object_stackinit_from_class (obj, klass);
+	if (klass->cmd_plobj_id_copy)
+		klass->cmd_plobj_id_copy (&obj->object, &src->object);
 	return obj;
 }
 
 const NMPObject *
 nmp_object_stackinit_id_link (NMPObject *obj, int ifindex)
 {
-	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_LINK, NULL);
+	_nmp_object_stackinit_from_type (obj, NMP_OBJECT_TYPE_LINK);
 	obj->link.ifindex = ifindex;
 	return obj;
 }
 
-static void
-_vt_cmd_obj_stackinit_id_link (NMPObject *obj, const NMPObject *src)
-{
-	nmp_object_stackinit_id_link (obj, src->link.ifindex);
-}
-
 const NMPObject *
 nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, guint8 plen, guint32 peer_address)
 {
-	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP4_ADDRESS, NULL);
+	_nmp_object_stackinit_from_type (obj, NMP_OBJECT_TYPE_IP4_ADDRESS);
 	obj->ip4_address.ifindex = ifindex;
 	obj->ip4_address.address = address;
 	obj->ip4_address.plen = plen;
@@ -350,64 +574,16 @@ nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 addres
 	return obj;
 }
 
-static void
-_vt_cmd_obj_stackinit_id_ip4_address (NMPObject *obj, const NMPObject *src)
-{
-	nmp_object_stackinit_id_ip4_address (obj, src->ip_address.ifindex, src->ip4_address.address, src->ip_address.plen, src->ip4_address.peer_address);
-}
-
 const NMPObject *
-nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, guint8 plen)
+nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address)
 {
-	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP6_ADDRESS, NULL);
+	_nmp_object_stackinit_from_type (obj, NMP_OBJECT_TYPE_IP6_ADDRESS);
 	obj->ip4_address.ifindex = ifindex;
 	if (address)
 		obj->ip6_address.address = *address;
-	obj->ip6_address.plen = plen;
 	return obj;
 }
 
-static void
-_vt_cmd_obj_stackinit_id_ip6_address (NMPObject *obj, const NMPObject *src)
-{
-	nmp_object_stackinit_id_ip6_address (obj, src->ip_address.ifindex, &src->ip6_address.address, src->ip_address.plen);
-}
-
-const NMPObject *
-nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, guint8 plen, guint32 metric)
-{
-	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
-	obj->ip4_route.ifindex = ifindex;
-	obj->ip4_route.network = network;
-	obj->ip4_route.plen = plen;
-	obj->ip4_route.metric = metric;
-	return obj;
-}
-
-static void
-_vt_cmd_obj_stackinit_id_ip4_route (NMPObject *obj, const NMPObject *src)
-{
-	nmp_object_stackinit_id_ip4_route (obj, src->ip_route.ifindex, src->ip4_route.network, src->ip_route.plen, src->ip_route.metric);
-}
-
-const NMPObject *
-nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, guint8 plen, guint32 metric)
-{
-	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
-	obj->ip6_route.ifindex = ifindex;
-	if (network)
-		obj->ip6_route.network = *network;
-	obj->ip6_route.plen = plen;
-	obj->ip6_route.metric = metric;
-	return obj;
-}
-
-static void
-_vt_cmd_obj_stackinit_id_ip6_route (NMPObject *obj, const NMPObject *src)
-{
-	nmp_object_stackinit_id_ip6_route (obj, src->ip_route.ifindex, &src->ip6_route.network, src->ip_route.plen, src->ip_route.metric);
-}
-
 /*****************************************************************************/
 
 const char *
@@ -435,9 +611,8 @@ nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode
 		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,%d,%ccache,%calive,%cvisible; %s]",
-		            klass->obj_type_name, obj, obj->_ref_count,
-		            obj->is_cached ? '+' : '-',
+		            "[%s,%p,%u,%calive,%cvisible; %s]",
+		            klass->obj_type_name, obj, obj->parent._ref_count,
 		            nmp_object_is_alive (obj) ? '+' : '-',
 		            nmp_object_is_visible (obj) ? '+' : '-',
 		            NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf2, sizeof (buf2)));
@@ -462,9 +637,8 @@ _vt_cmd_obj_to_string_link (const NMPObject *obj, NMPObjectToStringMode to_strin
 		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,%d,%ccache,%calive,%cvisible,%cin-nl,%p; %s]",
-		            klass->obj_type_name, obj, obj->_ref_count,
-		            obj->is_cached ? '+' : '-',
+		            "[%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 ? '+' : '-',
@@ -503,9 +677,8 @@ _vt_cmd_obj_to_string_lnk_vlan (const NMPObject *obj, NMPObjectToStringMode to_s
 	case NMP_OBJECT_TO_STRING_ALL:
 
 		g_snprintf (buf, buf_size,
-		            "[%s,%p,%d,%ccache,%calive,%cvisible; %s]",
-		            klass->obj_type_name, obj, obj->_ref_count,
-		            obj->is_cached ? '+' : '-',
+		            "[%s,%p,%u,%calive,%cvisible; %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)));
@@ -562,10 +735,53 @@ _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsiz
 _vt_cmd_plobj_to_string_id (link,        NMPlatformLink,       "%d",            obj->ifindex);
 _vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s", obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen,
                                                                obj->peer_address != obj->address ? "," : "",
-                                                               obj->peer_address != obj->address ? nm_utils_inet4_ntop (obj->peer_address & nm_utils_ip4_prefix_to_netmask (obj->plen), buf2) : "");
+                                                               obj->peer_address != obj->address ? nm_utils_inet4_ntop (nm_utils_ip4_address_clear_host_address (obj->peer_address, obj->plen), buf2) : "");
 _vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1));
-_vt_cmd_plobj_to_string_id (ip4_route,   NMPlatformIP4Route,   "%d: %s/%d %d",  obj->ifindex, nm_utils_inet4_ntop ( obj->network, buf1), obj->plen, obj->metric);
-_vt_cmd_plobj_to_string_id (ip6_route,   NMPlatformIP6Route,   "%d: %s/%d %d",  obj->ifindex, nm_utils_inet6_ntop (&obj->network, buf1), obj->plen, obj->metric);
+
+void
+nmp_object_hash_update (const NMPObject *obj, NMHashState *h)
+{
+	const NMPClass *klass;
+
+	g_return_if_fail (NMP_OBJECT_IS_VALID (obj));
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	nm_hash_update_val (h, klass->obj_type);
+	if (klass->cmd_obj_hash_update)
+		klass->cmd_obj_hash_update (obj, h);
+	else if (klass->cmd_plobj_hash_update)
+		klass->cmd_plobj_hash_update (&obj->object, h);
+	else
+		nm_hash_update_val (h, obj);
+}
+
+static void
+_vt_cmd_obj_hash_update_link (const NMPObject *obj, NMHashState *h)
+{
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK);
+
+	nm_platform_link_hash_update (&obj->link, h);
+	nm_hash_update_vals (h,
+	                     obj->_link.netlink.is_in_netlink,
+	                     obj->_link.udev.device);
+	if (obj->_link.netlink.lnk)
+		nmp_object_hash_update (obj->_link.netlink.lnk, h);
+}
+
+static void
+_vt_cmd_obj_hash_update_lnk_vlan (const NMPObject *obj, NMHashState *h)
+{
+	nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LNK_VLAN);
+
+	nm_platform_lnk_vlan_hash_update (&obj->lnk_vlan, h);
+	_vlan_xgress_qos_mappings_hash_update (obj->_lnk_vlan.n_ingress_qos_map,
+	                                       obj->_lnk_vlan.ingress_qos_map,
+	                                       h);
+	_vlan_xgress_qos_mappings_hash_update (obj->_lnk_vlan.n_egress_qos_map,
+	                                       obj->_lnk_vlan.egress_qos_map,
+	                                       h);
+}
 
 int
 nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
@@ -585,8 +801,10 @@ nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 	klass1 = NMP_OBJECT_GET_CLASS (obj1);
 	klass2 = NMP_OBJECT_GET_CLASS (obj2);
 
-	if (klass1 != klass2)
+	if (klass1 != klass2) {
+		nm_assert (klass1->obj_type != klass2->obj_type);
 		return klass1->obj_type < klass2->obj_type ? -1 : 1;
+	}
 
 	if (klass1->cmd_obj_cmp)
 		return klass1->cmd_obj_cmp (obj1, obj2);
@@ -731,16 +949,12 @@ _vt_cmd_plobj_id_copy (ip6_address, NMPlatformIP6Address, {
 	dst->address = src->address;
 });
 _vt_cmd_plobj_id_copy (ip4_route, NMPlatformIP4Route, {
-	dst->ifindex = src->ifindex;
-	dst->plen = src->plen;
-	dst->metric = src->metric;
-	dst->network = src->network;
+	*dst = *src;
+	nm_assert (nm_platform_ip4_route_cmp (dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0);
 });
 _vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, {
-	dst->ifindex = src->ifindex;
-	dst->plen = src->plen;
-	dst->metric = src->metric;
-	dst->network = src->network;
+	*dst = *src;
+	nm_assert (nm_platform_ip6_route_cmp (dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0);
 });
 
 /* Uses internally nmp_object_copy(), hence it also violates the const
@@ -761,127 +975,157 @@ nmp_object_clone (const NMPObject *obj, gboolean id_only)
 	return dst;
 }
 
-gboolean
-nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2)
+int
+nmp_object_id_cmp (const NMPObject *obj1, const NMPObject *obj2)
 {
-	const NMPClass *klass;
+	const NMPClass *klass, *klass2;
 
-	if (obj1 == obj2)
-		return TRUE;
-	if (!obj1 || !obj2)
-		return FALSE;
+	NM_CMP_SELF (obj1, obj2);
 
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE);
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE);
 
 	klass = NMP_OBJECT_GET_CLASS (obj1);
-	return    klass == NMP_OBJECT_GET_CLASS (obj2)
-	       && klass->cmd_plobj_id_equal
-	       && klass->cmd_plobj_id_equal (&obj1->object, &obj2->object);
+	nm_assert (!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp);
+
+	klass2 = NMP_OBJECT_GET_CLASS (obj2);
+	nm_assert (klass);
+	if (klass != klass2) {
+		nm_assert (klass2);
+		NM_CMP_DIRECT (klass->obj_type, klass2->obj_type);
+		/* resort to pointer comparison */
+		if (klass < klass2)
+			return -1;
+		return 1;
+	}
+
+	if (!klass->cmd_plobj_id_cmp) {
+		/* the klass doesn't implement ID cmp(). That means, different objects
+		 * never compare equal, but the cmp() according to their pointer value. */
+		return (obj1 < obj2) ? -1 : 1;
+	}
+
+	return klass->cmd_plobj_id_cmp (&obj1->object, &obj2->object);
 }
 
-#define _vt_cmd_plobj_id_equal(type, plat_type, cmd) \
-static gboolean \
-_vt_cmd_plobj_id_equal_##type (const NMPlatformObject *_obj1, const NMPlatformObject *_obj2) \
+#define _vt_cmd_plobj_id_cmp(type, plat_type, cmd) \
+static int \
+_vt_cmd_plobj_id_cmp_##type (const NMPlatformObject *_obj1, const NMPlatformObject *_obj2) \
 { \
 	const plat_type *const obj1 = (const plat_type *) _obj1; \
 	const plat_type *const obj2 = (const plat_type *) _obj2; \
-	return (cmd); \
-}
-_vt_cmd_plobj_id_equal (link, NMPlatformLink,
-                           obj1->ifindex == obj2->ifindex);
-_vt_cmd_plobj_id_equal (ip4_address, NMPlatformIP4Address,
-                           obj1->ifindex == obj2->ifindex
-                        && obj1->plen == obj2->plen
-                        && obj1->address == obj2->address
-                        /* for IPv4 addresses, you can add the same local address with differing peer-adddress
-                         * (IFA_ADDRESS), provided that their net-part differs. */
-                        && ((obj1->peer_address ^ obj2->peer_address) & nm_utils_ip4_prefix_to_netmask (obj1->plen)) == 0);
-_vt_cmd_plobj_id_equal (ip6_address, NMPlatformIP6Address,
-                           obj1->ifindex == obj2->ifindex
-                        /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
-                        && IN6_ARE_ADDR_EQUAL (&obj1->address, &obj2->address));
-_vt_cmd_plobj_id_equal (ip4_route, NMPlatformIP4Route,
-                           obj1->ifindex == obj2->ifindex
-                        && obj1->plen == obj2->plen
-                        && obj1->metric == obj2->metric
-                        && nm_utils_ip4_address_clear_host_address (obj1->network, obj1->plen) == nm_utils_ip4_address_clear_host_address (obj2->network, obj2->plen));
-_vt_cmd_plobj_id_equal (ip6_route, NMPlatformIP6Route,
-                           obj1->ifindex == obj2->ifindex
-                        && obj1->plen == obj2->plen
-                        && obj1->metric == obj2->metric
-                        && ({
-                                struct in6_addr n1, n2;
-
-                                IN6_ARE_ADDR_EQUAL(nm_utils_ip6_address_clear_host_address (&n1, &obj1->network, obj1->plen),
-                                                   nm_utils_ip6_address_clear_host_address (&n2, &obj2->network, obj2->plen));
-                            }));
+	\
+	NM_CMP_SELF (obj1, obj2); \
+	{ cmd; } \
+	return 0; \
+}
+_vt_cmd_plobj_id_cmp (link, NMPlatformLink,
+                      NM_CMP_FIELD (obj1, obj2, ifindex);
+)
+_vt_cmd_plobj_id_cmp (ip4_address, NMPlatformIP4Address,
+                      NM_CMP_FIELD (obj1, obj2, ifindex);
+                      NM_CMP_FIELD (obj1, obj2, plen);
+                      NM_CMP_FIELD (obj1, obj2, address);
+                      /* for IPv4 addresses, you can add the same local address with differing peer-adddress
+                       * (IFA_ADDRESS), provided that their net-part differs. */
+                      NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX (obj1->peer_address, obj2->peer_address, obj1->plen);
+)
+_vt_cmd_plobj_id_cmp (ip6_address, NMPlatformIP6Address,
+                      NM_CMP_FIELD (obj1, obj2, ifindex);
+                      /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
+                      NM_CMP_FIELD_IN6ADDR (obj1, obj2, address);
+)
 
-guint
-nmp_object_id_hash (const NMPObject *obj)
+static int
+_vt_cmd_plobj_id_cmp_ip4_route (const NMPlatformObject *obj1, const NMPlatformObject *obj2)
 {
-	const NMPClass *klass;
+	return nm_platform_ip4_route_cmp ((NMPlatformIP4Route *) obj1, (NMPlatformIP4Route *) obj2, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID);
+}
 
-	if (!obj)
-		return 0;
+static int
+_vt_cmd_plobj_id_cmp_ip6_route (const NMPlatformObject *obj1, const NMPlatformObject *obj2)
+{
+	return nm_platform_ip6_route_cmp ((NMPlatformIP6Route *) obj1, (NMPlatformIP6Route *) obj2, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID);
+}
+
+void
+nmp_object_id_hash_update (const NMPObject *obj, NMHashState *h)
+{
+	const NMPClass *klass;
 
-	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), 0);
+	g_return_if_fail (NMP_OBJECT_IS_VALID (obj));
 
 	klass = NMP_OBJECT_GET_CLASS (obj);
 
-	if (klass->cmd_plobj_id_hash)
-		return klass->cmd_plobj_id_hash (&obj->object);
+	nm_assert (!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp);
+
+	if (!klass->cmd_plobj_id_hash_update) {
+		/* The klass doesn't implement ID compare. It means, to use pointer
+		 * equality. */
+		nm_hash_update_val (h, obj);
+		return;
+	}
 
-	/* unhashable objects implement pointer equality. */
-	return g_direct_hash (obj);
+	nm_hash_update_val (h, klass->obj_type);
+	klass->cmd_plobj_id_hash_update (&obj->object, h);
 }
 
-#define _vt_cmd_plobj_id_hash(type, plat_type, cmd) \
-static guint \
-_vt_cmd_plobj_id_hash_##type (const NMPlatformObject *_obj) \
+guint
+nmp_object_id_hash (const NMPObject *obj)
+{
+	NMHashState h;
+
+	if (!obj)
+		return 0;
+
+	nm_hash_init (&h, 914932607u);
+	nmp_object_id_hash_update (obj, &h);
+	return nm_hash_complete (&h);
+}
+
+#define _vt_cmd_plobj_id_hash_update(type, plat_type, cmd) \
+static void \
+_vt_cmd_plobj_id_hash_update_##type (const NMPlatformObject *_obj, NMHashState *h) \
 { \
 	const plat_type *const obj = (const plat_type *) _obj; \
-	guint hash; \
 	{ cmd; } \
-	return hash; \
 }
-_vt_cmd_plobj_id_hash (link, NMPlatformLink, {
-	hash = (guint) 3982791431u;
-	hash = hash      + ((guint) obj->ifindex);
+_vt_cmd_plobj_id_hash_update (link, NMPlatformLink, {
+	nm_hash_update_val (h, obj->ifindex);
 })
-_vt_cmd_plobj_id_hash (ip4_address, NMPlatformIP4Address, {
-	hash = (guint) 3591309853u;
-	hash = hash      + ((guint) obj->ifindex);
-	hash = hash * 33 + ((guint) obj->plen);
-	hash = hash * 33 + ((guint) obj->address);
-
-	/* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */
-	hash = hash * 33 + ((guint) (obj->peer_address & nm_utils_ip4_prefix_to_netmask (obj->plen)));
+_vt_cmd_plobj_id_hash_update (ip4_address, NMPlatformIP4Address, {
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     obj->plen,
+	                     obj->address,
+	                     /* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */
+	                     nm_utils_ip4_address_clear_host_address (obj->peer_address, obj->plen));
 })
-_vt_cmd_plobj_id_hash (ip6_address, NMPlatformIP6Address, {
-	hash = (guint) 2907861637u;
-	hash = hash      + ((guint) obj->ifindex);
-	/* for IPv6 addresses, the prefix length is not part of the primary identifier. */
-	hash = hash * 33 + _id_hash_ip6_addr (&obj->address);
+_vt_cmd_plobj_id_hash_update (ip6_address, NMPlatformIP6Address, {
+	nm_hash_update_vals (h,
+	                     obj->ifindex,
+	                     /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
+	                     obj->address);
 })
-_vt_cmd_plobj_id_hash (ip4_route, NMPlatformIP4Route, {
-	hash = (guint) 2569857221u;
-	hash = hash      + ((guint) obj->ifindex);
-	hash = hash * 33 + ((guint) obj->plen);
-	hash = hash * 33 + ((guint) obj->metric);
-	hash = hash * 33 + ((guint) nm_utils_ip4_address_clear_host_address (obj->network, obj->plen));
+_vt_cmd_plobj_id_hash_update (ip4_route, NMPlatformIP4Route, {
+	nm_platform_ip4_route_hash_update (obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
 })
-_vt_cmd_plobj_id_hash (ip6_route, NMPlatformIP6Route, {
-	hash = (guint) 3999787007u;
-	hash = hash      + ((guint) obj->ifindex);
-	hash = hash * 33 + ((guint) obj->plen);
-	hash = hash * 33 + ((guint) obj->metric);
-	hash = hash * 33 + ({
-	                        struct in6_addr n1;
-	                        _id_hash_ip6_addr (nm_utils_ip6_address_clear_host_address (&n1, &obj->network, obj->plen));
-	                    });
+_vt_cmd_plobj_id_hash_update (ip6_route, NMPlatformIP6Route, {
+	nm_platform_ip6_route_hash_update (obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
 })
 
+static inline void
+_vt_cmd_plobj_hash_update_ip4_route (const NMPlatformObject *obj, NMHashState *h)
+{
+	return nm_platform_ip4_route_hash_update ((const NMPlatformIP4Route *) obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, h);
+}
+
+static inline void
+_vt_cmd_plobj_hash_update_ip6_route (const NMPlatformObject *obj, NMHashState *h)
+{
+	return nm_platform_ip6_route_hash_update ((const NMPlatformIP6Route *) obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, h);
+}
+
 gboolean
 nmp_object_is_alive (const NMPObject *obj)
 {
@@ -958,365 +1202,92 @@ _vt_cmd_obj_is_visible_link (const NMPObject *obj)
 
 /*****************************************************************************/
 
-_NM_UTILS_LOOKUP_DEFINE (static, _nmp_cache_id_size_by_type, NMPCacheIdType, guint,
-	NM_UTILS_LOOKUP_DEFAULT (({ nm_assert_not_reached (); (guint) 0; })),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_OBJECT_TYPE,                            nm_offsetofend (NMPCacheId, object_type)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,               nm_offsetofend (NMPCacheId, object_type)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT,              nm_offsetofend (NMPCacheId, object_type)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT,            nm_offsetofend (NMPCacheId, object_type)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX,           nm_offsetofend (NMPCacheId, object_type_by_ifindex)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT,   nm_offsetofend (NMPCacheId, object_type_by_ifindex)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT, nm_offsetofend (NMPCacheId, object_type_by_ifindex)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,                         nm_offsetofend (NMPCacheId, link_by_ifname)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4,              nm_offsetofend (NMPCacheId, routes_by_destination_ip4)),
-	NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6,              nm_offsetofend (NMPCacheId, routes_by_destination_ip6)),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_CACHE_ID_TYPE_NONE),
-	NM_UTILS_LOOKUP_ITEM_IGNORE (__NMP_CACHE_ID_TYPE_MAX),
-);
-
-gboolean
-nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b)
-{
-	if (a->_id_type != b->_id_type)
-		return FALSE;
-	return memcmp (a, b, _nmp_cache_id_size_by_type (a->_id_type)) == 0;
-}
-
-guint
-nmp_cache_id_hash (const NMPCacheId *id)
-{
-	guint hash = 5381;
-	guint i, n;
-
-	n = _nmp_cache_id_size_by_type (id->_id_type);
-	for (i = 0; i < n; i++)
-		hash = ((hash << 5) + hash) + ((char *) id)[i]; /* hash * 33 + c */
-	return hash;
-}
-
-NMPCacheId *
-nmp_cache_id_clone (const NMPCacheId *id)
-{
-	NMPCacheId *id2;
-	guint n;
-
-	n = _nmp_cache_id_size_by_type (id->_id_type);
-	id2 = g_slice_alloc (n);
-	memcpy (id2, id, n);
-	return id2;
-}
+static const guint8 _supported_cache_ids_link[] = {
+	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+	NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
+	0,
+};
 
-void
-nmp_cache_id_destroy (NMPCacheId *id)
-{
-	guint n;
+static const guint8 _supported_cache_ids_ipx_address[] = {
+	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+	NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX,
+	0,
+};
 
-	n = _nmp_cache_id_size_by_type (id->_id_type);
-	g_slice_free1 (n, id);
-}
+static const guint8 _supported_cache_ids_ipx_route[] = {
+	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+	NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX,
+	NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
+	NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
+	0,
+};
 
 /*****************************************************************************/
 
 static void
-_nmp_cache_id_init (NMPCacheId *id, NMPCacheIdType id_type)
-{
-	/* there is no need to set the entire @id to zero when
-	 * initializing the ID.
-	 *
-	 * First, depending on the @id_type only part of the
-	 * @id is actually used (_nmp_cache_id_size_by_type).
-	 *
-	 * Second, the nmp_cache_id_init_*() *MUST* anyway make sure
-	 * that all relevant fields are set. Since it happens that
-	 * all structs have the packed attribute, there are no holes
-	 * due to alignment, and it becomes simple for nmp_cache_id_init_*()
-	 * to ensure that all fields are set. */
-
-#if NM_MORE_ASSERTS
-	nm_assert (id);
-	{
-		guint i;
-
-		/* initialized with some bogus canary to hopefully detect when we miss
-		 * to initialize a field of the cache-id. */
-		for (i = 0; i < sizeof (*id); i++) {
-			((char *) id)[i] = GPOINTER_TO_UINT (id) ^ i;
-		}
-	}
-#endif
-
-	id->_id_type = id_type;
-}
-
-NMPCacheId *
-nmp_cache_id_init_object_type (NMPCacheId *id, NMPObjectType obj_type, gboolean visible_only)
+_vt_dedup_obj_destroy (NMDedupMultiObj *obj)
 {
-	_nmp_cache_id_init (id, visible_only
-	                        ? NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY
-	                        : NMP_CACHE_ID_TYPE_OBJECT_TYPE);
-	id->object_type.obj_type = obj_type;
-	return id;
-}
-
-NMPCacheId *
-nmp_cache_id_init_addrroute_visible_by_ifindex (NMPCacheId *id,
-                                                NMPObjectType obj_type,
-                                                int ifindex)
-{
-	g_return_val_if_fail (NM_IN_SET (obj_type,
-	                                 NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE,
-	                                 NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP6_ROUTE), NULL);
-
-	if (ifindex <= 0)
-		return nmp_cache_id_init_object_type (id, obj_type, TRUE);
-
-	_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX);
-	id->object_type_by_ifindex.obj_type = obj_type;
-	memcpy (&id->object_type_by_ifindex._misaligned_ifindex, &ifindex, sizeof (int));
-	return id;
-}
-
-NMPCacheId *
-nmp_cache_id_init_routes_visible (NMPCacheId *id,
-                                  NMPObjectType obj_type,
-                                  gboolean with_default,
-                                  gboolean with_non_default,
-                                  int ifindex)
-{
-	g_return_val_if_fail (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE), NULL);
-
-	if (with_default) {
-		if (with_non_default) {
-			if (ifindex <= 0)
-				return nmp_cache_id_init_object_type (id, obj_type, TRUE);
-			return nmp_cache_id_init_addrroute_visible_by_ifindex (id, obj_type, ifindex);
-		}
-		_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT);
-	} else if (with_non_default)
-		_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT);
-	else
-		g_return_val_if_reached (NULL);
-
-	id->object_type_by_ifindex.obj_type = obj_type;
-	memcpy (&id->object_type_by_ifindex._misaligned_ifindex, &ifindex, sizeof (int));
-	return id;
-}
-
-NMPCacheId *
-nmp_cache_id_init_link_by_ifname (NMPCacheId *id,
-                                  const char *ifname)
-{
-	gsize l;
-
-	if (   !ifname
-	    || (l = strlen (ifname)) > sizeof (id->link_by_ifname.ifname_short))
-		g_return_val_if_reached (id);
-
-	_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_LINK_BY_IFNAME);
+	NMPObject *o = (NMPObject *) obj;
+	const NMPClass *klass;
 
-	memset (id->link_by_ifname.ifname_short, 0, sizeof (id->link_by_ifname.ifname_short));
-	/* the trailing NUL is dropped!! */
-	memcpy (id->link_by_ifname.ifname_short, ifname, l);
+	nm_assert (o->parent._ref_count == 0);
+	nm_assert (!o->parent._multi_idx);
 
-	return id;
+	klass = o->_class;
+	if (klass->cmd_obj_dispose)
+		klass->cmd_obj_dispose (o);
+	g_slice_free1 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object), o);
 }
 
-NMPCacheId *
-nmp_cache_id_init_routes_by_destination_ip4 (NMPCacheId *id,
-                                             guint32 network,
-                                             guint8 plen,
-                                             guint32 metric)
+static const NMDedupMultiObj *
+_vt_dedup_obj_clone (const NMDedupMultiObj *obj)
 {
-	_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4);
-	id->routes_by_destination_ip4.plen = plen;
-	memcpy (&id->routes_by_destination_ip4._misaligned_metric,  &metric, sizeof (guint32));
-	memcpy (&id->routes_by_destination_ip4._misaligned_network, &network, sizeof (guint32));
-	return id;
+	return (const NMDedupMultiObj *) nmp_object_clone ((const NMPObject *) obj, FALSE);
 }
 
-NMPCacheId *
-nmp_cache_id_init_routes_by_destination_ip6 (NMPCacheId *id,
-                                             const struct in6_addr *network,
-                                             guint8 plen,
-                                             guint32 metric)
-{
-	_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6);
-	id->routes_by_destination_ip4.plen = plen;
-	memcpy (&id->routes_by_destination_ip6._misaligned_metric,  &metric, sizeof (guint32));
-	memcpy (&id->routes_by_destination_ip6._misaligned_network, network ?: &nm_ip_addr_zero.addr6, sizeof (struct in6_addr));
-	return id;
-}
+#define DEDUP_MULTI_OBJ_CLASS_INIT() \
+	{ \
+		.obj_clone                      = _vt_dedup_obj_clone, \
+		.obj_destroy                    = _vt_dedup_obj_destroy, \
+		.obj_full_hash_update           = (void (*)(const NMDedupMultiObj *obj, NMHashState *h)) nmp_object_hash_update, \
+		.obj_full_equal                 = (gboolean (*)(const NMDedupMultiObj *obj_a, const NMDedupMultiObj *obj_b)) nmp_object_equal, \
+	}
 
 /*****************************************************************************/
 
-static gboolean
-_nmp_object_init_cache_id (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+static NMDedupMultiIdxType *
+_idx_type_get (const NMPCache *cache, NMPCacheIdType cache_id_type)
 {
-	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+	nm_assert (cache);
+	nm_assert (cache_id_type > NMP_CACHE_ID_TYPE_NONE);
+	nm_assert (cache_id_type <= NMP_CACHE_ID_TYPE_MAX);
+	nm_assert ((int) cache_id_type - 1 >= 0);
+	nm_assert ((int) cache_id_type - 1 < G_N_ELEMENTS (cache->idx_types));
 
-	switch (id_type) {
-	case NMP_CACHE_ID_TYPE_OBJECT_TYPE:
-		*out_id = nmp_cache_id_init_object_type (id, klass->obj_type, FALSE);
-		return TRUE;
-	case NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY:
-		if (nmp_object_is_visible (obj))
-			*out_id = nmp_cache_id_init_object_type (id, klass->obj_type, TRUE);
-		else
-			*out_id = NULL;
-		return TRUE;
-	default:
-		return    klass->cmd_obj_init_cache_id
-		       && klass->cmd_obj_init_cache_id (obj, id_type, id, out_id);
-	}
+	return (NMDedupMultiIdxType *) &cache->idx_types[cache_id_type - 1];
 }
 
-static const guint8 _supported_cache_ids_link[] = {
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,
-	NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
-	0,
-};
-
-static gboolean
-_vt_cmd_obj_init_cache_id_link (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
+gboolean
+nmp_cache_use_udev_get (const NMPCache *cache)
 {
-	switch (id_type) {
-	case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME:
-		if (obj->link.name[0]) {
-			*out_id = nmp_cache_id_init_link_by_ifname (id, obj->link.name);
-			return TRUE;
-		}
-		break;
-	default:
-		return FALSE;
-	}
-	*out_id = NULL;
-	return TRUE;
-}
-
-static const guint8 _supported_cache_ids_ipx_address[] = {
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,
-	NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX,
-	0,
-};
-
-static gboolean
-_vt_cmd_obj_init_cache_id_ipx_address (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
-{
-	switch (id_type) {
-	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
-		if (nmp_object_is_visible (obj)) {
-			nm_assert (obj->object.ifindex > 0);
-			*out_id = nmp_cache_id_init_addrroute_visible_by_ifindex (id, NMP_OBJECT_GET_TYPE (obj), obj->object.ifindex);
-			return TRUE;
-		}
-		break;
-	default:
-		return FALSE;
-	}
-	*out_id = NULL;
-	return TRUE;
-}
-
-static const guint8 _supported_cache_ids_ip4_route[] = {
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,
-	NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4,
-	0,
-};
-
-static const guint8 _supported_cache_ids_ip6_route[] = {
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,
-	NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6,
-	0,
-};
+	g_return_val_if_fail (cache, TRUE);
 
-static gboolean
-_vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
-{
-	switch (id_type) {
-	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
-		if (nmp_object_is_visible (obj)) {
-			nm_assert (obj->object.ifindex > 0);
-			*out_id = nmp_cache_id_init_addrroute_visible_by_ifindex (id, NMP_OBJECT_GET_TYPE (obj), obj->object.ifindex);
-			return TRUE;
-		}
-		break;
-	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT:
-		if (   nmp_object_is_visible  (obj)
-		    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
-			nm_assert (obj->object.ifindex > 0);
-			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), FALSE, TRUE, 0);
-			return TRUE;
-		}
-		break;
-	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT:
-		if (   nmp_object_is_visible  (obj)
-		    && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
-			nm_assert (obj->object.ifindex > 0);
-			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), TRUE, FALSE, 0);
-			return TRUE;
-		}
-		break;
-	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT:
-		if (   nmp_object_is_visible (obj)
-		    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
-			nm_assert (obj->object.ifindex > 0);
-			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), FALSE, TRUE, obj->object.ifindex);
-			return TRUE;
-		}
-		break;
-	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT:
-		if (   nmp_object_is_visible (obj)
-		    && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
-			nm_assert (obj->object.ifindex > 0);
-			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), TRUE, FALSE, obj->object.ifindex);
-			return TRUE;
-		}
-		break;
-	case NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4:
-		if (NMP_OBJECT_GET_CLASS (obj)->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) {
-			*out_id = nmp_cache_id_init_routes_by_destination_ip4 (id, obj->ip4_route.network, obj->ip_route.plen, obj->ip_route.metric);
-			return TRUE;
-		}
-		return FALSE;
-	case NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6:
-		if (NMP_OBJECT_GET_CLASS (obj)->obj_type == NMP_OBJECT_TYPE_IP6_ROUTE) {
-			*out_id = nmp_cache_id_init_routes_by_destination_ip6 (id, &obj->ip6_route.network, obj->ip_route.plen, obj->ip_route.metric);
-			return TRUE;
-		}
-		return FALSE;
-	default:
-		return FALSE;
-	}
-	*out_id = NULL;
-	return TRUE;
+	return cache->use_udev;
 }
 
 /*****************************************************************************/
 
 gboolean
-nmp_cache_use_udev_get (const NMPCache *cache)
+nmp_cache_link_connected_for_slave (int ifindex_master, const NMPObject *slave)
 {
-	g_return_val_if_fail (cache, TRUE);
+	nm_assert (NMP_OBJECT_GET_TYPE (slave) == NMP_OBJECT_TYPE_LINK);
 
-	return cache->use_udev;
+	return    ifindex_master > 0
+	       && slave->link.master == ifindex_master
+	       && slave->link.connected
+	       && nmp_object_is_visible (slave);
 }
 
-/*****************************************************************************/
-
 /**
  * nmp_cache_link_connected_needs_toggle:
  * @cache: the platform cache
@@ -1341,9 +1312,7 @@ nmp_cache_use_udev_get (const NMPCache *cache)
 gboolean
 nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave)
 {
-	const NMPlatformLink *const *links;
 	gboolean is_lower_up = FALSE;
-	guint len, i;
 
 	if (   !master
 	    || NMP_OBJECT_GET_TYPE (master) != NMP_OBJECT_TYPE_LINK
@@ -1361,27 +1330,23 @@ nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *m
 		potential_slave = NULL;
 
 	if (   potential_slave
-	    && nmp_object_is_visible (potential_slave)
-	    && potential_slave->link.ifindex > 0
-	    && potential_slave->link.master == master->link.ifindex
-	    && potential_slave->link.connected) {
+	    && nmp_cache_link_connected_for_slave (master->link.ifindex, potential_slave))
 		is_lower_up = TRUE;
-	} else {
-		NMPCacheId cache_id;
-
-		links = (const NMPlatformLink *const *) nmp_cache_lookup_multi (cache, nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, FALSE), &len);
-		for (i = 0; i < len; i++) {
-			const NMPlatformLink *link = links[i];
+	else {
+		NMPLookup lookup;
+		NMDedupMultiIter iter;
+		const NMPlatformLink *link = NULL;
+
+		nmp_cache_iter_for_each_link (&iter,
+		                              nmp_cache_lookup (cache,
+		                                                nmp_lookup_init_obj_type (&lookup,
+		                                                                          NMP_OBJECT_TYPE_LINK)),
+		                              &link) {
 			const NMPObject *obj = NMP_OBJECT_UP_CAST ((NMPlatformObject *) link);
 
-			nm_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST ((NMPlatformObject *) link)) == NMP_OBJECT_TYPE_LINK);
-
 			if (   (!potential_slave || potential_slave->link.ifindex != link->ifindex)
 			    && ignore_slave != obj
-			    && link->ifindex > 0
-			    && link->master == master->link.ifindex
-			    && nmp_object_is_visible (obj)
-			    && link->connected) {
+			    && nmp_cache_link_connected_for_slave (master->link.ifindex, obj)) {
 				is_lower_up = TRUE;
 				break;
 			}
@@ -1424,95 +1389,293 @@ nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int mas
 
 /*****************************************************************************/
 
-const NMPlatformObject *const *
-nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len)
+static const NMDedupMultiEntry *
+_lookup_entry_with_idx_type (const NMPCache *cache,
+                             NMPCacheIdType cache_id_type,
+                             const NMPObject *obj)
 {
-	return (const NMPlatformObject *const *) nm_multi_index_lookup (cache->idx_multi,
-	                                                                (const NMMultiIndexId *) cache_id,
-	                                                                out_len);
+	const NMDedupMultiEntry *entry;
+
+	nm_assert (cache);
+	nm_assert (NMP_OBJECT_IS_VALID (obj));
+
+	entry = nm_dedup_multi_index_lookup_obj (cache->multi_idx,
+	                                         _idx_type_get (cache, cache_id_type),
+	                                         obj);
+	nm_assert (!entry
+	           || (   NMP_OBJECT_IS_VALID (entry->obj)
+	               && NMP_OBJECT_GET_CLASS (entry->obj) == NMP_OBJECT_GET_CLASS (obj)));
+	return entry;
 }
 
-GArray *
-nmp_cache_lookup_multi_to_array (const NMPCache *cache, NMPObjectType obj_type, const NMPCacheId *cache_id)
+static const NMDedupMultiEntry *
+_lookup_entry (const NMPCache *cache, const NMPObject *obj)
 {
-	const NMPClass *klass = nmp_class_from_type (obj_type);
-	guint len, i;
-	const NMPlatformObject *const *objects;
-	GArray *array;
-
-	g_return_val_if_fail (klass, NULL);
+	return _lookup_entry_with_idx_type (cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj);
+}
 
-	objects = nmp_cache_lookup_multi (cache, cache_id, &len);
-	array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len);
+const NMDedupMultiEntry *
+nmp_cache_lookup_entry_with_idx_type (const NMPCache *cache,
+                                      NMPCacheIdType cache_id_type,
+                                      const NMPObject *obj)
+{
+	g_return_val_if_fail (cache, NULL);
+	g_return_val_if_fail (obj, NULL);
+	g_return_val_if_fail (cache_id_type > NMP_CACHE_ID_TYPE_NONE && cache_id_type <= NMP_CACHE_ID_TYPE_MAX, NULL);
 
-	for (i = 0; i < len; i++) {
-		nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[i])) == klass);
-		g_array_append_vals (array, objects[i], 1);
-	}
-	return array;
+	return _lookup_entry_with_idx_type (cache, cache_id_type, obj);
 }
 
-const NMPObject *
-nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj)
+const NMDedupMultiEntry *
+nmp_cache_lookup_entry (const NMPCache *cache, const NMPObject *obj)
 {
+	g_return_val_if_fail (cache, NULL);
 	g_return_val_if_fail (obj, NULL);
 
-	return g_hash_table_lookup (cache->idx_main, obj);
+	return _lookup_entry (cache, obj);
 }
 
-const NMPObject *
-nmp_cache_lookup_link (const NMPCache *cache, int ifindex)
+const NMDedupMultiEntry *
+nmp_cache_lookup_entry_link (const NMPCache *cache, int ifindex)
 {
 	NMPObject obj_needle;
 
-	return nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
+	g_return_val_if_fail (cache, NULL);
+	g_return_val_if_fail (ifindex > 0, NULL);
+
+	nmp_object_stackinit_id_link (&obj_needle, ifindex);
+	return _lookup_entry (cache, &obj_needle);
 }
 
-/**
- * nmp_cache_find_other_route_for_same_destination:
- * @cache:
- * @route:
- *
- * Look into the cache whether there is a route to the same destination,
- * in terms of network/plen,metric.
- *
- * Returns: (transfer none): the first found route object from the cache
- *   that has the same (network/plen,metric) values as @route, but has different
- *   ID. Or %NULL, if no such route exists.
- */
 const NMPObject *
-nmp_cache_find_other_route_for_same_destination (const NMPCache *cache, const NMPObject *route)
+nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj)
 {
-	NMPCacheId cache_id;
-	const NMPlatformObject *const *list;
+	return nm_dedup_multi_entry_get_obj (nmp_cache_lookup_entry (cache, obj));
+}
 
+const NMPObject *
+nmp_cache_lookup_link (const NMPCache *cache, int ifindex)
+{
+	return nm_dedup_multi_entry_get_obj (nmp_cache_lookup_entry_link (cache, ifindex));
+}
+
+/*****************************************************************************/
+
+const NMDedupMultiHeadEntry *
+nmp_cache_lookup_all (const NMPCache *cache,
+                      NMPCacheIdType cache_id_type,
+                      const NMPObject *select_obj)
+{
 	nm_assert (cache);
+	nm_assert (NMP_OBJECT_IS_VALID (select_obj));
 
-	switch (NMP_OBJECT_GET_TYPE (route)) {
+	return nm_dedup_multi_index_lookup_head (cache->multi_idx,
+	                                         _idx_type_get (cache, cache_id_type),
+	                                         select_obj);
+}
+
+static const NMPLookup *
+_L (const NMPLookup *lookup)
+{
+#if NM_MORE_ASSERTS
+	DedupMultiIdxType idx_type;
+
+	nm_assert (lookup);
+	_dedup_multi_idx_type_init (&idx_type, lookup->cache_id_type);
+	nm_assert (idx_type.parent.klass->idx_obj_partitionable  ((NMDedupMultiIdxType *) &idx_type, (NMDedupMultiObj *) &lookup->selector_obj));
+#endif
+	return lookup;
+}
+
+const NMPLookup *
+nmp_lookup_init_obj_type (NMPLookup *lookup,
+                          NMPObjectType obj_type)
+{
+	NMPObject *o;
+
+	nm_assert (lookup);
+
+	switch (obj_type) {
+	case NMP_OBJECT_TYPE_LINK:
+	case NMP_OBJECT_TYPE_IP4_ADDRESS:
+	case NMP_OBJECT_TYPE_IP6_ADDRESS:
 	case NMP_OBJECT_TYPE_IP4_ROUTE:
-		nmp_cache_id_init_routes_by_destination_ip4 (&cache_id, route->ip4_route.network, route->ip_route.plen, route->ip_route.metric);
-		break;
 	case NMP_OBJECT_TYPE_IP6_ROUTE:
-		nmp_cache_id_init_routes_by_destination_ip6 (&cache_id, &route->ip6_route.network, route->ip_route.plen, route->ip_route.metric);
-		break;
+		o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
+		lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE;
+		return _L (lookup);
 	default:
+		nm_assert_not_reached ();
+		return NULL;
+	}
+}
+
+const NMPLookup *
+nmp_lookup_init_link_by_ifname (NMPLookup *lookup,
+                                const char *ifname)
+{
+	NMPObject *o;
+
+	nm_assert (lookup);
+	nm_assert (ifname);
+	nm_assert (strlen (ifname) < IFNAMSIZ);
+
+	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_LINK);
+	if (g_strlcpy (o->link.name, ifname, sizeof (o->link.name)) >= sizeof (o->link.name))
 		g_return_val_if_reached (NULL);
+	lookup->cache_id_type = NMP_CACHE_ID_TYPE_LINK_BY_IFNAME;
+	return _L (lookup);
+}
+
+const NMPLookup *
+nmp_lookup_init_addrroute (NMPLookup *lookup,
+                           NMPObjectType obj_type,
+                           int ifindex)
+{
+	NMPObject *o;
+
+	nm_assert (lookup);
+	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS,
+	                                NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                NMP_OBJECT_TYPE_IP6_ROUTE));
+
+	if (ifindex <= 0) {
+		return nmp_lookup_init_obj_type (lookup,
+		                                 obj_type);
 	}
 
-	list = nmp_cache_lookup_multi (cache, &cache_id, NULL);
-	if (list) {
-		for (; *list; list++) {
-			const NMPObject *candidate = NMP_OBJECT_UP_CAST (*list);
+	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
+	o->object.ifindex = ifindex;
+	lookup->cache_id_type = NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX;
+	return _L (lookup);
+}
+
+const NMPLookup *
+nmp_lookup_init_route_default (NMPLookup *lookup,
+                               NMPObjectType obj_type)
+{
+	NMPObject *o;
 
-			nm_assert (NMP_OBJECT_GET_CLASS (route) == NMP_OBJECT_GET_CLASS (candidate));
+	nm_assert (lookup);
+	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                NMP_OBJECT_TYPE_IP6_ROUTE));
 
-			if (!nmp_object_id_equal (route, candidate))
-				return candidate;
-		}
+	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
+	o->object.ifindex = 1;
+	lookup->cache_id_type = NMP_CACHE_ID_TYPE_DEFAULT_ROUTES;
+	return _L (lookup);
+}
+
+const NMPLookup *
+nmp_lookup_init_route_by_weak_id (NMPLookup *lookup,
+                                  const NMPObject *obj)
+{
+	const NMPlatformIP4Route *r4;
+	const NMPlatformIP6Route *r6;
+
+	nm_assert (lookup);
+
+	switch (NMP_OBJECT_GET_TYPE (obj)) {
+	case NMP_OBJECT_TYPE_IP4_ROUTE:
+		r4 = NMP_OBJECT_CAST_IP4_ROUTE (obj);
+		return nmp_lookup_init_ip4_route_by_weak_id (lookup,
+		                                             r4->network,
+		                                             r4->plen,
+		                                             r4->metric,
+		                                             r4->tos);
+	case NMP_OBJECT_TYPE_IP6_ROUTE:
+		r6 = NMP_OBJECT_CAST_IP6_ROUTE (obj);
+		return nmp_lookup_init_ip6_route_by_weak_id (lookup,
+		                                             &r6->network,
+		                                             r6->plen,
+		                                             r6->metric,
+		                                             &r6->src,
+		                                             r6->src_plen);
+	default:
+		nm_assert_not_reached ();
+		return NULL;
 	}
-	return NULL;
 }
 
+const NMPLookup *
+nmp_lookup_init_ip4_route_by_weak_id (NMPLookup *lookup,
+                                      in_addr_t network,
+                                      guint plen,
+                                      guint32 metric,
+                                      guint8 tos)
+{
+	NMPObject *o;
+
+	nm_assert (lookup);
+
+	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_IP4_ROUTE);
+	o->object.ifindex = 1;
+	o->ip_route.plen = plen;
+	o->ip_route.metric = metric;
+	if (network)
+		o->ip4_route.network = network;
+	o->ip4_route.tos = tos;
+	lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID;
+	return _L (lookup);
+}
+
+const NMPLookup *
+nmp_lookup_init_ip6_route_by_weak_id (NMPLookup *lookup,
+                                      const struct in6_addr *network,
+                                      guint plen,
+                                      guint32 metric,
+                                      const struct in6_addr *src,
+                                      guint8 src_plen)
+{
+	NMPObject *o;
+
+	nm_assert (lookup);
+
+	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_IP6_ROUTE);
+	o->object.ifindex = 1;
+	o->ip_route.plen = plen;
+	o->ip_route.metric = metric;
+	if (network)
+		o->ip6_route.network = *network;
+	if (src)
+		o->ip6_route.src = *src;
+	o->ip6_route.src_plen = src_plen;
+	lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID;
+	return _L (lookup);
+}
+
+/*****************************************************************************/
+
+GArray *
+nmp_cache_lookup_to_array (const NMDedupMultiHeadEntry *head_entry,
+                           NMPObjectType obj_type,
+                           gboolean visible_only)
+{
+	const NMPClass *klass = nmp_class_from_type (obj_type);
+	NMDedupMultiIter iter;
+	const NMPObject *o;
+	GArray *array;
+
+	g_return_val_if_fail (klass, NULL);
+
+	array = g_array_sized_new (FALSE, FALSE,
+	                           klass->sizeof_public,
+	                           head_entry ? head_entry->len : 0);
+	nmp_cache_iter_for_each (&iter,
+	                         head_entry,
+	                         &o) {
+		nm_assert (NMP_OBJECT_GET_CLASS (o) == klass);
+		if (   visible_only
+		    && !nmp_object_is_visible (o))
+			continue;
+		g_array_append_vals (array, &o->object, 1);
+	}
+	return array;
+}
+
+/*****************************************************************************/
+
 const NMPObject *
 nmp_cache_lookup_link_full (const NMPCache *cache,
                             int ifindex,
@@ -1524,9 +1687,10 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 {
 	NMPObject obj_needle;
 	const NMPObject *obj;
-	const NMPlatformObject *const *list;
-	guint i, len;
-	NMPCacheId cache_id, *p_cache_id;
+	NMDedupMultiIter iter;
+	const NMDedupMultiHeadEntry *head_entry;
+	const NMPlatformLink *link = NULL;
+	NMPLookup lookup;
 
 	if (ifindex > 0) {
 		obj = nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
@@ -1541,25 +1705,21 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	} else if (!ifname && !match_fn)
 		return NULL;
 	else {
-		if (   ifname
-		    && strlen (ifname) <= sizeof (cache_id.link_by_ifname.ifname_short)) {
-			p_cache_id = nmp_cache_id_init_link_by_ifname (&cache_id, ifname);
-			ifname = NULL;
-		} else {
-			p_cache_id = nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, visible_only);
-			visible_only = FALSE;
-		}
+		if (ifname) {
+			if (strlen (ifname) >= IFNAMSIZ)
+				return NULL;
+			nmp_lookup_init_link_by_ifname (&lookup, ifname);
+		} else
+			nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
 
-		list = nmp_cache_lookup_multi (cache, p_cache_id, &len);
-		for (i = 0; i < len; i++) {
-			obj = NMP_OBJECT_UP_CAST (list[i]);
+		head_entry = nmp_cache_lookup (cache, &lookup);
+		nmp_cache_iter_for_each_link (&iter, head_entry, &link) {
+			obj = NMP_OBJECT_UP_CAST (link);
 
 			if (visible_only && !nmp_object_is_visible (obj))
 				continue;
 			if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type)
 				continue;
-			if (ifname && strcmp (ifname, obj->link.name))
-				continue;
 			if (match_fn && !match_fn (obj, user_data))
 				continue;
 
@@ -1569,455 +1729,710 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	}
 }
 
-GHashTable *
-nmp_cache_lookup_all_to_hash (const NMPCache *cache,
-                              NMPCacheId *cache_id,
-                              GHashTable *hash)
-{
-	NMMultiIndexIdIter iter;
-	gpointer plobj;
-
-	nm_multi_index_id_iter_init (&iter, cache->idx_multi, (const NMMultiIndexId *) cache_id);
-
-	if (nm_multi_index_id_iter_next (&iter, &plobj)) {
-		if (!hash)
-			hash = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL);
-
-		do {
-			g_hash_table_add (hash, nmp_object_ref (NMP_OBJECT_UP_CAST (plobj)));
-		} while (nm_multi_index_id_iter_next (&iter, &plobj));
-	}
-
-	return hash;
-}
-
 /*****************************************************************************/
 
 static void
-_nmp_cache_update_cache (NMPCache *cache, NMPObject *obj, gboolean remove)
-{
-	const guint8 *id_type;
-
-	for (id_type = NMP_OBJECT_GET_CLASS (obj)->supported_cache_ids; *id_type; id_type++) {
-		NMPCacheId cache_id_storage;
-		const NMPCacheId *cache_id;
-
-		if (!_nmp_object_init_cache_id (obj, *id_type, &cache_id_storage, &cache_id))
-			continue;
-		if (!cache_id)
-			continue;
-
-		/* We don't put @obj itself into the multi index, but &obj->object. As of now, all
-		 * users expect a pointer to NMPlatformObject, not NMPObject.
-		 * You can use NMP_OBJECT_UP_CAST() to retrieve the original @obj pointer.
-		 *
-		 * If need be, we could determine based on @id_type which pointer we want to store. */
-
-		if (remove) {
-			if (!nm_multi_index_remove (cache->idx_multi, &cache_id->base, &obj->object))
-				g_assert_not_reached ();
+_idxcache_update_other_cache_ids (NMPCache *cache,
+                                  NMPCacheIdType cache_id_type,
+                                  const NMPObject *obj_old,
+                                  const NMPObject *obj_new,
+                                  gboolean is_dump)
+{
+	const NMDedupMultiEntry *entry_new;
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_order;
+	NMDedupMultiIdxType *idx_type;
+
+	nm_assert (obj_new || obj_old);
+	nm_assert (!obj_new || NMP_OBJECT_GET_TYPE (obj_new) != NMP_OBJECT_TYPE_UNKNOWN);
+	nm_assert (!obj_old || NMP_OBJECT_GET_TYPE (obj_old) != NMP_OBJECT_TYPE_UNKNOWN);
+	nm_assert (!obj_old || !obj_new || NMP_OBJECT_GET_CLASS (obj_new) == NMP_OBJECT_GET_CLASS (obj_old));
+	nm_assert (!obj_old || !obj_new || !nmp_object_equal (obj_new, obj_old));
+	nm_assert (!obj_new || obj_new == nm_dedup_multi_index_obj_find (cache->multi_idx, obj_new));
+	nm_assert (!obj_old || obj_old == nm_dedup_multi_index_obj_find (cache->multi_idx, obj_old));
+
+	idx_type = _idx_type_get (cache, cache_id_type);
+
+	if (obj_old) {
+		entry_old = nm_dedup_multi_index_lookup_obj (cache->multi_idx,
+		                                             idx_type,
+		                                             obj_old);
+		if (!obj_new) {
+			if (entry_old)
+				nm_dedup_multi_index_remove_entry (cache->multi_idx, entry_old);
+			return;
+		}
+	} else
+		entry_old = NULL;
+
+	if (obj_new) {
+		if (   obj_old
+		    && nm_dedup_multi_idx_type_id_equal (idx_type, obj_old, obj_new)
+		    && nm_dedup_multi_idx_type_partition_equal (idx_type, obj_old, obj_new)) {
+			/* optimize. We just looked up the @obj_old entry and @obj_new compares equal
+			 * according to idx_obj_id_equal(). entry_new is the same as entry_old. */
+			entry_new = entry_old;
 		} else {
-			if (!nm_multi_index_add (cache->idx_multi, &cache_id->base, &obj->object))
-				g_assert_not_reached ();
+			entry_new = nm_dedup_multi_index_lookup_obj (cache->multi_idx,
+			                                             idx_type,
+			                                             obj_new);
 		}
-	}
-}
 
-static void
-_nmp_cache_update_add (NMPCache *cache, NMPObject *obj)
-{
-	nm_assert (!obj->is_cached);
-	nmp_object_ref (obj);
-	nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object));
-	if (!nm_g_hash_table_add (cache->idx_main, obj))
-		g_assert_not_reached ();
-	obj->is_cached = TRUE;
-	_nmp_cache_update_cache (cache, obj, FALSE);
-}
+		if (entry_new)
+			entry_order = entry_new;
+		else if (   entry_old
+		         && nm_dedup_multi_idx_type_partition_equal (idx_type, entry_old->obj, obj_new))
+			entry_order = entry_old;
+		else
+			entry_order = NULL;
+		nm_dedup_multi_index_add_full (cache->multi_idx,
+		                               idx_type,
+		                               obj_new,
+		                               is_dump
+		                                 ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
+		                                 : NM_DEDUP_MULTI_IDX_MODE_APPEND,
+		                               is_dump
+		                                 ? NULL
+		                                 : entry_order,
+		                               entry_new ?: NM_DEDUP_MULTI_ENTRY_MISSING,
+		                               entry_new ? entry_new->head : (entry_order ? entry_order->head : NULL),
+		                               &entry_new,
+		                               NULL);
 
-static void
-_nmp_cache_update_remove (NMPCache *cache, NMPObject *obj)
-{
-	nm_assert (obj->is_cached);
-	_nmp_cache_update_cache (cache, obj, TRUE);
-	obj->is_cached = FALSE;
-	if (!g_hash_table_remove (cache->idx_main, obj))
-		g_assert_not_reached ();
+#if NM_MORE_ASSERTS
+		if (entry_new) {
+			nm_assert (idx_type->klass->idx_obj_partitionable);
+			nm_assert (idx_type->klass->idx_obj_partition_equal);
+			nm_assert (idx_type->klass->idx_obj_partitionable (idx_type, entry_new->obj));
+			nm_assert (idx_type->klass->idx_obj_partition_equal (idx_type, (gpointer) obj_new, entry_new->obj));
+		}
+#endif
+	} else
+		entry_new = NULL;
 
-	/* @obj is possibly a dangling pointer at this point. No problem, multi-index doesn't dereference. */
-	nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object));
+	if (   entry_old
+	    && entry_old != entry_new)
+		nm_dedup_multi_index_remove_entry (cache->multi_idx, entry_old);
 }
 
 static void
-_nmp_cache_update_update (NMPCache *cache, NMPObject *obj, const NMPObject *new)
+_idxcache_update (NMPCache *cache,
+                  const NMDedupMultiEntry *entry_old,
+                  NMPObject *obj_new,
+                  gboolean is_dump,
+                  const NMDedupMultiEntry **out_entry_new)
 {
-	const guint8 *id_type;
+	const NMPClass *klass;
+	const guint8 *i_idx_type;
+	NMDedupMultiIdxType *idx_type_o = _idx_type_get (cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE);
+	const NMDedupMultiEntry *entry_new = NULL;
+	nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+	/* we update an object in the cache.
+	 *
+	 * Note that @entry_old MUST be what is currently tracked in multi_idx, and it must
+	 * have the same ID as @obj_new. */
 
-	nm_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (new));
-	nm_assert (obj->is_cached);
-	nm_assert (!new->is_cached);
+	nm_assert (cache);
+	nm_assert (entry_old || obj_new);
+	nm_assert (!obj_new || nmp_object_is_alive (obj_new));
+	nm_assert (!entry_old || entry_old == nm_dedup_multi_index_lookup_obj (cache->multi_idx, idx_type_o, entry_old->obj));
+	nm_assert (!obj_new || entry_old == nm_dedup_multi_index_lookup_obj (cache->multi_idx, idx_type_o, obj_new));
+	nm_assert (!entry_old || entry_old->head->idx_type == idx_type_o);
+	nm_assert (   !entry_old
+	           || !obj_new
+	           || nm_dedup_multi_idx_type_partition_equal (idx_type_o, entry_old->obj, obj_new));
+	nm_assert (   !entry_old
+	           || !obj_new
+	           || nm_dedup_multi_idx_type_id_equal (idx_type_o, entry_old->obj, obj_new));
+	nm_assert (   !entry_old
+	           || !obj_new
+	           || (   obj_new->parent.klass == ((const NMPObject *) entry_old->obj)->parent.klass
+	               && !obj_new->parent.klass->obj_full_equal ((NMDedupMultiObj *) obj_new, entry_old->obj)));
+
+	/* keep a reference to the pre-existing entry */
+	if (entry_old)
+		obj_old = nmp_object_ref (entry_old->obj);
+
+	/* first update the main index NMP_CACHE_ID_TYPE_OBJECT_TYPE.
+	 * We already know the pre-existing @entry old, so all that
+	 * nm_dedup_multi_index_add_full() effectively does, is update the
+	 * obj reference.
+	 *
+	 * We also get the new boxed object, which we need below. */
+	if (obj_new) {
+		nm_auto_nmpobj NMPObject *obj_old2 = NULL;
+
+		nm_dedup_multi_index_add_full (cache->multi_idx,
+		                               idx_type_o,
+		                               obj_new,
+		                               is_dump
+		                                 ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE
+		                                 : NM_DEDUP_MULTI_IDX_MODE_APPEND,
+		                               NULL,
+		                               entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING,
+		                               NULL,
+		                               &entry_new,
+		                               (const NMDedupMultiObj **) &obj_old2);
+		nm_assert (entry_new);
+		nm_assert (obj_old == obj_old2);
+		nm_assert (!entry_old || entry_old == entry_new);
+	} else
+		nm_dedup_multi_index_remove_entry (cache->multi_idx, entry_old);
 
-	for (id_type = NMP_OBJECT_GET_CLASS (obj)->supported_cache_ids; *id_type; id_type++) {
-		NMPCacheId cache_id_storage_obj, cache_id_storage_new;
-		const NMPCacheId *cache_id_obj, *cache_id_new;
+	/* now update all other indexes. We know the previously boxed entry, and the
+	 * newly boxed one. */
+	klass = NMP_OBJECT_GET_CLASS (entry_new ? entry_new->obj : obj_old);
+	for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) {
+		NMPCacheIdType id_type = *i_idx_type;
 
-		if (!_nmp_object_init_cache_id (obj, *id_type, &cache_id_storage_obj, &cache_id_obj))
+		if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE)
 			continue;
-		if (!_nmp_object_init_cache_id (new, *id_type, &cache_id_storage_new, &cache_id_new))
-			g_assert_not_reached ();
-		if (!nm_multi_index_move (cache->idx_multi, (NMMultiIndexId *) cache_id_obj, (NMMultiIndexId *) cache_id_new, &obj->object))
-			g_assert_not_reached ();
+		_idxcache_update_other_cache_ids (cache, id_type,
+		                                  obj_old,
+		                                  entry_new ? entry_new->obj : NULL,
+		                                  is_dump);
 	}
-	nmp_object_copy (obj, new, FALSE);
+
+	NM_SET_OUT (out_entry_new, entry_new);
 }
 
 NMPCacheOpsType
-nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+nmp_cache_remove (NMPCache *cache,
+                  const NMPObject *obj_needle,
+                  gboolean equals_by_ptr,
+                  gboolean only_dirty,
+                  const NMPObject **out_obj_old)
 {
-	NMPObject *old;
+	const NMDedupMultiEntry *entry_old;
+	const NMPObject *obj_old;
 
-	nm_assert (NMP_OBJECT_IS_VALID (obj));
+	entry_old = _lookup_entry (cache, obj_needle);
 
-	old = g_hash_table_lookup (cache->idx_main, obj);
-	if (!old) {
-		if (out_obj)
-			*out_obj = NULL;
-		if (out_was_visible)
-			*out_was_visible = FALSE;
+	if (!entry_old) {
+		NM_SET_OUT (out_obj_old, NULL);
 		return NMP_CACHE_OPS_UNCHANGED;
 	}
 
-	if (out_obj)
-		*out_obj = nmp_object_ref (old);
-	if (out_was_visible)
-		*out_was_visible = nmp_object_is_visible (old);
-	if (equals_by_ptr && old != obj) {
+	obj_old = entry_old->obj;
+
+	NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
+
+	if (   equals_by_ptr
+	    && obj_old != obj_needle) {
 		/* We found an identical object, but we only delete it if it's the same pointer as
-		 * @obj. */
+		 * @obj_needle. */
 		return NMP_CACHE_OPS_UNCHANGED;
 	}
-	if (pre_hook)
-		pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
-	_nmp_cache_update_remove (cache, old);
+	if (   only_dirty
+	    && !entry_old->dirty) {
+		/* the entry is not dirty. Skip. */
+		return NMP_CACHE_OPS_UNCHANGED;
+	}
+	_idxcache_update (cache, entry_old, NULL, FALSE, NULL);
 	return NMP_CACHE_OPS_REMOVED;
 }
 
 NMPCacheOpsType
-nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj_needle, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+nmp_cache_remove_netlink (NMPCache *cache,
+                          const NMPObject *obj_needle,
+                          const NMPObject **out_obj_old,
+                          const NMPObject **out_obj_new)
 {
-	if (NMP_OBJECT_GET_TYPE (obj_needle) == NMP_OBJECT_TYPE_LINK) {
-		NMPObject *old;
-		nm_auto_nmpobj NMPObject *obj = NULL;
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_new = NULL;
+	const NMPObject *obj_old;
+	nm_auto_nmpobj NMPObject *obj_new = NULL;
+
+	entry_old = _lookup_entry (cache, obj_needle);
+
+	if (!entry_old) {
+		NM_SET_OUT (out_obj_old, NULL);
+		NM_SET_OUT (out_obj_new, NULL);
+		return NMP_CACHE_OPS_UNCHANGED;
+	}
+
+	obj_old = entry_old->obj;
 
+	if (NMP_OBJECT_GET_TYPE (obj_needle) == NMP_OBJECT_TYPE_LINK) {
 		/* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be
 		 * removed from netlink. Link objects are alive without being in netlink when they
 		 * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink
 		 * flag. */
 
-		old = (NMPObject *) nmp_cache_lookup_link (cache, obj_needle->link.ifindex);
-		if (!old) {
-			if (out_obj)
-				*out_obj = NULL;
-			if (out_was_visible)
-				*out_was_visible = FALSE;
-			return NMP_CACHE_OPS_UNCHANGED;
-		}
-
-		if (out_obj)
-			*out_obj = nmp_object_ref (old);
-		if (out_was_visible)
-			*out_was_visible = nmp_object_is_visible (old);
+		NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
 
-		if (!old->_link.netlink.is_in_netlink) {
-			nm_assert (old->_link.udev.device);
+		if (!obj_old->_link.netlink.is_in_netlink) {
+			nm_assert (obj_old->_link.udev.device);
+			NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old));
 			return NMP_CACHE_OPS_UNCHANGED;
 		}
 
-		if (!old->_link.udev.device) {
-			/* the update would make @old invalid. Remove it. */
-			if (pre_hook)
-				pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
-			_nmp_cache_update_remove (cache, old);
+		if (!obj_old->_link.udev.device) {
+			/* the update would make @obj_old invalid. Remove it. */
+			_idxcache_update (cache, entry_old, NULL, FALSE, NULL);
+			NM_SET_OUT (out_obj_new, NULL);
 			return NMP_CACHE_OPS_REMOVED;
 		}
 
-		obj = nmp_object_clone (old, FALSE);
-		obj->_link.netlink.is_in_netlink = FALSE;
+		obj_new = nmp_object_clone (obj_old, FALSE);
+		obj_new->_link.netlink.is_in_netlink = FALSE;
 
-		_nmp_object_fixup_link_master_connected (obj, cache);
-		_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+		_nmp_object_fixup_link_master_connected (&obj_new, NULL, cache);
+		_nmp_object_fixup_link_udev_fields (&obj_new, NULL, cache->use_udev);
 
-		if (pre_hook)
-			pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
-		_nmp_cache_update_update (cache, old, obj);
+		_idxcache_update (cache,
+		                  entry_old,
+		                  obj_new,
+		                  FALSE,
+		                  &entry_new);
+		NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj));
 		return NMP_CACHE_OPS_UPDATED;
-	} else
-		return nmp_cache_remove (cache, obj_needle, FALSE, out_obj, out_was_visible, pre_hook, user_data);
+	}
+
+	NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
+	NM_SET_OUT (out_obj_new, NULL);
+	_idxcache_update (cache, entry_old, NULL, FALSE, NULL);
+	return NMP_CACHE_OPS_REMOVED;
 }
 
 /**
  * nmp_cache_update_netlink:
  * @cache: the platform cache
- * @obj: a #NMPObject instance as received from netlink and created via
+ * @obj_hand_over: a #NMPObject instance as received from netlink and created via
  *    nmp_object_from_nl(). Especially for link, it must not have the udev
  *    replated fields set.
  *    This instance will be modified and might be put into the cache. When
  *    calling nmp_cache_update_netlink() you hand @obj over to the cache.
  *    Except, that the cache will increment the ref count as appropriate. You
  *    must still unref the obj to release your part of the ownership.
- * @out_obj: (allow-none): (out): return the object instance that is inside
- *    the cache. If you specify non %NULL, you must always unref the returned
- *    instance. If the return value indicates that the object was removed,
- *    the object is no longer in the cache. Even if the return value indicates
- *    that the object was unchanged, it will still return @out_obj -- if
- *    such an object is in the cache.
- * @out_was_visible: (allow-none): (out): whether the object was visible before
- *    the update operation.
- * @pre_hook: (allow-none): a callback *before* the object gets updated. You cannot
- *    influence the outcome and must not do anything beyong inspecting the changes.
- * @user_data:
+ * @is_dump: whether this update comes during a dump of object of the same kind.
+ *    kernel dumps objects in a certain order, which matters especially for routes.
+ *    Before a dump we mark all objects as dirty, and remove all untouched objects
+ *    afterwards. Hence, during a dump, every update should move the object to the
+ *    end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE,
+ *    instead of NM_DEDUP_MULTI_IDX_MODE_APPEND.
+ * @out_obj_old: (allow-none): (out): return the object with same ID as @obj_hand_over,
+ *    that was in the cache before update. If an object is returned, the caller must
+ *    unref it afterwards.
+ * @out_obj_new: (allow-none): (out): return the object from the cache after update.
+ *    The caller must unref this object.
  *
  * Returns: how the cache changed.
+ *
+ * Even if there was no change in the cace (NMP_CACHE_OPS_UNCHANGED), @out_obj_old
+ * and @out_obj_new will be set accordingly.
  **/
 NMPCacheOpsType
-nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+nmp_cache_update_netlink (NMPCache *cache,
+                          NMPObject *obj_hand_over,
+                          gboolean is_dump,
+                          const NMPObject **out_obj_old,
+                          const NMPObject **out_obj_new)
 {
-	NMPObject *old;
-
-	nm_assert (NMP_OBJECT_IS_VALID (obj));
-	nm_assert (!NMP_OBJECT_IS_STACKINIT (obj));
-	nm_assert (!obj->is_cached);
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_new;
+	const NMPObject *obj_old;
+	gboolean is_alive;
 
+	nm_assert (cache);
+	nm_assert (NMP_OBJECT_IS_VALID (obj_hand_over));
+	nm_assert (!NMP_OBJECT_IS_STACKINIT (obj_hand_over));
 	/* A link object from netlink must have the udev related fields unset.
 	 * We could implement to handle that, but there is no need to support such
 	 * a use-case */
-	nm_assert (NMP_OBJECT_GET_TYPE (obj) != NMP_OBJECT_TYPE_LINK ||
-	           (   !obj->_link.udev.device
-	            && !obj->link.driver));
+	nm_assert (NMP_OBJECT_GET_TYPE (obj_hand_over) != NMP_OBJECT_TYPE_LINK ||
+	           (   !obj_hand_over->_link.udev.device
+	            && !obj_hand_over->link.driver));
+	nm_assert (nm_dedup_multi_index_obj_find (cache->multi_idx, obj_hand_over) != obj_hand_over);
 
-	old = g_hash_table_lookup (cache->idx_main, obj);
+	entry_old = _lookup_entry (cache, obj_hand_over);
 
-	if (out_obj)
-		*out_obj = NULL;
-	if (out_was_visible)
-		*out_was_visible = FALSE;
+	if (!entry_old) {
 
-	if (!old) {
-		if (!nmp_object_is_alive (obj))
-			return NMP_CACHE_OPS_UNCHANGED;
+		NM_SET_OUT (out_obj_old, NULL);
 
-		if (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK) {
-			_nmp_object_fixup_link_master_connected (obj, cache);
-			_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+		if (!nmp_object_is_alive (obj_hand_over)) {
+			NM_SET_OUT (out_obj_new, NULL);
+			return NMP_CACHE_OPS_UNCHANGED;
 		}
 
-		if (out_obj)
-			*out_obj = nmp_object_ref (obj);
+		if (NMP_OBJECT_GET_TYPE (obj_hand_over) == NMP_OBJECT_TYPE_LINK) {
+			_nmp_object_fixup_link_master_connected (&obj_hand_over, NULL, cache);
+			_nmp_object_fixup_link_udev_fields (&obj_hand_over, NULL, cache->use_udev);
+		}
 
-		if (pre_hook)
-			pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data);
-		_nmp_cache_update_add (cache, obj);
+		_idxcache_update (cache,
+		                  entry_old,
+		                  obj_hand_over,
+		                  is_dump,
+		                  &entry_new);
+		NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj));
 		return NMP_CACHE_OPS_ADDED;
-	} else if (old == obj) {
-		/* updating a cached object inplace is not supported because the object contributes to hash-key
-		 * for NMMultiIndex. Modifying an object that is inside NMMultiIndex means that these
-		 * keys change.
-		 * The problem is, that for a given object NMMultiIndex does not support (efficient)
-		 * reverse lookup to get all the NMPCacheIds to which it belongs. If that would be implemented,
-		 * it would be possible to implement inplace-update.
-		 *
-		 * There is an un-optimized reverse lookup via nm_multi_index_iter_init(), but we don't want
-		 * that because we might have a large number of indexes to search.
-		 *
-		 * We could add efficient reverse lookup by adding a reverse index to NMMultiIndex. But that
-		 * also adds some cost to support an (uncommon?) usage pattern.
-		 *
-		 * Instead we just don't support it, instead we expect the user to
-		 * create a new instance from netlink.
-		 *
-		 * TL;DR: a cached object must never be modified.
-		 */
-		g_assert_not_reached ();
-	} else {
-		gboolean is_alive = FALSE;
-
-		nm_assert (old->is_cached);
-
-		if (out_obj)
-			*out_obj = nmp_object_ref (old);
-		if (out_was_visible)
-			*out_was_visible = nmp_object_is_visible (old);
-
-		if (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK) {
-			if (!obj->_link.netlink.is_in_netlink) {
-				if (!old->_link.netlink.is_in_netlink) {
-					nm_assert (old->_link.udev.device);
-					return NMP_CACHE_OPS_UNCHANGED;
-				}
-				if (old->_link.udev.device) {
-					/* @obj is not in netlink.
-					 *
-					 * This is similar to nmp_cache_remove_netlink(), but there we preserve the
-					 * preexisting netlink properties. The use case of that is when kernel_get_object()
-					 * cannot load an object (based on the id of a needle).
-					 *
-					 * Here we keep the data provided from @obj. The usecase is when receiving
-					 * a valid @obj instance from netlink with RTM_DELROUTE.
-					 */
-					is_alive = TRUE;
-				}
-			} else
-				is_alive = TRUE;
+	}
 
-			if (is_alive) {
-				_nmp_object_fixup_link_master_connected (obj, cache);
+	obj_old = entry_old->obj;
 
-				/* Merge the netlink parts with what we have from udev. */
-				udev_device_unref (obj->_link.udev.device);
-				obj->_link.udev.device = old->_link.udev.device ? udev_device_ref (old->_link.udev.device) : NULL;
-				_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+	if (NMP_OBJECT_GET_TYPE (obj_hand_over) == NMP_OBJECT_TYPE_LINK) {
+		if (!obj_hand_over->_link.netlink.is_in_netlink) {
+			if (!obj_old->_link.netlink.is_in_netlink) {
+				nm_assert (obj_old->_link.udev.device);
+				NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
+				NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old));
+				return NMP_CACHE_OPS_UNCHANGED;
 			}
+			if (obj_old->_link.udev.device) {
+				/* @obj_hand_over is not in netlink.
+				 *
+				 * This is similar to nmp_cache_remove_netlink(), but there we preserve the
+				 * preexisting netlink properties. The use case of that is when kernel_get_object()
+				 * cannot load an object (based on the id of a needle).
+				 *
+				 * Here we keep the data provided from @obj_hand_over. The usecase is when receiving
+				 * a valid @obj_hand_over instance from netlink with RTM_DELROUTE.
+				 */
+				is_alive = TRUE;
+			} else
+				is_alive = FALSE;
 		} else
-			is_alive = nmp_object_is_alive (obj);
+			is_alive = TRUE;
 
-		if (!is_alive) {
-			/* the update would make @old invalid. Remove it. */
-			if (pre_hook)
-				pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
-			_nmp_cache_update_remove (cache, old);
-			return NMP_CACHE_OPS_REMOVED;
+		if (is_alive) {
+			_nmp_object_fixup_link_master_connected (&obj_hand_over, NULL, cache);
+
+			/* Merge the netlink parts with what we have from udev. */
+			udev_device_unref (obj_hand_over->_link.udev.device);
+			obj_hand_over->_link.udev.device = obj_old->_link.udev.device ? udev_device_ref (obj_old->_link.udev.device) : NULL;
+			_nmp_object_fixup_link_udev_fields (&obj_hand_over, NULL, cache->use_udev);
+
+			if (obj_hand_over->_link.netlink.lnk) {
+				nm_auto_nmpobj const NMPObject *lnk_old = obj_hand_over->_link.netlink.lnk;
+
+				/* let's dedup/intern the lnk object. */
+				obj_hand_over->_link.netlink.lnk = nm_dedup_multi_index_obj_intern (cache->multi_idx, lnk_old);
+			}
 		}
+	} else
+		is_alive = nmp_object_is_alive (obj_hand_over);
 
-		if (nmp_object_equal (old, obj))
-			return NMP_CACHE_OPS_UNCHANGED;
+	NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
 
-		if (pre_hook)
-			pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
-		_nmp_cache_update_update (cache, old, obj);
-		return NMP_CACHE_OPS_UPDATED;
+	if (!is_alive) {
+		/* the update would make @obj_old invalid. Remove it. */
+		_idxcache_update (cache, entry_old, NULL, FALSE, NULL);
+		NM_SET_OUT (out_obj_new, NULL);
+		return NMP_CACHE_OPS_REMOVED;
 	}
+
+	if (nmp_object_equal (obj_old, obj_hand_over)) {
+		nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old));
+		return NMP_CACHE_OPS_UNCHANGED;
+	}
+
+	_idxcache_update (cache,
+	                  entry_old,
+	                  obj_hand_over,
+	                  is_dump,
+	                  &entry_new);
+	NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj));
+	return NMP_CACHE_OPS_UPDATED;
 }
 
 NMPCacheOpsType
-nmp_cache_update_link_udev (NMPCache *cache, int ifindex, struct udev_device *udevice, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
-{
-	NMPObject *old;
-	nm_auto_nmpobj NMPObject *obj = NULL;
+nmp_cache_update_netlink_route (NMPCache *cache,
+                                NMPObject *obj_hand_over,
+                                gboolean is_dump,
+                                guint16 nlmsgflags,
+                                const NMPObject **out_obj_old,
+                                const NMPObject **out_obj_new,
+                                const NMPObject **out_obj_replace,
+                                gboolean *out_resync_required)
+{
+	NMDedupMultiIter iter;
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_new;
+	const NMDedupMultiEntry *entry_cur;
+	const NMDedupMultiEntry *entry_replace;
+	const NMDedupMultiHeadEntry *head_entry;
+	gboolean is_alive;
+	NMPCacheOpsType ops_type = NMP_CACHE_OPS_UNCHANGED;
+	gboolean resync_required;
 
-	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
+	nm_assert (cache);
+	nm_assert (NMP_OBJECT_IS_VALID (obj_hand_over));
+	nm_assert (!NMP_OBJECT_IS_STACKINIT (obj_hand_over));
+	/* A link object from netlink must have the udev related fields unset.
+	 * We could implement to handle that, but there is no need to support such
+	 * a use-case */
+	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_hand_over), NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                           NMP_OBJECT_TYPE_IP6_ROUTE));
+	nm_assert (nm_dedup_multi_index_obj_find (cache->multi_idx, obj_hand_over) != obj_hand_over);
 
-	if (out_obj)
-		*out_obj = NULL;
-	if (out_was_visible)
-		*out_was_visible = FALSE;
+	entry_old = _lookup_entry (cache, obj_hand_over);
+	entry_new = NULL;
 
-	if (!old) {
-		if (!udevice)
-			return NMP_CACHE_OPS_UNCHANGED;
+	NM_SET_OUT (out_obj_old, nmp_object_ref (nm_dedup_multi_entry_get_obj (entry_old)));
+
+	if (!entry_old) {
 
-		obj = nmp_object_new (NMP_OBJECT_TYPE_LINK, NULL);
-		obj->link.ifindex = ifindex;
-		obj->_link.udev.device = udev_device_ref (udevice);
+		if (!nmp_object_is_alive (obj_hand_over))
+			goto update_done;
+
+		_idxcache_update (cache,
+		                  NULL,
+		                  obj_hand_over,
+		                  is_dump,
+		                  &entry_new);
+		ops_type = NMP_CACHE_OPS_ADDED;
+		goto update_done;
+	}
 
-		_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+	is_alive = nmp_object_is_alive (obj_hand_over);
+
+	if (!is_alive) {
+		/* the update would make @entry_old invalid. Remove it. */
+		_idxcache_update (cache, entry_old, NULL, FALSE, NULL);
+		ops_type = NMP_CACHE_OPS_REMOVED;
+		goto update_done;
+	}
+
+	if (nmp_object_equal (entry_old->obj, obj_hand_over)) {
+		nm_dedup_multi_entry_set_dirty (entry_old, FALSE);
+		goto update_done;
+	}
 
-		nm_assert (nmp_object_is_alive (obj));
+	_idxcache_update (cache,
+	                  entry_old,
+	                  obj_hand_over,
+	                  is_dump,
+	                  &entry_new);
+	ops_type = NMP_CACHE_OPS_UPDATED;
 
-		if (out_obj)
-			*out_obj = nmp_object_ref (obj);
+update_done:
+	NM_SET_OUT (out_obj_new, nmp_object_ref (nm_dedup_multi_entry_get_obj (entry_new)));
 
-		if (pre_hook)
-			pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data);
-		_nmp_cache_update_add (cache, obj);
+	/* a RTM_GETROUTE event may signal that another object was replaced.
+	 * Find out whether that is the case and return it as @obj_replaced.
+	 *
+	 * Also, fixup the order of @entry_new within NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID
+	 * index. For most parts, we don't care about the order of objects (including routes).
+	 * But NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID we must keep in the correct order, to
+	 * properly find @obj_replaced. */
+	resync_required = FALSE;
+	entry_replace = NULL;
+	if (is_dump) {
+		goto out;
+	}
+
+	if (!entry_new) {
+		if (   NM_FLAGS_HAS (nlmsgflags, NLM_F_REPLACE)
+		    && nmp_cache_lookup_all (cache,
+		                             NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
+		                             obj_hand_over)) {
+			/* hm. @obj_hand_over was not added, meaning it was not alive.
+			 * However, we track some other objects with the same weak-id.
+			 * It's unclear what that means. To be sure, resync. */
+			resync_required = TRUE;
+		}
+		goto out;
+	}
+
+	entry_cur = _lookup_entry_with_idx_type (cache,
+	                                         NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
+	                                         entry_new->obj);
+	if (!entry_cur) {
+		nm_assert_not_reached ();
+		goto out;
+	}
+	nm_assert (entry_cur->obj == entry_new->obj);
+
+	head_entry = entry_cur->head;
+	nm_assert (head_entry == nmp_cache_lookup_all (cache,
+	                                               NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
+	                                               entry_cur->obj));
+
+	if (head_entry->len == 1) {
+		/* there is only one object, and we expect it to be @obj_new. */
+		nm_assert (nm_dedup_multi_head_entry_get_idx (head_entry, 0) == entry_cur);
+		goto out;
+	}
+
+	switch (nlmsgflags & (NLM_F_REPLACE | NLM_F_EXCL | NLM_F_CREATE | NLM_F_APPEND)) {
+	case NLM_F_REPLACE:
+		/* ip route change */
+
+		/* get the first element (but skip @obj_new). */
+		nm_dedup_multi_iter_init (&iter, head_entry);
+		if (!nm_dedup_multi_iter_next (&iter))
+			nm_assert_not_reached ();
+		if (iter.current == entry_cur) {
+			if (!nm_dedup_multi_iter_next (&iter))
+				nm_assert_not_reached ();
+		}
+		entry_replace = iter.current;
+
+		nm_assert (   entry_replace
+		           && entry_cur != entry_replace);
+
+		nm_dedup_multi_entry_reorder (entry_cur, entry_replace, FALSE);
+		break;
+	case NLM_F_CREATE | NLM_F_APPEND:
+		/* ip route append */
+		nm_dedup_multi_entry_reorder (entry_cur, NULL, TRUE);
+		break;
+	case NLM_F_CREATE:
+		/* ip route prepend */
+		nm_dedup_multi_entry_reorder (entry_cur, NULL, FALSE);
+		break;
+	default:
+		/* this is an unexecpted case, probably a bug that we need to handle better. */
+		resync_required = TRUE;
+		break;
+	}
+
+out:
+	NM_SET_OUT (out_obj_replace, nmp_object_ref (nm_dedup_multi_entry_get_obj (entry_replace)));
+	NM_SET_OUT (out_resync_required, resync_required);
+	return ops_type;
+}
+
+
+NMPCacheOpsType
+nmp_cache_update_link_udev (NMPCache *cache,
+                            int ifindex,
+                            struct udev_device *udevice,
+                            const NMPObject **out_obj_old,
+                            const NMPObject **out_obj_new)
+{
+	const NMPObject *obj_old;
+	nm_auto_nmpobj NMPObject *obj_new = NULL;
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_new;
+
+	entry_old = nmp_cache_lookup_entry_link (cache, ifindex);
+
+	if (!entry_old) {
+		if (!udevice) {
+			NM_SET_OUT (out_obj_old, NULL);
+			NM_SET_OUT (out_obj_new, NULL);
+			return NMP_CACHE_OPS_UNCHANGED;
+		}
+
+		obj_new = nmp_object_new (NMP_OBJECT_TYPE_LINK, NULL);
+		obj_new->link.ifindex = ifindex;
+		obj_new->_link.udev.device = udev_device_ref (udevice);
+
+		_nmp_object_fixup_link_udev_fields (&obj_new, NULL, cache->use_udev);
+
+		_idxcache_update (cache,
+		                  NULL,
+		                  obj_new,
+		                  FALSE,
+		                  &entry_new);
+		NM_SET_OUT (out_obj_old, NULL);
+		NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj));
 		return NMP_CACHE_OPS_ADDED;
 	} else {
-		nm_assert (old->is_cached);
-
-		if (out_obj)
-			*out_obj = nmp_object_ref (old);
-		if (out_was_visible)
-			*out_was_visible = nmp_object_is_visible (old);
+		obj_old = entry_old->obj;
+		NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
 
-		if (old->_link.udev.device == udevice)
+		if (obj_old->_link.udev.device == udevice) {
+			NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old));
 			return NMP_CACHE_OPS_UNCHANGED;
+		}
 
-		if (!udevice && !old->_link.netlink.is_in_netlink) {
-			/* the update would make @old invalid. Remove it. */
-			if (pre_hook)
-				pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data);
-			_nmp_cache_update_remove (cache, old);
+		if (!udevice && !obj_old->_link.netlink.is_in_netlink) {
+			/* the update would make @obj_old invalid. Remove it. */
+			_idxcache_update (cache, entry_old, NULL, FALSE, NULL);
+			NM_SET_OUT (out_obj_new, NULL);
 			return NMP_CACHE_OPS_REMOVED;
 		}
 
-		obj = nmp_object_clone (old, FALSE);
-
-		udev_device_unref (obj->_link.udev.device);
-		obj->_link.udev.device = udevice ? udev_device_ref (udevice) : NULL;
+		obj_new = nmp_object_clone (obj_old, FALSE);
 
-		_nmp_object_fixup_link_udev_fields (obj, cache->use_udev);
+		udev_device_unref (obj_new->_link.udev.device);
+		obj_new->_link.udev.device = udevice ? udev_device_ref (udevice) : NULL;
 
-		nm_assert (nmp_object_is_alive (obj));
+		_nmp_object_fixup_link_udev_fields (&obj_new, NULL, cache->use_udev);
 
-		if (pre_hook)
-			pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
-		_nmp_cache_update_update (cache, old, obj);
+		_idxcache_update (cache,
+		                  entry_old,
+		                  obj_new,
+		                  FALSE,
+		                  &entry_new);
+		NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj));
 		return NMP_CACHE_OPS_UPDATED;
 	}
 }
 
 NMPCacheOpsType
-nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
+nmp_cache_update_link_master_connected (NMPCache *cache,
+                                        int ifindex,
+                                        const NMPObject **out_obj_old,
+                                        const NMPObject **out_obj_new)
 {
-	NMPObject *old;
-	nm_auto_nmpobj NMPObject *obj = NULL;
+	const NMDedupMultiEntry *entry_old;
+	const NMDedupMultiEntry *entry_new = NULL;
+	const NMPObject *obj_old;
+	nm_auto_nmpobj NMPObject *obj_new = NULL;
+
+	entry_old = nmp_cache_lookup_entry_link (cache, ifindex);
 
-	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
+	if (!entry_old) {
+		NM_SET_OUT (out_obj_old, NULL);
+		NM_SET_OUT (out_obj_new, NULL);
+		return NMP_CACHE_OPS_UNCHANGED;
+	}
 
-	if (!old) {
-		if (out_obj)
-			*out_obj = NULL;
-		if (out_was_visible)
-			*out_was_visible = FALSE;
+	obj_old = entry_old->obj;
 
+	if (!nmp_cache_link_connected_needs_toggle (cache, obj_old, NULL, NULL)) {
+		NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
+		NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old));
 		return NMP_CACHE_OPS_UNCHANGED;
 	}
 
-	nm_assert (old->is_cached);
+	obj_new = nmp_object_clone (obj_old, FALSE);
+	obj_new->link.connected = !obj_old->link.connected;
 
-	if (out_obj)
-		*out_obj = nmp_object_ref (old);
-	if (out_was_visible)
-		*out_was_visible = nmp_object_is_visible (old);
+	NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old));
+	_idxcache_update (cache,
+	                  entry_old,
+	                  obj_new,
+	                  FALSE,
+	                  &entry_new);
+	NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj));
+	return NMP_CACHE_OPS_UPDATED;
+}
 
-	if (!nmp_cache_link_connected_needs_toggle (cache, old, NULL, NULL))
-		return NMP_CACHE_OPS_UNCHANGED;
+/*****************************************************************************/
 
-	obj = nmp_object_clone (old, FALSE);
-	obj->link.connected = !old->link.connected;
+void
+nmp_cache_dirty_set_all (NMPCache *cache, NMPObjectType obj_type)
+{
+	NMPObject obj_needle;
 
-	nm_assert (nmp_object_is_alive (obj));
+	nm_assert (cache);
 
-	if (pre_hook)
-		pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data);
-	_nmp_cache_update_update (cache, old, obj);
-	return NMP_CACHE_OPS_UPDATED;
+	nm_dedup_multi_index_dirty_set_head (cache->multi_idx,
+	                                     _idx_type_get (cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE),
+	                                     _nmp_object_stackinit_from_type (&obj_needle, obj_type));
 }
 
 /*****************************************************************************/
 
 NMPCache *
-nmp_cache_new (gboolean use_udev)
-{
-	NMPCache *cache = g_new (NMPCache, 1);
-
-	cache->idx_main = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
-	                                         (GEqualFunc) nmp_object_id_equal,
-	                                         (GDestroyNotify) nmp_object_unref,
-	                                         NULL);
-	cache->idx_multi = nm_multi_index_new ((NMMultiIndexFuncHash) nmp_cache_id_hash,
-	                                       (NMMultiIndexFuncEqual) nmp_cache_id_equal,
-	                                       (NMMultiIndexFuncClone) nmp_cache_id_clone,
-	                                       (NMMultiIndexFuncDestroy) nmp_cache_id_destroy);
+nmp_cache_new (NMDedupMultiIndex *multi_idx, gboolean use_udev)
+{
+	NMPCache *cache = g_slice_new0 (NMPCache);
+	guint i;
+
+	for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++)
+		_dedup_multi_idx_type_init ((DedupMultiIdxType *) _idx_type_get (cache, i), i);
+
+	cache->multi_idx = nm_dedup_multi_index_ref (multi_idx);
+
 	cache->use_udev = !!use_udev;
 	return cache;
 }
@@ -2025,23 +2440,14 @@ nmp_cache_new (gboolean use_udev)
 void
 nmp_cache_free (NMPCache *cache)
 {
-	GHashTableIter iter;
-	NMPObject *obj;
+	guint i;
 
-	/* No need to cumbersomely remove the objects properly. They are not hooked up
-	 * in a complicated way, we can just unref them together with cache->idx_main.
-	 *
-	 * But we must clear the @is_cached flag. */
-	g_hash_table_iter_init (&iter, cache->idx_main);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
-		nm_assert (obj->is_cached);
-		obj->is_cached = FALSE;
-	}
+	for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++)
+		nm_dedup_multi_index_remove_idx (cache->multi_idx, _idx_type_get (cache, i));
 
-	nm_multi_index_free (cache->idx_multi);
-	g_hash_table_unref (cache->idx_main);
+	nm_dedup_multi_index_unref (cache->multi_idx);
 
-	g_free (cache);
+	g_slice_free (NMPCache, cache);
 }
 
 /*****************************************************************************/
@@ -2049,63 +2455,13 @@ nmp_cache_free (NMPCache *cache)
 void
 ASSERT_nmp_cache_is_consistent (const NMPCache *cache)
 {
-#if NM_MORE_ASSERTS
-	NMMultiIndexIter iter_multi;
-	GHashTableIter iter_hash;
-	guint i, len;
-	NMPCacheId cache_id_storage;
-	const NMPCacheId *cache_id, *cache_id2;
-	const NMPlatformObject *const *objects;
-	const NMPObject *obj;
-
-	g_assert (cache);
-
-	g_hash_table_iter_init (&iter_hash, cache->idx_main);
-	while (g_hash_table_iter_next (&iter_hash, (gpointer *) &obj, NULL)) {
-		const guint8 *id_type;
-
-		g_assert (NMP_OBJECT_IS_VALID (obj));
-		g_assert (nmp_object_is_alive (obj));
-
-		for (id_type = NMP_OBJECT_GET_CLASS (obj)->supported_cache_ids; *id_type; id_type++) {
-			if (!_nmp_object_init_cache_id (obj, *id_type, &cache_id_storage, &cache_id))
-				continue;
-			if (!cache_id)
-				continue;
-			g_assert (nm_multi_index_contains (cache->idx_multi, &cache_id->base, &obj->object));
-		}
-	}
-
-	nm_multi_index_iter_init (&iter_multi, cache->idx_multi, NULL);
-	while (nm_multi_index_iter_next (&iter_multi,
-	                                 (const NMMultiIndexId **) &cache_id,
-	                                 (void *const**) &objects,
-	                                 &len)) {
-		g_assert (len > 0 && objects && objects[len] == NULL);
-
-		for (i = 0; i < len; i++) {
-			g_assert (objects[i]);
-			obj = NMP_OBJECT_UP_CAST (objects[i]);
-			g_assert (NMP_OBJECT_IS_VALID (obj));
-
-			/* for now, enforce that all objects for a certain index are of the same type. */
-			g_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[0])));
-
-			if (!_nmp_object_init_cache_id (obj, cache_id->_id_type, &cache_id_storage, &cache_id2))
-				g_assert_not_reached ();
-			g_assert (cache_id2);
-			g_assert (nmp_cache_id_equal (cache_id, cache_id2));
-			g_assert_cmpint (nmp_cache_id_hash (cache_id), ==, nmp_cache_id_hash (cache_id2));
-
-			g_assert (obj == g_hash_table_lookup (cache->idx_main, obj));
-		}
-	}
-#endif
 }
+
 /*****************************************************************************/
 
 const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 	[NMP_OBJECT_TYPE_LINK - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LINK,
 		.sizeof_data                        = sizeof (NMPObjectLink),
 		.sizeof_public                      = sizeof (NMPlatformLink),
@@ -2115,22 +2471,23 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_LINK,
 		.signal_type                        = NM_PLATFORM_SIGNAL_LINK_CHANGED,
 		.supported_cache_ids                = _supported_cache_ids_link,
-		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_link,
+		.cmd_obj_hash_update                = _vt_cmd_obj_hash_update_link,
 		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_link,
 		.cmd_obj_copy                       = _vt_cmd_obj_copy_link,
-		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_link,
 		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_link,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_link,
 		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_link,
 		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_link,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_link,
-		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_link,
-		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_link,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_link,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_link,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_link,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_link_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_link_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_link_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP4_ADDRESS - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_IP4_ADDRESS,
 		.sizeof_data                        = sizeof (NMPObjectIP4Address),
 		.sizeof_public                      = sizeof (NMPlatformIP4Address),
@@ -2140,17 +2497,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
 		.supported_cache_ids                = _supported_cache_ids_ipx_address,
-		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_address,
-		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip4_address,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_address,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip4_address,
-		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip4_address,
-		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip4_address,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_ip4_address,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_ip4_address,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip4_address,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_address_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_ip4_address_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_address_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP6_ADDRESS - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_IP6_ADDRESS,
 		.sizeof_data                        = sizeof (NMPObjectIP6Address),
 		.sizeof_public                      = sizeof (NMPlatformIP6Address),
@@ -2160,17 +2517,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
 		.supported_cache_ids                = _supported_cache_ids_ipx_address,
-		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_address,
-		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip6_address,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_address,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip6_address,
-		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip6_address,
-		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip6_address,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_ip6_address,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_ip6_address,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip6_address,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_address_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_ip6_address_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_address_cmp
 	},
 	[NMP_OBJECT_TYPE_IP4_ROUTE - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_IP4_ROUTE,
 		.sizeof_data                        = sizeof (NMPObjectIP4Route),
 		.sizeof_public                      = sizeof (NMPlatformIP4Route),
@@ -2179,18 +2536,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.rtm_gettype                        = RTM_GETROUTE,
 		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
-		.supported_cache_ids                = _supported_cache_ids_ip4_route,
-		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_route,
-		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip4_route,
+		.supported_cache_ids                = _supported_cache_ids_ipx_route,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_route,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip4_route,
-		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip4_route,
-		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip4_route,
-		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip4_route,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_ip4_route,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_ip4_route,
+		.cmd_plobj_to_string_id             = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_route_to_string,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_route_to_string,
-		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp,
+		.cmd_plobj_hash_update              = _vt_cmd_plobj_hash_update_ip4_route,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp_full,
 	},
 	[NMP_OBJECT_TYPE_IP6_ROUTE - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_IP6_ROUTE,
 		.sizeof_data                        = sizeof (NMPObjectIP6Route),
 		.sizeof_public                      = sizeof (NMPlatformIP6Route),
@@ -2199,109 +2556,129 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.rtm_gettype                        = RTM_GETROUTE,
 		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
-		.supported_cache_ids                = _supported_cache_ids_ip6_route,
-		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_route,
-		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip6_route,
+		.supported_cache_ids                = _supported_cache_ids_ipx_route,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_route,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip6_route,
-		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip6_route,
-		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip6_route,
-		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip6_route,
+		.cmd_plobj_id_cmp                   = _vt_cmd_plobj_id_cmp_ip6_route,
+		.cmd_plobj_id_hash_update           = _vt_cmd_plobj_id_hash_update_ip6_route,
+		.cmd_plobj_to_string_id             = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_route_to_string,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_route_to_string,
-		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp,
+		.cmd_plobj_hash_update              = _vt_cmd_plobj_hash_update_ip6_route,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp_full,
 	},
 	[NMP_OBJECT_TYPE_LNK_GRE - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_GRE,
 		.sizeof_data                        = sizeof (NMPObjectLnkGre),
 		.sizeof_public                      = sizeof (NMPlatformLnkGre),
 		.obj_type_name                      = "gre",
 		.lnk_link_type                      = NM_LINK_TYPE_GRE,
 		.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,
 		.sizeof_data                        = sizeof (NMPObjectLnkInfiniband),
 		.sizeof_public                      = sizeof (NMPlatformLnkInfiniband),
 		.obj_type_name                      = "infiniband",
 		.lnk_link_type                      = NM_LINK_TYPE_INFINIBAND,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_infiniband_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_infiniband_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_infiniband_cmp,
 	},
 	[NMP_OBJECT_TYPE_LNK_IP6TNL - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_IP6TNL,
 		.sizeof_data                        = sizeof (NMPObjectLnkIp6Tnl),
 		.sizeof_public                      = sizeof (NMPlatformLnkIp6Tnl),
 		.obj_type_name                      = "ip6tnl",
 		.lnk_link_type                      = NM_LINK_TYPE_IP6TNL,
 		.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,
 		.sizeof_data                        = sizeof (NMPObjectLnkIpIp),
 		.sizeof_public                      = sizeof (NMPlatformLnkIpIp),
 		.obj_type_name                      = "ipip",
 		.lnk_link_type                      = NM_LINK_TYPE_IPIP,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ipip_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ipip_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ipip_cmp,
 	},
 	[NMP_OBJECT_TYPE_LNK_MACSEC - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACSEC,
 		.sizeof_data                        = sizeof (NMPObjectLnkMacsec),
 		.sizeof_public                      = sizeof (NMPlatformLnkMacsec),
 		.obj_type_name                      = "macsec",
 		.lnk_link_type                      = NM_LINK_TYPE_MACSEC,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macsec_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_macsec_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macsec_cmp,
 	},
 	[NMP_OBJECT_TYPE_LNK_MACVLAN - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVLAN,
 		.sizeof_data                        = sizeof (NMPObjectLnkMacvlan),
 		.sizeof_public                      = sizeof (NMPlatformLnkMacvlan),
 		.obj_type_name                      = "macvlan",
 		.lnk_link_type                      = NM_LINK_TYPE_MACVLAN,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_macvlan_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp,
 	},
 	[NMP_OBJECT_TYPE_LNK_MACVTAP - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVTAP,
 		.sizeof_data                        = sizeof (NMPObjectLnkMacvtap),
 		.sizeof_public                      = sizeof (NMPlatformLnkMacvtap),
 		.obj_type_name                      = "macvtap",
 		.lnk_link_type                      = NM_LINK_TYPE_MACVTAP,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_macvlan_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp,
 	},
 	[NMP_OBJECT_TYPE_LNK_SIT - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_SIT,
 		.sizeof_data                        = sizeof (NMPObjectLnkSit),
 		.sizeof_public                      = sizeof (NMPlatformLnkSit),
 		.obj_type_name                      = "sit",
 		.lnk_link_type                      = NM_LINK_TYPE_SIT,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_sit_to_string,
+		.cmd_plobj_hash_update              = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_sit_hash_update,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_sit_cmp,
 	},
 	[NMP_OBJECT_TYPE_LNK_VLAN - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_VLAN,
 		.sizeof_data                        = sizeof (NMPObjectLnkVlan),
 		.sizeof_public                      = sizeof (NMPlatformLnkVlan),
 		.obj_type_name                      = "vlan",
 		.lnk_link_type                      = NM_LINK_TYPE_VLAN,
+		.cmd_obj_hash_update                = _vt_cmd_obj_hash_update_lnk_vlan,
 		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_lnk_vlan,
 		.cmd_obj_copy                       = _vt_cmd_obj_copy_lnk_vlan,
 		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_vlan,
 		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_vlan,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vlan_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,
 	},
 	[NMP_OBJECT_TYPE_LNK_VXLAN - 1] = {
+		.parent                             = DEDUP_MULTI_OBJ_CLASS_INIT(),
 		.obj_type                           = NMP_OBJECT_TYPE_LNK_VXLAN,
 		.sizeof_data                        = sizeof (NMPObjectLnkVxlan),
 		.sizeof_public                      = sizeof (NMPlatformLnkVxlan),
 		.obj_type_name                      = "vxlan",
 		.lnk_link_type                      = NM_LINK_TYPE_VXLAN,
 		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vxlan_to_string,
+		.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,
 	},
 };
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index b69680f6..41fd08cb 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -21,8 +21,9 @@
 #ifndef __NMP_OBJECT_H__
 #define __NMP_OBJECT_H__
 
+#include "nm-utils/nm-obj.h"
+#include "nm-utils/nm-dedup-multi.h"
 #include "nm-platform.h"
-#include "nm-multi-index.h"
 
 struct udev_device;
 
@@ -47,36 +48,42 @@ typedef enum { /*< skip >*/
  * but only route objects can be indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT.
  *
  * Of one index type, there can be multiple indexes or not.
- * For example, of the index type NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX there
+ * For example, of the index type NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX there
  * are multiple instances (for different route/addresses, v4/v6, per-ifindex).
  *
  * But one object, can only be indexed by one particular index of a
  * type. For example, a certain address instance is only indexed by
- * the index NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX with
+ * the index NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX with
  * matching v4/v6 and ifindex -- or maybe not at all if it isn't visible.
  * */
 typedef enum { /*< skip >*/
 	NMP_CACHE_ID_TYPE_NONE,
 
-	/* all the objects of a certain type */
+	/* all the objects of a certain type.
+	 *
+	 * This index is special. It is the only one that contains *all* object.
+	 * Other indexes may consider some object as non "partitionable", hence
+	 * they don't track all objects.
+	 *
+	 * Hence, this index type is used when looking at all objects (still
+	 * partitioned by type).
+	 *
+	 * Also, note that links may be considered invisible. This index type
+	 * expose all links, even invisible ones. For addresses/routes, this
+	 * distiction doesn't exist, as all addresses/routes that are alive
+	 * are visible as well. */
 	NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 
 	/* index for the link objects by ifname. */
 	NMP_CACHE_ID_TYPE_LINK_BY_IFNAME,
 
-	/* all the visible objects of a certain type */
-	NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY,
+	/* indeces for the visible default-routes, ignoring ifindex.
+	 * This index only contains two partitions: all visible default-routes,
+	 * separate for IPv4 and IPv6. */
+	NMP_CACHE_ID_TYPE_DEFAULT_ROUTES,
 
-	/* indeces for the visible routes, ignoring ifindex. */
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT,
-
-	/* all the visible addresses/routes (by object-type) for an ifindex. */
-	NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX,
-
-	/* three indeces for the visible routes, per ifindex. */
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT,
-	NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT,
+	/* all the addresses/routes (by object-type) for an ifindex. */
+	NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX,
 
 	/* Consider all the destination fields of a route, that is, the ID without the ifindex
 	 * and gateway (meaning: network/plen,metric).
@@ -86,65 +93,21 @@ typedef enum { /*< skip >*/
 	 * sends one RTM_NEWADDR notification without notifying about the deletion. We detect
 	 * that by having this index to contain overlapping routes which require special
 	 * cache-resync. */
-	NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4,
-	NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6,
+	NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID,
 
 	__NMP_CACHE_ID_TYPE_MAX,
 	NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1,
 } NMPCacheIdType;
 
-typedef struct _NMPCacheId NMPCacheId;
-
-struct _NMPCacheId {
-	union {
-		NMMultiIndexId base;
-		guint8 _id_type; /* NMPCacheIdType as guint8 */
-		struct _nm_packed {
-			/* NMP_CACHE_ID_TYPE_OBJECT_TYPE */
-			/* NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY */
-			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT */
-			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT */
-			guint8 _id_type;
-			guint8 obj_type; /* NMPObjectType as guint8 */
-		} object_type;
-		struct _nm_packed {
-			/* NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX */
-			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT */
-			/* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT */
-			guint8 _id_type;
-			guint8 obj_type; /* NMPObjectType as guint8 */
-			int _misaligned_ifindex;
-		} object_type_by_ifindex;
-		struct _nm_packed {
-			/* NMP_CACHE_ID_TYPE_LINK_BY_IFNAME */
-			guint8 _id_type;
-			char ifname_short[IFNAMSIZ - 1]; /* don't include the trailing NUL so the struct fits in 4 bytes. */
-		} link_by_ifname;
-		struct _nm_packed {
-			/* NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4 */
-			guint8 _id_type;
-			guint8 plen;
-			guint32 _misaligned_metric;
-			guint32 _misaligned_network;
-		} routes_by_destination_ip4;
-		struct _nm_packed {
-			/* NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6 */
-			guint8 _id_type;
-			guint8 plen;
-			guint32 _misaligned_metric;
-			struct in6_addr _misaligned_network;
-		} routes_by_destination_ip6;
-	};
-};
-
 typedef struct {
+	NMDedupMultiObjClass parent;
+	const char *obj_type_name;
+	int sizeof_data;
+	int sizeof_public;
 	NMPObjectType obj_type;
 	int addr_family;
 	int rtm_gettype;
-	int sizeof_data;
-	int sizeof_public;
 	NMPlatformSignalIdType signal_type_id;
-	const char *obj_type_name;
 	const char *signal_type;
 
 	const guint8 *supported_cache_ids;
@@ -152,13 +115,9 @@ typedef struct {
 	/* Only for NMPObjectLnk* types. */
 	NMLinkType lnk_link_type;
 
-	/* returns %FALSE, if the obj type would never have an entry for index type @id_type. If @obj has an index,
-	 * initialize @id and set @out_id to it. Otherwise, @out_id is NULL. */
-	gboolean (*cmd_obj_init_cache_id) (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id);
-
+	void (*cmd_obj_hash_update) (const NMPObject *obj, NMHashState *h);
 	int (*cmd_obj_cmp) (const NMPObject *obj1, const NMPObject *obj2);
 	void (*cmd_obj_copy) (NMPObject *dst, const NMPObject *src);
-	void (*cmd_obj_stackinit_id) (NMPObject *obj, const NMPObject *src);
 	void (*cmd_obj_dispose) (NMPObject *obj);
 	gboolean (*cmd_obj_is_alive) (const NMPObject *obj);
 	gboolean (*cmd_obj_is_visible) (const NMPObject *obj);
@@ -166,10 +125,11 @@ typedef struct {
 
 	/* functions that operate on NMPlatformObject */
 	void (*cmd_plobj_id_copy) (NMPlatformObject *dst, const NMPlatformObject *src);
-	gboolean (*cmd_plobj_id_equal) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
-	guint (*cmd_plobj_id_hash) (const NMPlatformObject *obj);
+	int (*cmd_plobj_id_cmp) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
+	void (*cmd_plobj_id_hash_update) (const NMPlatformObject *obj, NMHashState *h);
 	const char *(*cmd_plobj_to_string_id) (const NMPlatformObject *obj, char *buf, gsize buf_size);
 	const char *(*cmd_plobj_to_string) (const NMPlatformObject *obj, char *buf, gsize len);
+	void (*cmd_plobj_hash_update) (const NMPlatformObject *obj, NMHashState *h);
 	int (*cmd_plobj_cmp) (const NMPlatformObject *obj1, const NMPlatformObject *obj2);
 } NMPClass;
 
@@ -182,7 +142,7 @@ typedef struct {
 		bool is_in_netlink;
 
 		/* Additional data that depends on the link-type (IFLA_INFO_DATA) */
-		NMPObject *lnk;
+		const NMPObject *lnk;
 	} netlink;
 
 	struct {
@@ -266,9 +226,10 @@ typedef struct {
 } NMPObjectIP6Route;
 
 struct _NMPObject {
-	const NMPClass *_class;
-	int _ref_count;
-	bool is_cached;
+	union {
+		NMDedupMultiObj parent;
+		const NMPClass *_class;
+	};
 	union {
 		NMPlatformObject        object;
 
@@ -326,8 +287,6 @@ NMP_CLASS_IS_VALID (const NMPClass *klass)
 	    && ((((char *) klass) - ((char *) _nmp_classes)) % (sizeof (_nmp_classes[0]))) == 0;
 }
 
-#define NMP_REF_COUNT_STACKINIT (G_MAXINT)
-
 static inline NMPObject *
 NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj)
 {
@@ -336,7 +295,7 @@ NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj)
 	obj = plobj
 	      ? (NMPObject *) ( &(((char *) plobj)[-((int) G_STRUCT_OFFSET (NMPObject, object))]) )
 	      : NULL;
-	nm_assert (!obj || (obj->_ref_count > 0 && NMP_CLASS_IS_VALID (obj->_class)));
+	nm_assert (!obj || (obj->parent._ref_count > 0 && NMP_CLASS_IS_VALID (obj->_class)));
 	return obj;
 }
 #define NMP_OBJECT_UP_CAST(plobj) (NMP_OBJECT_UP_CAST ((const NMPlatformObject *) (plobj)))
@@ -345,7 +304,7 @@ static inline gboolean
 NMP_OBJECT_IS_VALID (const NMPObject *obj)
 {
 	nm_assert (!obj || (   obj
-	                    && obj->_ref_count > 0
+	                    && obj->parent._ref_count > 0
 	                    && NMP_CLASS_IS_VALID (obj->_class)));
 
 	/* There isn't really much to check. Either @obj is NULL, or we must
@@ -358,7 +317,7 @@ NMP_OBJECT_IS_STACKINIT (const NMPObject *obj)
 {
 	nm_assert (!obj || NMP_OBJECT_IS_VALID (obj));
 
-	return obj && obj->_ref_count == NMP_REF_COUNT_STACKINIT;
+	return obj && obj->parent._ref_count == NM_OBJ_REF_COUNT_STACKINIT;
 }
 
 static inline const NMPClass *
@@ -377,65 +336,264 @@ NMP_OBJECT_GET_TYPE (const NMPObject *obj)
 	return obj ? obj->_class->obj_type : NMP_OBJECT_TYPE_UNKNOWN;
 }
 
-
+#define NMP_OBJECT_CAST_LINK(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_LINK); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->link : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IP_ADDRESS(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip_address : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IPX_ADDRESS(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ipx_address : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IP4_ADDRESS(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip4_address : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IP6_ADDRESS(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP6_ADDRESS); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip6_address : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IPX_ROUTE(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ipx_route : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IP_ROUTE(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip_route : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IP4_ROUTE(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP4_ROUTE); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip4_route : NULL; \
+	})
+
+#define NMP_OBJECT_CAST_IP6_ROUTE(obj) \
+	({ \
+		typeof (obj) _obj = (obj); \
+		\
+		nm_assert (!_obj || NMP_OBJECT_GET_TYPE ((const NMPObject *) _obj) == NMP_OBJECT_TYPE_IP6_ROUTE); \
+		_obj ? &_NM_CONSTCAST (NMPObject, _obj)->ip6_route : NULL; \
+	})
 
 const NMPClass *nmp_class_from_type (NMPObjectType obj_type);
 
-NMPObject *nmp_object_ref (NMPObject *object);
-void nmp_object_unref (NMPObject *object);
+static inline const NMPObject *
+nmp_object_ref (const NMPObject *obj)
+{
+	if (!obj) {
+		/* for convenience, allow NULL. */
+		return NULL;
+	}
+
+	/* ref and unref accept const pointers. NMPObject is supposed to be shared
+	 * and kept immutable. Disallowing to take/retrun a reference to a const
+	 * NMPObject is cumbersome, because callers are precisely expected to
+	 * keep a ref on the otherwise immutable object. */
+	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
+	g_return_val_if_fail (obj->parent._ref_count != NM_OBJ_REF_COUNT_STACKINIT, NULL);
+
+	return (const NMPObject *) nm_dedup_multi_obj_ref ((const NMDedupMultiObj *) obj);
+}
+
+static inline const NMPObject *
+nmp_object_unref (const NMPObject *obj)
+{
+	nm_dedup_multi_obj_unref ((const NMDedupMultiObj *) obj);
+	return NULL;
+}
+
+#define nm_clear_nmp_object(ptr) \
+	({ \
+		typeof (ptr) _ptr = (ptr); \
+		typeof (*_ptr) _pptr; \
+		gboolean _changed = FALSE; \
+		\
+		if (   _ptr \
+		    && (_pptr = *_ptr)) { \
+			*_ptr = NULL; \
+			nmp_object_unref (_pptr); \
+			_changed = TRUE; \
+		} \
+		_changed; \
+	})
+
 NMPObject *nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plob);
 NMPObject *nmp_object_new_link (int ifindex);
 
 const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformObject *plobj);
+
+static inline NMPObject *
+nmp_object_stackinit_obj (NMPObject *obj, const NMPObject *src)
+{
+	return obj == src
+	         ? obj
+	         : (NMPObject *) nmp_object_stackinit (obj, NMP_OBJECT_GET_TYPE (src), &src->object);
+}
+
 const NMPObject *nmp_object_stackinit_id  (NMPObject *obj, const NMPObject *src);
 const NMPObject *nmp_object_stackinit_id_link (NMPObject *obj, int ifindex);
 const NMPObject *nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, guint8 plen, guint32 peer_address);
-const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, guint8 plen);
-const NMPObject *nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, guint8 plen, guint32 metric);
-const NMPObject *nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, guint8 plen, guint32 metric);
+const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address);
 
 const char *nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size);
+void nmp_object_hash_update (const NMPObject *obj, NMHashState *h);
 int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2);
 gboolean nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2);
 void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only);
 NMPObject *nmp_object_clone (const NMPObject *obj, gboolean id_only);
-gboolean nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2);
+
+int nmp_object_id_cmp (const NMPObject *obj1, const NMPObject *obj2);
+void nmp_object_id_hash_update (const NMPObject *obj, NMHashState *h);
 guint nmp_object_id_hash (const NMPObject *obj);
+
+static inline gboolean
+nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2)
+{
+	return nmp_object_id_cmp (obj1, obj2) == 0;
+}
+
 gboolean nmp_object_is_alive (const NMPObject *obj);
 gboolean nmp_object_is_visible (const NMPObject *obj);
 
-void _nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev);
+void _nmp_object_fixup_link_udev_fields (NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev);
 
-#define nm_auto_nmpobj __attribute__((cleanup(_nm_auto_nmpobj_cleanup)))
 static inline void
-_nm_auto_nmpobj_cleanup (NMPObject **pobj)
+_nm_auto_nmpobj_cleanup (gpointer p)
 {
-	nmp_object_unref (*pobj);
+	nmp_object_unref (*((const NMPObject **) p));
 }
+#define nm_auto_nmpobj nm_auto(_nm_auto_nmpobj_cleanup)
 
 typedef struct _NMPCache NMPCache;
 
 typedef void (*NMPCachePreHook) (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data);
 typedef gboolean (*NMPObjectMatchFn) (const NMPObject *obj, gpointer user_data);
 
-gboolean nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b);
-guint nmp_cache_id_hash (const NMPCacheId *id);
-NMPCacheId *nmp_cache_id_clone (const NMPCacheId *id);
-void nmp_cache_id_destroy (NMPCacheId *id);
+const NMDedupMultiEntry *nmp_cache_lookup_entry (const NMPCache *cache,
+                                                 const NMPObject *obj);
+const NMDedupMultiEntry *nmp_cache_lookup_entry_with_idx_type (const NMPCache *cache,
+                                                               NMPCacheIdType cache_id_type,
+                                                               const NMPObject *obj);
+const NMDedupMultiEntry *nmp_cache_lookup_entry_link (const NMPCache *cache,
+                                                      int ifindex);
+const NMPObject *nmp_cache_lookup_obj (const NMPCache *cache,
+                                       const NMPObject *obj);
+const NMPObject *nmp_cache_lookup_link (const NMPCache *cache,
+                                        int ifindex);
+
+typedef struct _NMPLookup NMPLookup;
+
+struct _NMPLookup {
+	NMPCacheIdType cache_id_type;
+	NMPObject selector_obj;
+};
+
+const NMDedupMultiHeadEntry *nmp_cache_lookup_all (const NMPCache *cache,
+                                                   NMPCacheIdType cache_id_type,
+                                                   const NMPObject *select_obj);
+
+static inline const NMDedupMultiHeadEntry *
+nmp_cache_lookup (const NMPCache *cache,
+                  const NMPLookup *lookup)
+{
+	return nmp_cache_lookup_all (cache, lookup->cache_id_type, &lookup->selector_obj);
+}
+
+const NMPLookup *nmp_lookup_init_obj_type (NMPLookup *lookup,
+                                           NMPObjectType obj_type);
+const NMPLookup *nmp_lookup_init_link_by_ifname (NMPLookup *lookup,
+                                                 const char *ifname);
+const NMPLookup *nmp_lookup_init_addrroute (NMPLookup *lookup,
+                                            NMPObjectType obj_type,
+                                            int ifindex);
+const NMPLookup *nmp_lookup_init_route_default (NMPLookup *lookup,
+                                                NMPObjectType obj_type);
+const NMPLookup *nmp_lookup_init_route_by_weak_id (NMPLookup *lookup,
+                                                   const NMPObject *obj);
+const NMPLookup *nmp_lookup_init_ip4_route_by_weak_id (NMPLookup *lookup,
+                                                       in_addr_t network,
+                                                       guint plen,
+                                                       guint32 metric,
+                                                       guint8 tos);
+const NMPLookup *nmp_lookup_init_ip6_route_by_weak_id (NMPLookup *lookup,
+                                                       const struct in6_addr *network,
+                                                       guint plen,
+                                                       guint32 metric,
+                                                       const struct in6_addr *src,
+                                                       guint8 src_plen);
+
+GArray *nmp_cache_lookup_to_array (const NMDedupMultiHeadEntry *head_entry,
+                                   NMPObjectType obj_type,
+                                   gboolean visible_only);
+
+static inline gboolean
+nmp_cache_iter_next (NMDedupMultiIter *iter, const NMPObject **out_obj)
+{
+	gboolean has_next;
+
+	has_next = nm_dedup_multi_iter_next (iter);
+	nm_assert (!has_next || NMP_OBJECT_IS_VALID (iter->current->obj));
+	if (out_obj)
+		*out_obj = has_next ? iter->current->obj : NULL;
+	return has_next;
+}
+
+static inline gboolean
+nmp_cache_iter_next_link (NMDedupMultiIter *iter, const NMPlatformLink **out_obj)
+{
+	gboolean has_next;
 
-NMPCacheId *nmp_cache_id_init_object_type (NMPCacheId *id, NMPObjectType obj_type, gboolean visible_only);
-NMPCacheId *nmp_cache_id_init_addrroute_visible_by_ifindex (NMPCacheId *id, NMPObjectType obj_type, int ifindex);
-NMPCacheId *nmp_cache_id_init_routes_visible (NMPCacheId *id, NMPObjectType obj_type, gboolean with_default, gboolean with_non_default, int ifindex);
-NMPCacheId *nmp_cache_id_init_link_by_ifname (NMPCacheId *id, const char *ifname);
-NMPCacheId *nmp_cache_id_init_routes_by_destination_ip4 (NMPCacheId *id, guint32 network, guint8 plen, guint32 metric);
-NMPCacheId *nmp_cache_id_init_routes_by_destination_ip6 (NMPCacheId *id, const struct in6_addr *network, guint8 plen, guint32 metric);
+	has_next = nm_dedup_multi_iter_next (iter);
+	nm_assert (!has_next || NMP_OBJECT_GET_TYPE (iter->current->obj) == NMP_OBJECT_TYPE_LINK);
+	if (out_obj)
+		*out_obj = has_next ? &(((const NMPObject *) iter->current->obj)->link) : NULL;
+	return has_next;
+}
 
-const NMPlatformObject *const *nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len);
-GArray *nmp_cache_lookup_multi_to_array (const NMPCache *cache, NMPObjectType obj_type, const NMPCacheId *cache_id);
-const NMPObject *nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj);
-const NMPObject *nmp_cache_lookup_link (const NMPCache *cache, int ifindex);
+#define nmp_cache_iter_for_each(iter, head, obj) \
+	for (nm_dedup_multi_iter_init ((iter), \
+	                               (head)); \
+	     nmp_cache_iter_next ((iter), (obj)); \
+	     )
 
-const NMPObject *nmp_cache_find_other_route_for_same_destination (const NMPCache *cache, const NMPObject *route);
+#define nmp_cache_iter_for_each_link(iter, head, obj) \
+	for (nm_dedup_multi_iter_init ((iter), \
+	                               (head)); \
+	     nmp_cache_iter_next_link ((iter), (obj)); \
+	     )
 
 const NMPObject *nmp_cache_lookup_link_full (const NMPCache *cache,
                                              int ifindex,
@@ -444,10 +602,8 @@ const NMPObject *nmp_cache_lookup_link_full (const NMPCache *cache,
                                              NMLinkType link_type,
                                              NMPObjectMatchFn match_fn,
                                              gpointer user_data);
-GHashTable *nmp_cache_lookup_all_to_hash (const NMPCache *cache,
-                                          NMPCacheId *cache_id,
-                                          GHashTable *hash);
 
+gboolean nmp_cache_link_connected_for_slave (int ifindex_master, const NMPObject *slave);
 gboolean nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave);
 const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave);
 
@@ -455,13 +611,182 @@ gboolean nmp_cache_use_udev_get (const NMPCache *cache);
 
 void ASSERT_nmp_cache_is_consistent (const NMPCache *cache);
 
-NMPCacheOpsType nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
-NMPCacheOpsType nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
-NMPCacheOpsType nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
-NMPCacheOpsType nmp_cache_update_link_udev (NMPCache *cache, int ifindex, struct udev_device *udevice, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
-NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data);
-
-NMPCache *nmp_cache_new (gboolean use_udev);
+NMPCacheOpsType nmp_cache_remove (NMPCache *cache,
+                                  const NMPObject *obj_needle,
+                                  gboolean equals_by_ptr,
+                                  gboolean only_dirty,
+                                  const NMPObject **out_obj_old);
+NMPCacheOpsType nmp_cache_remove_netlink (NMPCache *cache,
+                                          const NMPObject *obj_needle,
+                                          const NMPObject **out_obj_old,
+                                          const NMPObject **out_obj_new);
+NMPCacheOpsType nmp_cache_update_netlink (NMPCache *cache,
+                                          NMPObject *obj_hand_over,
+                                          gboolean is_dump,
+                                          const NMPObject **out_obj_old,
+                                          const NMPObject **out_obj_new);
+NMPCacheOpsType nmp_cache_update_netlink_route (NMPCache *cache,
+                                                NMPObject *obj_hand_over,
+                                                gboolean is_dump,
+                                                guint16 nlmsgflags,
+                                                const NMPObject **out_obj_old,
+                                                const NMPObject **out_obj_new,
+                                                const NMPObject **out_obj_replace,
+                                                gboolean *out_resync_required);
+NMPCacheOpsType nmp_cache_update_link_udev (NMPCache *cache,
+                                            int ifindex,
+                                            struct udev_device *udevice,
+                                            const NMPObject **out_obj_old,
+                                            const NMPObject **out_obj_new);
+NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache,
+                                                        int ifindex,
+                                                        const NMPObject **out_obj_old,
+                                                        const NMPObject **out_obj_new);
+
+void nmp_cache_dirty_set_all (NMPCache *cache, NMPObjectType obj_type);
+
+NMPCache *nmp_cache_new (NMDedupMultiIndex *multi_idx, gboolean use_udev);
 void nmp_cache_free (NMPCache *cache);
 
+static inline void
+ASSERT_nmp_cache_ops (const NMPCache *cache,
+                      NMPCacheOpsType ops_type,
+                      const NMPObject *obj_old,
+                      const NMPObject *obj_new)
+{
+#if NM_MORE_ASSERTS
+	nm_assert (cache);
+	nm_assert (obj_old || obj_new);
+	nm_assert (!obj_old || (   NMP_OBJECT_IS_VALID (obj_old)
+	                        && !NMP_OBJECT_IS_STACKINIT (obj_old)
+	                        && nmp_object_is_alive (obj_old)));
+	nm_assert (!obj_new || (   NMP_OBJECT_IS_VALID (obj_new)
+	                        && !NMP_OBJECT_IS_STACKINIT (obj_new)
+	                        && nmp_object_is_alive (obj_new)));
+
+	switch (ops_type) {
+	case NMP_CACHE_OPS_UNCHANGED:
+		nm_assert (obj_old == obj_new);
+		break;
+	case NMP_CACHE_OPS_ADDED:
+		nm_assert (!obj_old && obj_new);
+		break;
+	case NMP_CACHE_OPS_UPDATED:
+		nm_assert (obj_old &&  obj_new && obj_old != obj_new);
+		break;
+	case NMP_CACHE_OPS_REMOVED:
+		nm_assert (obj_old && !obj_new);
+		break;
+	default:
+		nm_assert_not_reached ();
+	}
+
+	nm_assert (obj_new == NULL || obj_old == NULL || nmp_object_id_equal (obj_new, obj_old));
+	nm_assert (!obj_old || !obj_new || NMP_OBJECT_GET_CLASS (obj_old) == NMP_OBJECT_GET_CLASS (obj_new));
+
+	nm_assert (obj_new == nmp_cache_lookup_obj (cache, obj_new ?: obj_old));
+#endif
+}
+
+const NMDedupMultiHeadEntry *nm_platform_lookup_all (NMPlatform *platform,
+                                                     NMPCacheIdType cache_id_type,
+                                                     const NMPObject *obj);
+
+const NMDedupMultiEntry *nm_platform_lookup_entry (NMPlatform *platform,
+                                                   NMPCacheIdType cache_id_type,
+                                                   const NMPObject *obj);
+
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_obj_type (NMPlatform *platform,
+                             NMPObjectType obj_type)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_obj_type (&lookup, obj_type);
+	return nm_platform_lookup (platform, &lookup);
+}
+
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_link_by_ifname (NMPlatform *platform,
+                                   const char *ifname)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_link_by_ifname (&lookup, ifname);
+	return nm_platform_lookup (platform, &lookup);
+}
+
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_addrroute (NMPlatform *platform,
+                              NMPObjectType obj_type,
+                              int ifindex)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_addrroute (&lookup, obj_type, ifindex);
+	return nm_platform_lookup (platform, &lookup);
+}
+
+static inline GPtrArray *
+nm_platform_lookup_addrroute_clone (NMPlatform *platform,
+                                    NMPObjectType obj_type,
+                                    int ifindex,
+                                    NMPObjectPredicateFunc predicate,
+                                    gpointer user_data)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_addrroute (&lookup, obj_type, ifindex);
+	return nm_platform_lookup_clone (platform, &lookup, predicate, user_data);
+}
+
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_route_default (NMPlatform *platform,
+                                  NMPObjectType obj_type)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_route_default (&lookup, obj_type);
+	return nm_platform_lookup (platform, &lookup);
+}
+
+static inline GPtrArray *
+nm_platform_lookup_route_default_clone (NMPlatform *platform,
+                                        NMPObjectType obj_type,
+                                        NMPObjectPredicateFunc predicate,
+                                        gpointer user_data)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_route_default (&lookup, obj_type);
+	return nm_platform_lookup_clone (platform, &lookup, predicate, user_data);
+}
+
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_ip4_route_by_weak_id (NMPlatform *platform,
+                                         in_addr_t network,
+                                         guint plen,
+                                         guint32 metric,
+                                         guint8 tos)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_ip4_route_by_weak_id (&lookup, network, plen, metric, tos);
+	return nm_platform_lookup (platform, &lookup);
+}
+
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_ip6_route_by_weak_id (NMPlatform *platform,
+                                         const struct in6_addr *network,
+                                         guint plen,
+                                         guint32 metric,
+                                         const struct in6_addr *src,
+                                         guint8 src_plen)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_ip6_route_by_weak_id (&lookup, network, plen, metric, src, src_plen);
+	return nm_platform_lookup (platform, &lookup);
+}
+
 #endif /* __NMP_OBJECT_H__ */
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index d0c58aeb..e1220052 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -78,6 +78,8 @@ main (int argc, char **argv)
 
 	nm_linux_platform_setup ();
 
+	nm_platform_check_kernel_support (NM_PLATFORM_GET, ~((NMPlatformKernelSupportFlags) 0));
+
 	if (global_opt.persist)
 		g_main_loop_run (loop);
 
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index 4c139ef0..93851ff7 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -22,7 +22,6 @@
 
 #include "test-common.h"
 
-#define DEVICE_NAME "nm-test-device"
 #define IP4_ADDRESS "192.0.2.1"
 #define IP4_ADDRESS_PEER "192.0.2.2"
 #define IP4_ADDRESS_PEER2 "192.0.3.1"
@@ -30,8 +29,8 @@
 #define IP6_ADDRESS "2001:db8:a:b:1:2:3:4"
 #define IP6_PLEN 64
 
-static int DEVICE_IFINDEX = -1;
-static int EX = -1;
+#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX
+#define EX             NMTSTP_ENV1_EX
 
 /*****************************************************************************/
 
@@ -103,7 +102,7 @@ test_ip4_address_general (void)
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
-	addresses = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 	g_assert (addresses);
 	g_assert_cmpint (addresses->len, ==, 1);
 	address = &g_array_index (addresses, NMPlatformIP4Address, 0);
@@ -143,9 +142,9 @@ test_ip6_address_general (void)
 	inet_pton (AF_INET6, IP6_ADDRESS, &addr);
 
 	/* Add address */
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 	accept_signal (address_added);
 
 	/* Add address again (aka update) */
@@ -153,7 +152,7 @@ test_ip6_address_general (void)
 	accept_signals (address_changed, 0, 1);
 
 	/* Test address listing */
-	addresses = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
 	g_assert (addresses);
 	g_assert_cmpint (addresses->len, ==, 1);
 	address = &g_array_index (addresses, NMPlatformIP6Address, 0);
@@ -164,7 +163,7 @@ test_ip6_address_general (void)
 
 	/* Remove address */
 	nmtstp_ip6_address_del (NULL, EX, ifindex, addr, IP6_PLEN);
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 	accept_signal (address_removed);
 
 	/* Remove address again */
@@ -229,20 +228,20 @@ test_ip6_address_general_2 (void)
 	/* Add/delete notification */
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
 	accept_signal (address_added);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	nmtstp_ip6_address_del (NULL, EX, ifindex, addr, IP6_PLEN);
 	accept_signal (address_removed);
-	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	/* Add/delete conflict */
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0);
 	accept_signal (address_added);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	nmtstp_ip6_address_add (NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, flags);
 	ensure_no_signal (address_added);
-	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr, IP6_PLEN));
+	g_assert (nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, addr));
 
 	free_signal (address_added);
 	free_signal (address_removed);
@@ -330,7 +329,7 @@ test_ip4_address_peer_zero (void)
 
 		nmtstp_ip4_address_add (NULL, EX, ifindex, addr, plen, r_peers[i], lifetime, preferred, 0, label);
 
-		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		addrs = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 		g_assert (addrs);
 		g_assert_cmpint (addrs->len, ==, i + 1);
 		g_array_unref (addrs);
@@ -345,7 +344,7 @@ test_ip4_address_peer_zero (void)
 
 		nmtstp_ip4_address_del (NULL, EX, ifindex, addr, plen, r_peers[i]);
 
-		addrs = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+		addrs = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
 		g_assert (addrs);
 		g_assert_cmpint (addrs->len, ==, G_N_ELEMENTS (peers) - i - 1);
 		g_array_unref (addrs);
@@ -366,62 +365,16 @@ _nmtstp_init_tests (int *argc, char ***argv)
  * SETUP TESTS
  *****************************************************************************/
 
-typedef struct {
-	const char *testpath;
-	GTestFunc test_func;
-} TestSetup;
-
-static void
-_g_test_run (gconstpointer user_data)
-{
-	const TestSetup *s = user_data;
-	int ifindex;
-
-	_LOGT ("TEST: start %s", s->testpath);
-
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
-
-	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
-	g_assert_cmpint (ifindex, >, 0);
-	g_assert_cmpint (DEVICE_IFINDEX, ==, -1);
-
-	DEVICE_IFINDEX = ifindex;
-	EX = nmtstp_run_command_check_external_global ();
-
-	s->test_func ();
-
-	g_assert_cmpint (DEVICE_IFINDEX, ==, ifindex);
-	DEVICE_IFINDEX = -1;
-
-	g_assert_cmpint (ifindex, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
-	_LOGT ("TEST: finished %s", s->testpath);
-}
-
-static void
-_g_test_add_func (const char *testpath,
-                  GTestFunc   test_func)
-{
-	TestSetup *s;
-
-	s = g_new0 (TestSetup, 1);
-	s->testpath = testpath;
-	s->test_func = test_func;
-
-	g_test_add_data_func_full (testpath, s, _g_test_run, g_free);
-}
-
 void
 _nmtstp_setup_tests (void)
 {
-	_g_test_add_func ("/address/ipv4/general", test_ip4_address_general);
-	_g_test_add_func ("/address/ipv6/general", test_ip6_address_general);
+#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, FALSE)
+	add_test_func ("/address/ipv4/general", test_ip4_address_general);
+	add_test_func ("/address/ipv6/general", test_ip6_address_general);
 
-	_g_test_add_func ("/address/ipv4/general-2", test_ip4_address_general_2);
-	_g_test_add_func ("/address/ipv6/general-2", test_ip6_address_general_2);
+	add_test_func ("/address/ipv4/general-2", test_ip4_address_general_2);
+	add_test_func ("/address/ipv6/general-2", test_ip6_address_general_2);
 
-	_g_test_add_func ("/address/ipv4/peer", test_ip4_address_peer);
-	_g_test_add_func ("/address/ipv4/peer/zero", test_ip4_address_peer_zero);
+	add_test_func ("/address/ipv4/peer", test_ip4_address_peer);
+	add_test_func ("/address/ipv4/peer/zero", test_ip4_address_peer_zero);
 }
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 71a92cbf..937cd12c 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -22,8 +22,6 @@
 
 #include "test-common.h"
 
-#define DEVICE_NAME "nm-test-device"
-
 static void
 test_cleanup_internal (void)
 {
@@ -31,8 +29,8 @@ test_cleanup_internal (void)
 	int ifindex;
 	GArray *addresses4;
 	GArray *addresses6;
-	GArray *routes4;
-	GArray *routes6;
+	GPtrArray *routes4;
+	GPtrArray *routes6;
 	in_addr_t addr4;
 	in_addr_t network4;
 	int plen4 = 24;
@@ -72,10 +70,10 @@ test_cleanup_internal (void)
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network6, plen6, gateway6, in6addr_any, metric, mss);
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway6, in6addr_any, metric, mss);
 
-	addresses4 = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
-	addresses6 = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
-	routes4 = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-	routes6 = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	addresses4 = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses6 = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	routes4 = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
+	routes6 = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, ifindex);
 
 	g_assert_cmpint (addresses4->len, ==, 1);
 	g_assert_cmpint (addresses6->len, ==, 2); /* also has a IPv6 LL address. */
@@ -84,26 +82,24 @@ test_cleanup_internal (void)
 
 	g_array_unref (addresses4);
 	g_array_unref (addresses6);
-	g_array_unref (routes4);
-	g_array_unref (routes6);
+	g_ptr_array_unref (routes4);
+	g_ptr_array_unref (routes6);
 
 	/* Delete interface with all addresses and routes */
 	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, ifindex));
 
-	addresses4 = nm_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
-	addresses6 = nm_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
-	routes4 = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-	routes6 = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	addresses4 = nmtstp_platform_ip4_address_get_all (NM_PLATFORM_GET, ifindex);
+	addresses6 = nmtstp_platform_ip6_address_get_all (NM_PLATFORM_GET, ifindex);
+	routes4 = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
+	routes6 = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, ifindex);
 
 	g_assert_cmpint (addresses4->len, ==, 0);
 	g_assert_cmpint (addresses6->len, ==, 0);
-	g_assert_cmpint (routes4->len, ==, 0);
-	g_assert_cmpint (routes6->len, ==, 0);
+	g_assert (!routes4);
+	g_assert (!routes6);
 
 	g_array_unref (addresses4);
 	g_array_unref (addresses6);
-	g_array_unref (routes4);
-	g_array_unref (routes6);
 }
 
 NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index a9d0694d..18076712 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -30,6 +30,9 @@
 #define SIGNAL_DATA_FMT "'%s-%s' ifindex %d%s%s%s (%d times received)"
 #define SIGNAL_DATA_ARG(data) (data)->name, nm_platform_signal_change_type_to_string ((data)->change_type), (data)->ifindex, (data)->ifname ? " ifname '" : "", (data)->ifname ? (data)->ifname : "", (data)->ifname ? "'" : "", (data)->received_count
 
+int NMTSTP_ENV1_IFINDEX = -1;
+int NMTSTP_ENV1_EX = -1;
+
 /*****************************************************************************/
 
 void
@@ -67,6 +70,90 @@ _init_platform (NMPlatform **platform, gboolean external_command)
 
 /*****************************************************************************/
 
+static GArray *
+_ipx_address_get_all (NMPlatform *self, int ifindex, NMPObjectType obj_type)
+{
+	NMPLookup lookup;
+
+	g_assert (NM_IS_PLATFORM (self));
+	g_assert (ifindex > 0);
+	g_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS));
+	nmp_lookup_init_addrroute (&lookup,
+	                           obj_type,
+	                           ifindex);
+	return nmp_cache_lookup_to_array (nm_platform_lookup (self, &lookup),
+	                                  obj_type,
+	                                  FALSE /*addresses are always visible. */);
+}
+
+GArray *
+nmtstp_platform_ip4_address_get_all (NMPlatform *self, int ifindex)
+{
+	return _ipx_address_get_all (self, ifindex, NMP_OBJECT_TYPE_IP4_ADDRESS);
+}
+
+GArray *
+nmtstp_platform_ip6_address_get_all (NMPlatform *self, int ifindex)
+{
+	return _ipx_address_get_all (self, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
+}
+
+/*****************************************************************************/
+
+gboolean
+nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric)
+{
+	NMDedupMultiIter iter;
+
+	nm_platform_process_events (platform);
+
+	nm_dedup_multi_iter_for_each (&iter,
+	                              nm_platform_lookup_addrroute (platform,
+	                                                            NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                                            ifindex)) {
+		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (iter.current->obj);
+
+		if (   r->ifindex != ifindex
+		    || r->network != network
+		    || r->plen != plen
+		    || r->metric != metric) {
+			continue;
+		}
+
+		return nm_platform_ip_route_delete (platform, NMP_OBJECT_UP_CAST (r));
+	}
+
+	return TRUE;
+}
+
+gboolean
+nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric)
+{
+	NMDedupMultiIter iter;
+
+	nm_platform_process_events (platform);
+
+	nm_dedup_multi_iter_for_each (&iter,
+	                              nm_platform_lookup_addrroute (platform,
+	                                                            NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                                            ifindex)) {
+		const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (iter.current->obj);
+
+		if (   r->ifindex != ifindex
+		    || !IN6_ARE_ADDR_EQUAL (&r->network, &network)
+		    || r->plen != plen
+		    || r->metric != metric) {
+			continue;
+		}
+
+		return nm_platform_ip_route_delete (platform, NMP_OBJECT_UP_CAST (r));
+	}
+
+	return TRUE;
+}
+
+/*****************************************************************************/
+
 SignalData *
 add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCallback callback, int ifindex, const char *ifname)
 {
@@ -153,9 +240,9 @@ link_callback (NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
-	GArray *links;
-	NMPlatformLink *cached;
-	int i;
+	NMPLookup lookup;
+	NMDedupMultiIter iter;
+	const NMPlatformLink *cached;
 
 	g_assert_cmpint (obj_type, ==, NMP_OBJECT_TYPE_LINK);
 	g_assert (received);
@@ -185,19 +272,21 @@ link_callback (NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink
 
 	/* Check the data */
 	g_assert (received->ifindex > 0);
-	links = nm_platform_link_get_all (NM_PLATFORM_GET, TRUE);
-	for (i = 0; i < links->len; i++) {
-		cached = &g_array_index (links, NMPlatformLink, i);
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
+	nmp_cache_iter_for_each_link (&iter,
+	                              nm_platform_lookup (platform, &lookup),
+	                              &cached) {
+		if (!nmp_object_is_visible (NMP_OBJECT_UP_CAST (cached)))
+			continue;
 		if (cached->ifindex == received->ifindex) {
 			g_assert_cmpint (nm_platform_link_cmp (cached, received), ==, 0);
 			g_assert (!memcmp (cached, received, sizeof (*cached)));
 			if (data->change_type == NM_PLATFORM_SIGNAL_REMOVED)
 				g_error ("Deleted link still found in the local cache.");
-			g_array_unref (links);
 			return;
 		}
 	}
-	g_array_unref (links);
 
 	if (data->change_type != NM_PLATFORM_SIGNAL_REMOVED)
 		g_error ("Added/changed link not found in the local cache.");
@@ -205,113 +294,219 @@ link_callback (NMPlatform *platform, int obj_type_i, int ifindex, NMPlatformLink
 
 /*****************************************************************************/
 
-gboolean
-nmtstp_ip4_route_exists (const char *ifname, guint32 network, int plen, guint32 metric)
+static const NMPlatformIP4Route *
+_ip4_route_get (NMPlatform *platform,
+                int ifindex,
+                guint32 network,
+                int plen,
+                guint32 metric,
+                guint8 tos,
+                guint *out_c_exists)
 {
-	gs_free char *arg_network = NULL;
-	const char *argv[] = {
-		NULL,
-		"route",
-		"list",
-		"dev",
-		ifname,
-		"exact",
-		NULL,
-		NULL,
-	};
-	int exit_status;
-	gs_free char *std_out = NULL, *std_err = NULL;
-	char *out;
-	gboolean success;
-	gs_free_error GError *error = NULL;
-	gs_free char *metric_pattern = NULL;
+	NMDedupMultiIter iter;
+	NMPLookup lookup;
+	const NMPObject *o = NULL;
+	guint c;
+	const NMPlatformIP4Route *r = NULL;
 
-	g_assert (ifname && nm_utils_is_valid_iface_name (ifname, NULL));
-	g_assert (!strstr (ifname, " metric "));
-	g_assert (plen >= 0 && plen <= 32);
+	_init_platform (&platform, FALSE);
 
-	if (!nmtstp_is_root_test ()) {
-		/* If we don't test against linux-platform, we don't actually configure any
-		 * routes in the system. */
-		return -1;
+	nmp_lookup_init_ip4_route_by_weak_id (&lookup,
+	                                      network,
+	                                      plen,
+	                                      metric,
+	                                      tos);
+
+	c = 0;
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup (platform, &lookup),
+	                         &o) {
+		if (   NMP_OBJECT_CAST_IP4_ROUTE (o)->ifindex != ifindex
+		    && ifindex > 0)
+			continue;
+		if (!r)
+			r = NMP_OBJECT_CAST_IP4_ROUTE (o);
+		c++;
 	}
 
-	argv[0] = nm_utils_file_search_in_paths ("ip", NULL,
-	                                         (const char *[]) { "/sbin", "/usr/sbin", NULL },
-	                                         G_FILE_TEST_IS_EXECUTABLE, NULL, NULL, NULL);
-	argv[6] = arg_network = g_strdup_printf ("%s/%d", nm_utils_inet4_ntop (network, NULL), plen);
+	NM_SET_OUT (out_c_exists, c);
+	return r;
+}
 
-	if (!argv[0]) {
-		/* Hm. There is no 'ip' binary. Return *unknown* */
-		return -1;
+const NMPlatformIP4Route *
+_nmtstp_assert_ip4_route_exists (const char *file,
+                                 guint line,
+                                 const char *func,
+                                 NMPlatform *platform,
+                                 int c_exists,
+                                 const char *ifname,
+                                 guint32 network,
+                                 int plen,
+                                 guint32 metric,
+                                 guint8 tos)
+{
+	int ifindex;
+	guint c;
+	const NMPlatformIP4Route *r = NULL;
+
+	_init_platform (&platform, FALSE);
+
+	ifindex = -1;
+	if (ifname) {
+		ifindex = nm_platform_link_get_ifindex (platform, ifname);
+		g_assert (ifindex > 0);
 	}
 
-	success = g_spawn_sync (NULL,
-	                        (char **) argv,
-	                        (char *[]) { NULL },
-	                        0,
-	                        NULL,
-	                        NULL,
-	                        &std_out,
-	                        &std_err,
-	                        &exit_status,
-	                        &error);
-	g_assert_no_error (error);
-	g_assert (success);
-	g_assert_cmpstr (std_err, ==, "");
-	g_assert (std_out);
-
-	metric_pattern = g_strdup_printf (" metric %u", metric);
-	out = std_out;
-	while (out) {
-		char *eol = strchr (out, '\n');
-		gs_free char *line = eol ? g_strndup (out, eol - out) : g_strdup (out);
-		const char *p;
-
-		out = eol ? &eol[1] : NULL;
-		if (!line[0])
-			continue;
+	r = _ip4_route_get (platform,
+	                    ifindex,
+	                    network,
+	                    plen,
+	                    metric,
+	                    tos,
+	                    &c);
 
-		if (metric == 0) {
-			if (!strstr (line, " metric "))
-				return TRUE;
-		}
-		p = strstr (line, metric_pattern);
-		if (p && NM_IN_SET (p[strlen (metric_pattern)], ' ', '\0'))
-			return TRUE;
+	if (c != c_exists && c_exists != -1) {
+		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but platform has it %u times",
+		         file, line, func,
+		         nm_utils_inet4_ntop (network, NULL), plen,
+		         metric,
+		         tos,
+		         c_exists,
+		         c);
 	}
-	return FALSE;
+
+	return r;
 }
 
-void
-_nmtstp_assert_ip4_route_exists (const char *file, guint line, const char *func, NMPlatform *platform, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric)
+const NMPlatformIP4Route *
+nmtstp_ip4_route_get (NMPlatform *platform,
+                      int ifindex,
+                      guint32 network,
+                      int plen,
+                      guint32 metric,
+                      guint8 tos)
+{
+	return _ip4_route_get (platform,
+	                       ifindex,
+	                       network,
+	                       plen,
+	                       metric,
+	                       tos,
+	                       NULL);
+}
+
+/*****************************************************************************/
+
+static const NMPlatformIP6Route *
+_ip6_route_get (NMPlatform *platform,
+                int ifindex,
+                const struct in6_addr *network,
+                guint plen,
+                guint32 metric,
+                const struct in6_addr *src,
+                guint8 src_plen,
+                guint *out_c_exists)
+{
+	NMDedupMultiIter iter;
+	NMPLookup lookup;
+	const NMPObject *o = NULL;
+	guint c;
+	const NMPlatformIP6Route *r = NULL;
+
+	_init_platform (&platform, FALSE);
+
+	nmp_lookup_init_ip6_route_by_weak_id (&lookup,
+	                                      network,
+	                                      plen,
+	                                      metric,
+	                                      src,
+	                                      src_plen);
+
+	c = 0;
+	nmp_cache_iter_for_each (&iter,
+	                         nm_platform_lookup (platform, &lookup),
+	                         &o) {
+		if (   NMP_OBJECT_CAST_IP6_ROUTE (o)->ifindex != ifindex
+		    && ifindex > 0)
+			continue;
+		if (!r)
+			r = NMP_OBJECT_CAST_IP6_ROUTE (o);
+		c++;
+	}
+
+	NM_SET_OUT (out_c_exists, c);
+	return r;
+}
+
+const NMPlatformIP6Route *
+_nmtstp_assert_ip6_route_exists (const char *file,
+                                 guint line,
+                                 const char *func,
+                                 NMPlatform *platform,
+                                 int c_exists,
+                                 const char *ifname,
+                                 const struct in6_addr *network,
+                                 guint plen,
+                                 guint32 metric,
+                                 const struct in6_addr *src,
+                                 guint8 src_plen)
 {
 	int ifindex;
-	gboolean exists_checked;
+	guint c;
+	const NMPlatformIP6Route *r = NULL;
 
 	_init_platform (&platform, FALSE);
 
-	/* Check for existance of the route by spawning iproute2. Do this because platform
-	 * code might be entirely borked, but we expect ip-route to give a correct result.
-	 * If the ip command cannot be found, we accept this as success. */
-	exists_checked = nmtstp_ip4_route_exists (ifname, network, plen, metric);
-	if (exists_checked != -1 && !exists_checked != !exists) {
-		g_error ("[%s:%u] %s(): We expect the ip4 route %s/%d metric %u %s, but it %s",
-		         file, line, func,
-		         nm_utils_inet4_ntop (network, NULL), plen, metric,
-		         exists ? "to exist" : "not to exist",
-		         exists ? "doesn't" : "does");
+	ifindex = -1;
+	if (ifname) {
+		ifindex = nm_platform_link_get_ifindex (platform, ifname);
+		g_assert (ifindex > 0);
 	}
 
-	ifindex = nm_platform_link_get_ifindex (platform, ifname);
-	g_assert (ifindex > 0);
-	if (!nm_platform_ip4_route_get (platform, ifindex, network, plen, metric) != !exists) {
-		g_error ("[%s:%u] %s(): The ip4 route %s/%d metric %u %s, but platform thinks %s",
+	r = _ip6_route_get (platform,
+	                    ifindex,
+	                    network,
+	                    plen,
+	                    metric,
+	                    src,
+	                    src_plen,
+	                    &c);
+
+	if (c != c_exists && c_exists != -1) {
+		char s_src[NM_UTILS_INET_ADDRSTRLEN];
+		char s_network[NM_UTILS_INET_ADDRSTRLEN];
+
+		g_error ("[%s:%u] %s(): The ip6 route %s/%d metric %u src %s/%d shall exist %u times, but platform has it %u times",
 		         file, line, func,
-		         nm_utils_inet4_ntop (network, NULL), plen, metric,
-		         exists ? "exists" : "does not exist",
-		         exists ? "it doesn't" : "it does");
+		         nm_utils_inet6_ntop (network, s_network),
+		         plen,
+		         metric,
+		         nm_utils_inet6_ntop (src, s_src),
+		         src_plen,
+		         c_exists,
+		         c);
 	}
+
+	return r;
+}
+
+const NMPlatformIP6Route *
+nmtstp_ip6_route_get (NMPlatform *platform,
+                      int ifindex,
+                      const struct in6_addr *network,
+                      guint plen,
+                      guint32 metric,
+                      const struct in6_addr *src,
+                      guint8 src_plen)
+{
+	return _ip6_route_get (platform,
+	                       ifindex,
+	                       network,
+	                       plen,
+	                       metric,
+	                       src,
+	                       src_plen,
+	                       NULL);
 }
 
 /*****************************************************************************/
@@ -369,7 +564,7 @@ _wait_for_signal_timeout (gpointer user_data)
 }
 
 guint
-nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms)
+nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms)
 {
 	WaitForSignalData data = { 0 };
 	gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route;
@@ -384,8 +579,18 @@ nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms)
 	id_ip4_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 	id_ip6_route   = g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data);
 
-	if (timeout_ms != 0)
-		data.id = g_timeout_add (timeout_ms, _wait_for_signal_timeout, &data);
+	/* if timeout_ms is negative, it means the wait-time already expired.
+	 * Maybe, we should do nothing and return right away, without even
+	 * processing events from platform. However, that inconsistency (of not
+	 * processing events from mainloop) is inconvenient.
+	 *
+	 * It's better that on the return of nmtstp_wait_for_signal(), we always
+	 * have no events pending. So, a negative timeout is treated the same as
+	 * a zero timeout: we check whether there are any events pending in platform,
+	 * and quite the mainloop immediately afterwards. But we always check. */
+
+	data.id = g_timeout_add (CLAMP (timeout_ms, 0, G_MAXUINT32),
+	                         _wait_for_signal_timeout, &data);
 
 	g_main_loop_run (data.loop);
 
@@ -414,14 +619,14 @@ nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms)
 		if (until_ms < now)
 			return 0;
 
-		signal_counts = nmtstp_wait_for_signal (platform, MAX (1, until_ms - now));
+		signal_counts = nmtstp_wait_for_signal (platform, until_ms - now);
 		if (signal_counts)
 			return signal_counts;
 	}
 }
 
 const NMPlatformLink *
-nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms)
+nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms)
 {
 	return nmtstp_wait_for_link_until (platform, ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms);
 }
@@ -445,7 +650,7 @@ nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType
 		if (until_ms < now)
 			return NULL;
 
-		nmtstp_wait_for_signal (platform, MAX (1, until_ms - now));
+		nmtstp_wait_for_signal (platform, until_ms - now);
 	}
 }
 
@@ -717,7 +922,7 @@ _ip_address_add (NMPlatform *platform,
 			g_assert (label == NULL);
 			g_assert (flags == 0);
 
-			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6, plen);
+			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6);
 			if (   a
 			    && !memcmp (nm_platform_ip6_address_get_peer (a),
 			                (IN6_IS_ADDR_UNSPECIFIED (&peer_address->addr6) || IN6_ARE_ADDR_EQUAL (&address->addr6, &peer_address->addr6))
@@ -804,7 +1009,7 @@ void nmtstp_ip4_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert (nm_platform_ip4_route_add (platform, &route));
+	g_assert_cmpint (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
 }
 
 void nmtstp_ip6_route_add (NMPlatform *platform,
@@ -828,7 +1033,7 @@ void nmtstp_ip6_route_add (NMPlatform *platform,
 	route.metric = metric;
 	route.mss = mss;
 
-	g_assert (nm_platform_ip6_route_add (platform, &route));
+	g_assert_cmpint (nm_platform_ip6_route_add (platform, NMP_NLM_FLAG_REPLACE, &route), ==, NM_PLATFORM_ERROR_SUCCESS);
 }
 
 /*****************************************************************************/
@@ -861,7 +1066,7 @@ _ip_address_del (NMPlatform *platform,
 		if (is_v4)
 			had_address = !!nm_platform_ip4_address_get (platform, ifindex, address->addr4, plen, peer_address->addr4);
 		else
-			had_address = !!nm_platform_ip6_address_get (platform, ifindex, address->addr6, plen);
+			had_address = !!nm_platform_ip6_address_get (platform, ifindex, address->addr6);
 
 		if (is_v4) {
 			success = nmtstp_run_command ("ip address delete %s%s%s/%d dev %s",
@@ -913,7 +1118,7 @@ _ip_address_del (NMPlatform *platform,
 		} else {
 			const NMPlatformIP6Address *a;
 
-			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6, plen);
+			a = nm_platform_ip6_address_get (platform, ifindex, address->addr6);
 			if (!a)
 				break;
 		}
@@ -974,6 +1179,36 @@ nmtstp_ip6_address_del (NMPlatform *platform,
 	} G_STMT_END
 
 const NMPlatformLink *
+nmtstp_link_veth_add (NMPlatform *platform,
+                      gboolean external_command,
+                      const char *name,
+                      const char *peer)
+{
+	const NMPlatformLink *pllink = NULL;
+	gboolean success;
+
+	g_assert (nm_utils_is_valid_iface_name (name, NULL));
+
+	external_command = nmtstp_run_command_check_external (external_command);
+
+	_init_platform (&platform, external_command);
+
+	if (external_command) {
+		success = !nmtstp_run_command ("ip link add dev %s type veth peer name %s",
+		                                name, peer);
+		if (success) {
+			pllink = nmtstp_assert_wait_for_link (platform, name, NM_LINK_TYPE_VETH, 100);
+			nmtstp_assert_wait_for_link (platform, peer, NM_LINK_TYPE_VETH, 10);
+		}
+	} else
+		success = nm_platform_link_veth_add (platform, name, peer, &pllink) == NM_PLATFORM_ERROR_SUCCESS;
+
+	g_assert (success);
+	_assert_pllink (platform, success, pllink, name, NM_LINK_TYPE_VETH);
+	return pllink;
+}
+
+const NMPlatformLink *
 nmtstp_link_dummy_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 a52a5db5..4010aa2f 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -5,6 +5,7 @@
 #include <arpa/inet.h>
 
 #include "platform/nm-platform.h"
+#include "platform/nmp-object.h"
 #include "platform/nm-fake-platform.h"
 #include "platform/nm-linux-platform.h"
 
@@ -93,9 +94,9 @@ int nmtstp_run_command (const char *format, ...) _nm_printf (1, 2);
 
 /*****************************************************************************/
 
-guint nmtstp_wait_for_signal (NMPlatform *platform, guint timeout_ms);
+guint nmtstp_wait_for_signal (NMPlatform *platform, gint64 timeout_ms);
 guint nmtstp_wait_for_signal_until (NMPlatform *platform, gint64 until_ms);
-const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, guint timeout_ms);
+const NMPlatformLink *nmtstp_wait_for_link (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 timeout_ms);
 const NMPlatformLink *nmtstp_wait_for_link_until (NMPlatform *platform, const char *ifname, NMLinkType expected_link_type, gint64 until_ms);
 
 #define nmtstp_assert_wait_for_signal(platform, timeout_ms) \
@@ -120,10 +121,45 @@ gboolean nmtstp_run_command_check_external (int external_command);
 
 /*****************************************************************************/
 
-gboolean nmtstp_ip4_route_exists (const char *ifname, guint32 network, int plen, guint32 metric);
-
-void _nmtstp_assert_ip4_route_exists (const char *file, guint line, const char *func, NMPlatform *platform, gboolean exists, const char *ifname, guint32 network, int plen, guint32 metric);
-#define nmtstp_assert_ip4_route_exists(platform, exists, ifname, network, plen, metric) _nmtstp_assert_ip4_route_exists (__FILE__, __LINE__, G_STRFUNC, platform, exists, ifname, network, plen, metric)
+const NMPlatformIP4Route *_nmtstp_assert_ip4_route_exists (const char *file,
+                                                           guint line,
+                                                           const char *func,
+                                                           NMPlatform *platform,
+                                                           int c_exists,
+                                                           const char *ifname,
+                                                           guint32 network,
+                                                           int plen,
+                                                           guint32 metric,
+                                                           guint8 tos);
+#define nmtstp_assert_ip4_route_exists(platform, c_exists, ifname, network, plen, metric, tos) _nmtstp_assert_ip4_route_exists (__FILE__, __LINE__, G_STRFUNC, platform, c_exists, ifname, network, plen, metric, tos)
+
+const NMPlatformIP4Route *nmtstp_ip4_route_get (NMPlatform *platform,
+                                                int ifindex,
+                                                guint32 network,
+                                                int plen,
+                                                guint32 metric,
+                                                guint8 tos);
+
+const NMPlatformIP6Route *_nmtstp_assert_ip6_route_exists (const char *file,
+                                                           guint line,
+                                                           const char *func,
+                                                           NMPlatform *platform,
+                                                           int c_exists,
+                                                           const char *ifname,
+                                                           const struct in6_addr *network,
+                                                           guint plen,
+                                                           guint32 metric,
+                                                           const struct in6_addr *src,
+                                                           guint8 src_plen);
+#define nmtstp_assert_ip6_route_exists(platform, c_exists, ifname, network, plen, metric, src, src_plen) _nmtstp_assert_ip6_route_exists (__FILE__, __LINE__, G_STRFUNC, platform, c_exists, ifname, network, plen, metric, src, src_plen)
+
+const NMPlatformIP6Route *nmtstp_ip6_route_get (NMPlatform *platform,
+                                                int ifindex,
+                                                const struct in6_addr *network,
+                                                guint plen,
+                                                guint32 metric,
+                                                const struct in6_addr *src,
+                                                guint8 src_plen);
 
 /*****************************************************************************/
 
@@ -187,8 +223,36 @@ void nmtstp_ip6_route_add (NMPlatform *platform,
                            guint32 metric,
                            guint32 mss);
 
+static inline GPtrArray *
+nmtstp_ip4_route_get_all (NMPlatform *platform,
+                          int ifindex)
+{
+	return nm_platform_lookup_addrroute_clone (platform,
+	                                           NMP_OBJECT_TYPE_IP4_ROUTE,
+	                                           ifindex,
+	                                           nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
+	                                           NULL);
+}
+
+static inline GPtrArray *
+nmtstp_ip6_route_get_all (NMPlatform *platform,
+                          int ifindex)
+{
+	return nm_platform_lookup_addrroute_clone (platform,
+	                                           NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                           ifindex,
+	                                           nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel,
+	                                           NULL);
+}
+
 /*****************************************************************************/
 
+GArray *nmtstp_platform_ip4_address_get_all (NMPlatform *self, int ifindex);
+GArray *nmtstp_platform_ip6_address_get_all (NMPlatform *self, int ifindex);
+
+gboolean nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
+gboolean nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
+
 const NMPlatformLink *nmtstp_link_get_typed (NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type);
 const NMPlatformLink *nmtstp_link_get (NMPlatform *platform, int ifindex, const char *name);
 
@@ -197,6 +261,10 @@ void nmtstp_link_set_updown (NMPlatform *platform,
                              int ifindex,
                              gboolean up);
 
+const NMPlatformLink *nmtstp_link_veth_add (NMPlatform *platform,
+                                            gboolean external_command,
+                                            const char *name,
+                                            const char *peer);
 const NMPlatformLink *nmtstp_link_dummy_add (NMPlatform *platform,
                                              gboolean external_command,
                                              const char *name);
@@ -231,6 +299,100 @@ void nmtstp_link_del (NMPlatform *platform,
                       int ifindex,
                       const char *name);
 
+/*****************************************************************************/
+
+extern int NMTSTP_ENV1_IFINDEX;
+extern int NMTSTP_ENV1_EX;
+
+static inline void
+_nmtstp_env1_wrapper_setup (const NmtstTestData *test_data)
+{
+	int *p_ifindex;
+	gpointer p_ifup;
+
+	nmtst_test_data_unpack (test_data, &p_ifindex, NULL, NULL, NULL, &p_ifup);
+
+	g_assert (p_ifindex && *p_ifindex == -1);
+
+	_LOGT ("TEST[%s]: setup", test_data->testpath);
+
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert_cmpint (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL), ==, NM_PLATFORM_ERROR_SUCCESS);
+
+	*p_ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	g_assert_cmpint (*p_ifindex, >, 0);
+	g_assert_cmpint (NMTSTP_ENV1_IFINDEX, ==, -1);
+
+	if (GPOINTER_TO_INT (p_ifup))
+		g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, *p_ifindex, NULL));
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	NMTSTP_ENV1_IFINDEX = *p_ifindex;
+	NMTSTP_ENV1_EX = nmtstp_run_command_check_external_global ();
+}
+
+static inline void
+_nmtstp_env1_wrapper_run (gconstpointer user_data)
+{
+	const NmtstTestData *test_data = user_data;
+	GTestDataFunc test_func_data;
+	GTestFunc test_func;
+	gconstpointer d;
+
+	nmtst_test_data_unpack (test_data, NULL, &test_func, &test_func_data, &d, NULL);
+
+	_LOGT ("TEST[%s]: run", test_data->testpath);
+	if (test_func)
+		test_func ();
+	else
+		test_func_data (d);
+}
+
+static inline void
+_nmtstp_env1_wrapper_teardown (const NmtstTestData *test_data)
+{
+	int *p_ifindex;
+
+	nmtst_test_data_unpack (test_data, &p_ifindex, NULL, NULL, NULL, NULL);
+
+	g_assert_cmpint (NMTSTP_ENV1_IFINDEX, ==, *p_ifindex);
+	NMTSTP_ENV1_IFINDEX = -1;
+
+	_LOGT ("TEST[%s]: teardown", test_data->testpath);
+
+	g_assert_cmpint (*p_ifindex, ==, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
+	g_assert (nm_platform_link_delete (NM_PLATFORM_GET, *p_ifindex));
+
+	nm_platform_process_events (NM_PLATFORM_GET);
+
+	_LOGT ("TEST[%s]: finished", test_data->testpath);
+
+	*p_ifindex = -1;
+}
+
+/* add test function, that set's up a particular environment, consisting
+ * of a dummy device with ifindex NMTSTP_ENV1_IFINDEX. */
+#define _nmtstp_env1_add_test_func_full(testpath, test_func, test_data_func, arg, ifup) \
+	nmtst_add_test_func_full (testpath, \
+	                          _nmtstp_env1_wrapper_run, \
+	                          _nmtstp_env1_wrapper_setup, \
+	                          _nmtstp_env1_wrapper_teardown, \
+	                          ({ static int _ifindex = -1; &_ifindex; }), \
+	                          ({ GTestFunc _test_func = (test_func); _test_func; }), \
+	                          ({ GTestDataFunc _test_func = (test_data_func); _test_func; }), \
+	                          (arg), \
+	                          ({ gboolean _ifup = (ifup); GINT_TO_POINTER (_ifup);}))
+
+#define nmtstp_env1_add_test_func_data(testpath, test_func, arg, ifup) \
+	_nmtstp_env1_add_test_func_full(testpath, NULL, test_func, arg, ifup)
+
+#define nmtstp_env1_add_test_func(testpath, test_func, ifup) \
+	_nmtstp_env1_add_test_func_full(testpath, test_func, NULL, NULL, ifup)
+
+/*****************************************************************************/
+
 typedef void (*NMTstpSetupFunc) (void);
 extern NMTstpSetupFunc const _nmtstp_setup_platform_func;
 
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-general.c
index e772662c..342aa0d6 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-general.c
@@ -44,7 +44,7 @@ static void
 test_link_get_all (void)
 {
 	gs_unref_object NMPlatform *platform = NULL;
-	gs_unref_array GArray *links = NULL;
+	gs_unref_ptrarray GPtrArray *links = NULL;
 
 	platform = nm_linux_platform_new (TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT);
 
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index ed435567..9c72371c 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -76,7 +76,7 @@ test_bogus(void)
 	g_assert (!addrlen);
 	g_assert (!nm_platform_link_get_address (NM_PLATFORM_GET, BOGUS_IFINDEX, NULL));
 
-	g_assert (!nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU));
+	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX, MTU) != NM_PLATFORM_ERROR_SUCCESS);
 
 	g_assert (!nm_platform_link_get_mtu (NM_PLATFORM_GET, BOGUS_IFINDEX));
 
@@ -264,7 +264,8 @@ test_slave (int master, int type, SignalData *master_changed)
 	}
 	g_assert (!nm_platform_link_is_up (NM_PLATFORM_GET, ifindex));
 	g_assert (!nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex));
-	if (nm_platform_link_is_connected (NM_PLATFORM_GET, master)) {
+	if (   nmtstp_is_root_test ()
+	    && nm_platform_link_is_connected (NM_PLATFORM_GET, master)) {
 		if (nm_platform_link_get_type (NM_PLATFORM_GET, master) == NM_LINK_TYPE_TEAM) {
 			/* Older team versions (e.g. Fedora 17) have a bug that team master stays
 			 * IFF_LOWER_UP even if its slave is down. Double check it with iproute2 and if
@@ -285,7 +286,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	g_assert (nm_platform_link_is_connected (NM_PLATFORM_GET, master));
 	accept_signals (link_changed, 1, 3);
 	/* NM running, can cause additional change of addrgenmode */
-	accept_signals (master_changed, 1, 2);
+	accept_signals (master_changed, 0, 2);
 
 	/* Enslave again
 	 *
@@ -294,7 +295,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	ensure_no_signal (link_changed);
 	g_assert (nm_platform_link_enslave (NM_PLATFORM_GET, master, ifindex));
 	accept_signals (link_changed, 0, 2);
-	ensure_no_signal (master_changed);
+	accept_signals (master_changed, 0, 2);
 
 	/* Set slave option */
 	switch (type) {
@@ -327,7 +328,7 @@ test_slave (int master, int type, SignalData *master_changed)
 		ensure_no_signal (link_changed);
 		accept_signal (link_removed);
 	}
-	accept_signals (master_changed, 1, 2);
+	accept_signals (master_changed, 0, 2);
 
 	ensure_no_signal (master_changed);
 
@@ -511,7 +512,8 @@ test_bridge_addr (void)
 	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 	g_assert (plink);
 
-	if (nm_platform_check_support_user_ipv6ll (NM_PLATFORM_GET)) {
+	if (nm_platform_check_kernel_support (NM_PLATFORM_GET,
+	                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL)) {
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 
@@ -601,7 +603,7 @@ test_internal (void)
 	accept_signal (link_changed);
 
 	/* Set MTU */
-	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU));
+	g_assert (nm_platform_link_set_mtu (NM_PLATFORM_GET, ifindex, MTU) == NM_PLATFORM_ERROR_SUCCESS);
 	g_assert_cmpint (nm_platform_link_get_mtu (NM_PLATFORM_GET, ifindex), ==, MTU);
 	accept_signal (link_changed);
 
@@ -790,7 +792,7 @@ test_software_detect (gconstpointer user_data)
 		 * namespaced, the creation can fail if a macvtap in another namespace
 		 * has the same index. Try to detect this situation and skip already
 		 * used indexes.
-		 * http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=17af2bce88d31e65ed73d638bb752d2e13c66ced
+		 * The fix (17af2bce) is included kernel 4.7, dated 24 July, 2016.
 		 */
 		for (i = ifindex_parent + 1; i < ifindex_parent + 100; i++) {
 			snprintf (buf, sizeof (buf), "/sys/class/macvtap/tap%d", i);
@@ -1713,9 +1715,8 @@ test_nl_bugs_veth (void)
 	NMTstpNamespaceHandle *ns_handle = NULL;
 
 	/* create veth pair. */
-	nmtstp_run_command_check ("ip link add dev %s type veth peer name %s", IFACE_VETH0, IFACE_VETH1);
-	ifindex_veth0 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH0, NM_LINK_TYPE_VETH, 100)->ifindex;
-	ifindex_veth1 = nmtstp_assert_wait_for_link (NM_PLATFORM_GET, IFACE_VETH1, NM_LINK_TYPE_VETH, 100)->ifindex;
+	ifindex_veth0 = nmtstp_link_veth_add (NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex;
+	ifindex_veth1 = nmtstp_link_get_typed (NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex;
 
 	/* assert that nm_platform_link_veth_get_properties() returns the expected peer ifindexes. */
 	g_assert (nm_platform_link_veth_get_properties (NM_PLATFORM_GET, ifindex_veth0, &i));
@@ -1728,8 +1729,8 @@ test_nl_bugs_veth (void)
 	pllink_veth0 = nm_platform_link_get (NM_PLATFORM_GET, ifindex_veth0);
 	g_assert (pllink_veth0);
 	if (pllink_veth0->parent == 0) {
-		/* pre-4.1 kernels don't support exposing the veth peer as IFA_LINK. skip the remainder
-		 * of the test. */
+		/* Kernels prior to 4.1 dated 21 June, 2015 don't support exposing the veth peer
+		 * as IFA_LINK. skip the remainder of the test. */
 		goto out;
 	}
 	g_assert_cmpint (pllink_veth0->parent, ==, ifindex_veth1);
@@ -2023,8 +2024,8 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 	_sysctl_assert_eq (platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6", NULL);
 	_sysctl_assert_eq (platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6", NULL);
 
-	/* older kernels (Ubuntu 12.04) don't support ethtool -i for dummy devices. Work around that and
-	 * skip asserts that are known to fail. */
+	/* Kernels prior to 3.19 dated 8 February, 2015 don't support ethtool -i for dummy devices.
+	 * Work around that and skip asserts that are known to fail. */
 	ethtool_support = nmtstp_run_command ("ethtool -i dummy1_ > /dev/null") == 0;
 	if (ethtool_support) {
 		g_assert (nmp_utils_ethtool_get_driver_info (nmtstp_link_get_typed (platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, &driver_info));
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 42dfc572..a02388d2 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -33,6 +33,60 @@ struct {
 
 /*****************************************************************************/
 
+static void
+test_obj_base (void)
+{
+	static const union {
+		GObject g;
+		NMPObject k;
+	} x = { };
+	static const union {
+		GTypeClass k;
+		NMPClass c;
+	} l = { };
+	static const GObject *g = &x.g;
+	static const GTypeClass *k = &l.k;
+	static const NMPObject *o = &x.k;
+	static const NMPClass *c = &l.c;
+
+	NMObjBaseInst *obj;
+	gs_unref_object GCancellable *obj_cancellable = g_cancellable_new ();
+	nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link (10);
+
+#define STATIC_ASSERT(cond) \
+	G_STMT_START { \
+		G_STATIC_ASSERT (cond); \
+		G_STATIC_ASSERT_EXPR (cond); \
+		g_assert (cond); \
+	} G_STMT_END
+
+	STATIC_ASSERT (&g->g_type_instance              == (void *) &o->_class);
+	STATIC_ASSERT (&g->g_type_instance.g_class      == (void *) &o->_class);
+
+	STATIC_ASSERT (sizeof (o->parent.parent)        == sizeof (GTypeInstance));
+
+	STATIC_ASSERT (&c->parent                       == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type_class   == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type         == (void *) c);
+	STATIC_ASSERT (&c->parent.parent.g_type         == &k->g_type);
+
+	STATIC_ASSERT (sizeof (c->parent.parent)        == sizeof (GTypeClass));
+
+	STATIC_ASSERT (&o->parent                       == (void *) o);
+	STATIC_ASSERT (&o->parent.klass                 == (void *) &o->_class);
+
+	obj = (NMObjBaseInst *) obj_cancellable;
+	g_assert (!NMP_CLASS_IS_VALID ((NMPClass *) obj->klass));
+	g_assert (G_TYPE_CHECK_INSTANCE_TYPE (obj, G_TYPE_CANCELLABLE));
+
+	obj = (NMObjBaseInst *) obj_link;
+	g_assert (NMP_CLASS_IS_VALID ((NMPClass *) obj->klass));
+	g_assert (!G_TYPE_CHECK_INSTANCE_TYPE (obj, G_TYPE_CANCELLABLE));
+
+}
+
+/*****************************************************************************/
+
 static gboolean
 _nmp_object_id_equal (const NMPObject *a, const NMPObject *b)
 {
@@ -56,150 +110,144 @@ _nmp_object_equal (const NMPObject *a, const NMPObject *b)
 /*****************************************************************************/
 
 static void
-_assert_cache_multi_lookup_contains (const NMPCache *cache, const NMPCacheId *cache_id, const NMPObject *obj, gboolean contains)
+_assert_cache_multi_lookup_contains (const NMPCache *cache, const NMDedupMultiHeadEntry *head_entry, const NMPObject *obj, gboolean visible_only, gboolean contains)
 {
-	const NMPlatformObject *const *objects;
-	guint i, len;
+	NMDedupMultiIter iter;
 	gboolean found;
+	guint i, len;
+	const NMPObject *o;
 
-	g_assert (cache_id);
 	g_assert (NMP_OBJECT_IS_VALID (obj));
 
 	g_assert (nmp_cache_lookup_obj (cache, obj) == obj);
+	g_assert (!head_entry || (head_entry->len > 0 && c_list_length (&head_entry->lst_entries_head) == head_entry->len));
 
-	objects = nmp_cache_lookup_multi (cache, cache_id, &len);
-
-	g_assert ((len == 0 && !objects) || (len > 0 && objects && !objects[len]));
+	len = head_entry ? head_entry->len : 0;
 
 	found = FALSE;
-	for (i = 0; i < len; i++) {
-		NMPObject *o;
-
-		g_assert (objects[i]);
-		o = NMP_OBJECT_UP_CAST (objects[i]);
+	i = 0;
+	nmp_cache_iter_for_each (&iter,
+	                         head_entry,
+	                         &o) {
 		g_assert (NMP_OBJECT_IS_VALID (o));
-
 		if (obj == o) {
-			g_assert (!found);
-			found = TRUE;
+			if (   !visible_only
+			    || nmp_object_is_visible (o)) {
+				g_assert (!found);
+				found = TRUE;
+			}
 		}
+		i++;
 	}
 
+	g_assert (len == i);
 	g_assert (!!contains == found);
 }
 
-/*****************************************************************************/
+static void
+_assert_cache_multi_lookup_contains_link (const NMPCache *cache,
+                                          gboolean visible_only,
+                                          const NMPObject *obj,
+                                          gboolean contains)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	NMPLookup lookup;
 
-typedef struct {
-	NMPCache *cache;
-	NMPCacheOpsType expected_ops_type;
-	const NMPObject *obj_clone;
-	NMPObject *new_clone;
-	gboolean was_visible;
-	gboolean called;
-} _NMPCacheUpdateData;
+	g_assert (cache);
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK);
+	head_entry = nmp_cache_lookup (cache, &lookup);
+	_assert_cache_multi_lookup_contains (cache, head_entry, obj, visible_only, contains);
+}
+
+/*****************************************************************************/
 
 static void
-_nmp_cache_update_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data)
+ops_post_check (NMPCache *cache,
+                NMPCacheOpsType ops_type,
+                const NMPObject *obj_old,
+                const NMPObject *obj_new,
+                const NMPObject *obj_new_expected,
+                NMPCacheOpsType expected_ops_type)
 {
-	_NMPCacheUpdateData *data = user_data;
-
-	g_assert (data);
-	g_assert (!data->called);
-	g_assert (data->cache == cache);
+	g_assert (cache);
 
-	g_assert_cmpint (data->expected_ops_type, ==, ops_type);
+	g_assert_cmpint (expected_ops_type, ==, ops_type);
 
 	switch (ops_type) {
 	case NMP_CACHE_OPS_ADDED:
-		g_assert (!old);
-		g_assert (NMP_OBJECT_IS_VALID (new));
-		g_assert (nmp_object_is_alive (new));
-		g_assert (nmp_object_id_equal (data->obj_clone, new));
-		g_assert (nmp_object_equal (data->obj_clone, new));
+		g_assert (!obj_old);
+		g_assert (NMP_OBJECT_IS_VALID (obj_new));
+		g_assert (nmp_object_is_alive (obj_new));
+		g_assert (nmp_object_id_equal (obj_new_expected, obj_new));
+		g_assert (nmp_object_equal (obj_new_expected, obj_new));
 		break;
 	case NMP_CACHE_OPS_UPDATED:
-		g_assert (NMP_OBJECT_IS_VALID (old));
-		g_assert (NMP_OBJECT_IS_VALID (new));
-		g_assert (nmp_object_is_alive (old));
-		g_assert (nmp_object_is_alive (new));
-		g_assert (nmp_object_id_equal (data->obj_clone, new));
-		g_assert (nmp_object_id_equal (data->obj_clone, old));
-		g_assert (nmp_object_id_equal (old, new));
-		g_assert (nmp_object_equal (data->obj_clone, new));
-		g_assert (!nmp_object_equal (data->obj_clone, old));
-		g_assert (!nmp_object_equal (old, new));
+		g_assert (obj_old != obj_new);
+		g_assert (NMP_OBJECT_IS_VALID (obj_old));
+		g_assert (NMP_OBJECT_IS_VALID (obj_new));
+		g_assert (nmp_object_is_alive (obj_old));
+		g_assert (nmp_object_is_alive (obj_new));
+		g_assert (nmp_object_id_equal (obj_new_expected, obj_new));
+		g_assert (nmp_object_id_equal (obj_new_expected, obj_old));
+		g_assert (nmp_object_id_equal (obj_old, obj_new));
+		g_assert (nmp_object_equal (obj_new_expected, obj_new));
+		g_assert (!nmp_object_equal (obj_new_expected, obj_old));
+		g_assert (!nmp_object_equal (obj_old, obj_new));
 		break;
 	case NMP_CACHE_OPS_REMOVED:
-		g_assert (!new);
-		g_assert (NMP_OBJECT_IS_VALID (old));
-		g_assert (nmp_object_is_alive (old));
-		g_assert (nmp_object_id_equal (data->obj_clone, old));
+		g_assert (!obj_new);
+		g_assert (NMP_OBJECT_IS_VALID (obj_old));
+		g_assert (nmp_object_is_alive (obj_old));
+		if (obj_new_expected)
+			g_assert (nmp_object_id_equal (obj_new_expected, obj_old));
+		break;
+	case NMP_CACHE_OPS_UNCHANGED:
+		g_assert (obj_old == obj_new);
+		if (obj_old) {
+			g_assert (NMP_OBJECT_IS_VALID (obj_old));
+			g_assert (nmp_object_is_alive (obj_old));
+			g_assert (nmp_object_equal (obj_old, obj_new));
+			g_assert (nmp_object_id_equal (obj_new_expected, obj_new));
+		} else
+			g_assert (!obj_new_expected);
 		break;
 	default:
 		g_assert_not_reached ();
 	}
-
-	data->was_visible = old ? nmp_object_is_visible (old) : FALSE;
-	data->new_clone = new ? nmp_object_clone (new, FALSE) : NULL;
-	data->called = TRUE;
 }
 
 static void
-_nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCacheOpsType expected_ops_type)
+_nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, const NMPObject **out_obj_old, const NMPObject **out_obj_new, NMPCacheOpsType expected_ops_type)
 {
 	NMPCacheOpsType ops_type;
-	NMPObject *obj2;
-	gboolean was_visible;
-	nm_auto_nmpobj NMPObject *obj_clone = nmp_object_clone (obj, FALSE);
-	nm_auto_nmpobj NMPObject *new_clone = NULL;
+	const NMPObject *obj_prev;
 	const NMPObject *obj_old;
-	_NMPCacheUpdateData data = {
-		.cache = cache,
-		.expected_ops_type = expected_ops_type,
-		.obj_clone = obj_clone,
-	};
-
-	obj_old = nmp_cache_lookup_link (cache, obj->object.ifindex);
-	if (obj_old && obj_old->_link.udev.device)
-		obj_clone->_link.udev.device = udev_device_ref (obj_old->_link.udev.device);
-	_nmp_object_fixup_link_udev_fields (obj_clone, nmp_cache_use_udev_get (cache));
+	const NMPObject *obj_new;
+	nm_auto_nmpobj NMPObject *obj_new_expected = NULL;
 
 	g_assert (cache);
 	g_assert (NMP_OBJECT_IS_VALID (obj));
 
-	ops_type = nmp_cache_update_netlink (cache, obj, &obj2, &was_visible, _nmp_cache_update_hook, &data);
-
-	new_clone = data.new_clone;
-
-	g_assert_cmpint (ops_type, ==, expected_ops_type);
+	obj_prev = nmp_cache_lookup_link (cache, obj->object.ifindex);
+	obj_new_expected = nmp_object_clone (obj, FALSE);
+	if (obj_prev && obj_prev->_link.udev.device)
+		obj_new_expected->_link.udev.device = udev_device_ref (obj_prev->_link.udev.device);
+	_nmp_object_fixup_link_udev_fields (&obj_new_expected, NULL, nmp_cache_use_udev_get (cache));
 
-	if (ops_type != NMP_CACHE_OPS_UNCHANGED) {
-		g_assert (NMP_OBJECT_IS_VALID (obj2));
-		g_assert (data.called);
-		g_assert_cmpint (data.was_visible, ==, was_visible);
+	ops_type = nmp_cache_update_netlink (cache, obj, FALSE, &obj_old, &obj_new);
+	ops_post_check (cache, ops_type, obj_old, obj_new,
+	                nmp_object_is_alive (obj_new_expected) ? obj_new_expected : NULL,
+	                expected_ops_type);
 
-		if (ops_type == NMP_CACHE_OPS_REMOVED)
-			g_assert (!data.new_clone);
-		else {
-			g_assert (data.new_clone);
-			g_assert (nmp_object_equal (obj2, data.new_clone));
-		}
-	} else {
-		g_assert (!data.called);
-		g_assert (!obj2 || was_visible == nmp_object_is_visible (obj2));
-	}
-
-	g_assert (!obj2 || nmp_object_id_equal (obj, obj2));
-	if (ops_type != NMP_CACHE_OPS_REMOVED && obj2)
-		g_assert (nmp_object_equal (obj, obj2));
-
-	if (out_obj)
-		*out_obj = obj2;
+	if (out_obj_new)
+		*out_obj_new = obj_new;
 	else
-		nmp_object_unref (obj2);
-	if (out_was_visible)
-		*out_was_visible = was_visible;
+		nmp_object_unref (obj_new);
+	if (out_obj_old)
+		*out_obj_old = obj_old;
+	else
+		nmp_object_unref (obj_old);
 }
 
 static const NMPlatformLink pl_link_2 = {
@@ -218,168 +266,189 @@ static void
 test_cache_link (void)
 {
 	NMPCache *cache;
-	NMPObject *obj1, *obj2;
+	NMPObject *objm1;
+	const NMPObject *obj_old, *obj_new;
 	NMPObject objs1;
-	gboolean was_visible;
-	NMPCacheId cache_id_storage;
 	struct udev_device *udev_device_2 = g_list_nth_data (global.udev_devices, 0);
 	struct udev_device *udev_device_3 = g_list_nth_data (global.udev_devices, 0);
 	NMPCacheOpsType ops_type;
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL;
+
+	multi_idx = nm_dedup_multi_index_new ();
 
-	cache = nmp_cache_new (nmtst_get_rand_int () % 2);
+	cache = nmp_cache_new (multi_idx, nmtst_get_rand_int () % 2);
 
 	/* if we have a link, and don't set is_in_netlink, adding it has no effect. */
-	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
-	g_assert (NMP_OBJECT_UP_CAST (&obj1->object) == obj1);
-	g_assert (!nmp_object_is_alive (obj1));
-	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	g_assert (NMP_OBJECT_UP_CAST (&objm1->object) == objm1);
+	g_assert (!nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED);
 	ASSERT_nmp_cache_is_consistent (cache);
-	g_assert (!obj2);
-	g_assert (!was_visible);
-	g_assert (!nmp_cache_lookup_obj (cache, obj1));
+	g_assert (!obj_old);
+	g_assert (!obj_new);
+	g_assert (!nmp_cache_lookup_obj (cache, objm1));
 	g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)));
-	nmp_object_unref (obj1);
+	nmp_object_unref (objm1);
 
 	/* Only when setting @is_in_netlink the link is added. */
-	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
-	obj1->_link.netlink.is_in_netlink = TRUE;
-	g_assert (nmp_object_is_alive (obj1));
-	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_ADDED);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_ADDED);
 	ASSERT_nmp_cache_is_consistent (cache);
-	g_assert (nmp_object_equal (obj1, obj2));
-	g_assert (!was_visible);
-	g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
-	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
-	g_assert (nmp_object_is_visible (obj2));
-	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
-	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
-	nmp_object_unref (obj1);
-	nmp_object_unref (obj2);
+	g_assert (!obj_old);
+	g_assert (obj_new);
+	g_assert (objm1 == obj_new);
+	g_assert (nmp_object_equal (objm1, obj_new));
+	g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+	g_assert (nmp_object_is_visible (obj_new));
+	_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+	_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_new);
 
 	/* updating the same link with identical value, has no effect. */
-	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
-	obj1->_link.netlink.is_in_netlink = TRUE;
-	g_assert (nmp_object_is_alive (obj1));
-	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED);
 	ASSERT_nmp_cache_is_consistent (cache);
-	g_assert (obj2 != obj1);
-	g_assert (nmp_object_equal (obj1, obj2));
-	g_assert (was_visible);
-	g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
-	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
-	nmp_object_unref (obj1);
-	nmp_object_unref (obj2);
+	g_assert (obj_old);
+	g_assert (obj_new);
+	g_assert (obj_new != objm1);
+	g_assert (nmp_object_equal (objm1, obj_new));
+	g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_new);
+	nmp_object_unref (obj_new);
 
 	/* remove the link from netlink */
-	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
-	g_assert (!nmp_object_is_alive (obj1));
-	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_REMOVED);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	g_assert (!nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_REMOVED);
 	ASSERT_nmp_cache_is_consistent (cache);
-	g_assert (obj2 != obj1);
-	g_assert (was_visible);
-	g_assert (!nmp_cache_lookup_obj (cache, obj1));
+	g_assert (obj_old);
+	g_assert (!obj_new);
+	g_assert (!nmp_cache_lookup_obj (cache, objm1));
 	g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)));
-	nmp_object_unref (obj1);
-	nmp_object_unref (obj2);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_old);
+	nmp_object_unref (obj_new);
 
 	if (udev_device_2) {
 		/* now add the link only with aspect UDEV. */
-		ops_type = nmp_cache_update_link_udev (cache, pl_link_2.ifindex, udev_device_2, &obj2, &was_visible, NULL, NULL);
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_2.ifindex, udev_device_2, &obj_old, &obj_new);
 		ASSERT_nmp_cache_is_consistent (cache);
 		g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED);
-		g_assert (!was_visible);
-		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
-		g_assert (!nmp_object_is_visible (obj2));
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, FALSE);
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
-		nmp_object_unref (obj2);
+		g_assert (!obj_old);
+		g_assert (obj_new);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+		g_assert (!nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, FALSE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		nmp_object_unref (obj_new);
 	}
 
 	/* add it in netlink too. */
-	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
-	obj1->_link.netlink.is_in_netlink = TRUE;
-	g_assert (nmp_object_is_alive (obj1));
-	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = TRUE;
+	g_assert (nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED);
 	ASSERT_nmp_cache_is_consistent (cache);
-	g_assert (nmp_object_equal (obj1, obj2));
-	g_assert (!was_visible);
-	g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
-	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
-	g_assert (nmp_object_is_visible (obj2));
-	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
-	_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
-	nmp_object_unref (obj1);
-	nmp_object_unref (obj2);
+	if (udev_device_2) {
+		g_assert (obj_old);
+		g_assert (!nmp_object_is_visible (obj_old));
+	} else
+		g_assert (!obj_old);
+	g_assert (nmp_object_equal (objm1, obj_new));
+	g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+	g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+	g_assert (nmp_object_is_visible (obj_new));
+	_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+	_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_old);
+	nmp_object_unref (obj_new);
 
 	/* remove again from netlink. */
-	obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
-	obj1->_link.netlink.is_in_netlink = FALSE;
-	g_assert (!nmp_object_is_alive (obj1));
-	_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED);
+	objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2);
+	objm1->_link.netlink.is_in_netlink = FALSE;
+	g_assert (!nmp_object_is_alive (objm1));
+	_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED);
 	ASSERT_nmp_cache_is_consistent (cache);
-	g_assert (obj2 != obj1);
-	g_assert (was_visible);
+	if (udev_device_2)
+		g_assert (obj_new == objm1);
+	else
+		g_assert (!obj_new);
+	g_assert (obj_old);
+	g_assert (nmp_object_is_alive (obj_old));
 	if (udev_device_2) {
-		g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
-		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2);
-		g_assert (!nmp_object_is_visible (obj2));
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, FALSE);
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
+		g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj_new);
+		g_assert (!nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, FALSE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
 	} else {
-		g_assert (nmp_cache_lookup_obj (cache, obj1) == NULL);
+		g_assert (nmp_cache_lookup_obj (cache, objm1) == NULL);
 		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == NULL);
-		g_assert (nmp_object_is_visible (obj2));
+		g_assert (nmp_object_is_visible (obj_new));
 	}
-	nmp_object_unref (obj1);
-	nmp_object_unref (obj2);
+	nmp_object_unref (objm1);
+	nmp_object_unref (obj_old);
+	nmp_object_unref (obj_new);
 
 	/* now another link only with aspect UDEV. */
 	if (udev_device_3) {
 		/* now add the link only with aspect UDEV. */
-		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, udev_device_3, &obj2, &was_visible, NULL, NULL);
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, udev_device_3, &obj_old, &obj_new);
 		g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED);
 		ASSERT_nmp_cache_is_consistent (cache);
-		g_assert (NMP_OBJECT_IS_VALID (obj2));
-		g_assert (!was_visible);
-		g_assert (!nmp_object_is_visible (obj2));
-		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, FALSE);
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
-		g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, FALSE);
-		g_assert_cmpint (obj2->link.initialized, ==, FALSE);
-		nmp_object_unref (obj2);
+		g_assert (NMP_OBJECT_IS_VALID (obj_new));
+		g_assert (!obj_old);
+		g_assert (!nmp_object_is_visible (obj_new));
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj_new);
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, FALSE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		g_assert_cmpint (obj_new->_link.netlink.is_in_netlink, ==, FALSE);
+		g_assert_cmpint (obj_new->link.initialized, ==, FALSE);
+		nmp_object_unref (obj_new);
 
 		/* add it in netlink too. */
-		obj1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3);
-		obj1->_link.netlink.is_in_netlink = TRUE;
-		g_assert (nmp_object_is_alive (obj1));
-		_nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UPDATED);
+		objm1 = nmp_object_new (NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3);
+		objm1->_link.netlink.is_in_netlink = TRUE;
+		g_assert (nmp_object_is_alive (objm1));
+		_nmp_cache_update_netlink (cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UPDATED);
 		ASSERT_nmp_cache_is_consistent (cache);
-		g_assert (obj2 != obj1);
-		g_assert (nmp_object_equal (obj1, obj2));
-		g_assert (!was_visible);
-		g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2);
-		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
-		g_assert (nmp_object_is_visible (obj2));
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
-		g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE);
-		g_assert_cmpint (obj2->link.initialized, ==, TRUE);
-		nmp_object_unref (obj1);
-		nmp_object_unref (obj2);
+		g_assert (obj_old);
+		g_assert (obj_new == objm1);
+		g_assert (nmp_object_equal (objm1, obj_new));
+		g_assert (!obj_old || !nmp_object_is_visible (obj_old));
+		g_assert (nmp_cache_lookup_obj (cache, objm1) == obj_new);
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj_new);
+		g_assert (nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		g_assert_cmpint (obj_new->_link.netlink.is_in_netlink, ==, TRUE);
+		g_assert_cmpint (obj_new->link.initialized, ==, TRUE);
+		nmp_object_unref (objm1);
+		nmp_object_unref (obj_old);
+		nmp_object_unref (obj_new);
 
 		/* remove UDEV. */
-		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, NULL, &obj2, &was_visible, NULL, NULL);
+		ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, NULL, &obj_old, &obj_new);
 		g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_UPDATED);
 		ASSERT_nmp_cache_is_consistent (cache);
-		g_assert (was_visible);
-		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2);
-		g_assert (nmp_object_is_visible (obj2));
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, TRUE), obj2, TRUE);
-		_assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_object_type (&cache_id_storage, NMP_OBJECT_TYPE_LINK, FALSE), obj2, TRUE);
-		g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE);
-		g_assert_cmpint (obj2->link.initialized, ==, !nmp_cache_use_udev_get (cache));
-		nmp_object_unref (obj2);
+		g_assert (obj_old && nmp_object_is_visible (obj_old));
+		g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj_new);
+		g_assert (nmp_object_is_visible (obj_new));
+		_assert_cache_multi_lookup_contains_link (cache, TRUE, obj_new, TRUE);
+		_assert_cache_multi_lookup_contains_link (cache, FALSE, obj_new, TRUE);
+		g_assert_cmpint (obj_new->_link.netlink.is_in_netlink, ==, TRUE);
+		g_assert_cmpint (obj_new->link.initialized, ==, !nmp_cache_use_udev_get (cache));
+		nmp_object_unref (obj_new);
+		nmp_object_unref (obj_old);
 	}
 
 	nmp_cache_free (cache);
@@ -429,6 +498,7 @@ main (int argc, char **argv)
 		udev_enumerate_unref (enumerator);
 	}
 
+	g_test_add_func ("/nmp-object/obj-base", test_obj_base);
 	g_test_add_func ("/nmp-object/cache_link", test_cache_link);
 
 	result = g_test_run ();
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 9960d867..2c00fada 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -27,13 +27,50 @@
 
 #include "test-common.h"
 
-#define DEVICE_NAME "nm-test-device"
+#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX
+#define EX             NMTSTP_ENV1_EX
+
+static void
+_wait_for_ipv6_addr_non_tentative (NMPlatform *platform,
+                                   gint64 timeout_ms,
+                                   int ifindex,
+                                   guint addr_n,
+                                   const struct in6_addr *addrs)
+{
+	guint i;
+
+	/* Wait that the addresses become non-tentative.  Dummy interfaces are NOARP
+	 * and thus don't do DAD, but the kernel sets the address as tentative for a
+	 * small amount of time, which prevents the immediate addition of the route
+	 * with RTA_PREFSRC */
+
+	NMTST_WAIT_ASSERT (400, {
+		gboolean should_wait = FALSE;
+		const NMPlatformIP6Address *plt_addr;
+
+		for (i = 0; i < addr_n; i++) {
+			plt_addr = nm_platform_ip6_address_get (platform, ifindex, addrs[i]);
+			if (   !plt_addr
+			    || NM_FLAGS_HAS (plt_addr->n_ifa_flags, IFA_F_TENTATIVE)) {
+				should_wait = TRUE;
+				break;
+			}
+		}
+		if (!should_wait)
+			return;
+		nmtstp_assert_wait_for_signal (platform,
+		                               (nmtst_wait_end_us - g_get_monotonic_time ()) / 1000);
+	});
+}
+
 
 static void
 ip4_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMPlatformIP4Route *received, int change_type_i, SignalData *data)
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMPObject o_id;
+	nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
 
 	g_assert_cmpint (obj_type, ==, NMP_OBJECT_TYPE_IP4_ROUTE);
 	g_assert (received);
@@ -41,6 +78,11 @@ ip4_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMP
 	g_assert (data && data->name);
 	g_assert_cmpstr (data->name, ==, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED);
 
+	/* run code for initializing the ID only */
+	nmp_object_stackinit_id (&o_id, NMP_OBJECT_UP_CAST (received));
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), TRUE);
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), FALSE);
+
 	if (data->ifindex && data->ifindex != received->ifindex)
 		return;
 	if (data->change_type != change_type)
@@ -58,6 +100,8 @@ ip6_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMP
 {
 	const NMPObjectType obj_type = obj_type_i;
 	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMPObject o_id;
+	nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
 
 	g_assert_cmpint (obj_type, ==, NMP_OBJECT_TYPE_IP6_ROUTE);
 	g_assert (received);
@@ -65,6 +109,11 @@ ip6_route_callback (NMPlatform *platform, int obj_type_i, int ifindex, const NMP
 	g_assert (data && data->name);
 	g_assert_cmpstr (data->name, ==, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED);
 
+	/* run code for initializing the ID only */
+	nmp_object_stackinit_id (&o_id, NMP_OBJECT_UP_CAST (received));
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), TRUE);
+	nmp_object_copy (o_id_p, NMP_OBJECT_UP_CAST (received), FALSE);
+
 	if (data->ifindex && data->ifindex != received->ifindex)
 		return;
 	if (data->change_type != change_type)
@@ -90,50 +139,50 @@ test_ip4_route_metric0 (void)
 	int mss = 1000;
 
 	/* No routes initially */
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 
 	/* add the first route */
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, metric, mss);
 	accept_signal (route_added);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Deleting route with metric 0 does nothing */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
 	ensure_no_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* add the second route */
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, INADDR_ANY, 0, 0, mss);
 	accept_signal (route_added);
 
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Delete route with metric 0 */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
 	accept_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Delete route with metric 0 again (we expect nothing to happen) */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, 0));
 	ensure_no_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE,  DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 1,  DEVICE_NAME, network, plen, metric, 0);
 
 	/* Delete the other route */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 	accept_signal (route_removed);
 
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, 0);
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, 0, 0);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 
 	free_signal (route_added);
 	free_signal (route_changed);
@@ -147,7 +196,7 @@ test_ip4_route (void)
 	SignalData *route_added = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip4_route_callback);
 	SignalData *route_changed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip4_route_callback);
 	SignalData *route_removed = add_signal (NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip4_route_callback);
-	GArray *routes;
+	GPtrArray *routes;
 	NMPlatformIP4Route rts[3];
 	in_addr_t network;
 	guint8 plen = 24;
@@ -164,9 +213,9 @@ test_ip4_route (void)
 	accept_signal (route_added);
 
 	/* Add route */
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, 0, metric, mss);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE, DEVICE_NAME, network, plen, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 1, DEVICE_NAME, network, plen, metric, 0);
 	accept_signal (route_added);
 
 	/* Add route again */
@@ -174,9 +223,9 @@ test_ip4_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Add default route */
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, 0, 0, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, 0, 0, metric, 0);
 	nmtstp_ip4_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, 0, 0, gateway, 0, metric, mss);
-	nmtstp_assert_ip4_route_exists (NULL, TRUE, DEVICE_NAME, 0, 0, metric);
+	nmtstp_assert_ip4_route_exists (NULL, 1, DEVICE_NAME, 0, 0, metric, 0);
 	accept_signal (route_added);
 
 	/* Add default route again */
@@ -184,7 +233,7 @@ test_ip4_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Test route listing */
-	routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	routes = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
 	memset (rts, 0, sizeof (rts));
 	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].network = gateway;
@@ -211,23 +260,23 @@ test_ip4_route (void)
 	rts[2].mss = mss;
 	rts[2].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE);
 	g_assert_cmpint (routes->len, ==, 3);
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, rts, routes->len, TRUE);
-	g_array_unref (routes);
+	nmtst_platform_ip4_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
+	g_ptr_array_unref (routes);
 
 	/* Remove route */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
-	nmtstp_assert_ip4_route_exists (NULL, FALSE, DEVICE_NAME, network, plen, metric);
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	nmtstp_assert_ip4_route_exists (NULL, 0, DEVICE_NAME, network, plen, metric, 0);
 	accept_signal (route_removed);
 
 	/* Remove route again */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 
 	/* Remove default route */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, 0, 0, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, 0, 0, metric));
 	accept_signal (route_removed);
 
 	/* Remove route to gateway */
-	g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, gateway, 32, metric));
+	g_assert (nmtstp_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, gateway, 32, metric));
 	accept_signal (route_removed);
 
 	free_signal (route_added);
@@ -242,7 +291,7 @@ test_ip6_route (void)
 	SignalData *route_added = add_signal (NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_ADDED, ip6_route_callback);
 	SignalData *route_changed = add_signal (NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_CHANGED, ip6_route_callback);
 	SignalData *route_removed = add_signal (NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NM_PLATFORM_SIGNAL_REMOVED, ip6_route_callback);
-	GArray *routes;
+	GPtrArray *routes;
 	NMPlatformIP6Route rts[3];
 	struct in6_addr network;
 	guint8 plen = 64;
@@ -259,28 +308,16 @@ test_ip6_route (void)
 	                                       NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0));
 	accept_signals (route_added, 0, 1);
 
-	/* Wait that the address becomes non-tentative.  Dummy interfaces are NOARP
-	 * and thus don't do DAD, but the kernel sets the address as tentative for a
-	 * small amount of time, which prevents the immediate addition of the route
-	 * with RTA_PREFSRC */
-	NMTST_WAIT_ASSERT (200, {
-		const NMPlatformIP6Address *plt_addr;
-
-		nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
-		nm_platform_process_events (NM_PLATFORM_GET);
-		plt_addr = nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, pref_src, 128);
-		if (plt_addr && !NM_FLAGS_HAS (plt_addr->n_ifa_flags, IFA_F_TENTATIVE))
-			break;
-	});
+	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 200, ifindex, 1, &pref_src);
 
 	/* Add route to gateway */
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, gateway, 128, in6addr_any, in6addr_any, metric, mss);
 	accept_signal (route_added);
 
 	/* Add route */
-	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (!nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0));
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, network, plen, gateway, pref_src, metric, mss);
-	g_assert (nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0));
 	accept_signal (route_added);
 
 	/* Add route again */
@@ -288,9 +325,9 @@ test_ip6_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Add default route */
-	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
+	g_assert (!nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0));
 	nmtstp_ip6_route_add (NM_PLATFORM_GET, ifindex, NM_IP_CONFIG_SOURCE_USER, in6addr_any, 0, gateway, in6addr_any, metric, mss);
-	g_assert (nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
+	g_assert (nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0));
 	accept_signal (route_added);
 
 	/* Add default route again */
@@ -298,7 +335,7 @@ test_ip6_route (void)
 	accept_signals (route_changed, 0, 1);
 
 	/* Test route listing */
-	routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	routes = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, ifindex);
 	memset (rts, 0, sizeof (rts));
 	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].network = gateway;
@@ -325,23 +362,23 @@ test_ip6_route (void)
 	rts[2].metric = nm_utils_ip6_route_metric_normalize (metric);
 	rts[2].mss = mss;
 	g_assert_cmpint (routes->len, ==, 3);
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, rts, routes->len, TRUE);
-	g_array_unref (routes);
+	nmtst_platform_ip6_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
+	g_ptr_array_unref (routes);
 
 	/* Remove route */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
-	g_assert (!nm_platform_ip6_route_get (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (!nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0));
 	accept_signal (route_removed);
 
 	/* Remove route again */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, plen, metric));
 
 	/* Remove default route */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric));
 	accept_signal (route_removed);
 
 	/* Remove route to gateway */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, gateway, 128, metric));
+	g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, gateway, 128, metric));
 	accept_signal (route_removed);
 
 	free_signal (route_added);
@@ -352,6 +389,45 @@ test_ip6_route (void)
 /*****************************************************************************/
 
 static void
+test_ip_route_get (void)
+{
+	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	in_addr_t a;
+	NMPlatformError result;
+	nm_auto_nmpobj NMPObject *route = NULL;
+	const NMPlatformIP4Route *r;
+
+	nmtstp_run_command_check ("ip route add 1.2.3.0/24 dev %s", DEVICE_NAME);
+
+	NMTST_WAIT_ASSERT (100, {
+		nmtstp_wait_for_signal (NM_PLATFORM_GET, 10);
+		if (nmtstp_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.0"), 24, 0, 0))
+			break;
+	});
+
+	a = nmtst_inet4_from_string ("1.2.3.1");
+	result = nm_platform_ip_route_get (NM_PLATFORM_GET,
+	                                   AF_INET,
+	                                   &a,
+	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
+	                                   &route);
+
+	g_assert (result == NM_PLATFORM_ERROR_SUCCESS);
+	g_assert (NMP_OBJECT_GET_TYPE (route) == NMP_OBJECT_TYPE_IP4_ROUTE);
+	g_assert (!NMP_OBJECT_IS_STACKINIT (route));
+	g_assert (route->parent._ref_count == 1);
+	r = NMP_OBJECT_CAST_IP4_ROUTE (route);
+	g_assert (r->rt_cloned);
+	g_assert (r->ifindex == ifindex);
+	g_assert (r->network == a);
+	g_assert (r->plen == 32);
+
+	nmtstp_run_command_check ("ip route flush dev %s", DEVICE_NAME);
+
+	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
+}
+
+static void
 test_ip4_zero_gateway (void)
 {
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
@@ -361,15 +437,14 @@ test_ip4_zero_gateway (void)
 
 	NMTST_WAIT_ASSERT (100, {
 		nmtstp_wait_for_signal (NM_PLATFORM_GET, 10);
-		if (   nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.1"), 32, 0)
-		    && nm_platform_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.2"), 32, 0))
+		if (   nmtstp_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.1"), 32, 0, 0)
+		    && nmtstp_ip4_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet4_from_string ("1.2.3.2"), 32, 0, 0))
 			break;
 	});
 
 	nmtstp_run_command_check ("ip route flush dev %s", DEVICE_NAME);
 
 	nmtstp_wait_for_signal (NM_PLATFORM_GET, 50);
-	nm_platform_process_events (NM_PLATFORM_GET);
 }
 
 static void
@@ -378,7 +453,7 @@ test_ip4_route_options (void)
 	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
 	NMPlatformIP4Route route = { };
 	in_addr_t network;
-	GArray *routes;
+	GPtrArray *routes;
 	NMPlatformIP4Route rts[1];
 
 	inet_pton (AF_INET, "172.16.1.0", &network);
@@ -396,12 +471,10 @@ test_ip4_route_options (void)
 	route.mtu = 1350;
 	route.lock_cwnd = TRUE;
 
-	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, &route));
+	g_assert (nm_platform_ip4_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &route) == NM_PLATFORM_ERROR_SUCCESS);
 
 	/* Test route listing */
-	routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET, ifindex,
-	                                        NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT |
-	                                        NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	routes = nmtstp_ip4_route_get_all (NM_PLATFORM_GET, ifindex);
 	memset (rts, 0, sizeof (rts));
 	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[0].scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK);
@@ -416,69 +489,268 @@ test_ip4_route_options (void)
 	rts[0].initrwnd = 50;
 	rts[0].mtu = 1350;
 	rts[0].lock_cwnd = TRUE;
-
 	g_assert_cmpint (routes->len, ==, 1);
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, rts, routes->len, TRUE);
+	nmtst_platform_ip4_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
 
 	/* Remove route */
-	/* FIXME. Due to a bug, we cannot delete routes with non-zero TOS. See bgo#785004. */
-	//g_assert (nm_platform_ip4_route_delete (NM_PLATFORM_GET, ifindex, network, 24, 20));
+	g_assert (nm_platform_ip_route_delete (NM_PLATFORM_GET, routes->pdata[0]));
 
-	g_array_unref (routes);
+	g_ptr_array_unref (routes);
 }
 
 
 static void
-test_ip6_route_options (void)
+test_ip6_route_options (gconstpointer test_data)
 {
-	int ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
-	NMPlatformIP6Route route = { };
-	struct in6_addr network;
-	GArray *routes;
-	NMPlatformIP6Route rts[3];
-
-	inet_pton (AF_INET6, "2001:db8:a:b:0:0:0:0", &network);
-
-	route.ifindex = ifindex;
-	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	route.network = network;
-	route.plen = 64;
-	route.gateway = in6addr_any;
-	route.metric = 1024;
-	route.window = 20000;
-	route.cwnd = 8;
-	route.initcwnd = 22;
-	route.initrwnd = 33;
-	route.mtu = 1300;
-	route.lock_mtu = TRUE;
-
-	g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, &route));
-
-	/* Test route listing */
-	routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET, ifindex,
-	                                        NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT |
-	                                        NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-	memset (rts, 0, sizeof (rts));
-	rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	rts[0].network = network;
-	rts[0].plen = 64;
-	rts[0].ifindex = ifindex;
-	rts[0].gateway = in6addr_any;
-	rts[0].metric = 1024;
-	rts[0].window = 20000;
-	rts[0].cwnd = 8;
-	rts[0].initcwnd = 22;
-	rts[0].initrwnd = 33;
-	rts[0].mtu = 1300;
-	rts[0].lock_mtu = TRUE;
-
-	g_assert_cmpint (routes->len, ==, 1);
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, rts, routes->len, TRUE);
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const int IFINDEX = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	GPtrArray *routes;
+#define RTS_MAX 3
+	NMPlatformIP6Route rts_add[RTS_MAX] = { };
+	NMPlatformIP6Route rts_cmp[RTS_MAX] = { };
+	NMPlatformIP6Address addr[1] = { };
+	struct in6_addr addr_in6[G_N_ELEMENTS (addr)] = { };
+	guint rts_n = 0;
+	guint addr_n = 0;
+	guint i;
+
+	switch (TEST_IDX) {
+	case 1:
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:a:b:0:0:0:0"),
+			.plen = 64,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.window = 20000,
+			.cwnd = 8,
+			.initcwnd = 22,
+			.initrwnd = 33,
+			.mtu = 1300,
+			.lock_mtu = TRUE,
+		});
+		break;
+	case 2:
+		addr[addr_n++] = ((NMPlatformIP6Address) {
+			.ifindex = IFINDEX,
+			.address = *nmtst_inet6_from_string ("2000::2"),
+			.plen = 128,
+			.peer_address = in6addr_any,
+			.lifetime = NM_PLATFORM_LIFETIME_PERMANENT,
+			.preferred = NM_PLATFORM_LIFETIME_PERMANENT,
+			.n_ifa_flags = 0,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("1010::1"),
+			.plen = 128,
+			.gateway = in6addr_any,
+			.metric = 256,
+			.pref_src = *nmtst_inet6_from_string ("2000::2"),
+		});
+		break;
+	case 3:
+		addr[addr_n++] = ((NMPlatformIP6Address) {
+			.ifindex = IFINDEX,
+			.address = *nmtst_inet6_from_string ("2001:db8:8086::5"),
+			.plen = 128,
+			.peer_address = in6addr_any,
+			.lifetime = NM_PLATFORM_LIFETIME_PERMANENT,
+			.preferred = NM_PLATFORM_LIFETIME_PERMANENT,
+			.n_ifa_flags = 0,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 110,
+			.metric = 10021,
+			.mss = 0,
+		});
+		rts_add[rts_n++] = ((NMPlatformIP6Route) {
+			.ifindex = IFINDEX,
+			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::1"),
+			.metric = 21,
+			.mss = 0,
+		});
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	for (i = 0; i < addr_n; i++) {
+		g_assert (addr[i].ifindex == IFINDEX);
+		addr_in6[i] = addr[i].address;
+		g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET,
+		                                       IFINDEX,
+		                                       addr[i].address,
+		                                       addr[i].plen,
+		                                       addr[i].peer_address,
+		                                       addr[i].lifetime,
+		                                       addr[i].preferred,
+		                                       addr[i].n_ifa_flags));
+	}
+
+	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6);
+
+	for (i = 0; i < rts_n; i++)
+		g_assert (nm_platform_ip6_route_add (NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]) == NM_PLATFORM_ERROR_SUCCESS);
+
+	routes = nmtstp_ip6_route_get_all (NM_PLATFORM_GET, IFINDEX);
+	switch (TEST_IDX) {
+	case 1:
+	case 2:
+	case 3:
+		for (i = 0; i < rts_n; i++) {
+			rts_cmp[i] = rts_add[i];
+			rts_cmp[i].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
+		}
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+	g_assert_cmpint (routes->len, ==, rts_n);
+	nmtst_platform_ip6_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts_cmp, routes->len, TRUE);
+	g_ptr_array_unref (routes);
+
+	for (i = 0; i < rts_n; i++) {
+		g_assert (nmtstp_platform_ip6_route_delete (NM_PLATFORM_GET, IFINDEX,
+		                                            rts_add[i].network, rts_add[i].plen,
+		                                            rts_add[i].metric));
+	}
+
+	for (i = 0; i < addr_n; i++) {
+		nmtstp_ip6_address_del (NM_PLATFORM_GET,
+		                        EX,
+		                        IFINDEX,
+		                        rts_add[i].network,
+		                        rts_add[i].plen);
+	}
+}
 
-	/* Remove route */
-	g_assert (nm_platform_ip6_route_delete (NM_PLATFORM_GET, ifindex, network, 64, 1024));
+/*****************************************************************************/
 
-	g_array_unref (routes);
+static void
+test_ip (gconstpointer test_data)
+{
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const int IFINDEX = nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME);
+	guint i, j, k;
+	const NMPlatformLink *l;
+	char ifname[IFNAMSIZ];
+	char ifname2[IFNAMSIZ];
+	char s1[NM_UTILS_INET_ADDRSTRLEN];
+	NMPlatform *platform = NM_PLATFORM_GET;
+	const int EX_ = -1;
+	struct {
+		int ifindex;
+	} iface_data[10] = { 0 };
+	int order_idx[G_N_ELEMENTS (iface_data)] = { 0 };
+	guint order_len;
+	guint try;
+
+	for (i = 0; i < G_N_ELEMENTS (iface_data); i++) {
+		nm_sprintf_buf (ifname, "v%02u", i);
+		nm_sprintf_buf (ifname2, "w%02u", i);
+
+		g_assert (!nm_platform_link_get_by_ifname (platform, ifname));
+		g_assert (!nm_platform_link_get_by_ifname (platform, ifname2));
+		l = nmtstp_link_veth_add (platform, EX_, ifname, ifname2);
+		iface_data[i].ifindex = l->ifindex;
+
+		nmtstp_link_set_updown (platform, EX_, iface_data[i].ifindex, TRUE);
+		nmtstp_link_set_updown (platform, EX_, nmtstp_link_get (platform, -1, ifname2)->ifindex, TRUE);
+
+		nm_sprintf_buf (s1, "192.168.7.%d", 100 + i);
+		nmtstp_ip4_address_add (platform,
+		                        EX_,
+		                        iface_data[i].ifindex,
+		                        nmtst_inet4_from_string (s1),
+		                        24,
+		                        nmtst_inet4_from_string (s1),
+		                        3600,
+		                        3600,
+		                        0,
+		                        NULL);
+	}
+
+	order_len = 0;
+	for (try = 0; try < 5 * G_N_ELEMENTS (order_idx); try++) {
+		NMPObject o;
+		NMPlatformIP4Route *r;
+		guint idx;
+		const NMDedupMultiHeadEntry *head_entry;
+		NMPLookup lookup;
+
+		nmp_object_stackinit (&o, NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
+		r = NMP_OBJECT_CAST_IP4_ROUTE (&o);
+		r->network = nmtst_inet4_from_string ("192.168.9.0");
+		r->plen = 24;
+		r->metric = 109;
+
+		if (   order_len == 0
+		    || (   order_len < G_N_ELEMENTS (order_idx)
+		        && nmtst_get_rand_int () % 2)) {
+again_find_idx:
+			idx = nmtst_get_rand_int () % G_N_ELEMENTS (iface_data);
+			for (i = 0; i < order_len; i++) {
+				if (order_idx[i] == idx)
+					goto again_find_idx;
+			}
+			order_idx[order_len++] = idx;
+
+			r->ifindex = iface_data[idx].ifindex;
+			g_assert (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_APPEND, r) == NM_PLATFORM_ERROR_SUCCESS);
+		} else {
+			i = nmtst_get_rand_int () % order_len;
+			idx = order_idx[i];
+			for (i++; i < order_len; i++)
+				order_idx[i - 1] = order_idx[i];
+			order_len--;
+
+			r->ifindex = iface_data[idx].ifindex;
+			g_assert (nm_platform_ip_route_delete (platform, &o));
+		}
+
+		head_entry = nm_platform_lookup (platform,
+		                                 nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_IP4_ROUTE));
+		for (j = 0; j < G_N_ELEMENTS (iface_data); j++) {
+			gboolean has;
+			NMDedupMultiIter iter;
+			const NMPObject *o_cached;
+
+			has = FALSE;
+			for (k = 0; k < order_len; k++) {
+				if (order_idx[k] == j) {
+					g_assert (!has);
+					has = TRUE;
+				}
+			}
+
+			nmp_cache_iter_for_each (&iter, head_entry, &o_cached) {
+				const NMPlatformIP4Route *r_cached = NMP_OBJECT_CAST_IP4_ROUTE (o_cached);
+
+				if (   r_cached->ifindex != iface_data[j].ifindex
+				    || r_cached->metric != 109)
+					continue;
+
+				g_assert (has);
+				has = FALSE;
+			}
+			g_assert (!has);
+		}
+	}
+
+	for (i = 0; i < G_N_ELEMENTS (iface_data); i++)
+		g_assert (nm_platform_link_delete (platform, iface_data[i].ifindex));
+
+	(void) TEST_IDX;
+	(void) IFINDEX;
 }
 
 /*****************************************************************************/
@@ -494,22 +766,19 @@ _nmtstp_init_tests (int *argc, char ***argv)
 void
 _nmtstp_setup_tests (void)
 {
-	SignalData *link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED, NM_PLATFORM_SIGNAL_ADDED, link_callback, DEVICE_NAME);
-
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, DEVICE_NAME));
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, DEVICE_NAME, NULL) == NM_PLATFORM_ERROR_SUCCESS);
-	accept_signal (link_added);
-	free_signal (link_added);
-
-	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME), NULL));
-
-	g_test_add_func ("/route/ip4", test_ip4_route);
-	g_test_add_func ("/route/ip6", test_ip6_route);
-	g_test_add_func ("/route/ip4_metric0", test_ip4_route_metric0);
-	g_test_add_func ("/route/ip4_options", test_ip4_route_options);
-	g_test_add_func ("/route/ip6_options", test_ip6_route_options);
-
-	if (nmtstp_is_root_test ())
-		g_test_add_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
+#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, TRUE)
+#define add_test_func_data(testpath, test_func, arg) nmtstp_env1_add_test_func_data(testpath, test_func, arg, TRUE)
+	add_test_func ("/route/ip4", test_ip4_route);
+	add_test_func ("/route/ip6", test_ip6_route);
+	add_test_func ("/route/ip4_metric0", test_ip4_route_metric0);
+	add_test_func ("/route/ip4_options", test_ip4_route_options);
+	add_test_func_data ("/route/ip6_options/1", test_ip6_route_options, GINT_TO_POINTER (1));
+	add_test_func_data ("/route/ip6_options/2", test_ip6_route_options, GINT_TO_POINTER (2));
+	add_test_func_data ("/route/ip6_options/3", test_ip6_route_options, GINT_TO_POINTER (3));
+
+	if (nmtstp_is_root_test ()) {
+		add_test_func_data ("/route/ip/1", test_ip, GINT_TO_POINTER (1));
+		add_test_func ("/route/ip_route_get", test_ip_route_get);
+		add_test_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
+	}
 }
diff --git a/src/platform/wifi/wifi-utils-nl80211.c b/src/platform/wifi/wifi-utils-nl80211.c
index 06eb7cb9..a5f25b02 100644
--- a/src/platform/wifi/wifi-utils-nl80211.c
+++ b/src/platform/wifi/wifi-utils-nl80211.c
@@ -46,6 +46,24 @@
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
+/*****************************************************************************/
+
+static int
+_nl_nla_parse (struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
+               const struct nla_policy *policy)
+{
+	return nla_parse (tb, maxtype, head, len, (struct nla_policy *) policy);
+}
+#define nla_parse(...) _nl_nla_parse(__VA_ARGS__)
+
+static int
+_nl_nla_parse_nested (struct nlattr *tb[], int maxtype, struct nlattr *nla,
+                      const struct nla_policy *policy)
+{
+	return nla_parse_nested (tb, maxtype, nla, (struct nla_policy *) policy);
+}
+#define nla_parse_nested(...) _nl_nla_parse_nested(__VA_ARGS__)
+
 /*****************************************************************************
  * Copied from libnl3/genl:
  *****************************************************************************/
@@ -131,7 +149,7 @@ genlmsg_valid_hdr (struct nlmsghdr *nlh, int hdrlen)
 
 static int
 genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
-               int maxtype, struct nla_policy *policy)
+               int maxtype, const struct nla_policy *policy)
 {
 	struct genlmsghdr *ghdr;
 
@@ -150,7 +168,7 @@ genlmsg_parse (struct nlmsghdr *nlh, int hdrlen, struct nlattr *tb[],
 static int
 probe_response (struct nl_msg *msg, void *arg)
 {
-	static struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
+	static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
 		[CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
 		[CTRL_ATTR_FAMILY_NAME]  = { .type = NLA_STRING,
 		                            .maxlen = GENL_NAMSIZ },
@@ -389,7 +407,7 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 
 	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-		       genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
 		return NL_SKIP;
 
 	if (!tb[NL80211_ATTR_IFTYPE])
@@ -529,7 +547,7 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 	struct nlattr *bss[NL80211_BSS_MAX + 1];
-	static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
+	static const struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
 		[NL80211_BSS_TSF] = { .type = NLA_U64 },
 		[NL80211_BSS_FREQUENCY] = { .type = NLA_U32 },
 		[NL80211_BSS_BSSID] = { },
@@ -543,15 +561,15 @@ nl80211_bss_dump_handler (struct nl_msg *msg, void *arg)
 	guint32 status;
 
 	if (nla_parse (tb, NL80211_ATTR_MAX, genlmsg_attrdata (gnlh, 0),
-		       genlmsg_attrlen (gnlh, 0), NULL) < 0)
+	               genlmsg_attrlen (gnlh, 0), NULL) < 0)
 		return NL_SKIP;
 
 	if (tb[NL80211_ATTR_BSS] == NULL)
 		return NL_SKIP;
 
 	if (nla_parse_nested (bss, NL80211_BSS_MAX,
-			      tb[NL80211_ATTR_BSS],
-			      bss_policy))
+	                      tb[NL80211_ATTR_BSS],
+	                      bss_policy))
 		return NL_SKIP;
 
 	if (bss[NL80211_BSS_STATUS] == NULL)
@@ -665,7 +683,7 @@ nl80211_station_handler (struct nl_msg *msg, void *arg)
 	struct genlmsghdr *gnlh = nlmsg_data (nlmsg_hdr (msg));
 	struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
 	struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];
-	static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
+	static const struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
 		[NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 },
 		[NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 },
 		[NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 },
@@ -678,7 +696,7 @@ nl80211_station_handler (struct nl_msg *msg, void *arg)
 		[NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 },
 	};
 
-	static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
+	static const struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
 		[NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 },
 		[NL80211_RATE_INFO_MCS] = { .type = NLA_U8 },
 		[NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG },
@@ -871,7 +889,7 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 	int rem_freq;
 	int rem_band;
 	int freq_idx;
-	static struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
+	static const struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
 		[NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 },
 		[NL80211_FREQUENCY_ATTR_DISABLED] = { .type = NLA_FLAG },
 #ifdef NL80211_FREQUENCY_ATTR_NO_IR
diff --git a/src/platform/wifi/wifi-utils-wext.c b/src/platform/wifi/wifi-utils-wext.c
index 1bc29ae8..c4d3c999 100644
--- a/src/platform/wifi/wifi-utils-wext.c
+++ b/src/platform/wifi/wifi-utils-wext.c
@@ -97,8 +97,7 @@ wifi_wext_deinit (WifiData *parent)
 {
 	WifiDataWext *wext = (WifiDataWext *) parent;
 
-	if (wext->fd >= 0)
-		close (wext->fd);
+	nm_close (wext->fd);
 }
 
 static gboolean
@@ -757,7 +756,7 @@ wifi_wext_is_wifi (const char *iface)
 		nm_utils_ifname_cpy (iwr.ifr_ifrn.ifrn_name, iface);
 		if (ioctl (fd, SIOCGIWNAME, &iwr) == 0)
 			is_wifi = TRUE;
-		close (fd);
+		nm_close (fd);
 	}
 	return is_wifi;
 }
diff --git a/src/ppp/nm-ppp-manager-call.c b/src/ppp/nm-ppp-manager-call.c
index d67c0a99..ad3307a9 100644
--- a/src/ppp/nm-ppp-manager-call.c
+++ b/src/ppp/nm-ppp-manager-call.c
@@ -98,6 +98,22 @@ nm_ppp_manager_create (const char *iface, GError **error)
 	return ret;
 }
 
+void
+nm_ppp_manager_set_route_parameters (NMPPPManager *self,
+                                     guint32 ip4_route_table,
+                                     guint32 ip4_route_metric,
+                                     guint32 ip6_route_table,
+                                     guint32 ip6_route_metric)
+{
+	g_return_if_fail (ppp_ops);
+
+	ppp_ops->set_route_parameters (self,
+	                               ip4_route_table,
+	                               ip4_route_metric,
+	                               ip6_route_table,
+	                               ip6_route_metric);
+}
+
 gboolean
 nm_ppp_manager_start (NMPPPManager *self,
                       NMActRequest *req,
diff --git a/src/ppp/nm-ppp-manager-call.h b/src/ppp/nm-ppp-manager-call.h
index f21005f0..2258ae08 100644
--- a/src/ppp/nm-ppp-manager-call.h
+++ b/src/ppp/nm-ppp-manager-call.h
@@ -25,6 +25,13 @@
 
 NMPPPManager *      nm_ppp_manager_create      (const char *iface,
                                                 GError **error);
+
+void                nm_ppp_manager_set_route_parameters (NMPPPManager *ppp_manager,
+                                                         guint32 ip4_route_table,
+                                                         guint32 ip4_route_metric,
+                                                         guint32 ip6_route_table,
+                                                         guint32 ip6_route_metric);
+
 gboolean            nm_ppp_manager_start       (NMPPPManager *self,
                                                 NMActRequest *req,
                                                 const char *ppp_name,
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index 6343df8b..3ef3f3dc 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -42,6 +42,7 @@
 #endif
 #include <linux/if.h>
 #include <linux/if_ppp.h>
+#include <linux/rtnetlink.h>
 
 #include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
@@ -105,6 +106,11 @@ typedef struct {
 	char *ip_iface;
 	int monitor_fd;
 	guint monitor_id;
+
+	guint32 ip4_route_table;
+	guint32 ip4_route_metric;
+	guint32 ip6_route_table;
+	guint32 ip6_route_metric;
 } NMPPPManagerPrivate;
 
 struct _NMPPPManager {
@@ -132,6 +138,37 @@ static void _ppp_kill (NMPPPManager *manager);
 
 /*****************************************************************************/
 
+static void
+_ppp_manager_set_route_paramters (NMPPPManager *self,
+                                  guint32 ip4_route_table,
+                                  guint32 ip4_route_metric,
+                                  guint32 ip6_route_table,
+                                  guint32 ip6_route_metric)
+{
+	NMPPPManagerPrivate *priv;
+
+	g_return_if_fail (NM_IS_PPP_MANAGER (self));
+
+	priv = NM_PPP_MANAGER_GET_PRIVATE (self);
+	if (   priv->ip4_route_table  != ip4_route_table
+	    || priv->ip4_route_metric != ip4_route_metric
+	    || priv->ip6_route_table  != ip6_route_table
+	    || priv->ip6_route_metric != ip6_route_metric) {
+		priv->ip4_route_table = ip4_route_table;
+		priv->ip4_route_metric = ip4_route_metric;
+		priv->ip6_route_table = ip6_route_table;
+		priv->ip6_route_metric = ip6_route_metric;
+
+		_LOGT ("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u",
+		       priv->ip4_route_table,
+		       priv->ip4_route_metric,
+		       priv->ip6_route_table,
+		       priv->ip6_route_metric);
+	}
+}
+
+/*****************************************************************************/
+
 static gboolean
 monitor_cb (gpointer user_data)
 {
@@ -400,16 +437,27 @@ impl_ppp_manager_set_ip4_config (NMPPPManager *manager,
                                  GVariant *config_dict)
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
-	NMIP4Config *config;
+	gs_unref_object NMIP4Config *config = NULL;
 	NMPlatformIP4Address address;
-	guint32 u32;
+	guint32 u32, mtu;
 	GVariantIter *iter;
+	int ifindex;
 
 	_LOGI ("(IPv4 Config Get) reply received.");
 
 	nm_clear_g_source (&priv->ppp_timeout_handler);
 
-	config = nm_ip4_config_new (nm_platform_link_get_ifindex (NM_PLATFORM_GET, priv->ip_iface));
+	if (!set_ip_config_common (manager, config_dict, NM_PPP_IP4_CONFIG_INTERFACE, &mtu))
+		goto out;
+
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, priv->ip_iface);
+	if (ifindex <= 0)
+		goto out;
+
+	config = nm_ip4_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET), ifindex);
+
+	if (mtu)
+		nm_ip4_config_set_mtu (config, mtu, NM_IP_CONFIG_SOURCE_PPP);
 
 	memset (&address, 0, sizeof (address));
 	address.plen = 32;
@@ -418,7 +466,15 @@ impl_ppp_manager_set_ip4_config (NMPPPManager *manager,
 		address.address = u32;
 
 	if (g_variant_lookup (config_dict, NM_PPP_IP4_CONFIG_GATEWAY, "u", &u32)) {
-		nm_ip4_config_set_gateway (config, u32);
+		const NMPlatformIP4Route r = {
+			.ifindex   = ifindex,
+			.rt_source = NM_IP_CONFIG_SOURCE_PPP,
+			.gateway   = u32,
+			.table_coerced = nm_platform_route_table_coerce (priv->ip4_route_table),
+			.metric    = priv->ip4_route_metric,
+		};
+
+		nm_ip4_config_add_route (config, &r, NULL);
 		address.peer_address = u32;
 	} else
 		address.peer_address = address.address;
@@ -446,17 +502,10 @@ impl_ppp_manager_set_ip4_config (NMPPPManager *manager,
 		g_variant_iter_free (iter);
 	}
 
-	if (!set_ip_config_common (manager, config_dict, NM_PPP_IP4_CONFIG_INTERFACE, &u32))
-		goto out;
-
-	if (u32)
-		nm_ip4_config_set_mtu (config, u32, NM_IP_CONFIG_SOURCE_PPP);
-
 	/* Push the IP4 config up to the device */
 	g_signal_emit (manager, signals[IP4_CONFIG], 0, priv->ip_iface, config);
 
 out:
-	g_object_unref (config);
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
@@ -495,23 +544,39 @@ impl_ppp_manager_set_ip6_config (NMPPPManager *manager,
                                  GVariant *config_dict)
 {
 	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
-	NMIP6Config *config;
+	gs_unref_object NMIP6Config *config = NULL;
 	NMPlatformIP6Address addr;
 	struct in6_addr a;
 	NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
 	gboolean has_peer = FALSE;
+	int ifindex;
 
 	_LOGI ("(IPv6 Config Get) reply received.");
 
 	nm_clear_g_source (&priv->ppp_timeout_handler);
 
-	config = nm_ip6_config_new (nm_platform_link_get_ifindex (NM_PLATFORM_GET, priv->ip_iface));
+	if (!set_ip_config_common (manager, config_dict, NM_PPP_IP6_CONFIG_INTERFACE, NULL))
+		goto out;
+
+	ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, priv->ip_iface);
+	if (ifindex <= 0)
+		goto out;
+
+	config = nm_ip6_config_new (nm_platform_get_multi_idx (NM_PLATFORM_GET), ifindex);
 
 	memset (&addr, 0, sizeof (addr));
 	addr.plen = 64;
 
 	if (iid_value_to_ll6_addr (config_dict, NM_PPP_IP6_CONFIG_PEER_IID, &a, NULL)) {
-		nm_ip6_config_set_gateway (config, &a);
+		const NMPlatformIP6Route r = {
+			.ifindex   = ifindex,
+			.rt_source = NM_IP_CONFIG_SOURCE_PPP,
+			.gateway   = a,
+			.table_coerced = nm_platform_route_table_coerce (priv->ip6_route_table),
+			.metric    = priv->ip6_route_metric,
+		};
+
+		nm_ip6_config_add_route (config, &r, NULL);
 		addr.peer_address = a;
 		has_peer = TRUE;
 	}
@@ -521,14 +586,12 @@ impl_ppp_manager_set_ip6_config (NMPPPManager *manager,
 			addr.peer_address = addr.address;
 		nm_ip6_config_add_address (config, &addr);
 
-		if (set_ip_config_common (manager, config_dict, NM_PPP_IP6_CONFIG_INTERFACE, NULL)) {
-			/* Push the IPv6 config and interface identifier up to the device */
-			g_signal_emit (manager, signals[IP6_CONFIG], 0, priv->ip_iface, &iid, config);
-		}
+		/* Push the IPv6 config and interface identifier up to the device */
+		g_signal_emit (manager, signals[IP6_CONFIG], 0, priv->ip_iface, &iid, config);
 	} else
 		_LOGE ("invalid IPv6 address received!");
 
-	g_object_unref (config);
+out:
 	g_dbus_method_invocation_return_value (context, NULL);
 }
 
@@ -676,6 +739,7 @@ create_pppd_cmd_line (NMPPPManager *self,
 	const char *pppd_binary = NULL;
 	NMCmdLine *cmd;
 	gboolean ppp_debug;
+	static int unit;
 
 	g_return_val_if_fail (setting != NULL, NULL);
 
@@ -840,6 +904,15 @@ create_pppd_cmd_line (NMPPPManager *self,
 	nm_cmd_line_add_string (cmd, "plugin");
 	nm_cmd_line_add_string (cmd, NM_PPPD_PLUGIN);
 
+	if (pppoe && nm_setting_pppoe_get_parent (pppoe)) {
+		/* The PPP interface is going to be renamed, so pass a
+		 * different unit each time so that activations don't
+		 * race with each others. */
+		nm_cmd_line_add_string (cmd, "unit");
+		nm_cmd_line_add_int (cmd, unit);
+		unit = unit < G_MAXINT ? unit + 1 : 0;
+	}
+
 	return cmd;
 }
 
@@ -1015,7 +1088,7 @@ _ppp_cleanup (NMPPPManager *manager)
 	if (priv->monitor_fd >= 0) {
 		/* Get the stats one last time */
 		monitor_cb (manager);
-		close (priv->monitor_fd);
+		nm_close (priv->monitor_fd);
 		priv->monitor_fd = -1;
 	}
 
@@ -1155,7 +1228,7 @@ set_property (GObject *object, guint prop_id,
 
 	switch (prop_id) {
 	case PROP_PARENT_IFACE:
-		g_free (priv->parent_iface);
+		/* construct-only */
 		priv->parent_iface = g_value_dup_string (value);
 		break;
 	default:
@@ -1169,7 +1242,13 @@ set_property (GObject *object, guint prop_id,
 static void
 nm_ppp_manager_init (NMPPPManager *manager)
 {
-	NM_PPP_MANAGER_GET_PRIVATE (manager)->monitor_fd = -1;
+	NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE (manager);
+
+	priv->monitor_fd = -1;
+	priv->ip4_route_table = RT_TABLE_MAIN;
+	priv->ip4_route_metric = 460;
+	priv->ip6_route_table = RT_TABLE_MAIN;
+	priv->ip6_route_metric = 460;
 }
 
 static NMPPPManager *
@@ -1279,9 +1358,10 @@ nm_ppp_manager_class_init (NMPPPManagerClass *manager_class)
 }
 
 NMPPPOps ppp_ops = {
-	.create       = _ppp_manager_new,
-	.start        = _ppp_manager_start,
-	.stop_async   = _ppp_manager_stop_async,
-	.stop_finish  = _ppp_manager_stop_finish,
-	.stop_sync    = _ppp_manager_stop_sync,
+	.create               = _ppp_manager_new,
+	.set_route_parameters = _ppp_manager_set_route_paramters,
+	.start                = _ppp_manager_start,
+	.stop_async           = _ppp_manager_stop_async,
+	.stop_finish          = _ppp_manager_stop_finish,
+	.stop_sync            = _ppp_manager_stop_sync,
 };
diff --git a/src/ppp/nm-ppp-manager.h b/src/ppp/nm-ppp-manager.h
index b1a7bb60..35fb1b60 100644
--- a/src/ppp/nm-ppp-manager.h
+++ b/src/ppp/nm-ppp-manager.h
@@ -22,7 +22,7 @@
 #ifndef __NM_PPP_MANAGER_H__
 #define __NM_PPP_MANAGER_H__
 
-#define NM_PPP_MANAGER_PARENT_IFACE "parent-iface"
+#define NM_PPP_MANAGER_PARENT_IFACE    "parent-iface"
 
 #define NM_PPP_MANAGER_SIGNAL_STATE_CHANGED "state-changed"
 #define NM_PPP_MANAGER_SIGNAL_IP4_CONFIG    "ip4-config"
diff --git a/src/ppp/nm-ppp-plugin-api.h b/src/ppp/nm-ppp-plugin-api.h
index 0a38fe05..bb53690c 100644
--- a/src/ppp/nm-ppp-plugin-api.h
+++ b/src/ppp/nm-ppp-plugin-api.h
@@ -24,6 +24,12 @@
 typedef const struct {
 	NMPPPManager *(*create) (const char *iface);
 
+	void (*set_route_parameters) (NMPPPManager *manager,
+	                              guint32 route_table_v4,
+	                              guint32 route_metric_v4,
+	                              guint32 route_table_v6,
+	                              guint32 route_metric_v6);
+
 	gboolean      (*start) (NMPPPManager *manager,
 	                        NMActRequest *req,
 	                        const char *ppp_name,
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 3d6b1cfb..bcd17843 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -36,6 +36,7 @@
 #include "nm-simple-connection.h"
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
+#include "nm-utils/c-list.h"
 
 #include "introspection/org.freedesktop.NetworkManager.AgentManager.h"
 
@@ -50,6 +51,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	NMAuthManager *auth_mgr;
+	NMSessionMonitor *session_monitor;
 
 	/* Auth chains for checking agent permissions */
 	GSList *chains;
@@ -59,7 +61,7 @@ typedef struct {
 	 */
 	GHashTable *agents;
 
-	GHashTable *requests;
+	CList requests;
 } NMAgentManagerPrivate;
 
 struct _NMAgentManager {
@@ -123,7 +125,7 @@ typedef struct _NMAgentManagerCallId Request;
 
 static void request_add_agent (Request *req, NMSecretAgent *agent);
 
-static void request_remove_agent (Request *req, NMSecretAgent *agent, GSList **pending_reqs);
+static void request_remove_agent (Request *req, NMSecretAgent *agent);
 
 static void request_next_agent (Request *req);
 
@@ -155,14 +157,62 @@ _request_type_to_string (RequestType request_type, gboolean verbose)
 
 /*****************************************************************************/
 
+struct _NMAgentManagerCallId {
+	CList lst_request;
+
+	NMAgentManager *self;
+
+	RequestType request_type;
+
+	char *detail;
+
+	NMAuthSubject *subject;
+
+	/* Current agent being asked for secrets */
+	NMSecretAgent *current;
+	NMSecretAgentCallId current_call_id;
+
+	/* Stores the sorted list of NMSecretAgents which will be asked for secrets */
+	GSList *pending;
+
+	guint idle_id;
+
+	union {
+		struct {
+			char *path;
+			NMConnection *connection;
+
+			NMAuthChain *chain;
+
+			/* Whether the agent currently being asked for secrets
+			 * has the system.modify privilege.
+			 */
+			gboolean current_has_modify;
+
+			union {
+				struct {
+					NMSecretAgentGetSecretsFlags flags;
+					char *setting_name;
+					char **hints;
+
+					GVariant *existing_secrets;
+
+					NMAgentSecretsResultFunc callback;
+					gpointer callback_data;
+				} get;
+			};
+		} con;
+	};
+};
+
+/*****************************************************************************/
+
 static gboolean
 remove_agent (NMAgentManager *self, const char *owner)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMSecretAgent *agent;
-	GHashTableIter iter;
-	gpointer data;
-	GSList *pending_reqs = NULL;
+	CList *iter, *safe;
 
 	g_return_val_if_fail (owner != NULL, FALSE);
 
@@ -174,16 +224,8 @@ remove_agent (NMAgentManager *self, const char *owner)
 	_LOGD (agent, "agent unregistered or disappeared");
 
 	/* Remove this agent from any in-progress secrets requests */
-	g_hash_table_iter_init (&iter, priv->requests);
-	while (g_hash_table_iter_next (&iter, &data, NULL))
-		request_remove_agent ((Request *) data, agent, &pending_reqs);
-
-	/* We cannot call request_next_agent() from within hash iterating loop,
-	 * because it may remove the request from the hash table, which invalidates
-	 * the iterator. So, only remove the agent from requests. And store the requests
-	 * that should be sent to other agent to a temporary list to proceed afterwards.
-	 */
-	g_slist_free_full (pending_reqs, (GDestroyNotify) request_next_agent);
+	c_list_for_each_safe (iter, safe, &priv->requests)
+		request_remove_agent (c_list_entry (iter, Request, lst_request), agent);
 
 	/* And dispose of the agent */
 	g_hash_table_remove (priv->agents, owner);
@@ -270,8 +312,7 @@ agent_register_permissions_done (NMAuthChain *chain,
 	const char *sender;
 	GError *local = NULL;
 	NMAuthCallResult result;
-	GHashTableIter iter;
-	Request *req;
+	CList *iter;
 
 	g_assert (context);
 
@@ -304,9 +345,8 @@ agent_register_permissions_done (NMAuthChain *chain,
 		g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
 
 		/* Add this agent to any in-progress secrets requests */
-		g_hash_table_iter_init (&iter, priv->requests);
-		while (g_hash_table_iter_next (&iter, (gpointer) &req, NULL))
-			request_add_agent (req, agent);
+		c_list_for_each (iter, &priv->requests)
+			request_add_agent (c_list_entry (iter, Request, lst_request), agent);
 	}
 
 	nm_auth_chain_unref (chain);
@@ -450,52 +490,6 @@ done:
 
 /*****************************************************************************/
 
-struct _NMAgentManagerCallId {
-	NMAgentManager *self;
-
-	RequestType request_type;
-
-	char *detail;
-
-	NMAuthSubject *subject;
-
-	/* Current agent being asked for secrets */
-	NMSecretAgent *current;
-	NMSecretAgentCallId current_call_id;
-
-	/* Stores the sorted list of NMSecretAgents which will be asked for secrets */
-	GSList *pending;
-
-	guint idle_id;
-
-	union {
-		struct {
-			char *path;
-			NMConnection *connection;
-
-			NMAuthChain *chain;
-
-			/* Whether the agent currently being asked for secrets
-			 * has the system.modify privilege.
-			 */
-			gboolean current_has_modify;
-
-			union {
-				struct {
-					NMSecretAgentGetSecretsFlags flags;
-					char *setting_name;
-					char **hints;
-
-					GVariant *existing_secrets;
-
-					NMAgentSecretsResultFunc callback;
-					gpointer callback_data;
-				} get;
-			};
-		} con;
-	};
-};
-
 static Request *
 request_new (NMAgentManager *self,
              RequestType request_type,
@@ -509,6 +503,7 @@ request_new (NMAgentManager *self,
 	req->request_type = request_type;
 	req->detail = g_strdup (detail);
 	req->subject = g_object_ref (subject);
+	c_list_link_tail (&NM_AGENT_MANAGER_GET_PRIVATE (self)->requests, &req->lst_request);
 	return req;
 }
 
@@ -597,7 +592,7 @@ req_complete_cancel (Request *req, gboolean is_disposing)
 	gs_free_error GError *error = NULL;
 
 	nm_assert (req && req->self);
-	nm_assert (!g_hash_table_contains (req->self->_priv.requests, req));
+	nm_assert (!c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (req->self)->requests, &req->lst_request));
 
 	nm_utils_error_set_cancelled (&error, is_disposing, "NMAgentManager");
 	req_complete_release (req, NULL, NULL, NULL, error);
@@ -611,10 +606,11 @@ req_complete (Request *req,
               GError *error)
 {
 	NMAgentManager *self = req->self;
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 
-	if (!g_hash_table_remove (priv->requests, req))
-		g_return_if_reached ();
+	nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->requests, &req->lst_request));
+
+	c_list_unlink_init (&req->lst_request);
+
 	req_complete_release (req, secrets, agent_dbus_owner, agent_username, error);
 }
 
@@ -630,6 +626,7 @@ agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 	NMSecretAgent *a = (NMSecretAgent *)aa;
 	NMSecretAgent *b = (NMSecretAgent *)bb;
 	Request *req = user_data;
+	NMSessionMonitor *sm;
 	gboolean a_active, b_active;
 	gulong a_pid, b_pid, requester;
 
@@ -648,8 +645,9 @@ agent_compare_func (gconstpointer aa, gconstpointer bb, gpointer user_data)
 	}
 
 	/* Prefer agents in active sessions */
-	a_active = nm_session_monitor_session_exists (nm_session_monitor_get (), nm_secret_agent_get_owner_uid (a), TRUE);
-	b_active = nm_session_monitor_session_exists (nm_session_monitor_get (), nm_secret_agent_get_owner_uid (b), TRUE);
+	sm = NM_AGENT_MANAGER_GET_PRIVATE (req->self)->session_monitor;
+	a_active = nm_session_monitor_session_exists (sm, nm_secret_agent_get_owner_uid (a), TRUE);
+	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)
@@ -734,7 +732,7 @@ request_next_agent (Request *req)
 			nm_secret_agent_cancel_secrets (req->current, req->current_call_id);
 		g_clear_object (&req->current);
 	}
-	g_warn_if_fail (!req->current_call_id);
+	nm_assert (!req->current_call_id);
 
 	if (req->pending) {
 		/* Send the request to the next agent */
@@ -769,7 +767,7 @@ request_next_agent (Request *req)
 }
 
 static void
-request_remove_agent (Request *req, NMSecretAgent *agent, GSList **pending_reqs)
+request_remove_agent (Request *req, NMSecretAgent *agent)
 {
 	NMAgentManager *self;
 
@@ -798,7 +796,7 @@ request_remove_agent (Request *req, NMSecretAgent *agent, GSList **pending_reqs)
 			g_assert_not_reached ();
 		}
 
-		*pending_reqs = g_slist_prepend (*pending_reqs, req);
+		request_next_agent (req);
 	} else if (g_slist_find (req->pending, agent)) {
 		req->pending = g_slist_remove (req->pending, agent);
 
@@ -1219,7 +1217,6 @@ nm_agent_manager_get_secrets (NMAgentManager *self,
                               NMAgentSecretsResultFunc callback,
                               gpointer callback_data)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	Request *req;
 
 	g_return_val_if_fail (self != NULL, NULL);
@@ -1253,9 +1250,6 @@ nm_agent_manager_get_secrets (NMAgentManager *self,
 	req->con.get.callback = callback;
 	req->con.get.callback_data = callback_data;
 
-	if (!nm_g_hash_table_add (priv->requests, req))
-		g_assert_not_reached ();
-
 	/* Kick off the request */
 	if (!(req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM))
 		request_add_agents (self, req);
@@ -1271,9 +1265,9 @@ nm_agent_manager_cancel_secrets (NMAgentManager *self,
 	g_return_if_fail (request_id);
 	g_return_if_fail (request_id->request_type == REQUEST_TYPE_CON_GET);
 
-	if (!g_hash_table_remove (NM_AGENT_MANAGER_GET_PRIVATE (self)->requests,
-	                          request_id))
-		g_return_if_reached ();
+	nm_assert (c_list_contains (&NM_AGENT_MANAGER_GET_PRIVATE (self)->requests, &request_id->lst_request));
+
+	c_list_unlink_init (&request_id->lst_request);
 
 	req_complete_cancel (request_id, FALSE);
 }
@@ -1341,7 +1335,6 @@ nm_agent_manager_save_secrets (NMAgentManager *self,
                                NMConnection *connection,
                                NMAuthSubject *subject)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	Request *req;
 
 	g_return_if_fail (self);
@@ -1359,8 +1352,6 @@ nm_agent_manager_save_secrets (NMAgentManager *self,
 	                   subject);
 	req->con.path = g_strdup (path);
 	req->con.connection = g_object_ref (connection);
-	if (!nm_g_hash_table_add (priv->requests, req))
-		g_assert_not_reached ();
 
 	/* Kick off the request */
 	request_add_agents (self, req);
@@ -1426,7 +1417,6 @@ nm_agent_manager_delete_secrets (NMAgentManager *self,
                                  const char *path,
                                  NMConnection *connection)
 {
-	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMAuthSubject *subject;
 	Request *req;
 
@@ -1447,8 +1437,6 @@ nm_agent_manager_delete_secrets (NMAgentManager *self,
 	req->con.path = g_strdup (path);
 	req->con.connection = g_object_ref (connection);
 	g_object_unref (subject);
-	if (!nm_g_hash_table_add (priv->requests, req))
-		g_assert_not_reached ();
 
 	/* Kick off the request */
 	request_add_agents (self, req);
@@ -1570,8 +1558,8 @@ nm_agent_manager_init (NMAgentManager *self)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 
-	priv->agents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
-	priv->requests = g_hash_table_new (g_direct_hash, g_direct_equal);
+	c_list_init (&priv->requests);
+	priv->agents = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 }
 
 static void
@@ -1582,6 +1570,7 @@ constructed (GObject *object)
 	G_OBJECT_CLASS (nm_agent_manager_parent_class)->constructed (object);
 
 	priv->auth_mgr = g_object_ref (nm_auth_manager_get ());
+	priv->session_monitor = g_object_ref (nm_session_monitor_get ());
 
 	nm_exported_object_export (NM_EXPORTED_OBJECT (object));
 
@@ -1589,28 +1578,19 @@ constructed (GObject *object)
 	                  NM_AUTH_MANAGER_SIGNAL_CHANGED,
 	                  G_CALLBACK (authority_changed_cb),
 	                  object);
-
-	NM_UTILS_KEEP_ALIVE (object, nm_session_monitor_get (), "NMAgentManager-depends-on-NMSessionMonitor");
 }
 
 static void
 dispose (GObject *object)
 {
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE ((NMAgentManager *) object);
-
-	if (priv->requests) {
-		GHashTableIter iter;
-		Request *req;
+	CList *iter;
 
 cancel_more:
-		g_hash_table_iter_init (&iter, priv->requests);
-		if (g_hash_table_iter_next (&iter, (gpointer *) &req, NULL)) {
-			g_hash_table_iter_remove (&iter);
-			req_complete_cancel (req, TRUE);
-			goto cancel_more;
-		}
-		g_hash_table_unref (priv->requests);
-		priv->requests = NULL;
+	c_list_for_each (iter, &priv->requests) {
+		c_list_unlink_init (iter);
+		req_complete_cancel (c_list_entry (iter, Request, lst_request), TRUE);
+		goto cancel_more;
 	}
 
 	g_slist_free_full (priv->chains, (GDestroyNotify) nm_auth_chain_unref);
@@ -1630,6 +1610,8 @@ cancel_more:
 
 	nm_exported_object_unexport (NM_EXPORTED_OBJECT (object));
 
+	g_clear_object (&priv->session_monitor);
+
 	G_OBJECT_CLASS (nm_agent_manager_parent_class)->dispose (object);
 }
 
diff --git a/src/settings/nm-inotify-helper.c b/src/settings/nm-inotify-helper.c
index a0432a25..4c65b02d 100644
--- a/src/settings/nm-inotify-helper.c
+++ b/src/settings/nm-inotify-helper.c
@@ -188,8 +188,7 @@ finalize (GObject *object)
 {
 	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE ((NMInotifyHelper *) object);
 
-	if (priv->ifd >= 0)
-		close (priv->ifd);
+	nm_close (priv->ifd);
 
 	g_hash_table_destroy (priv->wd_refs);
 
diff --git a/src/settings/nm-secret-agent.c b/src/settings/nm-secret-agent.c
index b9ca34d0..5fe4dd17 100644
--- a/src/settings/nm-secret-agent.c
+++ b/src/settings/nm-secret-agent.c
@@ -30,6 +30,7 @@
 #include "nm-auth-subject.h"
 #include "nm-simple-connection.h"
 #include "NetworkManagerUtils.h"
+#include "nm-utils/c-list.h"
 
 #include "introspection/org.freedesktop.NetworkManager.SecretAgent.h"
 
@@ -58,7 +59,7 @@ typedef struct {
 	gboolean connection_is_private;
 	gulong on_disconnected_id;
 
-	GHashTable *requests;
+	CList requests;
 } NMSecretAgentPrivate;
 
 struct _NMSecretAgent {
@@ -99,6 +100,7 @@ G_DEFINE_TYPE (NMSecretAgent, nm_secret_agent, G_TYPE_OBJECT)
 /*****************************************************************************/
 
 struct _NMSecretAgentCallId {
+	CList lst;
 	NMSecretAgent *agent;
 	GCancellable *cancellable;
 	char *path;
@@ -129,6 +131,8 @@ request_new (NMSecretAgent *self,
 	r->callback = callback;
 	r->callback_data = callback_data;
 	r->cancellable = g_cancellable_new ();
+	c_list_link_tail (&NM_SECRET_AGENT_GET_PRIVATE (self)->requests,
+	                  &r->lst);
 	_LOGt ("request "LOG_REQ_FMT": created", LOG_REQ_ARG (r));
 	return r;
 }
@@ -140,6 +144,7 @@ request_free (Request *r)
 	NMSecretAgent *self = r->agent;
 
 	_LOGt ("request "LOG_REQ_FMT": destroyed", LOG_REQ_ARG (r));
+	c_list_unlink (&r->lst);
 	g_free (r->path);
 	g_free (r->setting_name);
 	if (r->cancellable)
@@ -150,17 +155,15 @@ request_free (Request *r)
 static gboolean
 request_check_return (Request *r)
 {
-	NMSecretAgentPrivate *priv;
-
 	if (!r->cancellable)
 		return FALSE;
 
 	g_return_val_if_fail (NM_IS_SECRET_AGENT (r->agent), FALSE);
 
-	priv = NM_SECRET_AGENT_GET_PRIVATE (r->agent);
+	nm_assert (c_list_contains (&NM_SECRET_AGENT_GET_PRIVATE (r->agent)->requests,
+	                            &r->lst));
 
-	if (!g_hash_table_remove (priv->requests, r))
-		g_return_val_if_reached (FALSE);
+	c_list_unlink_init (&r->lst);
 
 	return TRUE;
 }
@@ -373,7 +376,6 @@ nm_secret_agent_get_secrets (NMSecretAgent *self,
 
 	r = request_new (self, "GetSecrets", path, setting_name, callback, callback_data);
 	r->is_get_secrets = TRUE;
-	g_hash_table_add (priv->requests, r);
 
 	/* Increase the timeout only for this call */
 	g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (priv->proxy), 120000);
@@ -467,15 +469,15 @@ do_cancel_secrets (NMSecretAgent *self, Request *r, gboolean disposing)
 void
 nm_secret_agent_cancel_secrets (NMSecretAgent *self, NMSecretAgentCallId call_id)
 {
-	NMSecretAgentPrivate *priv;
 	Request *r = call_id;
 
 	g_return_if_fail (NM_IS_SECRET_AGENT (self));
 	g_return_if_fail (r);
 
-	priv = NM_SECRET_AGENT_GET_PRIVATE (self);
-	if (!g_hash_table_remove (priv->requests, r))
-		g_return_if_reached ();
+	nm_assert (c_list_contains (&NM_SECRET_AGENT_GET_PRIVATE (self)->requests,
+	                            &r->lst));
+
+	c_list_unlink_init (&r->lst);
 
 	do_cancel_secrets (self, r, FALSE);
 }
@@ -523,7 +525,6 @@ nm_secret_agent_save_secrets (NMSecretAgent *self,
 	dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL);
 
 	r = request_new (self, "SaveSecrets", path, NULL, callback, callback_data);
-	g_hash_table_add (priv->requests, r);
 	nmdbus_secret_agent_call_save_secrets (priv->proxy,
 	                                       dict,
 	                                       path,
@@ -576,7 +577,6 @@ nm_secret_agent_delete_secrets (NMSecretAgent *self,
 	dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_NO_SECRETS);
 
 	r = request_new (self, "DeleteSecrets", path, NULL, callback, callback_data);
-	g_hash_table_add (priv->requests, r);
 	nmdbus_secret_agent_call_delete_secrets (priv->proxy,
 	                                         dict,
 	                                         path,
@@ -634,7 +634,7 @@ _on_disconnected_name_owner_changed (GDBusConnection *connection,
 {
 	NMSecretAgent *self = NM_SECRET_AGENT (user_data);
 	NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
-	const char *old_owner, *new_owner;
+	const char *old_owner = NULL, *new_owner = NULL;
 
 	g_variant_get (parameters,
 	               "(&s&s&s)",
@@ -747,7 +747,7 @@ nm_secret_agent_init (NMSecretAgent *self)
 {
 	NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
 
-	priv->requests = g_hash_table_new (g_direct_hash, g_direct_equal);
+	c_list_init (&priv->requests);
 }
 
 static void
@@ -755,13 +755,13 @@ dispose (GObject *object)
 {
 	NMSecretAgent *self = NM_SECRET_AGENT (object);
 	NMSecretAgentPrivate *priv = NM_SECRET_AGENT_GET_PRIVATE (self);
-	GHashTableIter iter;
-	Request *r;
+	CList *iter;
 
-	g_hash_table_iter_init (&iter, priv->requests);
-	while (g_hash_table_iter_next (&iter, (gpointer *) &r, NULL)) {
-		g_hash_table_iter_remove (&iter);
-		do_cancel_secrets (self, r, TRUE);
+again:
+	c_list_for_each (iter, &priv->requests) {
+		c_list_unlink_init (iter);
+		do_cancel_secrets (self, c_list_entry (iter, Request, lst), TRUE);
+		goto again;
 	}
 
 	_on_disconnected_cleanup (priv);
@@ -783,7 +783,6 @@ finalize (GObject *object)
 	g_free (priv->dbus_owner);
 
 	g_slist_free_full (priv->permissions, g_free);
-	g_hash_table_destroy (priv->requests);
 
 	G_OBJECT_CLASS (nm_secret_agent_parent_class)->finalize (object);
 
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 45a1b664..ed69115c 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -109,7 +109,7 @@ typedef struct _NMSettingsConnectionPrivate {
 	GHashTable *seen_bssids; /* Up-to-date BSSIDs that's been seen for the connection */
 
 	int autoconnect_retries;
-	gint32 autoconnect_retry_time;
+	gint32 autoconnect_blocked_until;
 
 	char *filename;
 } NMSettingsConnectionPrivate;
@@ -510,18 +510,12 @@ connection_changed_cb (NMSettingsConnection *self, gpointer unused)
 	_emit_updated (self, FALSE);
 }
 
-/* Update the settings of this connection to match that of 'new_connection',
- * taking care to make a private copy of secrets.
- */
 gboolean
-nm_settings_connection_replace_settings (NMSettingsConnection *self,
-                                         NMConnection *new_connection,
-                                         gboolean update_unsaved,
-                                         const char *log_diff_name,
-                                         GError **error)
+nm_settings_connection_replace_settings_prepare (NMSettingsConnection *self,
+                                                 NMConnection *new_connection,
+                                                 GError **error)
 {
 	NMSettingsConnectionPrivate *priv;
-	gboolean success = FALSE;
 
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 	g_return_val_if_fail (NM_IS_CONNECTION (new_connection), FALSE);
@@ -540,6 +534,30 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self,
 		return FALSE;
 	}
 
+	return TRUE;
+}
+
+gboolean
+nm_settings_connection_replace_settings_full (NMSettingsConnection *self,
+                                              NMConnection *new_connection,
+                                              gboolean prepare_new_connection,
+                                              gboolean update_unsaved,
+                                              const char *log_diff_name,
+                                              GError **error)
+{
+	NMSettingsConnectionPrivate *priv;
+
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (new_connection), FALSE);
+
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	if (   prepare_new_connection
+	    && !nm_settings_connection_replace_settings_prepare (self,
+	                                                         new_connection,
+	                                                         error))
+		return FALSE;
+
 	/* Do nothing if there's nothing to update */
 	if (nm_connection_compare (NM_CONNECTION (self),
 	                           new_connection,
@@ -567,7 +585,6 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self,
 	 * nm_connection_clear_secrets() and clears them.
 	 */
 	update_system_secrets_cache (self);
-	success = TRUE;
 
 	/* Add agent and always-ask secrets back; they won't necessarily be
 	 * in the replacement connection data if it was eg reread from disk.
@@ -594,114 +611,101 @@ nm_settings_connection_replace_settings (NMSettingsConnection *self,
 
 	_emit_updated (self, TRUE);
 
-	return success;
-}
-
-static void
-ignore_cb (NMSettingsConnection *self,
-           GError *error,
-           gpointer user_data)
-{
+	return TRUE;
 }
 
-/* Replaces the settings in this connection with those in 'new_connection'. If
- * any changes are made, commits them to permanent storage and to any other
- * subsystems watching this connection. Before returning, 'callback' is run
- * with the given 'user_data' along with any errors encountered.
+/* Update the settings of this connection to match that of 'new_connection',
+ * taking care to make a private copy of secrets.
  */
-static void
-replace_and_commit (NMSettingsConnection *self,
-                    NMConnection *new_connection,
-                    NMSettingsConnectionCommitFunc callback,
-                    gpointer user_data)
+gboolean
+nm_settings_connection_replace_settings (NMSettingsConnection *self,
+                                         NMConnection *new_connection,
+                                         gboolean update_unsaved,
+                                         const char *log_diff_name,
+                                         GError **error)
 {
-	GError *error = NULL;
-	NMSettingsConnectionCommitReason commit_reason = NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION;
-
-	if (g_strcmp0 (nm_connection_get_id (NM_CONNECTION (self)),
-	               nm_connection_get_id (new_connection)) != 0)
-		commit_reason |= NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED;
-
-	if (nm_settings_connection_replace_settings (self, new_connection, TRUE, "replace-and-commit-disk", &error))
-		nm_settings_connection_commit_changes (self, commit_reason, callback, user_data);
-	else {
-		g_assert (error);
-		if (callback)
-			callback (self, error, user_data);
-		g_clear_error (&error);
-	}
+	return nm_settings_connection_replace_settings_full (self,
+	                                                     new_connection,
+	                                                     TRUE,
+	                                                     update_unsaved,
+	                                                     log_diff_name,
+	                                                     error);
 }
 
-void
-nm_settings_connection_replace_and_commit (NMSettingsConnection *self,
-                                           NMConnection *new_connection,
-                                           NMSettingsConnectionCommitFunc callback,
-                                           gpointer user_data)
+gboolean
+nm_settings_connection_commit_changes (NMSettingsConnection *self,
+                                       NMConnection *new_connection,
+                                       NMSettingsConnectionCommitReason commit_reason,
+                                       GError **error)
 {
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
-	g_return_if_fail (NM_IS_CONNECTION (new_connection));
+	NMSettingsConnectionClass *klass;
+	gs_free_error GError *local = NULL;
+	gs_unref_object NMConnection *reread_connection = NULL;
+	gs_free char *logmsg_change = NULL;
 
-	NM_SETTINGS_CONNECTION_GET_CLASS (self)->replace_and_commit (self, new_connection, callback, user_data);
-}
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
-static void
-commit_changes (NMSettingsConnection *self,
-                NMSettingsConnectionCommitReason commit_reason,
-                NMSettingsConnectionCommitFunc callback,
-                gpointer user_data)
-{
-	/* Subclasses only call this function if the save was successful, so at
-	 * this point the connection is synced to disk and no longer unsaved.
-	 */
-	set_unsaved (self, FALSE);
+	klass = NM_SETTINGS_CONNECTION_GET_CLASS (self);
+	if (!klass->commit_changes) {
+		_LOGW ("write: setting plugin %s does not support to write connection",
+		       G_OBJECT_TYPE_NAME (self));
+		g_set_error (error,
+		             NM_SETTINGS_ERROR,
+		             NM_SETTINGS_ERROR_FAILED,
+		             "writing settings not supported");
+		return FALSE;
+	}
 
-	g_object_ref (self);
-	callback (self, NULL, user_data);
-	g_object_unref (self);
-}
+	if (   new_connection
+	    && !nm_settings_connection_replace_settings_prepare (self,
+	                                                         new_connection,
+	                                                         &local)) {
+		_LOGW ("write: failed to prepare connection for writing: %s",
+		       local->message);
+		g_propagate_error (error, g_steal_pointer (&local));
+		return FALSE;
+	}
 
-void
-nm_settings_connection_commit_changes (NMSettingsConnection *self,
-                                       NMSettingsConnectionCommitReason commit_reason,
-                                       NMSettingsConnectionCommitFunc callback,
-                                       gpointer user_data)
-{
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
+	if (!klass->commit_changes (self,
+	                            new_connection,
+	                            commit_reason,
+	                            &reread_connection,
+	                            &logmsg_change,
+	                            &local)) {
+		_LOGW ("write: failure to write setting: %s",
+		       local->message);
+		g_propagate_error (error, g_steal_pointer (&local));
+		return FALSE;
+	}
 
-	if (NM_SETTINGS_CONNECTION_GET_CLASS (self)->commit_changes) {
-		NM_SETTINGS_CONNECTION_GET_CLASS (self)->commit_changes (self,
-		                                                         commit_reason,
-		                                                         callback ? callback : ignore_cb,
-		                                                         user_data);
-	} else {
-		GError *error = g_error_new (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_FAILED,
-		                             "%s: %s:%d commit_changes() unimplemented", __func__, __FILE__, __LINE__);
-		if (callback)
-			callback (self, error, user_data);
-		g_error_free (error);
+	if (reread_connection || new_connection) {
+		if (!nm_settings_connection_replace_settings_full (self,
+		                                                   reread_connection ?: new_connection,
+		                                                   !reread_connection,
+		                                                   FALSE,
+		                                                   new_connection
+		                                                     ? "update-during-write"
+		                                                     : "replace-and-commit-disk",
+		                                                   &local)) {
+			/* this can't really happen, because at this point replace-settings
+			 * is no longer supposed to fail. It's a bug. */
+			_LOGE ("write: replacing setting failed: %s",
+			       local->message);
+			g_propagate_error (error, g_steal_pointer (&local));
+			g_return_val_if_reached (FALSE);
+		}
 	}
-}
 
-void
-nm_settings_connection_delete (NMSettingsConnection *self,
-                               NMSettingsConnectionDeleteFunc callback,
-                               gpointer user_data)
-{
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
+	set_unsaved (self, FALSE);
 
-	if (NM_SETTINGS_CONNECTION_GET_CLASS (self)->delete) {
-		NM_SETTINGS_CONNECTION_GET_CLASS (self)->delete (self,
-		                                                 callback ? callback : ignore_cb,
-		                                                 user_data);
-	} else {
-		GError *error = g_error_new (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_FAILED,
-		                             "%s: %s:%d delete() unimplemented", __func__, __FILE__, __LINE__);
-		if (callback)
-			callback (self, error, user_data);
-		g_error_free (error);
-	}
+	if (reread_connection)
+		_LOGI ("write: successfully updated (%s), connection was modified in the process", logmsg_change);
+	else if (new_connection)
+		_LOGI ("write: successfully updated (%s)", logmsg_change);
+	else
+		_LOGI ("write: successfully commited (%s)", logmsg_change);
+
+	return TRUE;
 }
 
 static void
@@ -740,15 +744,32 @@ remove_entry_from_db (NMSettingsConnection *self, const char* db_name)
 	g_key_file_free (key_file);
 }
 
-static void
-do_delete (NMSettingsConnection *self,
-           NMSettingsConnectionDeleteFunc callback,
-           gpointer user_data)
+gboolean
+nm_settings_connection_delete (NMSettingsConnection *self,
+                               GError **error)
 {
+	gs_unref_object NMSettingsConnection *self_keep_alive = NULL;
+	NMSettingsConnectionClass *klass;
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 	NMConnection *for_agents;
 
-	g_object_ref (self);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
+
+	klass = NM_SETTINGS_CONNECTION_GET_CLASS (self);
+
+	self_keep_alive = g_object_ref (self);
+
+	if (!klass->delete) {
+		g_set_error (error,
+		             NM_SETTINGS_ERROR,
+		             NM_SETTINGS_ERROR_FAILED,
+		             "delete not supported");
+		return FALSE;
+	}
+	if (!klass->delete (self,
+	                    error))
+		return FALSE;
+
 	set_visible (self, FALSE);
 
 	/* Tell agents to remove secrets for this connection */
@@ -766,12 +787,10 @@ do_delete (NMSettingsConnection *self,
 	remove_entry_from_db (self, "seen-bssids");
 
 	nm_settings_connection_signal_remove (self, FALSE);
-
-	callback (self, NULL, user_data);
-
-	g_object_unref (self);
+	return TRUE;
 }
 
+
 /*****************************************************************************/
 
 
@@ -887,15 +906,6 @@ secret_is_system_owned (NMSettingSecretFlags flags,
 }
 
 static void
-new_secrets_commit_cb (NMSettingsConnection *self,
-                       GError *error,
-                       gpointer user_data)
-{
-	if (error)
-		_LOGW ("Error saving new secrets to backing storage: %s", error->message);
-}
-
-static void
 get_cmp_flags (NMSettingsConnection *self, /* only needed for logging */
                GetSecretsInfo *info, /* only needed for logging */
                NMConnection *connection,
@@ -974,6 +984,33 @@ get_cmp_flags (NMSettingsConnection *self, /* only needed for logging */
 	}
 }
 
+gboolean
+nm_settings_connection_new_secrets (NMSettingsConnection *self,
+                                    NMConnection *applied_connection,
+                                    const char *setting_name,
+                                    GVariant *secrets,
+                                    GError **error)
+{
+	if (!nm_settings_connection_has_unmodified_applied_connection (self, applied_connection,
+	                                                              NM_SETTING_COMPARE_FLAG_NONE)) {
+		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		                     "The connection was modified since activation");
+		return FALSE;
+	}
+
+	if (!nm_connection_update_secrets (NM_CONNECTION (self), setting_name, secrets, error))
+		return FALSE;
+
+	update_system_secrets_cache (self);
+	update_agent_secrets_cache (self, NULL);
+
+	nm_settings_connection_commit_changes (self,
+	                                       NULL,
+	                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
+	                                       NULL);
+	return TRUE;
+}
+
 static void
 get_secrets_done_cb (NMAgentManager *manager,
                      NMAgentManagerCallId call_id_a,
@@ -1084,7 +1121,10 @@ get_secrets_done_cb (NMAgentManager *manager,
 				       setting_name,
 				       info);
 
-				nm_settings_connection_commit_changes (self, NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE, new_secrets_commit_cb, NULL);
+				nm_settings_connection_commit_changes (self,
+				                                       NULL,
+				                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
+				                                       NULL);
 			} else {
 				_LOGD ("(%s:%p) new agent secrets processed",
 				       setting_name,
@@ -1545,11 +1585,6 @@ typedef struct {
 	char *audit_args;
 } UpdateInfo;
 
-typedef struct {
-	GDBusMethodInvocation *context;
-	NMAuthSubject *subject;
-} CallbackInfo;
-
 static void
 has_some_secrets_cb (NMSetting *setting,
                      const char *key,
@@ -1626,33 +1661,6 @@ update_complete (NMSettingsConnection *self,
 }
 
 static void
-con_update_cb (NMSettingsConnection *self,
-               GError *error,
-               gpointer user_data)
-{
-	UpdateInfo *info = user_data;
-	NMConnection *for_agent;
-
-	if (!error) {
-		/* Dupe the connection so we can clear out non-agent-owned secrets,
-		 * 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));
-		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 (info->agent_mgr,
-		                               nm_connection_get_path (NM_CONNECTION (self)),
-		                               for_agent,
-		                               info->subject);
-		g_object_unref (for_agent);
-	}
-
-	update_complete (self, info, error);
-}
-
-static void
 update_auth_cb (NMSettingsConnection *self,
                 GDBusMethodInvocation *context,
                 NMAuthSubject *subject,
@@ -1660,63 +1668,93 @@ update_auth_cb (NMSettingsConnection *self,
                 gpointer data)
 {
 	UpdateInfo *info = data;
-	GError *local = NULL;
+	NMSettingsConnectionCommitReason commit_reason;
+	gs_free_error GError *local = NULL;
 
 	if (error) {
 		update_complete (self, info, error);
 		return;
 	}
 
-	if (!info->new_settings) {
-		/* We're just calling Save(). Just commit the existing connection. */
-		if (info->save_to_disk) {
-			nm_settings_connection_commit_changes (self,
-			                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION,
-			                                       con_update_cb,
-			                                       info);
+	if (info->new_settings) {
+		if (!any_secrets_present (info->new_settings)) {
+			/* If the new connection has no secrets, we do not want to remove all
+			 * secrets, rather we keep all the existing ones. Do that by merging
+			 * them in to the new connection.
+			 */
+			cached_secrets_to_connection (self, info->new_settings);
+		} else {
+			/* Cache the new secrets from the agent, as stuff like inotify-triggered
+			 * changes to connection's backing config files will blow them away if
+			 * they're in the main connection.
+			 */
+			update_agent_secrets_cache (self, info->new_settings);
 		}
-		return;
-	}
 
-	if (!any_secrets_present (info->new_settings)) {
-		/* If the new connection has no secrets, we do not want to remove all
-		 * secrets, rather we keep all the existing ones. Do that by merging
-		 * them in to the new connection.
-		 */
-		cached_secrets_to_connection (self, info->new_settings);
-	} else {
-		/* Cache the new secrets from the agent, as stuff like inotify-triggered
-		 * changes to connection's backing config files will blow them away if
-		 * they're in the main connection.
-		 */
-		update_agent_secrets_cache (self, info->new_settings);
+		if (nm_audit_manager_audit_enabled (nm_audit_manager_get ())) {
+			gs_unref_hashtable GHashTable *diff = NULL;
+			gboolean same;
+
+			same = nm_connection_diff (NM_CONNECTION (self), info->new_settings,
+			                           NM_SETTING_COMPARE_FLAG_EXACT |
+			                           NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT,
+			                           &diff);
+			if (!same && diff)
+				info->audit_args = nm_utils_format_con_diff_for_audit (diff);
+		}
 	}
 
-	if (nm_audit_manager_audit_enabled (nm_audit_manager_get ())) {
-		gs_unref_hashtable GHashTable *diff = NULL;
-		gboolean same;
+	if (!info->save_to_disk) {
+		if (info->new_settings) {
+			nm_settings_connection_replace_settings (self,
+			                                         info->new_settings,
+			                                         TRUE,
+			                                         "replace-unsaved",
+			                                         &local);
+		}
+		goto out;
+	}
 
-		same = nm_connection_diff (NM_CONNECTION (self), info->new_settings,
-		                           NM_SETTING_COMPARE_FLAG_EXACT |
-		                           NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT,
-		                           &diff);
-		if (!same && diff)
-			info->audit_args = nm_utils_format_con_diff_for_audit (diff);
+	if (info->new_settings) {
+		if (!nm_settings_connection_replace_settings_prepare (self,
+		                                                      info->new_settings,
+		                                                      &local))
+			goto out;
 	}
 
-	if (info->save_to_disk) {
-		nm_settings_connection_replace_and_commit (self,
-		                                           info->new_settings,
-		                                           con_update_cb,
-		                                           info);
-	} else {
-		if (!nm_settings_connection_replace_settings (self, info->new_settings, TRUE, "replace-and-commit-memory", &local))
-			g_assert (local);
-		con_update_cb (self, local, info);
-		g_clear_error (&local);
+	commit_reason = NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION;
+	if (   info->new_settings
+	    && !nm_streq0 (nm_connection_get_id (NM_CONNECTION (self)),
+	                   nm_connection_get_id (info->new_settings)))
+		commit_reason |= NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED;
+
+	nm_settings_connection_commit_changes (self,
+	                                       info->new_settings,
+	                                       commit_reason,
+	                                       &local);
+
+out:
+	if (!local) {
+		gs_unref_object NMConnection *for_agent = NULL;
+
+		/* Dupe the connection so we can clear out non-agent-owned secrets,
+		 * 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));
+		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 (info->agent_mgr,
+		                               nm_connection_get_path (NM_CONNECTION (self)),
+		                               for_agent,
+		                               info->subject);
 	}
+
+	update_complete (self, info, local);
 }
 
+
 static const char *
 get_update_modify_permission (NMConnection *old, NMConnection *new)
 {
@@ -1840,30 +1878,16 @@ impl_settings_connection_save (NMSettingsConnection *self,
 }
 
 static void
-con_delete_cb (NMSettingsConnection *self,
-               GError *error,
-               gpointer user_data)
-{
-	CallbackInfo *info = user_data;
-
-	if (error)
-		g_dbus_method_invocation_return_gerror (info->context, error);
-	else
-		g_dbus_method_invocation_return_value (info->context, NULL);
-
-	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DELETE, self,
-	                            !error, NULL, info->subject, error ? error->message : NULL);
-	g_free (info);
-}
-
-static void
 delete_auth_cb (NMSettingsConnection *self,
                 GDBusMethodInvocation *context,
                 NMAuthSubject *subject,
                 GError *error,
                 gpointer data)
 {
-	CallbackInfo *info;
+	gs_unref_object NMSettingsConnection *self_keep_alive = NULL;
+	gs_free_error GError *local = NULL;
+
+	self_keep_alive = g_object_ref (self);
 
 	if (error) {
 		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DELETE, self, FALSE, NULL, subject,
@@ -1872,11 +1896,15 @@ delete_auth_cb (NMSettingsConnection *self,
 		return;
 	}
 
-	info = g_malloc0 (sizeof (*info));
-	info->context = context;
-	info->subject = subject;
+	nm_settings_connection_delete (self, &local);
 
-	nm_settings_connection_delete (self, con_delete_cb, info);
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_DELETE, self,
+	                            !local, NULL, subject, local ? local->message : NULL);
+
+	if (local)
+		g_dbus_method_invocation_return_gerror (context, local);
+	else
+		g_dbus_method_invocation_return_value (context, NULL);
 }
 
 static const char *
@@ -1996,23 +2024,6 @@ impl_settings_connection_get_secrets (NMSettingsConnection *self,
 }
 
 static void
-clear_secrets_cb (NMSettingsConnection *self,
-                  GError *error,
-                  gpointer user_data)
-{
-	CallbackInfo *info = user_data;
-
-	if (error)
-		g_dbus_method_invocation_return_gerror (info->context, error);
-	else
-		g_dbus_method_invocation_return_value (info->context, NULL);
-
-	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_CLEAR_SECRETS, self,
-	                            !error, NULL, info->subject, error ? error->message : NULL);
-	g_free (info);
-}
-
-static void
 dbus_clear_secrets_auth_cb (NMSettingsConnection *self,
                             GDBusMethodInvocation *context,
                             NMAuthSubject *subject,
@@ -2020,31 +2031,39 @@ dbus_clear_secrets_auth_cb (NMSettingsConnection *self,
                             gpointer user_data)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	CallbackInfo *info;
+	gs_free_error GError *local = NULL;
 
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
 		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_CLEAR_SECRETS, self,
 		                            FALSE, NULL, subject, error->message);
-	} else {
-		/* Clear secrets in connection and caches */
-		nm_connection_clear_secrets (NM_CONNECTION (self));
-		if (priv->system_secrets)
-			nm_connection_clear_secrets (priv->system_secrets);
-		if (priv->agent_secrets)
-			nm_connection_clear_secrets (priv->agent_secrets);
+		return;
+	}
+
+	/* Clear secrets in connection and caches */
+	nm_connection_clear_secrets (NM_CONNECTION (self));
+	if (priv->system_secrets)
+		nm_connection_clear_secrets (priv->system_secrets);
+	if (priv->agent_secrets)
+		nm_connection_clear_secrets (priv->agent_secrets);
 
-		/* Tell agents to remove secrets for this connection */
-		nm_agent_manager_delete_secrets (priv->agent_mgr,
-		                                 nm_connection_get_path (NM_CONNECTION (self)),
-		                                 NM_CONNECTION (self));
+	/* Tell agents to remove secrets for this connection */
+	nm_agent_manager_delete_secrets (priv->agent_mgr,
+	                                 nm_connection_get_path (NM_CONNECTION (self)),
+	                                 NM_CONNECTION (self));
 
-		info = g_malloc0 (sizeof (*info));
-		info->context = context;
-		info->subject = subject;
+	nm_settings_connection_commit_changes (self,
+	                                       NULL,
+	                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
+	                                       &local);
 
-		nm_settings_connection_commit_changes (self, NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE, clear_secrets_cb, info);
-	}
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_CLEAR_SECRETS, self,
+	                            !local, NULL, subject, local ? local->message : NULL);
+
+	if (local)
+		g_dbus_method_invocation_return_gerror (context, local);
+	else
+		g_dbus_method_invocation_return_value (context, NULL);
 }
 
 static void
@@ -2512,8 +2531,10 @@ nm_settings_connection_read_and_fill_seen_bssids (NMSettingsConnection *self)
 	}
 }
 
+/*****************************************************************************/
+
 /**
- * nm_settings_connection_get_autoconnect_retries:
+ * nm_settings_connection_autoconnect_retries_get:
  * @self: the settings connection
  *
  * Returns the number of autoconnect retries left. If the value is
@@ -2521,14 +2542,13 @@ nm_settings_connection_read_and_fill_seen_bssids (NMSettingsConnection *self)
  * with the global default.
  */
 int
-nm_settings_connection_get_autoconnect_retries (NMSettingsConnection *self)
+nm_settings_connection_autoconnect_retries_get (NMSettingsConnection *self)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
-	if (priv->autoconnect_retries == AUTOCONNECT_RETRIES_UNSET) {
+	if (G_UNLIKELY (priv->autoconnect_retries == AUTOCONNECT_RETRIES_UNSET)) {
 		NMSettingConnection *s_con;
 		int retries = -1;
-		const char *value;
 
 		s_con = nm_connection_get_setting_connection ((NMConnection *) self);
 		if (s_con)
@@ -2536,20 +2556,18 @@ nm_settings_connection_get_autoconnect_retries (NMSettingsConnection *self)
 
 		/* -1 means 'default' */
 		if (retries == -1) {
-			value = nm_config_data_get_value_cached (NM_CONFIG_GET_DATA,
-			                                         NM_CONFIG_KEYFILE_GROUP_MAIN,
-			                                         "autoconnect-retries-default",
-			                                         NM_CONFIG_GET_VALUE_STRIP);
-
-			retries = _nm_utils_ascii_str_to_int64 (value,
-			                                        10, 0, G_MAXINT32,
-			                                        AUTOCONNECT_RETRIES_DEFAULT);
+			retries = nm_config_data_get_value_int64 (NM_CONFIG_GET_DATA,
+			                                          NM_CONFIG_KEYFILE_GROUP_MAIN,
+			                                          "autoconnect-retries-default",
+			                                          10, 0, G_MAXINT32,
+			                                          AUTOCONNECT_RETRIES_DEFAULT);
 		}
 
 		/* 0 means 'forever', which is translated to a retry count of -1 */
 		if (retries == 0)
 			retries = AUTOCONNECT_RETRIES_FOREVER;
 
+		_LOGT ("autoconnect-retries: init %d", retries);
 		priv->autoconnect_retries = retries;
 	}
 
@@ -2557,74 +2575,57 @@ nm_settings_connection_get_autoconnect_retries (NMSettingsConnection *self)
 }
 
 void
-nm_settings_connection_set_autoconnect_retries (NMSettingsConnection *self,
+nm_settings_connection_autoconnect_retries_set (NMSettingsConnection *self,
                                                 int retries)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	NMSettingsConnectionPrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
+	nm_assert (retries == AUTOCONNECT_RETRIES_UNSET || retries >= 0);
+
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
 	if (priv->autoconnect_retries != retries) {
 		_LOGT ("autoconnect-retries: set %d", retries);
 		priv->autoconnect_retries = retries;
 	}
 	if (retries)
-		priv->autoconnect_retry_time = 0;
+		priv->autoconnect_blocked_until = 0;
 	else
-		priv->autoconnect_retry_time = nm_utils_get_monotonic_timestamp_s () + AUTOCONNECT_RESET_RETRIES_TIMER;
+		priv->autoconnect_blocked_until = nm_utils_get_monotonic_timestamp_s () + AUTOCONNECT_RESET_RETRIES_TIMER;
 }
 
 void
-nm_settings_connection_reset_autoconnect_retries (NMSettingsConnection *self)
+nm_settings_connection_autoconnect_retries_reset (NMSettingsConnection *self)
 {
-	nm_settings_connection_set_autoconnect_retries (self, AUTOCONNECT_RETRIES_UNSET);
+	nm_settings_connection_autoconnect_retries_set (self, AUTOCONNECT_RETRIES_UNSET);
 }
 
 gint32
-nm_settings_connection_get_autoconnect_retry_time (NMSettingsConnection *self)
+nm_settings_connection_autoconnect_blocked_until_get (NMSettingsConnection *self)
 {
-	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_retry_time;
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_blocked_until;
 }
 
 NMSettingsAutoconnectBlockedReason
-nm_settings_connection_get_autoconnect_blocked_reason (NMSettingsConnection *self)
+nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self)
 {
 	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_blocked_reason;
 }
 
 void
-nm_settings_connection_set_autoconnect_blocked_reason (NMSettingsConnection *self,
+nm_settings_connection_autoconnect_blocked_reason_set (NMSettingsConnection *self,
                                                        NMSettingsAutoconnectBlockedReason reason)
 {
 	g_return_if_fail (NM_IN_SET (reason,
-	                             NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_UNBLOCKED,
-	                             NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_BLOCKED,
+	                             NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE,
+	                             NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+	                             NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
 	                             NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS));
 	NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->autoconnect_blocked_reason = reason;
 }
 
-gboolean
-nm_settings_connection_can_autoconnect (NMSettingsConnection *self)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
-	const char *permission;
-
-	if (   !priv->visible
-	    || priv->autoconnect_retries == 0
-	    || priv->autoconnect_blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_UNBLOCKED)
-		return FALSE;
-
-	s_con = nm_connection_get_setting_connection (NM_CONNECTION (self));
-	if (!nm_setting_connection_get_autoconnect (s_con))
-		return FALSE;
-
-	permission = nm_utils_get_shared_wifi_permission (NM_CONNECTION (self));
-	if (permission) {
-		if (nm_settings_connection_check_permission (self, permission) == FALSE)
-			return FALSE;
-	}
-
-	return TRUE;
-}
+/*****************************************************************************/
 
 /**
  * nm_settings_connection_get_nm_generated:
@@ -2747,7 +2748,7 @@ nm_settings_connection_init (NMSettingsConnection *self)
 
 	priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
 
-	priv->seen_bssids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	priv->seen_bssids = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
 	priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET;
 
@@ -2873,9 +2874,6 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *class)
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
-	class->replace_and_commit = replace_and_commit;
-	class->commit_changes = commit_changes;
-	class->delete = do_delete;
 	class->supports_secrets = supports_secrets;
 
 	obj_properties[PROP_VISIBLE] =
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index b449e2bd..faacd949 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -83,9 +83,10 @@ typedef enum { /*< skip >*/
 } NMSettingsConnectionCommitReason;
 
 typedef enum {
-	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_UNBLOCKED               = 0,
-	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_BLOCKED                 = 1,
-	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS              = 2,
+	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE                    = 0,
+	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST            = 1,
+	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED                  = 2,
+	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS              = 3,
 } NMSettingsAutoconnectBlockedReason;
 
 struct _NMSettingsConnectionCallId;
@@ -93,14 +94,6 @@ typedef struct _NMSettingsConnectionCallId *NMSettingsConnectionCallId;
 
 typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass;
 
-typedef void (*NMSettingsConnectionCommitFunc) (NMSettingsConnection *self,
-                                                GError *error,
-                                                gpointer user_data);
-
-typedef void (*NMSettingsConnectionDeleteFunc) (NMSettingsConnection *self,
-                                                GError *error,
-                                                gpointer user_data);
-
 struct _NMSettingsConnectionPrivate;
 
 struct _NMSettingsConnection {
@@ -111,20 +104,15 @@ struct _NMSettingsConnection {
 struct _NMSettingsConnectionClass {
 	NMExportedObjectClass parent;
 
-	/* virtual methods */
-	void (*replace_and_commit) (NMSettingsConnection *self,
+	gboolean (*commit_changes) (NMSettingsConnection *self,
 	                            NMConnection *new_connection,
-	                            NMSettingsConnectionCommitFunc callback,
-	                            gpointer user_data);
-
-	void (*commit_changes) (NMSettingsConnection *self,
-	                        NMSettingsConnectionCommitReason commit_reason,
-	                        NMSettingsConnectionCommitFunc callback,
-	                        gpointer user_data);
+	                            NMSettingsConnectionCommitReason commit_reason,
+	                            NMConnection **out_reread_connection,
+	                            char **out_logmsg_change,
+	                            GError **error);
 
-	void (*delete) (NMSettingsConnection *self,
-	                NMSettingsConnectionDeleteFunc callback,
-	                gpointer user_data);
+	gboolean (*delete) (NMSettingsConnection *self,
+	                    GError **error);
 
 	gboolean (*supports_secrets) (NMSettingsConnection *self,
 	                              const char *setting_name);
@@ -136,10 +124,14 @@ gboolean nm_settings_connection_has_unmodified_applied_connection (NMSettingsCon
                                                                    NMConnection *applied_connection,
                                                                    NMSettingCompareFlags compare_flage);
 
-void nm_settings_connection_commit_changes (NMSettingsConnection *self,
-                                            NMSettingsConnectionCommitReason commit_reason,
-                                            NMSettingsConnectionCommitFunc callback,
-                                            gpointer user_data);
+gboolean  nm_settings_connection_commit_changes (NMSettingsConnection *self,
+                                                 NMConnection *new_connection,
+                                                 NMSettingsConnectionCommitReason commit_reason,
+                                                 GError **error);
+
+gboolean nm_settings_connection_replace_settings_prepare (NMSettingsConnection *self,
+                                                          NMConnection *new_connection,
+                                                          GError **error);
 
 gboolean nm_settings_connection_replace_settings (NMSettingsConnection *self,
                                                   NMConnection *new_connection,
@@ -147,14 +139,15 @@ gboolean nm_settings_connection_replace_settings (NMSettingsConnection *self,
                                                   const char *log_diff_name,
                                                   GError **error);
 
-void nm_settings_connection_replace_and_commit (NMSettingsConnection *self,
-                                                NMConnection *new_connection,
-                                                NMSettingsConnectionCommitFunc callback,
-                                                gpointer user_data);
+gboolean nm_settings_connection_replace_settings_full (NMSettingsConnection *self,
+                                                       NMConnection *new_connection,
+                                                       gboolean prepare_new_connection,
+                                                       gboolean update_unsaved,
+                                                       const char *log_diff_name,
+                                                       GError **error);
 
-void nm_settings_connection_delete (NMSettingsConnection *self,
-                                    NMSettingsConnectionDeleteFunc callback,
-                                    gpointer user_data);
+gboolean nm_settings_connection_delete (NMSettingsConnection *self,
+                                        GError **error);
 
 typedef void (*NMSettingsConnectionSecretsFunc) (NMSettingsConnection *self,
                                                  NMSettingsConnectionCallId call_id,
@@ -163,6 +156,12 @@ typedef void (*NMSettingsConnectionSecretsFunc) (NMSettingsConnection *self,
                                                  GError *error,
                                                  gpointer user_data);
 
+gboolean nm_settings_connection_new_secrets (NMSettingsConnection *self,
+                                             NMConnection *applied_connection,
+                                             const char *setting_name,
+                                             GVariant *secrets,
+                                             GError **error);
+
 NMSettingsConnectionCallId nm_settings_connection_get_secrets (NMSettingsConnection *self,
                                                                NMConnection *applied_connection,
                                                                NMAuthSubject *subject,
@@ -214,19 +213,17 @@ void nm_settings_connection_add_seen_bssid (NMSettingsConnection *self,
 
 void nm_settings_connection_read_and_fill_seen_bssids (NMSettingsConnection *self);
 
-int nm_settings_connection_get_autoconnect_retries (NMSettingsConnection *self);
-void nm_settings_connection_set_autoconnect_retries (NMSettingsConnection *self,
+int nm_settings_connection_autoconnect_retries_get (NMSettingsConnection *self);
+void nm_settings_connection_autoconnect_retries_set (NMSettingsConnection *self,
                                                      int retries);
-void nm_settings_connection_reset_autoconnect_retries (NMSettingsConnection *self);
+void nm_settings_connection_autoconnect_retries_reset (NMSettingsConnection *self);
 
-gint32 nm_settings_connection_get_autoconnect_retry_time (NMSettingsConnection *self);
+gint32 nm_settings_connection_autoconnect_blocked_until_get (NMSettingsConnection *self);
 
-NMSettingsAutoconnectBlockedReason nm_settings_connection_get_autoconnect_blocked_reason (NMSettingsConnection *self);
-void                               nm_settings_connection_set_autoconnect_blocked_reason (NMSettingsConnection *self,
+NMSettingsAutoconnectBlockedReason nm_settings_connection_autoconnect_blocked_reason_get (NMSettingsConnection *self);
+void                               nm_settings_connection_autoconnect_blocked_reason_set (NMSettingsConnection *self,
                                                                                           NMSettingsAutoconnectBlockedReason reason);
 
-gboolean nm_settings_connection_can_autoconnect (NMSettingsConnection *self);
-
 gboolean nm_settings_connection_get_nm_generated (NMSettingsConnection *self);
 gboolean nm_settings_connection_get_volatile (NMSettingsConnection *self);
 
diff --git a/src/settings/nm-settings-plugin.c b/src/settings/nm-settings-plugin.c
index 7de7e597..234e345f 100644
--- a/src/settings/nm-settings-plugin.c
+++ b/src/settings/nm-settings-plugin.c
@@ -169,13 +169,17 @@ nm_settings_plugin_add_connection (NMSettingsPlugin *config,
                                    gboolean save_to_disk,
                                    GError **error)
 {
+	NMSettingsPluginInterface *config_interface;
+
 	g_return_val_if_fail (config != NULL, NULL);
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 
-	if (NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->add_connection)
-		return NM_SETTINGS_PLUGIN_GET_INTERFACE (config)->add_connection (config, connection, save_to_disk, error);
+	config_interface = NM_SETTINGS_PLUGIN_GET_INTERFACE (config);
+	if (!config_interface->add_connection) {
+		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
+		                     "Plugin does not support adding connections");
+		return NULL;
+	}
 
-	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);
 }
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index afd1b084..e2b467a2 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -76,6 +76,7 @@
 #include "NetworkManagerUtils.h"
 #include "nm-dispatcher.h"
 #include "nm-inotify-helper.h"
+#include "nm-hostname-manager.h"
 
 #include "introspection/org.freedesktop.NetworkManager.Settings.h"
 
@@ -90,20 +91,9 @@ EXPORT(nm_inotify_helper_remove_watch)
 
 EXPORT(nm_settings_connection_get_type)
 EXPORT(nm_settings_connection_replace_settings)
-EXPORT(nm_settings_connection_replace_and_commit)
 
 /*****************************************************************************/
 
-#define HOSTNAMED_SERVICE_NAME      "org.freedesktop.hostname1"
-#define HOSTNAMED_SERVICE_PATH      "/org/freedesktop/hostname1"
-#define HOSTNAMED_SERVICE_INTERFACE "org.freedesktop.hostname1"
-
-#define HOSTNAME_FILE_DEFAULT        "/etc/hostname"
-#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"
-
 static NM_CACHED_QUARK_FCN ("plugin-module-path", plugin_module_path_quark)
 
 #if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) + defined(HOSTNAME_PERSIST_GENTOO)) > 1
@@ -162,14 +152,8 @@ typedef struct {
 	gboolean started;
 	gboolean startup_complete;
 
-	struct {
-		char *value;
-		GFileMonitor *monitor;
-		GFileMonitor *dhcp_monitor;
-		gulong monitor_id;
-		gulong dhcp_monitor_id;
-		GDBusProxy *hostnamed_proxy;
-	} hostname;
+	NMHostnameManager *hostname_manager;
+
 } NMSettingsPrivate;
 
 struct _NMSettings {
@@ -568,131 +552,6 @@ get_plugin (NMSettings *self, guint32 capability)
 	return NULL;
 }
 
-#if defined(HOSTNAME_PERSIST_GENTOO)
-static gchar *
-read_hostname_gentoo (const char *path)
-{
-	gs_free char *contents = NULL;
-	gs_strfreev char **all_lines = NULL;
-	const char *tmp;
-	guint i;
-
-	if (!g_file_get_contents (path, &contents, NULL, NULL))
-		return NULL;
-
-	all_lines = g_strsplit (contents, "\n", 0);
-	for (i = 0; all_lines[i]; i++) {
-		g_strstrip (all_lines[i]);
-		if (all_lines[i][0] == '#' || all_lines[i][0] == '\0')
-			continue;
-		if (g_str_has_prefix (all_lines[i], "hostname=")) {
-			tmp = &all_lines[i][NM_STRLEN ("hostname=")];
-			return g_shell_unquote (tmp, NULL);
-		}
-	}
-	return NULL;
-}
-#endif
-
-#if defined(HOSTNAME_PERSIST_SLACKWARE)
-static gchar *
-read_hostname_slackware (const char *path)
-{
-	gs_free char *contents = NULL;
-	gs_strfreev char **all_lines = NULL;
-	char *tmp;
-	guint i, j = 0;
-
-	if (!g_file_get_contents (path, &contents, NULL, NULL))
-		return NULL;
-
-	all_lines = g_strsplit (contents, "\n", 0);
-	for (i = 0; all_lines[i]; i++) {
-		g_strstrip (all_lines[i]);
-		if (all_lines[i][0] == '#' || all_lines[i][0] == '\0')
-			continue;
-		tmp = &all_lines[i][0];
-		/* We only want up to the first '.' -- the rest of the */
-		/* fqdn is defined in /etc/hosts */
-		while (tmp[j] != '\0') {
-			if (tmp[j] == '.') {
-				tmp[j] = '\0';
-				break;
-			}
-			j++;
-		}
-		return g_shell_unquote (tmp, NULL);
-	}
-	return NULL;
-}
-#endif
-
-#if defined(HOSTNAME_PERSIST_SUSE)
-static gboolean
-hostname_is_dynamic (void)
-{
-	GIOChannel *channel;
-	char *str = NULL;
-	gboolean dynamic = FALSE;
-
-	channel = g_io_channel_new_file (CONF_DHCP, "r", NULL);
-	if (!channel)
-		return dynamic;
-
-	while (g_io_channel_read_line (channel, &str, NULL, NULL, NULL) != G_IO_STATUS_EOF) {
-		if (str) {
-			g_strstrip (str);
-			if (g_str_has_prefix (str, "DHCLIENT_SET_HOSTNAME="))
-				dynamic = strcmp (&str[NM_STRLEN ("DHCLIENT_SET_HOSTNAME=")], "\"yes\"") == 0;
-			g_free (str);
-		}
-	}
-
-	g_io_channel_shutdown (channel, FALSE, NULL);
-	g_io_channel_unref (channel);
-
-	return dynamic;
-}
-#endif
-
-/* Returns an allocated string which the caller owns and must eventually free */
-char *
-nm_settings_get_hostname (NMSettings *self)
-{
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	char *hostname = NULL;
-
-	if (!priv->started)
-		return NULL;
-
-	if (priv->hostname.hostnamed_proxy) {
-		hostname = g_strdup (priv->hostname.value);
-		goto out;
-	}
-
-#if defined(HOSTNAME_PERSIST_SUSE)
-	if (priv->hostname.dhcp_monitor_id && hostname_is_dynamic ())
-		return NULL;
-#endif
-
-#if defined(HOSTNAME_PERSIST_GENTOO)
-	hostname = read_hostname_gentoo (HOSTNAME_FILE);
-#elif defined(HOSTNAME_PERSIST_SLACKWARE)
-	hostname = read_hostname_slackware (HOSTNAME_FILE);
-#else
-	if (g_file_get_contents (HOSTNAME_FILE, &hostname, NULL, NULL))
-		g_strchomp (hostname);
-#endif
-
-out:
-	if (hostname && !hostname[0]) {
-		g_free (hostname);
-		hostname = NULL;
-	}
-
-	return hostname;
-}
-
 static gboolean
 find_spec (GSList *spec_list, const char *spec)
 {
@@ -1626,160 +1485,7 @@ impl_settings_reload_connections (NMSettings *self,
 	g_dbus_method_invocation_return_value (context, g_variant_new ("(b)", TRUE));
 }
 
-typedef struct {
-	char *hostname;
-	NMSettingsSetHostnameCb cb;
-	gpointer user_data;
-} SetHostnameInfo;
-
-static void
-set_transient_hostname_done (GObject *object,
-                             GAsyncResult *res,
-                             gpointer user_data)
-{
-	GDBusProxy *proxy = G_DBUS_PROXY (object);
-	gs_free SetHostnameInfo *info = user_data;
-	gs_unref_variant GVariant *result = NULL;
-	gs_free_error GError *error = NULL;
-
-	result = g_dbus_proxy_call_finish (proxy, res, &error);
-
-	if (error) {
-		_LOGW ("couldn't set the system hostname to '%s' using hostnamed: %s",
-		       info->hostname, error->message);
-	}
-
-	info->cb (info->hostname, !error, info->user_data);
-	g_free (info->hostname);
-}
-
-void
-nm_settings_set_transient_hostname (NMSettings *self,
-                                    const char *hostname,
-                                    NMSettingsSetHostnameCb cb,
-                                    gpointer user_data)
-{
-	NMSettingsPrivate *priv;
-	SetHostnameInfo *info;
-
-	g_return_if_fail (NM_IS_SETTINGS (self));
-	priv = NM_SETTINGS_GET_PRIVATE (self);
-
-	if (!priv->hostname.hostnamed_proxy) {
-		cb (hostname, FALSE, user_data);
-		return;
-	}
-
-	info = g_new0 (SetHostnameInfo, 1);
-	info->hostname = g_strdup (hostname);
-	info->cb = cb;
-	info->user_data = user_data;
-
-	g_dbus_proxy_call (priv->hostname.hostnamed_proxy,
-	                   "SetHostname",
-	                   g_variant_new ("(sb)", hostname, FALSE),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   -1,
-	                   NULL,
-	                   set_transient_hostname_done,
-	                   info);
-}
-
-gboolean
-nm_settings_get_transient_hostname (NMSettings *self, char **hostname)
-{
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GVariant *v_hostname;
-
-	if (!priv->hostname.hostnamed_proxy)
-		return FALSE;
-
-	v_hostname = g_dbus_proxy_get_cached_property (priv->hostname.hostnamed_proxy,
-	                                               "Hostname");
-	if (!v_hostname) {
-		_LOGT ("transient hostname retrieval failed");
-		return FALSE;
-	}
-
-	*hostname = g_variant_dup_string (v_hostname, NULL);
-	g_variant_unref (v_hostname);
-
-	return TRUE;
-}
-
-static gboolean
-write_hostname (NMSettingsPrivate *priv, const char *hostname)
-{
-	char *hostname_eol;
-	gboolean ret;
-	gs_free_error GError *error = NULL;
-	const char *file = HOSTNAME_FILE;
-	gs_free char *link_path = NULL;
-	gs_unref_variant GVariant *var = NULL;
-	struct stat file_stat;
-#if HAVE_SELINUX
-	security_context_t se_ctx_prev = NULL, se_ctx = NULL;
-	mode_t st_mode = 0;
-#endif
-
-	if (priv->hostname.hostnamed_proxy) {
-		var = g_dbus_proxy_call_sync (priv->hostname.hostnamed_proxy,
-		                              "SetStaticHostname",
-		                              g_variant_new ("(sb)", hostname, FALSE),
-		                              G_DBUS_CALL_FLAGS_NONE,
-		                              -1,
-		                              NULL,
-		                              &error);
-		if (error)
-			_LOGW ("could not set hostname: %s", error->message);
-
-		return !error;
-	}
-
-	/* If the hostname file is a symbolic link, follow it to find where the
-	 * real file is located, otherwise g_file_set_contents will attempt to
-	 * replace the link with a plain file.
-	 */
-	if (   lstat (file, &file_stat) == 0
-	    && S_ISLNK (file_stat.st_mode)
-	    && (link_path = nm_utils_read_link_absolute (file, NULL)))
-		file = link_path;
-
-#if HAVE_SELINUX
-	/* Get default context for hostname file and set it for fscreate */
-	if (stat (file, &file_stat) == 0)
-		st_mode = file_stat.st_mode;
-	matchpathcon (file, st_mode, &se_ctx);
-	matchpathcon_fini ();
-	getfscreatecon (&se_ctx_prev);
-	setfscreatecon (se_ctx);
-#endif
-
-#if defined (HOSTNAME_PERSIST_GENTOO)
-	hostname_eol = g_strdup_printf ("#Generated by NetworkManager\n"
-	                                "hostname=\"%s\"\n", hostname);
-#else
-	hostname_eol = g_strdup_printf ("%s\n", hostname);
-#endif
-
-	ret = g_file_set_contents (file, hostname_eol, -1, &error);
-
-#if HAVE_SELINUX
-	/* Restore previous context and cleanup */
-	setfscreatecon (se_ctx_prev);
-	freecon (se_ctx);
-	freecon (se_ctx_prev);
-#endif
-
-	g_free (hostname_eol);
-
-	if (!ret) {
-		_LOGW ("could not save hostname to %s: %s", file, error->message);
-		return FALSE;
-	}
-
-	return TRUE;
-}
+/*****************************************************************************/
 
 static void
 pk_hostname_cb (NMAuthChain *chain,
@@ -1812,7 +1518,7 @@ pk_hostname_cb (NMAuthChain *chain,
 	} else {
 		hostname = nm_auth_chain_get_data (chain, "hostname");
 
-		if (!write_hostname (priv, hostname)) {
+		if (!nm_hostname_manager_write_hostname (priv->hostname_manager, hostname)) {
 			error = g_error_new_literal (NM_SETTINGS_ERROR,
 			                             NM_SETTINGS_ERROR_FAILED,
 			                             "Saving the hostname failed.");
@@ -1827,33 +1533,6 @@ pk_hostname_cb (NMAuthChain *chain,
 	nm_auth_chain_unref (chain);
 }
 
-static gboolean
-validate_hostname (const char *hostname)
-{
-	const char *p;
-	gboolean dot = TRUE;
-
-	if (!hostname || !hostname[0])
-		return FALSE;
-
-	for (p = hostname; *p; p++) {
-		if (*p == '.') {
-			if (dot)
-				return FALSE;
-			dot = TRUE;
-		} else {
-			if (!g_ascii_isalnum (*p) && (*p != '-') && (*p != '_'))
-				return FALSE;
-			dot = FALSE;
-		}
-	}
-
-	if (dot)
-		return FALSE;
-
-	return (p - hostname <= HOST_NAME_MAX);
-}
-
 static void
 impl_settings_save_hostname (NMSettings *self,
                              GDBusMethodInvocation *context,
@@ -1864,7 +1543,7 @@ impl_settings_save_hostname (NMSettings *self,
 	GError *error = NULL;
 
 	/* Minimal validation of the hostname */
-	if (!validate_hostname (hostname)) {
+	if (!nm_hostname_manager_validate_hostname (hostname)) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_HOSTNAME,
 		                             "The hostname was too long or contained invalid characters.");
@@ -1888,37 +1567,7 @@ done:
 		g_dbus_method_invocation_take_error (context, error);
 }
 
-static void
-hostname_maybe_changed (NMSettings *settings)
-{
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (settings);
-	char *new_hostname;
-
-	new_hostname = nm_settings_get_hostname (settings);
-
-	if (   (new_hostname && !priv->hostname.value)
-	    || (!new_hostname && priv->hostname.value)
-	    || (priv->hostname.value && new_hostname && strcmp (priv->hostname.value, new_hostname))) {
-
-		_LOGI ("hostname changed from %s%s%s to %s%s%s",
-		       NM_PRINT_FMT_QUOTED (priv->hostname.value, "\"", priv->hostname.value, "\"", "(none)"),
-		       NM_PRINT_FMT_QUOTED (new_hostname, "\"", new_hostname, "\"", "(none)"));
-		g_free (priv->hostname.value);
-		priv->hostname.value = new_hostname;
-		_notify (settings, PROP_HOSTNAME);
-	} else
-		g_free (new_hostname);
-}
-
-static void
-hostname_file_changed_cb (GFileMonitor *monitor,
-                          GFile *file,
-                          GFile *other_file,
-                          GFileMonitorEvent event_type,
-                          gpointer user_data)
-{
-	hostname_maybe_changed (user_data);
-}
+/*****************************************************************************/
 
 static gboolean
 have_connection_for_device (NMSettings *self, NMDevice *device)
@@ -2124,7 +1773,7 @@ nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quittin
 		 * remains up and can be assumed if NM starts again.
 		 */
 		if (quitting == FALSE)
-			nm_settings_connection_delete (connection, NULL, NULL);
+			nm_settings_connection_delete (connection, NULL);
 	}
 }
 
@@ -2141,95 +1790,19 @@ nm_settings_get_startup_complete (NMSettings *self)
 /*****************************************************************************/
 
 static void
-hostnamed_properties_changed (GDBusProxy *proxy,
-                              GVariant *changed_properties,
-                              char **invalidated_properties,
-                              gpointer user_data)
+_hostname_changed_cb (NMHostnameManager *hostname_manager,
+                      GParamSpec *pspec,
+                      gpointer user_data)
 {
-	NMSettings *self = user_data;
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GVariant *v_hostname;
-	const char *hostname;
-
-	v_hostname = g_dbus_proxy_get_cached_property (priv->hostname.hostnamed_proxy,
-	                                               "StaticHostname");
-	if (!v_hostname)
-		return;
-
-	hostname = g_variant_get_string (v_hostname, NULL);
-
-	if (g_strcmp0 (priv->hostname.value, hostname) != 0) {
-		_LOGI ("hostname changed from %s%s%s to %s%s%s",
-		       NM_PRINT_FMT_QUOTED (priv->hostname.value, "\"", priv->hostname.value, "\"", "(none)"),
-		       NM_PRINT_FMT_QUOTED (hostname, "\"", hostname, "\"", "(none)"));
-		g_free (priv->hostname.value);
-		priv->hostname.value = g_strdup (hostname);
-		_notify (self, PROP_HOSTNAME);
-		nm_dispatcher_call_hostname (NULL, NULL, NULL);
-	}
-
-	g_variant_unref (v_hostname);
+	_notify (user_data, PROP_HOSTNAME);
 }
 
-static void
-setup_hostname_file_monitors (NMSettings *self)
-{
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GFileMonitor *monitor;
-	const char *path = HOSTNAME_FILE;
-	char *link_path = NULL;
-	struct stat file_stat;
-	GFile *file;
-
-	priv->hostname.value = nm_settings_get_hostname (self);
-
-	/* resolve the path to the hostname file if it is a symbolic link */
-	if (   lstat(path, &file_stat) == 0
-	    && S_ISLNK (file_stat.st_mode)
-	    && (link_path = nm_utils_read_link_absolute (path, NULL))) {
-		path = link_path;
-		if (   lstat(link_path, &file_stat) == 0
-		    && S_ISLNK (file_stat.st_mode)) {
-			_LOGW ("only one level of symbolic link indirection is allowed when monitoring "
-			       HOSTNAME_FILE);
-		}
-	}
-
-	/* monitor changes to hostname file */
-	file = g_file_new_for_path (path);
-	monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
-	g_object_unref (file);
-	g_free(link_path);
-	if (monitor) {
-		priv->hostname.monitor_id = g_signal_connect (monitor, "changed",
-		                                              G_CALLBACK (hostname_file_changed_cb),
-		                                              self);
-		priv->hostname.monitor = monitor;
-	}
-
-#if defined (HOSTNAME_PERSIST_SUSE)
-	/* monitor changes to dhcp file to know whether the hostname is valid */
-	file = g_file_new_for_path (CONF_DHCP);
-	monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
-	g_object_unref (file);
-	if (monitor) {
-		priv->hostname.dhcp_monitor_id = g_signal_connect (monitor, "changed",
-		                                                   G_CALLBACK (hostname_file_changed_cb),
-		                                                   self);
-		priv->hostname.dhcp_monitor = monitor;
-	}
-#endif
-
-	hostname_maybe_changed (self);
-}
+/*****************************************************************************/
 
 gboolean
 nm_settings_start (NMSettings *self, GError **error)
 {
 	NMSettingsPrivate *priv;
-	GDBusProxy *proxy;
-	GVariant *variant;
-	GError *local_error = NULL;
 	gs_strfreev char **plugins = NULL;
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
@@ -2245,33 +1818,14 @@ nm_settings_start (NMSettings *self, GError **error)
 	load_connections (self);
 	check_startup_complete (self);
 
-	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, 0, NULL,
-	                                       HOSTNAMED_SERVICE_NAME, HOSTNAMED_SERVICE_PATH,
-	                                       HOSTNAMED_SERVICE_INTERFACE, NULL, &local_error);
-	if (proxy) {
-		variant = g_dbus_proxy_get_cached_property (proxy, "StaticHostname");
-		if (variant) {
-			_LOGI ("hostname: using hostnamed");
-			priv->hostname.hostnamed_proxy = proxy;
-			g_signal_connect (proxy, "g-properties-changed",
-			                  G_CALLBACK (hostnamed_properties_changed), self);
-			hostnamed_properties_changed (proxy, NULL, NULL, self);
-			g_variant_unref (variant);
-		} else {
-			_LOGI ("hostname: couldn't get property from hostnamed");
-			g_object_unref (proxy);
-		}
-	} else {
-		_LOGI ("hostname: hostnamed not used as proxy creation failed with: %s",
-		       local_error->message);
-		g_clear_error (&local_error);
-	}
-
-	if (!priv->hostname.hostnamed_proxy)
-		setup_hostname_file_monitors (self);
+	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
+	g_signal_connect (priv->hostname_manager,
+	                  "notify::"NM_HOSTNAME_MANAGER_HOSTNAME,
+	                  G_CALLBACK (_hostname_changed_cb),
+	                  self);
+	if (nm_hostname_manager_get_hostname (priv->hostname_manager))
+		_notify (self, PROP_HOSTNAME);
 
-	priv->started = TRUE;
-	_notify (self, PROP_HOSTNAME);
 	return TRUE;
 }
 
@@ -2298,11 +1852,10 @@ get_property (GObject *object, guint prop_id,
 		g_value_take_boxed (value, (char **) g_ptr_array_free (array, FALSE));
 		break;
 	case PROP_HOSTNAME:
-		g_value_take_string (value, nm_settings_get_hostname (self));
-
-		/* Don't ever pass NULL through D-Bus */
-		if (!g_value_get_string (value))
-			g_value_set_static_string (value, "");
+		g_value_set_string (value,
+		                    priv->hostname_manager
+		                      ? nm_hostname_manager_get_hostname (priv->hostname_manager)
+		                      : NULL);
 		break;
 	case PROP_CAN_MODIFY:
 		g_value_set_boolean (value, !!get_plugin (self, NM_SETTINGS_PLUGIN_CAP_MODIFY_CONNECTIONS));
@@ -2331,7 +1884,7 @@ nm_settings_init (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
 
 	/* Hold a reference to the agent manager so it stays alive; the only
 	 * other holders are NMSettingsConnection objects which are often
@@ -2362,32 +1915,13 @@ dispose (GObject *object)
 
 	g_object_unref (priv->agent_mgr);
 
-	if (priv->hostname.hostnamed_proxy) {
-		g_signal_handlers_disconnect_by_func (priv->hostname.hostnamed_proxy,
-		                                      G_CALLBACK (hostnamed_properties_changed),
+	if (priv->hostname_manager) {
+		g_signal_handlers_disconnect_by_func (priv->hostname_manager,
+		                                      G_CALLBACK (_hostname_changed_cb),
 		                                      self);
-		g_clear_object (&priv->hostname.hostnamed_proxy);
-	}
-
-	if (priv->hostname.monitor) {
-		if (priv->hostname.monitor_id)
-			g_signal_handler_disconnect (priv->hostname.monitor, priv->hostname.monitor_id);
-
-		g_file_monitor_cancel (priv->hostname.monitor);
-		g_clear_object (&priv->hostname.monitor);
+		g_clear_object (&priv->hostname_manager);
 	}
 
-	if (priv->hostname.dhcp_monitor) {
-		if (priv->hostname.dhcp_monitor_id)
-			g_signal_handler_disconnect (priv->hostname.dhcp_monitor,
-			                             priv->hostname.dhcp_monitor_id);
-
-		g_file_monitor_cancel (priv->hostname.dhcp_monitor);
-		g_clear_object (&priv->hostname.dhcp_monitor);
-	}
-
-	g_clear_pointer (&priv->hostname.value, g_free);
-
 	G_OBJECT_CLASS (nm_settings_parent_class)->dispose (object);
 }
 
diff --git a/src/settings/nm-settings.h b/src/settings/nm-settings.h
index 7110a12b..eede76b0 100644
--- a/src/settings/nm-settings.h
+++ b/src/settings/nm-settings.h
@@ -119,20 +119,10 @@ gboolean nm_settings_has_connection (NMSettings *self, NMSettingsConnection *con
 
 const GSList *nm_settings_get_unmanaged_specs (NMSettings *self);
 
-char *nm_settings_get_hostname (NMSettings *self);
-
 void nm_settings_device_added (NMSettings *self, NMDevice *device);
 
 void nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean quitting);
 
 gboolean nm_settings_get_startup_complete (NMSettings *self);
 
-void nm_settings_set_transient_hostname (NMSettings *self,
-                                         const char *hostname,
-                                         NMSettingsSetHostnameCb cb,
-                                         gpointer user_data);
-
-gboolean nm_settings_get_transient_hostname (NMSettings *self,
-                                             char **hostname);
-
 #endif  /* __NM_SETTINGS_H__ */
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
index c9069dc7..9b1f5ccd 100644
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ b/src/settings/plugins/ibft/nms-ibft-plugin.c
@@ -152,7 +152,7 @@ nms_ibft_plugin_init (NMSIbftPlugin *self)
 {
 	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
 
-	priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 }
 
 static void
diff --git a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf b/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
index 8fefaf18..cc6ccb5c 100644
--- a/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
+++ b/src/settings/plugins/ifcfg-rh/nm-ifcfg-rh.conf
@@ -2,16 +2,11 @@
  "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
  "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
 <busconfig>
-        <policy user="root">
-                <allow own="com.redhat.ifcfgrh1"/>
-                <allow send_destination="com.redhat.ifcfgrh1"/>
-        </policy>
-        <policy at_console="true">
-                <allow send_destination="com.redhat.ifcfgrh1"/>
-        </policy>
         <policy context="default">
-                <deny own="com.redhat.ifcfgrh1"/>
                 <allow send_destination="com.redhat.ifcfgrh1"/>
         </policy>
+        <policy user="root">
+                <allow own="com.redhat.ifcfgrh1"/>
+        </policy>
 </busconfig>
 
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 b54f9549..4c1d02ae 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
@@ -306,75 +306,52 @@ nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self)
 	return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unrecognized_spec;
 }
 
-static void
-replace_and_commit (NMSettingsConnection *connection,
-                    NMConnection *new_connection,
-                    NMSettingsConnectionCommitFunc callback,
-                    gpointer user_data)
-{
-	const char *filename;
-	GError *error = NULL;
-
-	filename = nm_settings_connection_get_filename (connection);
-	if (filename && utils_has_complex_routes (filename)) {
-		if (callback) {
-			error = g_error_new_literal (NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			                             "Cannot modify a connection that has an associated 'rule-' or 'rule6-' file");
-			callback (connection, error, user_data);
-			g_clear_error (&error);
-		}
-		return;
-	}
-
-	NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->replace_and_commit (connection, new_connection, callback, user_data);
-}
-
-static void
+static gboolean
 commit_changes (NMSettingsConnection *connection,
+                NMConnection *new_connection,
                 NMSettingsConnectionCommitReason commit_reason,
-                NMSettingsConnectionCommitFunc callback,
-                gpointer user_data)
+                NMConnection **out_reread_connection,
+                char **out_logmsg_change,
+                GError **error)
 {
-	GError *error = NULL;
-	gboolean success = FALSE;
-	char *ifcfg_path = NULL;
 	const char *filename;
+	gs_unref_object NMConnection *reread = NULL;
+	gboolean reread_same = TRUE;
+	const char *operation_message;
+	gs_free char *ifcfg_path = NULL;
 
-	filename = nm_settings_connection_get_filename (connection);
-	if (filename) {
-		success = writer_update_connection (NM_CONNECTION (connection),
-		                                    IFCFG_DIR,
-		                                    filename,
-		                                    NULL,
-		                                    NULL,
-		                                    &error);
-	} else {
-		success = writer_new_connection (NM_CONNECTION (connection),
-		                                 IFCFG_DIR,
-		                                 &ifcfg_path,
-		                                 NULL,
-		                                 NULL,
-		                                 &error);
-		if (success) {
-			nm_settings_connection_set_filename (connection, ifcfg_path);
-			g_free (ifcfg_path);
-		}
-	}
+	nm_assert (out_reread_connection && !*out_reread_connection);
+	nm_assert (!out_logmsg_change || !*out_logmsg_change);
 
-	if (success) {
-		/* Chain up to parent to handle success */
-		NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->commit_changes (connection, commit_reason, callback, user_data);
-	} else {
-		/* Otherwise immediate error */
-		callback (connection, error, user_data);
-		g_error_free (error);
-	}
+	filename = nm_settings_connection_get_filename (connection);
+	if (!nms_ifcfg_rh_writer_write_connection (new_connection ?: NM_CONNECTION (connection),
+	                                           IFCFG_DIR,
+	                                           filename,
+	                                           &ifcfg_path,
+	                                           &reread,
+	                                           &reread_same,
+	                                           error))
+		return FALSE;
+
+	nm_assert ((!filename && ifcfg_path) || (filename && !ifcfg_path));
+	if (ifcfg_path) {
+		nm_settings_connection_set_filename (connection, ifcfg_path);
+		operation_message = "persist";
+	} else
+		operation_message = "update";
+
+	if (reread && !reread_same)
+		*out_reread_connection = g_steal_pointer (&reread);
+
+	NM_SET_OUT (out_logmsg_change,
+	            g_strdup_printf ("ifcfg-rh: %s %s",
+	                             operation_message, filename));
+	return TRUE;
 }
 
-static void
-do_delete (NMSettingsConnection *connection,
-	       NMSettingsConnectionDeleteFunc callback,
-	       gpointer user_data)
+static gboolean
+delete (NMSettingsConnection *connection,
+        GError **error)
 {
 	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) connection);
 	const char *filename;
@@ -390,7 +367,7 @@ do_delete (NMSettingsConnection *connection,
 			g_unlink (priv->route6file);
 	}
 
-	NM_SETTINGS_CONNECTION_CLASS (nm_ifcfg_connection_parent_class)->delete (connection, callback, user_data);
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -529,8 +506,7 @@ nm_ifcfg_connection_class_init (NMIfcfgConnectionClass *ifcfg_connection_class)
 	object_class->get_property = get_property;
 	object_class->dispose      = dispose;
 
-	settings_class->delete = do_delete;
-	settings_class->replace_and_commit = replace_and_commit;
+	settings_class->delete = delete;
 	settings_class->commit_changes = commit_changes;
 
 	obj_properties[PROP_UNMANAGED_SPEC] =
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 a3092e71..da0920ef 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -461,7 +461,7 @@ _paths_from_connections (GHashTable *connections)
 {
 	GHashTableIter iter;
 	NMIfcfgConnection *connection;
-	GHashTable *paths = g_hash_table_new (g_str_hash, g_str_equal);
+	GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	g_hash_table_iter_init (&iter, connections);
 	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
@@ -679,18 +679,16 @@ add_connection (NMSettingsPlugin *config,
 {
 	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (config);
 	gs_free char *path = NULL;
-
-	/* Ensure we reject attempts to add the connection long before we're
-	 * asked to write it to disk.
-	 */
-	if (!writer_can_write_connection (connection, error))
-		return NULL;
+	gs_unref_object NMConnection *reread = NULL;
 
 	if (save_to_disk) {
-		if (!writer_new_connection (connection, IFCFG_DIR, &path, NULL, NULL, error))
+		if (!nms_ifcfg_rh_writer_write_connection (connection, IFCFG_DIR, NULL, &path, &reread, NULL, error))
+			return NULL;
+	} else {
+		if (!nms_ifcfg_rh_writer_can_write_connection (connection, error))
 			return NULL;
 	}
-	return NM_SETTINGS_CONNECTION (update_connection (self, connection, path, NULL, FALSE, NULL, error));
+	return NM_SETTINGS_CONNECTION (update_connection (self, reread ?: connection, path, NULL, FALSE, NULL, error));
 }
 
 static void
@@ -991,7 +989,7 @@ settings_plugin_ifcfg_init (SettingsPluginIfcfg *plugin)
 {
 	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) plugin);
 
-	priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 }
 
 static void
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 164f6844..4754bea5 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -89,6 +89,64 @@ get_uint (const char *str, guint32 *value)
 	return TRUE;
 }
 
+static void
+check_if_bond_slave (shvarFile *ifcfg,
+                     NMSettingConnection *s_con)
+{
+	gs_free char *value = NULL;
+	const char *v;
+	const char *master;
+
+	v = svGetValueStr (ifcfg, "MASTER_UUID", &value);
+	if (!v)
+		v = svGetValueStr (ifcfg, "MASTER", &value);
+
+	if (v) {
+		master = nm_setting_connection_get_master (s_con);
+		if (master) {
+			PARSE_WARNING ("Already configured as slave of %s. Ignoring MASTER{_UUID}=\"%s\"",
+			               master, v);
+			return;
+		}
+
+		g_object_set (s_con,
+		              NM_SETTING_CONNECTION_MASTER, v,
+		              NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_BOND_SETTING_NAME,
+		              NULL);
+	}
+
+	/* We should be checking for SLAVE=yes as well, but NM used to not set that,
+	 * so for backward-compatibility, we don't check.
+	 */
+}
+
+static void
+check_if_team_slave (shvarFile *ifcfg,
+                     NMSettingConnection *s_con)
+{
+	gs_free char *value = NULL;
+	const char *v;
+	const char *master;
+
+	v = svGetValueStr (ifcfg, "TEAM_MASTER_UUID", &value);
+	if (!v)
+		v = svGetValueStr (ifcfg, "TEAM_MASTER", &value);
+	if (!v)
+		return;
+
+	master = nm_setting_connection_get_master (s_con);
+	if (master) {
+		PARSE_WARNING ("Already configured as slave of %s. Ignoring TEAM_MASTER{_UUID}=\"%s\"",
+		               master, v);
+		return;
+	}
+
+	g_object_set (s_con,
+	              NM_SETTING_CONNECTION_MASTER, v,
+	              NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_TEAM_SETTING_NAME,
+	              NULL);
+}
+
 static char *
 make_connection_name (shvarFile *ifcfg,
                       const char *ifcfg_name,
@@ -128,8 +186,14 @@ make_connection_setting (const char *file,
 	NMSettingConnection *s_con;
 	NMSettingConnectionLldp lldp;
 	const char *ifcfg_name = NULL;
-	char *new_id, *uuid = NULL, *zone = NULL, *value;
+	char *new_id;
+	const char *uuid;
+	gs_free char *uuid_free = NULL;
+	gs_free char *value = NULL;
+	const char *v;
 	gs_free char *stable_id = NULL;
+	const char *const *iter;
+	int vint64;
 
 	ifcfg_name = utils_get_ifcfg_name (file, TRUE);
 	if (!ifcfg_name)
@@ -142,38 +206,38 @@ make_connection_setting (const char *file,
 	g_free (new_id);
 
 	/* Try for a UUID key before falling back to hashing the file name */
-	uuid = svGetValueStr_cp (ifcfg, "UUID");
-	if (!uuid)
-		uuid = nm_utils_uuid_generate_from_string (svFileGetName (ifcfg), -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
+	uuid = svGetValueStr (ifcfg, "UUID", &uuid_free);
+	if (!uuid) {
+		uuid_free = nm_utils_uuid_generate_from_string (svFileGetName (ifcfg), -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
+		uuid = uuid_free;
+	}
 
 	g_object_set (s_con,
 	              NM_SETTING_CONNECTION_TYPE, type,
 	              NM_SETTING_CONNECTION_UUID, uuid,
 	              NM_SETTING_CONNECTION_STABLE_ID, svGetValue (ifcfg, "STABLE_ID", &stable_id),
 	              NULL);
-	g_free (uuid);
 
-	value = svGetValueStr_cp (ifcfg, "DEVICE");
-	if (value) {
+	v = svGetValueStr (ifcfg, "DEVICE", &value);
+	if (v) {
 		GError *error = NULL;
 
-		if (nm_utils_is_valid_iface_name (value, &error)) {
+		if (nm_utils_is_valid_iface_name (v, &error)) {
 			g_object_set (s_con,
-			              NM_SETTING_CONNECTION_INTERFACE_NAME, value,
+			              NM_SETTING_CONNECTION_INTERFACE_NAME, v,
 			              NULL);
 		} else {
-			PARSE_WARNING ("invalid DEVICE name '%s': %s", value, error->message);
+			PARSE_WARNING ("invalid DEVICE name '%s': %s", v, error->message);
 			g_error_free (error);
 		}
-		g_free (value);
 	}
 
-	value = svGetValueStr_cp (ifcfg, "LLDP");
-	if (!g_strcmp0 (value, "rx"))
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "LLDP", &value);
+	if (nm_streq0 (v, "rx"))
 		lldp = NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
 	else
-		lldp = svParseBoolean (value, NM_SETTING_CONNECTION_LLDP_DEFAULT);
-	g_free (value);
+		lldp = svParseBoolean (v, NM_SETTING_CONNECTION_LLDP_DEFAULT);
 
 	/* Missing ONBOOT is treated as "ONBOOT=true" by the old network service */
 	g_object_set (s_con,
@@ -192,68 +256,69 @@ make_connection_setting (const char *file,
 	              NM_SETTING_CONNECTION_LLDP, lldp,
 	              NULL);
 
-	value = svGetValueStr_cp (ifcfg, "USERS");
-	if (value) {
-		char **items, **iter;
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "USERS", &value);
+	if (v) {
+		gs_free const char **items = NULL;
 
-		items = g_strsplit_set (value, " ", -1);
+		items = nm_utils_strsplit_set (v, " ");
 		for (iter = items; iter && *iter; iter++) {
-			if (strlen (*iter)) {
-				if (!nm_setting_connection_add_permission (s_con, "user", *iter, NULL))
-					PARSE_WARNING ("invalid USERS item '%s'", *iter);
-			}
+			if (!nm_setting_connection_add_permission (s_con, "user", *iter, NULL))
+				PARSE_WARNING ("invalid USERS item '%s'", *iter);
 		}
-		g_free (value);
-		g_strfreev (items);
 	}
 
 
-	zone = svGetValueStr_cp (ifcfg, "ZONE");
-	g_object_set (s_con, NM_SETTING_CONNECTION_ZONE, zone, NULL);
-	g_free (zone);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "ZONE", &value);
+	g_object_set (s_con, NM_SETTING_CONNECTION_ZONE, v, NULL);
 
-	value = svGetValueStr_cp (ifcfg, "SECONDARY_UUIDS");
-	if (value) {
-		char **items, **iter;
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "SECONDARY_UUIDS", &value);
+	if (v) {
+		gs_free const char **items = NULL;
 
-		items = g_strsplit_set (value, " \t", -1);
+		items = nm_utils_strsplit_set (v, " \t");
 		for (iter = items; iter && *iter; iter++) {
-			if (strlen (*iter)) {
-				if (!nm_setting_connection_add_secondary (s_con, *iter))
-					PARSE_WARNING ("secondary connection UUID '%s' already added", *iter);
-			}
+			if (!nm_setting_connection_add_secondary (s_con, *iter))
+				PARSE_WARNING ("secondary connection UUID '%s' already added", *iter);
 		}
-		g_free (value);
-		g_strfreev (items);
 	}
 
-	value = svGetValueStr_cp (ifcfg, "BRIDGE_UUID");
-	if (!value)
-		value = svGetValueStr_cp (ifcfg, "BRIDGE");
-	if (value) {
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "BRIDGE_UUID", &value);
+	if (!v)
+		v = svGetValueStr (ifcfg, "BRIDGE", &value);
+	if (v) {
 		const char *old_value;
 
 		if ((old_value = nm_setting_connection_get_master (s_con))) {
 			PARSE_WARNING ("Already configured as slave of %s. Ignoring BRIDGE=\"%s\"",
-			               old_value, value);
+			               old_value, v);
 		} else {
-			g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, value, NULL);
+			g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
 			g_object_set (s_con, NM_SETTING_CONNECTION_SLAVE_TYPE,
 			              NM_SETTING_BRIDGE_SETTING_NAME, NULL);
 		}
-		g_free (value);
 	}
 
-	value = svGetValueStr_cp (ifcfg, "GATEWAY_PING_TIMEOUT");
-	if (value) {
+	check_if_bond_slave (ifcfg, s_con);
+	check_if_team_slave (ifcfg, s_con);
+
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "GATEWAY_PING_TIMEOUT", &value);
+	if (v) {
 		gint64 tmp;
 
-		tmp = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXINT32 - 1, -1);
-		if (tmp >= 0)
+		tmp = _nm_utils_ascii_str_to_int64 (v, 10, 0, G_MAXINT32 - 1, -1);
+		if (tmp >= 0) {
+			if (tmp > 600) {
+				tmp = 600;
+				PARSE_WARNING ("invalid GATEWAY_PING_TIMEOUT time");
+			}
 			g_object_set (s_con, NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, (guint) tmp, NULL);
-		else
+		} else
 			PARSE_WARNING ("invalid GATEWAY_PING_TIMEOUT time");
-		g_free (value);
 	}
 
 	switch (svGetValueBoolean (ifcfg, "CONNECTION_METERED", -1)) {
@@ -265,6 +330,9 @@ make_connection_setting (const char *file,
 		break;
 	}
 
+	vint64 = svGetValueInt64 (ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1);
+	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (gint) vint64, NULL);
+
 	return NM_SETTING (s_con);
 }
 
@@ -302,30 +370,6 @@ read_ip4_address (shvarFile *ifcfg,
 	return TRUE;
 }
 
-static void
-_numbered_tag (char *buf, gsize buf_len, const char *tag_name, int which)
-{
-	gsize l;
-
-	l = g_strlcpy (buf, tag_name, buf_len);
-	nm_assert (l < buf_len);
-	if (which != -1) {
-		buf_len -= l;
-		l = g_snprintf (&buf[l], buf_len, "%d", which);
-		nm_assert (l < buf_len);
-	}
-}
-#define numbered_tag(buf, tag_name, which) \
-	({ \
-		_nm_unused char *const _buf = (buf); \
-		\
-		/* some static assert trying to ensure that the buffer is statically allocated.
-		 * It disallows a buffer size of sizeof(gpointer) to catch that. */ \
-		G_STATIC_ASSERT (G_N_ELEMENTS (buf) == sizeof (buf) && sizeof (buf) != sizeof (char *) && sizeof (buf) < G_MAXINT); \
-		_numbered_tag (buf, sizeof (buf), ""tag_name"", (which)); \
-		buf; \
-	})
-
 static gboolean
 is_any_ip4_address_defined (shvarFile *ifcfg, int *idx)
 {
@@ -368,6 +412,7 @@ read_full_ip4_address (shvarFile *ifcfg,
 	char prefix_tag[256];
 	guint32 ipaddr;
 	gs_free char *value = NULL;
+	const char *v;
 	int prefix = 0;
 	gboolean has_key;
 	guint32 a;
@@ -402,12 +447,12 @@ read_full_ip4_address (shvarFile *ifcfg,
 
 	/* Prefix */
 	numbered_tag (prefix_tag, "PREFIX", which);
-	value = svGetValueStr_cp (ifcfg, prefix_tag);
-	if (value) {
-		prefix = _nm_utils_ascii_str_to_int64 (value, 10, 0, 32, -1);
+	v = svGetValueStr (ifcfg, prefix_tag, &value);
+	if (v) {
+		prefix = _nm_utils_ascii_str_to_int64 (v, 10, 0, 32, -1);
 		if (prefix < 0) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid IP4 prefix '%s'", value);
+			             "Invalid IP4 prefix '%s'", v);
 			return FALSE;
 		}
 	} else {
@@ -423,7 +468,7 @@ read_full_ip4_address (shvarFile *ifcfg,
 				prefix = nm_ip_address_get_prefix (base_addr);
 			else {
 				/* Try to autodetermine the prefix for the address' class */
-				prefix = nm_utils_ip4_get_default_prefix (ipaddr);
+				prefix = _nm_utils_ip4_get_default_prefix (ipaddr);
 				PARSE_WARNING ("missing %s, assuming %s/%d", prefix_tag, nm_utils_inet4_ntop (ipaddr, inet_buf), prefix);
 			}
 		}
@@ -436,131 +481,448 @@ read_full_ip4_address (shvarFile *ifcfg,
 	return FALSE;
 }
 
-/*
- * Use looser syntax to comprise all the possibilities.
- * The validity must be checked after the match.
- */
-#define IPV4_ADDR_REGEX "(?:[0-9]{1,3}\\.){3}[0-9]{1,3}"
-#define IPV6_ADDR_REGEX "[0-9A-Fa-f:.]+"
-
-/*
- * NOTE: The regexes below don't describe all variants allowed by 'ip route add',
- * namely destination IP without 'to' keyword is recognized just at line start.
- */
+/*****************************************************************************/
 
 static gboolean
-parse_route_options (NMIPRoute *route, int family, const char *line, GError **error)
+parse_route_line_is_comment (const char *line)
 {
-	GRegex *regex = NULL;
-	GMatchInfo *match_info = NULL;
-	gboolean success = FALSE;
-	static const char *metrics[] = { NM_IP_ROUTE_ATTRIBUTE_WINDOW, NM_IP_ROUTE_ATTRIBUTE_CWND,
-	                                 NM_IP_ROUTE_ATTRIBUTE_INITCWND, NM_IP_ROUTE_ATTRIBUTE_INITRWND,
-	                                 NM_IP_ROUTE_ATTRIBUTE_MTU, NULL };
-	char buffer[1024];
-	int i;
+	/* we obtained the line from a legacy route file. Here we skip
+	 * empty lines and comments.
+	 *
+	 * initscripts compares: "$line" =~ '^[[:space:]]*(\#.*)?$'
+	 */
+	while (NM_IN_SET (line[0], ' ', '\t'))
+		line++;
+	if (NM_IN_SET (line[0], '\0', '#'))
+		return TRUE;
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	const char *key;
+
+	/* the element is not available in this case. */
+	bool disabled:1;
+
+	/* whether the element is to be ignored. Ignord is different from
+	 * "disabled", because we still parse the option, but don't use it. */
+	bool ignore:1;
+
+	bool int_base_16:1;
+
+	/* the type, one of PARSE_LINE_TYPE_* */
+	char type;
 
-	g_return_val_if_fail (family == AF_INET || family == AF_INET6, FALSE);
+	/* whether the command line option was found, and @v is
+	 * initialized. */
+	bool has:1;
 
-	for (i = 0; metrics[i]; i++) {
-		nm_sprintf_buf (buffer, "(?:\\s|^)%s\\s+(lock\\s+)?(\\d+)(?:$|\\s)", metrics[i]);
-		regex = g_regex_new (buffer, 0, 0, NULL);
-		g_regex_match (regex, line, 0, &match_info);
-		if (g_match_info_matches (match_info)) {
-			gs_free char *lock = g_match_info_fetch (match_info, 1);
-			gs_free char *str = g_match_info_fetch (match_info, 2);
-			gint64 num = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXUINT32, -1);
+	union {
+		guint8 uint8;
+		guint32 uint32;
+		struct {
+			guint32 uint32;
+			bool lock:1;
+		} uint32_with_lock;
+		struct {
+			NMIPAddr addr;
+			guint8 plen;
+			bool has_plen:1;
+		} addr;
+	} v;
 
-			if (num == -1) {
-				g_match_info_free (match_info);
+} ParseLineInfo;
+
+enum {
+	/* route attributes */
+	PARSE_LINE_ATTR_ROUTE_TABLE,
+	PARSE_LINE_ATTR_ROUTE_SRC,
+	PARSE_LINE_ATTR_ROUTE_FROM,
+	PARSE_LINE_ATTR_ROUTE_TOS,
+	PARSE_LINE_ATTR_ROUTE_WINDOW,
+	PARSE_LINE_ATTR_ROUTE_CWND,
+	PARSE_LINE_ATTR_ROUTE_INITCWND,
+	PARSE_LINE_ATTR_ROUTE_INITRWND,
+	PARSE_LINE_ATTR_ROUTE_MTU,
+
+	/* iproute2 arguments that only matter when parsing the file. */
+	PARSE_LINE_ATTR_ROUTE_TO,
+	PARSE_LINE_ATTR_ROUTE_VIA,
+	PARSE_LINE_ATTR_ROUTE_METRIC,
+
+	/* iproute2 paramters that are well known and that we silently ignore. */
+	PARSE_LINE_ATTR_ROUTE_DEV,
+};
+
+#define PARSE_LINE_TYPE_UINT8             '8'
+#define PARSE_LINE_TYPE_UINT32            'u'
+#define PARSE_LINE_TYPE_UINT32_WITH_LOCK  'l'
+#define PARSE_LINE_TYPE_ADDR              'a'
+#define PARSE_LINE_TYPE_ADDR_WITH_PREFIX  'p'
+#define PARSE_LINE_TYPE_IFNAME            'i'
+
+/**
+ * parse_route_line:
+ * @line: the line to parse. This is either a line from the route-* or route6-* file,
+ *   or the numbered OPTIONS setting.
+ * @addr_family: the address family.
+ * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created
+ *   route object that is completed with the settings from options. Otherwise,
+ *   it shall point to %NULL and a new route is created and returned.
+ * @out_route: (out): (transfer-full): (allow-none): the parsed %NMIPRoute instance.
+ *   In case a @options_route is passed in, it returns the input route that was modified
+ *   in-place. But the caller must unref the returned route in either case.
+ * @error: the failure description.
+ *
+ * Parsing the route options line has two modes: one for the numbered OPTIONS
+ * setting, and one for initscript's handle_ip_file(), which takes the lines
+ * and passes them to `ip route add`. The modes are similar, but certain properties
+ * are not allowed for OPTIONS.
+ * The mode is differenciated by having an @options_route argument.
+ *
+ * Returns: returns a negative errno on failure. On success, it returns 0
+ *   and @out_route.
+ */
+static int
+parse_route_line (const char *line,
+                  int addr_family,
+                  NMIPRoute *options_route,
+                  NMIPRoute **out_route,
+                  GError **error)
+{
+	nm_auto_ip_route_unref NMIPRoute *route = NULL;
+	gs_free const char **words_free = NULL;
+	const char *const*words;
+	const char *s;
+	gsize i_words;
+	guint i;
+	char buf1[256];
+	char buf2[256];
+	ParseLineInfo infos[] = {
+		[PARSE_LINE_ATTR_ROUTE_TABLE]    = { .key = NM_IP_ROUTE_ATTRIBUTE_TABLE,
+		                                     .type = PARSE_LINE_TYPE_UINT32, },
+		[PARSE_LINE_ATTR_ROUTE_SRC]      = { .key = NM_IP_ROUTE_ATTRIBUTE_SRC,
+		                                     .type = PARSE_LINE_TYPE_ADDR, },
+		[PARSE_LINE_ATTR_ROUTE_FROM]     = { .key = NM_IP_ROUTE_ATTRIBUTE_FROM,
+		                                     .type = PARSE_LINE_TYPE_ADDR_WITH_PREFIX,
+		                                     .disabled = (addr_family != AF_INET6), },
+		[PARSE_LINE_ATTR_ROUTE_TOS]      = { .key = NM_IP_ROUTE_ATTRIBUTE_TOS,
+		                                     .type = PARSE_LINE_TYPE_UINT8,
+		                                     .int_base_16 = TRUE,
+		                                     .ignore = (addr_family != AF_INET), },
+		[PARSE_LINE_ATTR_ROUTE_WINDOW]   = { .key = NM_IP_ROUTE_ATTRIBUTE_WINDOW,
+		                                     .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
+		[PARSE_LINE_ATTR_ROUTE_CWND]     = { .key = NM_IP_ROUTE_ATTRIBUTE_CWND,
+		                                     .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
+		[PARSE_LINE_ATTR_ROUTE_INITCWND] = { .key = NM_IP_ROUTE_ATTRIBUTE_INITCWND,
+		                                     .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
+		[PARSE_LINE_ATTR_ROUTE_INITRWND] = { .key = NM_IP_ROUTE_ATTRIBUTE_INITRWND,
+		                                     .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
+		[PARSE_LINE_ATTR_ROUTE_MTU]      = { .key = NM_IP_ROUTE_ATTRIBUTE_MTU,
+		                                     .type = PARSE_LINE_TYPE_UINT32_WITH_LOCK, },
+
+		[PARSE_LINE_ATTR_ROUTE_TO]       = { .key = "to",
+		                                     .type = PARSE_LINE_TYPE_ADDR_WITH_PREFIX,
+		                                     .disabled = (options_route != NULL), },
+		[PARSE_LINE_ATTR_ROUTE_VIA]      = { .key = "via",
+		                                     .type = PARSE_LINE_TYPE_ADDR,
+		                                     .disabled = (options_route != NULL), },
+		[PARSE_LINE_ATTR_ROUTE_METRIC]   = { .key = "metric",
+		                                     .type = PARSE_LINE_TYPE_UINT32,
+		                                     .disabled = (options_route != NULL), },
+
+		[PARSE_LINE_ATTR_ROUTE_DEV]      = { .key = "dev",
+		                                     .type = PARSE_LINE_TYPE_IFNAME,
+		                                     .ignore = TRUE,
+		                                     .disabled = (options_route != NULL), },
+	};
+
+	nm_assert (line);
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+	nm_assert (!options_route || nm_ip_route_get_family (options_route) == addr_family);
+
+	/* initscripts read the legacy route file line-by-line and
+	 * use it as `ip route add $line`, thus doing split+glob.
+	 * Splitting on IFS (which we consider '<space><tab><newline>')
+	 * and globbing (which we obviously don't do).
+	 *
+	 * I think it's a mess, because it doesn't support escaping or
+	 * quoting. In fact, it can only encode benign values.
+	 *
+	 * We also use the same form for the numbered OPTIONS
+	 * variable. I think it's bad not to support any form of
+	 * escaping. But do that for now.
+	 *
+	 * 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 = words_free ?: NM_PTRARRAY_EMPTY (const char *);
+
+	for (i_words = 0; words[i_words]; ) {
+		const gsize i_words0 = i_words;
+		const char *const w = words[i_words0];
+		ParseLineInfo *info;
+		gboolean unqualified_addr = FALSE;
+
+		for (i = 0; i < G_N_ELEMENTS (infos); i++) {
+			info = &infos[i];
+
+			if (info->disabled)
+				continue;
+
+			if (!nm_streq (w, info->key))
+				continue;
+
+			if (info->has) {
+				/* iproute2 for most arguments allows specifying them multiple times.
+				 * Let's not do that. */
 				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid route %s '%s'", metrics[i], str);
-				goto out;
+				             "Duplicate option \"%s\"", w);
+				return -EINVAL;
 			}
 
-			nm_ip_route_set_attribute (route, metrics[i],
-			                           g_variant_new_uint32 (num));
-			if (lock && lock[0]) {
-				nm_sprintf_buf (buffer, "lock-%s", metrics[i]);
-				nm_ip_route_set_attribute (route, buffer,
-				                           g_variant_new_boolean (TRUE));
+			info->has = TRUE;
+			switch (info->type) {
+			case PARSE_LINE_TYPE_UINT8:
+				i_words++;
+				goto parse_line_type_uint8;
+			case PARSE_LINE_TYPE_UINT32:
+				i_words++;
+				goto parse_line_type_uint32;
+			case PARSE_LINE_TYPE_UINT32_WITH_LOCK:
+				i_words++;
+				goto parse_line_type_uint32_with_lock;
+			case PARSE_LINE_TYPE_ADDR:
+				i_words++;
+				goto parse_line_type_addr;
+			case PARSE_LINE_TYPE_ADDR_WITH_PREFIX:
+				i_words++;
+				goto parse_line_type_addr_with_prefix;
+			case PARSE_LINE_TYPE_IFNAME:
+				i_words++;
+				goto parse_line_type_ifname;
+			default:
+				nm_assert_not_reached ();
 			}
 		}
-		g_clear_pointer (&regex, g_regex_unref);
-		g_clear_pointer (&match_info, g_match_info_free);
-	}
 
-	/* tos */
-	regex = g_regex_new ("(?:\\s|^)tos\\s+(\\S+)(?:$|\\s)", 0, 0, NULL);
-	g_regex_match (regex, line, 0, &match_info);
-	if (g_match_info_matches (match_info)) {
-		gs_free char *str = g_match_info_fetch (match_info, 1);
-		gint64 num = _nm_utils_ascii_str_to_int64 (str, 0, 0, G_MAXUINT8, -1);
+		/* "to" is also accepted unqualified... (once) */
+		info = &infos[PARSE_LINE_ATTR_ROUTE_TO];
+		if (!info->has && !info->disabled) {
+			unqualified_addr = TRUE;
+			info->has = TRUE;
+			goto parse_line_type_addr;
+		}
 
-		if (num == -1) {
-			g_match_info_free (match_info);
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		             "Unrecognized argument (\"to\" is duplicate or \"%s\" is garbage)", w);
+		return -EINVAL;
+
+parse_line_type_uint8:
+		s = words[i_words];
+		if (!s)
+			goto err_word_missing_argument;
+		info->v.uint8 = _nm_utils_ascii_str_to_int64 (s,
+		                                              info->int_base_16 ? 16 : 10,
+		                                              0,
+		                                              G_MAXUINT8,
+		                                              0);;
+		if (errno) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid route %s '%s'", "tos", str);
-			goto out;
+			             "Argument for \"%s\" is not a valid number", w);
+			return -EINVAL;
 		}
-		nm_ip_route_set_attribute (route, NM_IP_ROUTE_ATTRIBUTE_TOS,
-		                           g_variant_new_byte ((guchar) num));
-	}
-	g_clear_pointer (&regex, g_regex_unref);
-	g_clear_pointer (&match_info, g_match_info_free);
-
-	/* from */
-	if (family == AF_INET6) {
-		regex = g_regex_new ("(?:\\s|^)from\\s+(" IPV6_ADDR_REGEX "(?:/\\d{1,3})?)(?:$|\\s)", 0, 0, NULL);
-		g_regex_match (regex, line, 0, &match_info);
-		if (g_match_info_matches (match_info)) {
-			gs_free char *str = g_match_info_fetch (match_info, 1);
-			gs_free_error GError *local_error = NULL;
-			GVariant *variant = g_variant_new_string (str);
-
-			if (!nm_ip_route_attribute_validate (NM_IP_ROUTE_ATTRIBUTE_FROM, variant, family, NULL, &local_error)) {
-				g_match_info_free (match_info);
-				g_variant_unref (variant);
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid route from '%s': %s", str, local_error->message);
-				goto out;
+		i_words++;
+		goto next;
+
+parse_line_type_uint32:
+parse_line_type_uint32_with_lock:
+		s = words[i_words];
+		if (!s)
+			goto err_word_missing_argument;
+		if (info->type == PARSE_LINE_TYPE_UINT32_WITH_LOCK) {
+			if (nm_streq (s, "lock")) {
+				s = words[++i_words];
+				if (!s)
+					goto err_word_missing_argument;
+				info->v.uint32_with_lock.lock = TRUE;
+			} else
+				info->v.uint32_with_lock.lock = FALSE;
+			info->v.uint32_with_lock.uint32 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT32, 0);;
+		} else {
+			info->v.uint32 = _nm_utils_ascii_str_to_int64 (s, 10, 0, G_MAXUINT32, 0);
+		}
+		if (errno) {
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "Argument for \"%s\" is not a valid number", w);
+			return -EINVAL;
+		}
+		i_words++;
+		goto next;
+
+parse_line_type_ifname:
+		s = words[i_words];
+		if (!s)
+			goto err_word_missing_argument;
+		i_words++;
+		goto next;
+
+parse_line_type_addr:
+parse_line_type_addr_with_prefix:
+		s = words[i_words];
+		if (!s)
+			goto err_word_missing_argument;
+		{
+			int prefix = -1;
+
+			if (info->type == PARSE_LINE_TYPE_ADDR) {
+				if (!nm_utils_parse_inaddr_bin (addr_family,
+				                                s,
+				                                &info->v.addr.addr)) {
+					if (   info == &infos[PARSE_LINE_ATTR_ROUTE_VIA]
+					    && nm_streq (s, "(null)")) {
+						/* Due to a bug, would older versions of NM write "via (null)"
+						 * (rh#1452648). Workaround that, and accept it.*/
+						memset (&info->v.addr.addr, 0, sizeof (info->v.addr.addr));
+					} else {
+						if (unqualified_addr) {
+							g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+							             "Unrecognized argument (inet prefix is expected rather then \"%s\")", w);
+							return -EINVAL;
+						} else {
+							g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+							             "Argument for \"%s\" is not a valid IPv%c address", w,
+							             addr_family == AF_INET ? '4' : '6');
+						}
+						return -EINVAL;
+					}
+				}
+			} else {
+				nm_assert (info->type == PARSE_LINE_TYPE_ADDR_WITH_PREFIX);
+				if (   info == &infos[PARSE_LINE_ATTR_ROUTE_TO]
+				    && nm_streq (s, "default")) {
+					memset (&info->v.addr.addr, 0, sizeof (info->v.addr.addr));
+					prefix = 0;
+				} else if (!nm_utils_parse_inaddr_prefix_bin (addr_family,
+				                                              s,
+				                                              &info->v.addr.addr,
+				                                              &prefix)) {
+					g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+					             "Argument for \"%s\" is not ADDR/PREFIX format", w);
+					return -EINVAL;
+				}
+			}
+			if (prefix == -1)
+				info->v.addr.has_plen = FALSE;
+			else {
+				info->v.addr.has_plen = TRUE;
+				info->v.addr.plen = prefix;
 			}
-			nm_ip_route_set_attribute (route, NM_IP_ROUTE_ATTRIBUTE_FROM, variant);
 		}
-		g_clear_pointer (&regex, g_regex_unref);
-		g_clear_pointer (&match_info, g_match_info_free);
+		i_words++;
+		goto next;
+
+err_word_missing_argument:
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		             "Missing argument for \"%s\"", w);
+		return -EINVAL;
+next:
+		;
 	}
 
-	if (family == AF_INET)
-		regex = g_regex_new ("(?:\\s|^)src\\s+(" IPV4_ADDR_REGEX ")(?:$|\\s)", 0, 0, NULL);
-	else
-		regex = g_regex_new ("(?:\\s|^)src\\s+(" IPV6_ADDR_REGEX ")(?:$|\\s)", 0, 0, NULL);
-	g_regex_match (regex, line, 0, &match_info);
-	if (g_match_info_matches (match_info)) {
-		gs_free char *str = g_match_info_fetch (match_info, 1);
-		gs_free_error GError *local_error = NULL;
-		GVariant *variant = g_variant_new_string (str);
-
-		if (!nm_ip_route_attribute_validate (NM_IP_ROUTE_ATTRIBUTE_SRC, variant, family,
-		                                     NULL, &local_error)) {
-			g_match_info_free (match_info);
-			g_variant_unref (variant);
+	if (options_route) {
+		route = options_route;
+		nm_ip_route_ref (route);
+	} else {
+		ParseLineInfo *info_to = &infos[PARSE_LINE_ATTR_ROUTE_TO];
+		ParseLineInfo *info_via = &infos[PARSE_LINE_ATTR_ROUTE_VIA];
+		ParseLineInfo *info_metric = &infos[PARSE_LINE_ATTR_ROUTE_METRIC];
+		guint prefix;
+
+		if (!info_to->has) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid route src '%s': %s", str, local_error->message);
-			goto out;
+			             "Missing destination prefix");
+			return -EINVAL;
 		}
 
-		nm_ip_route_set_attribute (route, NM_IP_ROUTE_ATTRIBUTE_SRC, variant);
+		prefix =   info_to->v.addr.has_plen
+		         ? info_to->v.addr.plen
+		         : (addr_family == AF_INET ? 32 : 128);
+
+		if (   (   (addr_family == AF_INET  && !info_to->v.addr.addr.addr4)
+		        || (addr_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED (&info_to->v.addr.addr.addr6)))
+		    && prefix == 0) {
+			/* we ignore default routes by returning -ERANGE. */
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "Ignore manual default route");
+			return -ERANGE;
+		}
+
+		route = nm_ip_route_new_binary (addr_family,
+		                                &info_to->v.addr.addr,
+		                                prefix,
+		                                info_via->has ? &info_via->v.addr.addr : NULL,
+		                                info_metric->has ? (gint64) info_metric->v.uint32 : (gint64) -1,
+		                                error);
+		info_to->has = FALSE;
+		info_via->has = FALSE;
+		info_metric->has = FALSE;
+		if (!route)
+			return -EINVAL;
 	}
-	success = TRUE;
 
-out:
-	if (regex)
-		g_regex_unref (regex);
-	if (match_info)
-		g_match_info_free (match_info);
+	for (i = 0; i < G_N_ELEMENTS (infos); i++) {
+		ParseLineInfo *info = &infos[i];
 
-	return success;
+		if (!info->has)
+			continue;
+		if (info->ignore || info->disabled)
+			continue;
+		switch (info->type) {
+		case PARSE_LINE_TYPE_UINT8:
+			nm_ip_route_set_attribute (route,
+			                           info->key,
+			                           g_variant_new_byte (info->v.uint8));
+			break;
+		case PARSE_LINE_TYPE_UINT32:
+			nm_ip_route_set_attribute (route,
+			                           info->key,
+			                           g_variant_new_uint32 (info->v.uint32));
+			break;
+		case PARSE_LINE_TYPE_UINT32_WITH_LOCK:
+			if (info->v.uint32_with_lock.lock) {
+				nm_ip_route_set_attribute (route,
+				                           nm_sprintf_buf (buf1, "lock-%s", info->key),
+				                           g_variant_new_boolean (TRUE));
+			}
+			nm_ip_route_set_attribute (route,
+			                           info->key,
+			                           g_variant_new_uint32 (info->v.uint32_with_lock.uint32));
+			break;
+		case PARSE_LINE_TYPE_ADDR:
+		case PARSE_LINE_TYPE_ADDR_WITH_PREFIX:
+			nm_ip_route_set_attribute (route,
+			                           info->key,
+			                           g_variant_new_printf ("%s%s",
+			                                                 inet_ntop (addr_family, &info->v.addr.addr, buf1, sizeof (buf1)),
+			                                                 info->v.addr.has_plen
+			                                                    ? nm_sprintf_buf (buf2, "/%u", (unsigned) info->v.addr.plen)
+			                                                    : ""));
+			break;
+		default:
+			nm_assert_not_reached ();
+			break;
+		}
+	}
+
+	nm_assert (_nm_ip_route_attribute_validate_all (route));
+
+	NM_SET_OUT (out_route, g_steal_pointer (&route));
+	return 0;
 }
 
 /* Returns TRUE on missing route or valid route */
@@ -576,13 +938,13 @@ read_one_ip4_route (shvarFile *ifcfg,
 	guint32 next_hop;
 	guint32 netmask;
 	gboolean has_key;
+	const char *v;
 	gs_free char *value = NULL;
 	gint64 prefix, metric;
 	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
 
 	g_return_val_if_fail (ifcfg != NULL, FALSE);
-	g_return_val_if_fail (out_route != NULL, FALSE);
-	g_return_val_if_fail (*out_route == NULL, FALSE);
+	g_return_val_if_fail (out_route && !*out_route, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
 	/* Destination */
@@ -610,7 +972,7 @@ read_one_ip4_route (shvarFile *ifcfg,
 		return FALSE;
 	if (has_key) {
 		prefix = nm_utils_ip4_netmask_to_prefix (netmask);
-		if (prefix == 0 || netmask != nm_utils_ip4_prefix_to_netmask (prefix)) {
+		if (prefix == 0 || netmask != _nm_utils_ip4_prefix_to_netmask (prefix)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid IP4 netmask '%s' \"%s\"", netmask_tag, nm_utils_inet4_ntop (netmask, inet_buf));
 			return FALSE;
@@ -623,12 +985,12 @@ read_one_ip4_route (shvarFile *ifcfg,
 
 	/* Metric */
 	nm_clear_g_free (&value);
-	value = svGetValueStr_cp (ifcfg, numbered_tag (tag, "METRIC", which));
-	if (value) {
-		metric = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXUINT32, -1);
+	v = svGetValueStr (ifcfg, numbered_tag (tag, "METRIC", which), &value);
+	if (v) {
+		metric = _nm_utils_ascii_str_to_int64 (v, 10, 0, G_MAXUINT32, -1);
 		if (metric < 0) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid IP4 route metric '%s'", value);
+			             "Invalid IP4 route metric '%s'", v);
 			return FALSE;
 		}
 	} else
@@ -640,9 +1002,9 @@ read_one_ip4_route (shvarFile *ifcfg,
 
 	/* Options */
 	nm_clear_g_free (&value);
-	value = svGetValueStr_cp (ifcfg, numbered_tag (tag, "OPTIONS", which));
-	if (value) {
-		if (!parse_route_options (*out_route, AF_INET, value, error)) {
+	v = svGetValueStr (ifcfg, numbered_tag (tag, "OPTIONS", which), &value);
+	if (v) {
+		if (parse_route_line (v, AF_INET, *out_route, NULL, error) < 0) {
 			g_clear_pointer (out_route, nm_ip_route_unref);
 			return FALSE;
 		}
@@ -652,156 +1014,65 @@ read_one_ip4_route (shvarFile *ifcfg,
 }
 
 static gboolean
-read_route_file_legacy (const char *filename, NMSettingIPConfig *s_ip4, GError **error)
+read_route_file (int addr_family,
+                 const char *filename,
+                 NMSettingIPConfig *s_ip,
+                 GError **error)
 {
-	char *contents = NULL;
+	gs_free char *contents = NULL;
+	char *contents_rest = NULL;
+	const char *line;
 	gsize len = 0;
-	char **lines = NULL, **iter;
-	GRegex *regex_to1, *regex_to2, *regex_via, *regex_metric;
-	GMatchInfo *match_info;
-	int prefix_int;
-	gint64 metric_int;
-	gboolean success = FALSE;
-
-	const char *pattern_empty = "^\\s*(\\#.*)?$";
-	const char *pattern_to1 = "^\\s*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|default)"  /* IP or 'default' keyword */
-	                          "(?:/(\\d{1,2}))?";                                         /* optional prefix */
-	const char *pattern_to2 = "to\\s+(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|default)" /* IP or 'default' keyword */
-	                          "(?:/(\\d{1,2}))?";                                         /* optional prefix */
-	const char *pattern_via = "via\\s+(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})";       /* IP of gateway */
-	const char *pattern_metric = "metric\\s+(\\d+)";                                      /* metric */
+	gsize line_num;
 
-	g_return_val_if_fail (filename != NULL, FALSE);
-	g_return_val_if_fail (s_ip4 != NULL, FALSE);
+	g_return_val_if_fail (filename, FALSE);
+	g_return_val_if_fail (   (addr_family == AF_INET  && NM_IS_SETTING_IP4_CONFIG (s_ip))
+	                      || (addr_family == AF_INET6 && NM_IS_SETTING_IP6_CONFIG (s_ip)), FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
-	/* Read the route file */
-	if (!g_file_get_contents (filename, &contents, &len, NULL) || !len) {
-		g_free (contents);
+	if (   !g_file_get_contents (filename, &contents, &len, NULL)
+	    || !len) {
 		return TRUE;  /* missing/empty = success */
 	}
 
-	/* Create regexes for pieces to be matched */
-	regex_to1 = g_regex_new (pattern_to1, 0, 0, NULL);
-	regex_to2 = g_regex_new (pattern_to2, 0, 0, NULL);
-	regex_via = g_regex_new (pattern_via, 0, 0, NULL);
-	regex_metric = g_regex_new (pattern_metric, 0, 0, NULL);
+	line_num = 0;
+	for (line = strtok_r (contents, "\n", &contents_rest);
+	     line;
+	     line = strtok_r (NULL, "\n", &contents_rest)) {
+		nm_auto_ip_route_unref NMIPRoute *route = NULL;
+		gs_free_error GError *local = NULL;
+		int e;
 
-	/* Iterate through file lines */
-	lines = g_strsplit_set (contents, "\n\r", -1);
-	for (iter = lines; iter && *iter; iter++) {
-		gs_free char *next_hop = NULL, *dest = NULL;
-		char *prefix, *metric;
-		NMIPRoute *route;
+		line_num++;
 
-		/* Skip empty lines */
-		if (g_regex_match_simple (pattern_empty, *iter, 0, 0))
+		if (parse_route_line_is_comment (line))
 			continue;
 
-		/* Destination */
-		g_regex_match (regex_to1, *iter, 0, &match_info);
-		if (!g_match_info_matches (match_info)) {
-			g_match_info_free (match_info);
-			g_regex_match (regex_to2, *iter, 0, &match_info);
-			if (!g_match_info_matches (match_info)) {
-				g_match_info_free (match_info);
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Missing IP4 route destination address in record: '%s'", *iter);
-				goto error;
-			}
-		}
-		dest = g_match_info_fetch (match_info, 1);
-		if (!strcmp (dest, "default")) {
-			g_match_info_free (match_info);
-			PARSE_WARNING ("ignoring manual default route: '%s' (%s)", *iter, filename);
-			continue;
-		}
-		if (!nm_utils_ipaddr_valid (AF_INET, dest)) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid IP4 route destination address '%s'", dest);
-			g_match_info_free (match_info);
-			goto error;
-		}
-
-		/* Prefix - is optional; 32 if missing */
-		prefix = g_match_info_fetch (match_info, 2);
-		g_match_info_free (match_info);
-		prefix_int = 32;
-		if (prefix) {
-			prefix_int = _nm_utils_ascii_str_to_int64 (prefix, 10, 1, 32, -1);
-			if (prefix_int == -1) {
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid IP4 route destination prefix '%s'", prefix);
-				g_free (prefix);
-				goto error;
-			}
-		}
-		g_free (prefix);
+		e = parse_route_line (line, addr_family, NULL, &route, &local);
 
-		/* Next hop */
-		g_regex_match (regex_via, *iter, 0, &match_info);
-		if (g_match_info_matches (match_info)) {
-			next_hop = g_match_info_fetch (match_info, 1);
-			if (!nm_utils_ipaddr_valid (AF_INET, next_hop)) {
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid IP4 route gateway address '%s'",
-				             next_hop);
-				g_match_info_free (match_info);
-				goto error;
-			}
-		} else {
-			/* we don't make distinction between missing GATEWAY IP and 0.0.0.0 */
-		}
-		g_match_info_free (match_info);
-
-		/* Metric */
-		g_regex_match (regex_metric, *iter, 0, &match_info);
-		metric_int = -1;
-		if (g_match_info_matches (match_info)) {
-			metric = g_match_info_fetch (match_info, 1);
-			metric_int = _nm_utils_ascii_str_to_int64 (metric, 10, 0, G_MAXUINT32, -1);
-			if (metric_int == -1) {
-				g_match_info_free (match_info);
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid IP4 route metric '%s'", metric);
-				g_free (metric);
-				goto error;
+		if (e < 0) {
+			if (e == -ERANGE)
+				PARSE_WARNING ("ignoring manual default route: '%s' (%s)", line, filename);
+			else {
+				/* we accept all unrecognized lines, because otherwise we would reject the
+				 * entire connection. */
+				PARSE_WARNING ("ignoring invalid route at \"%s\" (%s:%lu): %s", line, filename, (long unsigned) line_num, local->message);
 			}
-			g_free (metric);
-		}
-		g_match_info_free (match_info);
-
-		route = nm_ip_route_new (AF_INET, dest, prefix_int, next_hop, metric_int, error);
-		if (!route)
-			goto error;
-
-		if (!parse_route_options (route, AF_INET, *iter, error)) {
-			nm_ip_route_unref (route);
-			goto error;
+			continue;
 		}
 
-		if (!nm_setting_ip_config_add_route (s_ip4, route))
-			PARSE_WARNING ("duplicate IP4 route");
-		nm_ip_route_unref (route);
+		if (!nm_setting_ip_config_add_route (s_ip, route))
+			PARSE_WARNING ("duplicate IPv%c route", addr_family == AF_INET ? '4' : '6');
 	}
 
-	success = TRUE;
-
-error:
-	g_free (contents);
-	g_strfreev (lines);
-	g_regex_unref (regex_to1);
-	g_regex_unref (regex_to2);
-	g_regex_unref (regex_via);
-	g_regex_unref (regex_metric);
-
-	return success;
+	return TRUE;
 }
 
 static void
 parse_dns_options (NMSettingIPConfig *ip_config, const char *value)
 {
-	char **options = NULL;
+	gs_free const char **options = NULL;
+	const char *const *item;
 
 	g_return_if_fail (ip_config);
 
@@ -811,16 +1082,12 @@ 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 = g_strsplit (value, " ", 0);
+	options = nm_utils_strsplit_set (value, " ");
 	if (options) {
-		char **item;
 		for (item = options; *item; item++) {
-			if (strlen (*item)) {
-				if (!nm_setting_ip_config_add_dns_option (ip_config, *item))
-					PARSE_WARNING ("can't add DNS option '%s'", *item);
-			}
+			if (!nm_setting_ip_config_add_dns_option (ip_config, *item))
+				PARSE_WARNING ("can't add DNS option '%s'", *item);
 		}
-		g_strfreev (options);
 	}
 }
 
@@ -873,157 +1140,6 @@ error:
 	return success;
 }
 
-static gboolean
-read_route6_file (const char *filename, NMSettingIPConfig *s_ip6, GError **error)
-{
-	char *contents = NULL;
-	gsize len = 0;
-	char **lines = NULL, **iter;
-	GRegex *regex_to1, *regex_to2, *regex_via, *regex_metric;
-	GMatchInfo *match_info;
-	char *dest = NULL, *prefix = NULL, *next_hop = NULL, *metric = NULL;
-	int prefix_int;
-	gint64 metric_int;
-	gboolean success = FALSE;
-
-	const char *pattern_empty = "^\\s*(\\#.*)?$";
-	const char *pattern_to1 = "^\\s*(default|" IPV6_ADDR_REGEX ")"  /* IPv6 or 'default' keyword */
-	                          "(?:/(\\d{1,3}))?";                   /* optional prefix */
-	const char *pattern_to2 = "to\\s+(default|" IPV6_ADDR_REGEX ")" /* IPv6 or 'default' keyword */
-	                          "(?:/(\\d{1,3}))?";                   /* optional prefix */
-	const char *pattern_via = "via\\s+(" IPV6_ADDR_REGEX ")";       /* IPv6 of gateway */
-	const char *pattern_metric = "metric\\s+(\\d+)";                /* metric */
-
-
-	g_return_val_if_fail (filename != NULL, FALSE);
-	g_return_val_if_fail (s_ip6 != NULL, FALSE);
-	g_return_val_if_fail (!error || !*error, FALSE);
-
-	/* Read the route file */
-	if (!g_file_get_contents (filename, &contents, &len, NULL) || !len) {
-		g_free (contents);
-		return TRUE;  /* missing/empty = success */
-	}
-
-	/* Create regexes for pieces to be matched */
-	regex_to1 = g_regex_new (pattern_to1, 0, 0, NULL);
-	regex_to2 = g_regex_new (pattern_to2, 0, 0, NULL);
-	regex_via = g_regex_new (pattern_via, 0, 0, NULL);
-	regex_metric = g_regex_new (pattern_metric, 0, 0, NULL);
-
-	/* Iterate through file lines */
-	lines = g_strsplit_set (contents, "\n\r", -1);
-	for (iter = lines; iter && *iter; iter++) {
-		NMIPRoute *route;
-
-		/* Skip empty lines */
-		if (g_regex_match_simple (pattern_empty, *iter, 0, 0))
-			continue;
-
-		/* Destination */
-		g_regex_match (regex_to1, *iter, 0, &match_info);
-		if (!g_match_info_matches (match_info)) {
-			g_match_info_free (match_info);
-			g_regex_match (regex_to2, *iter, 0, &match_info);
-			if (!g_match_info_matches (match_info)) {
-				g_match_info_free (match_info);
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Missing IP6 route destination address in record: '%s'", *iter);
-				goto error;
-			}
-		}
-		dest = g_match_info_fetch (match_info, 1);
-		if (!g_strcmp0 (dest, "default")) {
-			/* Ignore default route - NM handles it internally */
-			g_clear_pointer (&dest, g_free);
-			g_match_info_free (match_info);
-			PARSE_WARNING ("ignoring manual default route: '%s' (%s)", *iter, filename);
-			continue;
-		}
-
-		/* Prefix - is optional; 128 if missing */
-		prefix = g_match_info_fetch (match_info, 2);
-		g_match_info_free (match_info);
-		prefix_int = 128;
-		if (prefix) {
-			prefix_int = _nm_utils_ascii_str_to_int64 (prefix, 10, 1, 128, -1);
-			if (prefix_int == -1) {
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid IP6 route destination prefix '%s'", prefix);
-				g_free (dest);
-				g_free (prefix);
-				goto error;
-			}
-		}
-		g_free (prefix);
-
-		/* Next hop */
-		g_regex_match (regex_via, *iter, 0, &match_info);
-		if (g_match_info_matches (match_info)) {
-			next_hop = g_match_info_fetch (match_info, 1);
-			if (!nm_utils_ipaddr_valid (AF_INET6, next_hop)) {
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid IPv6 route nexthop address '%s'",
-				             next_hop);
-				g_match_info_free (match_info);
-				g_free (dest);
-				g_free (next_hop);
-				goto error;
-			}
-		} else {
-			/* Missing "via" is taken as :: */
-			next_hop = NULL;
-		}
-		g_match_info_free (match_info);
-
-		/* Metric */
-		g_regex_match (regex_metric, *iter, 0, &match_info);
-		metric_int = -1;
-		if (g_match_info_matches (match_info)) {
-			metric = g_match_info_fetch (match_info, 1);
-			metric_int = _nm_utils_ascii_str_to_int64 (metric, 10, 0, G_MAXUINT32, -1);
-			if (metric_int == -1) {
-				g_match_info_free (match_info);
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-				             "Invalid IP6 route metric '%s'", metric);
-				g_free (dest);
-				g_free (next_hop);
-				g_free (metric);
-				goto error;
-			}
-			g_free (metric);
-		}
-		g_match_info_free (match_info);
-
-		route = nm_ip_route_new (AF_INET6, dest, prefix_int, next_hop, metric_int, error);
-		g_free (dest);
-		g_free (next_hop);
-		if (!route)
-			goto error;
-
-		if (!parse_route_options (route, AF_INET6, *iter, error)) {
-			nm_ip_route_unref (route);
-			goto error;
-		}
-
-		if (!nm_setting_ip_config_add_route (s_ip6, route))
-			PARSE_WARNING ("duplicate IP6 route");
-		nm_ip_route_unref (route);
-	}
-
-	success = TRUE;
-
-error:
-	g_free (contents);
-	g_strfreev (lines);
-	g_regex_unref (regex_to1);
-	g_regex_unref (regex_to2);
-	g_regex_unref (regex_via);
-	g_regex_unref (regex_metric);
-
-	return success;
-}
-
 static NMSetting *
 make_user_setting (shvarFile *ifcfg, GError **error)
 {
@@ -1076,18 +1192,18 @@ static NMSetting *
 make_proxy_setting (shvarFile *ifcfg, GError **error)
 {
 	NMSettingProxy *s_proxy = NULL;
-	char *value = NULL;
+	gs_free char *value = NULL;
+	const char *v;
 	NMSettingProxyMethod method;
 
-	value = svGetValueStr_cp (ifcfg, "PROXY_METHOD");
-	if (!value)
+	v = svGetValueStr (ifcfg, "PROXY_METHOD", &value);
+	if (!v)
 		return NULL;
 
-	if (!g_ascii_strcasecmp (value, "auto"))
+	if (!g_ascii_strcasecmp (v, "auto"))
 		method = NM_SETTING_PROXY_METHOD_AUTO;
 	else
 		method = NM_SETTING_PROXY_METHOD_NONE;
-	g_free (value);
 
 	s_proxy = (NMSettingProxy *) nm_setting_proxy_new ();
 
@@ -1097,19 +1213,15 @@ make_proxy_setting (shvarFile *ifcfg, GError **error)
 		              NM_SETTING_PROXY_METHOD, (int) NM_SETTING_PROXY_METHOD_AUTO,
 		              NULL);
 
-		value = svGetValueStr_cp (ifcfg, "PAC_URL");
-		if (value) {
-			value = g_strstrip (value);
-			g_object_set (s_proxy, NM_SETTING_PROXY_PAC_URL, value, NULL);
-			g_free (value);
-		}
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "PAC_URL", &value);
+		if (v)
+			g_object_set (s_proxy, NM_SETTING_PROXY_PAC_URL, v, NULL);
 
-		value = svGetValueStr_cp (ifcfg, "PAC_SCRIPT");
-		if (value) {
-			value = g_strstrip (value);
-			g_object_set (s_proxy, NM_SETTING_PROXY_PAC_SCRIPT, value, NULL);
-			g_free (value);
-		}
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "PAC_SCRIPT", &value);
+		if (v)
+			g_object_set (s_proxy, NM_SETTING_PROXY_PAC_SCRIPT, v, NULL);
 
 		break;
 	case NM_SETTING_PROXY_METHOD_NONE:
@@ -1119,12 +1231,8 @@ make_proxy_setting (shvarFile *ifcfg, GError **error)
 		break;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "BROWSER_ONLY");
-	if (value) {
-		if (!g_ascii_strcasecmp (value, "yes"))
-			g_object_set (s_proxy, NM_SETTING_PROXY_BROWSER_ONLY, TRUE, NULL);
-		g_free (value);
-	}
+	if (svGetValueBoolean (ifcfg, "BROWSER_ONLY", FALSE))
+		g_object_set (s_proxy, NM_SETTING_PROXY_BROWSER_ONLY, TRUE, NULL);
 
 	return NM_SETTING (s_proxy);
 }
@@ -1132,12 +1240,14 @@ make_proxy_setting (shvarFile *ifcfg, GError **error)
 static NMSetting *
 make_ip4_setting (shvarFile *ifcfg,
                   const char *network_file,
+                  gboolean routes_read,
                   gboolean *out_has_defroute,
                   GError **error)
 {
 	gs_unref_object NMSettingIPConfig *s_ip4 = NULL;
 	gs_free char *route_path = NULL;
-	char *value = NULL;
+	gs_free char *value = NULL;
+	const char *v;
 	char *method;
 	gs_free char *dns_options_free = NULL;
 	const char *dns_options = NULL;
@@ -1151,6 +1261,8 @@ make_ip4_setting (shvarFile *ifcfg,
 	gint64 timeout;
 	gint priority;
 	char inet_buf[NM_UTILS_INET_ADDRSTRLEN];
+	const char *const *item;
+	guint32 route_table;
 
 	nm_assert (out_has_defroute && !*out_has_defroute);
 
@@ -1172,44 +1284,43 @@ make_ip4_setting (shvarFile *ifcfg,
 	/* Then check if GATEWAYDEV; it's global and overrides DEFROUTE */
 	network_ifcfg = svOpenFile (network_file, NULL);
 	if (network_ifcfg) {
-		char *gatewaydev;
+		gs_free char *gatewaydev_value = NULL;
+		const char *gatewaydev;
 
 		/* Get the connection ifcfg device name and the global gateway device */
-		value = svGetValueStr_cp (ifcfg, "DEVICE");
-		gatewaydev = svGetValueStr_cp (network_ifcfg, "GATEWAYDEV");
+		v = svGetValueStr (ifcfg, "DEVICE", &value);
+		gatewaydev = svGetValueStr (network_ifcfg, "GATEWAYDEV", &gatewaydev_value);
 		dns_options = svGetValue (network_ifcfg, "RES_OPTIONS", &dns_options_free);
 
 		/* If there was a global gateway device specified, then only connections
 		 * for that device can be the default connection.
 		 */
-		if (gatewaydev && value)
-			never_default = !!strcmp (value, gatewaydev);
+		if (gatewaydev && v)
+			never_default = !!strcmp (v, gatewaydev);
 
-		g_free (gatewaydev);
-		g_free (value);
+		nm_clear_g_free (&value);
 		svCloseFile (network_ifcfg);
 	}
 
-	value = svGetValueStr_cp (ifcfg, "BOOTPROTO");
+	v = svGetValueStr (ifcfg, "BOOTPROTO", &value);
 
-	if (!value || !*value || !g_ascii_strcasecmp (value, "none")) {
+	if (!v || !*v || !g_ascii_strcasecmp (v, "none")) {
 		if (is_any_ip4_address_defined (ifcfg, NULL))
 			method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
 		else
 			method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-	} else if (!g_ascii_strcasecmp (value, "bootp") || !g_ascii_strcasecmp (value, "dhcp")) {
+	} else if (!g_ascii_strcasecmp (v, "bootp") || !g_ascii_strcasecmp (v, "dhcp")) {
 		method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
-	} else if (!g_ascii_strcasecmp (value, "static")) {
+	} else if (!g_ascii_strcasecmp (v, "static")) {
 		if (is_any_ip4_address_defined (ifcfg, NULL))
 			method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
 		else
 			method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
-	} else if (!g_ascii_strcasecmp (value, "autoip")) {
+	} else if (!g_ascii_strcasecmp (v, "autoip")) {
 		method = NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL;
-	} else if (!g_ascii_strcasecmp (value, "shared")) {
+	} else if (!g_ascii_strcasecmp (v, "shared")) {
 		int idx;
 
-		g_free (value);
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_SHARED,
 		              NM_SETTING_IP_CONFIG_NEVER_DEFAULT, never_default,
@@ -1233,11 +1344,18 @@ make_ip4_setting (shvarFile *ifcfg,
 		return g_steal_pointer (&s_ip4);
 	} else {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Unknown BOOTPROTO '%s'", value);
-		g_free (value);
+		             "Unknown BOOTPROTO '%s'", v);
 		return NULL;
 	}
-	g_free (value);
+
+	/* the route table (policy routing) is ignored if we don't handle routes. */
+	route_table = svGetValueInt64 (ifcfg, "IPV4_ROUTE_TABLE", 10,
+	                               0, G_MAXUINT32, 0);
+	if (   route_table != 0
+	    && !routes_read) {
+		PARSE_WARNING ("'rule-' or 'rule6-' files are present; Policy routing (IPV4_ROUTE_TABLE) is ignored");
+		route_table = 0;
+	}
 
 	g_object_set (s_ip4,
 	              NM_SETTING_IP_CONFIG_METHOD, method,
@@ -1247,44 +1365,43 @@ make_ip4_setting (shvarFile *ifcfg,
 	              NM_SETTING_IP_CONFIG_MAY_FAIL, !svGetValueBoolean (ifcfg, "IPV4_FAILURE_FATAL", FALSE),
 	              NM_SETTING_IP_CONFIG_ROUTE_METRIC, svGetValueInt64 (ifcfg, "IPV4_ROUTE_METRIC", 10,
 	                                                                  -1, G_MAXUINT32, -1),
+	              NM_SETTING_IP_CONFIG_ROUTE_TABLE, (guint) route_table,
 	              NULL);
 
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0)
 		return g_steal_pointer (&s_ip4);
 
 	/* Handle DHCP settings */
-	value = svGetValueStr_cp (ifcfg, "DHCP_HOSTNAME");
-	if (value) {
-		g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, value, NULL);
-		g_free (value);
-	}
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DHCP_HOSTNAME", &value);
+	if (v)
+		g_object_set (s_ip4, NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, v, NULL);
 
-	value = svGetValueStr_cp (ifcfg, "DHCP_FQDN");
-	if (value) {
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DHCP_FQDN", &value);
+	if (v) {
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, NULL,
-		              NM_SETTING_IP4_CONFIG_DHCP_FQDN, value,
+		              NM_SETTING_IP4_CONFIG_DHCP_FQDN, v,
 		              NULL);
-		g_free (value);
 	}
 
 	g_object_set (s_ip4,
-		      NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, svGetValueBoolean (ifcfg, "DHCP_SEND_HOSTNAME", TRUE),
-		      NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, svGetValueInt64 (ifcfg, "IPV4_DHCP_TIMEOUT", 10, 0, G_MAXINT32, 0),
-		      NULL);
+	              NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, svGetValueBoolean (ifcfg, "DHCP_SEND_HOSTNAME", TRUE),
+	              NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, svGetValueInt64 (ifcfg, "IPV4_DHCP_TIMEOUT", 10, 0, G_MAXINT32, 0),
+	              NULL);
 
-	value = svGetValueStr_cp (ifcfg, "DHCP_CLIENT_ID");
-	if (value) {
-		g_object_set (s_ip4, NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, value, NULL);
-		g_free (value);
-	}
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DHCP_CLIENT_ID", &value);
+	if (v)
+		g_object_set (s_ip4, NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, v, NULL);
 
 	/* Read static IP addresses.
 	 * Read them even for AUTO method - in this case the addresses are
 	 * added to the automatic ones. Note that this is not currently supported by
 	 * the legacy 'network' service (ifup-eth).
 	 */
-	for (i = -1; i < 256; i++) {
+	for (i = -1;; i++) {
 		NMIPAddress *addr = NULL;
 
 		/* gateway will only be set if still unset. Hence, we don't leak gateway
@@ -1337,46 +1454,40 @@ make_ip4_setting (shvarFile *ifcfg,
 		char tag[256];
 
 		numbered_tag (tag, "DNS", i);
-		value = svGetValueStr_cp (ifcfg, tag);
-		if (value) {
-			if (nm_utils_ipaddr_valid (AF_INET, value)) {
-				if (!nm_setting_ip_config_add_dns (s_ip4, value))
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, tag, &value);
+		if (v) {
+			if (nm_utils_ipaddr_valid (AF_INET, v)) {
+				if (!nm_setting_ip_config_add_dns (s_ip4, v))
 					PARSE_WARNING ("duplicate DNS server %s", tag);
-			} else if (nm_utils_ipaddr_valid (AF_INET6, value)) {
+			} else if (nm_utils_ipaddr_valid (AF_INET6, v)) {
 				/* Ignore IPv6 addresses */
 			} else {
-				PARSE_WARNING ("invalid DNS server address %s", value);
-				g_free (value);
+				PARSE_WARNING ("invalid DNS server address %s", v);
 				return NULL;
 			}
-
-			g_free (value);
 		}
 	}
 
 	/* DNS searches */
-	value = svGetValueStr_cp (ifcfg, "DOMAIN");
-	if (value) {
-		char **searches = NULL;
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DOMAIN", &value);
+	if (v) {
+		gs_free const char **searches = NULL;
 
-		searches = g_strsplit (value, " ", 0);
+		searches = nm_utils_strsplit_set (v, " ");
 		if (searches) {
-			char **item;
 			for (item = searches; *item; item++) {
-				if (strlen (*item)) {
-					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
-						PARSE_WARNING ("duplicate DNS domain '%s'", *item);
-				}
+				if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
+					PARSE_WARNING ("duplicate DNS domain '%s'", *item);
 			}
-			g_strfreev (searches);
 		}
-		g_free (value);
 	}
 
 	/* DNS options */
+	nm_clear_g_free (&value);
 	parse_dns_options (s_ip4, svGetValue (ifcfg, "RES_OPTIONS", &value));
 	parse_dns_options (s_ip4, dns_options);
-	g_free (value);
 
 	/* DNS priority */
 	priority = svGetValueInt64 (ifcfg, "IPV4_DNS_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
@@ -1388,13 +1499,13 @@ make_ip4_setting (shvarFile *ifcfg,
 	/* Static routes  - route-<name> file */
 	route_path = utils_get_route_path (svFileGetName (ifcfg));
 
-	if (utils_has_complex_routes (route_path)) {
-		PARSE_WARNING ("'rule-' or 'rule6-' file is present; you will need to use a dispatcher script to apply these routes");
+	if (!routes_read) {
+		/* NOP */
 	} else if (utils_has_route_file_new_syntax (route_path)) {
 		/* Parse route file in new syntax */
 		route_ifcfg = utils_get_route_ifcfg (svFileGetName (ifcfg), FALSE);
 		if (route_ifcfg) {
-			for (i = 0; i < 256; i++) {
+			for (i = 0;; i++) {
 				NMIPRoute *route = NULL;
 
 				if (!read_one_ip4_route (route_ifcfg, i, &route, error)) {
@@ -1412,28 +1523,24 @@ make_ip4_setting (shvarFile *ifcfg,
 			svCloseFile (route_ifcfg);
 		}
 	} else {
-		if (!read_route_file_legacy (route_path, s_ip4, error))
+		if (!read_route_file (AF_INET, route_path, s_ip4, error))
 			return NULL;
 	}
 
 	/* Legacy value NM used for a while but is incorrect (rh #459370) */
 	if (!nm_setting_ip_config_get_num_dns_searches (s_ip4)) {
-		value = svGetValueStr_cp (ifcfg, "SEARCH");
-		if (value) {
-			char **searches = NULL;
+		nm_clear_g_free (&value);
+		v = svGetValueStr (ifcfg, "SEARCH", &value);
+		if (v) {
+			gs_free const char **searches = NULL;
 
-			searches = g_strsplit (value, " ", 0);
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
-				char **item;
 				for (item = searches; *item; item++) {
-					if (strlen (*item)) {
-						if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
-							PARSE_WARNING ("duplicate DNS search '%s'", *item);
-					}
+					if (!nm_setting_ip_config_add_dns_search (s_ip4, *item))
+						PARSE_WARNING ("duplicate DNS search '%s'", *item);
 				}
-				g_strfreev (searches);
 			}
-			g_free (value);
 		}
 	}
 
@@ -1450,7 +1557,6 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 {
 	GDir *dir;
 	char *dirname, *base;
-	shvarFile *parsed;
 	NMIPAddress *base_addr = NULL;
 	GError *err = NULL;
 
@@ -1472,8 +1578,11 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 		gboolean ok;
 
 		while ((item = g_dir_read_name (dir))) {
+			nm_auto_shvar_file_close shvarFile *parsed = NULL;
 			gs_free char *gateway = NULL;
-			char *full_path, *device;
+			gs_free char *device_value = NULL;
+			gs_free char *full_path = NULL;
+			const char *device;
 			const char *p;
 
 			if (!utils_is_ifcfg_alias_file (item, base))
@@ -1489,32 +1598,25 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 			}
 			if (*p) {
 				PARSE_WARNING ("ignoring alias file '%s' with invalid name", full_path);
-				g_free (full_path);
 				continue;
 			}
 
 			parsed = svOpenFile (full_path, &err);
 			if (!parsed) {
 				PARSE_WARNING ("couldn't parse alias file '%s': %s", full_path, err->message);
-				g_free (full_path);
 				g_clear_error (&err);
 				continue;
 			}
 
-			device = svGetValueStr_cp (parsed, "DEVICE");
+			device = svGetValueStr (parsed, "DEVICE", &device_value);
 			if (!device) {
 				PARSE_WARNING ("alias file '%s' has no DEVICE", full_path);
-				svCloseFile (parsed);
-				g_free (full_path);
 				continue;
 			}
 			/* We know that item starts with IFCFG_TAG from utils_is_ifcfg_alias_file() */
 			if (strcmp (device, item + strlen (IFCFG_TAG)) != 0) {
 				PARSE_WARNING ("alias file '%s' has invalid DEVICE (%s) for filename",
 				               full_path, device);
-				g_free (device);
-				svCloseFile (parsed);
-				g_free (full_path);
 				continue;
 			}
 
@@ -1549,11 +1651,6 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 				g_clear_error (&err);
 			}
 			nm_ip_address_unref (addr);
-
-			svCloseFile (parsed);
-
-			g_free (device);
-			g_free (full_path);
 		}
 
 		g_dir_close (dir);
@@ -1569,6 +1666,7 @@ read_aliases (NMSettingIPConfig *s_ip4, gboolean read_defroute, const char *file
 static NMSetting *
 make_ip6_setting (shvarFile *ifcfg,
                   const char *network_file,
+                  gboolean routes_read,
                   GError **error)
 {
 	NMSettingIPConfig *s_ip6 = NULL;
@@ -1580,14 +1678,17 @@ make_ip6_setting (shvarFile *ifcfg,
 	gboolean ipv6init, ipv6forwarding, dhcp6 = FALSE;
 	char *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
 	char *ipv6addr, *ipv6addr_secondaries;
-	char **list = NULL, **iter;
+	gs_free const char **list = NULL;
+	const char *const *iter;
 	guint32 i;
+	int i_val;
+	GError *local = NULL;
 	gint priority;
 	shvarFile *network_ifcfg;
 	gboolean never_default = FALSE;
 	gboolean ip6_privacy = FALSE, ip6_privacy_prefer_public_ip;
 	NMSettingIP6ConfigPrivacy ip6_privacy_val;
-	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
+	guint32 route_table;
 
 	s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
 
@@ -1689,6 +1790,15 @@ make_ip6_setting (shvarFile *ifcfg,
 	                      NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 	g_free (str_value);
 
+	/* the route table (policy routing) is ignored if we don't handle routes. */
+	route_table = svGetValueInt64 (ifcfg, "IPV6_ROUTE_TABLE", 10,
+	                               0, G_MAXUINT32, 0);
+	if (   route_table != 0
+	    && !routes_read) {
+		PARSE_WARNING ("'rule-' or 'rule6-' files are present; Policy routing (IPV6_ROUTE_TABLE) is ignored");
+		route_table = 0;
+	}
+
 	g_object_set (s_ip6,
 	              NM_SETTING_IP_CONFIG_METHOD, method,
 	              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, !svGetValueBoolean (ifcfg, "IPV6_PEERDNS", TRUE),
@@ -1697,6 +1807,7 @@ make_ip6_setting (shvarFile *ifcfg,
 	              NM_SETTING_IP_CONFIG_MAY_FAIL, !svGetValueBoolean (ifcfg, "IPV6_FAILURE_FATAL", FALSE),
 	              NM_SETTING_IP_CONFIG_ROUTE_METRIC, svGetValueInt64 (ifcfg, "IPV6_ROUTE_METRIC", 10,
 	                                                                  -1, G_MAXUINT32, -1),
+	              NM_SETTING_IP_CONFIG_ROUTE_TABLE, (guint) route_table,
 	              NM_SETTING_IP6_CONFIG_IP6_PRIVACY, ip6_privacy_val,
 	              NULL);
 
@@ -1736,21 +1847,18 @@ make_ip6_setting (shvarFile *ifcfg,
 	g_free (ipv6addr);
 	g_free (ipv6addr_secondaries);
 
-	list = g_strsplit_set (value, " ", 0);
+	list = nm_utils_strsplit_set (value, " ");
 	g_free (value);
 	for (iter = list, i = 0; iter && *iter; iter++, i++) {
 		NMIPAddress *addr = NULL;
 
-		if (!parse_full_ip6_address (ifcfg, *iter, i, &addr, error)) {
-			g_strfreev (list);
+		if (!parse_full_ip6_address (ifcfg, *iter, i, &addr, error))
 			goto error;
-		}
 
 		if (!nm_setting_ip_config_add_address (s_ip6, addr))
 			PARSE_WARNING ("duplicate IP6 address");
 		nm_ip_address_unref (addr);
 	}
-	g_strfreev (list);
 
 	/* Gateway */
 	if (nm_setting_ip_config_get_num_addresses (s_ip6)) {
@@ -1779,21 +1887,14 @@ make_ip6_setting (shvarFile *ifcfg,
 		}
 	}
 
-	/* IPv6 addressing mode configuration */
-	str_value = svGetValueStr_cp (ifcfg, "IPV6_ADDR_GEN_MODE");
-	if (str_value) {
-		if (nm_utils_enum_from_str (nm_setting_ip6_config_addr_gen_mode_get_type (), str_value,
-		                            (int *) &addr_gen_mode, NULL))
-			g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, addr_gen_mode, NULL);
-		else
-			PARSE_WARNING ("Invalid IPV6_ADDR_GEN_MODE");
-		g_free (str_value);
-	} else {
-		g_object_set (s_ip6,
-		              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
-		              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
-		              NULL);
+	i_val = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64;
+	if (!svGetValueEnum (ifcfg, "IPV6_ADDR_GEN_MODE",
+	                     nm_setting_ip6_config_addr_gen_mode_get_type (),
+	                     &i_val, &local)) {
+		PARSE_WARNING ("%s", local->message);
+		g_clear_error (&local);
 	}
+	g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, i_val, NULL);
 
 	/* IPv6 tokenized interface identifier */
 	str_value = svGetValueStr_cp (ifcfg, "IPV6_TOKEN");
@@ -1831,12 +1932,13 @@ make_ip6_setting (shvarFile *ifcfg,
 
 	/* DNS searches ('DOMAIN' key) are read by make_ip4_setting() and included in NMSettingIPConfig */
 
-	if (!utils_has_complex_routes (svFileGetName (ifcfg))) {
+	if (!routes_read) {
+		/* NOP */
+	} else {
 		/* Read static routes from route6-<interface> file */
 		route6_path = utils_get_route6_path (svFileGetName (ifcfg));
-		if (!read_route6_file (route6_path, s_ip6, error))
+		if (!read_route_file (AF_INET6, route6_path, s_ip6, error))
 			goto error;
-
 		g_free (route6_path);
 	}
 
@@ -1860,57 +1962,6 @@ error:
 	return NULL;
 }
 
-static void
-check_if_bond_slave (shvarFile *ifcfg,
-                     NMSettingConnection *s_con)
-{
-	char *value;
-
-	value = svGetValueStr_cp (ifcfg, "MASTER_UUID");
-	if (!value)
-		value = svGetValueStr_cp (ifcfg, "MASTER");
-
-	if (value) {
-		g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, value, NULL);
-		g_object_set (s_con,
-		              NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_BOND_SETTING_NAME,
-		              NULL);
-		g_free (value);
-	}
-
-	/* We should be checking for SLAVE=yes as well, but NM used to not set that,
-	 * so for backward-compatibility, we don't check.
-	 */
-}
-
-static gboolean
-check_if_team_slave (shvarFile *ifcfg,
-                     NMSettingConnection *s_con)
-{
-	gs_free char *value = NULL;
-
-	value = svGetValueStr_cp (ifcfg, "TEAM_MASTER_UUID");
-	if (!value)
-		value = svGetValueStr_cp (ifcfg, "TEAM_MASTER");
-	if (!value)
-		return FALSE;
-
-	g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, value, NULL);
-	g_object_set (s_con, NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_TEAM_SETTING_NAME, NULL);
-	return TRUE;
-}
-
-static void
-check_if_slave (shvarFile *ifcfg,
-                NMSettingConnection *s_con)
-{
-	g_return_if_fail (NM_IS_SETTING_CONNECTION (s_con));
-
-	if (check_if_team_slave (ifcfg, s_con))
-		return;
-	check_if_bond_slave (ifcfg, s_con);
-}
-
 typedef struct {
 	const char *enable_key;
 	const char *advertise_key;
@@ -1959,29 +2010,29 @@ read_dcb_app (shvarFile *ifcfg,
               GError **error)
 {
 	NMSettingDcbFlags flags = NM_SETTING_DCB_FLAG_NONE;
-	char *tmp, *val;
+	gs_free char *value = NULL;
+	const char *v;
 	gboolean success = TRUE;
 	int priority = -1;
+	char key[255];
 
 	flags = read_dcb_flags (ifcfg, flags_prop);
 
 	/* Priority */
-	tmp = g_strdup_printf ("DCB_APP_%s_PRIORITY", app);
-	val = svGetValueStr_cp (ifcfg, tmp);
-	if (val) {
-		priority = _nm_utils_ascii_str_to_int64 (val, 0, 0, 7, -1);
+	nm_sprintf_buf (key, "DCB_APP_%s_PRIORITY", app);
+	v = svGetValueStr (ifcfg, key, &value);
+	if (v) {
+		priority = _nm_utils_ascii_str_to_int64 (v, 0, 0, 7, -1);
 		if (priority < 0) {
 			success = FALSE;
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Invalid %s value '%s' (expected 0 - 7)",
-			             tmp, val);
+			             key, v);
 		}
-		g_free (val);
 
 		if (!(flags & NM_SETTING_DCB_FLAG_ENABLE))
 			PARSE_WARNING ("ignoring DCB %s priority; app not enabled", app);
 	}
-	g_free (tmp);
 
 	if (success) {
 		g_object_set (G_OBJECT (s_dcb),
@@ -2004,11 +2055,12 @@ read_dcb_bool_array (shvarFile *ifcfg,
                      DcbSetBoolFunc set_func,
                      GError **error)
 {
-	gs_free char *val = NULL;
+	gs_free char *value = NULL;
+	const char *v;
 	guint i;
 
-	val = svGetValueStr_cp (ifcfg, prop);
-	if (!val)
+	v = svGetValueStr (ifcfg, prop, &value);
+	if (!v)
 		return TRUE;
 
 	if (!(flags & NM_SETTING_DCB_FLAG_ENABLE)) {
@@ -2016,8 +2068,8 @@ read_dcb_bool_array (shvarFile *ifcfg,
 		return TRUE;
 	}
 
-	if (strlen (val) != 8) {
-		PARSE_WARNING ("%s value '%s' must be 8 characters long", prop, val);
+	if (strlen (v) != 8) {
+		PARSE_WARNING ("%s value '%s' must be 8 characters long", prop, v);
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		                     "boolean array must be 8 characters");
 		return FALSE;
@@ -2025,13 +2077,13 @@ read_dcb_bool_array (shvarFile *ifcfg,
 
 	/* All characters must be either 0 or 1 */
 	for (i = 0; i < 8; i++) {
-		if (val[i] != '0' && val[i] != '1') {
-			PARSE_WARNING ("invalid %s value '%s': not all 0s and 1s", prop, val);
+		if (v[i] != '0' && v[i] != '1') {
+			PARSE_WARNING ("invalid %s value '%s': not all 0s and 1s", prop, v);
 			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			                     "invalid boolean digit");
 			return FALSE;
 		}
-		set_func (s_dcb, i, (val[i] == '1'));
+		set_func (s_dcb, i, (v[i] == '1'));
 	}
 	return TRUE;
 }
@@ -2096,8 +2148,8 @@ read_dcb_percent_array (shvarFile *ifcfg,
                         GError **error)
 {
 	gs_free char *val = NULL;
-	gs_strfreev char **split = NULL;
-	char **iter;
+	gs_free const char **split = NULL;
+	const char *const *iter;
 	guint i, sum = 0;
 
 	val = svGetValueStr_cp (ifcfg, prop);
@@ -2109,8 +2161,8 @@ read_dcb_percent_array (shvarFile *ifcfg,
 		return TRUE;
 	}
 
-	split = g_strsplit_set (val, ",", 0);
-	if (!split || (g_strv_length (split) != 8)) {
+	split = nm_utils_strsplit_set (val, ",");
+	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,
 		                     "percent array must be 8 elements");
@@ -2531,15 +2583,17 @@ fill_wpa_ciphers (shvarFile *ifcfg,
                   gboolean group,
                   gboolean adhoc)
 {
-	char *value = NULL, *p;
-	char **list = NULL, **iter;
+	gs_free char *value = NULL;
+	const char *p;
+	gs_free const char **list = NULL;
+	const char *const *iter;
 	int i = 0;
 
-	p = value = svGetValueStr_cp (ifcfg, group ? "CIPHER_GROUP" : "CIPHER_PAIRWISE");
-	if (!value)
+	p = svGetValueStr (ifcfg, group ? "CIPHER_GROUP" : "CIPHER_PAIRWISE", &value);
+	if (!p)
 		return TRUE;
 
-	list = g_strsplit_set (p, " ", 0);
+	list = nm_utils_strsplit_set (p, " ");
 	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
@@ -2578,9 +2632,6 @@ fill_wpa_ciphers (shvarFile *ifcfg,
 		}
 	}
 
-	if (list)
-		g_strfreev (list);
-	g_free (value);
 	return TRUE;
 }
 
@@ -2637,6 +2688,23 @@ 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,
@@ -2646,6 +2714,7 @@ eap_simple_reader (const char *eap_method,
                    GError **error)
 {
 	NMSettingSecretFlags flags;
+	GBytes *bytes;
 	char *value;
 
 	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_IDENTITY");
@@ -2656,28 +2725,29 @@ eap_simple_reader (const char *eap_method,
 		return FALSE;
 	}
 	g_object_set (s_8021x, NM_SETTING_802_1X_IDENTITY, value, NULL);
-	g_free (value);
+	nm_clear_g_free (&value);
 
-	flags = read_secret_flags (ifcfg, "IEEE_8021X_PASSWORD_FLAGS");
+	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);
+	}
 
-	/* Only read the password if it's system-owned */
-	if (flags == NM_SETTING_SECRET_FLAG_NONE) {
-		value = svGetValueStr_cp (ifcfg, "IEEE_8021X_PASSWORD");
-		if (!value && keys) {
-			/* Try the lookaside keys file */
-			value = svGetValueStr_cp (keys, "IEEE_8021X_PASSWORD");
-		}
-
-		if (!value) {
+	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,
-			             "Missing IEEE_8021X_PASSWORD for EAP method '%s'.",
-			             eap_method);
+			             "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, value, NULL);
-		g_free (value);
+		g_object_set (s_8021x, NM_SETTING_802_1X_PASSWORD_RAW, bytes, NULL);
+		g_bytes_unref (bytes);
+		nm_clear_g_free (&value);
 	}
 
 	return TRUE;
@@ -2795,14 +2865,6 @@ eap_tls_reader (const char *eap_method,
 			/* Try the lookaside keys file */
 			privkey_password = svGetValueStr_cp (keys, pk_pw_key);
 		}
-
-		if (!privkey_password) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Missing %s for EAP method '%s'.",
-			             pk_pw_key,
-			             eap_method);
-			return FALSE;
-		}
 	}
 
 	/* The private key itself */
@@ -2843,8 +2905,7 @@ eap_tls_reader (const char *eap_method,
 	 * 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_RAW_KEY
-	    || privkey_format == NM_SETTING_802_1X_CK_FORMAT_X509) {
+	if (privkey_format != NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
 		gs_free char *real_cert_value = NULL;
 		gs_free char *client_cert = NULL;
 
@@ -2888,98 +2949,89 @@ eap_peap_reader (const char *eap_method,
                  gboolean phase2,
                  GError **error)
 {
-	char *anon_ident = NULL;
-	char *ca_cert = NULL;
-	char *real_cert_value = NULL;
-	char *inner_auth = NULL;
-	char *peapver = NULL;
-	char *lower;
-	char **list = NULL, **iter;
-	gboolean success = FALSE;
+	gs_free char *value = NULL;
+	const char *v;
+	gs_free const char **list = NULL;
+	const char *const *iter;
 	NMSetting8021xCKScheme scheme;
 
-	ca_cert = svGetValueStr_cp (ifcfg, "IEEE_8021X_CA_CERT");
-	if (ca_cert) {
-		real_cert_value = get_cert_value (svFileGetName (ifcfg), ca_cert, &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))
-			goto done;
+			return FALSE;
 	} else {
 		PARSE_WARNING ("missing IEEE_8021X_CA_CERT for EAP method '%s'; this is insecure!",
 		               eap_method);
 	}
 
-	peapver = svGetValueStr_cp (ifcfg, "IEEE_8021X_PEAP_VERSION");
-	if (peapver) {
-		if (!strcmp (peapver, "0"))
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_PEAP_VERSION", &value);
+	if (v) {
+		if (!strcmp (v, "0"))
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "0", NULL);
-		else if (!strcmp (peapver, "1"))
+		else if (!strcmp (v, "1"))
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPVER, "1", NULL);
 		else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Unknown IEEE_8021X_PEAP_VERSION value '%s'",
-			             peapver);
-			goto done;
+			             v);
+			return FALSE;
 		}
 	}
 
 	if (svGetValueBoolean (ifcfg, "IEEE_8021X_PEAP_FORCE_NEW_LABEL", FALSE))
 		g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_PEAPLABEL, "1", NULL);
 
-	anon_ident = svGetValueStr_cp (ifcfg, "IEEE_8021X_ANON_IDENTITY");
-	if (anon_ident)
-		g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, anon_ident, NULL);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_ANON_IDENTITY", &value);
+	if (v)
+		g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, v, NULL);
 
-	inner_auth = svGetValueStr_cp (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS");
-	if (!inner_auth) {
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS", &value);
+	if (!v) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Missing IEEE_8021X_INNER_AUTH_METHODS.");
-		goto done;
+		return FALSE;
 	}
 
 	/* Handle options for the inner auth method */
-	list = g_strsplit (inner_auth, " ", 0);
-	for (iter = list; iter && *iter; iter++) {
-		if (!strlen (*iter))
-			continue;
-
-		if (   !strcmp (*iter, "MSCHAPV2")
-		    || !strcmp (*iter, "MD5")
-		    || !strcmp (*iter, "GTC")) {
+	list = nm_utils_strsplit_set (v, " ");
+	iter = list;
+	if (iter) {
+		if (NM_IN_STRSET (*iter, "MSCHAPV2",
+		                         "MD5",
+		                         "GTC")) {
 			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
-				goto done;
-		} else if (!strcmp (*iter, "TLS")) {
+				return FALSE;
+		} else if (nm_streq (*iter, "TLS")) {
 			if (!eap_tls_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
-				goto done;
+				return FALSE;
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Unknown IEEE_8021X_INNER_AUTH_METHOD '%s'.",
 			             *iter);
-			goto done;
+			return FALSE;
 		}
 
-		lower = g_ascii_strdown (*iter, -1);
-		g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, lower, NULL);
-		g_free (lower);
-		break;
+		{
+			gs_free char *lower = NULL;
+
+			lower = g_ascii_strdown (*iter, -1);
+			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, lower, NULL);
+		}
 	}
 
 	if (!nm_setting_802_1x_get_phase2_auth (s_8021x)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "No valid IEEE_8021X_INNER_AUTH_METHODS found.");
-		goto done;
+		return FALSE;
 	}
 
-	success = TRUE;
-
-done:
-	if (list)
-		g_strfreev (list);
-	g_free (inner_auth);
-	g_free (peapver);
-	g_free (real_cert_value);
-	g_free (ca_cert);
-	g_free (anon_ident);
-	return success;
+	return TRUE;
 }
 
 static gboolean
@@ -2990,81 +3042,70 @@ eap_ttls_reader (const char *eap_method,
                  gboolean phase2,
                  GError **error)
 {
-	gboolean success = FALSE;
-	char *anon_ident = NULL;
-	char *ca_cert = NULL;
-	char *real_cert_value = NULL;
-	char *inner_auth = NULL;
-	char *tmp;
-	char **list = NULL, **iter;
+	gs_free char *inner_auth = NULL;
+	gs_free char *value = NULL;
+	const char *v;
+	gs_free const char **list = NULL;
+	const char *const *iter;
 	NMSetting8021xCKScheme scheme;
 
-	ca_cert = svGetValueStr_cp (ifcfg, "IEEE_8021X_CA_CERT");
-	if (ca_cert) {
-		real_cert_value = get_cert_value (svFileGetName (ifcfg), ca_cert, &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))
-			goto done;
+			return FALSE;
 	} else {
 		PARSE_WARNING ("missing IEEE_8021X_CA_CERT for EAP method '%s'; this is insecure!",
 		               eap_method);
 	}
 
-	anon_ident = svGetValueStr_cp (ifcfg, "IEEE_8021X_ANON_IDENTITY");
-	if (anon_ident)
-		g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, anon_ident, NULL);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_ANON_IDENTITY", &value);
+	if (v)
+		g_object_set (s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, v, NULL);
 
-	tmp = svGetValueStr_cp (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS");
-	if (!tmp) {
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS", &value);
+	if (!v) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Missing IEEE_8021X_INNER_AUTH_METHODS.");
-		goto done;
+		return FALSE;
 	}
 
-	inner_auth = g_ascii_strdown (tmp, -1);
-	g_free (tmp);
+	inner_auth = g_ascii_strdown (v, -1);
 
 	/* Handle options for the inner auth method */
-	list = g_strsplit (inner_auth, " ", 0);
-	for (iter = list; iter && *iter; iter++) {
-		if (!strlen (*iter))
-			continue;
-
-		if (   !strcmp (*iter, "mschapv2")
-		    || !strcmp (*iter, "mschap")
-		    || !strcmp (*iter, "pap")
-		    || !strcmp (*iter, "chap")) {
+	list = nm_utils_strsplit_set (inner_auth, " ");
+	iter = list;
+	if (iter) {
+		if (NM_IN_STRSET (*iter, "mschapv2",
+		                         "mschap",
+		                         "pap",
+		                         "chap")) {
 			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
-				goto done;
+				return FALSE;
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, *iter, NULL);
-		} else if (!strcmp (*iter, "eap-tls")) {
+		} else if (nm_streq (*iter, "eap-tls")) {
 			if (!eap_tls_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
-				goto done;
+				return FALSE;
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, "tls", NULL);
-		} else if (   !strcmp (*iter, "eap-mschapv2")
-		           || !strcmp (*iter, "eap-md5")
-		           || !strcmp (*iter, "eap-gtc")) {
+		} else if (NM_IN_STRSET (*iter, "eap-mschapv2",
+		                                "eap-md5",
+		                                "eap-gtc")) {
 			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
-				goto done;
+				return FALSE;
 			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTHEAP, (*iter + NM_STRLEN ("eap-")), NULL);
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Unknown IEEE_8021X_INNER_AUTH_METHOD '%s'.",
 			             *iter);
-			goto done;
+			return FALSE;
 		}
-		break;
 	}
 
-	success = TRUE;
-
-done:
-	if (list)
-		g_strfreev (list);
-	g_free (inner_auth);
-	g_free (real_cert_value);
-	g_free (ca_cert);
-	g_free (anon_ident);
-	return success;
+	return TRUE;
 }
 
 static gboolean
@@ -3081,7 +3122,8 @@ eap_fast_reader (const char *eap_method,
 	char *inner_auth = NULL;
 	char *fast_provisioning = NULL;
 	char *lower;
-	char **list = NULL, **iter;
+	gs_free const char **list = NULL;
+	const char *const *iter;
 	const char *pac_prov_str;
 	gboolean allow_unauth = FALSE, allow_auth = FALSE;
 	gboolean success = FALSE;
@@ -3094,10 +3136,10 @@ eap_fast_reader (const char *eap_method,
 
 	fast_provisioning = svGetValueStr_cp (ifcfg, "IEEE_8021X_FAST_PROVISIONING");
 	if (fast_provisioning) {
-		list = g_strsplit_set (fast_provisioning, " \t", 0);
-		for (iter = list; iter && *iter; iter++) {
-			if (**iter == '\0')
-				continue;
+		gs_free const char **list1 = NULL;
+
+		list1 = nm_utils_strsplit_set (fast_provisioning, " \t");
+		for (iter = list1; iter && *iter; iter++) {
 			if (strcmp (*iter, "allow-unauth") == 0)
 				allow_unauth = TRUE;
 			else if (strcmp (*iter, "allow-auth") == 0)
@@ -3108,8 +3150,6 @@ eap_fast_reader (const char *eap_method,
 				               *iter);
 			}
 		}
-		g_strfreev (list);
-		list = NULL;
 	}
 	pac_prov_str = allow_unauth ? (allow_auth ? "3" : "1") : (allow_auth ? "2" : "0");
 	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, pac_prov_str, NULL);
@@ -3132,11 +3172,9 @@ eap_fast_reader (const char *eap_method,
 	}
 
 	/* Handle options for the inner auth method */
-	list = g_strsplit (inner_auth, " ", 0);
-	for (iter = list; iter && *iter; iter++) {
-		if (!strlen (*iter))
-			continue;
-
+	list = nm_utils_strsplit_set (inner_auth, " ");
+	iter = list;
+	if (iter) {
 		if (   !strcmp (*iter, "MSCHAPV2")
 		    || !strcmp (*iter, "GTC")) {
 			if (!eap_simple_reader (*iter, ifcfg, keys, s_8021x, TRUE, error))
@@ -3151,7 +3189,6 @@ eap_fast_reader (const char *eap_method,
 		lower = g_ascii_strdown (*iter, -1);
 		g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, lower, NULL);
 		g_free (lower);
-		break;
 	}
 
 	if (!nm_setting_802_1x_get_phase2_auth (s_8021x)) {
@@ -3163,7 +3200,6 @@ eap_fast_reader (const char *eap_method,
 	success = TRUE;
 
 done:
-	g_strfreev (list);
 	g_free (inner_auth);
 	g_free (fast_provisioning);
 	g_free (real_pac_path);
@@ -3204,22 +3240,21 @@ read_8021x_list_value (shvarFile *ifcfg,
                        NMSetting8021x *setting,
                        const char *prop_name)
 {
-	char *value;
-	char **strv;
+	gs_free char *value = NULL;
+	gs_free const char **strv = NULL;
+	const char *v;
 
 	g_return_if_fail (ifcfg != NULL);
 	g_return_if_fail (ifcfg_var_name != NULL);
 	g_return_if_fail (prop_name != NULL);
 
-	value = svGetValueStr_cp (ifcfg, ifcfg_var_name);
-	if (!value)
+	v = svGetValueStr (ifcfg, ifcfg_var_name, &value);
+	if (!v)
 		return;
 
-	strv = g_strsplit_set (value, " \t", 0);
-	if (strv && strv[0])
+	strv = nm_utils_strsplit_set (v, " \t");
+	if (strv)
 		g_object_set (setting, prop_name, strv, NULL);
-	g_strfreev (strv);
-	g_free (value);
 }
 
 static NMSetting8021x *
@@ -3230,21 +3265,23 @@ fill_8021x (shvarFile *ifcfg,
             GError **error)
 {
 	nm_auto_shvar_file_close shvarFile *keys = NULL;
-	NMSetting8021x *s_8021x;
-	char *value;
-	char **list = NULL, **iter;
+	gs_unref_object NMSetting8021x *s_8021x = NULL;
+	gs_free char *value = NULL;
+	const char *v;
+	gs_free const char **list = NULL;
+	const char *const *iter;
 	gint64 timeout;
+	int i_val;
 
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_EAP_METHODS");
-	if (!value) {
+	v = svGetValueStr (ifcfg, "IEEE_8021X_EAP_METHODS", &value);
+	if (!v) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Missing IEEE_8021X_EAP_METHODS for key management '%s'",
 		             key_mgmt);
 		return NULL;
 	}
 
-	list = g_strsplit (value, " ", 0);
-	g_free (value);
+	list = nm_utils_strsplit_set (v, " ");
 
 	s_8021x = (NMSetting8021x *) nm_setting_802_1x_new ();
 
@@ -3255,7 +3292,7 @@ fill_8021x (shvarFile *ifcfg,
 	for (iter = list; iter && *iter; iter++) {
 		EAPReader *eap = &eap_readers[0];
 		gboolean found = FALSE;
-		char *lower = NULL;
+		gs_free char *lower = NULL;
 
 		lower = g_ascii_strdown (*iter, -1);
 		while (eap->method) {
@@ -3273,10 +3310,9 @@ fill_8021x (shvarFile *ifcfg,
 			}
 
 			/* Parse EAP method specific options */
-			if (!(*eap->reader)(lower, ifcfg, keys, s_8021x, FALSE, error)) {
-				g_free (lower);
-				goto error;
-			}
+			if (!(*eap->reader)(lower, ifcfg, keys, s_8021x, FALSE, error))
+				return NULL;
+
 			nm_setting_802_1x_add_eap_method (s_8021x, lower);
 			found = TRUE;
 			break;
@@ -3287,65 +3323,46 @@ next:
 
 		if (!found)
 			PARSE_WARNING ("ignored unknown IEEE_8021X_EAP_METHOD '%s'.", lower);
-		g_free (lower);
 	}
 
 	if (nm_setting_802_1x_get_num_eap_methods (s_8021x) == 0) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "No valid EAP methods found in IEEE_8021X_EAP_METHODS.");
-		goto error;
+		return NULL;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_SUBJECT_MATCH");
-	g_object_set (s_8021x, NM_SETTING_802_1X_SUBJECT_MATCH, value, NULL);
-	g_free (value);
-
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_PHASE2_SUBJECT_MATCH");
-	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH, value, NULL);
-	g_free (value);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_SUBJECT_MATCH", &value);
+	g_object_set (s_8021x, NM_SETTING_802_1X_SUBJECT_MATCH, v, NULL);
 
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_PHASE1_AUTH_FLAGS");
-	if (value) {
-		NMSetting8021xAuthFlags flags;
-		char *token;
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_PHASE2_SUBJECT_MATCH", &value);
+	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH, v, NULL);
 
-		if (nm_utils_enum_from_str (nm_setting_802_1x_auth_flags_get_type (), value,
-		                            (int *) &flags, &token)) {
-			g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_AUTH_FLAGS, flags, NULL);
-		} else {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid IEEE_8021X_PHASE1_AUTH_FLAGS flag '%s'", token);
-			g_free (token);
-			g_free (value);
-			goto error;
-		}
-		g_free (value);
-	}
+	i_val = NM_SETTING_802_1X_AUTH_FLAGS_NONE;
+	if (!svGetValueEnum (ifcfg, "IEEE_8021X_PHASE1_AUTH_FLAGS",
+	                     nm_setting_802_1x_auth_flags_get_type (),
+	                     &i_val, error))
+		return NULL;
+	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE1_AUTH_FLAGS, (guint) i_val, NULL);
 
 	read_8021x_list_value (ifcfg, "IEEE_8021X_ALTSUBJECT_MATCHES",
 	                       s_8021x, NM_SETTING_802_1X_ALTSUBJECT_MATCHES);
 	read_8021x_list_value (ifcfg, "IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES",
 	                       s_8021x, NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES);
 
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_DOMAIN_SUFFIX_MATCH");
-	g_object_set (s_8021x, NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH, value, NULL);
-	g_free (value);
-	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH");
-	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH, value, NULL);
-	g_free (value);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_DOMAIN_SUFFIX_MATCH", &value);
+	g_object_set (s_8021x, NM_SETTING_802_1X_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, (gint32) timeout, NULL);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH", &value);
+	g_object_set (s_8021x, NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH, v, NULL);
 
-	if (list)
-		g_strfreev (list);
-	return s_8021x;
+	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);
 
-error:
-	if (list)
-		g_strfreev (list);
-	g_object_unref (s_8021x);
-	return NULL;
+	return g_steal_pointer (&s_8021x);
 }
 
 static NMSetting *
@@ -3356,18 +3373,31 @@ make_wpa_setting (shvarFile *ifcfg,
                   NMSetting8021x **s_8021x,
                   GError **error)
 {
-	NMSettingWirelessSecurity *wsec;
-	char *value, *psk, *lower;
+	gs_unref_object NMSettingWirelessSecurity *wsec = NULL;
+	gs_free char *value = NULL;
+	const char *v;
 	gboolean wpa_psk = FALSE, wpa_eap = FALSE, ieee8021x = FALSE;
+	int i_val;
+	GError *local = NULL;
 
 	wsec = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ());
 
-	value = svGetValueStr_cp (ifcfg, "KEY_MGMT");
-	wpa_psk = !g_strcmp0 (value, "WPA-PSK");
-	wpa_eap = !g_strcmp0 (value, "WPA-EAP");
-	ieee8021x = !g_strcmp0 (value, "IEEE8021X");
+	v = svGetValueStr (ifcfg, "KEY_MGMT", &value);
+	wpa_psk = nm_streq0 (v, "WPA-PSK");
+	wpa_eap = nm_streq0 (v, "WPA-EAP");
+	ieee8021x = nm_streq0 (v, "IEEE8021X");
 	if (!wpa_psk && !wpa_eap && !ieee8021x)
-		goto error; /* Not WPA or Dynamic WEP */
+		return NULL; /* Not WPA or Dynamic WEP */
+
+	/* WPS */
+	i_val = NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT;
+	if (!svGetValueEnum (ifcfg, "WPS_METHOD",
+	                     nm_setting_wireless_security_wps_method_get_type (),
+	                     &i_val, error))
+		return NULL;
+	g_object_set (wsec,
+	              NM_SETTING_WIRELESS_SECURITY_WPS_METHOD, (guint) i_val,
+	              NULL);
 
 	/* Pairwise and Group ciphers (only relevant for WPA/RSN) */
 	if (wpa_psk || wpa_eap) {
@@ -3380,18 +3410,17 @@ make_wpa_setting (shvarFile *ifcfg,
 		/* Ad-Hoc mode only supports WPA proto for now */
 		nm_setting_wireless_security_add_proto (wsec, "wpa");
 	} else {
-		char *allow_wpa, *allow_rsn;
+		gs_free char *value2 = NULL;
+		const char *v2;
 
-		allow_wpa = svGetValueStr_cp (ifcfg, "WPA_ALLOW_WPA");
-		allow_rsn = svGetValueStr_cp (ifcfg, "WPA_ALLOW_WPA2");
-
-		if (allow_wpa && svGetValueBoolean (ifcfg, "WPA_ALLOW_WPA", TRUE))
+		v2 = svGetValueStr (ifcfg, "WPA_ALLOW_WPA", &value2);
+		if (v2 && svParseBoolean (v2, TRUE))
 			nm_setting_wireless_security_add_proto (wsec, "wpa");
-		if (allow_rsn && svGetValueBoolean (ifcfg, "WPA_ALLOW_WPA2", TRUE))
-			nm_setting_wireless_security_add_proto (wsec, "rsn");
 
-		g_free (allow_wpa);
-		g_free (allow_rsn);
+		nm_clear_g_free (&value2);
+		v2 = svGetValueStr (ifcfg, "WPA_ALLOW_WPA2", &value2);
+		if (v2 && svParseBoolean (v2, TRUE))
+			nm_setting_wireless_security_add_proto (wsec, "rsn");
 	}
 
 	if (wpa_psk) {
@@ -3402,12 +3431,15 @@ make_wpa_setting (shvarFile *ifcfg,
 
 		/* Read PSK if it's system-owned */
 		if (psk_flags == NM_SETTING_SECRET_FLAG_NONE) {
-			psk = parse_wpa_psk (ifcfg, file, ssid, error);
-			if (psk) {
+			gs_free char *psk = NULL;
+
+			psk = parse_wpa_psk (ifcfg, file, ssid, &local);
+			if (psk)
 				g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_PSK, psk, NULL);
-				g_free (psk);
-			} else if (error)
-				goto error;
+			else if (local) {
+				g_propagate_error (error, local);
+				return NULL;
+			}
 		}
 
 		if (adhoc)
@@ -3418,37 +3450,38 @@ make_wpa_setting (shvarFile *ifcfg,
 		/* Adhoc mode is mutually exclusive with any 802.1x-based authentication */
 		if (adhoc) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Ad-Hoc mode cannot be used with KEY_MGMT type '%s'", value);
-			goto error;
+			             "Ad-Hoc mode cannot be used with KEY_MGMT type '%s'", v);
+			return NULL;
 		}
 
-		*s_8021x = fill_8021x (ifcfg, file, value, TRUE, error);
+		*s_8021x = fill_8021x (ifcfg, file, v, TRUE, error);
 		if (!*s_8021x)
-			goto error;
+			return NULL;
 
-		lower = g_ascii_strdown (value, -1);
-		g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, lower, NULL);
-		g_free (lower);
+		{
+			gs_free char *lower = g_ascii_strdown (v, -1);
+
+			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, lower, NULL);
+		}
 	} else {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Unknown wireless KEY_MGMT type '%s'", value);
-		goto error;
+		             "Unknown wireless KEY_MGMT type '%s'", v);
+		return NULL;
 	}
 
-	g_free (value);
-
-	value = svGetValueStr_cp (ifcfg, "SECURITYMODE");
-	if (NM_IN_STRSET (value, NULL, "open"))
-		g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, value, NULL);
+	i_val = NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT;
+	if (!svGetValueEnum (ifcfg, "PMF",
+	                     nm_setting_wireless_security_pmf_get_type (),
+	                     &i_val, error))
+		return NULL;
+	g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_PMF, i_val, NULL);
 
-	g_free (value);
-	return (NMSetting *) wsec;
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "SECURITYMODE", &value);
+	if (NM_IN_STRSET (v, NULL, "open"))
+		g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, v, NULL);
 
-error:
-	g_free (value);
-	if (wsec)
-		g_object_unref (wsec);
-	return NULL;
+	return (NMSetting *) g_steal_pointer (&wsec);
 }
 
 static NMSetting *
@@ -3456,23 +3489,22 @@ make_leap_setting (shvarFile *ifcfg,
                    const char *file,
                    GError **error)
 {
-	NMSettingWirelessSecurity *wsec;
+	gs_unref_object NMSettingWirelessSecurity *wsec = NULL;
 	shvarFile *keys_ifcfg;
-	char *value;
+	gs_free char *value = NULL;
 	NMSettingSecretFlags flags;
 
 	wsec = NM_SETTING_WIRELESS_SECURITY (nm_setting_wireless_security_new ());
 
 	value = svGetValueStr_cp (ifcfg, "KEY_MGMT");
 	if (!value || strcmp (value, "IEEE8021X"))
-		goto error; /* Not LEAP */
+		return NULL;
+	nm_clear_g_free (&value);
 
-	g_free (value);
 	value = svGetValueStr_cp (ifcfg, "SECURITYMODE");
 	if (!value || strcasecmp (value, "leap"))
-		goto error; /* Not LEAP */
-
-	g_free (value);
+		return NULL; /* Not LEAP */
+	nm_clear_g_free (&value);
 
 	flags = read_secret_flags (ifcfg, "IEEE_8021X_PASSWORD_FLAGS");
 	g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, flags, NULL);
@@ -3490,30 +3522,24 @@ make_leap_setting (shvarFile *ifcfg,
 		}
 		if (value && strlen (value))
 			g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, value, NULL);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	value = svGetValueStr_cp (ifcfg, "IEEE_8021X_IDENTITY");
 	if (!value) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "Missing LEAP identity");
-		goto error;
+		return NULL;
 	}
 	g_object_set (wsec, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, value, NULL);
-	g_free (value);
+	nm_clear_g_free (&value);
 
 	g_object_set (wsec,
 	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x",
 	              NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap",
 	              NULL);
 
-	return (NMSetting *) wsec;
-
-error:
-	g_free (value);
-	if (wsec)
-		g_object_unref (wsec);
-	return NULL;
+	return (NMSetting *) g_steal_pointer (&wsec);
 }
 
 static NMSetting *
@@ -3980,39 +4006,29 @@ parse_ethtool_option (const char *value,
                       guint32 *out_speed,
                       const char **out_duplex)
 {
-	gs_strfreev char **words = NULL;
-	const char **iter = NULL, *opt_val, *opt;
+	gs_free const char **words = NULL;
+	const char *const *iter;
+	const char *opt_val, *opt;
 
-	if (!value || !value[0])
+	words = nm_utils_strsplit_set (value, "\t ");
+	if (!words)
 		return;
 
-	words = g_strsplit_set (value, "\t ", 0);
-	iter = (const char **) words;
+	iter = words;
 
 	while (iter[0]) {
-		/* g_strsplit_set() returns empty tokens when extra spaces are found: skip them */
-		if (!*iter[0]) {
-			iter++;
-			continue;
-		}
-
 		opt = iter++[0];
-
-		/* skip over repeated space characters like to parse "wol     d". */
-		while (iter[0] && !*iter[0])
-			iter++;
-
 		opt_val = iter[0];
 
-		if (g_str_equal (opt, "autoneg"))
+		if (nm_streq (opt, "autoneg"))
 			parse_ethtool_option_autoneg (opt_val, out_autoneg);
-		else if (g_str_equal (opt, "speed"))
+		else if (nm_streq (opt, "speed"))
 			parse_ethtool_option_speed (opt_val, out_speed);
-		else if (g_str_equal (opt, "duplex"))
+		else if (nm_streq (opt, "duplex"))
 			parse_ethtool_option_duplex (opt_val, out_duplex);
-		else if (g_str_equal (opt, "wol"))
+		else if (nm_streq (opt, "wol"))
 			parse_ethtool_option_wol (opt_val, out_flags);
-		else if (g_str_equal (opt, "sopass"))
+		else if (nm_streq (opt, "sopass"))
 			parse_ethtool_option_sopass (opt_val, out_password);
 		else {
 			/* Silently skip unknown options */
@@ -4034,15 +4050,15 @@ parse_ethtool_options (shvarFile *ifcfg, NMSettingWired *s_wired, const char *va
 	const char *duplex = NULL;
 
 	if (value) {
-		gs_strfreev char **opts = NULL;
-		const char **iter;
+		gs_free const char **opts = NULL;
+		const char *const *iter;
 
 		/* WAKE_ON_LAN_IGNORE is inferred from a specified but empty ETHTOOL_OPTS */
 		if (!value[0])
 			wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
 
-		opts = g_strsplit_set (value, ";", 0);
-		for (iter = (const char **) opts; iter[0]; iter++) {
+		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);
@@ -4079,8 +4095,8 @@ make_wired_setting (shvarFile *ifcfg,
                     NMSetting8021x **s_8021x,
                     GError **error)
 {
-	NMSettingWired *s_wired;
-	char *value = NULL;
+	gs_unref_object NMSettingWired *s_wired = NULL;
+	gs_free char *value = NULL;
 	char *nettype;
 
 	s_wired = NM_SETTING_WIRED (nm_setting_wired_new ());
@@ -4094,21 +4110,20 @@ make_wired_setting (shvarFile *ifcfg,
 			g_object_set (s_wired, NM_SETTING_WIRED_MTU, (guint) mtu, NULL);
 		else
 			PARSE_WARNING ("invalid MTU '%s'", value);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	value = svGetValueStr_cp (ifcfg, "HWADDR");
 	if (value) {
 		value = g_strstrip (value);
 		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, value, NULL);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	value = svGetValueStr_cp (ifcfg, "SUBCHANNELS");
 	if (value) {
 		const char *p = value;
 		gboolean success = TRUE;
-		char **chans = NULL;
 
 		/* basic sanity checks */
 		while (*p) {
@@ -4121,30 +4136,30 @@ make_wired_setting (shvarFile *ifcfg,
 		}
 
 		if (success) {
+			gs_free const char **chans = NULL;
 			guint32 num_chans;
 
-			chans = g_strsplit_set (value, ",", 0);
-			num_chans = g_strv_length (chans);
+			chans = nm_utils_strsplit_set (value, ",");
+			num_chans = NM_PTRARRAY_LEN (chans);
 			if (num_chans < 2 || num_chans > 3) {
-				PARSE_WARNING ("invalid SUBCHANNELS '%s' (%d channels, 2 or 3 expected)",
-				               value, g_strv_length (chans));
+				PARSE_WARNING ("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
+				               value, (unsigned) NM_PTRARRAY_LEN (chans));
 			} else
 				g_object_set (s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, chans, NULL);
-			g_strfreev (chans);
 		}
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	value = svGetValueStr_cp (ifcfg, "PORTNAME");
 	if (value) {
 		nm_setting_wired_add_s390_option (s_wired, "portname", value);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	value = svGetValueStr_cp (ifcfg, "CTCPROT");
 	if (value) {
 		nm_setting_wired_add_s390_option (s_wired, "ctcprot", value);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	nettype = svGetValueStr_cp (ifcfg, "NETTYPE");
@@ -4174,28 +4189,28 @@ make_wired_setting (shvarFile *ifcfg,
 			iter++;
 		}
 		g_strfreev (options);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
-	value = svGetValueStr_cp (ifcfg, "MACADDR");
-	if (value) {
-		value = g_strstrip (value);
-		g_object_set (s_wired, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, value, NULL);
-		g_free (value);
-	}
+	g_object_set (s_wired,
+	              NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+	              svGetValueStr (ifcfg, "MACADDR", &value),
+	              NULL);
+	nm_clear_g_free (&value);
 
-	value = svGetValueStr_cp (ifcfg, "GENERATE_MAC_ADDRESS_MASK");
-	g_object_set (s_wired, NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, value, NULL);
-	g_free (value);
+	g_object_set (s_wired,
+	              NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK,
+	              svGetValueStr (ifcfg, "GENERATE_MAC_ADDRESS_MASK", &value),
+	              NULL);
+	nm_clear_g_free (&value);
 
 	value = svGetValueStr_cp (ifcfg, "HWADDR_BLACKLIST");
 	if (value) {
-		char **strv;
+		gs_strfreev char **strv = NULL;
 
 		strv = transform_hwaddr_blacklist (value);
 		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, strv, NULL);
-		g_strfreev (strv);
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	value = svGetValueStr_cp (ifcfg, "KEY_MGMT");
@@ -4203,25 +4218,20 @@ make_wired_setting (shvarFile *ifcfg,
 		if (!strcmp (value, "IEEE8021X")) {
 			*s_8021x = fill_8021x (ifcfg, file, value, FALSE, error);
 			if (!*s_8021x)
-				goto error;
+				return NULL;
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "Unknown wired KEY_MGMT type '%s'", value);
-			goto error;
+			return NULL;
 		}
-		g_free (value);
+		nm_clear_g_free (&value);
 	}
 
 	parse_ethtool_options (ifcfg, s_wired,
 	                       svGetValue (ifcfg, "ETHTOOL_OPTS", &value));
-	g_free (value);
-
-	return (NMSetting *) s_wired;
+	nm_clear_g_free (&value);
 
-error:
-	g_free (value);
-	g_object_unref (s_wired);
-	return NULL;
+	return (NMSetting *) g_steal_pointer (&s_wired);
 }
 
 static NMConnection *
@@ -4246,7 +4256,6 @@ wired_connection_from_ifcfg (const char *file,
 		g_object_unref (connection);
 		return NULL;
 	}
-	check_if_slave (ifcfg, (NMSettingConnection *) con_setting);
 	nm_connection_add_setting (connection, con_setting);
 
 	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
@@ -4396,7 +4405,6 @@ infiniband_connection_from_ifcfg (const char *file,
 		g_object_unref (connection);
 		return NULL;
 	}
-	check_if_slave (ifcfg, (NMSettingConnection *) con_setting);
 	nm_connection_add_setting (connection, con_setting);
 
 	infiniband_setting = make_infiniband_setting (ifcfg, file, error);
@@ -4441,40 +4449,37 @@ make_bond_setting (shvarFile *ifcfg,
                    GError **error)
 {
 	NMSettingBond *s_bond;
-	char *value;
+	gs_free char *value = NULL;
+	const char *v;
 
-	value = svGetValueStr_cp (ifcfg, "DEVICE");
-	if (!value) {
+	v = svGetValueStr (ifcfg, "DEVICE", &value);
+	if (!v) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "mandatory DEVICE keyword missing");
 		return NULL;
 	}
-	g_free (value);
 
 	s_bond = NM_SETTING_BOND (nm_setting_bond_new ());
 
-	value = svGetValueStr_cp (ifcfg, "BONDING_OPTS");
-	if (value) {
-		char **items, **iter;
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "BONDING_OPTS", &value);
+	if (v) {
+		gs_free const char **items = NULL;
+		const char *const *iter;
 
-		items = g_strsplit_set (value, " ", -1);
+		items = nm_utils_strsplit_set (v, " ");
 		for (iter = items; iter && *iter; iter++) {
-			if (strlen (*iter)) {
-				char **keys, *key, *val;
-
-				keys = g_strsplit_set (*iter, "=", 2);
-				if (keys && *keys) {
-					key = *keys;
-					val = *(keys + 1);
-					if (val && key[0] && val[0])
-						handle_bond_option (s_bond, key, val);
-				}
+			gs_strfreev char **keys = NULL;
+			const char *key, *val;
 
-				g_strfreev (keys);
+			keys = g_strsplit_set (*iter, "=", 2);
+			if (keys && *keys) {
+				key = *keys;
+				val = *(keys + 1);
+				if (val && key[0] && val[0])
+					handle_bond_option (s_bond, key, val);
 			}
 		}
-		g_free (value);
-		g_strfreev (items);
 	}
 
 	return (NMSetting *) s_bond;
@@ -4673,6 +4678,12 @@ handle_bridge_option (NMSetting *setting,
 			              (gboolean) u, NULL);
 		else
 			PARSE_WARNING ("invalid multicast_snooping value '%s'", value);
+	} else if (!strcmp (key, "group_fwd_mask")) {
+		if (get_uint (value, &u) && u <= 0xFFFF && !NM_FLAGS_ANY (u, 7))
+			g_object_set (setting, NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,
+			              (gboolean) u, NULL);
+		else
+			PARSE_WARNING ("invalid group_fwd_mask value '%s'", value);
 	} else
 			PARSE_WARNING ("unhandled bridge option '%s'", key);
 }
@@ -4683,25 +4694,22 @@ handle_bridging_opts (NMSetting *setting,
                       const char *value,
                       BridgeOptFunc func)
 {
-	char **items, **iter;
+	gs_free const char **items = NULL;
+	const char *const *iter;
 
-	items = g_strsplit_set (value, " ", -1);
+	items = nm_utils_strsplit_set (value, " ");
 	for (iter = items; iter && *iter; iter++) {
-		if (strlen (*iter)) {
-			char **keys, *key, *val;
-
-			keys = g_strsplit_set (*iter, "=", 2);
-			if (keys && *keys) {
-				key = *keys;
-				val = *(keys + 1);
-				if (val && strlen (key) && strlen (val))
-					func (setting, stp, key, val);
-			}
-
-			g_strfreev (keys);
+		gs_strfreev char **keys = NULL;
+		const char *key, *val;
+
+		keys = g_strsplit_set (*iter, "=", 2);
+		if (keys && *keys) {
+			key = *keys;
+			val = *(keys + 1);
+			if (val && key[0] && val[0])
+				func (setting, stp, key, val);
 		}
 	}
-	g_strfreev (items);
 }
 
 static NMSetting *
@@ -4923,24 +4931,22 @@ parse_prio_map_list (NMSettingVlan *s_vlan,
                      const char *key,
                      NMVlanPriorityMap map)
 {
-	char *value;
-	gchar **list = NULL, **iter;
+	gs_free char *value = NULL;
+	gs_free const char **list = NULL;
+	const char *const *iter;
+	const char *v;
 
-	value = svGetValueStr_cp (ifcfg, key);
-	if (!value)
+	v = svGetValueStr (ifcfg, key, &value);
+	if (!v)
 		return;
-
-	list = g_strsplit_set (value, ",", -1);
-	g_free (value);
+	list = nm_utils_strsplit_set (v, ",");
 
 	for (iter = list; iter && *iter; iter++) {
-		if (!*iter || !strchr (*iter, ':'))
+		if (!strchr (*iter, ':'))
 			continue;
-
 		if (!nm_setting_vlan_add_priority_str (s_vlan, map, *iter))
 			PARSE_WARNING ("invalid %s priority map item '%s'", key, *iter);
 	}
-	g_strfreev (list);
 }
 
 static NMSetting *
@@ -4951,22 +4957,20 @@ make_vlan_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingVlan *s_vlan = NULL;
 	gs_free char *parent = NULL;
 	gs_free char *iface_name = NULL;
-	char *value = NULL;
-	const char *p = NULL;
+	gs_free char *value = NULL;
+	const char *v = NULL;
 	int vlan_id = -1;
 	guint32 vlan_flags = 0;
 	gint gvrp, reorder_hdr;
 
-	value = svGetValueStr_cp (ifcfg, "VLAN_ID");
-	if (value) {
-		vlan_id = _nm_utils_ascii_str_to_int64 (value, 10, 0, 4095, -1);
+	v = svGetValueStr (ifcfg, "VLAN_ID", &value);
+	if (v) {
+		vlan_id = _nm_utils_ascii_str_to_int64 (v, 10, 0, 4095, -1);
 		if (vlan_id == -1) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Invalid VLAN_ID '%s'", value);
-			g_free (value);
+			             "Invalid VLAN_ID '%s'", v);
 			return NULL;
 		}
-		g_free (value);
 	}
 
 	/* Need DEVICE if we don't have a separate VLAN_ID property */
@@ -4983,11 +4987,11 @@ make_vlan_setting (shvarFile *ifcfg,
 	parent = svGetValueStr_cp (ifcfg, "PHYSDEV");
 
 	if (iface_name) {
-		p = strchr (iface_name, '.');
-		if (p) {
+		v = strchr (iface_name, '.');
+		if (v) {
 			/* eth0.43; PHYSDEV is assumed from it if unknown */
 			if (!parent) {
-				parent = g_strndup (iface_name, p - iface_name);
+				parent = g_strndup (iface_name, v - iface_name);
 				if (g_str_has_prefix (parent, "vlan")) {
 					/* Like initscripts, if no PHYSDEV and we get an obviously
 					 * invalid parent interface from DEVICE, fail.
@@ -4995,20 +4999,20 @@ make_vlan_setting (shvarFile *ifcfg,
 					nm_clear_g_free (&parent);
 				}
 			}
-			p++;
+			v++;
 		} else {
 			/* format like vlan43; PHYSDEV must be set */
 			if (g_str_has_prefix (iface_name, "vlan"))
-				p = iface_name + 4;
+				v = iface_name + 4;
 		}
 
-		if (p) {
+		if (v) {
 			int device_vlan_id;
 
 			/* Grab VLAN ID from interface name; this takes precedence over the
 			 * separate VLAN_ID property for backwards compat.
 			 */
-			device_vlan_id = _nm_utils_ascii_str_to_int64 (p, 10, 0, 4095, -1);
+			device_vlan_id = _nm_utils_ascii_str_to_int64 (v, 10, 0, 4095, -1);
 			if (device_vlan_id != -1)
 				vlan_id = device_vlan_id;
 		}
@@ -5034,13 +5038,13 @@ make_vlan_setting (shvarFile *ifcfg,
 	if (gvrp > 0)
 		vlan_flags |= NM_VLAN_FLAG_GVRP;
 
-	value = svGetValueStr_cp (ifcfg, "VLAN_FLAGS");
-	if (value) {
-		gs_strfreev char **strv = NULL;
-		char **ptr;
-
-		strv = g_strsplit_set (value, ", ", 0);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "VLAN_FLAGS", &value);
+	if (v) {
+		gs_free const char **strv = NULL;
+		const char *const *ptr;
 
+		strv = nm_utils_strsplit_set (v, ", ");
 		for (ptr = strv; ptr && *ptr; ptr++) {
 			if (nm_streq (*ptr, "GVRP") && gvrp == -1)
 				vlan_flags |= NM_VLAN_FLAG_GVRP;
@@ -5060,7 +5064,6 @@ make_vlan_setting (shvarFile *ifcfg,
 		vlan_flags |= NM_VLAN_FLAG_MVRP;
 
 	g_object_set (s_vlan, NM_SETTING_VLAN_FLAGS, vlan_flags, NULL);
-	g_free (value);
 
 	parse_prio_map_list (s_vlan, ifcfg, "VLAN_INGRESS_PRIORITY_MAP", NM_VLAN_INGRESS_MAP);
 	parse_prio_map_list (s_vlan, ifcfg, "VLAN_EGRESS_PRIORITY_MAP", NM_VLAN_EGRESS_MAP);
@@ -5091,7 +5094,6 @@ vlan_connection_from_ifcfg (const char *file,
 		g_object_unref (connection);
 		return NULL;
 	}
-	check_if_slave (ifcfg, (NMSettingConnection *) con_setting);
 	nm_connection_add_setting (connection, con_setting);
 
 	vlan_setting = make_vlan_setting (ifcfg, file, error);
@@ -5120,7 +5122,8 @@ create_unhandled_connection (const char *filename, shvarFile *ifcfg,
 {
 	NMConnection *connection;
 	NMSetting *s_con;
-	char *value;
+	gs_free char *value = NULL;
+	const char *v;
 
 	nm_assert (out_spec && !*out_spec);
 
@@ -5137,26 +5140,25 @@ create_unhandled_connection (const char *filename, shvarFile *ifcfg,
 	nm_connection_add_setting (connection, nm_setting_generic_new ());
 
 	/* Get a spec */
-	value = svGetValueStr_cp (ifcfg, "HWADDR");
-	if (value) {
-		char *lower = g_ascii_strdown (value, -1);
+	v = svGetValueStr (ifcfg, "HWADDR", &value);
+	if (v) {
+		gs_free char *lower = g_ascii_strdown (v, -1);
+
 		*out_spec = g_strdup_printf ("%s:mac:%s", type, lower);
-		g_free (lower);
-		g_free (value);
 		return connection;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "SUBCHANNELS");
-	if (value) {
-		*out_spec = g_strdup_printf ("%s:s390-subchannels:%s", type, value);
-		g_free (value);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "SUBCHANNELS", &value);
+	if (v) {
+		*out_spec = g_strdup_printf ("%s:s390-subchannels:%s", type, v);
 		return connection;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "DEVICE");
-	if (value) {
-		*out_spec = g_strdup_printf ("%s:interface-name:%s", type, value);
-		g_free (value);
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DEVICE", &value);
+	if (v) {
+		*out_spec = g_strdup_printf ("%s:interface-name:%s", type, v);
 		return connection;
 	}
 
@@ -5164,32 +5166,6 @@ create_unhandled_connection (const char *filename, shvarFile *ifcfg,
 	return NULL;
 }
 
-char *
-uuid_from_file (const char *filename)
-{
-	const char *ifcfg_name = NULL;
-	shvarFile *ifcfg;
-	char *uuid;
-
-	g_return_val_if_fail (filename != NULL, NULL);
-
-	ifcfg_name = utils_get_ifcfg_name (filename, TRUE);
-	if (!ifcfg_name)
-		return NULL;
-
-	ifcfg = svOpenFile (filename, NULL);
-	if (!ifcfg)
-		return NULL;
-
-	/* Try for a UUID key before falling back to hashing the file name */
-	uuid = svGetValueStr_cp (ifcfg, "UUID");
-	if (!uuid)
-		uuid = nm_utils_uuid_generate_from_string (svFileGetName (ifcfg), -1, NM_UTILS_UUID_TYPE_LEGACY, NULL);
-
-	svCloseFile (ifcfg);
-	return uuid;
-}
-
 static void
 check_dns_search_domains (shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6)
 {
@@ -5199,23 +5175,24 @@ check_dns_search_domains (shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6)
 	/* If there is no IPv4 config or it doesn't contain DNS searches,
 	 * read DOMAIN and put the domains into IPv6.
 	 */
-	if (!s_ip4 || nm_setting_ip_config_get_num_dns_searches (NM_SETTING_IP_CONFIG (s_ip4)) == 0) {
+	if (   !s_ip4
+	    || nm_setting_ip_config_get_num_dns_searches (NM_SETTING_IP_CONFIG (s_ip4)) == 0) {
 		/* DNS searches */
-		char *value = svGetValueStr_cp (ifcfg, "DOMAIN");
+		gs_free char *value = NULL;
+		const char *v;
 
-		if (value) {
-			char **searches = g_strsplit (value, " ", 0);
+		v = svGetValueStr (ifcfg, "DOMAIN", &value);
+		if (v) {
+			gs_free const char **searches = NULL;
+			const char *const *item;
+
+			searches = nm_utils_strsplit_set (v, " ");
 			if (searches) {
-				char **item;
 				for (item = searches; *item; item++) {
-					if (strlen (*item)) {
-						if (!nm_setting_ip_config_add_dns_search (NM_SETTING_IP_CONFIG (s_ip6), *item))
-							PARSE_WARNING ("duplicate DNS domain '%s'", *item);
-					}
+					if (!nm_setting_ip_config_add_dns_search (NM_SETTING_IP_CONFIG (s_ip6), *item))
+						PARSE_WARNING ("duplicate DNS domain '%s'", *item);
 				}
-				g_strfreev (searches);
 			}
-			g_free (value);
 		}
 	}
 }
@@ -5235,6 +5212,8 @@ connection_from_file_full (const char *filename,
 	NMSetting *s_ip4, *s_ip6, *s_proxy, *s_port, *s_dcb = NULL, *s_user;
 	const char *ifcfg_name = NULL;
 	gboolean has_ip4_defroute = FALSE;
+	gboolean has_complex_routes_v4;
+	gboolean has_complex_routes_v6;
 
 	g_return_val_if_fail (filename != NULL, NULL);
 	g_return_val_if_fail (out_unhandled && !*out_unhandled, NULL);
@@ -5446,13 +5425,32 @@ connection_from_file_full (const char *filename,
 	if (!connection)
 		return NULL;
 
-	s_ip6 = make_ip6_setting (parsed, network_file, error);
+	has_complex_routes_v4 = utils_has_complex_routes (filename, AF_INET);
+	has_complex_routes_v6 = utils_has_complex_routes (filename, AF_INET6);
+
+	if (has_complex_routes_v4 || has_complex_routes_v6) {
+		if (has_complex_routes_v4 && !has_complex_routes_v6)
+			PARSE_WARNING ("'rule-' file is present; you will need to use a dispatcher script to apply these routes");
+		else if (has_complex_routes_v6 && !has_complex_routes_v4)
+			PARSE_WARNING ("'rule6-' file is present; you will need to use a dispatcher script to apply these routes");
+		else
+			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,
+	                          network_file,
+	                          !has_complex_routes_v4 && !has_complex_routes_v6,
+	                          error);
 	if (!s_ip6)
 		return NULL;
 	else
 		nm_connection_add_setting (connection, s_ip6);
 
-	s_ip4 = make_ip4_setting (parsed, network_file, &has_ip4_defroute, error);
+	s_ip4 = make_ip4_setting (parsed,
+	                          network_file,
+	                          !has_complex_routes_v4 && !has_complex_routes_v6,
+	                          &has_ip4_defroute,
+	                          error);
 	if (!s_ip4)
 		return NULL;
 	else {
@@ -5510,11 +5508,11 @@ connection_from_file (const char *filename,
 }
 
 NMConnection *
-connection_from_file_test (const char *filename,
-                           const char *network_file,
-                           const char *test_type,
-                           char **out_unhandled,
-                           GError **error)
+nmtst_connection_from_file (const char *filename,
+                            const char *network_file,
+                            const char *test_type,
+                            char **out_unhandled,
+                            GError **error)
 {
 	return connection_from_file_full (filename,
 	                                  network_file,
@@ -5528,7 +5526,6 @@ guint
 devtimeout_from_file (const char *filename)
 {
 	shvarFile *ifcfg;
-	char *devtimeout_str;
 	guint devtimeout;
 
 	g_return_val_if_fail (filename != NULL, 0);
@@ -5537,14 +5534,7 @@ devtimeout_from_file (const char *filename)
 	if (!ifcfg)
 		return 0;
 
-	devtimeout_str = svGetValueStr_cp (ifcfg, "DEVTIMEOUT");
-	if (devtimeout_str) {
-		devtimeout = _nm_utils_ascii_str_to_int64 (devtimeout_str, 10, 0, G_MAXUINT, 0);
-		g_free (devtimeout_str);
-	} else
-		devtimeout = 0;
-
+	devtimeout = svGetValueInt64 (ifcfg, "DEVTIMEOUT", 10, 0, G_MAXUINT, 0);
 	svCloseFile (ifcfg);
-
 	return devtimeout;
 }
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
index 35464474..a8937ac8 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
@@ -28,15 +28,12 @@ NMConnection *connection_from_file (const char *filename,
                                     GError **error,
                                     gboolean *out_ignore_error);
 
-char *uuid_from_file (const char *filename);
-
 guint devtimeout_from_file (const char *filename);
 
-/* for test-ifcfg-rh */
-NMConnection *connection_from_file_test (const char *filename,
-                                         const char *network_file,
-                                         const char *test_type,
-                                         char **out_unhandled,
-                                         GError **error);
+NMConnection *nmtst_connection_from_file (const char *filename,
+                                          const char *network_file,
+                                          const char *test_type,
+                                          char **out_unhandled,
+                                          GError **error);
 
 #endif  /* __READER_H__ */
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 e82ef60c..6434ad7d 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -173,7 +173,7 @@ utils_get_extra_path (const char *parent, const char *tag)
 
 	dirname = g_path_get_dirname (parent);
 	if (!dirname)
-		return NULL;
+		g_return_val_if_reached (NULL);
 
 	name = utils_get_ifcfg_name (parent, FALSE);
 	if (name) {
@@ -280,25 +280,22 @@ gone:
 }
 
 gboolean
-utils_has_complex_routes (const char *filename)
+utils_has_complex_routes (const char *filename, int addr_family)
 {
-	char *rules;
+	g_return_val_if_fail (filename, TRUE);
 
-	g_return_val_if_fail (filename != NULL, TRUE);
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) {
+		gs_free char *rules = utils_get_extra_path (filename, RULE_TAG);
 
-	rules = utils_get_extra_path (filename, RULE_TAG);
-	if (g_file_test (rules, G_FILE_TEST_EXISTS)) {
-		g_free (rules);
-		return TRUE;
+		if (g_file_test (rules, G_FILE_TEST_EXISTS))
+			return TRUE;
 	}
-	g_free (rules);
 
-	rules = utils_get_extra_path (filename, RULE6_TAG);
-	if (g_file_test (rules, G_FILE_TEST_EXISTS)) {
-		g_free (rules);
-		return TRUE;
+	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) {
+		gs_free char *rules = utils_get_extra_path (filename, RULE6_TAG);
+		if (g_file_test (rules, G_FILE_TEST_EXISTS))
+			return TRUE;
 	}
-	g_free (rules);
 
 	return FALSE;
 }
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 2b2c2755..e7abf4d8 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -48,7 +48,7 @@ shvarFile *utils_get_route_ifcfg (const char *parent, gboolean should_create);
 shvarFile *utils_get_route6_ifcfg (const char *parent, gboolean should_create);
 
 gboolean utils_has_route_file_new_syntax (const char *filename);
-gboolean utils_has_complex_routes (const char *filename);
+gboolean utils_has_complex_routes (const char *filename, int addr_family);
 
 gboolean utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg);
 
@@ -57,4 +57,28 @@ char *utils_detect_ifcfg_path (const char *path, gboolean only_ifcfg);
 void nms_ifcfg_rh_utils_user_key_encode (const char *key, GString *str_buffer);
 gboolean nms_ifcfg_rh_utils_user_key_decode (const char *name, GString *str_buffer);
 
+static inline const char *
+_nms_ifcfg_rh_utils_numbered_tag (char *buf, gsize buf_len, const char *tag_name, int which)
+{
+	gsize l;
+
+	l = g_strlcpy (buf, tag_name, buf_len);
+	nm_assert (l < buf_len);
+	if (which != -1) {
+		buf_len -= l;
+		l = g_snprintf (&buf[l], buf_len, "%d", which);
+		nm_assert (l < buf_len);
+	}
+	return buf;
+}
+#define numbered_tag(buf, tag_name, which) \
+	({ \
+		_nm_unused char *const _buf = (buf); \
+		\
+		/* some static assert trying to ensure that the buffer is statically allocated.
+		 * It disallows a buffer size of sizeof(gpointer) to catch that. */ \
+		G_STATIC_ASSERT (G_N_ELEMENTS (buf) == sizeof (buf) && sizeof (buf) != sizeof (char *) && sizeof (buf) < G_MAXINT); \
+		_nms_ifcfg_rh_utils_numbered_tag (buf, sizeof (buf), ""tag_name"", (which)); \
+	})
+
 #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 d2b7ff67..d16f46be 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -32,6 +32,7 @@
 #include <unistd.h>
 #include <stdio.h>
 
+#include "nm-utils/nm-enum-utils.h"
 #include "nm-manager.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wired.h"
@@ -48,7 +49,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
-#include "nm-setting-metadata.h"
+#include "nm-meta-setting.h"
 
 #include "nms-ifcfg-rh-common.h"
 #include "nms-ifcfg-rh-reader.h"
@@ -107,45 +108,75 @@ save_secret_flags (shvarFile *ifcfg,
 
 static void
 set_secret (shvarFile *ifcfg,
+            GHashTable *secrets,
             const char *key,
             const char *value,
             const char *flags_key,
             NMSettingSecretFlags flags)
 {
-	shvarFile *keyfile;
-	GError *error = NULL;
-
 	/* Clear the secret from the ifcfg and the associated "keys" file */
 	svUnsetValue (ifcfg, key);
 
 	/* Save secret flags */
 	save_secret_flags (ifcfg, flags_key, flags);
 
+	/* Only write the secret if it's system owned and supposed to be saved */
+	if (flags != NM_SETTING_SECRET_FLAG_NONE)
+		value = NULL;
+
+	g_hash_table_replace (secrets, g_strdup (key), g_strdup (value));
+}
+
+static gboolean
+write_secrets (shvarFile *ifcfg,
+               GHashTable *secrets,
+               GError **error)
+{
+	nm_auto_shvar_file_close shvarFile *keyfile = NULL;
+	gs_free const char **secrets_keys = NULL;
+	guint i, secrets_keys_n;
+	GError *local = NULL;
+	gboolean any_secrets = FALSE;
+
 	keyfile = utils_get_keys_ifcfg (svFileGetName (ifcfg), TRUE);
 	if (!keyfile) {
-		_LOGW ("could not create ifcfg file for '%s'", svFileGetName (ifcfg));
-		goto error;
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "Failure to create secrets file for '%s'", svFileGetName (ifcfg));
+		return FALSE;
 	}
 
-	/* Only write the secret if it's system owned and supposed to be saved */
-	if (flags == NM_SETTING_SECRET_FLAG_NONE)
-		svSetValueStr (keyfile, key, value);
-	else
-		svUnsetValue (keyfile, key);
+	/* we purge all existing secrets. */
+	svUnsetAll (keyfile, SV_KEY_TYPE_ANY);
+
+	/* sort the keys. */
+	secrets_keys = (const char **) g_hash_table_get_keys_as_array (secrets, &secrets_keys_n);
+	if (secrets_keys) {
+		g_qsort_with_data (secrets_keys,
+		                   secrets_keys_n,
+		                   sizeof (const char *),
+		                   nm_strcmp_p_with_data,
+		                   NULL);
+	}
+
+	for (i = 0; i < secrets_keys_n; i++) {
+		const char *k = secrets_keys[i];
+		const char *v = g_hash_table_lookup (secrets, k);
+
+		if (v) {
+			svSetValueStr (keyfile, k, v);
+			any_secrets = TRUE;
+		}
+	}
 
-	if (!svWriteFile (keyfile, 0600, &error)) {
-		_LOGW ("could not update ifcfg file '%s': %s",
-		       svFileGetName (keyfile), error->message);
-		g_clear_error (&error);
-		svCloseFile (keyfile);
-		goto error;
+	if (!any_secrets)
+		(void) unlink (svFileGetName (keyfile));
+	else if (!svWriteFile (keyfile, 0600, &local)) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "Failure to write secrets to '%s': %s", svFileGetName (keyfile), local->message);
+		return FALSE;
 	}
-	svCloseFile (keyfile);
-	return;
 
-error:
-	/* Try setting the secret in the actual ifcfg */
-	svSetValueStr (ifcfg, key, value);
+	return TRUE;
 }
 
 typedef struct {
@@ -183,6 +214,8 @@ static const Setting8021xSchemeVtable setting_8021x_scheme_vtable[] = {
 static gboolean
 write_object (NMSetting8021x *s_8021x,
               shvarFile *ifcfg,
+              GHashTable *secrets,
+              GHashTable *blobs,
               const Setting8021xSchemeVtable *objtype,
               GError **error)
 {
@@ -193,6 +226,7 @@ write_object (NMSetting8021x *s_8021x,
 	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
 	char *secret_name, *secret_flags;
 	const char *extension;
+	char *standard_file;
 
 	g_return_val_if_fail (ifcfg != NULL, FALSE);
 	g_return_val_if_fail (objtype != NULL, FALSE);
@@ -221,7 +255,7 @@ write_object (NMSetting8021x *s_8021x,
 	secret_flags = g_strdup_printf ("%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key);
 	password = (*(objtype->vtable->passwd_func))(s_8021x);
 	flags = (*(objtype->vtable->pwflag_func))(s_8021x);
-	set_secret (ifcfg, secret_name, password, secret_flags, flags);
+	set_secret (ifcfg, secrets, secret_name, password, secret_flags, flags);
 	g_free (secret_name);
 	g_free (secret_flags);
 
@@ -232,28 +266,6 @@ write_object (NMSetting8021x *s_8021x,
 	else
 		extension = "pem";
 
-	/* If certificate/private key wasn't sent, the connection may no longer be
-	 * 802.1x and thus we clear out the paths and certs.
-	 */
-	if (!value && !blob) {
-		char *standard_file;
-		int ignored;
-
-		/* Since no cert/private key is now being used, delete any standard file
-		 * that was created for this connection, but leave other files alone.
-		 * Thus, for example,
-		 * /etc/sysconfig/network-scripts/ca-cert-Test_Write_Wifi_WPA_EAP-TLS.der
-		 * will be deleted, but /etc/pki/tls/cert.pem will not.
-		 */
-		standard_file = utils_cert_path (svFileGetName (ifcfg), objtype->vtable->file_suffix, extension);
-		if (g_file_test (standard_file, G_FILE_TEST_EXISTS))
-			ignored = unlink (standard_file);
-		g_free (standard_file);
-
-		svUnsetValue (ifcfg, objtype->ifcfg_rh_key);
-		return TRUE;
-	}
-
 	/* If the object path was specified, prefer that over any raw cert data that
 	 * may have been sent.
 	 */
@@ -264,46 +276,72 @@ write_object (NMSetting8021x *s_8021x,
 
 	/* If it's raw certificate data, write the data out to the standard file */
 	if (blob) {
-		gboolean success;
 		char *new_file;
-		GError *write_error = NULL;
 
 		new_file = utils_cert_path (svFileGetName (ifcfg), objtype->vtable->file_suffix, extension);
-		if (!new_file) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			             "Could not create file path for %s / %s",
-			             NM_SETTING_802_1X_SETTING_NAME, objtype->vtable->setting_key);
-			return FALSE;
+		g_hash_table_replace (blobs, new_file, g_bytes_ref (blob));
+		svSetValueStr (ifcfg, objtype->ifcfg_rh_key, new_file);
+		return TRUE;
+	}
+
+	/* If certificate/private key wasn't sent, the connection may no longer be
+	 * 802.1x and thus we clear out the paths and certs.
+	 *
+	 * Since no cert/private key is now being used, delete any standard file
+	 * that was created for this connection, but leave other files alone.
+	 * Thus, for example,
+	 * /etc/sysconfig/network-scripts/ca-cert-Test_Write_Wifi_WPA_EAP-TLS.der
+	 * will be deleted, but /etc/pki/tls/cert.pem will not.
+	 */
+	standard_file = utils_cert_path (svFileGetName (ifcfg), objtype->vtable->file_suffix, extension);
+	g_hash_table_replace (blobs, standard_file, NULL);
+	svUnsetValue (ifcfg, objtype->ifcfg_rh_key);
+	return TRUE;
+}
+
+static gboolean
+write_blobs (GHashTable *blobs, GError **error)
+{
+	GHashTableIter iter;
+	const char *filename;
+	GBytes *blob;
+
+	if (!blobs)
+		return TRUE;
+
+	g_hash_table_iter_init (&iter, blobs);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &filename, (gpointer *) &blob)) {
+		GError *write_error = NULL;
+
+		if (!blob) {
+			(void) unlink (filename);
+			continue;
 		}
 
 		/* Write the raw certificate data out to the standard file so that we
 		 * can use paths from now on instead of pushing around the certificate
 		 * data itself.
 		 */
-		success = nm_utils_file_set_contents (new_file,
-		                                      (const char *) g_bytes_get_data (blob, NULL),
-		                                      g_bytes_get_size (blob),
-		                                      0600,
-		                                      &write_error);
-		if (success) {
-			svSetValueStr (ifcfg, objtype->ifcfg_rh_key, new_file);
-			g_free (new_file);
-			return TRUE;
-		} else {
+		if (!nm_utils_file_set_contents (filename,
+		                                 (const char *) g_bytes_get_data (blob, NULL),
+		                                 g_bytes_get_size (blob),
+		                                 0600,
+		                                 &write_error)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			             "Could not write certificate/key for %s / %s: %s",
-			             NM_SETTING_802_1X_SETTING_NAME, objtype->vtable->setting_key,
-			             (write_error && write_error->message) ? write_error->message : "(unknown)");
-			g_clear_error (&write_error);
+			             "Could not write certificate to file \"%s\": %s",
+			             filename,
+			             write_error->message);
+			return FALSE;
 		}
-		g_free (new_file);
 	}
 
-	return FALSE;
+	return TRUE;
 }
 
 static gboolean
 write_8021x_certs (NMSetting8021x *s_8021x,
+                   GHashTable *secrets,
+                   GHashTable *blobs,
                    gboolean phase2,
                    shvarFile *ifcfg,
                    GError **error)
@@ -311,7 +349,7 @@ write_8021x_certs (NMSetting8021x *s_8021x,
 	const Setting8021xSchemeVtable *otype = NULL;
 
 	/* CA certificate */
-	if (!write_object (s_8021x, ifcfg,
+	if (!write_object (s_8021x, ifcfg, secrets, blobs,
 	                   phase2
 	                       ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT]
 	                       : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT],
@@ -325,7 +363,7 @@ write_8021x_certs (NMSetting8021x *s_8021x,
 		otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY];
 
 	/* Save the private key */
-	if (!write_object (s_8021x, ifcfg, otype, error))
+	if (!write_object (s_8021x, ifcfg, secrets, blobs, otype, error))
 		return FALSE;
 
 	/* Client certificate */
@@ -338,7 +376,7 @@ write_8021x_certs (NMSetting8021x *s_8021x,
 		               NULL);
 	} else {
 		/* Save the client certificate */
-		if (!write_object (s_8021x, ifcfg,
+		if (!write_object (s_8021x, ifcfg, secrets, blobs,
 		                   phase2
 		                       ? &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT]
 		                       : &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT],
@@ -352,17 +390,22 @@ write_8021x_certs (NMSetting8021x *s_8021x,
 static gboolean
 write_8021x_setting (NMConnection *connection,
                      shvarFile *ifcfg,
+                     GHashTable *secrets,
+                     GHashTable *blobs,
                      gboolean wired,
                      GError **error)
 {
 	NMSetting8021x *s_8021x;
 	NMSetting8021xAuthFlags auth_flags;
 	const char *value, *match;
+	gconstpointer ptr;
+	GBytes* bytes;
 	char *tmp = NULL;
 	GString *phase2_auth;
 	GString *str;
 	guint32 i, num;
-	gint timeout;
+	gsize size;
+	int vint;
 
 	s_8021x = nm_connection_get_setting_802_1x (connection);
 	if (!s_8021x) {
@@ -392,11 +435,26 @@ write_8021x_setting (NMConnection *connection,
 	               nm_setting_802_1x_get_anonymous_identity (s_8021x));
 
 	set_secret (ifcfg,
+	            secrets,
 	            "IEEE_8021X_PASSWORD",
 	            nm_setting_802_1x_get_password (s_8021x),
 	            "IEEE_8021X_PASSWORD_FLAGS",
 	            nm_setting_802_1x_get_password_flags (s_8021x));
 
+	tmp = NULL;
+	bytes = nm_setting_802_1x_get_password_raw (s_8021x);
+	if (bytes) {
+		ptr = g_bytes_get_data (bytes, &size);
+		tmp = nm_utils_bin2hexstr (ptr, size, -1);
+	}
+	set_secret (ifcfg,
+	            secrets,
+	            "IEEE_8021X_PASSWORD_RAW",
+	            tmp,
+	            "IEEE_8021X_PASSWORD_RAW_FLAGS",
+	            nm_setting_802_1x_get_password_raw_flags (s_8021x));
+	g_free (tmp);
+
 	/* PEAP version */
 	value = nm_setting_802_1x_get_phase1_peapver (s_8021x);
 	svUnsetValue (ifcfg, "IEEE_8021X_PEAP_VERSION");
@@ -452,11 +510,9 @@ write_8021x_setting (NMConnection *connection,
 	if (auth_flags == NM_SETTING_802_1X_AUTH_FLAGS_NONE) {
 		svUnsetValue (ifcfg, "IEEE_8021X_PHASE1_AUTH_FLAGS");
 	} else {
-		gs_free char *flags_str = NULL;
-
-		flags_str = _nm_utils_enum_to_str_full (nm_setting_802_1x_auth_flags_get_type (),
-		                                        auth_flags, " ");
-		svSetValueStr (ifcfg, "IEEE_8021X_PHASE1_AUTH_FLAGS", flags_str);
+		svSetValueEnum (ifcfg, "IEEE_8021X_PHASE1_AUTH_FLAGS",
+		                nm_setting_802_1x_auth_flags_get_type(),
+		                auth_flags);
 	}
 
 	svSetValueStr (ifcfg, "IEEE_8021X_INNER_AUTH_METHODS",
@@ -501,17 +557,14 @@ write_8021x_setting (NMConnection *connection,
 	svSetValueStr (ifcfg, "IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH",
 	               nm_setting_802_1x_get_phase2_domain_suffix_match (s_8021x));
 
-	timeout = nm_setting_802_1x_get_auth_timeout (s_8021x);
-	if (timeout > 0)
-		svSetValueInt64 (ifcfg, "IEEE_8021X_AUTH_TIMEOUT", timeout);
-	else
-		svUnsetValue (ifcfg, "IEEE_8021X_AUTH_TIMEOUT");
+	vint = nm_setting_802_1x_get_auth_timeout (s_8021x);
+	svSetValueInt64_cond (ifcfg, "IEEE_8021X_AUTH_TIMEOUT", vint > 0, vint);
 
-	if (!write_8021x_certs (s_8021x, FALSE, ifcfg, error))
+	if (!write_8021x_certs (s_8021x, secrets, blobs, FALSE, ifcfg, error))
 		return FALSE;
 
 	/* phase2/inner certs */
-	if (!write_8021x_certs (s_8021x, TRUE, ifcfg, error))
+	if (!write_8021x_certs (s_8021x, secrets, blobs, TRUE, ifcfg, error))
 		return FALSE;
 
 	return TRUE;
@@ -520,6 +573,7 @@ write_8021x_setting (NMConnection *connection,
 static gboolean
 write_wireless_security_setting (NMConnection *connection,
                                  shvarFile *ifcfg,
+                                 GHashTable *secrets,
                                  gboolean adhoc,
                                  gboolean *no_8021x,
                                  GError **error)
@@ -528,6 +582,7 @@ write_wireless_security_setting (NMConnection *connection,
 	const char *key_mgmt, *auth_alg, *key, *proto, *cipher;
 	const char *psk = NULL;
 	gboolean wep = FALSE, wpa = FALSE, dynamic_wep = FALSE;
+	NMSettingWirelessSecurityWpsMethod wps_method;
 	char *tmp;
 	guint32 i, num;
 	GString *str;
@@ -573,6 +628,7 @@ write_wireless_security_setting (NMConnection *connection,
 			svSetValueStr (ifcfg, "IEEE_8021X_IDENTITY",
 			               nm_setting_wireless_security_get_leap_username (s_wsec));
 			set_secret (ifcfg,
+			            secrets,
 			            "IEEE_8021X_PASSWORD",
 			            nm_setting_wireless_security_get_leap_password (s_wsec),
 			            "IEEE_8021X_PASSWORD_FLAGS",
@@ -581,35 +637,42 @@ write_wireless_security_setting (NMConnection *connection,
 		}
 	}
 
+	/* WPS */
+	wps_method = nm_setting_wireless_security_get_wps_method (s_wsec);
+	if (wps_method == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT)
+		svUnsetValue (ifcfg, "WPS_METHOD");
+	else
+		svSetValueEnum (ifcfg, "WPS_METHOD", nm_setting_wireless_security_wps_method_get_type (), wps_method);
+
 	/* WEP keys */
 
 	/* Clear any default key */
-	set_secret (ifcfg, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+	set_secret (ifcfg, secrets, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 
 	/* Clear existing keys */
 	for (i = 0; i < 4; i++) {
-		tmp = g_strdup_printf ("KEY_PASSPHRASE%d", i + 1);
-		set_secret (ifcfg, tmp, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
-		g_free (tmp);
+		char tag[64];
 
-		tmp = g_strdup_printf ("KEY%d", i + 1);
-		set_secret (ifcfg, tmp, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
-		g_free (tmp);
+		numbered_tag (tag, "KEY_PASSPHRASE", i + 1);
+		set_secret (ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+
+		numbered_tag (tag, "KEY", i + 1);
+		set_secret (ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 	}
 
 	/* And write the new ones out */
 	if (wep) {
 		/* Default WEP TX key index */
-		tmp = g_strdup_printf ("%d", nm_setting_wireless_security_get_wep_tx_keyidx (s_wsec) + 1);
-		svSetValueStr (ifcfg, "DEFAULTKEY", tmp);
-		g_free (tmp);
+		svSetValueInt64 (ifcfg, "DEFAULTKEY", nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) + 1);
 
 		for (i = 0; i < 4; i++) {
 			NMWepKeyType key_type;
 
 			key = nm_setting_wireless_security_get_wep_key (s_wsec, i);
 			if (key) {
-				char *ascii_key = NULL;
+				gs_free char *ascii_key = NULL;
+				char tag[64];
+				gboolean key_valid = TRUE;
 
 				/* Passphrase needs a different ifcfg key since with WEP, there
 				 * are some passphrases that are indistinguishable from WEP hex
@@ -622,10 +685,11 @@ write_wireless_security_setting (NMConnection *connection,
 					else if (nm_utils_wep_key_valid (key, NM_WEP_KEY_TYPE_PASSPHRASE))
 						key_type = NM_WEP_KEY_TYPE_PASSPHRASE;
 				}
+
 				if (key_type == NM_WEP_KEY_TYPE_PASSPHRASE)
-					tmp = g_strdup_printf ("KEY_PASSPHRASE%d", i + 1);
+					numbered_tag (tag, "KEY_PASSPHRASE", i + 1);
 				else if (key_type == NM_WEP_KEY_TYPE_KEY) {
-					tmp = g_strdup_printf ("KEY%d", i + 1);
+					numbered_tag (tag, "KEY", i + 1);
 
 					/* Add 's:' prefix for ASCII keys */
 					if (strlen (key) == 5 || strlen (key) == 13) {
@@ -633,19 +697,18 @@ write_wireless_security_setting (NMConnection *connection,
 						key = ascii_key;
 					}
 				} else {
-					_LOGW ("invalid WEP key '%s'", key);
-					tmp = NULL;
+					g_warn_if_reached ();
+					key_valid = FALSE;
 				}
 
-				if (tmp) {
+				if (key_valid) {
 					set_secret (ifcfg,
-					            tmp,
+					            secrets,
+					            tag,
 					            key,
 					            "WEP_KEY_FLAGS",
 					            nm_setting_wireless_security_get_wep_key_flags (s_wsec));
 				}
-				g_free (tmp);
-				g_free (ascii_key);
 			}
 		}
 	}
@@ -704,22 +767,31 @@ write_wireless_security_setting (NMConnection *connection,
 		psk = nm_setting_wireless_security_get_psk (s_wsec);
 
 	set_secret (ifcfg,
+	            secrets,
 	            "WPA_PSK",
 	            psk,
 	            "WPA_PSK_FLAGS",
 	            wpa ? nm_setting_wireless_security_get_psk_flags (s_wsec) : NM_SETTING_SECRET_FLAG_NONE);
 
+
+	if (nm_setting_wireless_security_get_pmf (s_wsec) == NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT)
+		svUnsetValue (ifcfg, "PMF");
+	else {
+		svSetValueEnum (ifcfg, "PMF", nm_setting_wireless_security_pmf_get_type (),
+		                nm_setting_wireless_security_get_pmf (s_wsec));
+	}
+
 	return TRUE;
 }
 
 static gboolean
 write_wireless_setting (NMConnection *connection,
                         shvarFile *ifcfg,
+                        GHashTable *secrets,
                         gboolean *no_8021x,
                         GError **error)
 {
 	NMSettingWireless *s_wireless;
-	char *tmp;
 	GBytes *ssid;
 	const guint8 *ssid_data;
 	gsize ssid_len;
@@ -755,13 +827,8 @@ write_wireless_setting (NMConnection *connection,
 		g_free (blacklist_str);
 	}
 
-	svUnsetValue (ifcfg, "MTU");
 	mtu = nm_setting_wireless_get_mtu (s_wireless);
-	if (mtu) {
-		tmp = g_strdup_printf ("%u", mtu);
-		svSetValueStr (ifcfg, "MTU", tmp);
-		g_free (tmp);
-	}
+	svSetValueInt64_cond (ifcfg, "MTU", mtu != 0, mtu);
 
 	ssid = nm_setting_wireless_get_ssid (s_wireless);
 	if (!ssid) {
@@ -837,9 +904,7 @@ write_wireless_setting (NMConnection *connection,
 	svUnsetValue (ifcfg, "BAND");
 	chan = nm_setting_wireless_get_channel (s_wireless);
 	if (chan) {
-		tmp = g_strdup_printf ("%u", chan);
-		svSetValueStr (ifcfg, "CHANNEL", tmp);
-		g_free (tmp);
+		svSetValueInt64 (ifcfg, "CHANNEL", chan);
 	} else {
 		/* Band only set if channel is not, since channel implies band */
 		svSetValueStr (ifcfg, "BAND", nm_setting_wireless_get_band (s_wireless));
@@ -856,27 +921,25 @@ write_wireless_setting (NMConnection *connection,
 	svUnsetValue (ifcfg, "SECURITYMODE");
 
 	if (nm_connection_get_setting_wireless_security (connection)) {
-		if (!write_wireless_security_setting (connection, ifcfg, adhoc, no_8021x, error))
+		if (!write_wireless_security_setting (connection, ifcfg, secrets, adhoc, no_8021x, error))
 			return FALSE;
 	} else {
-		char *keys_path;
-
 		/* Clear out wifi security keys */
 		svUnsetValue (ifcfg, "KEY_MGMT");
 		svUnsetValue (ifcfg, "IEEE_8021X_IDENTITY");
-		set_secret (ifcfg, "IEEE_8021X_PASSWORD", NULL, "IEEE_8021X_PASSWORD_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+		set_secret (ifcfg, secrets, "IEEE_8021X_PASSWORD", NULL, "IEEE_8021X_PASSWORD_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 		svUnsetValue (ifcfg, "SECURITYMODE");
 
 		/* Clear existing keys */
-		set_secret (ifcfg, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+		set_secret (ifcfg, secrets, "KEY", NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 		for (i = 0; i < 4; i++) {
-			tmp = g_strdup_printf ("KEY_PASSPHRASE%d", i + 1);
-			set_secret (ifcfg, tmp, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
-			g_free (tmp);
+			char tag[64];
 
-			tmp = g_strdup_printf ("KEY%d", i + 1);
-			set_secret (ifcfg, tmp, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
-			g_free (tmp);
+			numbered_tag (tag, "KEY_PASSPHRASE", i + 1);
+			set_secret (ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
+
+			numbered_tag (tag, "KEY", i + 1);
+			set_secret (ifcfg, secrets, tag, NULL, "WEP_KEY_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 		}
 
 		svUnsetValue (ifcfg, "DEFAULTKEY");
@@ -884,12 +947,7 @@ write_wireless_setting (NMConnection *connection,
 		svUnsetValue (ifcfg, "WPA_ALLOW_WPA2");
 		svUnsetValue (ifcfg, "CIPHER_PAIRWISE");
 		svUnsetValue (ifcfg, "CIPHER_GROUP");
-		set_secret (ifcfg, "WPA_PSK", NULL, "WPA_PSK_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
-
-		/* Kill any old keys file */
-		keys_path = utils_get_keys_path (svFileGetName (ifcfg));
-		(void) unlink (keys_path);
-		g_free (keys_path);
+		set_secret (ifcfg, secrets, "WPA_PSK", NULL, "WPA_PSK_FLAGS", NM_SETTING_SECRET_FLAG_NONE);
 	}
 
 	svSetValueStr (ifcfg, "SSID_HIDDEN", nm_setting_wireless_get_hidden (s_wireless) ? "yes" : NULL);
@@ -932,7 +990,6 @@ static gboolean
 write_infiniband_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
 	NMSettingInfiniband *s_infiniband;
-	char *tmp;
 	const char *mac, *transport_mode, *parent;
 	guint32 mtu;
 	int p_key;
@@ -947,13 +1004,8 @@ write_infiniband_setting (NMConnection *connection, shvarFile *ifcfg, GError **e
 	mac = nm_setting_infiniband_get_mac_address (s_infiniband);
 	svSetValueStr (ifcfg, "HWADDR", mac);
 
-	svUnsetValue (ifcfg, "MTU");
 	mtu = nm_setting_infiniband_get_mtu (s_infiniband);
-	if (mtu) {
-		tmp = g_strdup_printf ("%u", mtu);
-		svSetValueStr (ifcfg, "MTU", tmp);
-		g_free (tmp);
-	}
+	svSetValueInt64_cond (ifcfg, "MTU", mtu != 0, mtu);
 
 	transport_mode = nm_setting_infiniband_get_transport_mode (s_infiniband);
 	svSetValueBoolean (ifcfg, "CONNECTED_MODE", nm_streq (transport_mode, "connected"));
@@ -961,9 +1013,7 @@ write_infiniband_setting (NMConnection *connection, shvarFile *ifcfg, GError **e
 	p_key = nm_setting_infiniband_get_p_key (s_infiniband);
 	if (p_key != -1) {
 		svSetValueStr (ifcfg, "PKEY", "yes");
-		tmp = g_strdup_printf ("%u", p_key);
-		svSetValueStr (ifcfg, "PKEY_ID", tmp);
-		g_free (tmp);
+		svSetValueInt64 (ifcfg, "PKEY_ID", p_key);
 
 		parent = nm_setting_infiniband_get_parent (s_infiniband);
 		if (parent)
@@ -1016,13 +1066,8 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 		g_free (blacklist_str);
 	}
 
-	svUnsetValue (ifcfg, "MTU");
 	mtu = nm_setting_wired_get_mtu (s_wired);
-	if (mtu) {
-		tmp = g_strdup_printf ("%u", mtu);
-		svSetValueStr (ifcfg, "MTU", tmp);
-		g_free (tmp);
-	}
+	svSetValueInt64_cond (ifcfg, "MTU", mtu != 0, mtu);
 
 	svUnsetValue (ifcfg, "SUBCHANNELS");
 	s390_subchannels = nm_setting_wired_get_s390_subchannels (s_wired);
@@ -1176,7 +1221,6 @@ write_wired_for_virtual (NMConnection *connection, shvarFile *ifcfg)
 	s_wired = nm_connection_get_setting_wired (connection);
 	if (s_wired) {
 		const char *device_mac, *cloned_mac;
-		char *tmp;
 		guint32 mtu;
 
 		has_wired = TRUE;
@@ -1191,12 +1235,7 @@ write_wired_for_virtual (NMConnection *connection, shvarFile *ifcfg)
 		               nm_setting_wired_get_generate_mac_address_mask (s_wired));
 
 		mtu = nm_setting_wired_get_mtu (s_wired);
-		if (mtu) {
-			tmp = g_strdup_printf ("%u", mtu);
-			svSetValueStr (ifcfg, "MTU", tmp);
-			g_free (tmp);
-		} else
-			svUnsetValue (ifcfg, "MTU");
+		svSetValueInt64_cond (ifcfg, "MTU", mtu != 0, mtu);
 	}
 	return has_wired;
 }
@@ -1205,19 +1244,11 @@ static gboolean
 write_vlan_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingVlan *s_vlan;
-	NMSettingConnection *s_con;
 	char *tmp;
 	guint32 vlan_flags = 0;
 	gsize s_buf_len;
 	char s_buf[50], *s_buf_ptr;
 
-	s_con = nm_connection_get_setting_connection (connection);
-	if (!s_con) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		                     "Missing connection setting");
-		return FALSE;
-	}
-
 	s_vlan = nm_connection_get_setting_vlan (connection);
 	if (!s_vlan) {
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
@@ -1227,12 +1258,8 @@ write_vlan_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
 
 	svSetValueStr (ifcfg, "VLAN", "yes");
 	svSetValueStr (ifcfg, "TYPE", TYPE_VLAN);
-	svSetValueStr (ifcfg, "DEVICE", nm_setting_connection_get_interface_name (s_con));
 	svSetValueStr (ifcfg, "PHYSDEV", nm_setting_vlan_get_parent (s_vlan));
-
-	tmp = g_strdup_printf ("%d", nm_setting_vlan_get_id (s_vlan));
-	svSetValueStr (ifcfg, "VLAN_ID", tmp);
-	g_free (tmp);
+	svSetValueInt64 (ifcfg, "VLAN_ID", nm_setting_vlan_get_id (s_vlan));
 
 	vlan_flags = nm_setting_vlan_get_flags (s_vlan);
 	svSetValueBoolean (ifcfg, "REORDER_HDR", NM_FLAGS_HAS (vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS));
@@ -1267,10 +1294,9 @@ write_vlan_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
 }
 
 static gboolean
-write_bonding_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
+write_bond_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingBond *s_bond;
-	const char *iface;
 	guint32 i, num_opts;
 
 	s_bond = nm_connection_get_setting_bond (connection);
@@ -1280,14 +1306,6 @@ write_bonding_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wir
 		return FALSE;
 	}
 
-	iface = nm_connection_get_interface_name (connection);
-	if (!iface) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Missing interface name");
-		return FALSE;
-	}
-
-	svSetValueStr (ifcfg, "DEVICE", iface);
 	svUnsetValue (ifcfg, "BONDING_OPTS");
 
 	num_opts = nm_setting_bond_get_num_options (s_bond);
@@ -1324,7 +1342,6 @@ static gboolean
 write_team_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingTeam *s_team;
-	const char *iface;
 	const char *config;
 
 	s_team = nm_connection_get_setting_team (connection);
@@ -1334,14 +1351,6 @@ write_team_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
 		return FALSE;
 	}
 
-	iface = nm_connection_get_interface_name (connection);
-	if (!iface) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Missing interface name");
-		return FALSE;
-	}
-
-	svSetValueStr (ifcfg, "DEVICE", iface);
 	config = nm_setting_team_get_config (s_team);
 	svSetValueStr (ifcfg, "TEAM_CONFIG", config);
 
@@ -1385,15 +1394,13 @@ get_setting_default_boolean (NMSetting *setting, const char *prop)
 }
 
 static gboolean
-write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, gboolean *wired, GError **error)
 {
 	NMSettingBridge *s_bridge;
-	const char *iface;
 	guint32 i;
 	gboolean b;
 	GString *opts;
 	const char *mac;
-	char *s;
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
 	if (!s_bridge) {
@@ -1402,14 +1409,6 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, GError **error
 		return FALSE;
 	}
 
-	iface = nm_connection_get_interface_name (connection);
-	if (!iface) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Missing interface name");
-		return FALSE;
-	}
-
-	svSetValueStr (ifcfg, "DEVICE", iface);
 	svUnsetValue (ifcfg, "BRIDGING_OPTS");
 	svSetValueBoolean (ifcfg, "STP", FALSE);
 	svUnsetValue (ifcfg, "DELAY");
@@ -1424,11 +1423,8 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, GError **error
 		svSetValueStr (ifcfg, "STP", "yes");
 
 		i = nm_setting_bridge_get_forward_delay (s_bridge);
-		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_FORWARD_DELAY)) {
-			s = g_strdup_printf ("%u", i);
-			svSetValueStr (ifcfg, "DELAY", s);
-			g_free (s);
-		}
+		if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_FORWARD_DELAY))
+			svSetValueInt64 (ifcfg, "DELAY", i);
 
 		g_string_append_printf (opts, "priority=%u", nm_setting_bridge_get_priority (s_bridge));
 
@@ -1454,6 +1450,13 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, GError **error
 		g_string_append_printf (opts, "ageing_time=%u", i);
 	}
 
+	i = nm_setting_bridge_get_group_forward_mask (s_bridge);
+	if (i != get_setting_default_uint (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_GROUP_FORWARD_MASK)) {
+		if (opts->len)
+			g_string_append_c (opts, ' ');
+		g_string_append_printf (opts, "group_fwd_mask=%u", i);
+	}
+
 	b = nm_setting_bridge_get_multicast_snooping (s_bridge);
 	if (b != get_setting_default_boolean (NM_SETTING (s_bridge), NM_SETTING_BRIDGE_MULTICAST_SNOOPING)) {
 		if (opts->len)
@@ -1467,6 +1470,8 @@ write_bridge_setting (NMConnection *connection, shvarFile *ifcfg, GError **error
 
 	svSetValueStr (ifcfg, "TYPE", TYPE_BRIDGE);
 
+	*wired = write_wired_for_virtual (connection, ifcfg);
+
 	return TRUE;
 }
 
@@ -1715,8 +1720,9 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	guint32 n, i;
 	GString *str;
 	const char *master, *master_iface = NULL, *type;
-	char *tmp;
-	gint i_int;
+	gint vint;
+	guint32 vuint32;
+	const char *tmp;
 
 	svSetValueStr (ifcfg, "NAME", nm_setting_connection_get_id (s_con));
 	svSetValueStr (ifcfg, "UUID", nm_setting_connection_get_uuid (s_con));
@@ -1724,16 +1730,15 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	svSetValueStr (ifcfg, "DEVICE", nm_setting_connection_get_interface_name (s_con));
 	svSetValueBoolean (ifcfg, "ONBOOT", nm_setting_connection_get_autoconnect (s_con));
 
-	i_int = nm_setting_connection_get_autoconnect_priority (s_con);
-	tmp = i_int != NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT
-	      ? g_strdup_printf ("%d", i_int) : NULL;
-	svSetValueStr (ifcfg, "AUTOCONNECT_PRIORITY", tmp);
-	g_free (tmp);
+	vint = nm_setting_connection_get_autoconnect_priority (s_con);
+	svSetValueInt64_cond (ifcfg, "AUTOCONNECT_PRIORITY",
+	                      vint != NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT,
+	                      vint);
 
-	i_int = nm_setting_connection_get_autoconnect_retries (s_con);
-	tmp = i_int != -1 ? g_strdup_printf ("%d", i_int) : NULL;
-	svSetValueStr (ifcfg, "AUTOCONNECT_RETRIES", tmp);
-	g_free (tmp);
+	vint = nm_setting_connection_get_autoconnect_retries (s_con);
+	svSetValueInt64_cond (ifcfg, "AUTOCONNECT_RETRIES",
+	                      vint != -1,
+	                      vint);
 
 	/* Only save the value for master connections */
 	type = nm_setting_connection_get_connection_type (s_con);
@@ -1849,12 +1854,10 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 		g_string_free (str, TRUE);
 	}
 
-	svUnsetValue (ifcfg, "GATEWAY_PING_TIMEOUT");
-	if (nm_setting_connection_get_gateway_ping_timeout (s_con)) {
-		tmp = g_strdup_printf ("%" G_GUINT32_FORMAT, nm_setting_connection_get_gateway_ping_timeout (s_con));
-		svSetValueStr (ifcfg, "GATEWAY_PING_TIMEOUT", tmp);
-		g_free (tmp);
-	}
+	vuint32 = nm_setting_connection_get_gateway_ping_timeout (s_con);
+	svSetValueInt64_cond (ifcfg, "GATEWAY_PING_TIMEOUT",
+	                      vuint32 != 0,
+	                      vuint32);
 
 	switch (nm_setting_connection_get_metered (s_con)) {
 	case NM_METERED_YES:
@@ -1866,23 +1869,26 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	default:
 		svUnsetValue (ifcfg, "CONNECTION_METERED");
 	}
+
+	vint = nm_setting_connection_get_auth_retries (s_con);
+	svSetValueInt64_cond (ifcfg, "AUTH_RETRIES", vint >= 0, vint);
 }
 
 static char *
 get_route_attributes_string (NMIPRoute *route, int family)
 {
-	gs_strfreev char **names = NULL;
+	gs_free const char **names = NULL;
 	GVariant *attr, *lock;
 	GString *str;
-	int i;
+	guint i, len;
 
-	names = nm_ip_route_get_attribute_names (route);
-	if (!names || !names[0])
+	names = _nm_ip_route_get_attribute_names (route, TRUE, &len);
+	if (!len)
 		return NULL;
 
 	str = g_string_new ("");
 
-	for (i = 0; names[i]; i++) {
+	for (i = 0; i < len; i++) {
 		attr = nm_ip_route_get_attribute (route, names[i]);
 
 		if (!nm_ip_route_attribute_validate (names[i], attr, family, NULL, NULL))
@@ -1904,16 +1910,28 @@ get_route_attributes_string (NMIPRoute *route, int family)
 			                        (lock && g_variant_get_boolean (lock)) ? "lock " : "",
 			                        g_variant_get_uint32 (attr));
 		} else if (strstr (names[i], "lock-")) {
-			/* handled above */
+			const char *n = &(names[i])[NM_STRLEN ("lock-")];
+
+			attr = nm_ip_route_get_attribute (route, n);
+			if (!attr) {
+				g_string_append_printf (str,
+				                        "%s lock 0",
+				                        n);
+			} else {
+				/* we also have a corresponding attribute with the numeric value. The
+				 * lock setting is handled above. */
+			}
 		} else if (nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_TOS)) {
-			g_string_append_printf (str, "%s %u", names[i], (unsigned) g_variant_get_byte (attr));
+			g_string_append_printf (str, "%s 0x%02x", names[i], (unsigned) g_variant_get_byte (attr));
+		} else if (nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_TABLE)) {
+			g_string_append_printf (str, "%s %u", names[i], (unsigned) g_variant_get_uint32 (attr));
 		} else if (   nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_SRC)
 		           || nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_FROM)) {
 			char *arg = nm_streq (names[i], NM_IP_ROUTE_ATTRIBUTE_SRC) ? "src" : "from";
 
 			g_string_append_printf (str, "%s %s", arg, g_variant_get_string (attr, NULL));
 		} else {
-			_LOGW ("unknown route option '%s'", names[i]);
+			g_warn_if_reached ();
 			continue;
 		}
 		if (names[i + 1])
@@ -1923,35 +1941,86 @@ get_route_attributes_string (NMIPRoute *route, int family)
 	return g_string_free (str, FALSE);
 }
 
-static gboolean
-write_route_file_legacy (const char *filename, NMSettingIPConfig *s_ip4, GError **error)
+static shvarFile *
+write_route_file_svformat (const char *filename, NMSettingIPConfig *s_ip4)
 {
-	nm_auto_free_gstring GString *contents = NULL;
-	NMIPRoute *route;
-	guint32 i, num;
+	shvarFile *routefile;
+	guint i, num;
 
-	g_return_val_if_fail (filename != NULL, FALSE);
-	g_return_val_if_fail (s_ip4 != NULL, FALSE);
-	g_return_val_if_fail (error != NULL, FALSE);
-	g_return_val_if_fail (*error == NULL, FALSE);
+	routefile = utils_get_route_ifcfg (filename, TRUE);
+
+	svUnsetAll (routefile, SV_KEY_TYPE_ROUTE_SVFORMAT);
 
 	num = nm_setting_ip_config_get_num_routes (s_ip4);
-	if (num == 0) {
-		unlink (filename);
-		return TRUE;
+	for (i = 0; i < num; i++) {
+		char buf[INET_ADDRSTRLEN];
+		NMIPRoute *route;
+		guint32 netmask;
+		gint64 metric;
+		char addr_key[64];
+		char gw_key[64];
+		char netmask_key[64];
+		char metric_key[64];
+		char options_key[64];
+		gs_free char *options = NULL;
+
+		numbered_tag (addr_key, "ADDRESS", i);
+		numbered_tag (netmask_key, "NETMASK", i);
+		numbered_tag (gw_key, "GATEWAY", i);
+
+		route = nm_setting_ip_config_get_route (s_ip4, i);
+
+		svSetValueStr (routefile, addr_key, nm_ip_route_get_dest (route));
+
+		netmask = _nm_utils_ip4_prefix_to_netmask (nm_ip_route_get_prefix (route));
+		svSetValueStr (routefile, netmask_key,
+		               nm_utils_inet4_ntop (netmask, buf));
+
+		svSetValueStr (routefile, gw_key, nm_ip_route_get_next_hop (route));
+
+		metric = nm_ip_route_get_metric (route);
+		if (metric != -1) {
+			svSetValueInt64 (routefile,
+			                 numbered_tag (metric_key, "METRIC", i),
+			                 metric);
+		}
+
+		options = get_route_attributes_string (route, AF_INET);
+		if (options) {
+			svSetValueStr (routefile,
+			               numbered_tag (options_key, "OPTIONS", i),
+			               options);
+		}
 	}
 
+	return routefile;
+}
+
+static GString *
+write_route_file (NMSettingIPConfig *s_ip)
+{
+	GString *contents;
+	NMIPRoute *route;
+	guint32 i, num;
+	int addr_family;
+
+	addr_family = nm_setting_ip_config_get_addr_family (s_ip);
+
+	num = nm_setting_ip_config_get_num_routes (s_ip);
+	if (num == 0)
+		return NULL;
+
 	contents = g_string_new ("");
 
 	for (i = 0; i < num; i++) {
-		const char *next_hop;
 		gs_free char *options = NULL;
+		const char *next_hop;
 		gint64 metric;
 
-		route = nm_setting_ip_config_get_route (s_ip4, i);
+		route = nm_setting_ip_config_get_route (s_ip, i);
 		next_hop = nm_ip_route_get_next_hop (route);
 		metric = nm_ip_route_get_metric (route);
-		options = get_route_attributes_string (route, AF_INET);
+		options = get_route_attributes_string (route, addr_family);
 
 		g_string_append_printf (contents, "%s/%u",
 		                        nm_ip_route_get_dest (route),
@@ -1968,13 +2037,7 @@ write_route_file_legacy (const char *filename, NMSettingIPConfig *s_ip4, GError
 		g_string_append_c (contents, '\n');
 	}
 
-	if (!g_file_set_contents (filename, contents->str, contents->len, NULL)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Writing route file '%s' failed", filename);
-		return FALSE;
-	}
-
-	return TRUE;
+	return contents;
 }
 
 static gboolean
@@ -2025,7 +2088,7 @@ write_user_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 
 	s_user = NM_SETTING_USER (nm_connection_get_setting (connection, NM_TYPE_SETTING_USER));
 
-	svUnsetValuesWithPrefix (ifcfg, "NM_USER_");
+	svUnsetAll (ifcfg, SV_KEY_TYPE_USER);
 
 	if (!s_user)
 		return TRUE;
@@ -2050,25 +2113,29 @@ write_user_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 }
 
 static gboolean
-write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+write_ip4_setting (NMConnection *connection,
+                   shvarFile *ifcfg,
+                   shvarFile **out_route_content_svformat,
+                   GString **out_route_content,
+                   GError **error)
 {
 	NMSettingIPConfig *s_ip4;
 	const char *value;
 	char *tmp;
-	char addr_key[64];
-	char prefix_key[64];
-	char netmask_key[64];
-	char gw_key[64];
-	char *route_path = NULL;
+	char tag[64];
 	gint j;
 	guint i, num, n;
 	gint64 route_metric;
+	NMIPRouteTableSyncMode route_table;
 	gint priority;
 	int timeout;
 	GString *searches;
 	const char *method = NULL;
 	gboolean has_netmask;
 
+	NM_SET_OUT (out_route_content_svformat, NULL);
+	NM_SET_OUT (out_route_content, NULL);
+
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	if (!s_ip4) {
 		/* slave-type: clear IPv4 settings.
@@ -2076,16 +2143,7 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 		 * Some IPv4 setting related options are not cleared,
 		 * for no strong reason. */
 		svUnsetValue (ifcfg, "BOOTPROTO");
-
-		svUnsetValue (ifcfg, "IPADDR");
-		svUnsetValue (ifcfg, "PREFIX");
-		svUnsetValue (ifcfg, "NETMASK");
-		svUnsetValue (ifcfg, "GATEWAY");
-
-		svUnsetValue (ifcfg, "IPADDR0");
-		svUnsetValue (ifcfg, "PREFIX0");
-		svUnsetValue (ifcfg, "NETMASK0");
-		svUnsetValue (ifcfg, "GATEWAY0");
+		svUnsetAll (ifcfg, SV_KEY_TYPE_IP4_ADDRESS);
 		return TRUE;
 	}
 
@@ -2096,32 +2154,14 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 		method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
 
 	if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
-		int result;
-
 		/* IPv4 disabled, clear IPv4 related parameters */
 		svUnsetValue (ifcfg, "BOOTPROTO");
 		for (j = -1; j < 256; j++) {
-			if (j == -1) {
-				nm_sprintf_buf (addr_key, "IPADDR");
-				nm_sprintf_buf (prefix_key, "PREFIX");
-				nm_sprintf_buf (netmask_key, "NETMASK");
-				nm_sprintf_buf (gw_key, "GATEWAY");
-			} else {
-				nm_sprintf_buf (addr_key, "IPADDR%d", (guint) j);
-				nm_sprintf_buf (prefix_key, "PREFIX%u",  (guint) j);
-				nm_sprintf_buf (netmask_key, "NETMASK%u",  (guint) j);
-				nm_sprintf_buf (gw_key, "GATEWAY%u",  (guint) j);
-			}
-
-			svUnsetValue (ifcfg, addr_key);
-			svUnsetValue (ifcfg, prefix_key);
-			svUnsetValue (ifcfg, netmask_key);
-			svUnsetValue (ifcfg, gw_key);
+			svUnsetValue (ifcfg, numbered_tag (tag, "IPADDR", j));
+			svUnsetValue (ifcfg, numbered_tag (tag, "PREFIX", j));
+			svUnsetValue (ifcfg, numbered_tag (tag, "NETMASK", j));
+			svUnsetValue (ifcfg, numbered_tag (tag, "GATEWAY", j));
 		}
-
-		route_path = utils_get_route_path (svFileGetName (ifcfg));
-		result = unlink (route_path);
-		g_free (route_path);
 		return TRUE;
 	}
 
@@ -2165,72 +2205,60 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 			 * See https://bugzilla.redhat.com/show_bug.cgi?id=771673
 			 * and https://bugzilla.redhat.com/show_bug.cgi?id=1105770
 			 */
-			nm_sprintf_buf (addr_key, "IPADDR");
-			nm_sprintf_buf (prefix_key, "PREFIX");
-			nm_sprintf_buf (netmask_key, "NETMASK");
-			nm_sprintf_buf (gw_key, "GATEWAY");
-		} else {
-			nm_sprintf_buf (addr_key, "IPADDR%u", n);
-			nm_sprintf_buf (prefix_key, "PREFIX%u", n);
-			nm_sprintf_buf (netmask_key, "NETMASK%u", n);
-			nm_sprintf_buf (gw_key, "GATEWAY%u", n);
-		}
+			j = -1;
+		} else
+			j = n;
 
-		svSetValueStr (ifcfg, addr_key, nm_ip_address_get_address (addr));
+		svSetValueStr (ifcfg,
+		               numbered_tag (tag, "IPADDR", j),
+		               nm_ip_address_get_address (addr));
 
 		prefix = nm_ip_address_get_prefix (addr);
-		tmp = g_strdup_printf ("%u", prefix);
-		svSetValueStr (ifcfg, prefix_key, tmp);
-		g_free (tmp);
+		svSetValueInt64 (ifcfg, numbered_tag (tag, "PREFIX", j), prefix);
 
 		/* If the legacy "NETMASK" is present, keep it. */
+		numbered_tag (tag, "NETMASK", j);
 		if (has_netmask) {
 			char buf[INET_ADDRSTRLEN];
 
-			svSetValueStr (ifcfg, netmask_key,
-			               nm_utils_inet4_ntop (nm_utils_ip4_prefix_to_netmask (prefix), buf));
+			svSetValueStr (ifcfg, tag,
+			               nm_utils_inet4_ntop (_nm_utils_ip4_prefix_to_netmask (prefix), buf));
 		} else
-			svUnsetValue (ifcfg, netmask_key);
+			svUnsetValue (ifcfg, tag);
 
-		svUnsetValue (ifcfg, gw_key);
 		n++;
 	}
 
-	svUnsetValue (ifcfg, "IPADDR0");
-	svUnsetValue (ifcfg, "PREFIX0");
-	svUnsetValue (ifcfg, "NETMASK0");
-	svUnsetValue (ifcfg, "GATEWAY0");
+	svUnsetValue (ifcfg, numbered_tag (tag, "IPADDR", 0));
+	svUnsetValue (ifcfg, numbered_tag (tag, "PREFIX", 0));
+	svUnsetValue (ifcfg, numbered_tag (tag, "NETMASK", 0));
 	if (n == 0) {
 		svUnsetValue (ifcfg, "IPADDR");
 		svUnsetValue (ifcfg, "PREFIX");
 		svUnsetValue (ifcfg, "NETMASK");
-		i = 1;
-	} else
-		i = n;
-	for (; i < 256; i++) {
-		nm_sprintf_buf (addr_key, "IPADDR%u", i);
-		nm_sprintf_buf (prefix_key, "PREFIX%u", i);
-		nm_sprintf_buf (netmask_key, "NETMASK%u", i);
-		nm_sprintf_buf (gw_key, "GATEWAY%u", i);
-
-		svUnsetValue (ifcfg, addr_key);
-		svUnsetValue (ifcfg, prefix_key);
-		svUnsetValue (ifcfg, netmask_key);
-		svUnsetValue (ifcfg, gw_key);
+	}
+	for (j = n; j < 256; j++) {
+		svUnsetValue (ifcfg, numbered_tag (tag, "IPADDR", j));
+		svUnsetValue (ifcfg, numbered_tag (tag, "PREFIX", j));
+		svUnsetValue (ifcfg, numbered_tag (tag, "NETMASK", j));
 	}
 
+	for (j = -1; j < 256; j++) {
+		if (j != 0)
+			svUnsetValue (ifcfg, numbered_tag (tag, "GATEWAY", j));
+	}
 	svSetValueStr (ifcfg, "GATEWAY", nm_setting_ip_config_get_gateway (s_ip4));
 
 	num = nm_setting_ip_config_get_num_dns (s_ip4);
 	for (i = 0; i < 254; i++) {
 		const char *dns;
 
-		nm_sprintf_buf (addr_key, "DNS%u", i + 1);
+		numbered_tag (tag, "DNS", i + 1);
 		if (i >= num)
-			svUnsetValue (ifcfg, addr_key);
+			svUnsetValue (ifcfg, tag);
 		else {
 			dns = nm_setting_ip_config_get_dns (s_ip4, i);
-			svSetValueStr (ifcfg, addr_key, dns);
+			svSetValueStr (ifcfg, tag, dns);
 		}
 	}
 
@@ -2273,98 +2301,27 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	svSetValueStr (ifcfg, "DHCP_CLIENT_ID", value);
 
 	timeout = nm_setting_ip_config_get_dhcp_timeout (s_ip4);
-	tmp = timeout ? g_strdup_printf ("%d", timeout) : NULL;
-	svSetValueStr (ifcfg, "IPV4_DHCP_TIMEOUT", tmp);
-	g_free (tmp);
+	svSetValueInt64_cond (ifcfg,
+	                      "IPV4_DHCP_TIMEOUT",
+	                      timeout != 0,
+	                      timeout);
 
 	svSetValueBoolean (ifcfg, "IPV4_FAILURE_FATAL", !nm_setting_ip_config_get_may_fail (s_ip4));
 
 	route_metric = nm_setting_ip_config_get_route_metric (s_ip4);
-	tmp = route_metric != -1 ? g_strdup_printf ("%"G_GINT64_FORMAT, route_metric) : NULL;
-	svSetValueStr (ifcfg, "IPV4_ROUTE_METRIC", tmp);
-	g_free (tmp);
-
-	/* Static routes - route-<name> file */
-	route_path = utils_get_route_path (svFileGetName (ifcfg));
-	if (!route_path) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Could not get route file path for '%s'", svFileGetName (ifcfg));
-		return FALSE;
-	}
-
-	if (utils_has_route_file_new_syntax (route_path)) {
-		shvarFile *routefile;
-
-		routefile = utils_get_route_ifcfg (svFileGetName (ifcfg), TRUE);
-		if (!routefile) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			             "Could not create route file '%s'", route_path);
-			g_free (route_path);
-			return FALSE;
-		}
-		g_free (route_path);
+	svSetValueInt64_cond (ifcfg,
+	                      "IPV4_ROUTE_METRIC",
+	                      route_metric != -1,
+	                      route_metric);
 
-		num = nm_setting_ip_config_get_num_routes (s_ip4);
-		for (i = 0; i < 256; i++) {
-			char buf[INET_ADDRSTRLEN];
-			NMIPRoute *route;
-			guint32 netmask;
-			gint64 metric;
-			char metric_key[64];
-			char options_key[64];
-
-			nm_sprintf_buf (addr_key, "ADDRESS%u", i);
-			nm_sprintf_buf (netmask_key, "NETMASK%u", i);
-			nm_sprintf_buf (gw_key, "GATEWAY%u", i);
-			nm_sprintf_buf (metric_key, "METRIC%u", i);
-			nm_sprintf_buf (options_key, "OPTIONS%u", i);
-
-			if (i >= num) {
-				svUnsetValue (routefile, addr_key);
-				svUnsetValue (routefile, netmask_key);
-				svUnsetValue (routefile, gw_key);
-				svUnsetValue (routefile, metric_key);
-				svUnsetValue (routefile, options_key);
-			} else {
-				gs_free char *options = NULL;
-
-				route = nm_setting_ip_config_get_route (s_ip4, i);
+	route_table = nm_setting_ip_config_get_route_table (s_ip4);
+	svSetValueInt64_cond (ifcfg,
+	                      "IPV4_ROUTE_TABLE",
+	                      route_table != 0,
+	                      route_table);
 
-				svSetValueStr (routefile, addr_key, nm_ip_route_get_dest (route));
-
-				memset (buf, 0, sizeof (buf));
-				netmask = nm_utils_ip4_prefix_to_netmask (nm_ip_route_get_prefix (route));
-				inet_ntop (AF_INET, (const void *) &netmask, &buf[0], sizeof (buf));
-				svSetValueStr (routefile, netmask_key, &buf[0]);
-
-				svSetValueStr (routefile, gw_key, nm_ip_route_get_next_hop (route));
-
-				memset (buf, 0, sizeof (buf));
-				metric = nm_ip_route_get_metric (route);
-				if (metric == -1)
-					svUnsetValue (routefile, metric_key);
-				else {
-					tmp = g_strdup_printf ("%u", (guint32) metric);
-					svSetValueStr (routefile, metric_key, tmp);
-					g_free (tmp);
-				}
-
-				options = get_route_attributes_string (route, AF_INET);
-				if (options)
-					svSetValueStr (routefile, options_key, options);
-			}
-		}
-		if (!svWriteFile (routefile, 0644, error)) {
-			svCloseFile (routefile);
-			return FALSE;
-		}
-		svCloseFile (routefile);
-	} else {
-		write_route_file_legacy (route_path, s_ip4, error);
-		g_free (route_path);
-		if (error && *error)
-			return FALSE;
-	}
+	NM_SET_OUT (out_route_content_svformat, write_route_file_svformat (svFileGetName (ifcfg), s_ip4));
+	NM_SET_OUT (out_route_content, write_route_file (s_ip4));
 
 	timeout = nm_setting_ip_config_get_dad_timeout (s_ip4);
 	if (timeout < 0)
@@ -2386,7 +2343,7 @@ write_ip4_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 }
 
 static void
-write_ip4_aliases (NMConnection *connection, char *base_ifcfg_path)
+write_ip4_aliases (NMConnection *connection, const char *base_ifcfg_path)
 {
 	NMSettingIPConfig *s_ip4;
 	gs_free char *base_ifcfg_dir = NULL, *base_ifcfg_name = NULL;
@@ -2432,7 +2389,7 @@ write_ip4_aliases (NMConnection *connection, char *base_ifcfg_path)
 	for (i = 0; i < num; i++) {
 		GVariant *label_var;
 		const char *label, *p;
-		char *path, *tmp;
+		char *path;
 		NMIPAddress *addr;
 		shvarFile *ifcfg;
 
@@ -2462,68 +2419,13 @@ write_ip4_aliases (NMConnection *connection, char *base_ifcfg_path)
 		addr = nm_setting_ip_config_get_address (s_ip4, i);
 		svSetValueStr (ifcfg, "IPADDR", nm_ip_address_get_address (addr));
 
-		tmp = g_strdup_printf ("%u", nm_ip_address_get_prefix (addr));
-		svSetValueStr (ifcfg, "PREFIX", tmp);
-		g_free (tmp);
+		svSetValueInt64 (ifcfg, "PREFIX", nm_ip_address_get_prefix(addr));
 
 		svWriteFile (ifcfg, 0644, NULL);
 		svCloseFile (ifcfg);
 	}
 }
 
-static gboolean
-write_route6_file (const char *filename, NMSettingIPConfig *s_ip6, GError **error)
-{
-	nm_auto_free_gstring GString *contents = NULL;
-	NMIPRoute *route;
-	guint32 i, num;
-
-	g_return_val_if_fail (filename, FALSE);
-	g_return_val_if_fail (s_ip6, FALSE);
-	g_return_val_if_fail (!error || !*error, FALSE);
-
-	num = nm_setting_ip_config_get_num_routes (s_ip6);
-	if (num == 0) {
-		unlink (filename);
-		return TRUE;
-	}
-
-	contents = g_string_new ("");
-
-	for (i = 0; i < num; i++) {
-		gs_free char *options = NULL;
-		const char *next_hop;
-		gint64 metric;
-
-		route = nm_setting_ip_config_get_route (s_ip6, i);
-		next_hop = nm_ip_route_get_next_hop (route);
-		metric = nm_ip_route_get_metric (route);
-		options = get_route_attributes_string (route, AF_INET6);
-
-		g_string_append_printf (contents, "%s/%u",
-		                        nm_ip_route_get_dest (route),
-		                        nm_ip_route_get_prefix (route));
-		if (next_hop)
-			g_string_append_printf (contents, " via %s", next_hop);
-		if (metric >= 0)
-			g_string_append_printf (contents, " metric %u", (guint) metric);
-		if (options) {
-			g_string_append_c (contents, ' ');
-			g_string_append (contents, options);
-		}
-
-		g_string_append_c (contents, '\n');
-	}
-
-	if (!g_file_set_contents (filename, contents->str, contents->len, NULL)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Writing route6 file '%s' failed", filename);
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
 static void
 write_ip6_setting_dhcp_hostname (NMSettingIPConfig *s_ip6, shvarFile *ifcfg)
 {
@@ -2542,21 +2444,25 @@ write_ip6_setting_dhcp_hostname (NMSettingIPConfig *s_ip6, shvarFile *ifcfg)
 }
 
 static gboolean
-write_ip6_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
+write_ip6_setting (NMConnection *connection,
+                   shvarFile *ifcfg,
+                   GString **out_route6_content,
+                   GError **error)
 {
 	NMSettingIPConfig *s_ip6;
 	NMSettingIPConfig *s_ip4;
 	const char *value;
-	char *tmp;
 	guint i, num, num4;
 	gint priority;
 	NMIPAddress *addr;
 	const char *dns;
 	gint64 route_metric;
+	NMIPRouteTableSyncMode route_table;
 	GString *ip_str1, *ip_str2, *ip_ptr;
-	char *route6_path;
 	NMSettingIP6ConfigAddrGenMode addr_gen_mode;
 
+	NM_SET_OUT (out_route6_content, NULL);
+
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	if (!s_ip6) {
 		/* slave-type: clear IPv6 settings
@@ -2636,15 +2542,15 @@ write_ip6_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	num4 = s_ip4 ? nm_setting_ip_config_get_num_dns (s_ip4) : 0; /* from where to start with IPv6 entries */
 	num = nm_setting_ip_config_get_num_dns (s_ip6);
 	for (i = 0; i < 254; i++) {
-		char addr_key[64];
+		char tag[64];
 
-		nm_sprintf_buf (addr_key, "DNS%u", i + num4 + 1);
+		numbered_tag (tag, "DNS", i + num4 + 1);
 
 		if (i >= num)
-			svUnsetValue (ifcfg, addr_key);
+			svUnsetValue (ifcfg, tag);
 		else {
 			dns = nm_setting_ip_config_get_dns (s_ip6, i);
-			svSetValueStr (ifcfg, addr_key, dns);
+			svSetValueStr (ifcfg, tag, dns);
 		}
 	}
 
@@ -2681,9 +2587,16 @@ write_ip6_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	               nm_setting_ip_config_get_may_fail (s_ip6) ? "no" : "yes");
 
 	route_metric = nm_setting_ip_config_get_route_metric (s_ip6);
-	tmp = route_metric != -1 ? g_strdup_printf ("%"G_GINT64_FORMAT, route_metric) : NULL;
-	svSetValueStr (ifcfg, "IPV6_ROUTE_METRIC", tmp);
-	g_free (tmp);
+	svSetValueInt64_cond (ifcfg,
+	                      "IPV6_ROUTE_METRIC",
+	                      route_metric != -1,
+	                      route_metric);
+
+	route_table = nm_setting_ip_config_get_route_table (s_ip6);
+	svSetValueInt64_cond (ifcfg,
+	                      "IPV6_ROUTE_TABLE",
+	                      route_table != 0,
+	                      route_table);
 
 	/* IPv6 Privacy Extensions */
 	svUnsetValue (ifcfg, "IPV6_PRIVACY");
@@ -2706,10 +2619,8 @@ write_ip6_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	/* IPv6 Address generation mode */
 	addr_gen_mode = nm_setting_ip6_config_get_addr_gen_mode (NM_SETTING_IP6_CONFIG (s_ip6));
 	if (addr_gen_mode != NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) {
-		tmp = nm_utils_enum_to_str (nm_setting_ip6_config_addr_gen_mode_get_type (),
-		                            addr_gen_mode);
-		svSetValueStr (ifcfg, "IPV6_ADDR_GEN_MODE", tmp);
-		g_free (tmp);
+		svSetValueEnum (ifcfg, "IPV6_ADDR_GEN_MODE", nm_setting_ip6_config_addr_gen_mode_get_type (),
+		                addr_gen_mode);
 	} else {
 		svUnsetValue (ifcfg, "IPV6_ADDR_GEN_MODE");
 	}
@@ -2724,17 +2635,7 @@ write_ip6_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 	else
 		svUnsetValue (ifcfg, "IPV6_DNS_PRIORITY");
 
-	/* Static routes go to route6-<dev> file */
-	route6_path = utils_get_route6_path (svFileGetName (ifcfg));
-	if (!route6_path) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Could not get route6 file path for '%s'", svFileGetName (ifcfg));
-		return FALSE;
-	}
-	write_route6_file (route6_path, s_ip6, error);
-	g_free (route6_path);
-	if (error && *error)
-		return FALSE;
+	NM_SET_OUT (out_route6_content, write_route_file (s_ip6));
 
 	return TRUE;
 }
@@ -2817,30 +2718,43 @@ escape_id (const char *id)
 }
 
 static gboolean
-write_connection (NMConnection *connection,
-                  const char *ifcfg_dir,
-                  const char *filename,
-                  char **out_filename,
-                  NMConnection **out_reread,
-                  gboolean *out_reread_same,
-                  GError **error)
+do_write_construct (NMConnection *connection,
+                    const char *ifcfg_dir,
+                    const char *filename,
+                    shvarFile **out_ifcfg,
+                    GHashTable **out_blobs,
+                    GHashTable **out_secrets,
+                    gboolean *out_route_ignore,
+                    shvarFile **out_route_content_svformat,
+                    GString **out_route_content,
+                    GString **out_route6_content,
+                    GError **error)
 {
 	NMSettingConnection *s_con;
 	nm_auto_shvar_file_close shvarFile *ifcfg = NULL;
 	gs_free char *ifcfg_name = NULL;
+	gs_free char *route_path = NULL;
+	gs_free char *route6_path = NULL;
 	const char *type;
-	gboolean no_8021x = FALSE;
-	gboolean wired = FALSE;
+	gs_unref_hashtable GHashTable *blobs = NULL;
+	gs_unref_hashtable GHashTable *secrets = NULL;
+	gboolean wired;
+	gboolean no_8021x;
+	gboolean route_path_is_svformat;
+	gboolean has_complex_routes_v4;
+	gboolean has_complex_routes_v6;
+	gboolean route_ignore;
+	nm_auto_shvar_file_close shvarFile *route_content_svformat = NULL;
+	nm_auto_free_gstring GString *route_content = NULL;
+	nm_auto_free_gstring GString *route6_content = NULL;
 
 	nm_assert (NM_IS_CONNECTION (connection));
 	nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
-	nm_assert (!out_reread || !*out_reread);
 
-	if (!writer_can_write_connection (connection, error))
+	if (!nms_ifcfg_rh_writer_can_write_connection (connection, error))
 		return FALSE;
 
 	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
 
 	if (filename) {
 		/* For existing connections, 'filename' should be full path to ifcfg file */
@@ -2849,7 +2763,7 @@ write_connection (NMConnection *connection,
 			return FALSE;
 
 		ifcfg_name = g_strdup (filename);
-	} else {
+	} else if (ifcfg_dir) {
 		char *escaped;
 
 		escaped = escape_id (nm_setting_connection_get_id (s_con));
@@ -2880,6 +2794,21 @@ write_connection (NMConnection *connection,
 		}
 
 		ifcfg = svCreateFile (ifcfg_name);
+	} else
+		ifcfg = svCreateFile ("/tmp/ifcfg-dummy");
+
+	route_path = utils_get_route_path (svFileGetName (ifcfg));
+	if (!route_path) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "Could not get route file path for '%s'", svFileGetName (ifcfg));
+		return FALSE;
+	}
+
+	route6_path = utils_get_route6_path (svFileGetName (ifcfg));
+	if (!route6_path) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "Could not get route6 file path for '%s'", svFileGetName (ifcfg));
+		return FALSE;
 	}
 
 	type = nm_setting_connection_get_connection_type (s_con);
@@ -2889,6 +2818,10 @@ write_connection (NMConnection *connection,
 		return FALSE;
 	}
 
+	secrets = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
+
+	wired = FALSE;
+	no_8021x = FALSE;
 	if (!strcmp (type, NM_SETTING_WIRED_SETTING_NAME)) {
 		// FIXME: can't write PPPoE at this time
 		if (nm_connection_get_setting_pppoe (connection)) {
@@ -2905,19 +2838,19 @@ write_connection (NMConnection *connection,
 		if (!write_vlan_setting (connection, ifcfg, &wired, error))
 			return FALSE;
 	} else if (!strcmp (type, NM_SETTING_WIRELESS_SETTING_NAME)) {
-		if (!write_wireless_setting (connection, ifcfg, &no_8021x, error))
+		if (!write_wireless_setting (connection, ifcfg, secrets, &no_8021x, error))
 			return FALSE;
 	} else if (!strcmp (type, NM_SETTING_INFINIBAND_SETTING_NAME)) {
 		if (!write_infiniband_setting (connection, ifcfg, error))
 			return FALSE;
 	} else if (!strcmp (type, NM_SETTING_BOND_SETTING_NAME)) {
-		if (!write_bonding_setting (connection, ifcfg, &wired, error))
+		if (!write_bond_setting (connection, ifcfg, &wired, error))
 			return FALSE;
 	} else if (!strcmp (type, NM_SETTING_TEAM_SETTING_NAME)) {
 		if (!write_team_setting (connection, ifcfg, &wired, error))
 			return FALSE;
 	} else if (!strcmp (type, NM_SETTING_BRIDGE_SETTING_NAME)) {
-		if (!write_bridge_setting (connection, ifcfg, error))
+		if (!write_bridge_setting (connection, ifcfg, &wired, error))
 			return FALSE;
 	} else {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
@@ -2926,7 +2859,8 @@ write_connection (NMConnection *connection,
 	}
 
 	if (!no_8021x) {
-		if (!write_8021x_setting (connection, ifcfg, wired, error))
+		blobs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref);
+		if (!write_8021x_setting (connection, ifcfg, secrets, blobs, wired, error))
 			return FALSE;
 	}
 
@@ -2948,11 +2882,49 @@ write_connection (NMConnection *connection,
 	svUnsetValue (ifcfg, "DHCP_HOSTNAME");
 	svUnsetValue (ifcfg, "DHCP_FQDN");
 
-	if (!write_ip4_setting (connection, ifcfg, error))
+	route_path_is_svformat = utils_has_route_file_new_syntax (route_path);
+
+	has_complex_routes_v4 = utils_has_complex_routes (ifcfg_name, AF_INET);
+	has_complex_routes_v6 = utils_has_complex_routes (ifcfg_name, AF_INET6);
+
+	if (has_complex_routes_v4 || has_complex_routes_v6) {
+		NMSettingIPConfig *s_ip4, *s_ip6;
+
+		s_ip4 = nm_connection_get_setting_ip4_config (connection);
+		s_ip6 = nm_connection_get_setting_ip6_config (connection);
+		if (   (   s_ip4
+		        && nm_setting_ip_config_get_num_routes (s_ip4) > 0)
+		    || (   s_ip6
+		        && nm_setting_ip_config_get_num_routes (s_ip6) > 0)) {
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+			             "Cannot configure static routes on a connection that has an associated 'rule%s-' file",
+			             has_complex_routes_v4 ? "" : "6");
+			return FALSE;
+		}
+		if (   (   s_ip4
+		        && nm_setting_ip_config_get_route_table (s_ip4) != 0)
+		    || (   s_ip6
+		        && nm_setting_ip_config_get_route_table (s_ip6) != 0)) {
+			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+			             "Cannot configure a route table for policy routing on a connection that has an associated 'rule%s-' file",
+			             has_complex_routes_v4 ? "" : "6");
+			return FALSE;
+		}
+		route_ignore = TRUE;
+	} else
+		route_ignore = FALSE;
+
+	if (!write_ip4_setting (connection,
+	                        ifcfg,
+	                        !route_ignore && route_path_is_svformat ? &route_content_svformat : NULL,
+	                        !route_ignore && route_path_is_svformat ? NULL                      :&route_content,
+	                        error))
 		return FALSE;
-	write_ip4_aliases (connection, ifcfg_name);
 
-	if (!write_ip6_setting (connection, ifcfg, error))
+	if (!write_ip6_setting (connection,
+	                        ifcfg,
+	                        !route_ignore ? &route6_content : NULL,
+	                        error))
 		return FALSE;
 
 	if (!write_res_options (connection, ifcfg, error))
@@ -2960,100 +2932,228 @@ write_connection (NMConnection *connection,
 
 	write_connection_setting (s_con, ifcfg);
 
+	NM_SET_OUT (out_ifcfg, g_steal_pointer (&ifcfg));
+	NM_SET_OUT (out_blobs, g_steal_pointer (&blobs));
+	NM_SET_OUT (out_secrets, g_steal_pointer (&secrets));
+	NM_SET_OUT (out_route_ignore, route_ignore);
+	NM_SET_OUT (out_route_content_svformat, g_steal_pointer (&route_content_svformat));
+	NM_SET_OUT (out_route_content, g_steal_pointer (&route_content));
+	NM_SET_OUT (out_route6_content, g_steal_pointer (&route6_content));
+	return TRUE;
+}
+
+static gboolean
+do_write_to_disk (NMConnection *connection,
+                  shvarFile *ifcfg,
+                  GHashTable *blobs,
+                  GHashTable *secrets,
+                  gboolean route_ignore,
+                  shvarFile *route_content_svformat,
+                  GString *route_content,
+                  GString *route6_content,
+                  GError **error)
+{
+	/* From here on, we persist data to disk. Before, it was all in-memory
+	 * only. But we loaded the ifcfg files from disk, and managled our
+	 * new settings (in-momory). */
+
 	if (!svWriteFile (ifcfg, 0644, error))
 		return FALSE;
 
-	if (out_reread || out_reread_same) {
-		gs_unref_object NMConnection *reread = NULL;
-		gs_free_error GError *local = NULL;
-		gs_free char *unhandled = NULL;
-		gboolean reread_same = FALSE;
-
-		reread = connection_from_file (ifcfg_name, &unhandled, &local, NULL);
-
-		if (unhandled) {
-			_LOGW ("failure to re-read the new connection from file \"%s\": %s",
-			       ifcfg_name, "connection is unhandled");
-			g_clear_object (&reread);
-		} else if (!reread) {
-			_LOGW ("failure to re-read the new connection from file \"%s\": %s",
-			       ifcfg_name, local ? local->message : "<unknown>");
-		} else {
-			if (out_reread_same) {
-				if (nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT))
-					reread_same = TRUE;
+	write_ip4_aliases (connection, svFileGetName (ifcfg));
 
-				nm_assert (reread_same == nm_connection_compare (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
-				nm_assert (reread_same == ({
-				                                gs_unref_hashtable GHashTable *_settings = NULL;
+	if (!write_blobs (blobs, error))
+		return FALSE;
 
-				                                (   nm_connection_diff (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT, &_settings)
-				                                 && !_settings);
-				                           }));
+	if (!write_secrets (ifcfg, secrets, error))
+		return FALSE;
+
+	if (!route_ignore) {
+		gs_free char *route_path = utils_get_route_path (svFileGetName (ifcfg));
+
+		if (!route_content && !route_content_svformat)
+			(void) unlink (route_path);
+		else {
+			nm_assert (route_content_svformat || route_content);
+			if (route_content_svformat) {
+				if (!svWriteFile (route_content_svformat, 0644, error))
+					return FALSE;
+			} else {
+				if (!g_file_set_contents (route_path, route_content->str, route_content->len, NULL)) {
+					g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+					             "Writing route file '%s' failed", route_path);
+					return FALSE;
+				}
 			}
 		}
-
-		NM_SET_OUT (out_reread, g_steal_pointer (&reread));
-		NM_SET_OUT (out_reread_same, reread_same);
 	}
 
-	/* Only return the filename if this was a newly written ifcfg */
-	if (out_filename && !filename)
-		*out_filename = g_steal_pointer (&ifcfg_name);
+	if (!route_ignore) {
+		gs_free char *route6_path = utils_get_route6_path (svFileGetName (ifcfg));
+
+		if (!route6_content)
+			(void) unlink (route6_path);
+		else {
+			if (!g_file_set_contents (route6_path, route6_content->str, route6_content->len, NULL)) {
+				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+				             "Writing route6 file '%s' failed", route6_path);
+				return FALSE;
+			}
+		}
+	}
 
 	return TRUE;
 }
 
-gboolean
-writer_can_write_connection (NMConnection *connection, GError **error)
+static gboolean
+do_write_reread (NMConnection *connection,
+                 const char *ifcfg_name,
+                 NMConnection **out_reread,
+                 gboolean *out_reread_same,
+                 GError **error)
 {
-	NMSettingConnection *s_con;
+	gs_unref_object NMConnection *reread = NULL;
+	gs_free_error GError *local = NULL;
+	gs_free char *unhandled = NULL;
+	gboolean reread_same = FALSE;
 
-	if (   (   nm_connection_is_type (connection, NM_SETTING_WIRED_SETTING_NAME)
-	        && !nm_connection_get_setting_pppoe (connection))
-	    || nm_connection_is_type (connection, NM_SETTING_VLAN_SETTING_NAME)
-	    || nm_connection_is_type (connection, NM_SETTING_WIRELESS_SETTING_NAME)
-	    || nm_connection_is_type (connection, NM_SETTING_INFINIBAND_SETTING_NAME)
-	    || nm_connection_is_type (connection, NM_SETTING_BOND_SETTING_NAME)
-	    || nm_connection_is_type (connection, NM_SETTING_TEAM_SETTING_NAME)
-	    || nm_connection_is_type (connection, NM_SETTING_BRIDGE_SETTING_NAME))
-		return TRUE;
+	nm_assert (!out_reread || !*out_reread);
 
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-	             "The ifcfg-rh plugin cannot write the connection '%s' (type '%s' pppoe %d)",
-	             nm_connection_get_id (connection),
-	             nm_setting_connection_get_connection_type (s_con),
-	             !!nm_connection_get_setting_pppoe (connection));
-	return FALSE;
+	reread = connection_from_file (ifcfg_name, &unhandled, &local, NULL);
+
+	if (!reread) {
+		g_propagate_error (error, local);
+		local = NULL;
+		return FALSE;
+	}
+	if (unhandled) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "connection is unhandled");
+		return FALSE;
+	}
+	if (out_reread_same) {
+		if (nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT))
+			reread_same = TRUE;
+
+		nm_assert (reread_same == nm_connection_compare (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+		nm_assert (reread_same == ({
+		                                gs_unref_hashtable GHashTable *_settings = NULL;
+
+		                                (   nm_connection_diff (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT, &_settings)
+		                                 && !_settings);
+		                           }));
+	}
+
+	NM_SET_OUT (out_reread, g_steal_pointer (&reread));
+	NM_SET_OUT (out_reread_same, reread_same);
+	return TRUE;
 }
 
 gboolean
-writer_new_connection (NMConnection *connection,
-                       const char *ifcfg_dir,
-                       char **out_filename,
-                       NMConnection **out_reread,
-                       gboolean *out_reread_same,
-                       GError **error)
+nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
+                                      const char *ifcfg_dir,
+                                      const char *filename,
+                                      char **out_filename,
+                                      NMConnection **out_reread,
+                                      gboolean *out_reread_same,
+                                      GError **error)
 {
-	return write_connection (connection, ifcfg_dir, NULL, out_filename, out_reread, out_reread_same, error);
+	nm_auto_shvar_file_close shvarFile *ifcfg = NULL;
+	nm_auto_free_gstring GString *route_content = NULL;
+	gboolean route_ignore = FALSE;
+	nm_auto_shvar_file_close shvarFile *route_content_svformat = NULL;
+	nm_auto_free_gstring GString *route6_content = NULL;
+	gs_unref_hashtable GHashTable *secrets = NULL;
+	gs_unref_hashtable GHashTable *blobs = NULL;
+	GError *local = NULL;
+
+	nm_assert (!out_reread || !*out_reread);
+
+	if (!do_write_construct (connection,
+	                         ifcfg_dir,
+	                         filename,
+	                         &ifcfg,
+	                         &blobs,
+	                         &secrets,
+	                         &route_ignore,
+	                         &route_content_svformat,
+	                         &route_content,
+	                         &route6_content,
+	                         error))
+		return FALSE;
+
+	_LOGT ("write: write connection %s (%s) to file \"%s\"",
+	       nm_connection_get_id (connection),
+	       nm_connection_get_uuid (connection),
+	       svFileGetName (ifcfg));
+
+	if (!do_write_to_disk (connection,
+	                       ifcfg,
+	                       blobs,
+	                       secrets,
+	                       route_ignore,
+	                       route_content_svformat,
+	                       route_content,
+	                       route6_content,
+	                       error))
+		return FALSE;
+
+	/* Note that we just wrote the connection to disk, and re-read it from there.
+	 * That is racy if somebody else modifies the connection.
+	 *
+	 * A better solution might be, to re-read the connection only based on the
+	 * in-memory representation of what we collected above. But the reader
+	 * does not yet allow to inject the configuration. */
+	if (out_reread || out_reread_same) {
+		if (!do_write_reread (connection,
+		                         svFileGetName (ifcfg),
+		                         out_reread,
+		                         out_reread_same,
+		                         &local)) {
+			_LOGW ("write: failure to re-read connection \"%s\": %s",
+			       svFileGetName (ifcfg), local->message);
+			g_clear_error (&local);
+		} else {
+			if (   out_reread_same
+			    && !*out_reread_same) {
+				_LOGD ("write: connection %s (%s) was modified by persisting it to \"%s\" ",
+				       nm_connection_get_id (connection),
+				       nm_connection_get_uuid (connection),
+				       svFileGetName (ifcfg));
+			}
+		}
+	}
+
+	/* Only return the filename if this was a newly written ifcfg */
+	if (out_filename && !filename)
+		*out_filename = g_strdup (svFileGetName (ifcfg));
+
+	return TRUE;
 }
 
 gboolean
-writer_update_connection (NMConnection *connection,
-                          const char *ifcfg_dir,
-                          const char *filename,
-                          NMConnection **out_reread,
-                          gboolean *out_reread_same,
-                          GError **error)
+nms_ifcfg_rh_writer_can_write_connection (NMConnection *connection, GError **error)
 {
-	if (utils_has_complex_routes (filename)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Cannot modify a connection that has an associated 'rule-' or 'rule6-' file");
-		return FALSE;
-	}
+	const char *type, *id;
+
+	type = nm_connection_get_connection_type (connection);
+	if (NM_IN_STRSET (type,
+	                  NM_SETTING_VLAN_SETTING_NAME,
+	                  NM_SETTING_WIRELESS_SETTING_NAME,
+	                  NM_SETTING_INFINIBAND_SETTING_NAME,
+	                  NM_SETTING_BOND_SETTING_NAME,
+	                  NM_SETTING_TEAM_SETTING_NAME,
+	                  NM_SETTING_BRIDGE_SETTING_NAME))
+		return TRUE;
+	if (   nm_streq0 (type, NM_SETTING_WIRED_SETTING_NAME)
+	    && !nm_connection_get_setting_pppoe (connection))
+		return TRUE;
 
-	return write_connection (connection, ifcfg_dir, filename, NULL, out_reread, out_reread_same, error);
+	id = nm_connection_get_id (connection);
+	g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+	             "The ifcfg-rh plugin cannot write the connection %s%s%s (type %s%s%s)",
+	             NM_PRINT_FMT_QUOTE_STRING (id),
+	             NM_PRINT_FMT_QUOTE_STRING (type));
+	return FALSE;
 }
 
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
index 9cd9513e..d7a255a9 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
@@ -18,26 +18,20 @@
  * Copyright (C) 2009 Red Hat, Inc.
  */
 
-#ifndef _WRITER_H_
-#define _WRITER_H_
+#ifndef __NMS_IFCFG_RH_WRITER_H__
+#define __NMS_IFCFG_RH_WRITER_H__
 
 #include "nm-connection.h"
 
-gboolean writer_can_write_connection (NMConnection *connection,
-                                      GError **error);
+gboolean nms_ifcfg_rh_writer_can_write_connection (NMConnection *connection,
+                                                   GError **error);
 
-gboolean writer_new_connection (NMConnection *connection,
-                                const char *ifcfg_dir,
-                                char **out_filename,
-                                NMConnection **out_reread,
-                                gboolean *out_reread_same,
-                                GError **error);
+gboolean nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
+                                               const char *ifcfg_dir,
+                                               const char *filename,
+                                               char **out_filename,
+                                               NMConnection **out_reread,
+                                               gboolean *out_reread_same,
+                                               GError **error);
 
-gboolean writer_update_connection (NMConnection *connection,
-                                   const char *ifcfg_dir,
-                                   const char *filename,
-                                   NMConnection **out_reread,
-                                   gboolean *out_reread_same,
-                                   GError **error);
-
-#endif /* _WRITER_H_ */
+#endif /* __NMS_IFCFG_RH_WRITER_H__ */
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index 47ad5a23..df03bf65 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -38,10 +38,15 @@
 
 #include "nm-core-internal.h"
 #include "nm-core-utils.h"
+#include "nm-utils/nm-enum-utils.h"
+#include "nm-utils/c-list.h"
 
 /*****************************************************************************/
 
 struct _shvarLine {
+
+	CList lst;
+
 	/* There are three cases:
 	 *
 	 * 1) the line is not a valid variable assignment (that is, it doesn't
@@ -68,7 +73,7 @@ typedef struct _shvarLine shvarLine;
 struct _shvarFile {
 	char      *fileName;
 	int        fd;
-	GList     *lineList;
+	CList      lst_head;
 	gboolean   modified;
 };
 
@@ -627,6 +632,7 @@ svFile_new (const char *name)
 	s = g_slice_new0 (shvarFile);
 	s->fd = -1;
 	s->fileName = g_strdup (name);
+	c_list_init (&s->lst_head);
 	return s;
 }
 
@@ -639,7 +645,7 @@ svFileGetName (const shvarFile *s)
 }
 
 void
-svFileSetName_test_only (shvarFile *s, const char *fileName)
+_nmtst_svFileSetName (shvarFile *s, const char *fileName)
 {
 	/* changing the file name is not supported for regular
 	 * operation. Only allowed to use in tests, othewise,
@@ -649,7 +655,7 @@ svFileSetName_test_only (shvarFile *s, const char *fileName)
 }
 
 void
-svFileSetModified_test_only (shvarFile *s)
+_nmtst_svFileSetModified (shvarFile *s)
 {
 	/* marking a file as modified is only for testing. */
 	s->modified = TRUE;
@@ -687,6 +693,7 @@ line_new_parse (const char *value, gsize len)
 	nm_assert (value);
 
 	line = g_slice_new0 (shvarLine);
+	c_list_init (&line->lst);
 
 	for (k = 0; k < len; k++) {
 		if (g_ascii_isspace (value[k]))
@@ -724,6 +731,7 @@ line_new_build (const char *key, const char *value)
 	value = svEscape (value, &value_escaped);
 
 	line = g_slice_new (shvarLine);
+	c_list_init (&line->lst);
 	line->line = value_escaped ?: g_strdup (value);
 	line->key_with_prefix = g_strdup (key);
 	line->key = line->key_with_prefix;
@@ -768,6 +776,7 @@ line_free (shvarLine *line)
 	ASSERT_shvarLine (line);
 	g_free (line->line);
 	g_free (line->key_with_prefix);
+	c_list_unlink (&line->lst);
 	g_slice_free (shvarLine, line);
 }
 
@@ -785,9 +794,8 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 	int errsv = 0;
 	char *arena;
 	const char *p, *q;
-	GError *local = NULL;
+	gs_free_error GError *local = NULL;
 	nm_auto_close int fd = -1;
-	GList *lineList = NULL;
 
 	if (create)
 		fd = open (name, O_RDWR | O_CLOEXEC); /* NOT O_CREAT */
@@ -810,34 +818,35 @@ svOpenFileInternal (const char *name, gboolean create, GError **error)
 		return NULL;
 	}
 
-	if (nm_utils_fd_get_contents (fd,
+	if (nm_utils_fd_get_contents (closefd ? nm_steal_fd (&fd) : fd,
+	                              closefd,
 	                              10 * 1024 * 1024,
 	                              &arena,
 	                              NULL,
 	                              &local) < 0) {
+		if (create)
+			return svFile_new (name);
+
 		g_set_error (error, G_FILE_ERROR,
 		             local->domain == G_FILE_ERROR ? local->code : G_FILE_ERROR_FAILED,
 		             "Could not read file '%s': %s",
 		             name, local->message);
-		g_error_free (local);
 		return NULL;
 	}
 
+	s = svFile_new (name);
+
 	for (p = arena; (q = strchr (p, '\n')) != NULL; p = q + 1)
-		lineList = g_list_prepend (lineList, line_new_parse (p, q - p));
+		c_list_link_tail (&s->lst_head, &line_new_parse (p, q - p)->lst);
 	if (p[0])
-		lineList = g_list_prepend (lineList, line_new_parse (p, strlen (p)));
+		c_list_link_tail (&s->lst_head, &line_new_parse (p, strlen (p))->lst);
 	g_free (arena);
-	lineList = g_list_reverse (lineList);
-
-	s = svFile_new (name);
-	s->lineList = lineList;
 
 	/* closefd is set if we opened the file read-only, so go ahead and
 	 * close it, because we can't write to it anyway */
 	if (!closefd) {
-		s->fd = fd;
-		fd = -1;
+		nm_assert (fd > 0);
+		s->fd = nm_steal_fd (&fd);
 	}
 
 	return s;
@@ -861,42 +870,22 @@ svCreateFile (const char *name)
 
 /*****************************************************************************/
 
-static const GList *
-shlist_find (const GList *current, const char *key)
-{
-	nm_assert (_shell_is_name (key, -1));
-
-	if (current) {
-		do {
-			shvarLine *line = current->data;
-
-			ASSERT_shvarLine (line);
-			if (line->key && nm_streq (line->key, key))
-				return current;
-			current = current->next;
-		} while (current);
-	}
-	return NULL;
-}
-
-/*****************************************************************************/
-
 GHashTable *
 svGetKeys (shvarFile *s)
 {
 	GHashTable *keys = NULL;
-	const GList *current;
+	CList *current;
 	const shvarLine *line;
 
 	nm_assert (s);
 
-	for (current = s->lineList; current; current = current->next) {
-		line = current->data;
+	c_list_for_each (current, &s->lst_head) {
+		line = c_list_entry (current, shvarLine, lst);
 		if (line->key && line->line) {
 			/* we don't clone the keys. The keys are only valid
 			 * until @s gets modified. */
 			if (!keys)
-				keys = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+				keys = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, NULL);
 			g_hash_table_add (keys, (gpointer) line->key);
 		}
 	}
@@ -908,14 +897,14 @@ svGetKeys (shvarFile *s)
 const char *
 svFindFirstKeyWithPrefix (shvarFile *s, const char *key_prefix)
 {
-	const GList *current;
+	CList *current;
 	const shvarLine *l;
 
 	g_return_val_if_fail (s, NULL);
 	g_return_val_if_fail (key_prefix, NULL);
 
-	for (current = s->lineList; current; current = current->next) {
-		l = current->data;
+	c_list_for_each (current, &s->lst_head) {
+		l = c_list_entry (current, shvarLine, lst);
 		if (   l->key
 		    && l->line
 		    && g_str_has_prefix (l->key, key_prefix))
@@ -930,34 +919,31 @@ svFindFirstKeyWithPrefix (shvarFile *s, const char *key_prefix)
 static const char *
 _svGetValue (shvarFile *s, const char *key, char **to_free)
 {
-	const GList *current, *last;
-	const shvarLine *line;
+	CList *current;
+	const shvarLine *line, *l;
+	const char *v;
 
 	nm_assert (s);
 	nm_assert (_shell_is_name (key, -1));
 	nm_assert (to_free);
 
-	last = NULL;
-	current = s->lineList;
-	while ((current = shlist_find (current, key))) {
-		last = current;
-		current = current->next;
+	line = NULL;
+	c_list_for_each (current, &s->lst_head) {
+		l = c_list_entry (current, shvarLine, lst);
+		if (l->key && nm_streq (l->key, key))
+			line = l;
 	}
-	if (last) {
-		line = last->data;
-		if (line->line) {
-			const char *v;
-
-			v = svUnescape (line->line, to_free);
-			if (!v) {
-				/* a wrongly quoted value is treated like the empty string.
-				 * See also svWriteFile(), which handles unparsable values
-				 * that way. */
-				nm_assert (!*to_free);
-				return "";
-			}
-			return v;
+
+	if (line && line->line) {
+		v = svUnescape (line->line, to_free);
+		if (!v) {
+			/* a wrongly quoted value is treated like the empty string.
+			 * See also svWriteFile(), which handles unparsable values
+			 * that way. */
+			nm_assert (!*to_free);
+			return "";
 		}
+		return v;
 	}
 	*to_free = NULL;
 	return NULL;
@@ -1104,50 +1090,148 @@ svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 m
 	return result;
 }
 
+gboolean
+svGetValueEnum (shvarFile *s, const char *key,
+                GType gtype, int *out_value,
+                GError **error)
+{
+	gs_free char *to_free = NULL;
+	const char *svalue;
+	gs_free char *err_token = NULL;
+	int value;
+
+	svalue = _svGetValue (s, key, &to_free);
+	if (!svalue) {
+		/* don't touch out_value. The caller is supposed
+		 * to initialize it with the default value. */
+		return TRUE;
+	}
+
+	if (!nm_utils_enum_from_str (gtype, svalue, &value, &err_token)) {
+		g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN,
+		             "Invalid token \"%s\" in \"%s\" for %s",
+		             err_token, svalue, key);
+		return FALSE;
+	}
+
+	NM_SET_OUT (out_value, value);
+	return TRUE;
+}
+
 /*****************************************************************************/
 
+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)
+{
+	CList *current;
+	shvarLine *line;
+	gboolean changed = FALSE;
+
+	g_return_val_if_fail (s, FALSE);
+
+	c_list_for_each (current, &s->lst_head) {
+		line = c_list_entry (current, shvarLine, lst);
+		ASSERT_shvarLine (line);
+		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;
+		}
+
+		continue;
+do_clear:
+		if (nm_clear_g_free (&line->line)) {
+			ASSERT_shvarLine (line);
+			changed = TRUE;
+		}
+	}
+
+	if (changed)
+		s->modified = TRUE;
+	return changed;
+}
+
 /* Same as svSetValueStr() but it preserves empty @value -- contrary to
  * svSetValueStr() for which "" effectively means to remove the value. */
-void
+gboolean
 svSetValue (shvarFile *s, const char *key, const char *value)
 {
-	GList *current, *last;
+	CList *current;
+	shvarLine *line, *l;
+	gboolean changed = FALSE;
 
-	g_return_if_fail (s != NULL);
-	g_return_if_fail (key != NULL);
+	g_return_val_if_fail (s, FALSE);
+	g_return_val_if_fail (key, FALSE);
 
 	nm_assert (_shell_is_name (key, -1));
 
-	last = NULL;
-	current = s->lineList;
-	while ((current = (GList *) shlist_find (current, key))) {
-		if (last) {
-			/* if we find multiple entries for the same key, we can
-			 * delete all but the last. */
-			line_free (last->data);
-			s->lineList = g_list_delete_link (s->lineList, last);
-			s->modified = TRUE;
+	line = NULL;
+	c_list_for_each (current, &s->lst_head) {
+		l = c_list_entry (current, shvarLine, lst);
+		if (l->key && nm_streq (l->key, key)) {
+			if (line) {
+				/* if we find multiple entries for the same key, we can
+				 * delete all but the last. */
+				line_free (line);
+				changed = TRUE;
+			}
+			line = l;
 		}
-		last = current;
-		current = current->next;
 	}
 
 	if (!value) {
-		if (last) {
-			shvarLine *line = last->data;
-
-			if (nm_clear_g_free (&line->line))
-				s->modified = TRUE;
+		if (line) {
+			if (nm_clear_g_free (&line->line)) {
+				changed = TRUE;
+			}
 		}
 	} else {
-		if (!last) {
-			s->lineList = g_list_append (s->lineList, line_new_build (key, value));
-			s->modified = TRUE;
+		if (!line) {
+			c_list_link_tail (&s->lst_head, &line_new_build (key, value)->lst);
+			changed = TRUE;
 		} else {
-			if (line_set (last->data, value))
-				s->modified = TRUE;
+			if (line_set (line, value))
+				changed = TRUE;
 		}
 	}
+
+	if (changed)
+		s->modified = TRUE;
+	return changed;
 }
 
 /* Set the variable <key> equal to the value <value>.
@@ -1155,51 +1239,48 @@ svSetValue (shvarFile *s, const char *key, const char *value)
  * the key=value pair after that line.  Otherwise, append the pair
  * to the bottom of the file.
  */
-void
+gboolean
 svSetValueStr (shvarFile *s, const char *key, const char *value)
 {
-	svSetValue (s, key, value && value[0] ? value : NULL);
+	return svSetValue (s, key, value && value[0] ? value : NULL);
 }
 
-void
+gboolean
 svSetValueInt64 (shvarFile *s, const char *key, gint64 value)
 {
 	char buf[NM_DECIMAL_STR_MAX (value)];
 
-	svSetValue (s, key, nm_sprintf_buf (buf, "%"G_GINT64_FORMAT, value));
+	return svSetValue (s, key, nm_sprintf_buf (buf, "%"G_GINT64_FORMAT, value));
 }
 
-void
-svSetValueBoolean (shvarFile *s, const char *key, gboolean value)
+gboolean
+svSetValueInt64_cond (shvarFile *s, const char *key, gboolean do_set, gint64 value)
 {
-	svSetValue (s, key, value ? "yes" : "no");
+	if (do_set)
+		return svSetValueInt64 (s, key, value);
+	else
+		return svUnsetValue (s, key);
 }
 
-void
-svUnsetValue (shvarFile *s, const char *key)
+gboolean
+svSetValueBoolean (shvarFile *s, const char *key, gboolean value)
 {
-	svSetValue (s, key, NULL);
+	return svSetValue (s, key, value ? "yes" : "no");
 }
 
-void
-svUnsetValuesWithPrefix (shvarFile *s, const char *prefix)
+gboolean
+svSetValueEnum (shvarFile *s, const char *key, GType gtype, int value)
 {
-	GList *current;
-
-	g_return_if_fail (s);
-	g_return_if_fail (prefix);
+	gs_free char *v = NULL;
 
-	for (current = s->lineList; current; current = current->next) {
-		shvarLine *line = current->data;
+	v = _nm_utils_enum_to_str_full (gtype, value, " ");
+	return svSetValueStr (s, key, v);
+}
 
-		ASSERT_shvarLine (line);
-		if (   line->key
-		    && g_str_has_prefix (line->key, prefix)) {
-			if (nm_clear_g_free (&line->line))
-				s->modified = TRUE;
-		}
-		ASSERT_shvarLine (line);
-	}
+gboolean
+svUnsetValue (shvarFile *s, const char *key)
+{
+	return svSetValue (s, key, NULL);
 }
 
 /*****************************************************************************/
@@ -1215,7 +1296,7 @@ svWriteFile (shvarFile *s, int mode, GError **error)
 {
 	FILE *f;
 	int tmpfd;
-	const GList *current;
+	CList *current;
 
 	if (s->modified) {
 		if (s->fd == -1)
@@ -1248,8 +1329,8 @@ svWriteFile (shvarFile *s, int mode, GError **error)
 		}
 		f = fdopen (tmpfd, "w");
 		fseek (f, 0, SEEK_SET);
-		for (current = s->lineList; current; current = current->next) {
-			const shvarLine *line = current->data;
+		c_list_for_each (current, &s->lst_head) {
+			const shvarLine *line = c_list_entry (current, shvarLine, lst);
 			const char *str;
 			char *s_tmp;
 			gboolean valid_value;
@@ -1288,11 +1369,13 @@ svWriteFile (shvarFile *s, int mode, GError **error)
 void
 svCloseFile (shvarFile *s)
 {
+	CList *current, *safe;
+
 	g_return_if_fail (s != NULL);
 
-	if (s->fd != -1)
-		close (s->fd);
+	nm_close (s->fd);
 	g_free (s->fileName);
-	g_list_free_full (s->lineList, (GDestroyNotify) line_free);
+	c_list_for_each_safe (current, safe, &s->lst_head)
+		line_free (c_list_entry (current, shvarLine, lst));
 	g_slice_free (shvarFile, s);
 }
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index a13920a1..c48bbfd3 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -35,8 +35,8 @@ typedef struct _shvarFile shvarFile;
 
 const char *svFileGetName (const shvarFile *s);
 
-void svFileSetName_test_only (shvarFile *s, const char *fileName);
-void svFileSetModified_test_only (shvarFile *s);
+void _nmtst_svFileSetName (shvarFile *s, const char *fileName);
+void _nmtst_svFileSetModified (shvarFile *s);
 
 /* Create the file <name>, return a shvarFile (never fails) */
 shvarFile *svCreateFile (const char *name);
@@ -68,19 +68,32 @@ gint svGetValueBoolean (shvarFile *s, const char *key, gint def);
 
 gint64 svGetValueInt64 (shvarFile *s, const char *key, guint base, gint64 min, gint64 max, gint64 fallback);
 
+gboolean svGetValueEnum (shvarFile *s, const char *key,
+                         GType gtype, int *out_value,
+                         GError **error);
+
 /* Set the variable <key> equal to the value <value>.
  * If <key> does not exist, and the <current> pointer is set, append
  * the key=value pair after that line.  Otherwise, prepend the pair
  * to the top of the file.
  */
-void svSetValue (shvarFile *s, const char *key, const char *value);
-void svSetValueStr (shvarFile *s, const char *key, const char *value);
-void svSetValueBoolean (shvarFile *s, const char *key, gboolean value);
-void svSetValueInt64 (shvarFile *s, const char *key, gint64 value);
-
-void svUnsetValue (shvarFile *s, const char *key);
-
-void svUnsetValuesWithPrefix (shvarFile *s, const char *prefix);
+gboolean svSetValue (shvarFile *s, const char *key, const char *value);
+gboolean svSetValueStr (shvarFile *s, const char *key, const char *value);
+gboolean svSetValueBoolean (shvarFile *s, const char *key, gboolean value);
+gboolean svSetValueInt64 (shvarFile *s, const char *key, gint64 value);
+gboolean svSetValueInt64_cond (shvarFile *s, const char *key, gboolean do_set, gint64 value);
+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_USER                    = (1LL << 3),
+} SvKeyType;
+
+gboolean svUnsetAll (shvarFile *s, SvKeyType match_key_type);
 
 /* Write the current contents iff modified.  Returns FALSE on error
  * and TRUE on success.  Do not write if no values have been modified.
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
index 3956003d..854d2490 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
@@ -1,4 +1,3 @@
-DEVICE=bond0
 BONDING_OPTS=mode=balance-rr
 TYPE=Bond
 BONDING_MASTER=yes
@@ -13,4 +12,5 @@ IPV4_FAILURE_FATAL=no
 IPV6INIT=no
 NAME="Test Write Bond Main"
 UUID=${UUID}
+DEVICE=bond0
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
index 60091b7b..793713ea 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
@@ -1,6 +1,5 @@
 VLAN=yes
 TYPE=Vlan
-DEVICE=vlan43
 PHYSDEV=eth9
 VLAN_ID=43
 REORDER_HDR=yes
@@ -19,4 +18,5 @@ IPV4_FAILURE_FATAL=no
 IPV6INIT=no
 NAME="Vlan test-vlan-interface"
 UUID=${UUID}
+DEVICE=vlan43
 ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
index 367d0ddb..87980dc4 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected
@@ -1,5 +1,4 @@
 VLAN=yes
-DEVICE=enp31s0f1-142
 PHYSDEV=enp31s0f1
 VLAN_ID=142
 REORDER_HDR=yes
@@ -7,6 +6,7 @@ GVRP=no
 MVRP=no
 NAME=team-slave-enp31s0f1-142
 UUID=74f435bb-ede4-415a-9d48-f580b60eba04
+DEVICE=enp31s0f1-142
 ONBOOT=no
 TEAM_MASTER=team142
 DEVICETYPE=TeamPort
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
index 1d31b5ba..2bc987c2 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bridge-main
@@ -4,5 +4,5 @@ TYPE=Bridge
 BOOTPROTO=dhcp
 STP=on
 DELAY=2
-BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0"
+BRIDGING_OPTS="priority=32744 hello_time=7 max_age=39 ageing_time=235352 multicast_snooping=0 group_fwd_mask=24"
 MACADDR=00:16:41:11:22:33
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw
new file mode 100644
index 00000000..181ffbef
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-802-1x-password-raw
@@ -0,0 +1,13 @@
+# Intel Corporation 82540EP Gigabit Ethernet Controller (Mobile)
+TYPE=Ethernet
+DEVICE=eth0
+HWADDR=00:11:22:33:44:ee
+BOOTPROTO=dhcp
+ONBOOT=yes
+NM_CONTROLLED=yes
+KEY_MGMT=IEEE8021X
+IEEE_8021X_EAP_METHODS=TTLS
+IEEE_8021X_IDENTITY="Bill Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_INNER_AUTH_METHODS=EAP-GTC
+IEEE_8021X_PASSWORD_RAW=0408151623420001
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy b/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
index 3f02032a..faa247d8 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/route-test-wired-static-routes-legacy
@@ -5,3 +5,4 @@
 	via 8.8.8.8 to 32.42.52.62
  43.53.0.0/16 metric 3 via 7.7.7.7 dev eth2 cwnd 14 mtu lock 9000  initrwnd 20 window lock 10000 initcwnd lock 42 src 1.2.3.4
 
+7.7.7.8/32 via (null) metric 18
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 496a164b..52dbd932 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -232,7 +232,7 @@ _assert_expected_content (NMConnection *connection, const char *filename, const
 		g_assert (_ifcfg_dir && _ifcfg_dir[0]); \
 		g_assert (_filename && _filename[0]); \
 		\
-		_success = writer_update_connection (_connection, _ifcfg_dir, _filename, _out_reread, _out_reread_same, &_error); \
+		_success = nms_ifcfg_rh_writer_write_connection (_connection, _ifcfg_dir, _filename, NULL, _out_reread, _out_reread_same, &_error); \
 		nmtst_assert_success (_success, _error); \
 		_assert_expected_content (_connection, _filename, _expected); \
 	} G_STMT_END
@@ -260,8 +260,8 @@ _connection_from_file (const char *filename,
 
 	g_assert (!out_unhandled || !*out_unhandled);
 
-	connection = connection_from_file_test (filename, network_file, test_type,
-	                                        out_unhandled ?: &unhandled_fallback, &error);
+	connection = nmtst_connection_from_file (filename, network_file, test_type,
+	                                         out_unhandled ?: &unhandled_fallback, &error);
 	g_assert_no_error (error);
 	g_assert (!unhandled_fallback);
 
@@ -282,7 +282,7 @@ _connection_from_file_fail (const char *filename,
 	GError *local = NULL;
 	char *unhandled = NULL;
 
-	connection = connection_from_file_test (filename, network_file, test_type, &unhandled, &local);
+	connection = nmtst_connection_from_file (filename, network_file, test_type, &unhandled, &local);
 
 	g_assert (!connection);
 	g_assert (local);
@@ -310,12 +310,13 @@ _writer_new_connection_reread (NMConnection *connection,
 
 	con_verified = nmtst_connection_duplicate_and_normalize (connection);
 
-	success = writer_new_connection (con_verified,
-	                                 ifcfg_dir,
-	                                 &filename,
-	                                 reread,
-	                                 out_reread_same,
-	                                 &error);
+	success = nms_ifcfg_rh_writer_write_connection (con_verified,
+	                                                ifcfg_dir,
+	                                                NULL,
+	                                                &filename,
+	                                                reread,
+	                                                out_reread_same,
+	                                                &error);
 	nmtst_assert_success (success, error);
 	g_assert (filename && filename[0]);
 
@@ -384,12 +385,13 @@ _writer_new_connection_fail (NMConnection *connection,
 
 	connection_normalized = nmtst_connection_duplicate_and_normalize (connection);
 
-	success = writer_new_connection (connection_normalized,
-	                                 ifcfg_dir,
-	                                 &filename,
-	                                 &reread,
-	                                 NULL,
-	                                 &local);
+	success = nms_ifcfg_rh_writer_write_connection (connection_normalized,
+	                                                ifcfg_dir,
+	                                                NULL,
+	                                                &filename,
+	                                                &reread,
+	                                                NULL,
+	                                                &local);
 	nmtst_assert_no_success (success, local);
 	g_assert (!filename);
 	g_assert (!reread);
@@ -1376,7 +1378,7 @@ test_read_wired_static_routes_legacy (void)
 	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
 
 	/* Routes */
-	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 3);
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 4);
 
 	/* Route #1 */
 	ip4_route = nm_setting_ip_config_get_route (s_ip4, 0);
@@ -1410,6 +1412,13 @@ test_read_wired_static_routes_legacy (void)
 	nmtst_assert_route_attribute_boolean (ip4_route, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, TRUE);
 	nmtst_assert_route_attribute_string (ip4_route, NM_IP_ROUTE_ATTRIBUTE_SRC, "1.2.3.4");
 
+	ip4_route = nm_setting_ip_config_get_route (s_ip4, 3);
+	g_assert (ip4_route != NULL);
+	g_assert_cmpstr (nm_ip_route_get_dest (ip4_route), ==, "7.7.7.8");
+	g_assert_cmpint (nm_ip_route_get_prefix (ip4_route), ==, 32);
+	g_assert_cmpstr (nm_ip_route_get_next_hop (ip4_route), ==, NULL);
+	g_assert_cmpint (nm_ip_route_get_metric (ip4_route), ==, 18);
+
 	g_object_unref (connection);
 }
 
@@ -1954,6 +1963,46 @@ test_read_802_1x_ttls_eapgtc (void)
 }
 
 static void
+test_read_write_802_1x_password_raw (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	nmtst_auto_unlinkfile char *keyfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSetting8021x *s_8021x;
+	GBytes *bytes;
+	gconstpointer data;
+	gsize size;
+
+	/* Test that the 802-1x.password-raw is correctly read and written. */
+
+	connection = _connection_from_file (TEST_IFCFG_DIR"/network-scripts/ifcfg-test-wired-802-1x-password-raw",
+	                                    NULL, TYPE_ETHERNET, NULL);
+
+	/* ===== 802.1x SETTING ===== */
+	s_8021x = nm_connection_get_setting_802_1x (connection);
+	g_assert (s_8021x);
+
+	bytes = nm_setting_802_1x_get_password_raw (s_8021x);
+	g_assert (bytes);
+	data = g_bytes_get_data (bytes, &size);
+	g_assert_cmpmem (data, size, "\x04\x08\x15\x16\x23\x42\x00\x01", 8);
+
+	g_assert_cmpint (nm_setting_802_1x_get_password_raw_flags (s_8021x),
+	                 ==,
+	                 NM_SETTING_SECRET_FLAG_NONE);
+
+	_writer_new_connection (connection,
+	                        TEST_SCRATCH_DIR "/network-scripts/",
+	                        &testfile);
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+	keyfile = utils_get_keys_path (testfile);
+	g_assert (g_file_test (keyfile, G_FILE_TEST_EXISTS));
+
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
+static void
 test_read_wired_aliases_good (gconstpointer test_data)
 {
 	const int N = GPOINTER_TO_INT (test_data);
@@ -4108,7 +4157,6 @@ test_write_wired_static (void)
 
 	route6 = nm_ip_route_new (AF_INET6, "::", 128, "2222:aaaa::9999", 1, &error);
 	g_assert_no_error (error);
-	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_TOS, g_variant_new_byte (0xb8));
 	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32 (100));
 	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32 (1280));
 	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, g_variant_new_boolean (TRUE));
@@ -4154,6 +4202,171 @@ test_write_wired_static (void)
 }
 
 static void
+test_write_wired_static_with_generic (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	nmtst_auto_unlinkfile char *route6file = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWired *s_wired;
+	NMSettingIPConfig *s_ip4, *reread_s_ip4;
+	NMSettingIPConfig *s_ip6, *reread_s_ip6;
+	NMIPAddress *addr;
+	NMIPAddress *addr6;
+	NMIPRoute *route6;
+	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 Wired Static",
+	              NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_a (),
+	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
+	              NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES, 1,
+	              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));
+
+	g_object_set (s_wired,
+	              NM_SETTING_WIRED_MAC_ADDRESS, "31:33:33:37:be:cd",
+	              NM_SETTING_WIRED_MTU, (guint32) 1492,
+	              NULL);
+
+	/* 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_MAY_FAIL, TRUE,
+	              NM_SETTING_IP_CONFIG_GATEWAY, "1.1.1.1",
+	              NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64) 204,
+	              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);
+
+	addr = nm_ip_address_new (AF_INET, "1.1.1.5", 24, &error);
+	g_assert_no_error (error);
+	nm_setting_ip_config_add_address (s_ip4, addr);
+	nm_ip_address_unref (addr);
+
+	nm_setting_ip_config_add_dns (s_ip4, "4.2.2.1");
+	nm_setting_ip_config_add_dns (s_ip4, "4.2.2.2");
+
+	nm_setting_ip_config_add_dns_search (s_ip4, "foobar.com");
+	nm_setting_ip_config_add_dns_search (s_ip4, "lab.foobar.com");
+
+	/* 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_MANUAL,
+	              NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE,
+	              NM_SETTING_IP_CONFIG_ROUTE_METRIC, (gint64) 206,
+	              NULL);
+
+	/* Add addresses */
+	addr6 = nm_ip_address_new (AF_INET6, "1003:1234:abcd::1", 11, &error);
+	g_assert_no_error (error);
+	nm_setting_ip_config_add_address (s_ip6, addr6);
+	nm_ip_address_unref (addr6);
+
+	addr6 = nm_ip_address_new (AF_INET6, "2003:1234:abcd::2", 22, &error);
+	g_assert_no_error (error);
+	nm_setting_ip_config_add_address (s_ip6, addr6);
+	nm_ip_address_unref (addr6);
+
+	addr6 = nm_ip_address_new (AF_INET6, "3003:1234:abcd::3", 33, &error);
+	g_assert_no_error (error);
+	nm_setting_ip_config_add_address (s_ip6, addr6);
+	nm_ip_address_unref (addr6);
+
+	/* Add routes */
+	route6 = nm_ip_route_new (AF_INET6,
+	                          "2222:aaaa:bbbb:cccc::", 64,
+	                          "2222:aaaa:bbbb:cccc:dddd:eeee:5555:6666", 99, &error);
+	g_assert_no_error (error);
+	nm_setting_ip_config_add_route (s_ip6, route6);
+	nm_ip_route_unref (route6);
+
+	route6 = nm_ip_route_new (AF_INET6, "::", 128, "2222:aaaa::9999", 1, &error);
+	g_assert_no_error (error);
+	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_CWND, g_variant_new_uint32 (100));
+	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32 (1280));
+	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, g_variant_new_boolean (TRUE));
+	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_FROM, g_variant_new_string ("2222::bbbb/32"));
+	nm_ip_route_set_attribute (route6, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string ("::42"));
+	nm_setting_ip_config_add_route (s_ip6, route6);
+	nm_ip_route_unref (route6);
+
+	/* DNS servers */
+	nm_setting_ip_config_add_dns (s_ip6, "fade:0102:0103::face");
+	nm_setting_ip_config_add_dns (s_ip6, "cafe:ffff:eeee:dddd:cccc:bbbb:aaaa:feed");
+
+	/* DNS domains */
+	nm_setting_ip_config_add_dns_search (s_ip6, "foobar6.com");
+	nm_setting_ip_config_add_dns_search (s_ip6, "lab6.foobar.com");
+
+	nm_connection_add_setting (connection, nm_setting_generic_new ());
+
+	nmtst_assert_connection_verifies (connection);
+
+	_writer_new_connection_FIXME (connection,
+	                              TEST_SCRATCH_DIR "/network-scripts/",
+	                              &testfile);
+	route6file = utils_get_route6_path (testfile);
+
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+
+	/* FIXME: currently DNS domains from IPv6 setting are stored in 'DOMAIN' key in ifcfg-file
+	 * However after re-reading they are dropped into IPv4 setting.
+	 * So, in order to comparison succeeded, move DNS domains back to IPv6 setting.
+	 */
+	reread_s_ip4 = nm_connection_get_setting_ip4_config (reread);
+	reread_s_ip6 = nm_connection_get_setting_ip6_config (reread);
+	nm_setting_ip_config_add_dns_search (reread_s_ip6, nm_setting_ip_config_get_dns_search (reread_s_ip4, 2));
+	nm_setting_ip_config_add_dns_search (reread_s_ip6, nm_setting_ip_config_get_dns_search (reread_s_ip4, 3));
+	nm_setting_ip_config_remove_dns_search (reread_s_ip4, 3);
+	nm_setting_ip_config_remove_dns_search (reread_s_ip4, 2);
+
+	g_assert_cmpint (nm_setting_ip_config_get_route_metric (reread_s_ip4), ==, 204);
+	g_assert_cmpint (nm_setting_ip_config_get_route_metric (reread_s_ip6), ==, 206);
+
+	nm_connection_add_setting (connection, nm_setting_proxy_new ());
+
+	{
+		gs_unref_hashtable GHashTable *diffs = NULL;
+
+		g_assert (!nm_connection_diff (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT, &diffs));
+		g_assert (diffs);
+		g_assert (g_hash_table_size (diffs) == 1);
+		g_assert (g_hash_table_lookup (diffs, "generic"));
+		g_assert (!nm_connection_compare (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+	}
+	g_assert (!nm_connection_get_setting (reread, NM_TYPE_SETTING_GENERIC));
+	nm_connection_add_setting (reread, nm_setting_generic_new ());
+	{
+		gs_unref_hashtable GHashTable *diffs = NULL;
+
+		g_assert (nm_connection_diff (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT, &diffs));
+		g_assert (!diffs);
+		g_assert (nm_connection_compare (connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
+	}
+}
+
+static void
 test_write_wired_dhcp (void)
 {
 	nmtst_auto_unlinkfile char *testfile = NULL;
@@ -5867,6 +6080,7 @@ test_write_wifi_wpa_psk (gconstpointer test_data)
 	g_object_set (s_wsec,
 	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk",
 	              NM_SETTING_WIRELESS_SECURITY_PSK, args.psk,
+	              NM_SETTING_WIRELESS_SECURITY_PMF, (int) NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED,
 	              NULL);
 
 	if (GPOINTER_TO_INT (args.wep_group_p)) {
@@ -7172,6 +7386,7 @@ test_read_bridge_main (void)
 	g_assert_cmpuint (nm_setting_bridge_get_hello_time (s_bridge), ==, 7);
 	g_assert_cmpuint (nm_setting_bridge_get_max_age (s_bridge), ==, 39);
 	g_assert_cmpuint (nm_setting_bridge_get_ageing_time (s_bridge), ==, 235352);
+	g_assert_cmpuint (nm_setting_bridge_get_group_forward_mask (s_bridge), ==, 24);
 	g_assert (!nm_setting_bridge_get_multicast_snooping (s_bridge));
 
 	/* MAC address */
@@ -7217,6 +7432,7 @@ test_write_bridge_main (void)
 
 	g_object_set (s_bridge,
 	              NM_SETTING_BRIDGE_MAC_ADDRESS, mac,
+	              NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, 19008,
 	              NULL);
 
 	/* IP4 setting */
@@ -9147,8 +9363,8 @@ test_write_unknown (gconstpointer test_data)
 
 	sv = _svOpenFile (testfile);
 
-	svFileSetName_test_only (sv, filename_tmp_1);
-	svFileSetModified_test_only (sv);
+	_nmtst_svFileSetName (sv, filename_tmp_1);
+	_nmtst_svFileSetModified (sv);
 
 	if (g_str_has_suffix (testfile, "ifcfg-test-write-unknown-4")) {
 		_svGetValue_check (sv, "NAME", "l4x");
@@ -9429,6 +9645,7 @@ int main (int argc, char **argv)
 
 	g_test_add_func (TPATH "802-1x/subj-matches", test_read_write_802_1X_subj_matches);
 	g_test_add_func (TPATH "802-1x/ttls-eapgtc", test_read_802_1x_ttls_eapgtc);
+	g_test_add_func (TPATH "802-1x/password_raw", test_read_write_802_1x_password_raw);
 	g_test_add_data_func (TPATH "wired/read/aliases/good/0", GINT_TO_POINTER (0), test_read_wired_aliases_good);
 	g_test_add_data_func (TPATH "wired/read/aliases/good/3", GINT_TO_POINTER (3), test_read_wired_aliases_good);
 	g_test_add_func (TPATH "wired/read/aliases/bad1", test_read_wired_aliases_bad_1);
@@ -9492,6 +9709,7 @@ int main (int argc, char **argv)
 	g_test_add_func (TPATH "wired/read/unkwnown-ethtool-opt", test_read_wired_unknown_ethtool_opt);
 
 	g_test_add_func (TPATH "wired/write/static", test_write_wired_static);
+	g_test_add_func (TPATH "wired/write/static-with-generic", test_write_wired_static_with_generic);
 	g_test_add_func (TPATH "wired/write/static-ip6-only", test_write_wired_static_ip6_only);
 	g_test_add_func (TPATH "wired/write-static-routes", test_write_wired_static_routes);
 	g_test_add_func (TPATH "wired/read-write-static-routes-legacy", test_read_write_static_routes_legacy);
diff --git a/src/settings/plugins/ifnet/nms-ifnet-connection-parser.c b/src/settings/plugins/ifnet/nms-ifnet-connection-parser.c
index c5129fea..ed0a757f 100644
--- a/src/settings/plugins/ifnet/nms-ifnet-connection-parser.c
+++ b/src/settings/plugins/ifnet/nms-ifnet-connection-parser.c
@@ -31,7 +31,7 @@
 #include "settings/nm-settings-plugin.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
-#include "nm-setting-metadata.h"
+#include "nm-meta-setting.h"
 
 #include "nms-ifnet-net-utils.h"
 #include "nms-ifnet-wpa-parser.h"
diff --git a/src/settings/plugins/ifnet/nms-ifnet-connection.c b/src/settings/plugins/ifnet/nms-ifnet-connection.c
index ba87d46c..5dbb124c 100644
--- a/src/settings/plugins/ifnet/nms-ifnet-connection.c
+++ b/src/settings/plugins/ifnet/nms-ifnet-connection.c
@@ -74,89 +74,81 @@ nm_ifnet_connection_get_conn_name (NMIfnetConnection *connection)
 	return NM_IFNET_CONNECTION_GET_PRIVATE (connection)->conn_name;
 }
 
-static void
+static gboolean
 commit_changes (NMSettingsConnection *connection,
+                NMConnection *new_connection,
                 NMSettingsConnectionCommitReason commit_reason,
-                NMSettingsConnectionCommitFunc callback,
-                gpointer user_data)
+                NMConnection **out_reread_connection,
+                char **out_logmsg_change,
+                GError **error)
 {
-	GError *error = NULL;
 	NMIfnetConnectionPrivate *priv = NM_IFNET_CONNECTION_GET_PRIVATE ((NMIfnetConnection *) connection);
-	gchar *new_name = NULL;
+	char *new_name = NULL;
 	gboolean success = FALSE;
+	gboolean added = FALSE;
+
+	nm_assert (out_reread_connection && !*out_reread_connection);
+	nm_assert (!out_logmsg_change || !*out_logmsg_change);
 
 	g_signal_emit (connection, signals[IFNET_CANCEL_MONITORS], 0);
 
 	if (priv->conn_name) {
-		/* Existing connection; update it */
 		success = ifnet_update_parsers_by_connection (NM_CONNECTION (connection),
 		                                              priv->conn_name,
 		                                              CONF_NET_FILE,
 		                                              WPA_SUPPLICANT_CONF,
 		                                              &new_name,
 		                                              NULL,
-		                                              &error);
+		                                              error);
 	} else {
-		/* New connection, add it */
+		added = TRUE;
 		success = ifnet_add_new_connection (NM_CONNECTION (connection),
 		                                    CONF_NET_FILE,
 		                                    WPA_SUPPLICANT_CONF,
 		                                    &new_name,
 		                                    NULL,
-		                                    &error);
-		if (success)
-			reload_parsers ();
+		                                    error);
 	}
 
+	g_assert (!!success == (new_name != NULL));
 	if (success) {
-		/* update connection name */
-		g_assert (new_name);
 		g_free (priv->conn_name);
 		priv->conn_name = new_name;
-
-		NM_SETTINGS_CONNECTION_CLASS (nm_ifnet_connection_parent_class)->commit_changes (connection, commit_reason, callback, user_data);
-		nm_log_info (LOGD_SETTINGS, "Successfully updated %s", priv->conn_name);
-	} else {
-		nm_log_warn (LOGD_SETTINGS, "Failed to update %s",
-		             priv->conn_name ? priv->conn_name :
-		             nm_connection_get_id (NM_CONNECTION (connection)));
-		reload_parsers ();
-		callback (connection, error, user_data);
-		g_error_free (error);
 	}
 
+	reload_parsers ();
+
 	g_signal_emit (connection, signals[IFNET_SETUP_MONITORS], 0);
+
+	if (success) {
+		NM_SET_OUT (out_logmsg_change,
+		            g_strdup_printf ("ifcfg-rh: %s %s",
+		                             added ? "persist" : "updated",
+		                             new_name));
+	}
+	return success;
 }
 
-static void
-do_delete (NMSettingsConnection *connection,
-           NMSettingsConnectionDeleteFunc callback,
-           gpointer user_data)
+static gboolean
+delete (NMSettingsConnection *connection,
+        GError **error)
 {
-	GError *error = NULL;
 	NMIfnetConnectionPrivate *priv = NM_IFNET_CONNECTION_GET_PRIVATE ((NMIfnetConnection *) connection);
 
-	g_signal_emit (connection, signals[IFNET_CANCEL_MONITORS], 0);
-
 	/* Only connections which exist in /etc/conf.d/net will have a conn_name */
 	if (priv->conn_name) {
+		g_signal_emit (connection, signals[IFNET_CANCEL_MONITORS], 0);
+
 		if (!ifnet_delete_connection_in_parsers (priv->conn_name, CONF_NET_FILE, WPA_SUPPLICANT_CONF, NULL)) {
 			nm_log_warn (LOGD_SETTINGS, "Failed to delete %s", priv->conn_name);
 			reload_parsers ();
-			callback (connection, error, user_data);
-			g_error_free (error);
-			g_signal_emit (connection, signals[IFNET_SETUP_MONITORS], 0);
-			return;
+			/* let's not return an error. */
 		}
-	}
-
-	NM_SETTINGS_CONNECTION_CLASS (nm_ifnet_connection_parent_class)->delete (connection, callback, user_data);
 
-	g_signal_emit (connection, signals[IFNET_SETUP_MONITORS], 0);
+		g_signal_emit (connection, signals[IFNET_SETUP_MONITORS], 0);
+	}
 
-	nm_log_info (LOGD_SETTINGS, "Successfully deleted %s",
-	             priv->conn_name ? priv->conn_name :
-	             nm_connection_get_id (NM_CONNECTION (connection)));
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -222,7 +214,7 @@ nm_ifnet_connection_class_init (NMIfnetConnectionClass * ifnet_connection_class)
 
 	object_class->finalize = finalize;
 
-	settings_class->delete = do_delete;
+	settings_class->delete = delete;
 	settings_class->commit_changes = commit_changes;
 
 	signals[IFNET_SETUP_MONITORS] =
diff --git a/src/settings/plugins/ifnet/nms-ifnet-net-parser.c b/src/settings/plugins/ifnet/nms-ifnet-net-parser.c
index 0007f9cd..d3e47219 100644
--- a/src/settings/plugins/ifnet/nms-ifnet-net-parser.c
+++ b/src/settings/plugins/ifnet/nms-ifnet-net-parser.c
@@ -58,7 +58,7 @@ add_new_connection_config (const gchar * type, const gchar * name)
 	/* Return existing connection */
 	if ((new_conn = g_hash_table_lookup (conn_table, name)) != NULL)
 		return new_conn;
-	new_conn = g_hash_table_new (g_str_hash, g_str_equal);
+	new_conn = g_hash_table_new (nm_str_hash, g_str_equal);
 	new_name = g_strdup (name);
 	g_hash_table_insert (new_conn, g_strdup ("name"), new_name);
 	g_hash_table_insert (new_conn, g_strdup ("type"), g_strdup (type));
@@ -302,8 +302,8 @@ ifnet_init (gchar * config_file)
 
 	net_parser_data_changed = FALSE;
 
-	conn_table = g_hash_table_new (g_str_hash, g_str_equal);
-	global_settings_table = g_hash_table_new (g_str_hash, g_str_equal);
+	conn_table = g_hash_table_new (nm_str_hash, g_str_equal);
+	global_settings_table = g_hash_table_new (nm_str_hash, g_str_equal);
 	functions_list = NULL;
 
 	if (g_file_test (config_file, G_FILE_TEST_IS_REGULAR))
diff --git a/src/settings/plugins/ifnet/nms-ifnet-plugin.c b/src/settings/plugins/ifnet/nms-ifnet-plugin.c
index 5a6a8ce8..998b04b4 100644
--- a/src/settings/plugins/ifnet/nms-ifnet-plugin.c
+++ b/src/settings/plugins/ifnet/nms-ifnet-plugin.c
@@ -138,9 +138,9 @@ monitor_file_changes (const char *filename,
 		info->callback = callback;
 		info->user_data = user_data;
 		g_object_weak_ref (G_OBJECT (monitor), (GWeakNotify) g_free,
-				   info);
+		                   info);
 		g_signal_connect (monitor, "changed", G_CALLBACK (file_changed),
-				  info);
+		                  info);
 	} else {
 		nm_log_warn (LOGD_SETTINGS, "Monitoring %s failed, error: %s", filename,
 		             error == NULL ? "nothing" : (*error)->message);
@@ -150,34 +150,38 @@ monitor_file_changes (const char *filename,
 }
 
 static void
-setup_monitors (NMIfnetConnection * connection, gpointer user_data)
+setup_monitors (NMIfnetConnection *connection, gpointer user_data)
 {
 	SettingsPluginIfnet *self = SETTINGS_PLUGIN_IFNET (user_data);
 	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE (self);
 
-	if (nm_config_get_monitor_connection_files (nm_config_get ())) {
-		priv->net_monitor =
-			monitor_file_changes (CONF_NET_FILE, (FileChangedFn) reload_connections,
-			                      user_data);
-		priv->wpa_monitor =
-			monitor_file_changes (WPA_SUPPLICANT_CONF, (FileChangedFn) reload_connections,
-			                      user_data);
-	}
+	if (!nm_config_get_monitor_connection_files (nm_config_get ()))
+		return;
+
+	if (priv->net_monitor || priv->wpa_monitor)
+		return;
+
+	priv->net_monitor = monitor_file_changes (CONF_NET_FILE,
+	                                          (FileChangedFn) reload_connections,
+	                                          user_data);
+	priv->wpa_monitor = monitor_file_changes (WPA_SUPPLICANT_CONF,
+	                                          (FileChangedFn) reload_connections,
+	                                          user_data);
 }
 
 static void
-cancel_monitors (NMIfnetConnection * connection, gpointer user_data)
+cancel_monitors (NMIfnetConnection *connection, gpointer user_data)
 {
 	SettingsPluginIfnet *self = SETTINGS_PLUGIN_IFNET (user_data);
 	SettingsPluginIfnetPrivate *priv = SETTINGS_PLUGIN_IFNET_GET_PRIVATE (self);
 
 	if (priv->net_monitor) {
 		g_file_monitor_cancel (priv->net_monitor);
-		g_object_unref (priv->net_monitor);
+		g_clear_object (&priv->net_monitor);
 	}
 	if (priv->wpa_monitor) {
 		g_file_monitor_cancel (priv->wpa_monitor);
-		g_object_unref (priv->wpa_monitor);
+		g_clear_object (&priv->wpa_monitor);
 	}
 }
 
@@ -226,7 +230,7 @@ reload_connections (NMSettingsPlugin *config)
 	                                                 NM_CONFIG_KEYFILE_GROUP_IFNET, NM_CONFIG_KEYFILE_KEY_IFNET_AUTO_REFRESH,
 	                                                 FALSE);
 
-	new_connections = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+	new_connections = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_object_unref);
 
 	/* Reread on-disk data and refresh in-memory connections from it */
 	conn_names = ifnet_get_connection_names ();
@@ -321,11 +325,11 @@ add_connection (NMSettingsPlugin *config,
 	 * asked to write it to disk.
 	 */
 	if (!ifnet_can_write_connection (source, error))
-		return NULL;
+		goto out;
 
 	if (save_to_disk) {
 		if (!ifnet_add_new_connection (source, CONF_NET_FILE, WPA_SUPPLICANT_CONF, NULL, NULL, error))
-			return NULL;
+			goto out;
 		reload_connections (config);
 		new = g_hash_table_lookup (priv->connections, nm_connection_get_uuid (source));
 	} else {
@@ -337,6 +341,11 @@ add_connection (NMSettingsPlugin *config,
 		}
 	}
 
+out:
+	if (!new && error && !*error) {
+		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		                     "The ifnet plugin cannot add the connection (unknown error).");
+	}
 	return (NMSettingsConnection *) new;
 }
 
@@ -439,7 +448,7 @@ init (NMSettingsPlugin *config)
 
 	nm_log_info (LOGD_SETTINGS, "Initializing!");
 
-	priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 	priv->unmanaged_well_known = !is_managed_plugin ();
 	nm_log_info (LOGD_SETTINGS, "management mode: %s",
 	             priv->unmanaged_well_known ? "unmanaged" : "managed");
diff --git a/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c b/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c
index 61e4da7c..2b62e886 100644
--- a/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c
+++ b/src/settings/plugins/ifnet/nms-ifnet-wpa-parser.c
@@ -26,6 +26,7 @@
 #include <string.h>
 #include <stdlib.h>
 
+#include "nm-utils/nm-hash-utils.h"
 #include "settings/nm-settings-plugin.h"
 
 #include "nms-ifnet-net-parser.h"
@@ -270,8 +271,8 @@ wpa_parser_init (const char *wpa_supplicant_conf)
 	gboolean complete = FALSE;
 
 	wpa_parser_data_changed = FALSE;
-	wsec_table = g_hash_table_new (g_str_hash, g_str_equal);
-	wsec_global_table = g_hash_table_new (g_str_hash, g_str_equal);
+	wsec_table = g_hash_table_new (nm_str_hash, g_str_equal);
+	wsec_global_table = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	if (g_file_test (wpa_supplicant_conf, G_FILE_TEST_IS_REGULAR))
 		channel =
@@ -292,7 +293,7 @@ wpa_parser_init (const char *wpa_supplicant_conf)
 				continue;
 			} else {
 				GHashTable *network =
-				    g_hash_table_new (g_str_hash, g_str_equal);
+				    g_hash_table_new (nm_str_hash, g_str_equal);
 
 				do {
 					gchar *quote_start, *quote_end = NULL, *comment;
@@ -512,7 +513,7 @@ wpa_add_security (const char *ssid)
 		return TRUE;
 	else {
 		GHashTable *security =
-		    g_hash_table_new (g_str_hash, g_str_equal);
+		    g_hash_table_new (nm_str_hash, g_str_equal);
 		gchar *ssid_i;
 
 		nm_log_info (LOGD_SETTINGS, "Adding security for %s", ssid);
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 189b6e69..0928772a 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -139,7 +139,10 @@ bind_device_to_connection (SettingsPluginIfupdown *self,
 		g_object_set (s_wifi, NM_SETTING_WIRELESS_MAC_ADDRESS, address, NULL);
 	}
 
-	nm_settings_connection_commit_changes (NM_SETTINGS_CONNECTION (exported), NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE, NULL, NULL);
+	nm_settings_connection_commit_changes (NM_SETTINGS_CONNECTION (exported),
+	                                       NULL,
+	                                       NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
+	                                       NULL);
 }
 
 static void
@@ -338,16 +341,16 @@ init (NMSettingsPlugin *config)
 	const char *block_name;
 	NMIfupdownConnection *connection;
 
-	auto_ifaces = g_hash_table_new (g_str_hash, g_str_equal);
+	auto_ifaces = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	if(!priv->connections)
-		priv->connections = g_hash_table_new (g_str_hash, g_str_equal);
+		priv->connections = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	if(!priv->kernel_ifaces)
-		priv->kernel_ifaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _udev_device_unref);
+		priv->kernel_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, _udev_device_unref);
 
 	if(!priv->eni_ifaces)
-		priv->eni_ifaces = g_hash_table_new (g_str_hash, g_str_equal);
+		priv->eni_ifaces = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	nm_log_info (LOGD_SETTINGS, "init!");
 
@@ -409,7 +412,7 @@ init (NMSettingsPlugin *config)
 			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, NULL);
+				nm_settings_connection_delete (NM_SETTINGS_CONNECTION (exported), NULL);
 				g_hash_table_remove (priv->connections, block->name);
 			}
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
index bd07d263..300aa9f7 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-connection.c
@@ -50,29 +50,30 @@ G_DEFINE_TYPE (NMSKeyfileConnection, nms_keyfile_connection, NM_TYPE_SETTINGS_CO
 
 /*****************************************************************************/
 
-static void
+static gboolean
 commit_changes (NMSettingsConnection *connection,
+                NMConnection *new_connection,
                 NMSettingsConnectionCommitReason commit_reason,
-                NMSettingsConnectionCommitFunc callback,
-                gpointer user_data)
+                NMConnection **out_reread_connection,
+                char **out_logmsg_change,
+                GError **error)
 {
-	char *path = NULL;
-	GError *error = NULL;
+	gs_free char *path = NULL;
 	gs_unref_object NMConnection *reread = NULL;
 	gboolean reread_same = FALSE;
 
-	if (!nms_keyfile_writer_connection (NM_CONNECTION (connection),
+	nm_assert (out_reread_connection && !*out_reread_connection);
+	nm_assert (!out_logmsg_change || !*out_logmsg_change);
+
+	if (!nms_keyfile_writer_connection (new_connection ?: NM_CONNECTION (connection),
 	                                    nm_settings_connection_get_filename (connection),
 	                                    NM_FLAGS_ALL (commit_reason,   NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION
 	                                                                 | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED),
 	                                    &path,
 	                                    &reread,
 	                                    &reread_same,
-	                                    &error)) {
-		callback (connection, error, user_data);
-		g_clear_error (&error);
-		return;
-	}
+	                                    error))
+		return FALSE;
 
 	/* Update the filename if it changed */
 	if (   path
@@ -81,52 +82,37 @@ commit_changes (NMSettingsConnection *connection,
 
 		nm_settings_connection_set_filename (connection, path);
 		if (old_path) {
-			nm_log_info (LOGD_SETTINGS, "keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" and rename from \"%s\"",
-			             NMS_KEYFILE_CONNECTION_LOG_ARG (connection),
-			             old_path);
+			NM_SET_OUT (out_logmsg_change,
+			            g_strdup_printf ("keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" and rename from \"%s\"",
+			                             NMS_KEYFILE_CONNECTION_LOG_ARG (connection),
+			                             old_path));
 		} else {
-			nm_log_info (LOGD_SETTINGS, "keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" and persist connection",
-			             NMS_KEYFILE_CONNECTION_LOG_ARG (connection));
+			NM_SET_OUT (out_logmsg_change,
+			            g_strdup_printf ("keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" and persist connection",
+			                             NMS_KEYFILE_CONNECTION_LOG_ARG (connection)));
 		}
 	} else {
-		nm_log_info (LOGD_SETTINGS, "keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT,
-		             NMS_KEYFILE_CONNECTION_LOG_ARG (connection));
-	}
-
-	if (reread && !reread_same) {
-		gs_free_error GError *local = NULL;
-
-		if (!nm_settings_connection_replace_settings (connection, reread, FALSE, "update-during-write", &local)) {
-			nm_log_warn (LOGD_SETTINGS, "keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" after persisting connection failed: %s",
-			             NMS_KEYFILE_CONNECTION_LOG_ARG (connection), local->message);
-		} else {
-			nm_log_info (LOGD_SETTINGS, "keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT" after persisting connection",
-			             NMS_KEYFILE_CONNECTION_LOG_ARG (connection));
-		}
+		NM_SET_OUT (out_logmsg_change,
+		            g_strdup_printf ("keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT,
+		                             NMS_KEYFILE_CONNECTION_LOG_ARG (connection)));
 	}
 
-	g_free (path);
+	if (reread && !reread_same)
+		*out_reread_connection = g_steal_pointer (&reread);
 
-	NM_SETTINGS_CONNECTION_CLASS (nms_keyfile_connection_parent_class)->commit_changes (connection,
-	                                                                                    commit_reason,
-	                                                                                    callback,
-	                                                                                    user_data);
+	return TRUE;
 }
 
-static void
-do_delete (NMSettingsConnection *connection,
-           NMSettingsConnectionDeleteFunc callback,
-           gpointer user_data)
+static gboolean
+delete (NMSettingsConnection *connection,
+        GError **error)
 {
 	const char *path;
 
 	path = nm_settings_connection_get_filename (connection);
 	if (path)
 		g_unlink (path);
-
-	NM_SETTINGS_CONNECTION_CLASS (nms_keyfile_connection_parent_class)->delete (connection,
-	                                                                            callback,
-	                                                                            user_data);
+	return TRUE;
 }
 
 /*****************************************************************************/
@@ -192,5 +178,5 @@ nms_keyfile_connection_class_init (NMSKeyfileConnectionClass *keyfile_connection
 	NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (keyfile_connection_class);
 
 	settings_class->commit_changes = commit_changes;
-	settings_class->delete = do_delete;
+	settings_class->delete = delete;
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index 4af80142..ee4db320 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -378,7 +378,7 @@ _paths_from_connections (GHashTable *connections)
 {
 	GHashTableIter iter;
 	NMSKeyfileConnection *connection;
-	GHashTable *paths = g_hash_table_new (g_str_hash, g_str_equal);
+	GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
 
 	g_hash_table_iter_init (&iter, connections);
 	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
@@ -588,7 +588,7 @@ nms_keyfile_plugin_init (NMSKeyfilePlugin *plugin)
 	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin);
 
 	priv->config = g_object_ref (nm_config_get ());
-	priv->connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 }
 
 static void
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 92ed2849..270a217e 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -213,6 +213,9 @@ _internal_write_connection (NMConnection *connection,
 	if (!data)
 		return FALSE;
 
+	if (!g_file_test (keyfile_dir, G_FILE_TEST_IS_DIR))
+		(void) g_mkdir_with_parents (keyfile_dir, 0755);
+
 	/* If we have existing file path, use it. Else generate one from
 	 * connection's ID.
 	 */
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile.c
index d9da5317..2584a722 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile.c
@@ -312,11 +312,11 @@ test_read_valid_wired_connection (void)
 	check_ip_route (s_ip4, 3, "1.1.1.3", 13, NULL, -1);
 	check_ip_route (s_ip4, 4, "1.1.1.4", 14, "2.2.2.4", -1);
 	check_ip_route (s_ip4, 5, "1.1.1.5", 15, "2.2.2.5", -1);
-	check_ip_route (s_ip4, 6, "1.1.1.6", 16, "2.2.2.6", -1);
+	check_ip_route (s_ip4, 6, "1.1.1.6", 16, "2.2.2.6", 0);
 	check_ip_route (s_ip4, 7, "1.1.1.7", 17, NULL, -1);
 	check_ip_route (s_ip4, 8, "1.1.1.8", 18, NULL, -1);
-	check_ip_route (s_ip4, 9, "1.1.1.9", 19, NULL, -1);
-	check_ip_route (s_ip4, 10, "1.1.1.10", 20, NULL, -1);
+	check_ip_route (s_ip4, 9, "1.1.1.9", 19, NULL, 0);
+	check_ip_route (s_ip4, 10, "1.1.1.10", 20, NULL, 0);
 	check_ip_route (s_ip4, 11, "1.1.1.11", 21, NULL, 21);
 
 	/* Route attributes */
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index f9a84620..16e7851a 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -31,6 +31,7 @@
 #include "nm-auth-subject.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
+#include "nm-setting-ip4-config.h"
 
 typedef struct {
 	char *value;
@@ -87,11 +88,11 @@ nm_supplicant_config_init (NMSupplicantConfig * self)
 {
 	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
 
-	priv->config = g_hash_table_new_full (g_str_hash, g_str_equal,
+	priv->config = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                      (GDestroyNotify) g_free,
 	                                      (GDestroyNotify) config_option_free);
 
-	priv->blobs = g_hash_table_new_full (g_str_hash, g_str_equal,
+	priv->blobs = g_hash_table_new_full (nm_str_hash, g_str_equal,
 	                                     (GDestroyNotify) g_free,
 	                                     (GDestroyNotify) blob_free);
 
@@ -537,6 +538,56 @@ nm_supplicant_config_add_setting_wireless (NMSupplicantConfig * self,
 	return TRUE;
 }
 
+gboolean
+nm_supplicant_config_add_bgscan (NMSupplicantConfig *self,
+                                 NMConnection *connection,
+                                 GError **error)
+{
+	NMSettingWireless *s_wifi;
+	NMSettingWirelessSecurity *s_wsec;
+	const char *bgscan;
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	g_assert (s_wifi);
+
+	/* Don't scan when a shared connection (either AP or Ad-Hoc) is active;
+	 * it will disrupt connected clients.
+	 */
+	if (NM_IN_STRSET (nm_setting_wireless_get_mode (s_wifi),
+	                  NM_SETTING_WIRELESS_MODE_AP,
+	                  NM_SETTING_WIRELESS_MODE_ADHOC))
+		return TRUE;
+
+	/* Don't scan when the connection is locked to a specifc AP, since
+	 * intra-ESS roaming (which requires periodic scanning) isn't being
+	 * used due to the specific AP lock. (bgo #513820)
+	 */
+	if (nm_setting_wireless_get_bssid (s_wifi))
+		return TRUE;
+
+	/* Default to a very long bgscan interval when signal is OK on the assumption
+	 * that either (a) there aren't multiple APs and we don't need roaming, or
+	 * (b) since EAP/802.1x isn't used and thus there are fewer steps to fail
+	 * during a roam, we can wait longer before scanning for roam candidates.
+	 */
+	bgscan = "simple:30:-80:86400";
+
+	/* If using WPA Enterprise or Dynamic WEP use a shorter bgscan interval on
+	 * the assumption that this is a multi-AP ESS in which we want more reliable
+	 * roaming between APs.  Thus trigger scans when the signal is still somewhat
+	 * OK so we have an up-to-date roam candidate list when the signal gets bad.
+	 */
+	s_wsec = nm_connection_get_setting_wireless_security (connection);
+	if (s_wsec) {
+		if (NM_IN_STRSET (nm_setting_wireless_security_get_key_mgmt (s_wsec),
+		                  "ieee8021x",
+		                  "wpa-eap"))
+			bgscan = "simple:30:-65:300";
+	}
+
+	return nm_supplicant_config_add_option (self, "bgscan", bgscan, -1, FALSE, error);
+}
+
 static gboolean
 add_string_val (NMSupplicantConfig *self,
                 const char *field,
@@ -684,9 +735,10 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
                                                     NMSetting8021x *setting_8021x,
                                                     const char *con_uuid,
                                                     guint32 mtu,
+                                                    NMSettingWirelessSecurityPmf pmf,
                                                     GError **error)
 {
-	const char *key_mgmt, *auth_alg;
+	const char *key_mgmt, *key_mgmt_conf, *auth_alg;
 	const char *psk;
 
 	g_return_val_if_fail (NM_IS_SUPPLICANT_CONFIG (self), FALSE);
@@ -694,8 +746,19 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 	g_return_val_if_fail (con_uuid != NULL, FALSE);
 	g_return_val_if_fail (!error || !*error, FALSE);
 
-	key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting);
-	if (!add_string_val (self, key_mgmt, "key_mgmt", TRUE, NULL, error))
+	key_mgmt = key_mgmt_conf = nm_setting_wireless_security_get_key_mgmt (setting);
+	if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL) {
+		if (nm_streq (key_mgmt_conf, "wpa-psk"))
+			key_mgmt_conf = "wpa-psk wpa-psk-sha256";
+		else if (nm_streq (key_mgmt_conf, "wpa-eap"))
+			key_mgmt_conf = "wpa-eap wpa-eap-sha256";
+	} else if (pmf == NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) {
+		if (nm_streq (key_mgmt_conf, "wpa-psk"))
+			key_mgmt_conf = "wpa-psk-sha256";
+		else if (nm_streq (key_mgmt_conf, "wpa-eap"))
+			key_mgmt_conf = "wpa-eap-sha256";
+	}
+	if (!add_string_val (self, key_mgmt_conf, "key_mgmt", TRUE, NULL, error))
 		return FALSE;
 
 	auth_alg = nm_setting_wireless_security_get_auth_alg (setting);
@@ -750,6 +813,19 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 			return FALSE;
 		if (!ADD_STRING_LIST_VAL (self, setting, wireless_security, group, groups, "group", ' ', TRUE, NULL, error))
 			return FALSE;
+
+		if (   !nm_streq (key_mgmt, "wpa-none")
+		    && NM_IN_SET (pmf,
+		                  NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL,
+		                  NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)) {
+			if (!nm_supplicant_config_add_option (self,
+			                                      "ieee80211w",
+			                                      pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL ? "1" : "2",
+			                                      -1,
+			                                      NULL,
+			                                      error))
+				return FALSE;
+		}
 	}
 
 	/* WEP keys if required */
@@ -811,12 +887,6 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 		}
 
 		if (!strcmp (key_mgmt, "wpa-eap")) {
-			/* If using WPA Enterprise, enable optimized background scanning
-			 * to ensure roaming within an ESS works well.
-			 */
-			if (!nm_supplicant_config_add_option (self, "bgscan", "simple:30:-65:300", -1, NULL, error))
-				return FALSE;
-
 			/* When using WPA-Enterprise, we want to use Proactive Key Caching (also
 			 * called Opportunistic Key Caching) to avoid full EAP exchanges when
 			 * roaming between access points in the same mobility group.
@@ -866,11 +936,11 @@ add_pkcs11_uri_with_pin (NMSupplicantConfig *self,
 	}
 
 	tmp = g_strdup_printf ("%s%s%s", split[0],
-	                       (pin_qattr ? "&" : ""),
+	                       (pin_qattr ? "?" : ""),
 	                       (pin_qattr ? pin_qattr : ""));
 
 	tmp_log = g_strdup_printf ("%s%s%s", split[0],
-	                           (pin_qattr ? "&" : ""),
+	                           (pin_qattr ? "?" : ""),
 	                           (pin_qattr ? "pin-value=<hidden>" : ""));
 
 	return add_string_val (self, tmp, name, FALSE, tmp_log, error);
diff --git a/src/supplicant/nm-supplicant-config.h b/src/supplicant/nm-supplicant-config.h
index 6acfb7ee..d90d82b8 100644
--- a/src/supplicant/nm-supplicant-config.h
+++ b/src/supplicant/nm-supplicant-config.h
@@ -55,11 +55,16 @@ gboolean nm_supplicant_config_add_setting_wireless (NMSupplicantConfig *self,
                                                     guint32 fixed_freq,
                                                     GError **error);
 
+gboolean nm_supplicant_config_add_bgscan           (NMSupplicantConfig *self,
+                                                    NMConnection *connection,
+                                                    GError **error);
+
 gboolean nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
                                                              NMSettingWirelessSecurity *setting,
                                                              NMSetting8021x *setting_8021x,
                                                              const char *con_uuid,
                                                              guint32 mtu,
+                                                             NMSettingWirelessSecurityPmf pmf,
                                                              GError **error);
 
 gboolean nm_supplicant_config_add_no_security (NMSupplicantConfig *self,
@@ -76,4 +81,6 @@ gboolean nm_supplicant_config_add_setting_macsec (NMSupplicantConfig *self,
                                                   NMSettingMacsec *setting,
                                                   GError **error);
 
+gboolean nm_supplicant_config_enable_pmf_akm (NMSupplicantConfig *self,
+                                              GError **error);
 #endif /* __NETWORKMANAGER_SUPPLICANT_CONFIG_H__ */
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index ab8a0670..44f887cb 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.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) 2006 - 2012 Red Hat, Inc.
+ * Copyright (C) 2006 - 2017 Red Hat, Inc.
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
@@ -31,11 +31,12 @@
 #include "nm-core-internal.h"
 #include "nm-dbus-compat.h"
 
-#define WPAS_DBUS_IFACE_INTERFACE   WPAS_DBUS_INTERFACE ".Interface"
-#define WPAS_DBUS_IFACE_BSS         WPAS_DBUS_INTERFACE ".BSS"
-#define WPAS_DBUS_IFACE_NETWORK	    WPAS_DBUS_INTERFACE ".Network"
-#define WPAS_ERROR_INVALID_IFACE    WPAS_DBUS_INTERFACE ".InvalidInterface"
-#define WPAS_ERROR_EXISTS_ERROR     WPAS_DBUS_INTERFACE ".InterfaceExists"
+#define WPAS_DBUS_IFACE_INTERFACE       WPAS_DBUS_INTERFACE ".Interface"
+#define WPAS_DBUS_IFACE_INTERFACE_WPS   WPAS_DBUS_INTERFACE ".Interface.WPS"
+#define WPAS_DBUS_IFACE_BSS             WPAS_DBUS_INTERFACE ".BSS"
+#define WPAS_DBUS_IFACE_NETWORK         WPAS_DBUS_INTERFACE ".Network"
+#define WPAS_ERROR_INVALID_IFACE        WPAS_DBUS_INTERFACE ".InvalidInterface"
+#define WPAS_ERROR_EXISTS_ERROR         WPAS_DBUS_INTERFACE ".InterfaceExists"
 
 /*****************************************************************************/
 
@@ -48,6 +49,16 @@ struct _AddNetworkData;
 
 typedef struct {
 	NMSupplicantInterface *self;
+	char *type;
+	char *bssid;
+	char *pin;
+	GDBusProxy *proxy;
+	GCancellable *cancellable;
+	bool is_cancelling;
+} WpsData;
+
+typedef struct {
+	NMSupplicantInterface *self;
 	NMSupplicantConfig *cfg;
 	GCancellable *cancellable;
 	NMSupplicantInterfaceAssocCb callback;
@@ -69,6 +80,7 @@ enum {
 	BSS_REMOVED,         /* supplicant removed BSS from its scan list */
 	SCAN_DONE,           /* wifi scan is complete */
 	CREDENTIALS_REQUEST, /* 802.1x identity or password requested */
+	WPS_CREDENTIALS,     /* WPS credentials received */
 	LAST_SIGNAL
 };
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -80,6 +92,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_DRIVER,
 	PROP_FAST_SUPPORT,
 	PROP_AP_SUPPORT,
+	PROP_PMF_SUPPORT,
 );
 
 typedef struct {
@@ -88,6 +101,7 @@ typedef struct {
 	gboolean       has_credreq;  /* Whether querying 802.1x credentials is supported */
 	NMSupplicantFeature fast_support;
 	NMSupplicantFeature ap_support;   /* Lightweight AP mode support */
+	NMSupplicantFeature pmf_support;
 	guint32        max_scan_ssids;
 	guint32        ready_count;
 
@@ -105,6 +119,8 @@ typedef struct {
 	GDBusProxy *   iface_proxy;
 	GCancellable * other_cancellable;
 
+	WpsData *wps_data;
+
 	AssocData *    assoc_data;
 
 	char *         net_path;
@@ -543,6 +559,12 @@ nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self)
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ap_support;
 }
 
+NMSupplicantFeature
+nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->pmf_support;
+}
+
 void
 nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                         NMSupplicantFeature ap_support)
@@ -565,6 +587,315 @@ nm_supplicant_interface_set_fast_support (NMSupplicantInterface *self,
 	priv->fast_support = fast_support;
 }
 
+void
+nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
+                                         NMSupplicantFeature pmf_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->pmf_support = pmf_support;
+}
+
+/*****************************************************************************/
+
+static void
+_wps_data_free (WpsData *data)
+{
+	g_free (data->type);
+	g_free (data->pin);
+	g_free (data->bssid);
+	g_clear_object (&data->cancellable);
+	if (data->proxy && data->self)
+		g_signal_handlers_disconnect_by_data (data->proxy, data->self);
+	g_clear_object (&data->proxy);
+	g_slice_free (WpsData, data);
+}
+
+static void
+_wps_credentials_changed_cb (GDBusProxy *proxy,
+                             GVariant *props,
+                             gpointer user_data)
+{
+	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (user_data);
+
+	_LOGT ("wps: new credentials");
+	g_signal_emit (self, signals[WPS_CREDENTIALS], 0, props);
+}
+
+static void
+_wps_handle_start_cb (GObject *source_object,
+                      GAsyncResult *res,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	WpsData *data;
+	gs_unref_variant GVariant *result = NULL;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
+	if (   !result
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	data = user_data;
+	self = data->self;
+
+	if (result)
+		_LOGT ("wps: started with success");
+	else
+		_LOGW ("wps: start failed with %s", error->message);
+
+	g_clear_object (&data->cancellable);
+	nm_clear_g_free (&data->type);
+	nm_clear_g_free (&data->pin);
+	nm_clear_g_free (&data->bssid);
+}
+
+static void
+_wps_handle_set_pc_cb (GObject *source_object,
+                       GAsyncResult *res,
+                       gpointer user_data)
+{
+	WpsData *data;
+	NMSupplicantInterface *self;
+	gs_unref_variant GVariant *result = NULL;
+	gs_free_error GError *error = NULL;
+	GVariantBuilder start_args;
+	guint8 bssid_buf[ETH_ALEN];
+
+	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
+	if (   !result
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	data = user_data;
+	self = data->self;
+
+	if (result)
+		_LOGT ("wps: ProcessCredentials successfully set, starting...");
+	else
+		_LOGW ("wps: ProcessCredentials failed to set (%s), starting...", error->message);
+
+	_nm_dbus_signal_connect (data->proxy, "Credentials", G_VARIANT_TYPE ("(a{sv})"),
+	                         G_CALLBACK (_wps_credentials_changed_cb), self);
+
+	g_variant_builder_init (&start_args, G_VARIANT_TYPE_VARDICT);
+	g_variant_builder_add (&start_args, "{sv}", "Role", g_variant_new_string ("enrollee"));
+	g_variant_builder_add (&start_args, "{sv}", "Type", g_variant_new_string (data->type));
+	if (data->pin)
+		g_variant_builder_add (&start_args, "{sv}", "Pin", g_variant_new_string (data->pin));
+
+	if (data->bssid) {
+		/* The BSSID is in fact not mandatory. If it is not set the supplicant would
+		 * enroll with any BSS in range. */
+		if (!nm_utils_hwaddr_aton (data->bssid, bssid_buf, sizeof (bssid_buf)))
+			nm_assert_not_reached ();
+		g_variant_builder_add (&start_args, "{sv}", "Bssid",
+		                       g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, bssid_buf,
+		                                                  ETH_ALEN, sizeof (guint8)));
+	}
+
+	g_dbus_proxy_call (data->proxy,
+	                   "Start",
+	                   g_variant_new ("(a{sv})", &start_args),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   data->cancellable,
+	                   _wps_handle_start_cb,
+	                   data);
+}
+
+static void
+_wps_call_set_pc (WpsData *data)
+{
+	g_dbus_proxy_call (data->proxy,
+	                   "org.freedesktop.DBus.Properties.Set",
+	                   g_variant_new ("(ssv)",
+	                                  WPAS_DBUS_IFACE_INTERFACE_WPS,
+	                                  "ProcessCredentials",
+	                                  g_variant_new_boolean (TRUE)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   data->cancellable,
+	                   _wps_handle_set_pc_cb,
+	                   data);
+}
+
+static void
+_wps_handle_proxy_cb (GObject *source_object,
+                      GAsyncResult *res,
+                      gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	WpsData *data;
+	gs_free_error GError *error = NULL;
+	GDBusProxy *proxy;
+
+	proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+	if (   !proxy
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	data = user_data;
+	self = data->self;
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	if (!proxy) {
+		_LOGW ("wps: failure to create D-Bus proxy: %s", error->message);
+		_wps_data_free (data);
+		priv->wps_data = NULL;
+		return;
+	}
+
+	data->proxy = proxy;
+	_LOGT ("wps: D-Bus proxy created. set ProcessCredentials...");
+	_wps_call_set_pc (data);
+}
+
+static void
+_wps_handle_cancel_cb (GObject *source_object,
+                       GAsyncResult *res,
+                       gpointer user_data)
+{
+	NMSupplicantInterface *self;
+	NMSupplicantInterfacePrivate *priv;
+	WpsData *data;
+	gs_unref_variant GVariant *result = NULL;
+	gs_free_error GError *error = NULL;
+
+	result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error);
+	if (   !result
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
+
+	data = user_data;
+	self = data->self;
+
+	if (!self) {
+		_wps_data_free (data);
+		if (result)
+			_LOGT ("wps: cancel completed successfully, after supplicant interface is gone");
+		else
+			_LOGW ("wps: cancel failed (%s), after supplicant interface is gone", error->message);
+		return;
+	}
+
+	priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	data->is_cancelling = FALSE;
+
+	if (!data->type) {
+		priv->wps_data = NULL;
+		_wps_data_free (data);
+		if (result)
+			_LOGT ("wps: cancel completed successfully");
+		else
+			_LOGW ("wps: cancel failed (%s)", error->message);
+		return;
+	}
+
+	if (result)
+		_LOGT ("wps: cancel completed successfully, setting ProcessCredentials now...");
+	else
+		_LOGW ("wps: cancel failed (%s), setting ProcessCredentials now...", error->message);
+	_wps_call_set_pc (data);
+}
+
+static void
+_wps_start (NMSupplicantInterface *self,
+            const char *type,
+            const char *bssid,
+            const char *pin)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	WpsData *data = priv->wps_data;
+
+	if (type)
+		_LOGI ("wps: type %s start...", type);
+
+	if (!data) {
+		if (!type)
+			return;
+
+		data = g_slice_new0 (WpsData);
+		data->self = self;
+		data->type = g_strdup (type);
+		data->bssid = g_strdup (bssid);
+		data->pin = g_strdup (pin);
+		data->cancellable = g_cancellable_new ();
+
+		priv->wps_data = data;
+
+		_LOGT ("wps: create D-Bus proxy...");
+
+		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+		                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+		                          NULL,
+		                          WPAS_DBUS_SERVICE,
+		                          priv->object_path,
+		                          WPAS_DBUS_IFACE_INTERFACE_WPS,
+		                          data->cancellable,
+		                          _wps_handle_proxy_cb,
+		                          data);
+		return;
+	}
+
+	g_free (data->type);
+	g_free (data->bssid);
+	g_free (data->pin);
+	data->type = g_strdup (type);
+	data->bssid = g_strdup (bssid);
+	data->pin = g_strdup (pin);
+
+	if (!data->proxy) {
+		if (!type) {
+			nm_clear_g_cancellable (&data->cancellable);
+			priv->wps_data = NULL;
+			_wps_data_free (data);
+
+			_LOGT ("wps: abort creation of D-Bus proxy");
+		} else
+			_LOGT ("wps: new enrollment. Wait for D-Bus proxy...");
+		return;
+	}
+
+	if (data->is_cancelling)
+		return;
+
+	_LOGT ("wps: cancel previous enrollment...");
+
+	data->is_cancelling = TRUE;
+	nm_clear_g_cancellable (&data->cancellable);
+	data->cancellable = g_cancellable_new ();
+	g_signal_handlers_disconnect_by_data (data->proxy, self);
+	g_dbus_proxy_call (data->proxy,
+	                   "Cancel",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   data->cancellable,
+	                   _wps_handle_cancel_cb,
+	                   data);
+}
+
+void
+nm_supplicant_interface_enroll_wps (NMSupplicantInterface *self,
+                                    const char *type,
+                                    const char *bssid,
+                                    const char *pin)
+{
+	_wps_start (self, type, bssid, pin);
+}
+
+void
+nm_supplicant_interface_cancel_wps (NMSupplicantInterface *self)
+{
+	_wps_start (self, NULL, NULL, NULL);
+}
+
+/*****************************************************************************/
+
 static void
 iface_introspect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
@@ -786,7 +1117,7 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 
 	/* Scan result aging parameters */
 	g_dbus_proxy_call (priv->iface_proxy,
-	                   "org.freedesktop.DBus.Properties.Set",
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
 	                   g_variant_new ("(ssv)",
 	                                  WPAS_DBUS_IFACE_INTERFACE,
 	                                  "BSSExpireAge",
@@ -797,7 +1128,7 @@ on_iface_proxy_acquired (GDBusProxy *proxy, GAsyncResult *result, gpointer user_
 	                   NULL,
 	                   NULL);
 	g_dbus_proxy_call (priv->iface_proxy,
-	                   "org.freedesktop.DBus.Properties.Set",
+	                   DBUS_INTERFACE_PROPERTIES ".Set",
 	                   g_variant_new ("(ssv)",
 	                                  WPAS_DBUS_IFACE_INTERFACE,
 	                                  "BSSExpireCount",
@@ -1158,6 +1489,9 @@ nm_supplicant_interface_disconnect (NMSupplicantInterface * self)
 		g_free (priv->net_path);
 		priv->net_path = NULL;
 	}
+
+	/* Cancel any WPS enrollment, if any */
+	nm_supplicant_interface_cancel_wps (self);
 }
 
 static void
@@ -1455,6 +1789,7 @@ nm_supplicant_interface_request_scan (NMSupplicantInterface *self, const GPtrArr
 	/* Scan parameters */
 	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) {
 		GVariantBuilder ssids_builder;
 
@@ -1561,6 +1896,10 @@ set_property (GObject *object,
 		/* construct-only */
 		priv->ap_support = g_value_get_int (value);
 		break;
+	case PROP_PMF_SUPPORT:
+		/* construct-only */
+		priv->pmf_support = g_value_get_int (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1573,14 +1912,15 @@ nm_supplicant_interface_init (NMSupplicantInterface * self)
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
 	priv->state = NM_SUPPLICANT_INTERFACE_STATE_INIT;
-	priv->bss_proxies = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, bss_data_destroy);
+	priv->bss_proxies = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, bss_data_destroy);
 }
 
 NMSupplicantInterface *
 nm_supplicant_interface_new (const char *ifname,
                              NMSupplicantDriver driver,
                              NMSupplicantFeature fast_support,
-                             NMSupplicantFeature ap_support)
+                             NMSupplicantFeature ap_support,
+                             NMSupplicantFeature pmf_support)
 {
 	g_return_val_if_fail (ifname != NULL, NULL);
 
@@ -1589,6 +1929,7 @@ nm_supplicant_interface_new (const char *ifname,
 	                     NM_SUPPLICANT_INTERFACE_DRIVER, (guint) driver,
 	                     NM_SUPPLICANT_INTERFACE_FAST_SUPPORT, (int) fast_support,
 	                     NM_SUPPLICANT_INTERFACE_AP_SUPPORT, (int) ap_support,
+	                     NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, (int) pmf_support,
 	                     NULL);
 }
 
@@ -1598,6 +1939,16 @@ dispose (GObject *object)
 	NMSupplicantInterface *self = NM_SUPPLICANT_INTERFACE (object);
 	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
 
+	nm_supplicant_interface_cancel_wps (self);
+	if (priv->wps_data) {
+		/* we shut down, but an asynchronous Cancel request is pending.
+		 * We don't want to cancel it, so mark wps-data that @self is gone.
+		 * This way, _wps_handle_cancel_cb() knows it must no longer touch
+		 * @self */
+		priv->wps_data->self = NULL;
+		priv->wps_data = NULL;
+	}
+
 	if (priv->assoc_data) {
 		gs_free_error GError *error = NULL;
 
@@ -1670,6 +2021,14 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                      G_PARAM_WRITABLE |
 	                      G_PARAM_CONSTRUCT_ONLY |
 	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_PMF_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
@@ -1720,5 +2079,12 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                  0,
 	                  NULL, NULL, NULL,
 	                  G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
-}
 
+	signals[WPS_CREDENTIALS] =
+	    g_signal_new (NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS,
+	                  G_OBJECT_CLASS_TYPE (object_class),
+	                  G_SIGNAL_RUN_LAST,
+	                  0,
+	                  NULL, NULL, NULL,
+	                  G_TYPE_NONE, 1, G_TYPE_VARIANT);
+}
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index d60d4a54..567cf96f 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.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) 2006 - 2010 Red Hat, Inc.
+ * Copyright (C) 2006 - 2017 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
  */
 
@@ -60,6 +60,7 @@ typedef enum {
 #define NM_SUPPLICANT_INTERFACE_DRIVER           "driver"
 #define NM_SUPPLICANT_INTERFACE_FAST_SUPPORT     "fast-support"
 #define NM_SUPPLICANT_INTERFACE_AP_SUPPORT       "ap-support"
+#define NM_SUPPLICANT_INTERFACE_PMF_SUPPORT      "pmf-support"
 
 /* Signals */
 #define NM_SUPPLICANT_INTERFACE_STATE            "state"
@@ -68,6 +69,7 @@ typedef enum {
 #define NM_SUPPLICANT_INTERFACE_BSS_REMOVED      "bss-removed"
 #define NM_SUPPLICANT_INTERFACE_SCAN_DONE        "scan-done"
 #define NM_SUPPLICANT_INTERFACE_CREDENTIALS_REQUEST "credentials-request"
+#define NM_SUPPLICANT_INTERFACE_WPS_CREDENTIALS  "wps-credentials"
 
 typedef struct _NMSupplicantInterfaceClass NMSupplicantInterfaceClass;
 
@@ -76,7 +78,8 @@ GType nm_supplicant_interface_get_type (void);
 NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
                                                      NMSupplicantDriver driver,
                                                      NMSupplicantFeature fast_support,
-                                                     NMSupplicantFeature ap_support);
+                                                     NMSupplicantFeature ap_support,
+                                                     NMSupplicantFeature pmf_support);
 
 void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
                                                        gboolean available);
@@ -119,6 +122,7 @@ gboolean nm_supplicant_interface_credentials_reply (NMSupplicantInterface *self,
                                                     GError **error);
 
 NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self);
 
 void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                              NMSupplicantFeature apmode);
@@ -126,4 +130,14 @@ void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
 void nm_supplicant_interface_set_fast_support (NMSupplicantInterface *self,
                                                NMSupplicantFeature fast_support);
 
+void nm_supplicant_interface_set_pmf_support (NMSupplicantInterface *self,
+                                              NMSupplicantFeature pmf_support);
+
+void nm_supplicant_interface_enroll_wps (NMSupplicantInterface *self,
+                                         const char *const type,
+                                         const char *bssid,
+                                         const char *pin);
+
+void nm_supplicant_interface_cancel_wps (NMSupplicantInterface *self);
+
 #endif /* __NM_SUPPLICANT_INTERFACE_H__ */
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
index 49650ab7..0f2eb63a 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -39,6 +39,7 @@ typedef struct {
 	GSList          *ifaces;
 	NMSupplicantFeature fast_support;
 	NMSupplicantFeature ap_support;
+	NMSupplicantFeature pmf_support;
 	guint             die_count_reset_id;
 	guint             die_count;
 } NMSupplicantManagerPrivate;
@@ -159,7 +160,8 @@ nm_supplicant_manager_create_interface (NMSupplicantManager *self,
 	iface = nm_supplicant_interface_new (ifname,
 	                                     driver,
 	                                     priv->fast_support,
-	                                     priv->ap_support);
+	                                     priv->ap_support,
+	                                     priv->pmf_support);
 
 	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
 	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
@@ -193,28 +195,37 @@ update_capabilities (NMSupplicantManager *self)
 	 * dbus: Add global capabilities property
 	 */
 	priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+	priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 
 	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
 	if (value) {
 		if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY)) {
 			array = g_variant_get_strv (value, NULL);
 			priv->ap_support = NM_SUPPLICANT_FEATURE_NO;
+			priv->pmf_support = NM_SUPPLICANT_FEATURE_NO;
 			if (array) {
 				if (g_strv_contains (array, "ap"))
 					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
+				if (g_strv_contains (array, "pmf"))
+					priv->pmf_support = NM_SUPPLICANT_FEATURE_YES;
 				g_free (array);
 			}
 		}
 		g_variant_unref (value);
 	}
 
-	/* Tell all interfaces about results of the AP check */
-	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next)
+	/* Tell all interfaces about results of the AP/PMF check */
+	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
 		nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support);
+		nm_supplicant_interface_set_pmf_support (ifaces->data, priv->pmf_support);
+	}
 
 	_LOGD ("AP mode is %ssupported",
 	       (priv->ap_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
 	           (priv->ap_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	_LOGD ("PMF is %ssupported",
+	       (priv->pmf_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->pmf_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
 
 	/* EAP-FAST */
 	priv->fast_support = NM_SUPPLICANT_FEATURE_NO;
@@ -337,6 +348,7 @@ name_owner_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data)
 
 		priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 		priv->fast_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+		priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 
 		set_running (self, FALSE);
 	}
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index ce3e46d8..14daf693 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -71,7 +71,9 @@ static const struct validate_entry validate_table[] = {
 const char * pairwise_allowed[] = { "CCMP", "TKIP", "NONE", NULL };
 const char * group_allowed[] =    { "CCMP", "TKIP", "WEP104", "WEP40", NULL };
 const char * proto_allowed[] =    { "WPA", "RSN", NULL };
-const char * key_mgmt_allowed[] = { "WPA-PSK", "WPA-EAP", "IEEE8021X", "WPA-NONE",
+const char * key_mgmt_allowed[] = { "WPA-PSK", "WPA-PSK-SHA256",
+                                    "WPA-EAP", "WPA-EAP-SHA256",
+                                    "IEEE8021X", "WPA-NONE",
                                     "NONE", NULL };
 const char * auth_alg_allowed[] = { "OPEN", "SHARED", "LEAP", NULL };
 const char * eap_allowed[] =      { "LEAP", "MD5", "TLS", "PEAP", "TTLS", "SIM",
@@ -149,6 +151,7 @@ static const struct Opt opt_table[] = {
 	{ "mka_cak",            TYPE_BYTES,   0, 65536, FALSE, NULL },
 	{ "mka_ckn",            TYPE_BYTES,   0, 65536, FALSE, NULL },
 	{ "macsec_port",        TYPE_INT,     1, 65534, FALSE, NULL },
+	{ "ieee80211w",         TYPE_INT,     0, 2, FALSE, NULL },
 };
 
 
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index ef6f2c64..4b4a4935 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -42,16 +42,11 @@ validate_opt (const char *detail,
               GVariant *config,
               const char *key,
               OptType val_type,
-              gconstpointer expected,
-              size_t expected_len)
+              gconstpointer expected)
 {
 	char *config_key;
 	GVariant *config_value;
 	gboolean found = FALSE;
-	const guint8 *bytes;
-	gsize len;
-	const char *s;
-	const unsigned char *expected_array = expected;
 	GVariantIter iter;
 
 	g_assert (g_variant_is_of_type (config, G_VARIANT_TYPE_VARDICT));
@@ -61,25 +56,33 @@ validate_opt (const char *detail,
 		if (!strcmp (key, config_key)) {
 			found = TRUE;
 			switch (val_type) {
-			case TYPE_INT:
+			case TYPE_INT: {
 				g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_INT32));
 				g_assert_cmpint (g_variant_get_int32 (config_value), ==, GPOINTER_TO_INT (expected));
 				break;
-			case TYPE_BYTES:
+			}
+			case TYPE_BYTES: {
+				const guint8 *expected_bytes;
+				gsize expected_len = 0;
+				const guint8 *config_bytes;
+				gsize config_len = 0;
+
+				expected_bytes = g_bytes_get_data ((GBytes *) expected, &expected_len);
 				g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_BYTESTRING));
-				bytes = g_variant_get_fixed_array (config_value, &len, 1);
-				g_assert_cmpint (len, ==, expected_len);
-				g_assert (memcmp (bytes, expected_array, expected_len) == 0);
+				config_bytes = g_variant_get_fixed_array (config_value, &config_len, 1);
+				g_assert_cmpmem (config_bytes, config_len, expected_bytes, expected_len);
 				break;
+			}
 			case TYPE_KEYWORD:
-			case TYPE_STRING:
+			case TYPE_STRING: {
+				const char *expected_str = expected;
+				const char *config_str;
+
 				g_assert (g_variant_is_of_type (config_value, G_VARIANT_TYPE_STRING));
-				if (expected_len == -1)
-					expected_len = strlen ((const char *) expected);
-				s = g_variant_get_string (config_value, NULL);
-				g_assert_cmpint (strlen (s), ==, expected_len);
-				g_assert_cmpstr (s, ==, expected);
+				config_str = g_variant_get_string (config_value, NULL);
+				g_assert_cmpstr (config_str, ==, expected_str);
 				break;
+			}
 			default:
 				g_assert_not_reached ();
 				break;
@@ -91,43 +94,80 @@ validate_opt (const char *detail,
 	return found;
 }
 
-static void
-test_wifi_open (void)
+static GVariant *
+build_supplicant_config (NMConnection *connection, guint mtu, guint fixed_freq)
 {
-	gs_unref_object NMConnection *connection = NULL;
 	gs_unref_object NMSupplicantConfig *config = NULL;
-	gs_unref_variant GVariant *config_dict = NULL;
+	gs_free_error GError *error = NULL;
+	NMSettingWireless *s_wifi;
+	NMSettingWirelessSecurity *s_wsec;
+	NMSetting8021x *s_8021x;
+	gboolean success;
+
+	config = nm_supplicant_config_new ();
+
+	s_wifi = nm_connection_get_setting_wireless (connection);
+	g_assert (s_wifi);
+	success = nm_supplicant_config_add_setting_wireless (config,
+	                                                     s_wifi,
+	                                                     fixed_freq,
+	                                                     &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	s_wsec = nm_connection_get_setting_wireless_security (connection);
+	if (s_wsec) {
+		NMSettingWirelessSecurityPmf pmf = nm_setting_wireless_security_get_pmf (s_wsec);
+		s_8021x = nm_connection_get_setting_802_1x (connection);
+		success = nm_supplicant_config_add_setting_wireless_security (config,
+			                                                          s_wsec,
+			                                                          s_8021x,
+			                                                          nm_connection_get_uuid (connection),
+			                                                          mtu,
+			                                                          pmf,
+			                                                          &error);
+	} else {
+		success = nm_supplicant_config_add_no_security (config, &error);
+	}
+	g_assert_no_error (error);
+	g_assert (success);
+
+
+	success = nm_supplicant_config_add_bgscan (config, connection, &error);
+	g_assert_no_error (error);
+	g_assert (success);
+
+	return nm_supplicant_config_to_variant (config);
+}
+
+#define EXPECT(msg) g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, msg)
+
+static NMConnection *
+new_basic_connection (const char *id,
+                      GBytes *ssid,
+                      const char *bssid_str)
+{
+	NMConnection *connection;
 	NMSettingConnection *s_con;
 	NMSettingWireless *s_wifi;
 	NMSettingIPConfig *s_ip4;
-	char *uuid;
-	gboolean success;
-	GError *error = NULL;
-	GBytes *ssid;
-	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
-	const char *bssid_str = "11:22:33:44:55:66";
+	gs_free char *uuid = nm_utils_uuid_generate ();
 
 	connection = nm_simple_connection_new ();
 
 	/* Connection setting */
 	s_con = (NMSettingConnection *) nm_setting_connection_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_con));
-
-	uuid = nm_utils_uuid_generate ();
 	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Wifi Open",
+	              NM_SETTING_CONNECTION_ID, id,
 	              NM_SETTING_CONNECTION_UUID, uuid,
 	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
 	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	              NULL);
-	g_free (uuid);
 
 	/* Wifi setting */
 	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
-
-	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
-
 	g_object_set (s_wifi,
 	              NM_SETTING_WIRELESS_SSID, ssid,
 	              NM_SETTING_WIRELESS_BSSID, bssid_str,
@@ -135,162 +175,106 @@ test_wifi_open (void)
 	              NM_SETTING_WIRELESS_BAND, "bg",
 	              NULL);
 
-	g_bytes_unref (ssid);
-
 	/* 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);
 
-	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	return connection;
+}
 
-	config = nm_supplicant_config_new ();
+static void
+test_wifi_open (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	gboolean success;
+	GError *error = NULL;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+	const char *bssid_str = "11:22:33:44:55:66";
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'ssid' value 'Test SSID'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'scan_ssid' value '1'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'bssid' value '11:22:33:44:55:66'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'freq_list' value *");
-	g_assert (nm_supplicant_config_add_setting_wireless (config,
-	                                                     s_wifi,
-	                                                     0,
-	                                                     &error));
+	connection = new_basic_connection ("Test Wifi Open", ssid, bssid_str);
+	success = nm_connection_verify (connection, &error);
 	g_assert_no_error (error);
-	g_test_assert_expected_messages ();
+	g_assert (success);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'key_mgmt' value 'NONE'");
-	g_assert (nm_supplicant_config_add_no_security (config, &error));
-	g_assert_no_error (error);
+	EXPECT ("*added 'ssid' value 'Test SSID'*");
+	EXPECT ("*added 'scan_ssid' value '1'*");
+	EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
+	EXPECT ("*added 'freq_list' value *");
+	EXPECT ("*added 'key_mgmt' value 'NONE'");
+	config_dict = build_supplicant_config (connection, 1500, 0);
 	g_test_assert_expected_messages ();
-
-	config_dict = nm_supplicant_config_to_variant (config);
 	g_assert (config_dict);
 
-	validate_opt ("wifi-open", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
-	validate_opt ("wifi-open", config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
-	validate_opt ("wifi-open", config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
-	validate_opt ("wifi-open", config_dict, "key_mgmt", TYPE_KEYWORD, "NONE", -1);
+	validate_opt ("wifi-open", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-open", config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt ("wifi-open", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-open", config_dict, "key_mgmt", TYPE_KEYWORD, "NONE");
 }
 
 static void
 test_wifi_wep_key (const char *detail,
+                   gboolean test_bssid,
                    NMWepKeyType wep_type,
                    const char *key_data,
                    const unsigned char *expected,
                    size_t expected_size)
 {
 	gs_unref_object NMConnection *connection = NULL;
-	gs_unref_object NMSupplicantConfig *config = NULL;
 	gs_unref_variant GVariant *config_dict = NULL;
-	NMSettingConnection *s_con;
-	NMSettingWireless *s_wifi;
 	NMSettingWirelessSecurity *s_wsec;
-	NMSettingIPConfig *s_ip4;
-	char *uuid;
 	gboolean success;
 	GError *error = NULL;
-	GBytes *ssid;
 	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
 	const char *bssid_str = "11:22:33:44:55:66";
+	gs_unref_bytes GBytes *wep_key_bytes = g_bytes_new (expected, expected_size);
+	const char *bgscan_data = "simple:30:-80:86400";
+	gs_unref_bytes GBytes *bgscan = g_bytes_new (bgscan_data, strlen (bgscan_data));
 
-	connection = nm_simple_connection_new ();
-
-	/* Connection setting */
-	s_con = (NMSettingConnection *) nm_setting_connection_new ();
-	nm_connection_add_setting (connection, NM_SETTING (s_con));
-
-	uuid = nm_utils_uuid_generate ();
-	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Wifi WEP Key",
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
-	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
-	              NULL);
-	g_free (uuid);
-
-	/* Wifi setting */
-	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
-	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
-
-	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
-
-	g_object_set (s_wifi,
-	              NM_SETTING_WIRELESS_SSID, ssid,
-	              NM_SETTING_WIRELESS_BSSID, bssid_str,
-	              NM_SETTING_WIRELESS_MODE, "infrastructure",
-	              NM_SETTING_WIRELESS_BAND, "bg",
-	              NULL);
-
-	g_bytes_unref (ssid);
+	connection = new_basic_connection ("Test Wifi WEP Key", ssid, test_bssid ? bssid_str : NULL);
 
 	/* Wifi Security setting */
 	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
-
 	g_object_set (s_wsec,
 	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none",
 	              NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, wep_type,
 	              NULL);
-	nm_setting_wireless_security_set_wep_key (s_wsec, 0, key_data);	
-
-	/* 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);
+	nm_setting_wireless_security_set_wep_key (s_wsec, 0, key_data);
 
 	success = nm_connection_verify (connection, &error);
 	g_assert_no_error (error);
 	g_assert (success);
 
-	config = nm_supplicant_config_new ();
+	EXPECT ("*added 'ssid' value 'Test SSID'*");
+	EXPECT ("*added 'scan_ssid' value '1'*");
+	if (test_bssid)
+		EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'ssid' value 'Test SSID'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'scan_ssid' value '1'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'bssid' value '11:22:33:44:55:66'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'freq_list' value *");
-	g_assert (nm_supplicant_config_add_setting_wireless (config,
-	                                                     s_wifi,
-	                                                     0,
-	                                                     &error));
-	g_assert_no_error (error);
-	g_test_assert_expected_messages ();
+	EXPECT ("*added 'freq_list' value *");
+	EXPECT ("*added 'key_mgmt' value 'NONE'");
+	EXPECT ("*added 'wep_key0' value *");
+	EXPECT ("*added 'wep_tx_keyidx' value '0'");
+	if (!test_bssid)
+		EXPECT ("*added 'bgscan' value 'simple:30:-80:86400'*");
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'key_mgmt' value 'NONE'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'wep_key0' value *");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'wep_tx_keyidx' value '0'");
-	g_assert (nm_supplicant_config_add_setting_wireless_security (config,
-	                                                              s_wsec,
-	                                                              NULL,
-	                                                              "376aced7-b28c-46be-9a62-fcdf072571da",
-	                                                              1500,
-	                                                              &error));
-	g_assert_no_error (error);
+	config_dict = build_supplicant_config (connection, 1500, 0);
 	g_test_assert_expected_messages ();
-
-	config_dict = nm_supplicant_config_to_variant (config);
 	g_assert (config_dict);
 
-	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
-	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
-	validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
-	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "NONE", -1);
-	validate_opt (detail, config_dict, "wep_tx_keyidx", TYPE_INT, GINT_TO_POINTER (0), -1);
-	validate_opt (detail, config_dict, "wep_key0", TYPE_BYTES, expected, expected_size);
+	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid);
+	if (test_bssid)
+		validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+	else
+		validate_opt (detail, config_dict, "bgscan", TYPE_BYTES, bgscan);
+
+	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "NONE");
+	validate_opt (detail, config_dict, "wep_tx_keyidx", TYPE_INT, GINT_TO_POINTER (0));
+	validate_opt (detail, config_dict, "wep_key0", TYPE_BYTES, wep_key_bytes);
 }
 
 static void
@@ -307,13 +291,16 @@ test_wifi_wep (void)
 	const char *key5 = "r34lly l33t w3p p4ssphr4s3 for t3st1ng";
 	const unsigned char key5_expected[] = { 0xce, 0x68, 0x8b, 0x35, 0xf6, 0x0a, 0x2b, 0xbf, 0xc9, 0x8f, 0xed, 0x10, 0xda };
 
-	test_wifi_wep_key ("wifi-wep-ascii-40", NM_WEP_KEY_TYPE_KEY, key1, key1_expected, sizeof (key1_expected));
-	test_wifi_wep_key ("wifi-wep-ascii-104", NM_WEP_KEY_TYPE_KEY, key2, key2_expected, sizeof (key2_expected));
-	test_wifi_wep_key ("wifi-wep-hex-40", NM_WEP_KEY_TYPE_KEY, key3, key3_expected, sizeof (key3_expected));
-	test_wifi_wep_key ("wifi-wep-hex-104", NM_WEP_KEY_TYPE_KEY, key4, key4_expected, sizeof (key4_expected));
-	test_wifi_wep_key ("wifi-wep-passphrase-104", NM_WEP_KEY_TYPE_PASSPHRASE, key5, key5_expected, sizeof (key5_expected));
+	test_wifi_wep_key ("wifi-wep-ascii-40", TRUE, NM_WEP_KEY_TYPE_KEY, key1, key1_expected, sizeof (key1_expected));
+	test_wifi_wep_key ("wifi-wep-ascii-104", TRUE, NM_WEP_KEY_TYPE_KEY, key2, key2_expected, sizeof (key2_expected));
+	test_wifi_wep_key ("wifi-wep-hex-40", TRUE, NM_WEP_KEY_TYPE_KEY, key3, key3_expected, sizeof (key3_expected));
+	test_wifi_wep_key ("wifi-wep-hex-104", TRUE, NM_WEP_KEY_TYPE_KEY, key4, key4_expected, sizeof (key4_expected));
+	test_wifi_wep_key ("wifi-wep-passphrase-104", TRUE, NM_WEP_KEY_TYPE_PASSPHRASE, key5, key5_expected, sizeof (key5_expected));
+
+	test_wifi_wep_key ("wifi-wep-old-hex-104", TRUE, NM_WEP_KEY_TYPE_UNKNOWN, key4, key4_expected, sizeof (key4_expected));
 
-	test_wifi_wep_key ("wifi-wep-old-hex-104", NM_WEP_KEY_TYPE_UNKNOWN, key4, key4_expected, sizeof (key4_expected));
+	/* Unlocked BSSID to test bgscan */
+	test_wifi_wep_key ("wifi-wep-hex-40", FALSE, NM_WEP_KEY_TYPE_KEY, key3, key3_expected, sizeof (key3_expected));
 }
 
 static void
@@ -324,58 +311,25 @@ test_wifi_wpa_psk (const char *detail,
                    size_t expected_size)
 {
 	gs_unref_object NMConnection *connection = NULL;
-	gs_unref_object NMSupplicantConfig *config = NULL;
 	gs_unref_variant GVariant *config_dict = NULL;
-	NMSettingConnection *s_con;
-	NMSettingWireless *s_wifi;
 	NMSettingWirelessSecurity *s_wsec;
-	NMSettingIPConfig *s_ip4;
-	char *uuid;
 	gboolean success;
 	GError *error = NULL;
-	GBytes *ssid;
 	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
 	const char *bssid_str = "11:22:33:44:55:66";
+	gs_unref_bytes GBytes *wpa_psk_bytes = g_bytes_new (expected, expected_size);
 
-	connection = nm_simple_connection_new ();
-
-	/* Connection setting */
-	s_con = (NMSettingConnection *) nm_setting_connection_new ();
-	nm_connection_add_setting (connection, NM_SETTING (s_con));
-
-	uuid = nm_utils_uuid_generate ();
-	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Wifi WEP Key",
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
-	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
-	              NULL);
-	g_free (uuid);
-
-	/* Wifi setting */
-	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
-	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
-
-	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
-
-	g_object_set (s_wifi,
-	              NM_SETTING_WIRELESS_SSID, ssid,
-	              NM_SETTING_WIRELESS_BSSID, bssid_str,
-	              NM_SETTING_WIRELESS_MODE, "infrastructure",
-	              NM_SETTING_WIRELESS_BAND, "bg",
-	              NULL);
-
-	g_bytes_unref (ssid);
+	connection = new_basic_connection ("Test Wifi WPA PSK", ssid, bssid_str);
 
 	/* Wifi Security setting */
 	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
-
 	g_object_set (s_wsec,
 	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk",
 	              NM_SETTING_WIRELESS_SECURITY_PSK, key_data,
+	              NM_SETTING_WIRELESS_SECURITY_PMF, (int) NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL,
 	              NULL);
-
 	nm_setting_wireless_security_add_proto (s_wsec, "wpa");
 	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
 	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
@@ -383,63 +337,38 @@ test_wifi_wpa_psk (const char *detail,
 	nm_setting_wireless_security_add_group (s_wsec, "tkip");
 	nm_setting_wireless_security_add_group (s_wsec, "ccmp");
 
-	/* 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);
-
 	success = nm_connection_verify (connection, &error);
 	g_assert_no_error (error);
 	g_assert (success);
 
-	config = nm_supplicant_config_new ();
-
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'ssid' value 'Test SSID'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'scan_ssid' value '1'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'bssid' value '11:22:33:44:55:66'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'freq_list' value *");
-	g_assert (nm_supplicant_config_add_setting_wireless (config,
-	                                                     s_wifi,
-	                                                     0,
-	                                                     &error));
-	g_assert_no_error (error);
-	g_test_assert_expected_messages ();
+	EXPECT ("*added 'ssid' value 'Test SSID'*");
+	EXPECT ("*added 'scan_ssid' value '1'*");
+	EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
+	EXPECT ("*added 'freq_list' value *");
+	EXPECT ("*added 'key_mgmt' value 'WPA-PSK WPA-PSK-SHA256'");
+	EXPECT ("*added 'psk' value *");
+	EXPECT ("*added 'proto' value 'WPA RSN'");
+	EXPECT ("*added 'pairwise' value 'TKIP CCMP'");
+	EXPECT ("*added 'group' value 'TKIP CCMP'");
+	EXPECT ("*added 'ieee80211w' value '1'");
+	config_dict = build_supplicant_config (connection, 1500, 0);
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'key_mgmt' value 'WPA-PSK'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'psk' value *");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'proto' value 'WPA RSN'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'pairwise' value 'TKIP CCMP'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'group' value 'TKIP CCMP'");
-	g_assert (nm_supplicant_config_add_setting_wireless_security (config,
-	                                                              s_wsec,
-	                                                              NULL,
-	                                                              "376aced7-b28c-46be-9a62-fcdf072571da",
-	                                                              1500,
-	                                                              &error));
-	g_assert_no_error (error);
 	g_test_assert_expected_messages ();
-
-	config_dict = nm_supplicant_config_to_variant (config);
 	g_assert (config_dict);
 
-	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
-	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
-	validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
-	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-PSK", -1);
-	validate_opt (detail, config_dict, "proto", TYPE_KEYWORD, "WPA RSN", -1);
-	validate_opt (detail, config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP", -1);
-	validate_opt (detail, config_dict, "group", TYPE_KEYWORD, "TKIP CCMP", -1);
-	validate_opt (detail, config_dict, "psk", key_type, expected, expected_size);
+	validate_opt (detail, config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt (detail, config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt (detail, config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+	validate_opt (detail, config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-PSK WPA-PSK-SHA256");
+	validate_opt (detail, config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
+	validate_opt (detail, config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt (detail, config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	if (key_type == TYPE_BYTES)
+		validate_opt (detail, config_dict, "psk", key_type, wpa_psk_bytes);
+	else if (key_type == TYPE_STRING)
+		validate_opt (detail, config_dict, "psk", key_type, expected);
+	else
+		g_assert_not_reached ();
 }
 
 static void
@@ -456,63 +385,23 @@ test_wifi_wpa_psk_types (void)
 	test_wifi_wpa_psk ("wifi-wep-psk-passphrase", TYPE_STRING, key2, (gconstpointer) key2, strlen (key2));
 }
 
-static void
-test_wifi_eap (void)
+static NMConnection *
+generate_wifi_eap_connection (const char *id, GBytes *ssid, const char *bssid_str)
 {
-	gs_unref_object NMConnection *connection = NULL;
-	gs_unref_object NMSupplicantConfig *config = NULL;
-	gs_unref_variant GVariant *config_dict = NULL;
-	NMSettingConnection *s_con;
-	NMSettingWireless *s_wifi;
+	NMConnection *connection = NULL;
 	NMSettingWirelessSecurity *s_wsec;
 	NMSetting8021x *s_8021x;
-	NMSettingIPConfig *s_ip4;
-	char *uuid;
 	gboolean success;
 	GError *error = NULL;
-	GBytes *ssid;
-	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
-	const char *bssid_str = "11:22:33:44:55:66";
-	guint32 mtu = 1100;
 
-	connection = nm_simple_connection_new ();
-
-	/* Connection setting */
-	s_con = (NMSettingConnection *) nm_setting_connection_new ();
-	nm_connection_add_setting (connection, NM_SETTING (s_con));
-
-	uuid = nm_utils_uuid_generate ();
-	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_ID, "Test Wifi EAP-TLS",
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, TRUE,
-	              NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
-	              NULL);
-	g_free (uuid);
-
-	/* Wifi setting */
-	s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
-	nm_connection_add_setting (connection, NM_SETTING (s_wifi));
-
-	ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
-
-	g_object_set (s_wifi,
-	              NM_SETTING_WIRELESS_SSID, ssid,
-	              NM_SETTING_WIRELESS_BSSID, bssid_str,
-	              NM_SETTING_WIRELESS_MODE, "infrastructure",
-	              NM_SETTING_WIRELESS_BAND, "bg",
-	              NULL);
-
-	g_bytes_unref (ssid);
+	connection = new_basic_connection (id, ssid, bssid_str);
 
 	/* Wifi Security setting */
 	s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
 	nm_connection_add_setting (connection, NM_SETTING (s_wsec));
-
 	g_object_set (s_wsec,
 	              NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap",
 	              NULL);
-
 	nm_setting_wireless_security_add_proto (s_wsec, "wpa");
 	nm_setting_wireless_security_add_proto (s_wsec, "rsn");
 	nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
@@ -528,74 +417,92 @@ test_wifi_eap (void)
 	g_assert (nm_setting_802_1x_set_ca_cert (s_8021x, TEST_CERT_DIR "/test-ca-cert.pem", NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL));
 	nm_setting_802_1x_set_private_key (s_8021x, TEST_CERT_DIR "/test-cert.p12", NULL, NM_SETTING_802_1X_CK_SCHEME_PATH, NULL, NULL);
 
-	/* 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);
-
 	success = nm_connection_verify (connection, &error);
 	g_assert_no_error (error);
 	g_assert (success);
 
-	config = nm_supplicant_config_new ();
+	return connection;
+}
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'ssid' value 'Test SSID'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'scan_ssid' value '1'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'bssid' value '11:22:33:44:55:66'*");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'freq_list' value *");
-	g_assert (nm_supplicant_config_add_setting_wireless (config,
-	                                                     s_wifi,
-	                                                     0,
-	                                                     &error));
-	g_assert_no_error (error);
-	g_test_assert_expected_messages ();
+static void
+test_wifi_eap_locked_bssid (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+	const char *bssid_str = "11:22:33:44:55:66";
+	guint32 mtu = 1100;
 
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'key_mgmt' value 'WPA-EAP'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'proto' value 'WPA RSN'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'pairwise' value 'TKIP CCMP'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*added 'group' value 'TKIP CCMP'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*Config: added 'eap' value 'TLS'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*Config: added 'fragment_size' value '1086'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "* Config: added 'ca_cert' value '*/test-ca-cert.pem'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "* Config: added 'private_key' value '*/test-cert.p12'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*Config: added 'bgscan' value 'simple:30:-65:300'");
-	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO,
-	                       "*Config: added 'proactive_key_caching' value '1'");
-	g_assert (nm_supplicant_config_add_setting_wireless_security (config,
-	                                                              s_wsec,
-	                                                              s_8021x,
-	                                                              "d5b488af-9cab-41ed-bad4-97709c58430f",
-	                                                              mtu,
-	                                                              &error));
-	g_assert_no_error (error);
+	connection = generate_wifi_eap_connection ("Test Wifi EAP-TLS Locked", ssid, bssid_str);
+
+	EXPECT ("*added 'ssid' value 'Test SSID'*");
+	EXPECT ("*added 'scan_ssid' value '1'*");
+	EXPECT ("*added 'bssid' value '11:22:33:44:55:66'*");
+	EXPECT ("*added 'freq_list' value *");
+	EXPECT ("*added 'key_mgmt' value 'WPA-EAP'");
+	EXPECT ("*added 'proto' value 'WPA RSN'");
+	EXPECT ("*added 'pairwise' value 'TKIP CCMP'");
+	EXPECT ("*added 'group' value 'TKIP CCMP'");
+	EXPECT ("*Config: added 'eap' value 'TLS'");
+	EXPECT ("*Config: added 'fragment_size' value '1086'");
+	EXPECT ("* Config: added 'ca_cert' value '*/test-ca-cert.pem'");
+	EXPECT ("* Config: added 'private_key' value '*/test-cert.p12'");
+	EXPECT ("*Config: added 'proactive_key_caching' value '1'");
+	config_dict = build_supplicant_config (connection, mtu, 0);
 	g_test_assert_expected_messages ();
+	g_assert (config_dict);
+
+	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt ("wifi-eap", config_dict, "bssid", TYPE_KEYWORD, bssid_str);
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP");
+	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS");
+	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
+	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14));
+}
 
-	config_dict = nm_supplicant_config_to_variant (config);
+static void
+test_wifi_eap_unlocked_bssid (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_variant GVariant *config_dict = NULL;
+	const unsigned char ssid_data[] = { 0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44 };
+	gs_unref_bytes GBytes *ssid = g_bytes_new (ssid_data, sizeof (ssid_data));
+	const char *bgscan_data = "simple:30:-65:300";
+	gs_unref_bytes GBytes *bgscan = g_bytes_new (bgscan_data, strlen (bgscan_data));
+	guint32 mtu = 1100;
+
+	connection = generate_wifi_eap_connection ("Test Wifi EAP-TLS Unlocked", ssid, NULL);
+
+	EXPECT ("*added 'ssid' value 'Test SSID'*");
+	EXPECT ("*added 'scan_ssid' value '1'*");
+	EXPECT ("*added 'freq_list' value *");
+	EXPECT ("*added 'key_mgmt' value 'WPA-EAP'");
+	EXPECT ("*added 'proto' value 'WPA RSN'");
+	EXPECT ("*added 'pairwise' value 'TKIP CCMP'");
+	EXPECT ("*added 'group' value 'TKIP CCMP'");
+	EXPECT ("*Config: added 'eap' value 'TLS'");
+	EXPECT ("*Config: added 'fragment_size' value '1086'");
+	EXPECT ("* Config: added 'ca_cert' value '*/test-ca-cert.pem'");
+	EXPECT ("* Config: added 'private_key' value '*/test-cert.p12'");
+	EXPECT ("*Config: added 'proactive_key_caching' value '1'");
+	EXPECT ("*Config: added 'bgscan' value 'simple:30:-65:300'");
+	config_dict = build_supplicant_config (connection, mtu, 0);
+	g_test_assert_expected_messages ();
 	g_assert (config_dict);
 
-	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1), -1);
-	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid_data, sizeof (ssid_data));
-	validate_opt ("wifi-eap", config_dict, "bssid", TYPE_KEYWORD, bssid_str, -1);
-	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP", -1);
-	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS", -1);
-	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN", -1);
-	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP", -1);
-	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP", -1);
-	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14), -1);
+	validate_opt ("wifi-eap", config_dict, "scan_ssid", TYPE_INT, GINT_TO_POINTER (1));
+	validate_opt ("wifi-eap", config_dict, "ssid", TYPE_BYTES, ssid);
+	validate_opt ("wifi-eap", config_dict, "key_mgmt", TYPE_KEYWORD, "WPA-EAP");
+	validate_opt ("wifi-eap", config_dict, "eap", TYPE_KEYWORD, "TLS");
+	validate_opt ("wifi-eap", config_dict, "proto", TYPE_KEYWORD, "WPA RSN");
+	validate_opt ("wifi-eap", config_dict, "pairwise", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "group", TYPE_KEYWORD, "TKIP CCMP");
+	validate_opt ("wifi-eap", config_dict, "fragment_size", TYPE_INT, GINT_TO_POINTER(mtu-14));
+	validate_opt ("wifi-eap", config_dict, "bgscan", TYPE_BYTES, bgscan);
 }
 
 NMTST_DEFINE ();
@@ -607,8 +514,8 @@ int main (int argc, char **argv)
 	g_test_add_func ("/supplicant-config/wifi-open", test_wifi_open);
 	g_test_add_func ("/supplicant-config/wifi-wep", test_wifi_wep);
 	g_test_add_func ("/supplicant-config/wifi-wpa-psk-types", test_wifi_wpa_psk_types);
-	g_test_add_func ("/supplicant-config/wifi-eap", test_wifi_eap);
+	g_test_add_func ("/supplicant-config/wifi-eap/locked-bssid", test_wifi_eap_locked_bssid);
+	g_test_add_func ("/supplicant-config/wifi-eap/unlocked-bssid", test_wifi_eap_unlocked_bssid);
 
 	return g_test_run ();
 }
-
diff --git a/src/systemd/sd-adapt/process-util.h b/src/systemd/sd-adapt/architecture.h
index 637892c2..637892c2 100644
--- a/src/systemd/sd-adapt/process-util.h
+++ b/src/systemd/sd-adapt/architecture.h
diff --git a/src/systemd/sd-adapt/btrfs-util.h b/src/systemd/sd-adapt/btrfs-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/btrfs-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/ioprio.h b/src/systemd/sd-adapt/ioprio.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/ioprio.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/nm-sd-adapt.h b/src/systemd/sd-adapt/nm-sd-adapt.h
index a8ff18bc..0d291e26 100644
--- a/src/systemd/sd-adapt/nm-sd-adapt.h
+++ b/src/systemd/sd-adapt/nm-sd-adapt.h
@@ -32,12 +32,24 @@
 #define CLOCK_BOOTTIME 7
 #endif
 
+#if defined(HAVE_DECL_EXPLICIT_BZERO) && HAVE_DECL_EXPLICIT_BZERO == 1
+#define HAVE_EXPLICIT_BZERO 1
+#else
+#define HAVE_EXPLICIT_BZERO 0
+#endif
+
+#define ENABLE_DEBUG_HASHMAP 0
+
+#ifndef HAVE_SYS_AUXV_H
+#define HAVE_SYS_AUXV_H 0
+#endif
+
 /*****************************************************************************/
 
 static inline NMLogLevel
 _slog_level_to_nm (int slevel)
 {
-    switch (slevel) {
+    switch (LOG_PRI (slevel)) {
     case LOG_DEBUG:   return LOGL_DEBUG;
 	case LOG_WARNING: return LOGL_WARN;
 	case LOG_CRIT:
@@ -48,7 +60,15 @@ _slog_level_to_nm (int slevel)
 	}
 }
 
-#define log_internal(level, error, file, line, func, format, ...) \
+static inline int
+_nm_log_get_max_level_realm (void)
+{
+	/* inline function, to avoid coverity warning about constant expression. */
+	return LOG_DEBUG;
+}
+#define log_get_max_level_realm(realm) _nm_log_get_max_level_realm ()
+
+#define log_internal_realm(level, error, file, line, func, format, ...) \
 ({ \
 	const int _nm_e = (error); \
 	const NMLogLevel _nm_l = _slog_level_to_nm ((level)); \
@@ -61,11 +81,6 @@ _slog_level_to_nm (int slevel)
 	(_nm_e > 0 ? -_nm_e : _nm_e); \
 })
 
-#define log_full_errno(level, error, ...) \
-({ \
-	log_internal(level, error, __FILE__, __LINE__, __func__, __VA_ARGS__); \
-})
-
 #define log_assert_failed(text, file, line, func) \
 G_STMT_START { \
 	log_internal (LOG_CRIT, 0, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.", text, file, line, func); \
@@ -171,10 +186,6 @@ static inline pid_t gettid(void) {
         return (pid_t) syscall(SYS_gettid);
 }
 
-static inline bool is_main_thread(void) {
-        return TRUE;
-}
-
 #endif /* (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_SYSTEMD */
 
 #endif /* NM_SD_ADAPT_H */
diff --git a/src/systemd/sd-adapt/raw-clone.h b/src/systemd/sd-adapt/raw-clone.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/systemd/sd-adapt/raw-clone.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/systemd/sd-adapt/udev.h b/src/systemd/sd-adapt/udev.h
index 00f60f92..419abcbf 100644
--- a/src/systemd/sd-adapt/udev.h
+++ b/src/systemd/sd-adapt/udev.h
@@ -3,3 +3,4 @@
 /* dummy header */
 
 #include "libudev.h"
+#include "strv.h"
diff --git a/src/systemd/src/basic/alloc-util.c b/src/systemd/src/basic/alloc-util.c
index 5fae60c9..97588312 100644
--- a/src/systemd/src/basic/alloc-util.c
+++ b/src/systemd/src/basic/alloc-util.c
@@ -27,16 +27,31 @@
 #include "util.h"
 
 void* memdup(const void *p, size_t l) {
-        void *r;
+        void *ret;
 
-        assert(p);
+        assert(l == 0 || p);
+
+        ret = malloc(l);
+        if (!ret)
+                return NULL;
+
+        memcpy(ret, p, l);
+        return ret;
+}
+
+void* memdup_suffix0(const void*p, size_t l) {
+        void *ret;
+
+        assert(l == 0 || p);
+
+        /* The same as memdup() but place a safety NUL byte after the allocated memory */
 
-        r = malloc(l);
-        if (!r)
+        ret = malloc(l + 1);
+        if (!ret)
                 return NULL;
 
-        memcpy(r, p, l);
-        return r;
+        *((uint8_t*) mempcpy(ret, p, l)) = 0;
+        return ret;
 }
 
 void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) {
diff --git a/src/systemd/src/basic/alloc-util.h b/src/systemd/src/basic/alloc-util.h
index a44dd473..0a89691b 100644
--- a/src/systemd/src/basic/alloc-util.h
+++ b/src/systemd/src/basic/alloc-util.h
@@ -36,6 +36,8 @@
 
 #define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n)))
 
+#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n)))
+
 #define malloc0(n) (calloc(1, (n)))
 
 static inline void *mfree(void *memory) {
@@ -52,6 +54,7 @@ static inline void *mfree(void *memory) {
         })
 
 void* memdup(const void *p, size_t l) _alloc_(2);
+void* memdup_suffix0(const void*p, size_t l) _alloc_(2);
 
 static inline void freep(void *p) {
         free(*(void**) p);
@@ -84,6 +87,13 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si
         return memdup(p, size * need);
 }
 
+_alloc_(2, 3) static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) {
+        if (size_multiply_overflow(size, need))
+                return NULL;
+
+        return memdup_suffix0(p, size * need);
+}
+
 void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size);
 void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size);
 
diff --git a/src/systemd/src/basic/escape.c b/src/systemd/src/basic/escape.c
index ac96f0ee..27a20702 100644
--- a/src/systemd/src/basic/escape.c
+++ b/src/systemd/src/basic/escape.c
@@ -316,7 +316,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
 
         /* Undoes C style string escaping, and optionally prefixes it. */
 
-        pl = prefix ? strlen(prefix) : 0;
+        pl = strlen_ptr(prefix);
 
         r = new(char, pl+length+1);
         if (!r)
@@ -428,7 +428,7 @@ char *octescape(const char *s, size_t len) {
 
         for (f = s, t = r; f < s + len; f++) {
 
-                if (*f < ' ' || *f >= 127 || *f == '\\' || *f == '"') {
+                if (*f < ' ' || *f >= 127 || IN_SET(*f, '\\', '"')) {
                         *(t++) = '\\';
                         *(t++) = '0' + (*f >> 6);
                         *(t++) = '0' + ((*f >> 3) & 8);
@@ -443,10 +443,16 @@ char *octescape(const char *s, size_t len) {
 
 }
 
-static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
+static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad, bool escape_tab_nl) {
         assert(bad);
 
         for (; *s; s++) {
+                if (escape_tab_nl && IN_SET(*s, '\n', '\t')) {
+                        *(t++) = '\\';
+                        *(t++) = *s == '\n' ? 'n' : 't';
+                        continue;
+                }
+
                 if (*s == '\\' || strchr(bad, *s))
                         *(t++) = '\\';
 
@@ -463,20 +469,21 @@ char *shell_escape(const char *s, const char *bad) {
         if (!r)
                 return NULL;
 
-        t = strcpy_backslash_escaped(r, s, bad);
+        t = strcpy_backslash_escaped(r, s, bad, false);
         *t = 0;
 
         return r;
 }
 
-char *shell_maybe_quote(const char *s) {
+char* shell_maybe_quote(const char *s, EscapeStyle style) {
         const char *p;
         char *r, *t;
 
         assert(s);
 
-        /* Encloses a string in double quotes if necessary to make it
-         * OK as shell string. */
+        /* Encloses a string in quotes if necessary to make it OK as a shell
+         * string. Note that we treat benign UTF-8 characters as needing
+         * escaping too, but that should be OK. */
 
         for (p = s; *p; p++)
                 if (*p <= ' ' ||
@@ -487,17 +494,30 @@ char *shell_maybe_quote(const char *s) {
         if (!*p)
                 return strdup(s);
 
-        r = new(char, 1+strlen(s)*2+1+1);
+        r = new(char, (style == ESCAPE_POSIX) + 1 + strlen(s)*2 + 1 + 1);
         if (!r)
                 return NULL;
 
         t = r;
-        *(t++) = '"';
+        if (style == ESCAPE_BACKSLASH)
+                *(t++) = '"';
+        else if (style == ESCAPE_POSIX) {
+                *(t++) = '$';
+                *(t++) = '\'';
+        } else
+                assert_not_reached("Bad EscapeStyle");
+
         t = mempcpy(t, s, p - s);
 
-        t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE);
+        if (style == ESCAPE_BACKSLASH)
+                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, false);
+        else
+                t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true);
 
-        *(t++)= '"';
+        if (style == ESCAPE_BACKSLASH)
+                *(t++) = '"';
+        else
+                *(t++) = '\'';
         *t = 0;
 
         return r;
diff --git a/src/systemd/src/basic/escape.h b/src/systemd/src/basic/escape.h
index 24729dc1..e62347af 100644
--- a/src/systemd/src/basic/escape.h
+++ b/src/systemd/src/basic/escape.h
@@ -33,13 +33,30 @@
 /* What characters are special in the shell? */
 /* must be escaped outside and inside double-quotes */
 #define SHELL_NEED_ESCAPE "\"\\`$"
-/* can be escaped or double-quoted */
-#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;"
+
+/* Those that can be escaped or double-quoted.
+ *
+ * Stricly speaking, ! does not need to be escaped, except in interactive
+ * mode, but let's be extra nice to the user and quote ! in case this
+ * output is ever used in interactive mode. */
+#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;!"
+
+/* Note that we assume control characters would need to be escaped too in
+ * addition to the "special" characters listed here, if they appear in the
+ * string. Current users disallow control characters. Also '"' shall not
+ * be escaped.
+ */
+#define SHELL_NEED_ESCAPE_POSIX "\\\'"
 
 typedef enum UnescapeFlags {
         UNESCAPE_RELAX = 1,
 } UnescapeFlags;
 
+typedef enum EscapeStyle {
+        ESCAPE_BACKSLASH = 1,
+        ESCAPE_POSIX = 2,
+} EscapeStyle;
+
 char *cescape(const char *s);
 char *cescape_length(const char *s, size_t n);
 size_t cescape_char(char c, char *buf);
@@ -53,4 +70,4 @@ char *xescape(const char *s, const char *bad);
 char *octescape(const char *s, size_t len);
 
 char *shell_escape(const char *s, const char *bad);
-char *shell_maybe_quote(const char *s);
+char* shell_maybe_quote(const char *s, EscapeStyle style);
diff --git a/src/systemd/src/basic/extract-word.c b/src/systemd/src/basic/extract-word.c
index 734712dc..69d8c48d 100644
--- a/src/systemd/src/basic/extract-word.c
+++ b/src/systemd/src/basic/extract-word.c
@@ -154,7 +154,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
                         for (;; (*p)++, c = **p) {
                                 if (c == 0)
                                         goto finish_force_terminate;
-                                else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) {
+                                else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_QUOTES)) {
                                         quote = c;
                                         break;
                                 } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
@@ -244,7 +244,12 @@ int extract_first_word_and_warn(
         return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
 }
 
-int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) {
+/* We pass ExtractFlags as unsigned int (to avoid undefined behaviour when passing
+ * an object that undergoes default argument promotion as an argument to va_start).
+ * Let's make sure that ExtractFlags fits into an unsigned int. */
+assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned));
+
+int extract_many_words(const char **p, const char *separators, unsigned flags, ...) {
         va_list ap;
         char **l;
         int n = 0, i, c, r;
diff --git a/src/systemd/src/basic/extract-word.h b/src/systemd/src/basic/extract-word.h
index 21db5ef3..04746c6d 100644
--- a/src/systemd/src/basic/extract-word.h
+++ b/src/systemd/src/basic/extract-word.h
@@ -32,4 +32,4 @@ typedef enum ExtractFlags {
 
 int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags);
 int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue);
-int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_;
+int extract_many_words(const char **p, const char *separators, unsigned flags, ...) _sentinel_;
diff --git a/src/systemd/src/basic/fd-util.c b/src/systemd/src/basic/fd-util.c
index d1c988e1..1c327d83 100644
--- a/src/systemd/src/basic/fd-util.c
+++ b/src/systemd/src/basic/fd-util.c
@@ -33,6 +33,7 @@
 #include "missing.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "process-util.h"
 #include "socket-util.h"
 #include "stdio-util.h"
 #include "util.h"
@@ -285,7 +286,7 @@ int same_fd(int a, int b) {
                 return true;
 
         /* Try to use kcmp() if we have it. */
-        pid = getpid();
+        pid = getpid_cached();
         r = kcmp(pid, pid, KCMP_FILE, a, b);
         if (r == 0)
                 return true;
diff --git a/src/systemd/src/basic/fileio.c b/src/systemd/src/basic/fileio.c
index 711580a4..51d1c052 100644
--- a/src/systemd/src/basic/fileio.c
+++ b/src/systemd/src/basic/fileio.c
@@ -32,6 +32,7 @@
 
 #include "alloc-util.h"
 #include "ctype.h"
+#include "def.h"
 #include "env-util.h"
 #include "escape.h"
 #include "fd-util.h"
@@ -43,6 +44,7 @@
 #include "missing.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "process-util.h"
 #include "random-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
@@ -53,19 +55,39 @@
 
 #define READ_FULL_BYTES_MAX (4U*1024U*1024U)
 
-int write_string_stream(FILE *f, const char *line, bool enforce_newline) {
+#if 0 /* NM_IGNORED */
+int write_string_stream_ts(
+                FILE *f,
+                const char *line,
+                WriteStringFileFlags flags,
+                struct timespec *ts) {
 
         assert(f);
         assert(line);
 
         fputs(line, f);
-        if (enforce_newline && !endswith(line, "\n"))
+        if (!(flags & WRITE_STRING_FILE_AVOID_NEWLINE) && !endswith(line, "\n"))
                 fputc('\n', f);
 
-        return fflush_and_check(f);
+        if (ts) {
+                struct timespec twice[2] = {*ts, *ts};
+
+                if (futimens(fileno(f), twice) < 0)
+                        return -errno;
+        }
+
+        if (flags & WRITE_STRING_FILE_SYNC)
+                return fflush_sync_and_check(f);
+        else
+                return fflush_and_check(f);
 }
 
-static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) {
+static int write_string_file_atomic(
+                const char *fn,
+                const char *line,
+                WriteStringFileFlags flags,
+                struct timespec *ts) {
+
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
         int r;
@@ -79,34 +101,47 @@ static int write_string_file_atomic(const char *fn, const char *line, bool enfor
 
         (void) fchmod_umask(fileno(f), 0644);
 
-        r = write_string_stream(f, line, enforce_newline);
-        if (r >= 0) {
-                if (rename(p, fn) < 0)
-                        r = -errno;
+        r = write_string_stream_ts(f, line, flags, ts);
+        if (r < 0)
+                goto fail;
+
+        if (rename(p, fn) < 0) {
+                r = -errno;
+                goto fail;
         }
 
-        if (r < 0)
-                (void) unlink(p);
+        return 0;
 
+fail:
+        (void) unlink(p);
         return r;
 }
 
-int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
+int write_string_file_ts(
+                const char *fn,
+                const char *line,
+                WriteStringFileFlags flags,
+                struct timespec *ts) {
+
         _cleanup_fclose_ FILE *f = NULL;
         int q, r;
 
         assert(fn);
         assert(line);
 
+        /* We don't know how to verify whether the file contents was already on-disk. */
+        assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC)));
+
         if (flags & WRITE_STRING_FILE_ATOMIC) {
                 assert(flags & WRITE_STRING_FILE_CREATE);
 
-                r = write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
+                r = write_string_file_atomic(fn, line, flags, ts);
                 if (r < 0)
                         goto fail;
 
                 return r;
-        }
+        } else
+                assert(ts == NULL);
 
         if (flags & WRITE_STRING_FILE_CREATE) {
                 f = fopen(fn, "we");
@@ -133,7 +168,7 @@ int write_string_file(const char *fn, const char *line, WriteStringFileFlags fla
                 }
         }
 
-        r = write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE));
+        r = write_string_stream_ts(f, line, flags, ts);
         if (r < 0)
                 goto fail;
 
@@ -157,7 +192,7 @@ fail:
 
 int read_one_line_file(const char *fn, char **line) {
         _cleanup_fclose_ FILE *f = NULL;
-        char t[LINE_MAX], *c;
+        int r;
 
         assert(fn);
         assert(line);
@@ -166,22 +201,10 @@ int read_one_line_file(const char *fn, char **line) {
         if (!f)
                 return -errno;
 
-        if (!fgets(t, sizeof(t), f)) {
-
-                if (ferror(f))
-                        return errno > 0 ? -errno : -EIO;
-
-                t[0] = 0;
-        }
-
-        c = strdup(t);
-        if (!c)
-                return -ENOMEM;
-        truncate_nl(c);
-
-        *line = c;
-        return 0;
+        r = read_line(f, LONG_LINE_MAX, line);
+        return r < 0 ? r : 0;
 }
+#endif /* NM_IGNORED */
 
 int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         _cleanup_fclose_ FILE *f = NULL;
@@ -239,11 +262,11 @@ int read_full_stream(FILE *f, char **contents, size_t *size) {
                 if (st.st_size > READ_FULL_BYTES_MAX)
                         return -E2BIG;
 
-                /* Start with the right file size, but be prepared for
-                 * files from /proc which generally report a file size
-                 * of 0 */
+                /* Start with the right file size, but be prepared for files from /proc which generally report a file
+                 * size of 0. Note that we increase the size to read here by one, so that the first read attempt
+                 * already makes us notice the EOF. */
                 if (st.st_size > 0)
-                        n = st.st_size;
+                        n = st.st_size + 1;
         }
 
         l = 0;
@@ -256,12 +279,13 @@ int read_full_stream(FILE *f, char **contents, size_t *size) {
                         return -ENOMEM;
 
                 buf = t;
+                errno = 0;
                 k = fread(buf + l, 1, n - l, f);
                 if (k > 0)
                         l += k;
 
                 if (ferror(f))
-                        return -errno;
+                        return errno > 0 ? -errno : -EIO;
 
                 if (feof(f))
                         break;
@@ -818,29 +842,29 @@ static void write_env_var(FILE *f, const char *v) {
         p = strchr(v, '=');
         if (!p) {
                 /* Fallback */
-                fputs(v, f);
-                fputc('\n', f);
+                fputs_unlocked(v, f);
+                fputc_unlocked('\n', f);
                 return;
         }
 
         p++;
-        fwrite(v, 1, p-v, f);
+        fwrite_unlocked(v, 1, p-v, f);
 
         if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) {
-                fputc('\"', f);
+                fputc_unlocked('\"', f);
 
                 for (; *p; p++) {
                         if (strchr(SHELL_NEED_ESCAPE, *p))
-                                fputc('\\', f);
+                                fputc_unlocked('\\', f);
 
-                        fputc(*p, f);
+                        fputc_unlocked(*p, f);
                 }
 
-                fputc('\"', f);
+                fputc_unlocked('\"', f);
         } else
-                fputs(p, f);
+                fputs_unlocked(p, f);
 
-        fputc('\n', f);
+        fputc_unlocked('\n', f);
 }
 
 int write_env_file(const char *fname, char **l) {
@@ -871,7 +895,6 @@ int write_env_file(const char *fname, char **l) {
         unlink(p);
         return r;
 }
-#endif /* NM_IGNORED */
 
 int executable_is_script(const char *path, char **interpreter) {
         int r;
@@ -901,6 +924,7 @@ int executable_is_script(const char *path, char **interpreter) {
         *interpreter = ans;
         return 1;
 }
+#endif /* NM_IGNORED */
 
 /**
  * Retrieve one field from a file like /proc/self/status.  pattern
@@ -1123,6 +1147,21 @@ int fflush_and_check(FILE *f) {
         return 0;
 }
 
+int fflush_sync_and_check(FILE *f) {
+        int r;
+
+        assert(f);
+
+        r = fflush_and_check(f);
+        if (r < 0)
+                return r;
+
+        if (fsync(fileno(f)) < 0)
+                return -errno;
+
+        return 0;
+}
+
 /* This is much like mkostemp() but is subject to umask(). */
 int mkostemp_safe(char *pattern) {
         _cleanup_umask_ mode_t u = 0;
@@ -1171,6 +1210,7 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
 int tempfn_random(const char *p, const char *extra, char **ret) {
         const char *fn;
         char *t, *x;
@@ -1213,7 +1253,6 @@ int tempfn_random(const char *p, const char *extra, char **ret) {
         return 0;
 }
 
-#if 0 /* NM_IGNORED */
 int tempfn_random_child(const char *p, const char *extra, char **ret) {
         char *t, *x;
         uint64_t u;
@@ -1400,7 +1439,7 @@ int open_serialization_fd(const char *ident) {
         if (fd < 0) {
                 const char *path;
 
-                path = getpid() == 1 ? "/run/systemd" : "/tmp";
+                path = getpid_cached() == 1 ? "/run/systemd" : "/tmp";
                 fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC);
                 if (fd < 0)
                         return fd;
@@ -1498,4 +1537,78 @@ int mkdtemp_malloc(const char *template, char **ret) {
         *ret = p;
         return 0;
 }
+
+static inline void funlockfilep(FILE **f) {
+        funlockfile(*f);
+}
+
+int read_line(FILE *f, size_t limit, char **ret) {
+        _cleanup_free_ char *buffer = NULL;
+        size_t n = 0, allocated = 0, count = 0;
+
+        assert(f);
+
+        /* Something like a bounded version of getline().
+         *
+         * Considers EOF, \n and \0 end of line delimiters, and does not include these delimiters in the string
+         * returned.
+         *
+         * Returns the number of bytes read from the files (i.e. including delimiters — this hence usually differs from
+         * the number of characters in the returned string). When EOF is hit, 0 is returned.
+         *
+         * The input parameter limit is the maximum numbers of characters in the returned string, i.e. excluding
+         * delimiters. If the limit is hit we fail and return -ENOBUFS.
+         *
+         * If a line shall be skipped ret may be initialized as NULL. */
+
+        if (ret) {
+                if (!GREEDY_REALLOC(buffer, allocated, 1))
+                        return -ENOMEM;
+        }
+
+        {
+                _cleanup_(funlockfilep) FILE *flocked = f;
+                flockfile(f);
+
+                for (;;) {
+                        int c;
+
+                        if (n >= limit)
+                                return -ENOBUFS;
+
+                        errno = 0;
+                        c = fgetc_unlocked(f);
+                        if (c == EOF) {
+                                /* if we read an error, and have no data to return, then propagate the error */
+                                if (ferror_unlocked(f) && n == 0)
+                                        return errno > 0 ? -errno : -EIO;
+
+                                break;
+                        }
+
+                        count++;
+
+                        if (IN_SET(c, '\n', 0)) /* Reached a delimiter */
+                                break;
+
+                        if (ret) {
+                                if (!GREEDY_REALLOC(buffer, allocated, n + 2))
+                                        return -ENOMEM;
+
+                                buffer[n] = (char) c;
+                        }
+
+                        n++;
+                }
+        }
+
+        if (ret) {
+                buffer[n] = 0;
+
+                *ret = buffer;
+                buffer = NULL;
+        }
+
+        return (int) count;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/fileio.h b/src/systemd/src/basic/fileio.h
index e547614c..eba05be2 100644
--- a/src/systemd/src/basic/fileio.h
+++ b/src/systemd/src/basic/fileio.h
@@ -29,14 +29,21 @@
 #include "time-util.h"
 
 typedef enum {
-        WRITE_STRING_FILE_CREATE = 1,
-        WRITE_STRING_FILE_ATOMIC = 2,
-        WRITE_STRING_FILE_AVOID_NEWLINE = 4,
-        WRITE_STRING_FILE_VERIFY_ON_FAILURE = 8,
+        WRITE_STRING_FILE_CREATE = 1<<0,
+        WRITE_STRING_FILE_ATOMIC = 1<<1,
+        WRITE_STRING_FILE_AVOID_NEWLINE = 1<<2,
+        WRITE_STRING_FILE_VERIFY_ON_FAILURE = 1<<3,
+        WRITE_STRING_FILE_SYNC = 1<<4,
 } WriteStringFileFlags;
 
-int write_string_stream(FILE *f, const char *line, bool enforce_newline);
-int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags);
+int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags, struct timespec *ts);
+static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
+        return write_string_stream_ts(f, line, flags, NULL);
+}
+int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, struct timespec *ts);
+static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
+        return write_string_file_ts(fn, line, flags, NULL);
+}
 
 int read_one_line_file(const char *fn, char **line);
 int read_full_file(const char *fn, char **contents, size_t *size);
@@ -71,6 +78,7 @@ int search_and_fopen_nulstr(const char *path, const char *mode, const char *root
                 } else
 
 int fflush_and_check(FILE *f);
+int fflush_sync_and_check(FILE *f);
 
 int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
 int mkostemp_safe(char *pattern);
@@ -93,3 +101,5 @@ int link_tmpfile(int fd, const char *path, const char *target);
 int read_nul_string(FILE *f, char **ret);
 
 int mkdtemp_malloc(const char *template, char **ret);
+
+int read_line(FILE *f, size_t limit, char **ret);
diff --git a/src/systemd/src/basic/fs-util.c b/src/systemd/src/basic/fs-util.c
index 5e980329..ff4ad5ab 100644
--- a/src/systemd/src/basic/fs-util.c
+++ b/src/systemd/src/basic/fs-util.c
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <linux/magic.h>
 #include <time.h>
 #include <unistd.h>
 
@@ -314,7 +315,7 @@ int fd_warn_permissions(const char *path, int fd) {
         if (st.st_mode & 0002)
                 log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
 
-        if (getpid() == 1 && (st.st_mode & 0044) != 0044)
+        if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
                 log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
 
         return 0;
@@ -330,7 +331,7 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi
                 mkdir_parents(path, 0755);
 
         fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
-                        (mode == 0 || mode == MODE_INVALID) ? 0644 : mode);
+                  IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
         if (fd < 0)
                 return -errno;
 
@@ -365,22 +366,25 @@ int touch(const char *path) {
 }
 
 int symlink_idempotent(const char *from, const char *to) {
-        _cleanup_free_ char *p = NULL;
         int r;
 
         assert(from);
         assert(to);
 
         if (symlink(from, to) < 0) {
+                _cleanup_free_ char *p = NULL;
+
                 if (errno != EEXIST)
                         return -errno;
 
                 r = readlink_malloc(to, &p);
-                if (r < 0)
+                if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
+                        return -EEXIST;
+                if (r < 0) /* Any other error? In that case propagate it as is */
                         return r;
 
-                if (!streq(p, from))
-                        return -EINVAL;
+                if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
+                        return -EEXIST;
         }
 
         return 0;
@@ -728,6 +732,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                 if (fstat(child, &st) < 0)
                         return -errno;
+                if ((flags & CHASE_NO_AUTOFS) &&
+                    fd_check_fstype(child, AUTOFS_SUPER_MAGIC) > 0)
+                        return -EREMOTE;
 
                 if (S_ISLNK(st.st_mode)) {
                         char *joined;
diff --git a/src/systemd/src/basic/fs-util.h b/src/systemd/src/basic/fs-util.h
index 094acf17..d3342d5c 100644
--- a/src/systemd/src/basic/fs-util.h
+++ b/src/systemd/src/basic/fs-util.h
@@ -81,6 +81,7 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask);
 enum {
         CHASE_PREFIX_ROOT = 1,   /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
         CHASE_NONEXISTENT = 2,   /* If set, it's OK if the path doesn't actually exist. */
+        CHASE_NO_AUTOFS = 4,     /* If set, return -EREMOTE if autofs mount point found */
 };
 
 int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
diff --git a/src/systemd/src/basic/hashmap.c b/src/systemd/src/basic/hashmap.c
index dc6bcab0..c95d4991 100644
--- a/src/systemd/src/basic/hashmap.c
+++ b/src/systemd/src/basic/hashmap.c
@@ -36,7 +36,7 @@
 #include "strv.h"
 #include "util.h"
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
 #include <pthread.h>
 #include "list.h"
 #endif
@@ -144,7 +144,7 @@ typedef uint8_t dib_raw_t;
 
 #define DIB_FREE UINT_MAX
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
 struct hashmap_debug_info {
         LIST_FIELDS(struct hashmap_debug_info, debug_list);
         unsigned max_entries;  /* high watermark of n_entries */
@@ -501,7 +501,7 @@ static void base_remove_entry(HashmapBase *h, unsigned idx) {
         dibs = dib_raw_ptr(h);
         assert(dibs[idx] != DIB_RAW_FREE);
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         h->debug.rem_count++;
         h->debug.last_rem_idx = idx;
 #endif
@@ -510,7 +510,7 @@ static void base_remove_entry(HashmapBase *h, unsigned idx) {
         /* Find the stop bucket ("right"). It is either free or has DIB == 0. */
         for (right = next_idx(h, left); ; right = next_idx(h, right)) {
                 raw_dib = dibs[right];
-                if (raw_dib == 0 || raw_dib == DIB_RAW_FREE)
+                if (IN_SET(raw_dib, 0, DIB_RAW_FREE))
                         break;
 
                 /* The buckets are not supposed to be all occupied and with DIB > 0.
@@ -580,7 +580,7 @@ static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *
                 assert(e->p.b.key == i->next_key);
         }
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         i->prev_idx = idx;
 #endif
 
@@ -637,7 +637,7 @@ static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) {
         }
 
         idx = i->idx;
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         i->prev_idx = idx;
 #endif
 
@@ -660,7 +660,7 @@ static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) {
                 return IDX_NIL;
         }
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         if (i->idx == IDX_FIRST) {
                 i->put_count = h->debug.put_count;
                 i->rem_count = h->debug.rem_count;
@@ -752,7 +752,7 @@ static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enu
                 shared_hash_key_initialized= true;
         }
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         h->debug.func = func;
         h->debug.file = file;
         h->debug.line = line;
@@ -809,7 +809,7 @@ static void hashmap_free_no_clear(HashmapBase *h) {
         assert(!h->has_indirect);
         assert(!h->n_direct_entries);
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0);
         LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug);
         assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
@@ -921,7 +921,7 @@ static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx,
         dib_raw_t raw_dib, *dibs;
         unsigned dib, distance;
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         h->debug.put_count++;
 #endif
 
@@ -929,7 +929,7 @@ static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx,
 
         for (distance = 0; ; distance++) {
                 raw_dib = dibs[idx];
-                if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) {
+                if (IN_SET(raw_dib, DIB_RAW_FREE, DIB_RAW_REHASH)) {
                         if (raw_dib == DIB_RAW_REHASH)
                                 bucket_move_entry(h, swap, idx, IDX_TMP);
 
@@ -1014,7 +1014,7 @@ static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx,
         assert_se(hashmap_put_robin_hood(h, idx, swap) == false);
 
         n_entries_inc(h);
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h));
 #endif
 
@@ -1242,7 +1242,7 @@ int hashmap_replace(Hashmap *h, const void *key, void *value) {
         idx = bucket_scan(h, hash, key);
         if (idx != IDX_NIL) {
                 e = plain_bucket_at(h, idx);
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
                 /* Although the key is equal, the key pointer may have changed,
                  * and this would break our assumption for iterating. So count
                  * this operation as incompatible with iteration. */
diff --git a/src/systemd/src/basic/hashmap.h b/src/systemd/src/basic/hashmap.h
index 6d1ae48b..c1089652 100644
--- a/src/systemd/src/basic/hashmap.h
+++ b/src/systemd/src/basic/hashmap.h
@@ -58,7 +58,7 @@ typedef struct Set Set;                       /* Stores just keys */
 typedef struct {
         unsigned idx;         /* index of an entry to be iterated next */
         const void *next_key; /* expected value of that entry's key pointer */
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
         unsigned put_count;   /* hashmap's put_count recorded at start of iteration */
         unsigned rem_count;   /* hashmap's rem_count in previous iteration */
         unsigned prev_idx;    /* idx in previous iteration */
@@ -89,7 +89,7 @@ typedef struct {
                 (Hashmap*)(h), \
                 (void)0)
 
-#ifdef ENABLE_DEBUG_HASHMAP
+#if ENABLE_DEBUG_HASHMAP
 # define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line
 # define HASHMAP_DEBUG_SRC_ARGS   , __func__, __FILE__, __LINE__
 # define HASHMAP_DEBUG_PASS_ARGS   , func, file, line
diff --git a/src/systemd/src/basic/hexdecoct.c b/src/systemd/src/basic/hexdecoct.c
index 2da8f8a2..e0ae83c1 100644
--- a/src/systemd/src/basic/hexdecoct.c
+++ b/src/systemd/src/basic/hexdecoct.c
@@ -27,6 +27,7 @@
 #include "alloc-util.h"
 #include "hexdecoct.h"
 #include "macro.h"
+#include "string-util.h"
 #include "util.h"
 
 char octchar(int x) {
@@ -571,7 +572,7 @@ static int base64_append_width(char **prefix, int plen,
 
         lines = (len + width - 1) / width;
 
-        slen = sep ? strlen(sep) : 0;
+        slen = strlen_ptr(sep);
         t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines);
         if (!t)
                 return -ENOMEM;
diff --git a/src/systemd/src/basic/hostname-util.c b/src/systemd/src/basic/hostname-util.c
index 823aa26a..be6e9e58 100644
--- a/src/systemd/src/basic/hostname-util.c
+++ b/src/systemd/src/basic/hostname-util.c
@@ -94,9 +94,7 @@ static bool hostname_valid_char(char c) {
                 (c >= 'a' && c <= 'z') ||
                 (c >= 'A' && c <= 'Z') ||
                 (c >= '0' && c <= '9') ||
-                c == '-' ||
-                c == '_' ||
-                c == '.';
+                IN_SET(c, '-', '_', '.');
 }
 
 /**
@@ -201,8 +199,11 @@ bool is_gateway_hostname(const char *hostname) {
          * synthetic "gateway" host. */
 
         return
-                strcaseeq(hostname, "gateway") ||
-                strcaseeq(hostname, "gateway.");
+                strcaseeq(hostname, "_gateway") || strcaseeq(hostname, "_gateway.")
+#if ENABLE_COMPAT_GATEWAY_HOSTNAME
+                || strcaseeq(hostname, "gateway") || strcaseeq(hostname, "gateway.")
+#endif
+                ;
 }
 
 int sethostname_idempotent(const char *s) {
@@ -237,7 +238,7 @@ int read_hostname_config(const char *path, char **hostname) {
         /* may have comments, ignore them */
         FOREACH_LINE(l, f, return -errno) {
                 truncate_nl(l);
-                if (l[0] != '\0' && l[0] != '#') {
+                if (!IN_SET(l[0], '\0', '#')) {
                         /* found line with value */
                         name = hostname_cleanup(l);
                         name = strdup(name);
diff --git a/src/systemd/src/basic/in-addr-util.c b/src/systemd/src/basic/in-addr-util.c
index 1140ca76..2a02d90b 100644
--- a/src/systemd/src/basic/in-addr-util.c
+++ b/src/systemd/src/basic/in-addr-util.c
@@ -310,22 +310,22 @@ int in_addr_from_string(int family, const char *s, union in_addr_union *ret) {
         return 0;
 }
 
-int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret) {
+int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret) {
         int r;
 
         assert(s);
 
         r = in_addr_from_string(AF_INET, s, ret);
         if (r >= 0) {
-                if (family)
-                        *family = AF_INET;
+                if (ret_family)
+                        *ret_family = AF_INET;
                 return 0;
         }
 
         r = in_addr_from_string(AF_INET6, s, ret);
         if (r >= 0) {
-                if (family)
-                        *family = AF_INET6;
+                if (ret_family)
+                        *ret_family = AF_INET6;
                 return 0;
         }
 
@@ -373,13 +373,13 @@ int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_u
         return r;
 }
 
-unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) {
+unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr) {
         assert(addr);
 
         return 32 - u32ctz(be32toh(addr->s_addr));
 }
 
-struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) {
+struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) {
         assert(addr);
         assert(prefixlen <= 32);
 
@@ -392,7 +392,7 @@ struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char
         return addr;
 }
 
-int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) {
+int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) {
         uint8_t msb_octet = *(uint8_t*) addr;
 
         /* addr may not be aligned, so make sure we only access it byte-wise */
@@ -416,28 +416,29 @@ int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixl
         return 0;
 }
 
-int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) {
+int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) {
         unsigned char prefixlen;
         int r;
 
         assert(addr);
         assert(mask);
 
-        r = in_addr_default_prefixlen(addr, &prefixlen);
+        r = in4_addr_default_prefixlen(addr, &prefixlen);
         if (r < 0)
                 return r;
 
-        in_addr_prefixlen_to_netmask(mask, prefixlen);
+        in4_addr_prefixlen_to_netmask(mask, prefixlen);
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
 int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) {
         assert(addr);
 
         if (family == AF_INET) {
                 struct in_addr mask;
 
-                if (!in_addr_prefixlen_to_netmask(&mask, prefixlen))
+                if (!in4_addr_prefixlen_to_netmask(&mask, prefixlen))
                         return -EINVAL;
 
                 addr->in.s_addr &= mask.s_addr;
@@ -466,3 +467,128 @@ int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen)
 
         return -EAFNOSUPPORT;
 }
+
+int in_addr_prefix_covers(int family,
+                          const union in_addr_union *prefix,
+                          unsigned char prefixlen,
+                          const union in_addr_union *address) {
+
+        union in_addr_union masked_prefix, masked_address;
+        int r;
+
+        assert(prefix);
+        assert(address);
+
+        masked_prefix = *prefix;
+        r = in_addr_mask(family, &masked_prefix, prefixlen);
+        if (r < 0)
+                return r;
+
+        masked_address = *address;
+        r = in_addr_mask(family, &masked_address, prefixlen);
+        if (r < 0)
+                return r;
+
+        return in_addr_equal(family, &masked_prefix, &masked_address);
+}
+
+int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret) {
+        uint8_t u;
+        int r;
+
+        if (!IN_SET(family, AF_INET, AF_INET6))
+                return -EAFNOSUPPORT;
+
+        r = safe_atou8(p, &u);
+        if (r < 0)
+                return r;
+
+        if (u > FAMILY_ADDRESS_SIZE(family) * 8)
+                return -ERANGE;
+
+        *ret = u;
+        return 0;
+}
+
+int in_addr_prefix_from_string(
+                const char *p,
+                int family,
+                union in_addr_union *ret_prefix,
+                unsigned char *ret_prefixlen) {
+
+        union in_addr_union buffer;
+        const char *e, *l;
+        unsigned char k;
+        int r;
+
+        assert(p);
+
+        if (!IN_SET(family, AF_INET, AF_INET6))
+                return -EAFNOSUPPORT;
+
+        e = strchr(p, '/');
+        if (e)
+                l = strndupa(p, e - p);
+        else
+                l = p;
+
+        r = in_addr_from_string(family, l, &buffer);
+        if (r < 0)
+                return r;
+
+        if (e) {
+                r = in_addr_parse_prefixlen(family, e+1, &k);
+                if (r < 0)
+                        return r;
+        } else
+                k = FAMILY_ADDRESS_SIZE(family) * 8;
+
+        if (ret_prefix)
+                *ret_prefix = buffer;
+        if (ret_prefixlen)
+                *ret_prefixlen = k;
+
+        return 0;
+}
+
+int in_addr_prefix_from_string_auto(
+                const char *p,
+                int *ret_family,
+                union in_addr_union *ret_prefix,
+                unsigned char *ret_prefixlen) {
+
+        union in_addr_union buffer;
+        const char *e, *l;
+        unsigned char k;
+        int family, r;
+
+        assert(p);
+
+        e = strchr(p, '/');
+        if (e)
+                l = strndupa(p, e - p);
+        else
+                l = p;
+
+        r = in_addr_from_string_auto(l, &family, &buffer);
+        if (r < 0)
+                return r;
+
+        if (e) {
+                r = in_addr_parse_prefixlen(family, e+1, &k);
+                if (r < 0)
+                        return r;
+        } else
+                k = FAMILY_ADDRESS_SIZE(family) * 8;
+
+        if (ret_family)
+                *ret_family = family;
+        if (ret_prefix)
+                *ret_prefix = buffer;
+        if (ret_prefixlen)
+                *ret_prefixlen = k;
+
+        return 0;
+
+}
+#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/in-addr-util.h b/src/systemd/src/basic/in-addr-util.h
index 51a5aa67..59f8eb7e 100644
--- a/src/systemd/src/basic/in-addr-util.h
+++ b/src/systemd/src/basic/in-addr-util.h
@@ -53,16 +53,20 @@ int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
 int in_addr_to_string(int family, const union in_addr_union *u, char **ret);
 int in_addr_ifindex_to_string(int family, const union in_addr_union *u, int ifindex, char **ret);
 int in_addr_from_string(int family, const char *s, union in_addr_union *ret);
-int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret);
+int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union *ret);
 int in_addr_ifindex_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex);
-unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr);
-struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
-int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
-int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask);
+unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr);
+struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen);
+int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen);
+int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask);
 int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen);
+int in_addr_prefix_covers(int family, const union in_addr_union *prefix, unsigned char prefixlen, const union in_addr_union *address);
+int in_addr_parse_prefixlen(int family, const char *p, unsigned char *ret);
+int in_addr_prefix_from_string(const char *p, int family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
+int in_addr_prefix_from_string_auto(const char *p, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
 
 static inline size_t FAMILY_ADDRESS_SIZE(int family) {
-        assert(family == AF_INET || family == AF_INET6);
+        assert(IN_SET(family, AF_INET, AF_INET6));
         return family == AF_INET6 ? 16 : 4;
 }
 
diff --git a/src/systemd/src/basic/io-util.h b/src/systemd/src/basic/io-util.h
index 4684ed3b..d9b69add 100644
--- a/src/systemd/src/basic/io-util.h
+++ b/src/systemd/src/basic/io-util.h
@@ -40,14 +40,6 @@ int fd_wait_for_event(int fd, int event, usec_t timeout);
 
 ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length);
 
-#define IOVEC_SET_STRING(i, s)                  \
-        do {                                    \
-                struct iovec *_i = &(i);        \
-                char *_s = (char *)(s);         \
-                _i->iov_base = _s;              \
-                _i->iov_len = strlen(_s);       \
-        } while (false)
-
 static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) {
         unsigned j;
         size_t r = 0;
@@ -93,3 +85,8 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
         return FILE_SIZE_VALID(l);
 
 }
+
+#define IOVEC_INIT(base, len) { .iov_base = (base), .iov_len = (len) }
+#define IOVEC_MAKE(base, len) (struct iovec) IOVEC_INIT(base, len)
+#define IOVEC_INIT_STRING(string) IOVEC_INIT((char*) string, strlen(string))
+#define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string)
diff --git a/src/systemd/src/basic/log.h b/src/systemd/src/basic/log.h
index d8335d12..67fda3f9 100644
--- a/src/systemd/src/basic/log.h
+++ b/src/systemd/src/basic/log.h
@@ -30,6 +30,17 @@
 #include "sd-id128.h"
 
 #include "macro.h"
+#include "process-util.h"
+
+typedef enum LogRealm {
+        LOG_REALM_SYSTEMD,
+        LOG_REALM_UDEV,
+        _LOG_REALM_MAX,
+} LogRealm;
+
+#ifndef LOG_REALM
+#  define LOG_REALM LOG_REALM_SYSTEMD
+#endif
 
 typedef enum LogTarget{
         LOG_TARGET_CONSOLE,
@@ -44,14 +55,24 @@ typedef enum LogTarget{
         LOG_TARGET_NULL,
         _LOG_TARGET_MAX,
         _LOG_TARGET_INVALID = -1
-}  LogTarget;
+} LogTarget;
+
+#define LOG_REALM_PLUS_LEVEL(realm, level)      \
+        ((realm) << 10 | (level))
+#define LOG_REALM_REMOVE_LEVEL(realm_level)     \
+        ((realm_level >> 10))
 
 void log_set_target(LogTarget target);
-void log_set_max_level(int level);
+void log_set_max_level_realm(LogRealm realm, int level);
+#define log_set_max_level(level)                \
+        log_set_max_level_realm(LOG_REALM, (level))
+
 void log_set_facility(int facility);
 
 int log_set_target_from_string(const char *e);
-int log_set_max_level_from_string(const char *e);
+int log_set_max_level_from_string_realm(LogRealm realm, const char *e);
+#define log_set_max_level_from_string(e)        \
+        log_set_max_level_from_string_realm(LOG_REALM, (e))
 
 void log_show_color(bool b);
 bool log_get_show_color(void) _pure_;
@@ -62,7 +83,16 @@ int log_show_color_from_string(const char *e);
 int log_show_location_from_string(const char *e);
 
 LogTarget log_get_target(void) _pure_;
-int log_get_max_level(void) _pure_;
+#if 0 /* NM_IGNORED */
+int log_get_max_level_realm(LogRealm realm) _pure_;
+#endif /* NM_IGNORED */
+#define log_get_max_level()                     \
+        log_get_max_level_realm(LOG_REALM)
+
+/* Functions below that open and close logs or configure logging based on the
+ * environment should not be called from library code — this is always a job
+ * for the application itself.
+ */
 
 int log_open(void);
 void log_close(void);
@@ -73,18 +103,36 @@ void log_close_journal(void);
 void log_close_kmsg(void);
 void log_close_console(void);
 
-void log_parse_environment(void);
+void log_parse_environment_realm(LogRealm realm);
+#define log_parse_environment() \
+        log_parse_environment_realm(LOG_REALM)
 
 #if 0 /* NM_IGNORED */
-int log_internal(
+int log_dispatch_internal(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const char *object_field,
+                const char *object,
+                const char *extra,
+                const char *extra_field,
+                char *buffer);
+
+int log_internal_realm(
                 int level,
                 int error,
                 const char *file,
                 int line,
                 const char *func,
                 const char *format, ...) _printf_(6,7);
+#endif /* NM_IGNORED */
+#define log_internal(level, ...) \
+        log_internal_realm(LOG_REALM_PLUS_LEVEL(LOG_REALM, (level)), __VA_ARGS__)
 
-int log_internalv(
+#if 0 /* NM_IGNORED */
+int log_internalv_realm(
                 int level,
                 int error,
                 const char *file,
@@ -92,7 +140,10 @@ int log_internalv(
                 const char *func,
                 const char *format,
                 va_list ap) _printf_(6,0);
+#define log_internalv(level, ...) \
+        log_internalv_realm(LOG_REALM_PLUS_LEVEL(LOG_REALM, (level)), __VA_ARGS__)
 
+/* Realm is fixed to LOG_REALM_SYSTEMD for those */
 int log_object_internal(
                 int level,
                 int error,
@@ -116,7 +167,7 @@ int log_object_internalv(
                 const char *extra_field,
                 const char *extra,
                 const char *format,
-                va_list ap) _printf_(9,0);
+                va_list ap) _printf_(10,0);
 
 int log_struct_internal(
                 int level,
@@ -127,6 +178,7 @@ int log_struct_internal(
                 const char *format, ...) _printf_(6,0) _sentinel_;
 
 int log_oom_internal(
+                LogRealm realm,
                 const char *file,
                 int line,
                 const char *func);
@@ -138,7 +190,16 @@ int log_format_iovec(
                 bool newline_separator,
                 int error,
                 const char *format,
-                va_list ap);
+                va_list ap) _printf_(6, 0);
+
+int log_struct_iovec_internal(
+                int level,
+                int error,
+                const char *file,
+                int line,
+                const char *func,
+                const struct iovec input_iovec[],
+                size_t n_input_iovec);
 
 /* This modifies the buffer passed! */
 int log_dump_internal(
@@ -150,35 +211,51 @@ int log_dump_internal(
                 char *buffer);
 
 /* Logging for various assertions */
-noreturn void log_assert_failed(
+noreturn void log_assert_failed_realm(
+                LogRealm realm,
                 const char *text,
                 const char *file,
                 int line,
                 const char *func);
+#define log_assert_failed(text, ...) \
+        log_assert_failed_realm(LOG_REALM, (text), __VA_ARGS__)
 
-noreturn void log_assert_failed_unreachable(
+noreturn void log_assert_failed_unreachable_realm(
+                LogRealm realm,
                 const char *text,
                 const char *file,
                 int line,
                 const char *func);
+#define log_assert_failed_unreachable(text, ...) \
+        log_assert_failed_unreachable_realm(LOG_REALM, (text), __VA_ARGS__)
 
-void log_assert_failed_return(
+void log_assert_failed_return_realm(
+                LogRealm realm,
                 const char *text,
                 const char *file,
                 int line,
                 const char *func);
+#define log_assert_failed_return(text, ...) \
+        log_assert_failed_return_realm(LOG_REALM, (text), __VA_ARGS__)
+
+#define log_dispatch(level, error, buffer)                              \
+        log_dispatch_internal(level, error, __FILE__, __LINE__, __func__, NULL, NULL, NULL, NULL, buffer)
+#endif /* NM_IGNORED */
 
 /* Logging with level */
-#define log_full_errno(level, error, ...)                               \
+#define log_full_errno_realm(realm, level, error, ...)                  \
         ({                                                              \
                 int _level = (level), _e = (error);                     \
-                (log_get_max_level() >= LOG_PRI(_level))                \
-                        ? log_internal(_level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \
+                (log_get_max_level_realm((realm)) >= LOG_PRI(_level))   \
+                        ? log_internal_realm(LOG_REALM_PLUS_LEVEL((realm), _level), _e, \
+                                             __FILE__, __LINE__, __func__, __VA_ARGS__) \
                         : -abs(_e);                                     \
         })
-#endif /* NM_IGNORED */
 
-#define log_full(level, ...) log_full_errno(level, 0, __VA_ARGS__)
+#define log_full_errno(level, error, ...)                               \
+        log_full_errno_realm(LOG_REALM, (level), (error), __VA_ARGS__)
+
+#define log_full(level, ...) log_full_errno((level), 0, __VA_ARGS__)
 
 /* Normal logging */
 #define log_debug(...)     log_full(LOG_DEBUG,   __VA_ARGS__)
@@ -186,7 +263,7 @@ void log_assert_failed_return(
 #define log_notice(...)    log_full(LOG_NOTICE,  __VA_ARGS__)
 #define log_warning(...)   log_full(LOG_WARNING, __VA_ARGS__)
 #define log_error(...)     log_full(LOG_ERR,     __VA_ARGS__)
-#define log_emergency(...) log_full(getpid() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__)
+#define log_emergency(...) log_full(getpid_cached() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__)
 
 /* Logging triggered by an errno-like error */
 #define log_debug_errno(error, ...)     log_full_errno(LOG_DEBUG,   error, __VA_ARGS__)
@@ -194,7 +271,7 @@ void log_assert_failed_return(
 #define log_notice_errno(error, ...)    log_full_errno(LOG_NOTICE,  error, __VA_ARGS__)
 #define log_warning_errno(error, ...)   log_full_errno(LOG_WARNING, error, __VA_ARGS__)
 #define log_error_errno(error, ...)     log_full_errno(LOG_ERR,     error, __VA_ARGS__)
-#define log_emergency_errno(error, ...) log_full_errno(getpid() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__)
+#define log_emergency_errno(error, ...) log_full_errno(getpid_cached() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__)
 
 #ifdef LOG_TRACE
 #  define log_trace(...) log_debug(__VA_ARGS__)
@@ -203,13 +280,22 @@ void log_assert_failed_return(
 #endif
 
 /* Structured logging */
-#define log_struct(level, ...) log_struct_internal(level, 0, __FILE__, __LINE__, __func__, __VA_ARGS__)
-#define log_struct_errno(level, error, ...) log_struct_internal(level, error, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_struct_errno(level, error, ...) \
+        log_struct_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
+                            error, __FILE__, __LINE__, __func__, __VA_ARGS__)
+#define log_struct(level, ...) log_struct_errno(level, 0, __VA_ARGS__)
+
+#define log_struct_iovec_errno(level, error, iovec, n_iovec)            \
+        log_struct_iovec_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
+                                  error, __FILE__, __LINE__, __func__, iovec, n_iovec)
+#define log_struct_iovec(level, iovec, n_iovec) log_struct_iovec_errno(level, 0, iovec, n_iovec)
 
 /* This modifies the buffer passed! */
-#define log_dump(level, buffer) log_dump_internal(level, 0, __FILE__, __LINE__, __func__, buffer)
+#define log_dump(level, buffer) \
+        log_dump_internal(LOG_REALM_PLUS_LEVEL(LOG_REALM, level), \
+                          0, __FILE__, __LINE__, __func__, buffer)
 
-#define log_oom() log_oom_internal(__FILE__, __LINE__, __func__)
+#define log_oom() log_oom_internal(LOG_REALM, __FILE__, __LINE__, __func__)
 
 bool log_on_console(void) _pure_;
 
@@ -223,6 +309,7 @@ void log_received_signal(int level, const struct signalfd_siginfo *si);
 
 void log_set_upgrade_syslog_to_journal(bool b);
 void log_set_always_reopen_console(bool b);
+void log_set_open_when_needed(bool b);
 
 int log_syntax_internal(
                 const char *unit,
diff --git a/src/systemd/src/basic/macro.h b/src/systemd/src/basic/macro.h
index 7db87b49..afcde459 100644
--- a/src/systemd/src/basic/macro.h
+++ b/src/systemd/src/basic/macro.h
@@ -19,7 +19,6 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <assert.h>
 #include <inttypes.h>
 #include <stdbool.h>
 #include <sys/param.h>
diff --git a/src/systemd/src/basic/parse-util.c b/src/systemd/src/basic/parse-util.c
index 8ffd9464..6d978e93 100644
--- a/src/systemd/src/basic/parse-util.c
+++ b/src/systemd/src/basic/parse-util.c
@@ -44,6 +44,7 @@ int parse_boolean(const char *v) {
         return -EINVAL;
 }
 
+#if 0 /* NM_IGNORED */
 int parse_pid(const char *s, pid_t* ret_pid) {
         unsigned long ul = 0;
         pid_t pid;
@@ -61,12 +62,13 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         if ((unsigned long) pid != ul)
                 return -ERANGE;
 
-        if (pid <= 0)
+        if (!pid_is_valid(pid))
                 return -ERANGE;
 
         *ret_pid = pid;
         return 0;
 }
+#endif /* NM_IGNORED */
 
 int parse_mode(const char *s, mode_t *ret) {
         char *x;
@@ -154,7 +156,7 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) {
         unsigned n_entries, start_pos = 0;
 
         assert(t);
-        assert(base == 1000 || base == 1024);
+        assert(IN_SET(base, 1000, 1024));
         assert(size);
 
         if (base == 1000) {
@@ -594,4 +596,19 @@ int parse_ip_port(const char *s, uint16_t *ret) {
 
         return 0;
 }
+
+int parse_dev(const char *s, dev_t *ret) {
+        unsigned x, y;
+        dev_t d;
+
+        if (sscanf(s, "%u:%u", &x, &y) != 2)
+                return -EINVAL;
+
+        d = makedev(x, y);
+        if ((unsigned) major(d) != x || (unsigned) minor(d) != y)
+                return -EINVAL;
+
+        *ret = d;
+        return 0;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/parse-util.h b/src/systemd/src/basic/parse-util.h
index 4d132f0d..dc09782c 100644
--- a/src/systemd/src/basic/parse-util.h
+++ b/src/systemd/src/basic/parse-util.h
@@ -30,6 +30,7 @@
 #define MODE_INVALID ((mode_t) -1)
 
 int parse_boolean(const char *v) _pure_;
+int parse_dev(const char *s, dev_t *ret);
 int parse_pid(const char *s, pid_t* ret_pid);
 int parse_mode(const char *s, mode_t *ret);
 int parse_ifindex(const char *s, int *ret);
diff --git a/src/systemd/src/basic/path-util.c b/src/systemd/src/basic/path-util.c
index 12ba6ae7..1bdcc653 100644
--- a/src/systemd/src/basic/path-util.c
+++ b/src/systemd/src/basic/path-util.c
@@ -134,8 +134,7 @@ int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
 
         /* Skip the common part. */
         for (;;) {
-                size_t a;
-                size_t b;
+                size_t a, b;
 
                 from_dir += strspn(from_dir, "/");
                 to_path += strspn(to_path, "/");
@@ -147,7 +146,6 @@ int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
                         else
                                 /* from_dir is a parent directory of to_path. */
                                 r = strdup(to_path);
-
                         if (!r)
                                 return -ENOMEM;
 
@@ -178,21 +176,32 @@ int path_make_relative(const char *from_dir, const char *to_path, char **_r) {
 
         /* Count the number of necessary ".." elements. */
         for (n_parents = 0;;) {
+                size_t w;
+
                 from_dir += strspn(from_dir, "/");
 
                 if (!*from_dir)
                         break;
 
-                from_dir += strcspn(from_dir, "/");
-                n_parents++;
+                w = strcspn(from_dir, "/");
+
+                /* If this includes ".." we can't do a simple series of "..", refuse */
+                if (w == 2 && from_dir[0] == '.' && from_dir[1] == '.')
+                        return -EINVAL;
+
+                /* Count number of elements, except if they are "." */
+                if (w != 1 || from_dir[0] != '.')
+                        n_parents++;
+
+                from_dir += w;
         }
 
-        r = malloc(n_parents * 3 + strlen(to_path) + 1);
+        r = new(char, n_parents * 3 + strlen(to_path) + 1);
         if (!r)
                 return -ENOMEM;
 
-        for (p = r; n_parents > 0; n_parents--, p += 3)
-                memcpy(p, "../", 3);
+        for (p = r; n_parents > 0; n_parents--)
+                p = mempcpy(p, "../", 3);
 
         strcpy(p, to_path);
         path_kill_slashes(r);
@@ -447,8 +456,8 @@ bool path_equal(const char *a, const char *b) {
         return path_compare(a, b) == 0;
 }
 
-bool path_equal_or_files_same(const char *a, const char *b) {
-        return path_equal(a, b) || files_same(a, b) > 0;
+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;
 }
 
 char* path_join(const char *root, const char *path, const char *rest) {
diff --git a/src/systemd/src/basic/path-util.h b/src/systemd/src/basic/path-util.h
index 35aef3ad..399ed5f9 100644
--- a/src/systemd/src/basic/path-util.h
+++ b/src/systemd/src/basic/path-util.h
@@ -27,14 +27,16 @@
 #include "string-util.h"
 #include "time-util.h"
 
+#if 0 /* NM_IGNORED */
 #define DEFAULT_PATH_NORMAL "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
 #define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":/sbin:/bin"
 
-#ifdef HAVE_SPLIT_USR
+#if HAVE_SPLIT_USR
 #  define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR
 #else
 #  define DEFAULT_PATH DEFAULT_PATH_NORMAL
 #endif
+#endif /* NM_IGNORED */
 
 bool is_path(const char *p) _pure_;
 int path_split_and_make_absolute(const char *p, char ***ret);
@@ -46,7 +48,7 @@ char* path_kill_slashes(char *path);
 char* path_startswith(const char *path, const char *prefix) _pure_;
 int path_compare(const char *a, const char *b) _pure_;
 bool path_equal(const char *a, const char *b) _pure_;
-bool path_equal_or_files_same(const char *a, const char *b);
+bool path_equal_or_files_same(const char *a, const char *b, int flags);
 char* path_join(const char *root, const char *path, const char *rest);
 
 static inline bool path_equal_ptr(const char *a, const char *b) {
@@ -143,3 +145,13 @@ bool is_deviceallow_pattern(const char *path);
 int systemd_installation_has_version(const char *root, unsigned minimal_version);
 
 bool dot_or_dot_dot(const char *path);
+
+static inline const char *skip_dev_prefix(const char *p) {
+        const char *e;
+
+        /* Drop any /dev prefix if there is any */
+
+        e = path_startswith(p, "/dev/");
+
+        return e ?: p;
+}
diff --git a/src/systemd/src/basic/process-util.c b/src/systemd/src/basic/process-util.c
new file mode 100644
index 00000000..272030d1
--- /dev/null
+++ b/src/systemd/src/basic/process-util.c
@@ -0,0 +1,1093 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  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
+  (at your option) any later version.
+
+  systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "nm-sd-adapt.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <linux/oom.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/personality.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <syslog.h>
+#include <unistd.h>
+#if 0 /* NM_IGNORED */
+#if HAVE_VALGRIND_VALGRIND_H
+#include <valgrind/valgrind.h>
+#endif
+#endif /* NM_IGNORED */
+
+#include "alloc-util.h"
+#include "architecture.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "ioprio.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "process-util.h"
+#include "raw-clone.h"
+#include "signal-util.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "user-util.h"
+#include "util.h"
+
+#if 0 /* NM_IGNORED */
+int get_process_state(pid_t pid) {
+        const char *p;
+        char state;
+        int r;
+        _cleanup_free_ char *line = NULL;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "stat");
+
+        r = read_one_line_file(p, &line);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        p = strrchr(line, ')');
+        if (!p)
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " %c", &state) != 1)
+                return -EIO;
+
+        return (unsigned char) state;
+}
+
+int get_process_comm(pid_t pid, char **name) {
+        const char *p;
+        int r;
+
+        assert(name);
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "comm");
+
+        r = read_one_line_file(p, name);
+        if (r == -ENOENT)
+                return -ESRCH;
+
+        return r;
+}
+
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) {
+        _cleanup_fclose_ FILE *f = NULL;
+        bool space = false;
+        char *k, *ans = NULL;
+        const char *p;
+        int c;
+
+        assert(line);
+        assert(pid >= 0);
+
+        /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing
+         * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most
+         * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If
+         * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a
+         * command line that resolves to the empty string will return the "comm" name of the process instead.
+         *
+         * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
+         * comm_fallback is false). Returns 0 and sets *line otherwise. */
+
+        p = procfs_file_alloca(pid, "cmdline");
+
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        if (max_length == 1) {
+
+                /* If there's only room for one byte, return the empty string */
+                ans = new0(char, 1);
+                if (!ans)
+                        return -ENOMEM;
+
+                *line = ans;
+                return 0;
+
+        } else if (max_length == 0) {
+                size_t len = 0, allocated = 0;
+
+                while ((c = getc(f)) != EOF) {
+
+                        if (!GREEDY_REALLOC(ans, allocated, len+3)) {
+                                free(ans);
+                                return -ENOMEM;
+                        }
+
+                        if (isprint(c)) {
+                                if (space) {
+                                        ans[len++] = ' ';
+                                        space = false;
+                                }
+
+                                ans[len++] = c;
+                        } else if (len > 0)
+                                space = true;
+               }
+
+                if (len > 0)
+                        ans[len] = '\0';
+                else
+                        ans = mfree(ans);
+
+        } else {
+                bool dotdotdot = false;
+                size_t left;
+
+                ans = new(char, max_length);
+                if (!ans)
+                        return -ENOMEM;
+
+                k = ans;
+                left = max_length;
+                while ((c = getc(f)) != EOF) {
+
+                        if (isprint(c)) {
+
+                                if (space) {
+                                        if (left <= 2) {
+                                                dotdotdot = true;
+                                                break;
+                                        }
+
+                                        *(k++) = ' ';
+                                        left--;
+                                        space = false;
+                                }
+
+                                if (left <= 1) {
+                                        dotdotdot = true;
+                                        break;
+                                }
+
+                                *(k++) = (char) c;
+                                left--;
+                        } else if (k > ans)
+                                space = true;
+                }
+
+                if (dotdotdot) {
+                        if (max_length <= 4) {
+                                k = ans;
+                                left = max_length;
+                        } else {
+                                k = ans + max_length - 4;
+                                left = 4;
+
+                                /* Eat up final spaces */
+                                while (k > ans && isspace(k[-1])) {
+                                        k--;
+                                        left++;
+                                }
+                        }
+
+                        strncpy(k, "...", left-1);
+                        k[left-1] = 0;
+                } else
+                        *k = 0;
+        }
+
+        /* Kernel threads have no argv[] */
+        if (isempty(ans)) {
+                _cleanup_free_ char *t = NULL;
+                int h;
+
+                free(ans);
+
+                if (!comm_fallback)
+                        return -ENOENT;
+
+                h = get_process_comm(pid, &t);
+                if (h < 0)
+                        return h;
+
+                if (max_length == 0)
+                        ans = strjoin("[", t, "]");
+                else {
+                        size_t l;
+
+                        l = strlen(t);
+
+                        if (l + 3 <= max_length)
+                                ans = strjoin("[", t, "]");
+                        else if (max_length <= 6) {
+
+                                ans = new(char, max_length);
+                                if (!ans)
+                                        return -ENOMEM;
+
+                                memcpy(ans, "[...]", max_length-1);
+                                ans[max_length-1] = 0;
+                        } else {
+                                char *e;
+
+                                t[max_length - 6] = 0;
+
+                                /* Chop off final spaces */
+                                e = strchr(t, 0);
+                                while (e > t && isspace(e[-1]))
+                                        e--;
+                                *e = 0;
+
+                                ans = strjoin("[", t, "...]");
+                        }
+                }
+                if (!ans)
+                        return -ENOMEM;
+        }
+
+        *line = ans;
+        return 0;
+}
+
+int rename_process(const char name[]) {
+        static size_t mm_size = 0;
+        static char *mm = NULL;
+        bool truncated = false;
+        size_t l;
+
+        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
+         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
+         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
+         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
+         * truncated.
+         *
+         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
+
+        if (isempty(name))
+                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
+
+        l = strlen(name);
+
+        /* First step, change the comm field. */
+        (void) prctl(PR_SET_NAME, name);
+        if (l > 15) /* Linux process names can be 15 chars at max */
+                truncated = true;
+
+        /* Second step, change glibc's ID of the process name. */
+        if (program_invocation_name) {
+                size_t k;
+
+                k = strlen(program_invocation_name);
+                strncpy(program_invocation_name, name, k);
+                if (l > k)
+                        truncated = true;
+        }
+
+        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
+         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
+         * the end. This is the best option for changing /proc/self/cmdline. */
+
+        /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
+         * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
+         * present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
+         * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
+         * mmap() is not. */
+        if (geteuid() != 0)
+                log_debug("Skipping PR_SET_MM, as we don't have privileges.");
+        else if (mm_size < l+1) {
+                size_t nn_size;
+                char *nn;
+
+                nn_size = PAGE_ALIGN(l+1);
+                nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+                if (nn == MAP_FAILED) {
+                        log_debug_errno(errno, "mmap() failed: %m");
+                        goto use_saved_argv;
+                }
+
+                strncpy(nn, name, nn_size);
+
+                /* Now, let's tell the kernel about this new memory */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
+                        log_debug_errno(errno, "PR_SET_MM_ARG_START failed, proceeding without: %m");
+                        (void) munmap(nn, nn_size);
+                        goto use_saved_argv;
+                }
+
+                /* And update the end pointer to the new end, too. If this fails, we don't really know what to do, it's
+                 * pretty unlikely that we can rollback, hence we'll just accept the failure, and continue. */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
+                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+
+                if (mm)
+                        (void) munmap(mm, mm_size);
+
+                mm = nn;
+                mm_size = nn_size;
+        } else {
+                strncpy(mm, name, mm_size);
+
+                /* Update the end pointer, continuing regardless of any failure. */
+                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
+                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
+        }
+
+use_saved_argv:
+        /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
+         * it still looks here */
+
+        if (saved_argc > 0) {
+                int i;
+
+                if (saved_argv[0]) {
+                        size_t k;
+
+                        k = strlen(saved_argv[0]);
+                        strncpy(saved_argv[0], name, k);
+                        if (l > k)
+                                truncated = true;
+                }
+
+                for (i = 1; i < saved_argc; i++) {
+                        if (!saved_argv[i])
+                                break;
+
+                        memzero(saved_argv[i], strlen(saved_argv[i]));
+                }
+        }
+
+        return !truncated;
+}
+
+int is_kernel_thread(pid_t pid) {
+        const char *p;
+        size_t count;
+        char c;
+        bool eof;
+        FILE *f;
+
+        if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */
+                return 0;
+
+        assert(pid > 1);
+
+        p = procfs_file_alloca(pid, "cmdline");
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        count = fread(&c, 1, 1, f);
+        eof = feof(f);
+        fclose(f);
+
+        /* Kernel threads have an empty cmdline */
+
+        if (count <= 0)
+                return eof ? 1 : -errno;
+
+        return 0;
+}
+
+int get_process_capeff(pid_t pid, char **capeff) {
+        const char *p;
+        int r;
+
+        assert(capeff);
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "status");
+
+        r = get_proc_field(p, "CapEff", WHITESPACE, capeff);
+        if (r == -ENOENT)
+                return -ESRCH;
+
+        return r;
+}
+
+static int get_process_link_contents(const char *proc_file, char **name) {
+        int r;
+
+        assert(proc_file);
+        assert(name);
+
+        r = readlink_malloc(proc_file, name);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int get_process_exe(pid_t pid, char **name) {
+        const char *p;
+        char *d;
+        int r;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "exe");
+        r = get_process_link_contents(p, name);
+        if (r < 0)
+                return r;
+
+        d = endswith(*name, " (deleted)");
+        if (d)
+                *d = '\0';
+
+        return 0;
+}
+
+static int get_process_id(pid_t pid, const char *field, uid_t *uid) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char line[LINE_MAX];
+        const char *p;
+
+        assert(field);
+        assert(uid);
+
+        if (pid < 0)
+                return -EINVAL;
+
+        p = procfs_file_alloca(pid, "status");
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        FOREACH_LINE(line, f, return -errno) {
+                char *l;
+
+                l = strstrip(line);
+
+                if (startswith(l, field)) {
+                        l += strlen(field);
+                        l += strspn(l, WHITESPACE);
+
+                        l[strcspn(l, WHITESPACE)] = 0;
+
+                        return parse_uid(l, uid);
+                }
+        }
+
+        return -EIO;
+}
+
+int get_process_uid(pid_t pid, uid_t *uid) {
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *uid = getuid();
+                return 0;
+        }
+
+        return get_process_id(pid, "Uid:", uid);
+}
+
+int get_process_gid(pid_t pid, gid_t *gid) {
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *gid = getgid();
+                return 0;
+        }
+
+        assert_cc(sizeof(uid_t) == sizeof(gid_t));
+        return get_process_id(pid, "Gid:", gid);
+}
+
+int get_process_cwd(pid_t pid, char **cwd) {
+        const char *p;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "cwd");
+
+        return get_process_link_contents(p, cwd);
+}
+
+int get_process_root(pid_t pid, char **root) {
+        const char *p;
+
+        assert(pid >= 0);
+
+        p = procfs_file_alloca(pid, "root");
+
+        return get_process_link_contents(p, root);
+}
+
+int get_process_environ(pid_t pid, char **env) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *outcome = NULL;
+        int c;
+        const char *p;
+        size_t allocated = 0, sz = 0;
+
+        assert(pid >= 0);
+        assert(env);
+
+        p = procfs_file_alloca(pid, "environ");
+
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        while ((c = fgetc(f)) != EOF) {
+                if (!GREEDY_REALLOC(outcome, allocated, sz + 5))
+                        return -ENOMEM;
+
+                if (c == '\0')
+                        outcome[sz++] = '\n';
+                else
+                        sz += cescape_char(c, outcome + sz);
+        }
+
+        if (!outcome) {
+                outcome = strdup("");
+                if (!outcome)
+                        return -ENOMEM;
+        } else
+                outcome[sz] = '\0';
+
+        *env = outcome;
+        outcome = NULL;
+
+        return 0;
+}
+
+int get_process_ppid(pid_t pid, pid_t *_ppid) {
+        int r;
+        _cleanup_free_ char *line = NULL;
+        long unsigned ppid;
+        const char *p;
+
+        assert(pid >= 0);
+        assert(_ppid);
+
+        if (pid == 0 || pid == getpid_cached()) {
+                *_ppid = getppid();
+                return 0;
+        }
+
+        p = procfs_file_alloca(pid, "stat");
+        r = read_one_line_file(p, &line);
+        if (r == -ENOENT)
+                return -ESRCH;
+        if (r < 0)
+                return r;
+
+        /* Let's skip the pid and comm fields. The latter is enclosed
+         * in () but does not escape any () in its value, so let's
+         * skip over it manually */
+
+        p = strrchr(line, ')');
+        if (!p)
+                return -EIO;
+
+        p++;
+
+        if (sscanf(p, " "
+                   "%*c "  /* state */
+                   "%lu ", /* ppid */
+                   &ppid) != 1)
+                return -EIO;
+
+        if ((long unsigned) (pid_t) ppid != ppid)
+                return -ERANGE;
+
+        *_ppid = (pid_t) ppid;
+
+        return 0;
+}
+
+int wait_for_terminate(pid_t pid, siginfo_t *status) {
+        siginfo_t dummy;
+
+        assert(pid >= 1);
+
+        if (!status)
+                status = &dummy;
+
+        for (;;) {
+                zero(*status);
+
+                if (waitid(P_PID, pid, status, WEXITED) < 0) {
+
+                        if (errno == EINTR)
+                                continue;
+
+                        return negative_errno();
+                }
+
+                return 0;
+        }
+}
+
+/*
+ * Return values:
+ * < 0 : wait_for_terminate() failed to get the state of the
+ *       process, the process was terminated by a signal, or
+ *       failed for an unknown reason.
+ * >=0 : The process terminated normally, and its exit code is
+ *       returned.
+ *
+ * That is, success is indicated by a return value of zero, and an
+ * error is indicated by a non-zero value.
+ *
+ * A warning is emitted if the process terminates abnormally,
+ * and also if it returns non-zero unless check_exit_code is true.
+ */
+int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) {
+        int r;
+        siginfo_t status;
+
+        assert(name);
+        assert(pid > 1);
+
+        r = wait_for_terminate(pid, &status);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to wait for %s: %m", name);
+
+        if (status.si_code == CLD_EXITED) {
+                if (status.si_status != 0)
+                        log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG,
+                                 "%s failed with error code %i.", name, status.si_status);
+                else
+                        log_debug("%s succeeded.", name);
+
+                return status.si_status;
+        } else if (IN_SET(status.si_code, CLD_KILLED, CLD_DUMPED)) {
+
+                log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status));
+                return -EPROTO;
+        }
+
+        log_warning("%s failed due to unknown reason.", name);
+        return -EPROTO;
+}
+
+void sigkill_wait(pid_t pid) {
+        assert(pid > 1);
+
+        if (kill(pid, SIGKILL) > 0)
+                (void) wait_for_terminate(pid, NULL);
+}
+
+void sigkill_waitp(pid_t *pid) {
+        if (!pid)
+                return;
+        if (*pid <= 1)
+                return;
+
+        sigkill_wait(*pid);
+}
+
+int kill_and_sigcont(pid_t pid, int sig) {
+        int r;
+
+        r = kill(pid, sig) < 0 ? -errno : 0;
+
+        /* If this worked, also send SIGCONT, unless we already just sent a SIGCONT, or SIGKILL was sent which isn't
+         * affected by a process being suspended anyway. */
+        if (r >= 0 && !IN_SET(sig, SIGCONT, SIGKILL))
+                (void) kill(pid, SIGCONT);
+
+        return r;
+}
+
+int getenv_for_pid(pid_t pid, const char *field, char **_value) {
+        _cleanup_fclose_ FILE *f = NULL;
+        char *value = NULL;
+        int r;
+        bool done = false;
+        size_t l;
+        const char *path;
+
+        assert(pid >= 0);
+        assert(field);
+        assert(_value);
+
+        path = procfs_file_alloca(pid, "environ");
+
+        f = fopen(path, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return -ESRCH;
+                return -errno;
+        }
+
+        l = strlen(field);
+        r = 0;
+
+        do {
+                char line[LINE_MAX];
+                unsigned i;
+
+                for (i = 0; i < sizeof(line)-1; i++) {
+                        int c;
+
+                        c = getc(f);
+                        if (_unlikely_(c == EOF)) {
+                                done = true;
+                                break;
+                        } else if (c == 0)
+                                break;
+
+                        line[i] = c;
+                }
+                line[i] = 0;
+
+                if (strneq(line, field, l) && line[l] == '=') {
+                        value = strdup(line + l + 1);
+                        if (!value)
+                                return -ENOMEM;
+
+                        r = 1;
+                        break;
+                }
+
+        } while (!done);
+
+        *_value = value;
+        return r;
+}
+
+bool pid_is_unwaited(pid_t pid) {
+        /* Checks whether a PID is still valid at all, including a zombie */
+
+        if (pid < 0)
+                return false;
+
+        if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */
+                return true;
+
+        if (pid == getpid_cached())
+                return true;
+
+        if (kill(pid, 0) >= 0)
+                return true;
+
+        return errno != ESRCH;
+}
+
+bool pid_is_alive(pid_t pid) {
+        int r;
+
+        /* Checks whether a PID is still valid and not a zombie */
+
+        if (pid < 0)
+                return false;
+
+        if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */
+                return true;
+
+        if (pid == getpid_cached())
+                return true;
+
+        r = get_process_state(pid);
+        if (IN_SET(r, -ESRCH, 'Z'))
+                return false;
+
+        return true;
+}
+
+int pid_from_same_root_fs(pid_t pid) {
+        const char *root;
+
+        if (pid < 0)
+                return false;
+
+        if (pid == 0 || pid == getpid_cached())
+                return true;
+
+        root = procfs_file_alloca(pid, "root");
+
+        return files_same(root, "/proc/1/root", 0);
+}
+#endif /* NM_IGNORED */
+
+bool is_main_thread(void) {
+        static thread_local int cached = 0;
+
+        if (_unlikely_(cached == 0))
+                cached = getpid_cached() == gettid() ? 1 : -1;
+
+        return cached > 0;
+}
+
+#if 0 /* NM_IGNORED */
+noreturn void freeze(void) {
+
+        log_close();
+
+        /* Make sure nobody waits for us on a socket anymore */
+        close_all_fds(NULL, 0);
+
+        sync();
+
+        for (;;)
+                pause();
+}
+
+bool oom_score_adjust_is_valid(int oa) {
+        return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX;
+}
+
+unsigned long personality_from_string(const char *p) {
+        int architecture;
+
+        if (!p)
+                return PERSONALITY_INVALID;
+
+        /* Parse a personality specifier. We use our own identifiers that indicate specific ABIs, rather than just
+         * hints regarding the register size, since we want to keep things open for multiple locally supported ABIs for
+         * the same register size. */
+
+        architecture = architecture_from_string(p);
+        if (architecture < 0)
+                return PERSONALITY_INVALID;
+
+        if (architecture == native_architecture())
+                return PER_LINUX;
+#ifdef SECONDARY_ARCHITECTURE
+        if (architecture == SECONDARY_ARCHITECTURE)
+                return PER_LINUX32;
+#endif
+
+        return PERSONALITY_INVALID;
+}
+
+const char* personality_to_string(unsigned long p) {
+        int architecture = _ARCHITECTURE_INVALID;
+
+        if (p == PER_LINUX)
+                architecture = native_architecture();
+#ifdef SECONDARY_ARCHITECTURE
+        else if (p == PER_LINUX32)
+                architecture = SECONDARY_ARCHITECTURE;
+#endif
+
+        if (architecture < 0)
+                return NULL;
+
+        return architecture_to_string(architecture);
+}
+
+int safe_personality(unsigned long p) {
+        int ret;
+
+        /* So here's the deal, personality() is weirdly defined by glibc. In some cases it returns a failure via errno,
+         * and in others as negative return value containing an errno-like value. Let's work around this: this is a
+         * wrapper that uses errno if it is set, and uses the return value otherwise. And then it sets both errno and
+         * the return value indicating the same issue, so that we are definitely on the safe side.
+         *
+         * See https://github.com/systemd/systemd/issues/6737 */
+
+        errno = 0;
+        ret = personality(p);
+        if (ret < 0) {
+                if (errno != 0)
+                        return -errno;
+
+                errno = -ret;
+        }
+
+        return ret;
+}
+
+int opinionated_personality(unsigned long *ret) {
+        int current;
+
+        /* Returns the current personality, or PERSONALITY_INVALID if we can't determine it. This function is a bit
+         * opinionated though, and ignores all the finer-grained bits and exotic personalities, only distinguishing the
+         * two most relevant personalities: PER_LINUX and PER_LINUX32. */
+
+        current = safe_personality(PERSONALITY_INVALID);
+        if (current < 0)
+                return current;
+
+        if (((unsigned long) current & 0xffff) == PER_LINUX32)
+                *ret = PER_LINUX32;
+        else
+                *ret = PER_LINUX;
+
+        return 0;
+}
+
+void valgrind_summary_hack(void) {
+#if HAVE_VALGRIND_VALGRIND_H
+        if (getpid_cached() == 1 && RUNNING_ON_VALGRIND) {
+                pid_t pid;
+                pid = raw_clone(SIGCHLD);
+                if (pid < 0)
+                        log_emergency_errno(errno, "Failed to fork off valgrind helper: %m");
+                else if (pid == 0)
+                        exit(EXIT_SUCCESS);
+                else {
+                        log_info("Spawned valgrind helper as PID "PID_FMT".", pid);
+                        (void) wait_for_terminate(pid, NULL);
+                }
+        }
+#endif
+}
+
+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;
+}
+
+int ioprio_parse_priority(const char *s, int *ret) {
+        int i, r;
+
+        assert(s);
+        assert(ret);
+
+        r = safe_atoi(s, &i);
+        if (r < 0)
+                return r;
+
+        if (!ioprio_priority_is_valid(i))
+                return -EINVAL;
+
+        *ret = i;
+        return 0;
+}
+#endif /* NM_IGNORED */
+
+/* The cached PID, possible values:
+ *
+ *     == UNSET [0]  → cache not initialized yet
+ *     == BUSY [-1]  → some thread is initializing it at the moment
+ *     any other     → the cached PID
+ */
+
+#define CACHED_PID_UNSET ((pid_t) 0)
+#define CACHED_PID_BUSY ((pid_t) -1)
+
+static pid_t cached_pid = CACHED_PID_UNSET;
+
+static void reset_cached_pid(void) {
+        /* Invoked in the child after a fork(), i.e. at the first moment the PID changed */
+        cached_pid = CACHED_PID_UNSET;
+}
+
+/* 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 void* __dso_handle __attribute__ ((__weak__));
+
+pid_t getpid_cached(void) {
+        pid_t current_value;
+
+        /* getpid_cached() is much like getpid(), but caches the value in local memory, to avoid having to invoke a
+         * system call each time. This restores glibc behaviour from before 2.24, when getpid() was unconditionally
+         * cached. Starting with 2.24 getpid() started to become prohibitively expensive when used for detecting when
+         * objects were used across fork()s. With this caching the old behaviour is somewhat restored.
+         *
+         * https://bugzilla.redhat.com/show_bug.cgi?id=1443976
+         * https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=c579f48edba88380635ab98cb612030e3ed8691e
+         */
+
+        current_value = __sync_val_compare_and_swap(&cached_pid, CACHED_PID_UNSET, CACHED_PID_BUSY);
+
+        switch (current_value) {
+
+        case CACHED_PID_UNSET: { /* Not initialized yet, then do so now */
+                pid_t new_pid;
+
+                new_pid = getpid();
+
+                if (__register_atfork(NULL, NULL, reset_cached_pid, __dso_handle) != 0) {
+                        /* OOM? Let's try again later */
+                        cached_pid = CACHED_PID_UNSET;
+                        return new_pid;
+                }
+
+                cached_pid = new_pid;
+                return new_pid;
+        }
+
+        case CACHED_PID_BUSY: /* Somebody else is currently initializing */
+                return getpid();
+
+        default: /* Properly initialized */
+                return current_value;
+        }
+}
+
+#if 0 /* NM_IGNORED */
+static const char *const ioprio_class_table[] = {
+        [IOPRIO_CLASS_NONE] = "none",
+        [IOPRIO_CLASS_RT] = "realtime",
+        [IOPRIO_CLASS_BE] = "best-effort",
+        [IOPRIO_CLASS_IDLE] = "idle"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX);
+
+static const char *const sigchld_code_table[] = {
+        [CLD_EXITED] = "exited",
+        [CLD_KILLED] = "killed",
+        [CLD_DUMPED] = "dumped",
+        [CLD_TRAPPED] = "trapped",
+        [CLD_STOPPED] = "stopped",
+        [CLD_CONTINUED] = "continued",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int);
+
+static const char* const sched_policy_table[] = {
+        [SCHED_OTHER] = "other",
+        [SCHED_BATCH] = "batch",
+        [SCHED_IDLE] = "idle",
+        [SCHED_FIFO] = "fifo",
+        [SCHED_RR] = "rr"
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX);
+#endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/process-util.h b/src/systemd/src/basic/process-util.h
new file mode 100644
index 00000000..e1bd2c5b
--- /dev/null
+++ b/src/systemd/src/basic/process-util.h
@@ -0,0 +1,141 @@
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  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
+  (at your option) any later version.
+
+  systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <alloca.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+
+#include "format-util.h"
+#include "ioprio.h"
+#include "macro.h"
+
+#define procfs_file_alloca(pid, field)                                  \
+        ({                                                              \
+                pid_t _pid_ = (pid);                                    \
+                const char *_r_;                                        \
+                if (_pid_ == 0) {                                       \
+                        _r_ = ("/proc/self/" field);                    \
+                } else {                                                \
+                        _r_ = alloca(strlen("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \
+                        sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_);                       \
+                }                                                       \
+                _r_;                                                    \
+        })
+
+int get_process_state(pid_t pid);
+int get_process_comm(pid_t pid, char **name);
+int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line);
+int get_process_exe(pid_t pid, char **name);
+int get_process_uid(pid_t pid, uid_t *uid);
+int get_process_gid(pid_t pid, gid_t *gid);
+int get_process_capeff(pid_t pid, char **capeff);
+int get_process_cwd(pid_t pid, char **cwd);
+int get_process_root(pid_t pid, char **root);
+int get_process_environ(pid_t pid, char **environ);
+int get_process_ppid(pid_t pid, pid_t *ppid);
+
+int wait_for_terminate(pid_t pid, siginfo_t *status);
+int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code);
+
+void sigkill_wait(pid_t pid);
+void sigkill_waitp(pid_t *pid);
+
+int kill_and_sigcont(pid_t pid, int sig);
+
+int rename_process(const char name[]);
+int is_kernel_thread(pid_t pid);
+
+int getenv_for_pid(pid_t pid, const char *field, char **_value);
+
+bool pid_is_alive(pid_t pid);
+bool pid_is_unwaited(pid_t pid);
+int pid_from_same_root_fs(pid_t pid);
+
+bool is_main_thread(void);
+
+noreturn void freeze(void);
+
+bool oom_score_adjust_is_valid(int oa);
+
+#ifndef PERSONALITY_INVALID
+/* personality(7) documents that 0xffffffffUL is used for querying the
+ * current personality, hence let's use that here as error
+ * indicator. */
+#define PERSONALITY_INVALID 0xffffffffLU
+#endif
+
+unsigned long personality_from_string(const char *p);
+const char *personality_to_string(unsigned long);
+
+int safe_personality(unsigned long p);
+int opinionated_personality(unsigned long *ret);
+
+int ioprio_class_to_string_alloc(int i, char **s);
+int ioprio_class_from_string(const char *s);
+
+const char *sigchld_code_to_string(int i) _const_;
+int sigchld_code_from_string(const char *s) _pure_;
+
+int sched_policy_to_string_alloc(int i, char **s);
+int sched_policy_from_string(const char *s);
+
+#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p))
+#define PID_TO_PTR(p) ((void*) ((uintptr_t) p))
+
+void valgrind_summary_hack(void);
+
+int pid_compare_func(const void *a, const void *b);
+
+#if 0 /* NM_IGNORED */
+static inline bool nice_is_valid(int n) {
+        return n >= PRIO_MIN && n < PRIO_MAX;
+}
+
+static inline bool sched_policy_is_valid(int i) {
+        return IN_SET(i, SCHED_OTHER, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR);
+}
+
+static inline bool sched_priority_is_valid(int i) {
+        return i >= 0 && i <= sched_get_priority_max(SCHED_RR);
+}
+
+static inline bool ioprio_class_is_valid(int i) {
+        return IN_SET(i, IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE);
+}
+
+static inline bool ioprio_priority_is_valid(int i) {
+        return i >= 0 && i < IOPRIO_BE_NR;
+}
+
+static inline bool pid_is_valid(pid_t p) {
+        return p > 0;
+}
+#endif /* NM_IGNORED */
+
+int ioprio_parse_priority(const char *s, int *ret);
+
+pid_t getpid_cached(void);
diff --git a/src/systemd/src/basic/random-util.c b/src/systemd/src/basic/random-util.c
index 1d8ca882..3b6ddb7d 100644
--- a/src/systemd/src/basic/random-util.c
+++ b/src/systemd/src/basic/random-util.c
@@ -28,8 +28,14 @@
 #include <linux/random.h>
 #include <stdint.h>
 
-#ifdef HAVE_SYS_AUXV_H
-#include <sys/auxv.h>
+#if HAVE_SYS_AUXV_H
+#  include <sys/auxv.h>
+#endif
+
+#if USE_SYS_RANDOM_H
+#  include <sys/random.h>
+#else
+#  include <linux/random.h>
 #endif
 
 #include "fd-util.h"
@@ -38,76 +44,86 @@
 #include "random-util.h"
 #include "time-util.h"
 
-int dev_urandom(void *p, size_t n) {
-#if 0 /* NM_IGNORED */
+int acquire_random_bytes(void *p, size_t n, bool high_quality_required) {
         static int have_syscall = -1;
 
         _cleanup_close_ int fd = -1;
+        unsigned already_done = 0;
         int r;
 
-        /* Gathers some randomness from the kernel. This call will
-         * never block, and will always return some data from the
-         * kernel, regardless if the random pool is fully initialized
-         * or not. It thus makes no guarantee for the quality of the
-         * returned entropy, but is good enough for our usual usecases
-         * of seeding the hash functions for hashtable */
-
-        /* Use the getrandom() syscall unless we know we don't have
-         * it, or when the requested size is too large for it. */
-        if (have_syscall != 0 || (size_t) (int) n != n) {
+        /* Gathers some randomness from the kernel. This call will never block. If
+         * high_quality_required, it will always return some data from the kernel,
+         * regardless of whether the random pool is fully initialized or not.
+         * Otherwise, it will return success if at least some random bytes were
+         * successfully acquired, and an error if the kernel has no entropy whatsover
+         * for us. */
+
+        /* Use the getrandom() syscall unless we know we don't have it. */
+        if (have_syscall != 0) {
+#if !HAVE_GETRANDOM
+                /* XXX: NM: systemd calls the syscall directly in this case. Don't add that workaround.
+                 * If you don't compile against a libc that provides getrandom(), you don't get it. */
+                r = -1;
+                errno = ENOSYS;
+#else
                 r = getrandom(p, n, GRND_NONBLOCK);
-                if (r == (int) n) {
+#endif
+                if (r > 0) {
+                        have_syscall = true;
+                        if ((size_t) r == n)
+                                return 0;
+                        if (!high_quality_required) {
+                                /* Fill in the remaining bytes using pseudorandom values */
+                                pseudorandom_bytes((uint8_t*) p + r, n - r);
+                                return 0;
+                        }
+
+                        already_done = r;
+                } else if (errno == ENOSYS)
+                          /* We lack the syscall, continue with reading from /dev/urandom. */
+                          have_syscall = false;
+                else if (errno == EAGAIN) {
+                        /* The kernel has no entropy whatsoever. Let's remember to
+                         * use the syscall the next time again though.
+                         *
+                         * If high_quality_required is false, return an error so that
+                         * random_bytes() can produce some pseudorandom
+                         * bytes. Otherwise, fall back to /dev/urandom, which we know
+                         * is empty, but the kernel will produce some bytes for us on
+                         * a best-effort basis. */
                         have_syscall = true;
-                        return 0;
-                }
-
-                if (r < 0) {
-                        if (errno == ENOSYS)
-                                /* we lack the syscall, continue with
-                                 * reading from /dev/urandom */
-                                have_syscall = false;
-                        else if (errno == EAGAIN)
-                                /* not enough entropy for now. Let's
-                                 * remember to use the syscall the
-                                 * next time, again, but also read
-                                 * from /dev/urandom for now, which
-                                 * doesn't care about the current
-                                 * amount of entropy.  */
-                                have_syscall = true;
-                        else
-                                return -errno;
+
+                        if (!high_quality_required)
+                                return -ENODATA;
                 } else
-                        /* too short read? */
-                        return -ENODATA;
+                        return -errno;
         }
-#else /* NM_IGNORED */
-        _cleanup_close_ int fd = -1;
-#endif /* NM_IGNORED */
 
         fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
         if (fd < 0)
                 return errno == ENOENT ? -ENOSYS : -errno;
 
-        return loop_read_exact(fd, p, n, true);
+        return loop_read_exact(fd, (uint8_t*) p + already_done, n - already_done, true);
 }
 
 void initialize_srand(void) {
         static bool srand_called = false;
         unsigned x;
-#ifdef HAVE_SYS_AUXV_H
+#if HAVE_SYS_AUXV_H
         void *auxv;
 #endif
 
         if (srand_called)
                 return;
 
-#ifdef HAVE_SYS_AUXV_H
-        /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed the
-         * pseudo-random generator. It's better than nothing... */
+#if HAVE_SYS_AUXV_H
+        /* The kernel provides us with 16 bytes of entropy in auxv, so let's
+         * try to make use of that to seed the pseudo-random generator. It's
+         * better than nothing... */
 
         auxv = (void*) getauxval(AT_RANDOM);
         if (auxv) {
-                assert_cc(sizeof(x) < 16);
+                assert_cc(sizeof(x) <= 16);
                 memcpy(&x, auxv, sizeof(x));
         } else
 #endif
@@ -121,19 +137,44 @@ void initialize_srand(void) {
         srand_called = true;
 }
 
-void random_bytes(void *p, size_t n) {
+/* INT_MAX gives us only 31 bits, so use 24 out of that. */
+#if RAND_MAX >= INT_MAX
+#  define RAND_STEP 3
+#else
+/* SHORT_INT_MAX or lower gives at most 15 bits, we just just 8 out of that. */
+#  define RAND_STEP 1
+#endif
+
+void pseudorandom_bytes(void *p, size_t n) {
         uint8_t *q;
+
+        initialize_srand();
+
+        for (q = p; q < (uint8_t*) p + n; q += RAND_STEP) {
+                unsigned rr;
+
+                rr = (unsigned) rand();
+
+#if RAND_STEP >= 3
+                if ((size_t) (q - (uint8_t*) p + 2) < n)
+                        q[2] = rr >> 16;
+#endif
+#if RAND_STEP >= 2
+                if ((size_t) (q - (uint8_t*) p + 1) < n)
+                        q[1] = rr >> 8;
+#endif
+                q[0] = rr;
+        }
+}
+
+void random_bytes(void *p, size_t n) {
         int r;
 
-        r = dev_urandom(p, n);
+        r = acquire_random_bytes(p, n, false);
         if (r >= 0)
                 return;
 
-        /* If some idiot made /dev/urandom unavailable to us, he'll
-         * get a PRNG instead. */
-
-        initialize_srand();
-
-        for (q = p; q < (uint8_t*) p + n; q ++)
-                *q = rand();
+        /* If some idiot made /dev/urandom unavailable to us, or the
+         * kernel has no entropy, use a PRNG instead. */
+        return pseudorandom_bytes(p, n);
 }
diff --git a/src/systemd/src/basic/random-util.h b/src/systemd/src/basic/random-util.h
index 3cee4c50..804e225f 100644
--- a/src/systemd/src/basic/random-util.h
+++ b/src/systemd/src/basic/random-util.h
@@ -19,10 +19,12 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
-int dev_urandom(void *p, size_t n);
+int acquire_random_bytes(void *p, size_t n, bool high_quality_required);
+void pseudorandom_bytes(void *p, size_t n);
 void random_bytes(void *p, size_t n);
 void initialize_srand(void);
 
diff --git a/src/systemd/src/basic/set.h b/src/systemd/src/basic/set.h
index a5f8beb0..12d0fda1 100644
--- a/src/systemd/src/basic/set.h
+++ b/src/systemd/src/basic/set.h
@@ -136,3 +136,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free);
 
 #define _cleanup_set_free_ _cleanup_(set_freep)
 #define _cleanup_set_free_free_ _cleanup_(set_free_freep)
+
+int set_make(Set **ret, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS, void *add, ...);
diff --git a/src/systemd/src/basic/siphash24.c b/src/systemd/src/basic/siphash24.c
deleted file mode 100644
index e4b1cb10..00000000
--- a/src/systemd/src/basic/siphash24.c
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
-   SipHash reference C implementation
-
-   Written in 2012 by
-   Jean-Philippe Aumasson <jeanphilippe.aumasson@gmail.com>
-   Daniel J. Bernstein <djb@cr.yp.to>
-
-   To the extent possible under law, the author(s) have dedicated all copyright
-   and related and neighboring rights to this software to the public domain
-   worldwide. This software is distributed without any warranty.
-
-   You should have received a copy of the CC0 Public Domain Dedication along with
-   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
-
-   (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd)
-   (Refactored by Tom Gundersen to split up in several functions and follow systemd
-    coding style)
-*/
-
-#include "nm-sd-adapt.h"
-
-#include <stdio.h>
-
-#include "macro.h"
-#include "siphash24.h"
-#include "unaligned.h"
-
-static inline uint64_t rotate_left(uint64_t x, uint8_t b) {
-        assert(b < 64);
-
-        return (x << b) | (x >> (64 - b));
-}
-
-static inline void sipround(struct siphash *state) {
-        assert(state);
-
-        state->v0 += state->v1;
-        state->v1 = rotate_left(state->v1, 13);
-        state->v1 ^= state->v0;
-        state->v0 = rotate_left(state->v0, 32);
-        state->v2 += state->v3;
-        state->v3 = rotate_left(state->v3, 16);
-        state->v3 ^= state->v2;
-        state->v0 += state->v3;
-        state->v3 = rotate_left(state->v3, 21);
-        state->v3 ^= state->v0;
-        state->v2 += state->v1;
-        state->v1 = rotate_left(state->v1, 17);
-        state->v1 ^= state->v2;
-        state->v2 = rotate_left(state->v2, 32);
-}
-
-void siphash24_init(struct siphash *state, const uint8_t k[16]) {
-        uint64_t k0, k1;
-
-        assert(state);
-        assert(k);
-
-        k0 = unaligned_read_le64(k);
-        k1 = unaligned_read_le64(k + 8);
-
-        *state = (struct siphash) {
-                /* "somepseudorandomlygeneratedbytes" */
-                .v0 = 0x736f6d6570736575ULL ^ k0,
-                .v1 = 0x646f72616e646f6dULL ^ k1,
-                .v2 = 0x6c7967656e657261ULL ^ k0,
-                .v3 = 0x7465646279746573ULL ^ k1,
-                .padding = 0,
-                .inlen = 0,
-        };
-}
-
-void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) {
-
-        const uint8_t *in = _in;
-        const uint8_t *end = in + inlen;
-        size_t left = state->inlen & 7;
-        uint64_t m;
-
-        assert(in);
-        assert(state);
-
-        /* Update total length */
-        state->inlen += inlen;
-
-        /* If padding exists, fill it out */
-        if (left > 0) {
-                for ( ; in < end && left < 8; in ++, left ++)
-                        state->padding |= ((uint64_t) *in) << (left * 8);
-
-                if (in == end && left < 8)
-                        /* We did not have enough input to fill out the padding completely */
-                        return;
-
-#ifdef DEBUG
-                printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-                printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-                printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-                printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-                printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding);
-#endif
-
-                state->v3 ^= state->padding;
-                sipround(state);
-                sipround(state);
-                state->v0 ^= state->padding;
-
-                state->padding = 0;
-        }
-
-        end -= (state->inlen % sizeof(uint64_t));
-
-        for ( ; in < end; in += 8) {
-                m = unaligned_read_le64(in);
-#ifdef DEBUG
-                printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-                printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-                printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-                printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-                printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m);
-#endif
-                state->v3 ^= m;
-                sipround(state);
-                sipround(state);
-                state->v0 ^= m;
-        }
-
-        left = state->inlen & 7;
-        switch (left) {
-                case 7:
-                        state->padding |= ((uint64_t) in[6]) << 48;
-                        /* fall through */
-                case 6:
-                        state->padding |= ((uint64_t) in[5]) << 40;
-                        /* fall through */
-                case 5:
-                        state->padding |= ((uint64_t) in[4]) << 32;
-                        /* fall through */
-                case 4:
-                        state->padding |= ((uint64_t) in[3]) << 24;
-                        /* fall through */
-                case 3:
-                        state->padding |= ((uint64_t) in[2]) << 16;
-                        /* fall through */
-                case 2:
-                        state->padding |= ((uint64_t) in[1]) <<  8;
-                        /* fall through */
-                case 1:
-                        state->padding |= ((uint64_t) in[0]);
-                        /* fall through */
-                case 0:
-                        break;
-        }
-}
-
-uint64_t siphash24_finalize(struct siphash *state) {
-        uint64_t b;
-
-        assert(state);
-
-        b = state->padding | (((uint64_t) state->inlen) << 56);
-
-#ifdef DEBUG
-        printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-        printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-        printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-        printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-        printf("(%3zu) padding   %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding);
-#endif
-
-        state->v3 ^= b;
-        sipround(state);
-        sipround(state);
-        state->v0 ^= b;
-
-#ifdef DEBUG
-        printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
-        printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
-        printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
-        printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
-#endif
-        state->v2 ^= 0xff;
-
-        sipround(state);
-        sipround(state);
-        sipround(state);
-        sipround(state);
-
-        return state->v0 ^ state->v1 ^ state->v2  ^ state->v3;
-}
-
-uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) {
-        struct siphash state;
-
-        assert(in);
-        assert(k);
-
-        siphash24_init(&state, k);
-        siphash24_compress(in, inlen, &state);
-
-        return siphash24_finalize(&state);
-}
diff --git a/src/systemd/src/basic/siphash24.h b/src/systemd/src/basic/siphash24.h
deleted file mode 100644
index 54e2420c..00000000
--- a/src/systemd/src/basic/siphash24.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include <inttypes.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-struct siphash {
-        uint64_t v0;
-        uint64_t v1;
-        uint64_t v2;
-        uint64_t v3;
-        uint64_t padding;
-        size_t inlen;
-};
-
-void siphash24_init(struct siphash *state, const uint8_t k[16]);
-void siphash24_compress(const void *in, size_t inlen, struct siphash *state);
-#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state))
-
-uint64_t siphash24_finalize(struct siphash *state);
-
-uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]);
diff --git a/src/systemd/src/basic/socket-util.c b/src/systemd/src/basic/socket-util.c
index e63dd0db..798ab16e 100644
--- a/src/systemd/src/basic/socket-util.c
+++ b/src/systemd/src/basic/socket-util.c
@@ -51,6 +51,12 @@
 #include "util.h"
 
 #if 0 /* NM_IGNORED */
+#if ENABLE_IDN
+#  define IDN_FLAGS (NI_IDN|NI_IDN_USE_STD3_ASCII_RULES)
+#else
+#  define IDN_FLAGS 0
+#endif
+
 int socket_address_parse(SocketAddress *a, const char *s) {
         char *e, *n;
         unsigned u;
@@ -265,7 +271,7 @@ int socket_address_verify(const SocketAddress *a) {
                 if (a->sockaddr.in.sin_port == 0)
                         return -EINVAL;
 
-                if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM)
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
                         return -EINVAL;
 
                 return 0;
@@ -277,7 +283,7 @@ int socket_address_verify(const SocketAddress *a) {
                 if (a->sockaddr.in6.sin6_port == 0)
                         return -EINVAL;
 
-                if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM)
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
                         return -EINVAL;
 
                 return 0;
@@ -301,7 +307,7 @@ int socket_address_verify(const SocketAddress *a) {
                         }
                 }
 
-                if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET)
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET))
                         return -EINVAL;
 
                 return 0;
@@ -311,7 +317,7 @@ int socket_address_verify(const SocketAddress *a) {
                 if (a->size != sizeof(struct sockaddr_nl))
                         return -EINVAL;
 
-                if (a->type != SOCK_RAW && a->type != SOCK_DGRAM)
+                if (!IN_SET(a->type, SOCK_RAW, SOCK_DGRAM))
                         return -EINVAL;
 
                 return 0;
@@ -320,7 +326,7 @@ int socket_address_verify(const SocketAddress *a) {
                 if (a->size != sizeof(struct sockaddr_vm))
                         return -EINVAL;
 
-                if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM)
+                if (!IN_SET(a->type, SOCK_STREAM, SOCK_DGRAM))
                         return -EINVAL;
 
                 return 0;
@@ -361,8 +367,7 @@ bool socket_address_can_accept(const SocketAddress *a) {
         assert(a);
 
         return
-                a->type == SOCK_STREAM ||
-                a->type == SOCK_SEQPACKET;
+                IN_SET(a->type, SOCK_STREAM, SOCK_SEQPACKET);
 }
 
 bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
@@ -409,7 +414,7 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
                         return false;
 
                 if (a->sockaddr.un.sun_path[0]) {
-                        if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path))
+                        if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0))
                                 return false;
                 } else {
                         if (a->size != b->size)
@@ -726,8 +731,7 @@ int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret)
 
         assert(_ret);
 
-        r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0,
-                        NI_IDN|NI_IDN_USE_STD3_ASCII_RULES);
+        r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS);
         if (r != 0) {
                 int saved_errno = errno;
 
@@ -791,7 +795,8 @@ static const char* const netlink_family_table[] = {
         [NETLINK_KOBJECT_UEVENT] = "kobject-uevent",
         [NETLINK_GENERIC] = "generic",
         [NETLINK_SCSITRANSPORT] = "scsitransport",
-        [NETLINK_ECRYPTFS] = "ecryptfs"
+        [NETLINK_ECRYPTFS] = "ecryptfs",
+        [NETLINK_RDMA] = "rdma",
 };
 
 DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX);
@@ -890,7 +895,7 @@ bool ifname_valid(const char *p) {
                 if ((unsigned char) *p <= 32U)
                         return false;
 
-                if (*p == ':' || *p == '/')
+                if (IN_SET(*p, ':', '/'))
                         return false;
 
                 numeric = numeric && (*p >= '0' && *p <= '9');
@@ -1079,7 +1084,7 @@ ssize_t next_datagram_size_fd(int fd) {
 
         l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC);
         if (l < 0) {
-                if (errno == EOPNOTSUPP || errno == EFAULT)
+                if (IN_SET(errno, EOPNOTSUPP, EFAULT))
                         goto fallback;
 
                 return -errno;
diff --git a/src/systemd/src/basic/socket-util.h b/src/systemd/src/basic/socket-util.h
index 19a9ddb2..d7e2d85f 100644
--- a/src/systemd/src/basic/socket-util.h
+++ b/src/systemd/src/basic/socket-util.h
@@ -27,6 +27,7 @@
 #include <sys/types.h>
 #include <sys/un.h>
 #include <linux/netlink.h>
+#include <linux/if_infiniband.h>
 #include <linux/if_packet.h>
 
 #include "macro.h"
@@ -44,6 +45,8 @@ union sockaddr_union {
 #if 0 /* NM_IGNORED */
         struct sockaddr_vm vm;
 #endif /* NM_IGNORED */
+        /* Ensure there is enough space to store Infiniband addresses */
+        uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + CONST_MAX(ETH_ALEN, INFINIBAND_ALEN)];
 };
 
 typedef struct SocketAddress {
@@ -149,6 +152,23 @@ int flush_accept(int fd);
 
 struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
 
+/*
+ * Certain hardware address types (e.g Infiniband) do not fit into sll_addr
+ * (8 bytes) and run over the structure. This macro returns the correct size that
+ * must be passed to kernel.
+ */
+#define SOCKADDR_LL_LEN(sa)                                             \
+        ({                                                              \
+                const struct sockaddr_ll *_sa = &(sa);                  \
+                size_t _mac_len = sizeof(_sa->sll_addr);                \
+                assert(_sa->sll_family == AF_PACKET);                   \
+                if (be16toh(_sa->sll_hatype) == ARPHRD_ETHER)           \
+                        _mac_len = MAX(_mac_len, (size_t) ETH_ALEN);    \
+                if (be16toh(_sa->sll_hatype) == ARPHRD_INFINIBAND)      \
+                        _mac_len = MAX(_mac_len, (size_t) INFINIBAND_ALEN); \
+                offsetof(struct sockaddr_ll, sll_addr) + _mac_len;      \
+        })
+
 /* Covers only file system and abstract AF_UNIX socket addresses, but not unnamed socket addresses. */
 #define SOCKADDR_UN_LEN(sa)                                             \
         ({                                                              \
diff --git a/src/systemd/src/basic/string-util.c b/src/systemd/src/basic/string-util.c
index 406d6d3c..047eb162 100644
--- a/src/systemd/src/basic/string-util.c
+++ b/src/systemd/src/basic/string-util.c
@@ -217,7 +217,7 @@ char *strnappend(const char *s, const char *suffix, size_t b) {
 }
 
 char *strappend(const char *s, const char *suffix) {
-        return strnappend(s, suffix, suffix ? strlen(suffix) : 0);
+        return strnappend(s, suffix, strlen_ptr(suffix));
 }
 
 char *strjoin_real(const char *x, ...) {
@@ -546,7 +546,7 @@ char *ellipsize(const char *s, size_t length, unsigned percent) {
 }
 #endif /* NM_IGNORED */
 
-bool nulstr_contains(const char*nulstr, const char *needle) {
+bool nulstr_contains(const char *nulstr, const char *needle) {
         const char *i;
 
         if (!nulstr)
@@ -562,7 +562,7 @@ bool nulstr_contains(const char*nulstr, const char *needle) {
 char* strshorten(char *s, size_t l) {
         assert(s);
 
-        if (l < strlen(s))
+        if (strnlen(s, l+1) > l)
                 s[l] = 0;
 
         return s;
@@ -639,6 +639,11 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
         if (!f)
                 return NULL;
 
+        /* Note we use the _unlocked() stdio variants on f for performance
+         * reasons.  It's safe to do so since we created f here and it
+         * doesn't leave our scope.
+         */
+
         for (i = *ibuf; i < *ibuf + isz + 1; i++) {
 
                 switch (state) {
@@ -649,21 +654,21 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
                         else if (*i == '\x1B')
                                 state = STATE_ESCAPE;
                         else if (*i == '\t')
-                                fputs("        ", f);
+                                fputs_unlocked("        ", f);
                         else
-                                fputc(*i, f);
+                                fputc_unlocked(*i, f);
                         break;
 
                 case STATE_ESCAPE:
                         if (i >= *ibuf + isz) { /* EOT */
-                                fputc('\x1B', f);
+                                fputc_unlocked('\x1B', f);
                                 break;
                         } else if (*i == '[') {
                                 state = STATE_BRACKET;
                                 begin = i + 1;
                         } else {
-                                fputc('\x1B', f);
-                                fputc(*i, f);
+                                fputc_unlocked('\x1B', f);
+                                fputc_unlocked(*i, f);
                                 state = STATE_OTHER;
                         }
 
@@ -672,9 +677,9 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz) {
                 case STATE_BRACKET:
 
                         if (i >= *ibuf + isz || /* EOT */
-                            (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) {
-                                fputc('\x1B', f);
-                                fputc('[', f);
+                            (!(*i >= '0' && *i <= '9') && !IN_SET(*i, ';', 'm'))) {
+                                fputc_unlocked('\x1B', f);
+                                fputc_unlocked('[', f);
                                 state = STATE_OTHER;
                                 i = begin-1;
                         } else if (*i == 'm')
@@ -706,7 +711,7 @@ char *strextend(char **x, ...) {
 
         assert(x);
 
-        l = f = *x ? strlen(*x) : 0;
+        l = f = strlen_ptr(*x);
 
         va_start(ap, x);
         for (;;) {
@@ -825,7 +830,7 @@ int free_and_strdup(char **p, const char *s) {
         return 1;
 }
 
-#if !HAVE_DECL_EXPLICIT_BZERO
+#if !HAVE_EXPLICIT_BZERO
 /*
  * Pointer to memset is volatile so that compiler must de-reference
  * the pointer and can't assume that it points to any function in
diff --git a/src/systemd/src/basic/string-util.h b/src/systemd/src/basic/string-util.h
index be44dedf..4c94b182 100644
--- a/src/systemd/src/basic/string-util.h
+++ b/src/systemd/src/basic/string-util.h
@@ -120,7 +120,7 @@ char *strjoin_real(const char *x, ...) _sentinel_;
         ({                                                              \
                 const char *_appendees_[] = { a, __VA_ARGS__ };         \
                 char *_d_, *_p_;                                        \
-                int _len_ = 0;                                          \
+                size_t _len_ = 0;                                          \
                 unsigned _i_;                                           \
                 for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \
                         _len_ += strlen(_appendees_[_i_]);              \
@@ -158,7 +158,7 @@ bool string_has_cc(const char *p, const char *ok) _pure_;
 char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent);
 char *ellipsize(const char *s, size_t length, unsigned percent);
 
-bool nulstr_contains(const char*nulstr, const char *needle);
+bool nulstr_contains(const char *nulstr, const char *needle);
 
 char* strshorten(char *s, size_t l);
 
@@ -189,7 +189,7 @@ static inline void *memmem_safe(const void *haystack, size_t haystacklen, const
         return memmem(haystack, haystacklen, needle, needlelen);
 }
 
-#if !HAVE_DECL_EXPLICIT_BZERO
+#if !HAVE_EXPLICIT_BZERO
 void explicit_bzero(void *p, size_t l);
 #endif
 
@@ -200,3 +200,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase);
 #define _cleanup_string_free_erase_ _cleanup_(string_free_erasep)
 
 bool string_is_safe(const char *p) _pure_;
+
+static inline size_t strlen_ptr(const char *s) {
+        if (!s)
+                return 0;
+
+        return strlen(s);
+}
diff --git a/src/systemd/src/basic/strv.c b/src/systemd/src/basic/strv.c
index a3660f08..08bcff6e 100644
--- a/src/systemd/src/basic/strv.c
+++ b/src/systemd/src/basic/strv.c
@@ -774,11 +774,7 @@ static int str_compare(const void *_a, const void *_b) {
 }
 
 char **strv_sort(char **l) {
-
-        if (strv_isempty(l))
-                return l;
-
-        qsort(l, strv_length(l), sizeof(char*), str_compare);
+        qsort_safe(l, strv_length(l), sizeof(char*), str_compare);
         return l;
 }
 
diff --git a/src/systemd/src/basic/time-util.c b/src/systemd/src/basic/time-util.c
index e8158d55..7f1c3f7c 100644
--- a/src/systemd/src/basic/time-util.c
+++ b/src/systemd/src/basic/time-util.c
@@ -23,6 +23,7 @@
 #include <limits.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/timerfd.h>
@@ -109,7 +110,7 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
         ts->realtime = u;
 
         delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
-        ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
+        ts->monotonic = usec_sub_signed(now(CLOCK_MONOTONIC), delta);
 
         return ts;
 }
@@ -126,8 +127,8 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u)
 
         ts->realtime = u;
         delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
-        ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
-        ts->boottime = clock_boottime_supported() ? usec_sub(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
+        ts->monotonic = usec_sub_signed(now(CLOCK_MONOTONIC), delta);
+        ts->boottime = clock_boottime_supported() ? usec_sub_signed(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
 
         return ts;
 }
@@ -143,7 +144,7 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
 
         ts->monotonic = u;
         delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
-        ts->realtime = usec_sub(now(CLOCK_REALTIME), delta);
+        ts->realtime = usec_sub_signed(now(CLOCK_REALTIME), delta);
 
         return ts;
 }
@@ -158,8 +159,8 @@ dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, us
 
         dual_timestamp_get(ts);
         delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u;
-        ts->realtime = usec_sub(ts->realtime, delta);
-        ts->monotonic = usec_sub(ts->monotonic, delta);
+        ts->realtime = usec_sub_signed(ts->realtime, delta);
+        ts->monotonic = usec_sub_signed(ts->monotonic, delta);
 
         return ts;
 }
@@ -244,7 +245,7 @@ usec_t timeval_load(const struct timeval *tv) {
 struct timeval *timeval_store(struct timeval *tv, usec_t u) {
         assert(tv);
 
-        if (u == USEC_INFINITY||
+        if (u == USEC_INFINITY ||
             u / USEC_PER_SEC > TIME_T_MAX) {
                 tv->tv_sec = (time_t) -1;
                 tv->tv_usec = (suseconds_t) -1;
@@ -560,15 +561,29 @@ void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
 
 int dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
         uint64_t a, b;
+        int r, pos;
 
         assert(value);
         assert(t);
 
-        if (sscanf(value, "%" PRIu64 "%" PRIu64, &a, &b) != 2) {
-                log_debug("Failed to parse dual timestamp value \"%s\": %m", value);
+        pos = strspn(value, WHITESPACE);
+        if (value[pos] == '-')
+                return -EINVAL;
+        pos += strspn(value + pos, DIGITS);
+        pos += strspn(value + pos, WHITESPACE);
+        if (value[pos] == '-')
+                return -EINVAL;
+
+        r = sscanf(value, "%" PRIu64 "%" PRIu64 "%n", &a, &b, &pos);
+        if (r != 2) {
+                log_debug("Failed to parse dual timestamp value \"%s\".", value);
                 return -EINVAL;
         }
 
+        if (value[pos] != '\0')
+                /* trailing garbage */
+                return -EINVAL;
+
         t->realtime = a;
         t->monotonic = b;
 
@@ -587,7 +602,7 @@ int timestamp_deserialize(const char *value, usec_t *timestamp) {
         return r;
 }
 
-int parse_timestamp(const char *t, usec_t *usec) {
+static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
         static const struct {
                 const char *name;
                 const int nr;
@@ -608,7 +623,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
                 { "Sat",       6 },
         };
 
-        const char *k, *utc, *tzn = NULL;
+        const char *k, *utc = NULL, *tzn = NULL;
         struct tm tm, copy;
         time_t x;
         usec_t x_usec, plus = 0, minus = 0, ret;
@@ -636,84 +651,86 @@ int parse_timestamp(const char *t, usec_t *usec) {
         assert(t);
         assert(usec);
 
-        if (t[0] == '@')
+        if (t[0] == '@' && !with_tz)
                 return parse_sec(t + 1, usec);
 
         ret = now(CLOCK_REALTIME);
 
-        if (streq(t, "now"))
-                goto finish;
+        if (!with_tz) {
+                if (streq(t, "now"))
+                        goto finish;
 
-        else if (t[0] == '+') {
-                r = parse_sec(t+1, &plus);
-                if (r < 0)
-                        return r;
+                else if (t[0] == '+') {
+                        r = parse_sec(t+1, &plus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
+                        goto finish;
 
-        } else if (t[0] == '-') {
-                r = parse_sec(t+1, &minus);
-                if (r < 0)
-                        return r;
+                } else if (t[0] == '-') {
+                        r = parse_sec(t+1, &minus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
+                        goto finish;
 
-        } else if ((k = endswith(t, " ago"))) {
-                t = strndupa(t, k - t);
+                } else if ((k = endswith(t, " ago"))) {
+                        t = strndupa(t, k - t);
 
-                r = parse_sec(t, &minus);
-                if (r < 0)
-                        return r;
+                        r = parse_sec(t, &minus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
+                        goto finish;
 
-        } else if ((k = endswith(t, " left"))) {
-                t = strndupa(t, k - t);
+                } else if ((k = endswith(t, " left"))) {
+                        t = strndupa(t, k - t);
 
-                r = parse_sec(t, &plus);
-                if (r < 0)
-                        return r;
+                        r = parse_sec(t, &plus);
+                        if (r < 0)
+                                return r;
 
-                goto finish;
-        }
+                        goto finish;
+                }
 
-        /* See if the timestamp is suffixed with UTC */
-        utc = endswith_no_case(t, " UTC");
-        if (utc)
-                t = strndupa(t, utc - t);
-        else {
-                const char *e = NULL;
-                int j;
+                /* See if the timestamp is suffixed with UTC */
+                utc = endswith_no_case(t, " UTC");
+                if (utc)
+                        t = strndupa(t, utc - t);
+                else {
+                        const char *e = NULL;
+                        int j;
 
-                tzset();
+                        tzset();
 
-                /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
-                 * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
-                 * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
-                 * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
-                 * support arbitrary timezone specifications.  */
+                        /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
+                        * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
+                        * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
+                        * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
+                        * support arbitrary timezone specifications.  */
 
-                for (j = 0; j <= 1; j++) {
+                        for (j = 0; j <= 1; j++) {
 
-                        if (isempty(tzname[j]))
-                                continue;
+                                if (isempty(tzname[j]))
+                                        continue;
 
-                        e = endswith_no_case(t, tzname[j]);
-                        if (!e)
-                                continue;
-                        if (e == t)
-                                continue;
-                        if (e[-1] != ' ')
-                                continue;
+                                e = endswith_no_case(t, tzname[j]);
+                                if (!e)
+                                        continue;
+                                if (e == t)
+                                        continue;
+                                if (e[-1] != ' ')
+                                        continue;
 
-                        break;
-                }
+                                break;
+                        }
 
-                if (IN_SET(j, 0, 1)) {
-                        /* Found one of the two timezones specified. */
-                        t = strndupa(t, e - t - 1);
-                        dst = j;
-                        tzn = tzname[j];
+                        if (IN_SET(j, 0, 1)) {
+                                /* Found one of the two timezones specified. */
+                                t = strndupa(t, e - t - 1);
+                                dst = j;
+                                tzn = tzname[j];
+                        }
                 }
         }
 
@@ -724,7 +741,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
                 return -EINVAL;
 
         tm.tm_isdst = dst;
-        if (tzn)
+        if (!with_tz && tzn)
                 tm.tm_zone = tzn;
 
         if (streq(t, "today")) {
@@ -837,11 +854,11 @@ parse_usec:
         }
 
 from_tm:
-        x = mktime_or_timegm(&tm, utc);
-        if (x < 0)
+        if (weekday >= 0 && tm.tm_wday != weekday)
                 return -EINVAL;
 
-        if (weekday >= 0 && tm.tm_wday != weekday)
+        x = mktime_or_timegm(&tm, utc);
+        if (x < 0)
                 return -EINVAL;
 
         ret = (usec_t) x * USEC_PER_SEC + x_usec;
@@ -855,16 +872,85 @@ finish:
         if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
                 return -EINVAL;
 
-        if (ret > minus)
+        if (ret >= minus)
                 ret -= minus;
         else
-                ret = 0;
+                return -EINVAL;
 
         *usec = ret;
 
         return 0;
 }
 
+typedef struct ParseTimestampResult {
+        usec_t usec;
+        int return_value;
+} ParseTimestampResult;
+
+int parse_timestamp(const char *t, usec_t *usec) {
+        char *last_space, *tz = NULL;
+        ParseTimestampResult *shared, tmp;
+        int r;
+        pid_t pid;
+
+        last_space = strrchr(t, ' ');
+        if (last_space != NULL && timezone_is_valid(last_space + 1))
+                tz = last_space + 1;
+
+        if (tz == NULL || endswith_no_case(t, " UTC"))
+                return parse_timestamp_impl(t, usec, false);
+
+        shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+        if (shared == MAP_FAILED)
+                return negative_errno();
+
+        pid = fork();
+
+        if (pid == -1) {
+                int fork_errno = errno;
+                (void) munmap(shared, sizeof *shared);
+                return -fork_errno;
+        }
+
+        if (pid == 0) {
+                bool with_tz = true;
+
+                if (setenv("TZ", tz, 1) != 0) {
+                        shared->return_value = negative_errno();
+                        _exit(EXIT_FAILURE);
+                }
+
+                tzset();
+
+                /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation.
+                 * Otherwise just cut it off */
+                with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
+
+                /*cut off the timezone if we dont need it*/
+                if (with_tz)
+                        t = strndupa(t, last_space - t);
+
+                shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        r = wait_for_terminate(pid, NULL);
+        if (r < 0) {
+                (void) munmap(shared, sizeof *shared);
+                return r;
+        }
+
+        tmp = *shared;
+        if (munmap(shared, sizeof *shared) != 0)
+                return negative_errno();
+
+        if (tmp.return_value == 0)
+                *usec = tmp.usec;
+
+        return tmp.return_value;
+}
+
 static char* extract_multiplier(char *p, usec_t *multiplier) {
         static const struct {
                 const char *suffix;
@@ -1000,6 +1086,20 @@ int parse_sec(const char *t, usec_t *usec) {
         return parse_time(t, usec, USEC_PER_SEC);
 }
 
+int parse_sec_fix_0(const char *t, usec_t *usec) {
+        assert(t);
+        assert(usec);
+
+        t += strspn(t, WHITESPACE);
+
+        if (streq(t, "0")) {
+                *usec = USEC_INFINITY;
+                return 0;
+        }
+
+        return parse_sec(t, usec);
+}
+
 int parse_nsec(const char *t, nsec_t *nsec) {
         static const struct {
                 const char *suffix;
@@ -1218,7 +1318,7 @@ bool timezone_is_valid(const char *name) {
                 if (!(*p >= '0' && *p <= '9') &&
                     !(*p >= 'a' && *p <= 'z') &&
                     !(*p >= 'A' && *p <= 'Z') &&
-                    !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
+                    !IN_SET(*p, '-', '_', '+', '/'))
                         return false;
 
                 if (*p == '/') {
@@ -1344,4 +1444,23 @@ unsigned long usec_to_jiffies(usec_t u) {
 
         return DIV_ROUND_UP(u , USEC_PER_SEC / hz);
 }
+
+usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to) {
+        usec_t a, b;
+
+        if (x == USEC_INFINITY)
+                return USEC_INFINITY;
+        if (map_clock_id(from) == map_clock_id(to))
+                return x;
+
+        a = now(from);
+        b = now(to);
+
+        if (x > a)
+                /* x lies in the future */
+                return usec_add(b, usec_sub_unsigned(x, a));
+        else
+                /* x lies in the past */
+                return usec_sub_unsigned(b, usec_sub_unsigned(a, x));
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/time-util.h b/src/systemd/src/basic/time-util.h
index 7463507f..73f7e400 100644
--- a/src/systemd/src/basic/time-util.h
+++ b/src/systemd/src/basic/time-util.h
@@ -133,6 +133,7 @@ int timestamp_deserialize(const char *value, usec_t *timestamp);
 int parse_timestamp(const char *t, usec_t *usec);
 
 int parse_sec(const char *t, usec_t *usec);
+int parse_sec_fix_0(const char *t, usec_t *usec);
 int parse_time(const char *t, usec_t *usec, usec_t default_unit);
 int parse_nsec(const char *t, nsec_t *nsec);
 
@@ -145,9 +146,7 @@ bool clock_boottime_supported(void);
 bool clock_supported(clockid_t clock);
 clockid_t clock_boottime_or_monotonic(void);
 
-#define xstrftime(buf, fmt, tm) \
-        assert_message_se(strftime(buf, ELEMENTSOF(buf), fmt, tm) > 0, \
-                          "xstrftime: " #buf "[] must be big enough")
+usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
 
 int get_timezone(char **timezone);
 
@@ -169,19 +168,23 @@ static inline usec_t usec_add(usec_t a, usec_t b) {
         return c;
 }
 
-static inline usec_t usec_sub(usec_t timestamp, int64_t delta) {
-        if (delta < 0)
-                return usec_add(timestamp, (usec_t) (-delta));
+static inline usec_t usec_sub_unsigned(usec_t timestamp, usec_t delta) {
 
         if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */
                 return USEC_INFINITY;
-
-        if (timestamp < (usec_t) delta)
+        if (timestamp < delta)
                 return 0;
 
         return timestamp - delta;
 }
 
+static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
+        if (delta < 0)
+                return usec_add(timestamp, (usec_t) (-delta));
+        else
+                return usec_sub_unsigned(timestamp, (usec_t) delta);
+}
+
 #if SIZEOF_TIME_T == 8
 /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit year
  * territory. However, since we want to stay away from this in all timezones we take one day off. */
diff --git a/src/systemd/src/basic/unaligned.h b/src/systemd/src/basic/unaligned.h
deleted file mode 100644
index 7c847a3c..00000000
--- a/src/systemd/src/basic/unaligned.h
+++ /dev/null
@@ -1,129 +0,0 @@
-#pragma once
-
-/***
-  This file is part of systemd.
-
-  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
-  (at your option) any later version.
-
-  systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
-
-#include <endian.h>
-#include <stdint.h>
-
-/* BE */
-
-static inline uint16_t unaligned_read_be16(const void *_u) {
-        const uint8_t *u = _u;
-
-        return (((uint16_t) u[0]) << 8) |
-                ((uint16_t) u[1]);
-}
-
-static inline uint32_t unaligned_read_be32(const void *_u) {
-        const uint8_t *u = _u;
-
-        return (((uint32_t) unaligned_read_be16(u)) << 16) |
-                ((uint32_t) unaligned_read_be16(u + 2));
-}
-
-static inline uint64_t unaligned_read_be64(const void *_u) {
-        const uint8_t *u = _u;
-
-        return (((uint64_t) unaligned_read_be32(u)) << 32) |
-                ((uint64_t) unaligned_read_be32(u + 4));
-}
-
-static inline void unaligned_write_be16(void *_u, uint16_t a) {
-        uint8_t *u = _u;
-
-        u[0] = (uint8_t) (a >> 8);
-        u[1] = (uint8_t) a;
-}
-
-static inline void unaligned_write_be32(void *_u, uint32_t a) {
-        uint8_t *u = _u;
-
-        unaligned_write_be16(u, (uint16_t) (a >> 16));
-        unaligned_write_be16(u + 2, (uint16_t) a);
-}
-
-static inline void unaligned_write_be64(void *_u, uint64_t a) {
-        uint8_t *u = _u;
-
-        unaligned_write_be32(u, (uint32_t) (a >> 32));
-        unaligned_write_be32(u + 4, (uint32_t) a);
-}
-
-/* LE */
-
-static inline uint16_t unaligned_read_le16(const void *_u) {
-        const uint8_t *u = _u;
-
-        return (((uint16_t) u[1]) << 8) |
-                ((uint16_t) u[0]);
-}
-
-static inline uint32_t unaligned_read_le32(const void *_u) {
-        const uint8_t *u = _u;
-
-        return (((uint32_t) unaligned_read_le16(u + 2)) << 16) |
-                ((uint32_t) unaligned_read_le16(u));
-}
-
-static inline uint64_t unaligned_read_le64(const void *_u) {
-        const uint8_t *u = _u;
-
-        return (((uint64_t) unaligned_read_le32(u + 4)) << 32) |
-                ((uint64_t) unaligned_read_le32(u));
-}
-
-static inline void unaligned_write_le16(void *_u, uint16_t a) {
-        uint8_t *u = _u;
-
-        u[0] = (uint8_t) a;
-        u[1] = (uint8_t) (a >> 8);
-}
-
-static inline void unaligned_write_le32(void *_u, uint32_t a) {
-        uint8_t *u = _u;
-
-        unaligned_write_le16(u, (uint16_t) a);
-        unaligned_write_le16(u + 2, (uint16_t) (a >> 16));
-}
-
-static inline void unaligned_write_le64(void *_u, uint64_t a) {
-        uint8_t *u = _u;
-
-        unaligned_write_le32(u, (uint32_t) a);
-        unaligned_write_le32(u + 4, (uint32_t) (a >> 32));
-}
-
-#if __BYTE_ORDER == __BIG_ENDIAN
-#define unaligned_read_ne16 unaligned_read_be16
-#define unaligned_read_ne32 unaligned_read_be32
-#define unaligned_read_ne64 unaligned_read_be64
-
-#define unaligned_write_ne16 unaligned_write_be16
-#define unaligned_write_ne32 unaligned_write_be32
-#define unaligned_write_ne64 unaligned_write_be64
-#else
-#define unaligned_read_ne16 unaligned_read_le16
-#define unaligned_read_ne32 unaligned_read_le32
-#define unaligned_read_ne64 unaligned_read_le64
-
-#define unaligned_write_ne16 unaligned_write_le16
-#define unaligned_write_ne32 unaligned_write_le32
-#define unaligned_write_ne64 unaligned_write_le64
-#endif
diff --git a/src/systemd/src/basic/utf8.c b/src/systemd/src/basic/utf8.c
index a6bdda6e..ff281e43 100644
--- a/src/systemd/src/basic/utf8.c
+++ b/src/systemd/src/basic/utf8.c
@@ -75,7 +75,7 @@ static bool unichar_is_control(char32_t ch) {
           '\t' is in C0 range, but more or less harmless and commonly used.
         */
 
-        return (ch < ' ' && ch != '\t' && ch != '\n') ||
+        return (ch < ' ' && !IN_SET(ch, '\t', '\n')) ||
                 (0x7F <= ch && ch <= 0x9F);
 }
 
diff --git a/src/systemd/src/basic/util.c b/src/systemd/src/basic/util.c
index 563ee87e..c8a22d68 100644
--- a/src/systemd/src/basic/util.c
+++ b/src/systemd/src/basic/util.c
@@ -36,6 +36,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
+#include "btrfs-util.h"
 #include "build.h"
 #include "cgroup-util.h"
 #include "def.h"
@@ -181,15 +182,12 @@ int block_get_whole_disk(dev_t d, dev_t *ret) {
 }
 
 bool kexec_loaded(void) {
-       bool loaded = false;
-       char *s;
-
-       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) {
-               if (s[0] == '1')
-                       loaded = true;
-               free(s);
-       }
-       return loaded;
+       _cleanup_free_ char *s = NULL;
+
+       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) < 0)
+               return false;
+
+       return s[0] == '1';
 }
 
 int prot_from_flags(int flags) {
@@ -224,7 +222,7 @@ int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *pa
         /* Spawns a temporary TTY agent, making sure it goes away when
          * we go away */
 
-        parent_pid = getpid();
+        parent_pid = getpid_cached();
 
         /* First we temporarily block all signals, so that the new
          * child has them blocked initially. This way, we can be sure
@@ -382,7 +380,7 @@ int on_ac_power(void) {
 
                 device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY);
                 if (device < 0) {
-                        if (errno == ENOENT || errno == ENOTDIR)
+                        if (IN_SET(errno, ENOENT, ENOTDIR))
                                 continue;
 
                         return -errno;
@@ -546,7 +544,7 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
                 if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0)
                         return -ENOMEM;
 
-                r = files_same(userns_fd_path, "/proc/self/ns/user");
+                r = files_same(userns_fd_path, "/proc/self/ns/user", 0);
                 if (r < 0)
                         return r;
                 if (r)
@@ -724,4 +722,134 @@ int version(void) {
              SYSTEMD_FEATURES);
         return 0;
 }
+
+int get_block_device(const char *path, dev_t *dev) {
+        struct stat st;
+        struct statfs sfs;
+
+        assert(path);
+        assert(dev);
+
+        /* Get's the block device directly backing a file system. If
+         * the block device is encrypted, returns the device mapper
+         * block device. */
+
+        if (lstat(path, &st))
+                return -errno;
+
+        if (major(st.st_dev) != 0) {
+                *dev = st.st_dev;
+                return 1;
+        }
+
+        if (statfs(path, &sfs) < 0)
+                return -errno;
+
+        if (F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC))
+                return btrfs_get_block_device(path, dev);
+
+        return 0;
+}
+
+int get_block_device_harder(const char *path, dev_t *dev) {
+        _cleanup_closedir_ DIR *d = NULL;
+        _cleanup_free_ char *p = NULL, *t = NULL;
+        struct dirent *de, *found = NULL;
+        const char *q;
+        unsigned maj, min;
+        dev_t dt;
+        int r;
+
+        assert(path);
+        assert(dev);
+
+        /* Gets the backing block device for a file system, and
+         * handles LUKS encrypted file systems, looking for its
+         * immediate parent, if there is one. */
+
+        r = get_block_device(path, &dt);
+        if (r <= 0)
+                return r;
+
+        if (asprintf(&p, "/sys/dev/block/%u:%u/slaves", major(dt), minor(dt)) < 0)
+                return -ENOMEM;
+
+        d = opendir(p);
+        if (!d) {
+                if (errno == ENOENT)
+                        goto fallback;
+
+                return -errno;
+        }
+
+        FOREACH_DIRENT_ALL(de, d, return -errno) {
+
+                if (dot_or_dot_dot(de->d_name))
+                        continue;
+
+                if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN))
+                        continue;
+
+                if (found) {
+                        _cleanup_free_ char *u = NULL, *v = NULL, *a = NULL, *b = NULL;
+
+                        /* We found a device backed by multiple other devices. We don't really support automatic
+                         * discovery on such setups, with the exception of dm-verity partitions. In this case there are
+                         * two backing devices: the data partition and the hash partition. We are fine with such
+                         * setups, however, only if both partitions are on the same physical device. Hence, let's
+                         * verify this. */
+
+                        u = strjoin(p, "/", de->d_name, "/../dev");
+                        if (!u)
+                                return -ENOMEM;
+
+                        v = strjoin(p, "/", found->d_name, "/../dev");
+                        if (!v)
+                                return -ENOMEM;
+
+                        r = read_one_line_file(u, &a);
+                        if (r < 0) {
+                                log_debug_errno(r, "Failed to read %s: %m", u);
+                                goto fallback;
+                        }
+
+                        r = read_one_line_file(v, &b);
+                        if (r < 0) {
+                                log_debug_errno(r, "Failed to read %s: %m", v);
+                                goto fallback;
+                        }
+
+                        /* Check if the parent device is the same. If not, then the two backing devices are on
+                         * different physical devices, and we don't support that. */
+                        if (!streq(a, b))
+                                goto fallback;
+                }
+
+                found = de;
+        }
+
+        if (!found)
+                goto fallback;
+
+        q = strjoina(p, "/", found->d_name, "/dev");
+
+        r = read_one_line_file(q, &t);
+        if (r == -ENOENT)
+                goto fallback;
+        if (r < 0)
+                return r;
+
+        if (sscanf(t, "%u:%u", &maj, &min) != 2)
+                return -EINVAL;
+
+        if (maj == 0)
+                goto fallback;
+
+        *dev = makedev(maj, min);
+        return 1;
+
+fallback:
+        *dev = dt;
+        return 1;
+}
 #endif /* NM_IGNORED */
diff --git a/src/systemd/src/basic/util.h b/src/systemd/src/basic/util.h
index c7da6c39..b31dfd1c 100644
--- a/src/systemd/src/basic/util.h
+++ b/src/systemd/src/basic/util.h
@@ -192,3 +192,6 @@ uint64_t system_tasks_max_scale(uint64_t v, uint64_t max);
 int update_reboot_parameter_and_warn(const char *param);
 
 int version(void);
+
+int get_block_device(const char *path, dev_t *dev);
+int get_block_device_harder(const char *path, dev_t *dev);
diff --git a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
index 82cae230..7847ce07 100644
--- a/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-lease-internal.h
@@ -75,6 +75,7 @@ struct sd_dhcp_lease {
         uint16_t mtu; /* 0 if unset */
 
         char *domainname;
+        char **search_domains;
         char *hostname;
         char *root_path;
 
@@ -92,6 +93,7 @@ struct sd_dhcp_lease {
 int dhcp_lease_new(sd_dhcp_lease **ret);
 
 int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata);
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains);
 int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len);
 
 int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index 7ad0ec37..f01b2cfe 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -110,14 +110,16 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link,
         if (r < 0)
                 return -errno;
 
-        link->ll.sll_family = AF_PACKET;
-        link->ll.sll_protocol = htobe16(ETH_P_IP);
-        link->ll.sll_ifindex = ifindex;
-        link->ll.sll_hatype = htobe16(arp_type);
-        link->ll.sll_halen = mac_addr_len;
+        link->ll = (struct sockaddr_ll) {
+                .sll_family = AF_PACKET,
+                .sll_protocol = htobe16(ETH_P_IP),
+                .sll_ifindex = ifindex,
+                .sll_hatype = htobe16(arp_type),
+                .sll_halen = mac_addr_len,
+        };
         memcpy(link->ll.sll_addr, bcast_addr, mac_addr_len);
 
-        r = bind(s, &link->sa, sizeof(link->ll));
+        r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll));
         if (r < 0)
                 return -errno;
 
@@ -223,7 +225,7 @@ int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link,
         assert(packet);
         assert(len);
 
-        r = sendto(s, packet, len, 0, &link->sa, sizeof(link->ll));
+        r = sendto(s, packet, len, 0, &link->sa, SOCKADDR_LL_LEN(link->ll));
         if (r < 0)
                 return -errno;
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/systemd/src/libsystemd-network/dhcp-packet.c
index 27410520..1bb1bfcf 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/systemd/src/libsystemd-network/dhcp-packet.c
@@ -36,8 +36,8 @@ int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid,
         size_t offset = 0;
         int r;
 
-        assert(op == BOOTREQUEST || op == BOOTREPLY);
-        assert(arp_type == ARPHRD_ETHER || arp_type == ARPHRD_INFINIBAND);
+        assert(IN_SET(op, BOOTREQUEST, BOOTREPLY));
+        assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND));
 
         message->op = op;
         message->htype = arp_type;
diff --git a/src/systemd/src/libsystemd-network/lldp-neighbor.c b/src/systemd/src/libsystemd-network/lldp-neighbor.c
index afede1e7..c560a864 100644
--- a/src/systemd/src/libsystemd-network/lldp-neighbor.c
+++ b/src/systemd/src/libsystemd-network/lldp-neighbor.c
@@ -251,10 +251,9 @@ int lldp_neighbor_parse(sd_lldp_neighbor *n) {
                                 log_lldp("End marker TLV not zero-sized, ignoring datagram.");
                                 return -EBADMSG;
                         }
-                        if (left != 0) {
-                                log_lldp("Trailing garbage in datagram, ignoring datagram.");
-                                return -EBADMSG;
-                        }
+
+                        /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
+                         * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
 
                         goto end_marker;
 
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index 285a73e3..de37b9f0 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -351,8 +351,47 @@ int config_parse_iaid(const char *unit,
 
         return 0;
 }
+
+int config_parse_bridge_port_priority(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        uint16_t i;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        r = safe_atou16(rvalue, &i);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Failed to parse bridge port priority, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        if (i > LINK_BRIDGE_PORT_PRIORITY_MAX) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Bridge port priority is larger than maximum %u, ignoring: %s", LINK_BRIDGE_PORT_PRIORITY_MAX, rvalue);
+                return 0;
+        }
+
+        *((uint16_t *)data) = i;
+
+        return 0;
+}
 #endif /* NM_IGNORED */
 
+
 void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) {
         unsigned i;
 
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index 5bcd5771..4666f174 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -26,6 +26,9 @@
 #include "condition.h"
 #include "udev.h"
 
+#define LINK_BRIDGE_PORT_PRIORITY_INVALID 128
+#define LINK_BRIDGE_PORT_PRIORITY_MAX 63
+
 bool net_match_config(const struct ether_addr *match_mac,
                       char * const *match_path,
                       char * const *match_driver,
@@ -62,6 +65,10 @@ int config_parse_iaid(const char *unit, const char *filename, unsigned line,
                       const char *section, unsigned section_line, const char *lvalue,
                       int ltype, const char *rvalue, void *data, void *userdata);
 
+int config_parse_bridge_port_priority(const char *unit, const char *filename, unsigned line,
+                      const char *section, unsigned section_line, const char *lvalue,
+                      int ltype, const char *rvalue, void *data, void *userdata);
+
 int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result);
 const char *net_get_name(struct udev_device *device);
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index 17393e20..fc6ad422 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -64,6 +64,7 @@ struct sd_dhcp_client {
         uint8_t *req_opts;
         size_t req_opts_allocated;
         size_t req_opts_size;
+        bool anonymize;
         be32_t last_addr;
         uint8_t mac_addr[MAX_MAC_ADDR_LEN];
         size_t mac_addr_len;
@@ -118,6 +119,32 @@ static const uint8_t default_req_opts[] = {
         SD_DHCP_OPTION_DOMAIN_NAME_SERVER,
 };
 
+/* RFC7844 section 3:
+   MAY contain the Parameter Request List option.
+   RFC7844 section 3.6:
+   The client intending to protect its privacy SHOULD only request a
+   minimal number of options in the PRL and SHOULD also randomly shuffle
+   the ordering of option codes in the PRL.  If this random ordering
+   cannot be implemented, the client MAY order the option codes in the
+   PRL by option code number (lowest to highest).
+*/
+/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */
+static const uint8_t default_req_opts_anonymize[] = {
+       SD_DHCP_OPTION_SUBNET_MASK,                     /* 1 */
+       SD_DHCP_OPTION_ROUTER,                          /* 3 */
+       SD_DHCP_OPTION_DOMAIN_NAME_SERVER,              /* 6 */
+       SD_DHCP_OPTION_DOMAIN_NAME,                     /* 15 */
+       SD_DHCP_OPTION_ROUTER_DISCOVER,                 /* 31 */
+       SD_DHCP_OPTION_STATIC_ROUTE,                    /* 33 */
+       SD_DHCP_OPTION_VENDOR_SPECIFIC,                 /* 43 */
+       SD_DHCP_OPTION_NETBIOS_NAMESERVER,              /* 44 */
+       SD_DHCP_OPTION_NETBIOS_NODETYPE,                /* 46 */
+       SD_DHCP_OPTION_NETBIOS_SCOPE,                   /* 47 */
+       SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,          /* 121 */
+       SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE,  /* 249 */
+       SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,     /* 252 */
+};
+
 static int client_receive_message_raw(
                 sd_event_source *s,
                 int fd,
@@ -432,9 +459,7 @@ int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) {
 int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         assert_return(client, -EINVAL);
 
-        if (client->state != DHCP_STATE_BOUND &&
-            client->state != DHCP_STATE_RENEWING &&
-            client->state != DHCP_STATE_REBINDING)
+        if (!IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
                 return -EADDRNOTAVAIL;
 
         if (ret)
@@ -507,7 +532,7 @@ static int client_message_init(
         assert(ret);
         assert(_optlen);
         assert(_optoffset);
-        assert(type == DHCP_DISCOVER || type == DHCP_REQUEST);
+        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST));
 
         optlen = DHCP_MIN_OPTIONS_SIZE;
         size = sizeof(DHCPPacket) + optlen;
@@ -592,11 +617,18 @@ static int client_message_init(
            it MUST include that list in any subsequent DHCPREQUEST
            messages.
          */
-        r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
-                               SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
-                               client->req_opts_size, client->req_opts);
-        if (r < 0)
-                return r;
+
+        /* RFC7844 section 3:
+           MAY contain the Parameter Request List option. */
+        /* NOTE: in case that there would be an option to do not send
+         * any PRL at all, the size should be checked before sending */
+        if (client->req_opts_size > 0) {
+                r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
+                                       SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
+                                       client->req_opts_size, client->req_opts);
+                if (r < 0)
+                        return r;
+        }
 
         /* RFC2131 section 3.5:
            The client SHOULD include the ’maximum DHCP message size’ option to
@@ -620,12 +652,16 @@ static int client_message_init(
            Maximum DHCP Message Size option is the total maximum packet size,
            including IP and UDP headers.)
          */
-        max_size = htobe16(size);
-        r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
-                               SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
-                               2, &max_size);
-        if (r < 0)
-                return r;
+        /* RFC7844 section 3:
+           SHOULD NOT contain any other option. */
+        if (!client->anonymize) {
+                max_size = htobe16(size);
+                r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
+                                       SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
+                                       2, &max_size);
+                if (r < 0)
+                        return r;
+        }
 
         *_optlen = optlen;
         *_optoffset = optoffset;
@@ -675,8 +711,7 @@ static int client_send_discover(sd_dhcp_client *client) {
         int r;
 
         assert(client);
-        assert(client->state == DHCP_STATE_INIT ||
-               client->state == DHCP_STATE_SELECTING);
+        assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
 
         r = client_message_init(client, &discover, DHCP_DISCOVER,
                                 &optlen, &optoffset);
@@ -1130,7 +1165,7 @@ static int client_start_delayed(sd_dhcp_client *client) {
         }
         client->fd = r;
 
-        if (client->state == DHCP_STATE_INIT || client->state == DHCP_STATE_INIT_REBOOT)
+        if (IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT))
                 client->start_time = now(clock_boottime_or_monotonic());
 
         return client_initialize_events(client, client_receive_message_raw);
@@ -1656,10 +1691,11 @@ static int client_receive_message_udp(
 
         len = recv(fd, message, buflen, 0);
         if (len < 0) {
-                if (errno == EAGAIN || errno == EINTR)
+                if (IN_SET(errno, EAGAIN, EINTR))
                         return 0;
 
-                return log_dhcp_client_errno(client, errno, "Could not receive message from UDP socket: %m");
+                return log_dhcp_client_errno(client, errno,
+                                             "Could not receive message from UDP socket: %m");
         }
         if ((size_t) len < sizeof(DHCPMessage)) {
                 log_dhcp_client(client, "Too small to be a DHCP message: ignoring");
@@ -1749,12 +1785,11 @@ static int client_receive_message_raw(
 
         len = recvmsg(fd, &msg, 0);
         if (len < 0) {
-                if (errno == EAGAIN || errno == EINTR)
+                if (IN_SET(errno, EAGAIN, EINTR))
                         return 0;
 
-                log_dhcp_client(client, "Could not receive message from raw socket: %m");
-
-                return -errno;
+                return log_dhcp_client_errno(client, errno,
+                                             "Could not receive message from raw socket: %m");
         } else if ((size_t)len < sizeof(DHCPPacket))
                 return 0;
 
@@ -1787,7 +1822,14 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
         if (r < 0)
                 return r;
 
-        if (client->last_addr)
+        /* RFC7844 section 3.3:
+           SHOULD perform a complete four-way handshake, starting with a
+           DHCPDISCOVER, to obtain a new address lease.  If the client can
+           ascertain that this is exactly the same network to which it was
+           previously connected, and if the link-layer address did not change,
+           the client MAY issue a DHCPREQUEST to try to reclaim the current
+           address. */
+        if (client->last_addr && !client->anonymize)
                 client->state = DHCP_STATE_INIT_REBOOT;
 
         r = client_start(client);
@@ -1879,7 +1921,7 @@ sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) {
         return mfree(client);
 }
 
-int sd_dhcp_client_new(sd_dhcp_client **ret) {
+int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
         _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL;
 
         assert_return(ret, -EINVAL);
@@ -1896,8 +1938,15 @@ int sd_dhcp_client_new(sd_dhcp_client **ret) {
         client->mtu = DHCP_DEFAULT_MIN_SIZE;
         client->port = DHCP_PORT_CLIENT;
 
-        client->req_opts_size = ELEMENTSOF(default_req_opts);
-        client->req_opts = memdup(default_req_opts, client->req_opts_size);
+        client->anonymize = !!anonymize;
+        /* NOTE: this could be moved to a function. */
+        if (anonymize) {
+                client->req_opts_size = ELEMENTSOF(default_req_opts_anonymize);
+                client->req_opts = memdup(default_req_opts_anonymize, client->req_opts_size);
+        } else {
+                client->req_opts_size = ELEMENTSOF(default_req_opts);
+                client->req_opts = memdup(default_req_opts, client->req_opts_size);
+        }
         if (!client->req_opts)
                 return -ENOMEM;
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index 5a3bff2f..c00190b5 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -233,6 +233,21 @@ int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) {
         return (int) lease->static_route_size;
 }
 
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) {
+        unsigned r;
+
+        assert_return(lease, -EINVAL);
+        assert_return(domains, -EINVAL);
+
+        r = strv_length(lease->search_domains);
+        if (r > 0) {
+                *domains = lease->search_domains;
+                return (int) r;
+        }
+
+        return -ENODATA;
+}
+
 int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) {
         assert_return(lease, -EINVAL);
         assert_return(data, -EINVAL);
@@ -284,6 +299,7 @@ sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) {
         free(lease->static_route);
         free(lease->client_id);
         free(lease->vendor_specific);
+        strv_free(lease->search_domains);
         return mfree(lease);
 }
 
@@ -457,7 +473,7 @@ static int lease_parse_routes(
                 struct sd_dhcp_route *route = *routes + *routes_size;
                 int r;
 
-                r = in_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen);
+                r = in4_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen);
                 if (r < 0) {
                         log_debug("Failed to determine destination prefix length from class based IP, ignoring");
                         continue;
@@ -596,6 +612,11 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
                 r = lease_parse_u16(option, len, &lease->mtu, 68);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse MTU, ignoring: %m");
+                if (lease->mtu < DHCP_DEFAULT_MIN_SIZE) {
+                        log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_DEFAULT_MIN_SIZE);
+                        lease->mtu = DHCP_DEFAULT_MIN_SIZE;
+                }
+
                 break;
 
         case SD_DHCP_OPTION_DOMAIN_NAME:
@@ -607,6 +628,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
 
                 break;
 
+        case SD_DHCP_OPTION_DOMAIN_SEARCH_LIST:
+                r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m");
+                break;
+
         case SD_DHCP_OPTION_HOST_NAME:
                 r = lease_parse_domain(option, len, &lease->hostname);
                 if (r < 0) {
@@ -698,6 +725,96 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
         return 0;
 }
 
+/* Parses compressed domain names. */
+int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) {
+        _cleanup_strv_free_ char **names = NULL;
+        size_t pos = 0, cnt = 0;
+        int r;
+
+        assert(domains);
+        assert_return(option && len > 0, -ENODATA);
+
+        while (pos < len) {
+                _cleanup_free_ char *name = NULL;
+                size_t n = 0, allocated = 0;
+                size_t jump_barrier = pos, next_chunk = 0;
+                bool first = true;
+
+                for (;;) {
+                        uint8_t c;
+                        c = option[pos++];
+
+                        if (c == 0) {
+                                /* End of name */
+                                break;
+                        } else if (c <= 63) {
+                                const char *label;
+
+                                /* Literal label */
+                                label = (const char*) (option + pos);
+                                pos += c;
+                                if (pos >= len)
+                                        return -EBADMSG;
+
+                                if (!GREEDY_REALLOC(name, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
+                                        return -ENOMEM;
+
+                                if (first)
+                                        first = false;
+                                else
+                                        name[n++] = '.';
+
+                                r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX);
+                                if (r < 0)
+                                        return r;
+
+                                n += r;
+                        } else if ((c & 0xc0) == 0xc0) {
+                                /* Pointer */
+
+                                uint8_t d;
+                                uint16_t ptr;
+
+                                if (pos >= len)
+                                        return -EBADMSG;
+
+                                d = option[pos++];
+                                ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d;
+
+                                /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */
+                                if (ptr >= jump_barrier)
+                                        return -EBADMSG;
+                                jump_barrier = ptr;
+
+                                /* Save current location so we don't end up re-parsing what's parsed so far. */
+                                if (next_chunk == 0)
+                                        next_chunk = pos;
+
+                                pos = ptr;
+                        } else
+                                return -EBADMSG;
+                }
+
+                if (!GREEDY_REALLOC(name, allocated, n + 1))
+                        return -ENOMEM;
+                name[n] = 0;
+
+                r = strv_extend(&names, name);
+                if (r < 0)
+                        return r;
+
+                cnt++;
+
+                if (next_chunk != 0)
+                      pos = next_chunk;
+        }
+
+        *domains = names;
+        names = NULL;
+
+        return cnt;
+}
+
 int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) {
         struct sd_dhcp_raw_option *cur, *option;
 
@@ -753,6 +870,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         const char *string;
         uint16_t mtu;
         _cleanup_free_ sd_dhcp_route **routes = NULL;
+        char **search_domains = NULL;
         uint32_t t1, t2, lifetime;
         int r;
 
@@ -810,22 +928,29 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
 
         r = sd_dhcp_lease_get_dns(lease, &addresses);
         if (r > 0) {
-                fputs("DNS=", f);
+                fputs_unlocked("DNS=", f);
                 serialize_in_addrs(f, addresses, r);
-                fputs("\n", f);
+                fputs_unlocked("\n", f);
         }
 
         r = sd_dhcp_lease_get_ntp(lease, &addresses);
         if (r > 0) {
-                fputs("NTP=", f);
+                fputs_unlocked("NTP=", f);
                 serialize_in_addrs(f, addresses, r);
-                fputs("\n", f);
+                fputs_unlocked("\n", f);
         }
 
         r = sd_dhcp_lease_get_domainname(lease, &string);
         if (r >= 0)
                 fprintf(f, "DOMAINNAME=%s\n", string);
 
+        r = sd_dhcp_lease_get_search_domains(lease, &search_domains);
+        if (r > 0) {
+                fputs_unlocked("DOMAIN_SEARCH_LIST=", f);
+                fputstrv(f, search_domains, NULL, NULL);
+                fputs_unlocked("\n", f);
+        }
+
         r = sd_dhcp_lease_get_hostname(lease, &string);
         if (r >= 0)
                 fprintf(f, "HOSTNAME=%s\n", string);
@@ -907,6 +1032,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                 *ntp = NULL,
                 *mtu = NULL,
                 *routes = NULL,
+                *domains = NULL,
                 *client_id_hex = NULL,
                 *vendor_specific_hex = NULL,
                 *lifetime = NULL,
@@ -935,6 +1061,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                            "MTU", &mtu,
                            "DOMAINNAME", &lease->domainname,
                            "HOSTNAME", &lease->hostname,
+                           "DOMAIN_SEARCH_LIST", &domains,
                            "ROOT_PATH", &lease->root_path,
                            "ROUTES", &routes,
                            "CLIENTID", &client_id_hex,
@@ -1040,6 +1167,18 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
                         log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu);
         }
 
+        if (domains) {
+                _cleanup_strv_free_ char **a = NULL;
+                a = strv_split(domains, " ");
+                if (!a)
+                        return -ENOMEM;
+
+                if (!strv_isempty(a)) {
+                        lease->search_domains = a;
+                        a = NULL;
+                }
+        }
+
         if (routes) {
                 r = deserialize_dhcp_routes(
                                 &lease->static_route,
@@ -1116,7 +1255,7 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) {
         address.s_addr = lease->address;
 
         /* fall back to the default subnet masks based on address class */
-        r = in_addr_default_subnet_mask(&address, &mask);
+        r = in4_addr_default_subnet_mask(&address, &mask);
         if (r < 0)
                 return r;
 
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index fa508d68..f512b65b 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -944,7 +944,7 @@ static int client_receive_message(
 
         len = recv(fd, message, buflen, 0);
         if (len < 0) {
-                if (errno == EAGAIN || errno == EINTR)
+                if (IN_SET(errno, EAGAIN, EINTR))
                         return 0;
 
                 return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m");
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index 3976768b..694384b5 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -356,7 +356,7 @@ static int ipv4acd_on_packet(
 
         n = recv(fd, &packet, sizeof(struct ether_arp), 0);
         if (n < 0) {
-                if (errno == EAGAIN || errno == EINTR)
+                if (IN_SET(errno, EAGAIN, EINTR))
                         return 0;
 
                 log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m");
diff --git a/src/systemd/src/libsystemd-network/sd-lldp.c b/src/systemd/src/libsystemd-network/sd-lldp.c
index 2a64a99b..31e24486 100644
--- a/src/systemd/src/libsystemd-network/sd-lldp.c
+++ b/src/systemd/src/libsystemd-network/sd-lldp.c
@@ -220,7 +220,7 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
 
         length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
         if (length < 0) {
-                if (errno == EAGAIN || errno == EINTR)
+                if (IN_SET(errno, EAGAIN, EINTR))
                         return 0;
 
                 return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index 3f7b703e..9dfe6847 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -438,7 +438,7 @@ _public_ int sd_event_new(sd_event** ret) {
         e->watchdog_fd = e->epoll_fd = e->realtime.fd = e->boottime.fd = e->monotonic.fd = e->realtime_alarm.fd = e->boottime_alarm.fd = -1;
         e->realtime.next = e->boottime.next = e->monotonic.next = e->realtime_alarm.next = e->boottime_alarm.next = USEC_INFINITY;
         e->realtime.wakeup = e->boottime.wakeup = e->monotonic.wakeup = e->realtime_alarm.wakeup = e->boottime_alarm.wakeup = WAKEUP_CLOCK_DATA;
-        e->original_pid = getpid();
+        e->original_pid = getpid_cached();
         e->perturb = USEC_INFINITY;
 
         r = prioq_ensure_allocated(&e->pending, pending_prioq_compare);
@@ -495,7 +495,7 @@ static bool event_pid_changed(sd_event *e) {
         /* We don't support people creating an event loop and keeping
          * it around over a fork(). Let's complain. */
 
-        return e->original_pid != getpid();
+        return e->original_pid != getpid_cached();
 }
 
 static void source_io_unregister(sd_event_source *s) {
@@ -1601,7 +1601,7 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
         int r;
 
         assert_return(s, -EINVAL);
-        assert_return(m == SD_EVENT_OFF || m == SD_EVENT_ON || m == SD_EVENT_ONESHOT, -EINVAL);
+        assert_return(IN_SET(m, SD_EVENT_OFF, SD_EVENT_ON, SD_EVENT_ONESHOT), -EINVAL);
         assert_return(!event_pid_changed(s->event), -ECHILD);
 
         /* If we are dead anyway, we are fine with turning off
@@ -2054,7 +2054,7 @@ static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) {
 
         ss = read(fd, &x, sizeof(x));
         if (ss < 0) {
-                if (errno == EAGAIN || errno == EINTR)
+                if (IN_SET(errno, EAGAIN, EINTR))
                         return 0;
 
                 return -errno;
@@ -2143,10 +2143,7 @@ static int process_child(sd_event *e) {
                         return -errno;
 
                 if (s->child.siginfo.si_pid != 0) {
-                        bool zombie =
-                                s->child.siginfo.si_code == CLD_EXITED ||
-                                s->child.siginfo.si_code == CLD_KILLED ||
-                                s->child.siginfo.si_code == CLD_DUMPED;
+                        bool zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED);
 
                         if (!zombie && (s->child.options & WEXITED)) {
                                 /* If the child isn't dead then let's
@@ -2197,7 +2194,7 @@ static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) {
 
                 n = read(d->fd, &si, sizeof(si));
                 if (n < 0) {
-                        if (errno == EAGAIN || errno == EINTR)
+                        if (IN_SET(errno, EAGAIN, EINTR))
                                 return read_one;
 
                         return -errno;
@@ -2239,7 +2236,7 @@ static int source_dispatch(sd_event_source *s) {
          * the event. */
         saved_type = s->type;
 
-        if (s->type != SOURCE_DEFER && s->type != SOURCE_EXIT) {
+        if (!IN_SET(s->type, SOURCE_DEFER, SOURCE_EXIT)) {
                 r = source_set_pending(s, false);
                 if (r < 0)
                         return r;
@@ -2291,9 +2288,7 @@ static int source_dispatch(sd_event_source *s) {
         case SOURCE_CHILD: {
                 bool zombie;
 
-                zombie = s->child.siginfo.si_code == CLD_EXITED ||
-                         s->child.siginfo.si_code == CLD_KILLED ||
-                         s->child.siginfo.si_code == CLD_DUMPED;
+                zombie = IN_SET(s->child.siginfo.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED);
 
                 r = s->child.callback(s, &s->child.siginfo, s->userdata);
 
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index 57525ddb..19277021 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -78,7 +78,7 @@ bool id128_is_valid(const char *s) {
                 for (i = 0; i < l; i++) {
                         char c = s[i];
 
-                        if ((i == 8 || i == 13 || i == 18 || i == 23)) {
+                        if (IN_SET(i, 8, 13, 18, 23)) {
                                 if (c != '-')
                                         return false;
                         } else {
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index 9920bfdf..052110d5 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -70,7 +70,7 @@ _public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
 
                         if (i == 8)
                                 is_guid = true;
-                        else if (i == 13 || i == 18 || i == 23) {
+                        else if (IN_SET(i, 13, 18, 23)) {
                                 if (!is_guid)
                                         return -EINVAL;
                         } else
@@ -294,7 +294,7 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
 
         assert_return(ret, -EINVAL);
 
-        r = dev_urandom(&t, sizeof(t));
+        r = acquire_random_bytes(&t, sizeof t, true);
         if (r < 0)
                 return r;
 
diff --git a/src/systemd/src/shared/dns-domain.c b/src/systemd/src/shared/dns-domain.c
index 5ed27191..c313a033 100644
--- a/src/systemd/src/shared/dns-domain.c
+++ b/src/systemd/src/shared/dns-domain.c
@@ -19,9 +19,13 @@
 
 #include "nm-sd-adapt.h"
 
-#ifdef HAVE_LIBIDN
-#include <idna.h>
-#include <stringprep.h>
+#if 0 /* NM_IGNORED */
+#if HAVE_LIBIDN2
+#  include <idn2.h>
+#elif HAVE_LIBIDN
+#  include <idna.h>
+#  include <stringprep.h>
+#endif
 #endif
 
 #include <endian.h>
@@ -76,7 +80,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
                                 /* Ending NUL */
                                 return -EINVAL;
 
-                        else if (*n == '\\' || *n == '.') {
+                        else if (IN_SET(*n, '\\', '.')) {
                                 /* Escaped backslash or dot */
 
                                 if (d)
@@ -144,6 +148,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) {
         return r;
 }
 
+#if 0 /* NM_IGNORED */
 /* @label_terminal: terminal character of a label, updated to point to the terminal character of
  *                  the previous label (always skipping one dot) or to NULL if there are no more
  *                  labels. */
@@ -164,7 +169,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
         }
 
         terminal = *label_terminal;
-        assert(*terminal == '.' || *terminal == 0);
+        assert(IN_SET(*terminal, 0, '.'));
 
         /* Skip current terminal character (and accept domain names ending it ".") */
         if (*terminal == 0)
@@ -209,6 +214,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha
 
         return r;
 }
+#endif /* NM_IGNORED */
 
 int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
         char *q;
@@ -228,7 +234,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
         q = dest;
         while (l > 0) {
 
-                if (*p == '.' || *p == '\\') {
+                if (IN_SET(*p, '.', '\\')) {
 
                         /* Dot or backslash */
 
@@ -240,8 +246,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
 
                         sz -= 2;
 
-                } else if (*p == '_' ||
-                           *p == '-' ||
+                } else if (IN_SET(*p, '_', '-') ||
                            (*p >= '0' && *p <= '9') ||
                            (*p >= 'a' && *p <= 'z') ||
                            (*p >= 'A' && *p <= 'Z')) {
@@ -277,6 +282,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
         return (int) (q - dest);
 }
 
+#if 0 /* NM_IGNORED */
 int dns_label_escape_new(const char *p, size_t l, char **ret) {
         _cleanup_free_ char *s = NULL;
         int r;
@@ -301,8 +307,8 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
         return r;
 }
 
+#if HAVE_LIBIDN
 int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
-#ifdef HAVE_LIBIDN
         _cleanup_free_ uint32_t *input = NULL;
         size_t input_size, l;
         const char *p;
@@ -350,13 +356,9 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
                 decoded[l] = 0;
 
         return (int) l;
-#else
-        return 0;
-#endif
 }
 
 int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
-#ifdef HAVE_LIBIDN
         size_t input_size, output_size;
         _cleanup_free_ uint32_t *input = NULL;
         _cleanup_free_ char *result = NULL;
@@ -401,10 +403,9 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded,
                 decoded[w] = 0;
 
         return w;
-#else
-        return 0;
-#endif
 }
+#endif
+#endif /* NM_IGNORED */
 
 int dns_name_concat(const char *a, const char *b, char **_ret) {
         _cleanup_free_ char *ret = NULL;
@@ -1279,6 +1280,49 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
 }
 
 int dns_name_apply_idna(const char *name, char **ret) {
+        /* Return negative on error, 0 if not implemented, positive on success. */
+
+#if HAVE_LIBIDN2
+        int r;
+        _cleanup_free_ char *t = NULL;
+
+        assert(name);
+        assert(ret);
+
+        r = idn2_lookup_u8((uint8_t*) name, (uint8_t**) &t,
+                           IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL);
+        log_debug("idn2_lookup_u8: %s → %s", name, t);
+        if (r == IDN2_OK) {
+                if (!startswith(name, "xn--")) {
+                        _cleanup_free_ char *s = NULL;
+
+                        r = idn2_to_unicode_8z8z(t, &s, 0);
+                        if (r != IDN2_OK) {
+                                log_debug("idn2_to_unicode_8z8z(\"%s\") failed: %d/%s",
+                                          t, r, idn2_strerror(r));
+                                return 0;
+                        }
+
+                        if (!streq_ptr(name, s)) {
+                                log_debug("idn2 roundtrip failed: \"%s\" → \"%s\" → \"%s\", ignoring.",
+                                          name, t, s);
+                                return 0;
+                        }
+                }
+
+                *ret = t;
+                t = NULL;
+                return 1; /* *ret has been written */
+        }
+
+        log_debug("idn2_lookup_u8(\"%s\") failed: %d/%s", name, r, idn2_strerror(r));
+        if (r == IDN2_2HYPHEN)
+                /* The name has two hypens — forbidden by IDNA2008 in some cases */
+                return 0;
+        if (IN_SET(r, IDN2_TOO_BIG_DOMAIN, IDN2_TOO_BIG_LABEL))
+                return -ENOSPC;
+        return -EINVAL;
+#elif HAVE_LIBIDN
         _cleanup_free_ char *buf = NULL;
         size_t n = 0, allocated = 0;
         bool first = true;
@@ -1314,7 +1358,7 @@ int dns_name_apply_idna(const char *name, char **ret) {
                 else
                         buf[n++] = '.';
 
-                n +=r;
+                n += r;
         }
 
         if (n > DNS_HOSTNAME_MAX)
@@ -1327,7 +1371,10 @@ int dns_name_apply_idna(const char *name, char **ret) {
         *ret = buf;
         buf = NULL;
 
-        return (int) n;
+        return 1;
+#else
+        return 0;
+#endif
 }
 
 int dns_name_is_valid_or_address(const char *name) {
diff --git a/src/systemd/src/shared/dns-domain.h b/src/systemd/src/shared/dns-domain.h
index 03f16036..d1a99be7 100644
--- a/src/systemd/src/shared/dns-domain.h
+++ b/src/systemd/src/shared/dns-domain.h
@@ -51,8 +51,12 @@ static inline int dns_name_parent(const char **name) {
         return dns_label_unescape(name, NULL, DNS_LABEL_MAX);
 }
 
+#if 0 /* NM_IGNORED */
+#if HAVE_LIBIDN
 int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
 int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max);
+#endif
+#endif /* NM_IGNORED */
 
 int dns_name_concat(const char *a, const char *b, char **ret);
 
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index ffe7f836..5e46d8d0 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -58,9 +58,17 @@ enum {
         SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24,
         SD_DHCP_OPTION_INTERFACE_MTU               = 26,
         SD_DHCP_OPTION_BROADCAST                   = 28,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_ROUTER_DISCOVER             = 31,
         SD_DHCP_OPTION_STATIC_ROUTE                = 33,
         SD_DHCP_OPTION_NTP_SERVER                  = 42,
         SD_DHCP_OPTION_VENDOR_SPECIFIC             = 43,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_NETBIOS_NAMESERVER          = 44,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_NETBIOS_NODETYPE            = 46,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_NETBIOS_SCOPE               = 47,
         SD_DHCP_OPTION_REQUESTED_IP_ADDRESS        = 50,
         SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME       = 51,
         SD_DHCP_OPTION_OVERLOAD                    = 52,
@@ -76,8 +84,13 @@ enum {
         SD_DHCP_OPTION_FQDN                        = 81,
         SD_DHCP_OPTION_NEW_POSIX_TIMEZONE          = 100,
         SD_DHCP_OPTION_NEW_TZDB_TIMEZONE           = 101,
+        SD_DHCP_OPTION_DOMAIN_SEARCH_LIST          = 119,
         SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE      = 121,
         SD_DHCP_OPTION_PRIVATE_BASE                = 224,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249,
+       /* Windows 10 option to send when Anonymize=true */
+        SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252,
         SD_DHCP_OPTION_PRIVATE_LAST                = 254,
         SD_DHCP_OPTION_END                         = 255,
 };
@@ -145,7 +158,9 @@ int sd_dhcp_client_start(sd_dhcp_client *client);
 sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
 sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);
 
-int sd_dhcp_client_new(sd_dhcp_client **ret);
+/* NOTE: anonymize parameter is used to initialize PRL memory with different
+ * options when using RFC7844 Anonymity Profiles */
+int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize);
 
 int sd_dhcp_client_attach_event(
                 sd_dhcp_client *client,
diff --git a/src/systemd/src/systemd/sd-dhcp-lease.h b/src/systemd/src/systemd/sd-dhcp-lease.h
index 2f565ca8..7ab99ccc 100644
--- a/src/systemd/src/systemd/sd-dhcp-lease.h
+++ b/src/systemd/src/systemd/sd-dhcp-lease.h
@@ -49,6 +49,7 @@ int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr);
 int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu);
 int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname);
+int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains);
 int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname);
 int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path);
 int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes);
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index edb5c1aa..80e17d01 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -26,6 +26,7 @@
 #include "nm-test-device.h"
 #include "platform/nm-fake-platform.h"
 #include "nm-bus-manager.h"
+#include "nm-connectivity.h"
 
 #include "nm-test-utils-core.h"
 
@@ -318,6 +319,40 @@ test_config_global_dns (void)
 }
 
 static void
+test_config_connectivity_check (void)
+{
+	const char *CONFIG_INTERN = BUILDDIR"/test-connectivity-check-intern.conf";
+	NMConfig *config;
+	NMConnectivity *connectivity;
+
+	g_assert (g_file_set_contents (CONFIG_INTERN, "", 0, NULL));
+	config = setup_config (NULL, SRCDIR "/NetworkManager.conf", CONFIG_INTERN, NULL,
+	                       "/no/such/dir", "", NULL);
+	connectivity = nm_connectivity_get();
+
+	g_assert (nm_connectivity_check_enabled (connectivity));
+
+	/* disable connectivity checking */
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal *");
+	nm_config_set_connectivity_check_enabled (config, FALSE);
+	g_test_assert_expected_messages ();
+
+	g_assert (!nm_connectivity_check_enabled (connectivity));
+
+	/* re-enable connectivity checking */
+	g_test_expect_message ("NetworkManager", G_LOG_LEVEL_INFO, "*config: signal *");
+	nm_config_set_connectivity_check_enabled (config, TRUE);
+	g_test_assert_expected_messages ();
+
+	g_assert (nm_connectivity_check_enabled (connectivity));
+
+	g_object_unref (connectivity);
+	g_object_unref (config);
+
+	g_assert (remove (CONFIG_INTERN) == 0);
+}
+
+static void
 test_config_no_auto_default (void)
 {
 	NMConfig *config;
@@ -1018,6 +1053,7 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/config/set-values", test_config_set_values);
 	g_test_add_func ("/config/global-dns", test_config_global_dns);
+	g_test_add_func ("/config/connectivity-check", test_config_connectivity_check);
 
 	g_test_add_func ("/config/signal", test_config_signal);
 
diff --git a/src/tests/test-general-with-expect.c b/src/tests/test-general-with-expect.c
index 492e1b1f..c5d26163 100644
--- a/src/tests/test-general-with-expect.c
+++ b/src/tests/test-general-with-expect.c
@@ -29,7 +29,6 @@
 #include <fcntl.h>
 
 #include "NetworkManagerUtils.h"
-#include "nm-multi-index.h"
 
 #include "nm-test-utils-core.h"
 
@@ -499,362 +498,6 @@ test_nm_ethernet_address_is_valid (void)
 
 /*****************************************************************************/
 
-typedef struct {
-	union {
-		NMMultiIndexId id_base;
-		guint bucket;
-	};
-} NMMultiIndexIdTest;
-
-typedef struct {
-	guint64 buckets;
-	gpointer ptr_value;
-} NMMultiIndexTestValue;
-
-static gboolean
-_mi_value_bucket_has (const NMMultiIndexTestValue *value, guint bucket)
-{
-	g_assert (value);
-	g_assert (bucket < 64);
-
-	return (value->buckets & (((guint64) 0x01) << bucket)) != 0;
-}
-
-static gboolean
-_mi_value_bucket_set (NMMultiIndexTestValue *value, guint bucket)
-{
-	g_assert (value);
-	g_assert (bucket < 64);
-
-	if (_mi_value_bucket_has (value, bucket))
-		return FALSE;
-
-	value->buckets |= (((guint64) 0x01) << bucket);
-	return TRUE;
-}
-
-static gboolean
-_mi_value_bucket_unset (NMMultiIndexTestValue *value, guint bucket)
-{
-	g_assert (value);
-	g_assert (bucket < 64);
-
-	if (!_mi_value_bucket_has (value, bucket))
-		return FALSE;
-
-	value->buckets &= ~(((guint64) 0x01) << bucket);
-	return TRUE;
-}
-
-static guint
-_mi_idx_hash (const NMMultiIndexIdTest *id)
-{
-	g_assert (id && id->bucket < 64);
-	return id->bucket;
-}
-
-static gboolean
-_mi_idx_equal (const NMMultiIndexIdTest *a, const NMMultiIndexIdTest *b)
-{
-	g_assert (a && a->bucket < 64);
-	g_assert (b && b->bucket < 64);
-
-	return a->bucket == b->bucket;
-}
-
-static NMMultiIndexIdTest *
-_mi_idx_clone (const NMMultiIndexIdTest *id)
-{
-	NMMultiIndexIdTest *n;
-
-	g_assert (id && id->bucket < 64);
-
-	n = g_new0 (NMMultiIndexIdTest, 1);
-	n->bucket = id->bucket;
-	return n;
-}
-
-static void
-_mi_idx_destroy (NMMultiIndexIdTest *id)
-{
-	g_assert (id && id->bucket < 64);
-	g_free (id);
-}
-
-static NMMultiIndexTestValue *
-_mi_create_array (guint num_values)
-{
-	NMMultiIndexTestValue *array = g_new0 (NMMultiIndexTestValue, num_values);
-	guint i;
-
-	g_assert (num_values > 0);
-
-	for (i = 0; i < num_values; i++) {
-		array[i].buckets = 0;
-		array[i].ptr_value = GUINT_TO_POINTER (i + 1);
-	}
-	return array;
-}
-
-typedef struct {
-	guint num_values;
-	guint num_buckets;
-	NMMultiIndexTestValue *array;
-	int test_idx;
-} NMMultiIndexAssertData;
-
-static gboolean
-_mi_assert_index_equals_array_cb (const NMMultiIndexIdTest *id, void *const* values, guint len, NMMultiIndexAssertData *data)
-{
-	guint i;
-	gboolean has_test_idx = FALSE;
-
-	g_assert (id && id->bucket < 64);
-	g_assert (data);
-	g_assert (values);
-	g_assert (len > 0);
-	g_assert (values[len] == NULL);
-	g_assert (data->test_idx >= -1 || data->test_idx < data->num_buckets);
-
-	g_assert (id->bucket < data->num_buckets);
-
-	for (i = 0; i < data->num_values; i++)
-		g_assert (!_mi_value_bucket_has (&data->array[i], id->bucket));
-
-	for (i = 0; i < len; i++) {
-		guint vi = GPOINTER_TO_UINT (values[i]);
-
-		g_assert (vi >= 1);
-		g_assert (vi <= data->num_values);
-		vi--;
-		if (data->test_idx == vi)
-			has_test_idx = TRUE;
-		g_assert (data->array[vi].ptr_value == values[i]);
-		if (!_mi_value_bucket_set (&data->array[vi], id->bucket))
-			g_assert_not_reached ();
-	}
-	g_assert ((data->test_idx == -1 && !has_test_idx) || has_test_idx);
-	return TRUE;
-}
-
-static void
-_mi_assert_index_equals_array (guint num_values, guint num_buckets, int test_idx, const NMMultiIndexTestValue *array, const NMMultiIndex *index)
-{
-	NMMultiIndexAssertData data = {
-		.num_values = num_values,
-		.num_buckets = num_buckets,
-		.test_idx = test_idx,
-	};
-	NMMultiIndexIter iter;
-	const NMMultiIndexIdTest *id;
-	void *const* values;
-	guint len;
-	NMMultiIndexTestValue *v;
-
-	data.array = _mi_create_array (num_values);
-	v = test_idx >= 0 ? data.array[test_idx].ptr_value : NULL;
-	nm_multi_index_foreach (index, v, (NMMultiIndexFuncForeach) _mi_assert_index_equals_array_cb, &data);
-	if (test_idx >= 0)
-		g_assert (memcmp (&data.array[test_idx], &array[test_idx], sizeof (NMMultiIndexTestValue)) == 0);
-	else
-		g_assert (memcmp (data.array, array, sizeof (NMMultiIndexTestValue) * num_values) == 0);
-	g_free (data.array);
-
-
-	data.array = _mi_create_array (num_values);
-	v = test_idx >= 0 ? data.array[test_idx].ptr_value : NULL;
-	nm_multi_index_iter_init (&iter, index, v);
-	while (nm_multi_index_iter_next (&iter, (gpointer) &id, &values, &len))
-		_mi_assert_index_equals_array_cb (id, values, len, &data);
-	if (test_idx >= 0)
-		g_assert (memcmp (&data.array[test_idx], &array[test_idx], sizeof (NMMultiIndexTestValue)) == 0);
-	else
-		g_assert (memcmp (data.array, array, sizeof (NMMultiIndexTestValue) * num_values) == 0);
-	g_free (data.array);
-}
-
-typedef enum {
-	MI_OP_ADD,
-	MI_OP_REMOVE,
-	MI_OP_MOVE,
-} NMMultiIndexOperation;
-
-static void
-_mi_rebucket (GRand *rand, guint num_values, guint num_buckets, NMMultiIndexOperation op, guint bucket, guint bucket_old, guint array_idx, NMMultiIndexTestValue *array, NMMultiIndex *index)
-{
-	NMMultiIndexTestValue *v;
-	NMMultiIndexIdTest id, id_old;
-	const NMMultiIndexIdTest *id_reverse;
-	guint64 buckets_old;
-	guint i;
-	gboolean had_bucket, had_bucket_old;
-
-	g_assert (array_idx < num_values);
-	g_assert (bucket < (int) num_buckets);
-
-	v = &array[array_idx];
-
-	buckets_old = v->buckets;
-	if (op == MI_OP_MOVE)
-		had_bucket_old = _mi_value_bucket_has (v, bucket_old);
-	else
-		had_bucket_old = FALSE;
-	had_bucket = _mi_value_bucket_has (v, bucket);
-
-	switch (op) {
-
-	case MI_OP_ADD:
-		_mi_value_bucket_set (v, bucket);
-		id.bucket = bucket;
-		if (nm_multi_index_add (index, &id.id_base, v->ptr_value))
-			g_assert (!had_bucket);
-		else
-			g_assert (had_bucket);
-		break;
-
-	case MI_OP_REMOVE:
-		_mi_value_bucket_unset (v, bucket);
-		id.bucket = bucket;
-		if (nm_multi_index_remove (index, &id.id_base, v->ptr_value))
-			g_assert (had_bucket);
-		else
-			g_assert (!had_bucket);
-		break;
-
-	case MI_OP_MOVE:
-
-		_mi_value_bucket_unset (v, bucket_old);
-		_mi_value_bucket_set (v, bucket);
-
-		id.bucket = bucket;
-		id_old.bucket = bucket_old;
-
-		if (nm_multi_index_move (index, &id_old.id_base, &id.id_base, v->ptr_value)) {
-			if (bucket == bucket_old)
-				g_assert (had_bucket_old && had_bucket);
-			else
-				g_assert (had_bucket_old && !had_bucket);
-		} else {
-			if (bucket == bucket_old)
-				g_assert (!had_bucket_old && !had_bucket);
-			else
-				g_assert (!had_bucket_old || had_bucket);
-		}
-		break;
-
-	default:
-		g_assert_not_reached ();
-	}
-
-#if 0
-	g_print (">>> rebucket: idx=%3u, op=%3s, bucket=%3i%c -> %3i%c, buckets=%08llx -> %08llx %s\n", array_idx,
-	         op == MI_OP_ADD ? "ADD" : (op == MI_OP_REMOVE ? "REM" : "MOV"),
-	         bucket_old, had_bucket_old ? '*' : ' ',
-	         bucket, had_bucket ? '*' : ' ',
-	         (unsigned long long) buckets_old, (unsigned long long) v->buckets,
-	         buckets_old != v->buckets ? "(changed)" : "(unchanged)");
-#endif
-
-	id_reverse = (const NMMultiIndexIdTest *) nm_multi_index_lookup_first_by_value (index, v->ptr_value);
-	if (id_reverse)
-		g_assert (_mi_value_bucket_has (v, id_reverse->bucket));
-	else
-		g_assert (v->buckets == 0);
-
-	for (i = 0; i < 64; i++) {
-		id.bucket = i;
-		if (nm_multi_index_contains (index, &id.id_base, v->ptr_value))
-			g_assert (_mi_value_bucket_has (v, i));
-		else
-			g_assert (!_mi_value_bucket_has (v, i));
-	}
-
-	_mi_assert_index_equals_array (num_values, num_buckets, -1, array, index);
-	_mi_assert_index_equals_array (num_values, num_buckets, array_idx, array, index);
-	_mi_assert_index_equals_array (num_values, num_buckets, g_rand_int_range (rand, 0, num_values), array, index);
-}
-
-static void
-_mi_test_run (guint num_values, guint num_buckets)
-{
-	NMMultiIndex *index = nm_multi_index_new ((NMMultiIndexFuncHash) _mi_idx_hash,
-	                                          (NMMultiIndexFuncEqual) _mi_idx_equal,
-	                                          (NMMultiIndexFuncClone) _mi_idx_clone,
-	                                          (NMMultiIndexFuncDestroy) _mi_idx_destroy);
-	gs_free NMMultiIndexTestValue *array = _mi_create_array (num_values);
-	GRand *rand = nmtst_get_rand ();
-	guint i, i_rd, i_idx, i_bucket;
-	guint num_buckets_all = num_values * num_buckets;
-
-	g_assert (array[0].ptr_value == GUINT_TO_POINTER (1));
-
-	_mi_assert_index_equals_array (num_values, num_buckets, -1, array, index);
-
-	_mi_rebucket (rand, num_values, num_buckets, MI_OP_ADD, 0, 0, 0, array, index);
-	_mi_rebucket (rand, num_values, num_buckets, MI_OP_REMOVE, 0, 0, 0, array, index);
-
-	if (num_buckets >= 3) {
-		_mi_rebucket (rand, num_values, num_buckets, MI_OP_ADD, 0, 0, 0, array, index);
-		_mi_rebucket (rand, num_values, num_buckets, MI_OP_MOVE, 2, 0, 0, array, index);
-		_mi_rebucket (rand, num_values, num_buckets, MI_OP_REMOVE, 2, 0, 0, array, index);
-	}
-
-	g_assert (nm_multi_index_get_num_groups (index) == 0);
-
-	/* randomly change the bucket of entries. */
-	for (i = 0; i < 5 * num_values; i++) {
-		guint array_idx = g_rand_int_range (rand, 0, num_values);
-		guint bucket = g_rand_int_range (rand, 0, num_buckets);
-		NMMultiIndexOperation op = g_rand_int_range (rand, 0, MI_OP_MOVE + 1);
-		guint bucket_old = 0;
-
-		if (op == MI_OP_MOVE) {
-			if ((g_rand_int (rand) % 2) && array[array_idx].buckets != 0) {
-				guint64 b;
-
-				/* choose the highest (existing) bucket. */
-				bucket_old = 0;
-				for (b = array[array_idx].buckets; b; b >>= 1)
-					bucket_old++;
-			} else {
-				/* choose a random bucket (even if the item is currently not in that bucket). */
-				bucket_old = g_rand_int_range (rand, 0, num_buckets);
-			}
-		}
-
-		_mi_rebucket (rand, num_values, num_buckets, op, bucket, bucket_old, array_idx, array, index);
-	}
-
-	/* remove all elements from all buckets */
-	i_rd = g_rand_int (rand);
-	for (i = 0; i < num_buckets_all; i++) {
-		i_rd = (i_rd + 101) % num_buckets_all;
-		i_idx = i_rd / num_buckets;
-		i_bucket = i_rd % num_buckets;
-
-		if (_mi_value_bucket_has (&array[i_idx], i_bucket))
-			_mi_rebucket (rand, num_values, num_buckets, MI_OP_REMOVE, i_bucket, 0, i_idx, array, index);
-	}
-
-	g_assert (nm_multi_index_get_num_groups (index) == 0);
-	nm_multi_index_free (index);
-}
-
-static void
-test_nm_multi_index (void)
-{
-	guint i, j;
-
-	for (i = 1; i < 7; i++) {
-		for (j = 1; j < 6; j++)
-			_mi_test_run (i, j);
-	}
-	_mi_test_run (50, 3);
-	_mi_test_run (50, 18);
-}
-
-/*****************************************************************************/
-
 static void
 test_nm_utils_new_vlan_name (void)
 {
@@ -909,7 +552,6 @@ main (int argc, char **argv)
 	g_test_add_func ("/general/nm_utils_kill_child", test_nm_utils_kill_child);
 	g_test_add_func ("/general/nm_utils_array_remove_at_indexes", test_nm_utils_array_remove_at_indexes);
 	g_test_add_func ("/general/nm_ethernet_address_is_valid", test_nm_ethernet_address_is_valid);
-	g_test_add_func ("/general/nm_multi_index", test_nm_multi_index);
 	g_test_add_func ("/general/nm_utils_new_vlan_name", test_nm_utils_new_vlan_name);
 
 	return g_test_run ();
diff --git a/src/tests/test-general.c b/src/tests/test-general.c
index 37bbc5ca..d36a26ef 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-general.c
@@ -272,6 +272,32 @@ test_nm_utils_log_connection_diff (void)
 
 /*****************************************************************************/
 
+static void
+do_test_sysctl_ip_conf (int addr_family,
+                        const char *iface,
+                        const char *property)
+{
+	char path[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
+	const char *pp;
+
+	pp = nm_utils_sysctl_ip_conf_path (addr_family, path, iface, property);
+	g_assert (pp == path);
+	g_assert (path[0] == '/');
+
+	g_assert (nm_utils_sysctl_ip_conf_is_path (addr_family, path, iface, property));
+	g_assert (nm_utils_sysctl_ip_conf_is_path (addr_family, path, NULL, property));
+}
+
+static void
+test_nm_utils_sysctl_ip_conf_path (void)
+{
+	do_test_sysctl_ip_conf (AF_INET6, "a", "mtu");
+	do_test_sysctl_ip_conf (AF_INET6, "eth0", "mtu");
+	do_test_sysctl_ip_conf (AF_INET6, "e23456789012345", "mtu");
+}
+
+/*****************************************************************************/
+
 static NMConnection *
 _match_connection_new (void)
 {
@@ -1716,6 +1742,8 @@ main (int argc, char **argv)
 	g_test_add_func ("/general/nm_utils_ip6_address_same_prefix", test_nm_utils_ip6_address_same_prefix);
 	g_test_add_func ("/general/nm_utils_log_connection_diff", test_nm_utils_log_connection_diff);
 
+	g_test_add_func ("/general/nm_utils_sysctl_ip_conf_path", test_nm_utils_sysctl_ip_conf_path);
+
 	g_test_add_func ("/general/exp10", test_nm_utils_exp10);
 
 	g_test_add_func ("/general/connection-match/basic", test_connection_match_basic);
diff --git a/src/tests/test-ip4-config.c b/src/tests/test-ip4-config.c
index bff715e0..649b443f 100644
--- a/src/tests/test-ip4-config.c
+++ b/src/tests/test-ip4-config.c
@@ -36,18 +36,27 @@ build_test_config (void)
 	NMPlatformIP4Route route;
 
 	/* Build up the config to subtract */
-	config = nm_ip4_config_new (1);
+	config = nmtst_ip4_config_new (1);
 
 	addr = *nmtst_platform_ip4_address ("192.168.1.10", "1.2.3.4", 24);
 	nm_ip4_config_add_address (config, &addr);
 
 	route = *nmtst_platform_ip4_route ("10.0.0.0", 8, "192.168.1.1");
-	nm_ip4_config_add_route (config, &route);
+	nm_ip4_config_add_route (config, &route, NULL);
 
 	route = *nmtst_platform_ip4_route ("172.16.0.0", 16, "192.168.1.1");
-	nm_ip4_config_add_route (config, &route);
+	nm_ip4_config_add_route (config, &route, NULL);
 
-	nm_ip4_config_set_gateway (config, nmtst_inet4_from_string ("192.168.1.1"));
+	{
+		const NMPlatformIP4Route r = {
+			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
+			.gateway = nmtst_inet4_from_string ("192.168.1.1"),
+			.table_coerced = 0,
+			.metric = 100,
+		};
+
+		nm_ip4_config_add_route (config, &r, NULL);
+	}
 
 	nm_ip4_config_add_nameserver (config, nmtst_inet4_from_string ("4.2.2.1"));
 	nm_ip4_config_add_nameserver (config, nmtst_inet4_from_string ("4.2.2.2"));
@@ -75,7 +84,7 @@ test_subtract (void)
 	const NMPlatformIP4Route *test_route;
 	const char *expected_addr = "192.168.1.12";
 	guint32 expected_addr_plen = 24;
-	const char *expected_route_dest = "8.7.6.5";
+	const char *expected_route_dest = "8.0.0.0";
 	guint32 expected_route_plen = 8;
 	const char *expected_route_next_hop = "192.168.1.1";
 	guint32 expected_ns1 = nmtst_inet4_from_string ("8.8.8.8");
@@ -84,7 +93,6 @@ test_subtract (void)
 	const char *expected_search = "somewhere.com";
 	guint32 expected_nis = nmtst_inet4_from_string ("1.2.3.13");
 	guint32 expected_wins = nmtst_inet4_from_string ("2.3.4.5");
-	guint32 expected_mss = 1400;
 	guint32 expected_mtu = 1492;
 
 	src = build_test_config ();
@@ -95,7 +103,7 @@ test_subtract (void)
 	nm_ip4_config_add_address (dst, &addr);
 
 	route = *nmtst_platform_ip4_route (expected_route_dest, expected_route_plen, expected_route_next_hop);
-	nm_ip4_config_add_route (dst, &route);
+	nm_ip4_config_add_route (dst, &route, NULL);
 
 	nm_ip4_config_add_nameserver (dst, expected_ns1);
 	nm_ip4_config_add_nameserver (dst, expected_ns2);
@@ -105,23 +113,23 @@ test_subtract (void)
 	nm_ip4_config_add_nis_server (dst, expected_nis);
 	nm_ip4_config_add_wins (dst, expected_wins);
 
-	nm_ip4_config_set_mss (dst, expected_mss);
 	nm_ip4_config_set_mtu (dst, expected_mtu, NM_IP_CONFIG_SOURCE_UNKNOWN);
 
-	nm_ip4_config_subtract (dst, src);
+	nm_ip4_config_subtract (dst, src, 0);
 
 	/* ensure what's left is what we expect */
 	g_assert_cmpuint (nm_ip4_config_get_num_addresses (dst), ==, 1);
-	test_addr = nm_ip4_config_get_address (dst, 0);
+	test_addr = _nmtst_ip4_config_get_address (dst, 0);
 	g_assert (test_addr != NULL);
 	g_assert_cmpuint (test_addr->address, ==, nmtst_inet4_from_string (expected_addr));
 	g_assert_cmpuint (test_addr->peer_address, ==, test_addr->address);
 	g_assert_cmpuint (test_addr->plen, ==, expected_addr_plen);
 
-	g_assert_cmpuint (nm_ip4_config_get_gateway (dst), ==, 0);
+	g_assert (!nm_ip4_config_best_default_route_get (dst));
+	g_assert_cmpuint (nmtst_ip4_config_get_gateway (dst), ==, 0);
 
 	g_assert_cmpuint (nm_ip4_config_get_num_routes (dst), ==, 1);
-	test_route = nm_ip4_config_get_route (dst, 0);
+	test_route = _nmtst_ip4_config_get_route (dst, 0);
 	g_assert (test_route != NULL);
 	g_assert_cmpuint (test_route->network, ==, nmtst_inet4_from_string (expected_route_dest));
 	g_assert_cmpuint (test_route->plen, ==, expected_route_plen);
@@ -142,7 +150,6 @@ test_subtract (void)
 	g_assert_cmpuint (nm_ip4_config_get_num_wins (dst), ==, 1);
 	g_assert_cmpuint (nm_ip4_config_get_wins (dst, 0), ==, expected_wins);
 
-	g_assert_cmpuint (nm_ip4_config_get_mss (dst), ==, expected_mss);
 	g_assert_cmpuint (nm_ip4_config_get_mtu (dst), ==, expected_mtu);
 
 	g_object_unref (src);
@@ -156,8 +163,8 @@ test_compare_with_source (void)
 	NMPlatformIP4Address addr;
 	NMPlatformIP4Route route;
 
-	a = nm_ip4_config_new (1);
-	b = nm_ip4_config_new (2);
+	a = nmtst_ip4_config_new (1);
+	b = nmtst_ip4_config_new (2);
 
 	/* Address */
 	addr = *nmtst_platform_ip4_address ("1.2.3.4", NULL, 24);
@@ -170,10 +177,10 @@ test_compare_with_source (void)
 	/* Route */
 	route = *nmtst_platform_ip4_route ("10.0.0.0", 8, "192.168.1.1");
 	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	nm_ip4_config_add_route (a, &route);
+	nm_ip4_config_add_route (a, &route, NULL);
 
 	route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-	nm_ip4_config_add_route (b, &route);
+	nm_ip4_config_add_route (b, &route, NULL);
 
 	/* Assert that the configs are basically the same, eg that the source is ignored */
 	g_assert (nm_ip4_config_equal (a, b));
@@ -189,34 +196,34 @@ test_add_address_with_source (void)
 	NMPlatformIP4Address addr;
 	const NMPlatformIP4Address *test_addr;
 
-	a = nm_ip4_config_new (1);
+	a = nmtst_ip4_config_new (1);
 
 	/* Test that a higher priority source is not overwritten */
 	addr = *nmtst_platform_ip4_address ("1.2.3.4", NULL, 24);
 	addr.addr_source = NM_IP_CONFIG_SOURCE_USER;
 	nm_ip4_config_add_address (a, &addr);
 
-	test_addr = nm_ip4_config_get_address (a, 0);
+	test_addr = _nmtst_ip4_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	addr.addr_source = NM_IP_CONFIG_SOURCE_VPN;
 	nm_ip4_config_add_address (a, &addr);
 
-	test_addr = nm_ip4_config_get_address (a, 0);
+	test_addr = _nmtst_ip4_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	/* Test that a lower priority address source is overwritten */
-	nm_ip4_config_del_address (a, 0);
+	_nmtst_ip4_config_del_address (a, 0);
 	addr.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
 	nm_ip4_config_add_address (a, &addr);
 
-	test_addr = nm_ip4_config_get_address (a, 0);
+	test_addr = _nmtst_ip4_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_KERNEL);
 
 	addr.addr_source = NM_IP_CONFIG_SOURCE_USER;
 	nm_ip4_config_add_address (a, &addr);
 
-	test_addr = nm_ip4_config_get_address (a, 0);
+	test_addr = _nmtst_ip4_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	g_object_unref (a);
@@ -225,50 +232,52 @@ test_add_address_with_source (void)
 static void
 test_add_route_with_source (void)
 {
-	NMIP4Config *a;
+	gs_unref_object NMIP4Config *a = NULL;
 	NMPlatformIP4Route route;
 	const NMPlatformIP4Route *test_route;
 
-	a = nm_ip4_config_new (1);
+	a = nmtst_ip4_config_new (1);
 
 	/* Test that a higher priority source is not overwritten */
-	route = *nmtst_platform_ip4_route ("1.2.3.4", 24, "1.2.3.1");
+	route = *nmtst_platform_ip4_route ("1.2.3.0", 24, "1.2.3.1");
 	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	nm_ip4_config_add_route (a, &route);
+	nm_ip4_config_add_route (a, &route, NULL);
 
-	test_route = nm_ip4_config_get_route (a, 0);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip4_config_get_route (a, 0);
 	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-	nm_ip4_config_add_route (a, &route);
+	nm_ip4_config_add_route (a, &route, NULL);
 
-	test_route = nm_ip4_config_get_route (a, 0);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip4_config_get_route (a, 0);
 	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
+	_nmtst_ip4_config_del_route (a, 0);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (a), ==, 0);
+
 	/* Test that a lower priority address source is overwritten */
-	nm_ip4_config_del_route (a, 0);
-	route.rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
-	nm_ip4_config_add_route (a, &route);
+	route.rt_source = NM_IP_CONFIG_SOURCE_RTPROT_KERNEL;
+	nm_ip4_config_add_route (a, &route, NULL);
 
-	test_route = nm_ip4_config_get_route (a, 0);
-	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_KERNEL);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip4_config_get_route (a, 0);
+	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_RTPROT_KERNEL);
 
-	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	nm_ip4_config_add_route (a, &route);
-
-	test_route = nm_ip4_config_get_route (a, 0);
-	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER);
+	route.rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
+	nm_ip4_config_add_route (a, &route, NULL);
 
-	g_object_unref (a);
+	g_assert_cmpint (nm_ip4_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip4_config_get_route (a, 0);
+	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_KERNEL);
 }
 
 static void
-test_merge_subtract_mss_mtu (void)
+test_merge_subtract_mtu (void)
 {
 	NMIP4Config *cfg1, *cfg2, *cfg3;
-	guint32 expected_mss2 = 1400;
 	guint32 expected_mtu2 = 1492;
-	guint32 expected_mss3 = 555;
 	guint32 expected_mtu3 = 666;
 
 	cfg1 = build_test_config ();
@@ -276,24 +285,19 @@ test_merge_subtract_mss_mtu (void)
 	cfg3 = build_test_config ();
 
 	/* add MSS, MTU to configs to test them */
-	nm_ip4_config_set_mss (cfg2, expected_mss2);
 	nm_ip4_config_set_mtu (cfg2, expected_mtu2, NM_IP_CONFIG_SOURCE_UNKNOWN);
-	nm_ip4_config_set_mss (cfg3, expected_mss3);
 	nm_ip4_config_set_mtu (cfg3, expected_mtu3, NM_IP_CONFIG_SOURCE_UNKNOWN);
 
-	nm_ip4_config_merge (cfg1, cfg2, NM_IP_CONFIG_MERGE_DEFAULT);
+	nm_ip4_config_merge (cfg1, cfg2, NM_IP_CONFIG_MERGE_DEFAULT, 0);
 	/* ensure MSS and MTU are in cfg1 */
-	g_assert_cmpuint (nm_ip4_config_get_mss (cfg1), ==, expected_mss2);
 	g_assert_cmpuint (nm_ip4_config_get_mtu (cfg1), ==, expected_mtu2);
 
-	nm_ip4_config_merge (cfg1, cfg3, NM_IP_CONFIG_MERGE_DEFAULT);
-	/* ensure again the MSS and MTU in cfg1 got overriden */
-	g_assert_cmpuint (nm_ip4_config_get_mss (cfg1), ==, expected_mss3);
+	nm_ip4_config_merge (cfg1, cfg3, NM_IP_CONFIG_MERGE_DEFAULT, 0);
+	/* ensure again the MSS and MTU in cfg1 got overridden */
 	g_assert_cmpuint (nm_ip4_config_get_mtu (cfg1), ==, expected_mtu3);
 
-	nm_ip4_config_subtract (cfg1, cfg3);
+	nm_ip4_config_subtract (cfg1, cfg3, 0);
 	/* ensure MSS and MTU are zero in cfg1 */
-	g_assert_cmpuint (nm_ip4_config_get_mss (cfg1), ==, 0);
 	g_assert_cmpuint (nm_ip4_config_get_mtu (cfg1), ==, 0);
 
 	g_object_unref (cfg1);
@@ -306,7 +310,7 @@ test_strip_search_trailing_dot (void)
 {
 	NMIP4Config *config;
 
-	config = nm_ip4_config_new (1);
+	config = nmtst_ip4_config_new (1);
 
 	nm_ip4_config_add_search (config, ".");
 	nm_ip4_config_add_search (config, "foo");
@@ -335,7 +339,7 @@ main (int argc, char **argv)
 	g_test_add_func ("/ip4-config/compare-with-source", test_compare_with_source);
 	g_test_add_func ("/ip4-config/add-address-with-source", test_add_address_with_source);
 	g_test_add_func ("/ip4-config/add-route-with-source", test_add_route_with_source);
-	g_test_add_func ("/ip4-config/merge-subtract-mss-mtu", test_merge_subtract_mss_mtu);
+	g_test_add_func ("/ip4-config/merge-subtract-mtu", test_merge_subtract_mtu);
 	g_test_add_func ("/ip4-config/strip-search-trailing-dot", test_strip_search_trailing_dot);
 
 	return g_test_run ();
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index 7e83625c..bcbeee3e 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -34,13 +34,13 @@ build_test_config (void)
 	NMIP6Config *config;
 
 	/* Build up the config to subtract */
-	config = nm_ip6_config_new (1);
+	config = nmtst_ip6_config_new (1);
 
 	nm_ip6_config_add_address (config, nmtst_platform_ip6_address ("abcd:1234:4321::cdde", "1:2:3:4::5", 64));
-	nm_ip6_config_add_route (config, nmtst_platform_ip6_route ("abcd:1234:4321::", 24, "abcd:1234:4321:cdde::2", NULL));
-	nm_ip6_config_add_route (config, nmtst_platform_ip6_route ("2001:abba::", 16, "2001:abba::2234", NULL));
+	nm_ip6_config_add_route (config, nmtst_platform_ip6_route ("abcd:1200::", 24, "abcd:1234:4321:cdde::2", NULL), NULL);
+	nm_ip6_config_add_route (config, nmtst_platform_ip6_route ("2001::", 16, "2001:abba::2234", NULL), NULL);
 
-	nm_ip6_config_set_gateway (config, nmtst_inet6_from_string ("3001:abba::3234"));
+	nm_ip6_config_add_route (config, nmtst_platform_ip6_route ("::", 0, "3001:abba::3234", NULL), NULL);
 
 	nm_ip6_config_add_nameserver (config, nmtst_inet6_from_string ("1:2:3:4::1"));
 	nm_ip6_config_add_nameserver (config, nmtst_inet6_from_string ("1:2:3:4::2"));
@@ -60,7 +60,7 @@ test_subtract (void)
 	const NMPlatformIP6Route *test_route;
 	const char *expected_addr = "1122:3344:5566::7788";
 	guint32 expected_addr_plen = 96;
-	const char *expected_route_dest = "9991:8882:7773::";
+	const char *expected_route_dest = "9991:8800::";
 	guint32 expected_route_plen = 24;
 	const char *expected_route_next_hop = "1119:2228:3337:4446::5555";
 	struct in6_addr expected_ns1;
@@ -74,7 +74,7 @@ test_subtract (void)
 	/* add a couple more things to the test config */
 	dst = build_test_config ();
 	nm_ip6_config_add_address (dst, nmtst_platform_ip6_address (expected_addr, NULL, expected_addr_plen));
-	nm_ip6_config_add_route (dst, nmtst_platform_ip6_route (expected_route_dest, expected_route_plen, expected_route_next_hop, NULL));
+	nm_ip6_config_add_route (dst, nmtst_platform_ip6_route (expected_route_dest, expected_route_plen, expected_route_next_hop, NULL), NULL);
 
 	expected_ns1 = *nmtst_inet6_from_string ("2222:3333:4444::5555");
 	nm_ip6_config_add_nameserver (dst, &expected_ns1);
@@ -84,21 +84,21 @@ test_subtract (void)
 	nm_ip6_config_add_domain (dst, expected_domain);
 	nm_ip6_config_add_search (dst, expected_search);
 
-	nm_ip6_config_subtract (dst, src);
+	nm_ip6_config_subtract (dst, src, 0);
 
 	/* ensure what's left is what we expect */
 	g_assert_cmpuint (nm_ip6_config_get_num_addresses (dst), ==, 1);
-	test_addr = nm_ip6_config_get_address (dst, 0);
+	test_addr = _nmtst_ip6_config_get_address (dst, 0);
 	g_assert (test_addr != NULL);
 	tmp = *nmtst_inet6_from_string (expected_addr);
 	g_assert (memcmp (&test_addr->address, &tmp, sizeof (tmp)) == 0);
 	g_assert (memcmp (&test_addr->peer_address, &in6addr_any, sizeof (tmp)) == 0);
 	g_assert_cmpuint (test_addr->plen, ==, expected_addr_plen);
 
-	g_assert (nm_ip6_config_get_gateway (dst) == NULL);
+	g_assert (nm_ip6_config_best_default_route_get (dst) == NULL);
 
 	g_assert_cmpuint (nm_ip6_config_get_num_routes (dst), ==, 1);
-	test_route = nm_ip6_config_get_route (dst, 0);
+	test_route = _nmtst_ip6_config_get_route (dst, 0);
 	g_assert (test_route != NULL);
 
 	tmp = *nmtst_inet6_from_string (expected_route_dest);
@@ -127,8 +127,8 @@ test_compare_with_source (void)
 	NMPlatformIP6Address addr;
 	NMPlatformIP6Route route;
 
-	a = nm_ip6_config_new (1);
-	b = nm_ip6_config_new (2);
+	a = nmtst_ip6_config_new (1);
+	b = nmtst_ip6_config_new (2);
 
 	/* Address */
 	addr = *nmtst_platform_ip6_address ("1122:3344:5566::7788", NULL, 64);
@@ -139,12 +139,12 @@ test_compare_with_source (void)
 	nm_ip6_config_add_address (b, &addr);
 
 	/* Route */
-	route = *nmtst_platform_ip6_route ("abcd:1234:4321::", 24, "abcd:1234:4321:cdde::2", NULL);
+	route = *nmtst_platform_ip6_route ("abcd:1200::", 24, "abcd:1234:4321:cdde::2", NULL);
 	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	nm_ip6_config_add_route (a, &route);
+	nm_ip6_config_add_route (a, &route, NULL);
 
 	route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-	nm_ip6_config_add_route (b, &route);
+	nm_ip6_config_add_route (b, &route, NULL);
 
 	/* Assert that the configs are basically the same, eg that the source is ignored */
 	g_assert (nm_ip6_config_equal (a, b));
@@ -160,34 +160,34 @@ test_add_address_with_source (void)
 	NMPlatformIP6Address addr;
 	const NMPlatformIP6Address *test_addr;
 
-	a = nm_ip6_config_new (1);
+	a = nmtst_ip6_config_new (1);
 
 	/* Test that a higher priority source is not overwritten */
 	addr = *nmtst_platform_ip6_address ("1122:3344:5566::7788", NULL, 64);
 	addr.addr_source = NM_IP_CONFIG_SOURCE_USER;
 	nm_ip6_config_add_address (a, &addr);
 
-	test_addr = nm_ip6_config_get_address (a, 0);
+	test_addr = _nmtst_ip6_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	addr.addr_source = NM_IP_CONFIG_SOURCE_VPN;
 	nm_ip6_config_add_address (a, &addr);
 
-	test_addr = nm_ip6_config_get_address (a, 0);
+	test_addr = _nmtst_ip6_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	/* Test that a lower priority address source is overwritten */
-	nm_ip6_config_del_address (a, 0);
+	_nmtst_ip6_config_del_address (a, 0);
 	addr.addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
 	nm_ip6_config_add_address (a, &addr);
 
-	test_addr = nm_ip6_config_get_address (a, 0);
+	test_addr = _nmtst_ip6_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_KERNEL);
 
 	addr.addr_source = NM_IP_CONFIG_SOURCE_USER;
 	nm_ip6_config_add_address (a, &addr);
 
-	test_addr = nm_ip6_config_get_address (a, 0);
+	test_addr = _nmtst_ip6_config_get_address (a, 0);
 	g_assert_cmpint (test_addr->addr_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	g_object_unref (a);
@@ -196,41 +196,45 @@ test_add_address_with_source (void)
 static void
 test_add_route_with_source (void)
 {
-	NMIP6Config *a;
+	gs_unref_object NMIP6Config *a = NULL;
 	NMPlatformIP6Route route;
 	const NMPlatformIP6Route *test_route;
 
-	a = nm_ip6_config_new (1);
+	a = nmtst_ip6_config_new (1);
 
 	/* Test that a higher priority source is not overwritten */
-	route = *nmtst_platform_ip6_route ("abcd:1234:4321::", 24, "abcd:1234:4321:cdde::2", NULL);
+	route = *nmtst_platform_ip6_route ("abcd:1200::", 24, "abcd:1234:4321:cdde::2", NULL);
 	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	nm_ip6_config_add_route (a, &route);
+	nm_ip6_config_add_route (a, &route, NULL);
 
-	test_route = nm_ip6_config_get_route (a, 0);
+	g_assert_cmpint (nm_ip6_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip6_config_get_route (a, 0);
 	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
 	route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-	nm_ip6_config_add_route (a, &route);
+	nm_ip6_config_add_route (a, &route, NULL);
 
-	test_route = nm_ip6_config_get_route (a, 0);
+	g_assert_cmpint (nm_ip6_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip6_config_get_route (a, 0);
 	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER);
 
+	_nmtst_ip6_config_del_route (a, 0);
+	g_assert_cmpint (nm_ip6_config_get_num_routes (a), ==, 0);
+
 	/* Test that a lower priority address source is overwritten */
-	nm_ip6_config_del_route (a, 0);
 	route.rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
-	nm_ip6_config_add_route (a, &route);
+	nm_ip6_config_add_route (a, &route, NULL);
 
-	test_route = nm_ip6_config_get_route (a, 0);
+	g_assert_cmpint (nm_ip6_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip6_config_get_route (a, 0);
 	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_KERNEL);
 
 	route.rt_source = NM_IP_CONFIG_SOURCE_USER;
-	nm_ip6_config_add_route (a, &route);
+	nm_ip6_config_add_route (a, &route, NULL);
 
-	test_route = nm_ip6_config_get_route (a, 0);
+	g_assert_cmpint (nm_ip6_config_get_num_routes (a), ==, 1);
+	test_route = _nmtst_ip6_config_get_route (a, 0);
 	g_assert_cmpint (test_route->rt_source, ==, NM_IP_CONFIG_SOURCE_USER);
-
-	g_object_unref (a);
 }
 
 static void
@@ -256,18 +260,18 @@ test_nm_ip6_config_addresses_sort_check (NMIP6Config *config, NMSettingIP6Config
 			int j = g_rand_int_range (nmtst_get_rand (), i, addr_count);
 
 			NMTST_SWAP (idx[i], idx[j]);
-			nm_ip6_config_add_address (copy, nm_ip6_config_get_address (config, idx[i]));
+			nm_ip6_config_add_address (copy, _nmtst_ip6_config_get_address (config, idx[i]));
 		}
 
 		/* reorder them again */
-		nm_ip6_config_addresses_sort (copy);
+		_nmtst_ip6_config_addresses_sort (copy);
 
 		/* check equality using nm_ip6_config_equal() */
 		if (!nm_ip6_config_equal (copy, config)) {
 			g_message ("%s", "SORTING yields unexpected output:");
 			for (i = 0; i < addr_count; i++) {
-				g_message ("   >> [%d] = %s", i, nm_platform_ip6_address_to_string (nm_ip6_config_get_address (config, i), NULL, 0));
-				g_message ("   << [%d] = %s", i, nm_platform_ip6_address_to_string (nm_ip6_config_get_address (copy, i), NULL, 0));
+				g_message ("   >> [%d] = %s", i, nm_platform_ip6_address_to_string (_nmtst_ip6_config_get_address (config, i), NULL, 0));
+				g_message ("   << [%d] = %s", i, nm_platform_ip6_address_to_string (_nmtst_ip6_config_get_address (copy, i), NULL, 0));
 			}
 			g_assert_not_reached ();
 		}
@@ -327,7 +331,7 @@ test_strip_search_trailing_dot (void)
 {
 	NMIP6Config *config;
 
-	config = nm_ip6_config_new (1);
+	config = nmtst_ip6_config_new (1);
 
 	nm_ip6_config_add_search (config, ".");
 	nm_ip6_config_add_search (config, "foo");
@@ -345,6 +349,71 @@ test_strip_search_trailing_dot (void)
 
 /*****************************************************************************/
 
+static void
+test_replace (gconstpointer user_data)
+{
+	nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = nm_dedup_multi_index_new ();
+	const int TEST_IDX = GPOINTER_TO_INT (user_data);
+	const int IFINDEX = 1;
+	gs_unref_object NMIP6Config *src_conf = NULL;
+	gs_unref_object NMIP6Config *dst_conf = NULL;
+	NMPlatformIP6Address *addr;
+	NMPlatformIP6Address addrs[5] = { };
+	guint addrs_n = 0;
+	guint i;
+
+	dst_conf = nm_ip6_config_new (multi_idx, IFINDEX);
+	src_conf = nm_ip6_config_new (multi_idx, IFINDEX);
+
+	switch (TEST_IDX) {
+	case 1:
+		addr = &addrs[addrs_n++];
+		addr->ifindex = IFINDEX;
+		addr->address = *nmtst_inet6_from_string ("fe80::78ec:7a6d:602d:20f2");
+		addr->plen = 64;
+		addr->n_ifa_flags = IFA_F_PERMANENT;
+		addr->addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
+		break;
+	case 2:
+		addr = &addrs[addrs_n++];
+		addr->ifindex = IFINDEX;
+		addr->address = *nmtst_inet6_from_string ("fe80::78ec:7a6d:602d:20f2");
+		addr->plen = 64;
+		addr->n_ifa_flags = IFA_F_PERMANENT;
+		addr->addr_source = NM_IP_CONFIG_SOURCE_KERNEL;
+
+		addr = &addrs[addrs_n++];
+		addr->ifindex = IFINDEX;
+		addr->address = *nmtst_inet6_from_string ("1::1");
+		addr->plen = 64;
+		addr->addr_source = NM_IP_CONFIG_SOURCE_USER;
+
+		nm_ip6_config_add_address (dst_conf, addr);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	g_assert (addrs_n < G_N_ELEMENTS (addrs));
+
+	for (i = 0; i < addrs_n; i++)
+		nm_ip6_config_add_address (src_conf, &addrs[i]);
+
+	nm_ip6_config_replace (dst_conf, src_conf, NULL);
+
+	for (i = 0; i < addrs_n; i++) {
+		const NMPlatformIP6Address *a = _nmtst_ip6_config_get_address (dst_conf, i);
+		const NMPlatformIP6Address *b = _nmtst_ip6_config_get_address (src_conf, i);
+
+		g_assert (nm_platform_ip6_address_cmp (&addrs[i], a) == 0);
+		g_assert (nm_platform_ip6_address_cmp (&addrs[i], b) == 0);
+	}
+	g_assert (addrs_n == nm_ip6_config_get_num_addresses (dst_conf));
+	g_assert (addrs_n == nm_ip6_config_get_num_addresses (src_conf));
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -358,6 +427,8 @@ main (int argc, char **argv)
 	g_test_add_func ("/ip6-config/add-route-with-source", test_add_route_with_source);
 	g_test_add_func ("/ip6-config/test_nm_ip6_config_addresses_sort", test_nm_ip6_config_addresses_sort);
 	g_test_add_func ("/ip6-config/strip-search-trailing-dot", test_strip_search_trailing_dot);
+	g_test_add_data_func ("/ip6-config/replace/1", GINT_TO_POINTER (1), test_replace);
+	g_test_add_data_func ("/ip6-config/replace/2", GINT_TO_POINTER (2), test_replace);
 
 	return g_test_run ();
 }
diff --git a/src/tests/test-resolvconf-capture.c b/src/tests/test-resolvconf-capture.c
index ccbdafa6..2c34ff74 100644
--- a/src/tests/test-resolvconf-capture.c
+++ b/src/tests/test-resolvconf-capture.c
@@ -36,42 +36,24 @@ test_capture_empty (void)
 	GArray *ns4 = g_array_new (FALSE, FALSE, sizeof (guint32));
 	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
 
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, NULL, "") == FALSE);
+	g_assert (!nm_utils_resolve_conf_parse (AF_INET, "", ns4, NULL));
 	g_assert_cmpint (ns4->len, ==, 0);
 
-	g_assert (nm_ip6_config_capture_resolv_conf (ns6, NULL, "") == FALSE);
+	g_assert (!nm_utils_resolve_conf_parse (AF_INET6, "", ns6, NULL));
 	g_assert_cmpint (ns6->len, ==, 0);
 
 	g_array_free (ns4, TRUE);
 	g_array_free (ns6, TRUE);
 }
 
-static void
-assert_dns4_entry (const GArray *a, guint i, const char *s)
-{
-	guint32 n, m;
-
-	g_assert (inet_aton (s, (void *) &n) != 0);
-	m = g_array_index (a, guint32, i);
-	g_assert_cmpint (m, ==, n);
-}
-
-static void
-assert_dns6_entry (const GArray *a, guint i, const char *s)
-{
-	struct in6_addr n = IN6ADDR_ANY_INIT;
-	struct in6_addr *m;
+#define assert_dns4_entry(a, i, s) \
+	g_assert_cmpint ((g_array_index ((a), guint32, (i))), ==, nmtst_inet4_from_string (s));
 
-	g_assert (inet_pton (AF_INET6, s, (void *) &n) == 1);
-	m = &g_array_index (a, struct in6_addr, i);
-	g_assert (IN6_ARE_ADDR_EQUAL (&n, m));
-}
+#define assert_dns6_entry(a, i, s) \
+	g_assert (IN6_ARE_ADDR_EQUAL (&g_array_index ((a), struct in6_addr, (i)), nmtst_inet6_from_string (s)))
 
-static void
-assert_dns_option (GPtrArray *a, guint i, const char *s)
-{
-	g_assert_cmpstr (a->pdata[i], ==, s);
-}
+#define assert_dns_option(a, i, s) \
+	g_assert_cmpstr ((a)->pdata[(i)], ==, (s));
 
 static void
 test_capture_basic4 (void)
@@ -84,7 +66,7 @@ test_capture_basic4 (void)
 "nameserver 4.2.2.1\r\n"
 "nameserver 4.2.2.2\r\n";
 
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, NULL, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
 	g_assert_cmpint (ns4->len, ==, 2);
 	assert_dns4_entry (ns4, 0, "4.2.2.1");
 	assert_dns4_entry (ns4, 1, "4.2.2.2");
@@ -105,7 +87,7 @@ test_capture_dup4 (void)
 "nameserver 4.2.2.2\r\n";
 
 	/* Check that duplicates are ignored */
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, NULL, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
 	g_assert_cmpint (ns4->len, ==, 2);
 	assert_dns4_entry (ns4, 0, "4.2.2.1");
 	assert_dns4_entry (ns4, 1, "4.2.2.2");
@@ -124,7 +106,7 @@ test_capture_basic6 (void)
 "nameserver 2001:4860:4860::8888\r\n"
 "nameserver 2001:4860:4860::8844\r\n";
 
-	g_assert (nm_ip6_config_capture_resolv_conf (ns6, NULL, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, NULL));
 	g_assert_cmpint (ns6->len, ==, 2);
 	assert_dns6_entry (ns6, 0, "2001:4860:4860::8888");
 	assert_dns6_entry (ns6, 1, "2001:4860:4860::8844");
@@ -145,7 +127,7 @@ test_capture_dup6 (void)
 "nameserver 2001:4860:4860::8844\r\n";
 
 	/* Check that duplicates are ignored */
-	g_assert (nm_ip6_config_capture_resolv_conf (ns6, NULL, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, NULL));
 	g_assert_cmpint (ns6->len, ==, 2);
 	assert_dns6_entry (ns6, 0, "2001:4860:4860::8888");
 	assert_dns6_entry (ns6, 1, "2001:4860:4860::8844");
@@ -165,7 +147,7 @@ test_capture_addr4_with_6 (void)
 "nameserver 4.2.2.2\r\n"
 "nameserver 2001:4860:4860::8888\r\n";
 
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, NULL, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
 	g_assert_cmpint (ns4->len, ==, 2);
 	assert_dns4_entry (ns4, 0, "4.2.2.1");
 	assert_dns4_entry (ns4, 1, "4.2.2.2");
@@ -185,7 +167,7 @@ test_capture_addr6_with_4 (void)
 "nameserver 2001:4860:4860::8888\r\n"
 "nameserver 2001:4860:4860::8844\r\n";
 
-	g_assert (nm_ip6_config_capture_resolv_conf (ns6, NULL, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, NULL));
 	g_assert_cmpint (ns6->len, ==, 2);
 	assert_dns6_entry (ns6, 0, "2001:4860:4860::8888");
 	assert_dns6_entry (ns6, 1, "2001:4860:4860::8844");
@@ -200,15 +182,17 @@ test_capture_format (void)
 	const char *rc =
 " nameserver 4.2.2.1\r\n"     /* bad */
 "nameserver4.2.2.1\r\n"       /* bad */
-"nameserver     4.2.2.3\r"  /* good */
+"nameserver     4.2.2.3\r"    /* good */
 "nameserver\t\t4.2.2.4\r\n"   /* good */
-"nameserver  4.2.2.5\t\t\r\n"; /* good */
+"nameserver  4.2.2.5\t\t\r\n" /* good */
+"nameserver  4.2.2.6   \r\n"; /* good */
 
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, NULL, rc));
-	g_assert_cmpint (ns4->len, ==, 3);
+	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, NULL));
+	g_assert_cmpint (ns4->len, ==, 4);
 	assert_dns4_entry (ns4, 0, "4.2.2.3");
 	assert_dns4_entry (ns4, 1, "4.2.2.4");
 	assert_dns4_entry (ns4, 2, "4.2.2.5");
+	assert_dns4_entry (ns4, 3, "4.2.2.6");
 
 	g_array_free (ns4, TRUE);
 }
@@ -223,7 +207,7 @@ test_capture_dns_options (void)
 "options debug rotate  timeout:5 \r\n"
 "options edns0\r\n";
 
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, dns_options, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, dns_options));
 	g_assert_cmpint (dns_options->len, ==, 4);
 	assert_dns_option (dns_options, 0, "debug");
 	assert_dns_option (dns_options, 1, "rotate");
@@ -244,7 +228,7 @@ test_capture_dns_options_dup (void)
 "options edns0 debug\r\n"
 "options timeout:5\r\n";
 
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, dns_options, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, dns_options));
 	g_assert_cmpint (dns_options->len, ==, 4);
 	assert_dns_option (dns_options, 0, "debug");
 	assert_dns_option (dns_options, 1, "rotate");
@@ -263,7 +247,7 @@ test_capture_dns_options_valid4 (void)
 	const char *rc =
 "options debug: rotate:yes edns0 foobar : inet6\r\n";
 
-	g_assert (nm_ip4_config_capture_resolv_conf (ns4, dns_options, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET, rc, ns4, dns_options));
 	g_assert_cmpint (dns_options->len, ==, 1);
 	assert_dns_option (dns_options, 0, "edns0");
 
@@ -274,12 +258,12 @@ test_capture_dns_options_valid4 (void)
 static void
 test_capture_dns_options_valid6 (void)
 {
-	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (guint32));
+	GArray *ns6 = g_array_new (FALSE, FALSE, sizeof (struct in6_addr));
 	GPtrArray *dns_options = g_ptr_array_new_with_free_func (g_free);
 	const char *rc =
 "options inet6 debug foobar rotate:\r\n";
 
-	g_assert (nm_ip6_config_capture_resolv_conf (ns6, dns_options, rc));
+	g_assert (nm_utils_resolve_conf_parse (AF_INET6, rc, ns6, dns_options));
 	g_assert_cmpint (dns_options->len, ==, 2);
 	assert_dns_option (dns_options, 0, "inet6");
 	assert_dns_option (dns_options, 1, "debug");
diff --git a/src/tests/test-route-manager.c b/src/tests/test-route-manager.c
deleted file mode 100644
index 6650d26c..00000000
--- a/src/tests/test-route-manager.c
+++ /dev/null
@@ -1,933 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*
- * 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, 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) 2015 Red Hat, Inc.
- *
- */
-
-#include "nm-default.h"
-
-#include <arpa/inet.h>
-#include <linux/rtnetlink.h>
-
-#include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
-#include "nm-route-manager.h"
-
-#include "platform/tests/test-common.h"
-
-typedef struct {
-	int ifindex0, ifindex1;
-} test_fixture;
-
-NMRouteManager *route_manager_get (void);
-
-NM_DEFINE_SINGLETON_GETTER (NMRouteManager, route_manager_get, NM_TYPE_ROUTE_MANAGER);
-
-/*****************************************************************************/
-
-static void
-setup_dev0_ip4 (int ifindex, guint mss_of_first_route, guint32 metric_of_second_route)
-{
-	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-	NMPlatformIP4Route route = { 0 };
-
-	route.ifindex = ifindex;
-	route.mss = 0;
-
-	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	inet_pton (AF_INET, "6.6.6.0", &route.network);
-	route.plen = 24;
-	route.gateway = INADDR_ANY;
-	route.metric = 20;
-	route.mss = mss_of_first_route;
-	g_array_append_val (routes, route);
-
-	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	inet_pton (AF_INET, "7.0.0.0", &route.network);
-	route.plen = 8;
-	inet_pton (AF_INET, "6.6.6.1", &route.gateway);
-	route.metric = metric_of_second_route;
-	route.mss = 0;
-	g_array_append_val (routes, route);
-
-	nm_route_manager_ip4_route_sync (route_manager_get (), ifindex, routes, TRUE, TRUE);
-	g_array_free (routes, TRUE);
-}
-
-static void
-setup_dev1_ip4 (int ifindex)
-{
-	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-	NMPlatformIP4Route route = { 0 };
-
-	route.ifindex = ifindex;
-	route.mss = 0;
-
-	/* Add some route outside of route manager. The route manager
-	 * should get rid of it upon sync. */
-	nmtstp_ip4_route_add (NM_PLATFORM_GET,
-	                      route.ifindex,
-	                      NM_IP_CONFIG_SOURCE_USER,
-	                      nmtst_inet4_from_string ("9.0.0.0"),
-	                      8,
-	                      INADDR_ANY,
-	                      0,
-	                      10,
-	                      route.mss);
-
-	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	inet_pton (AF_INET, "6.6.6.0", &route.network);
-	route.plen = 24;
-	route.gateway = INADDR_ANY;
-	route.metric = 20;
-	g_array_append_val (routes, route);
-
-	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	inet_pton (AF_INET, "7.0.0.0", &route.network);
-	route.plen = 8;
-	route.gateway = INADDR_ANY;
-	route.metric = 22;
-	g_array_append_val (routes, route);
-
-	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	inet_pton (AF_INET, "8.0.0.0", &route.network);
-	route.plen = 8;
-	inet_pton (AF_INET, "6.6.6.2", &route.gateway);
-	route.metric = 22;
-	g_array_append_val (routes, route);
-
-	nm_route_manager_ip4_route_sync (route_manager_get (), ifindex, routes, TRUE, TRUE);
-	g_array_free (routes, TRUE);
-}
-
-static void
-update_dev0_ip4 (int ifindex)
-{
-	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-	NMPlatformIP4Route route = { 0 };
-
-	route.ifindex = ifindex;
-	route.mss = 0;
-
-	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	inet_pton (AF_INET, "6.6.6.0", &route.network);
-	route.plen = 24;
-	route.gateway = INADDR_ANY;
-	route.metric = 20;
-	g_array_append_val (routes, route);
-
-	route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
-	inet_pton (AF_INET, "7.0.0.0", &route.network);
-	route.plen = 8;
-	route.gateway = INADDR_ANY;
-	route.metric = 21;
-	g_array_append_val (routes, route);
-
-	nm_route_manager_ip4_route_sync (route_manager_get (), ifindex, routes, TRUE, TRUE);
-	g_array_free (routes, TRUE);
-}
-
-
-static GArray *
-ip4_routes (test_fixture *fixture)
-{
-	GArray *routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET,
-	                                                fixture->ifindex0,
-	                                                NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-	GArray *routes1 = nm_platform_ip4_route_get_all (NM_PLATFORM_GET,
-	                                                 fixture->ifindex1,
-	                                                 NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-
-	g_array_append_vals (routes, routes1->data, routes1->len);
-	g_array_free (routes1, TRUE);
-
-	return routes;
-}
-
-static void
-test_ip4 (test_fixture *fixture, gconstpointer user_data)
-{
-	GArray *routes;
-
-	NMPlatformIP4Route state1[] = {
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("6.6.6.0"),
-			.plen = 24,
-			.ifindex = fixture->ifindex0,
-			.gateway = INADDR_ANY,
-			.metric = 20,
-			.mss = 1000,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("7.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex0,
-			.gateway = nmtst_inet4_from_string ("6.6.6.1"),
-			.metric = 21021,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("7.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex1,
-			.gateway = INADDR_ANY,
-			.metric = 22,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("6.6.6.0"),
-			.plen = 24,
-			.ifindex = fixture->ifindex1,
-			.gateway = INADDR_ANY,
-			.metric = 21,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("8.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex1,
-			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
-			.metric = 22,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
-		},
-	};
-
-	NMPlatformIP4Route state2[] = {
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("6.6.6.0"),
-			.plen = 24,
-			.ifindex = fixture->ifindex0,
-			.gateway = INADDR_ANY,
-			.metric = 20,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("7.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex0,
-			.gateway = INADDR_ANY,
-			.metric = 21,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("7.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex1,
-			.gateway = INADDR_ANY,
-			.metric = 22,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("6.6.6.0"),
-			.plen = 24,
-			.ifindex = fixture->ifindex1,
-			.gateway = INADDR_ANY,
-			.metric = 21,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("8.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex1,
-			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
-			.metric = 22,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
-		},
-	};
-
-	NMPlatformIP4Route state3[] = {
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("7.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex1,
-			.gateway = INADDR_ANY,
-			.metric = 22,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("6.6.6.0"),
-			.plen = 24,
-			.ifindex = fixture->ifindex1,
-			.gateway = INADDR_ANY,
-			.metric = 20,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("8.0.0.0"),
-			.plen = 8,
-			.ifindex = fixture->ifindex1,
-			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
-			.metric = 22,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = nmtst_inet4_from_string ("6.6.6.0"),
-			.plen = 24,
-			.ifindex = fixture->ifindex1,
-			.gateway = INADDR_ANY,
-			/* this is a ghost entry because we synced ifindex0 and restore the route
-			 * with metric 20 (above). But we don't remove the metric 21. */
-			.metric = 21,
-			.mss = 0,
-			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
-		},
-	};
-
-	setup_dev0_ip4 (fixture->ifindex0, 1000, 21021);
-	setup_dev1_ip4 (fixture->ifindex1);
-	g_test_assert_expected_messages ();
-
-	/* - 6.6.6.0/24 on dev0 won over 6.6.6.0/24 on dev1
-	 * - 6.6.6.0/24 on dev1 has metric bumped.
-	 * - 7.0.0.0/8 route, metric 21021 added
-	 * - 7.0.0.0/8 route, metric 22 added
-	 * - 8.0.0.0/8 could be added. */
-	routes = ip4_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state1, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-	setup_dev1_ip4 (fixture->ifindex1);
-	g_test_assert_expected_messages ();
-
-	setup_dev0_ip4 (fixture->ifindex0, 0, 21);
-
-	/* Ensure nothing changed. */
-	routes = ip4_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
-	state1[0].mss = 0;
-	state1[1].metric = 21;
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state1, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-	update_dev0_ip4 (fixture->ifindex0);
-
-	/* minor changes in the routes. Quite similar to state1. */
-	routes = ip4_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state2));
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state2, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-	nm_route_manager_route_flush (route_manager_get (), fixture->ifindex0);
-
-	/* 6.6.6.0/24 is now on dev1
-	 * 6.6.6.0/24 is also still on dev1 with bumped metric 21.
-	 * 7.0.0.0/8 gone from dev0, still present on dev1
-	 * 8.0.0.0/8 is present on dev1
-	 * No dev0 routes left. */
-	routes = ip4_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state3));
-	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state3, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-	nm_route_manager_route_flush (route_manager_get (), fixture->ifindex1);
-
-	/* No routes left. */
-	routes = ip4_routes (fixture);
-	g_assert_cmpint (routes->len, ==, 0);
-	g_array_free (routes, TRUE);
-}
-
-static void
-setup_dev0_ip6 (int ifindex)
-{
-	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
-	NMPlatformIP6Route *route;
-
-	/* Add an address so that a route to the gateway below gets added. */
-	nm_platform_ip6_address_add (NM_PLATFORM_GET,
-	                             ifindex,
-	                             *nmtst_inet6_from_string ("2001:db8:8086::666"),
-	                             64,
-	                             in6addr_any,
-	                             3600,
-	                             3600,
-	                             0);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
-	                                       48,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       20,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
-	                                       48,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       0,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
-	                                       64,
-	                                       "2001:db8:8086::1",
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       21,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	nm_route_manager_ip6_route_sync (route_manager_get (), ifindex, routes, TRUE, TRUE);
-	g_array_free (routes, TRUE);
-}
-
-static void
-setup_dev1_ip6 (int ifindex)
-{
-	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
-	NMPlatformIP6Route *route;
-
-	/* Add some route outside of route manager. The route manager
-	 * should get rid of it upon sync. */
-	nmtstp_ip6_route_add (NM_PLATFORM_GET,
-	                      ifindex,
-	                      NM_IP_CONFIG_SOURCE_USER,
-	                      *nmtst_inet6_from_string ("2001:db8:8088::"),
-	                      48,
-	                      in6addr_any,
-	                      in6addr_any,
-	                      10,
-	                      0);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
-	                                       48,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       20,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
-	                                       48,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       1024,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:d34d::",
-	                                       64,
-	                                       "2001:db8:8086::2",
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       20,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
-	                                       64,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       22,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	nm_route_manager_ip6_route_sync (route_manager_get (), ifindex, routes, TRUE, TRUE);
-	g_array_free (routes, TRUE);
-}
-
-static void
-update_dev0_ip6 (int ifindex)
-{
-	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
-	NMPlatformIP6Route *route;
-
-	/* Add an address so that a route to the gateway below gets added. */
-	nm_platform_ip6_address_add (NM_PLATFORM_GET,
-	                             ifindex,
-	                             *nmtst_inet6_from_string ("2001:db8:8086::2"),
-	                             64,
-	                             in6addr_any,
-	                             3600,
-	                             3600,
-	                             0);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
-	                                       48,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       20,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
-	                                       48,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       0,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
-	                                       64,
-	                                       NULL,
-	                                       ifindex,
-	                                       NM_IP_CONFIG_SOURCE_USER,
-	                                       21,
-	                                       0);
-	g_array_append_val (routes, *route);
-
-	nm_route_manager_ip6_route_sync (route_manager_get (), ifindex, routes, TRUE, TRUE);
-	g_array_free (routes, TRUE);
-}
-
-static GArray *
-ip6_routes (test_fixture *fixture)
-{
-	GArray *routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET,
-	                                                fixture->ifindex0,
-	                                                NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-	GArray *routes1 = nm_platform_ip6_route_get_all (NM_PLATFORM_GET,
-	                                                 fixture->ifindex1,
-	                                                 NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
-
-	g_array_append_vals (routes, routes1->data, routes1->len);
-	g_array_free (routes1, TRUE);
-
-	return routes;
-}
-
-static void
-test_ip6 (test_fixture *fixture, gconstpointer user_data)
-{
-	GArray *routes;
-
-	NMPlatformIP6Route state1[] = {
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex0,
-			.gateway = in6addr_any,
-			.metric = 20,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex0,
-			.gateway = in6addr_any,
-			.metric = 1024,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex0,
-			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::1"),
-			.metric = 21,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 22,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 1025,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 21,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex1,
-			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
-			.metric = 20,
-			.mss = 0,
-		},
-	};
-
-	NMPlatformIP6Route state2[] = {
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex0,
-			.gateway = in6addr_any,
-			.metric = 20,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex0,
-			.gateway = in6addr_any,
-			.metric = 1024,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex0,
-			.gateway = in6addr_any,
-			.metric = 21,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 22,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 1025,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 21,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex1,
-			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
-			.metric = 20,
-			.mss = 0,
-		},
-	};
-
-	NMPlatformIP6Route state3[] = {
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 22,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 20,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 1024,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 1025,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
-			.plen = 48,
-			.ifindex = fixture->ifindex1,
-			.gateway = in6addr_any,
-			.metric = 21,
-			.mss = 0,
-		},
-		{
-			.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER),
-			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
-			.plen = 64,
-			.ifindex = fixture->ifindex1,
-			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
-			.metric = 20,
-			.mss = 0,
-		},
-	};
-
-	setup_dev0_ip6 (fixture->ifindex0);
-	setup_dev1_ip6 (fixture->ifindex1);
-	g_test_assert_expected_messages ();
-
-	/* 2001:db8:8086::/48 on dev0 won over 2001:db8:8086::/48 on dev1
-	 * 2001:db8:d34d::/64 on dev1 could not be added
-	 * 2001:db8:1337::/48 on dev0 won over 2001:db8:1337::/48 on dev1 and has metric 1024
-	 * 2001:db8:abad:c0de::/64 routes did not clash */
-	routes = ip6_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state1, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-
-	setup_dev1_ip6 (fixture->ifindex1);
-	g_test_assert_expected_messages ();
-	setup_dev0_ip6 (fixture->ifindex0);
-
-	/* Ensure nothing changed. */
-	routes = ip6_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state1, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-	update_dev0_ip6 (fixture->ifindex0);
-
-	/* 2001:db8:abad:c0de::/64 on dev0 was updated for gateway removal*/
-	routes = ip6_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state2));
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state2, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-	nm_route_manager_route_flush (route_manager_get (), fixture->ifindex0);
-
-	/* 2001:db8:abad:c0de::/64 on dev1 is still there, went away from dev0
-	 * 2001:db8:8086::/48 is now on dev1
-	 * 2001:db8:1337::/48 is now on dev1, metric of 1024 still applies
-	 * 2001:db8:d34d::/64 is present now that 2001:db8:8086::/48 is on dev1
-	 * No dev0 routes left. */
-	routes = ip6_routes (fixture);
-	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state3));
-	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state3, routes->len, TRUE);
-	g_array_free (routes, TRUE);
-
-	nm_route_manager_route_flush (route_manager_get (), fixture->ifindex1);
-
-	/* No routes left. */
-	routes = ip6_routes (fixture);
-	g_assert_cmpint (routes->len, ==, 0);
-	g_array_free (routes, TRUE);
-}
-
-/*****************************************************************************/
-
-static void
-_assert_route_check (const NMPlatformVTableRoute *vtable, gboolean has, const NMPlatformIPXRoute *route)
-{
-	const NMPlatformIPXRoute *r;
-	NMPlatformIPXRoute c;
-
-	g_assert (route);
-
-	if (vtable->is_ip4)
-		r = (const NMPlatformIPXRoute *) nm_platform_ip4_route_get (NM_PLATFORM_GET, route->rx.ifindex, route->r4.network, route->rx.plen, route->rx.metric);
-	else
-		r = (const NMPlatformIPXRoute *) nm_platform_ip6_route_get (NM_PLATFORM_GET, route->rx.ifindex, route->r6.network, route->rx.plen, route->rx.metric);
-
-	if (!has) {
-		g_assert (!r);
-	} else {
-		char buf[sizeof (_nm_utils_to_string_buffer)];
-
-		if (r) {
-			if (vtable->is_ip4)
-				c.r4 = route->r4;
-			else
-				c.r6 = route->r6;
-			c.rx.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (c.rx.rt_source);
-		}
-		if (!r || vtable->route_cmp (r, &c, TRUE) != 0) {
-			g_error ("Invalid route. Expect %s, has %s",
-			         vtable->route_to_string (&c, NULL, 0),
-			         vtable->route_to_string (r, buf, sizeof (buf)));
-		}
-	}
-}
-
-static void
-test_ip4_full_sync (test_fixture *fixture, gconstpointer user_data)
-{
-	const NMPlatformVTableRoute *vtable = &nm_platform_vtable_route_v4;
-	gs_unref_array GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
-	NMPlatformIP4Route r01, r02, r03;
-
-	nm_log_dbg (LOGD_CORE, "TEST start test_ip4_full_sync(): start");
-
-	r01 = *nmtst_platform_ip4_route_full ("12.3.4.0", 24, NULL,
-	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
-	                                      100, 0, RT_SCOPE_LINK, NULL);
-	r02 = *nmtst_platform_ip4_route_full ("13.4.5.6", 32, "12.3.4.1",
-	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
-	                                      100, 0, RT_SCOPE_UNIVERSE, NULL);
-	r03 = *nmtst_platform_ip4_route_full ("14.5.6.7", 32, "12.3.4.1",
-	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
-	                                      110, 0, RT_SCOPE_UNIVERSE, NULL);
-	g_array_set_size (routes, 2);
-	g_array_index (routes, NMPlatformIP4Route, 0) = r01;
-	g_array_index (routes, NMPlatformIP4Route, 1) = r02;
-	nm_route_manager_ip4_route_sync (route_manager_get (), fixture->ifindex0, routes, TRUE, TRUE);
-
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
-	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r03);
-
-	vtable->route_add (NM_PLATFORM_GET, 0, (const NMPlatformIPXRoute *) &r03, -1);
-
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
-
-	nm_route_manager_ip4_route_sync (route_manager_get (), fixture->ifindex0, routes, TRUE, FALSE);
-
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
-
-	g_array_set_size (routes, 1);
-
-	nm_route_manager_ip4_route_sync (route_manager_get (), fixture->ifindex0, routes, TRUE, FALSE);
-
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
-	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r02);
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
-
-	nm_route_manager_ip4_route_sync (route_manager_get (), fixture->ifindex0, routes, TRUE, TRUE);
-
-	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
-	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r02);
-	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r03);
-
-	nm_log_dbg (LOGD_CORE, "TEST test_ip4_full_sync(): done");
-}
-
-/*****************************************************************************/
-
-static void
-fixture_setup (test_fixture *fixture, gconstpointer user_data)
-{
-	SignalData *link_added;
-
-	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED,
-	                                NM_PLATFORM_SIGNAL_ADDED,
-	                                link_callback,
-	                                "nm-test-device0");
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device0"));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "nm-test-device0"));
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, "nm-test-device0", NULL) == NM_PLATFORM_ERROR_SUCCESS);
-	accept_signal (link_added);
-	free_signal (link_added);
-	fixture->ifindex0 = nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device0");
-	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex0, NULL));
-
-	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED,
-	                                NM_PLATFORM_SIGNAL_ADDED,
-	                                link_callback,
-	                                "nm-test-device1");
-	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device1"));
-	g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, "nm-test-device1"));
-	g_assert (nm_platform_link_dummy_add (NM_PLATFORM_GET, "nm-test-device1", NULL) == NM_PLATFORM_ERROR_SUCCESS);
-	accept_signal (link_added);
-	free_signal (link_added);
-	fixture->ifindex1 = nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device1");
-	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex1, NULL));
-}
-
-static void
-fixture_teardown (test_fixture *fixture, gconstpointer user_data)
-{
-	nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex0);
-	nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex1);
-}
-
-/*****************************************************************************/
-
-NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
-
-void
-_nmtstp_init_tests (int *argc, char ***argv)
-{
-	nmtst_init_assert_logging (argc, argv, "WARN", "ALL");
-}
-
-void
-_nmtstp_setup_tests (void)
-{
-	g_test_add ("/route-manager/ip4", test_fixture, NULL, fixture_setup, test_ip4, fixture_teardown);
-	g_test_add ("/route-manager/ip6", test_fixture, NULL, fixture_setup, test_ip6, fixture_teardown);
-
-	g_test_add ("/route-manager/ip4-full-sync", test_fixture, NULL, fixture_setup, test_ip4_full_sync, fixture_teardown);
-}
diff --git a/src/tests/test-systemd.c b/src/tests/test-systemd.c
index f6cf9b58..ab5fed22 100644
--- a/src/tests/test-systemd.c
+++ b/src/tests/test-systemd.c
@@ -78,7 +78,7 @@ test_dhcp_create (void)
 	sd_dhcp_client *client4 = NULL;
 	int r;
 
-	r = sd_dhcp_client_new (&client4);
+	r = sd_dhcp_client_new (&client4, FALSE);
 	g_assert (r == 0);
 	g_assert (client4);
 
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index ecc82068..2436ea33 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -31,6 +31,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <syslog.h>
+#include <linux/rtnetlink.h>
 
 #include "nm-proxy-config.h"
 #include "nm-ip4-config.h"
@@ -44,8 +45,6 @@
 #include "settings/nm-agent-manager.h"
 #include "nm-core-internal.h"
 #include "nm-pacrunner-manager.h"
-#include "nm-default-route-manager.h"
-#include "nm-route-manager.h"
 #include "nm-firewall-manager.h"
 #include "nm-config.h"
 #include "nm-vpn-plugin-info.h"
@@ -123,6 +122,8 @@ typedef struct {
 
 	NMNetns *netns;
 
+	GPtrArray *ip4_dev_route_blacklist;
+
 	GDBusProxy *proxy;
 	GCancellable *cancellable;
 	GVariant *connect_hash;
@@ -186,6 +187,8 @@ static void get_secrets (NMVpnConnection *self,
                          SecretsReq secrets_idx,
                          const char **hints);
 
+static guint32 get_route_table (NMVpnConnection *self, int addr_family, gboolean fallback_main);
+
 static void plugin_interactive_secrets_required (NMVpnConnection *self,
                                                  const char *message,
                                                  const char **secrets);
@@ -394,9 +397,11 @@ vpn_cleanup (NMVpnConnection *self, NMDevice *parent_dev)
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
 	if (priv->ip_ifindex) {
-		nm_platform_link_set_down (nm_netns_get_platform (priv->netns), priv->ip_ifindex);
-		nm_route_manager_route_flush (nm_netns_get_route_manager (priv->netns), priv->ip_ifindex);
-		nm_platform_address_flush (nm_netns_get_platform (priv->netns), priv->ip_ifindex);
+		NMPlatform *platform = nm_netns_get_platform (priv->netns);
+
+		nm_platform_link_set_down (platform, priv->ip_ifindex);
+		nm_platform_ip_route_flush (platform, AF_UNSPEC, priv->ip_ifindex);
+		nm_platform_ip_address_flush (platform, AF_UNSPEC, priv->ip_ifindex);
 	}
 
 	remove_parent_device_config (self, parent_dev);
@@ -497,9 +502,6 @@ _set_vpn_state (NMVpnConnection *self,
 
 	dispatcher_cleanup (self);
 
-	nm_default_route_manager_ip4_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-	nm_default_route_manager_ip6_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-
 	/* The connection gets destroyed by the VPN manager when it enters the
 	 * disconnected/failed state, but we need to keep it around for a bit
 	 * to send out signals and handle the dispatcher.  So ref it.
@@ -704,44 +706,63 @@ device_state_changed (NMActiveConnection *active,
 }
 
 static void
-add_ip4_vpn_gateway_route (NMIP4Config *config, NMDevice *parent_device, guint32 vpn_gw)
+add_ip4_vpn_gateway_route (NMIP4Config *config,
+                           NMDevice *parent_device,
+                           in_addr_t vpn_gw,
+                           NMPlatform *platform)
 {
-	NMIP4Config *parent_config;
-	guint32 parent_gw;
+	guint32 parent_gw = 0;
+	gboolean has_parent_gw = FALSE;
 	NMPlatformIP4Route route;
+	int ifindex;
 	guint32 route_metric;
+	nm_auto_nmpobj const NMPObject *route_resolved = NULL;
 
 	g_return_if_fail (NM_IS_IP4_CONFIG (config));
 	g_return_if_fail (NM_IS_DEVICE (parent_device));
 	g_return_if_fail (vpn_gw != 0);
 
-	/* Set up a route to the VPN gateway's public IP address through the default
-	 * network device if the VPN gateway is on a different subnet.
-	 */
-	parent_config = nm_device_get_ip4_config (parent_device);
-	g_return_if_fail (parent_config != NULL);
-	parent_gw = nm_ip4_config_get_gateway (parent_config);
+	ifindex = nm_ip4_config_get_ifindex (config);
+
+	nm_assert (ifindex > 0);
+	nm_assert (ifindex == nm_device_get_ip_ifindex (parent_device));
+
+	/* Ask kernel how to reach @vpn_gw. We can only inject the route in
+	 * @parent_device, so whatever we resolve, it can only be on @ifindex. */
+	if (nm_platform_ip_route_get (platform,
+	                              AF_INET,
+	                              &vpn_gw,
+	                              ifindex,
+	                              (NMPObject **) &route_resolved) == NM_PLATFORM_ERROR_SUCCESS) {
+		const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE (route_resolved);
+
+		if (r->ifindex == ifindex) {
+			/* `ip route get` always resolves the route, even if the destination is unreachable.
+			 * In which case, it pretends the destination is directly reachable.
+			 *
+			 * So, only accept direct routes, if @vpn_gw is a private network. */
+			if (   nm_platform_route_table_is_main (r->table_coerced)
+			    && (   r->gateway
+			        || nm_utils_ip_is_site_local (AF_INET, &vpn_gw))) {
+				parent_gw = r->gateway;
+				has_parent_gw = TRUE;
+			}
+		}
+	}
 
-	route_metric = nm_device_get_ip4_route_metric (parent_device);
+	if (!has_parent_gw)
+		return;
+
+	route_metric = nm_device_get_route_metric (parent_device, AF_INET);
 
 	memset (&route, 0, sizeof (route));
+	route.ifindex = ifindex;
 	route.network = vpn_gw;
 	route.plen = 32;
 	route.gateway = parent_gw;
-	/* Set up a device route if the parent device has no gateway */
-	if (!parent_gw)
-		route.ifindex = nm_device_get_ip_ifindex (parent_device);
-
-	/* If the VPN gateway is in the same subnet as one of the parent device's
-	 * IP addresses, don't add the host route to it, but a route through the
-	 * parent device.
-	 */
-	if (nm_ip4_config_destination_is_direct (parent_config, vpn_gw, 32))
-		route.gateway = 0;
-
 	route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
 	route.metric = route_metric;
-	nm_ip4_config_add_route (config, &route);
+	nm_ip4_config_add_route (config, &route, NULL);
 
 	if (parent_gw) {
 		/* Ensure there's a route to the parent device's gateway through the
@@ -754,61 +775,83 @@ add_ip4_vpn_gateway_route (NMIP4Config *config, NMDevice *parent_device, guint32
 		route.plen = 32;
 		route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
 		route.metric = route_metric;
-
-		nm_ip4_config_add_route (config, &route);
+		nm_ip4_config_add_route (config, &route, NULL);
 	}
 }
 
 static void
 add_ip6_vpn_gateway_route (NMIP6Config *config,
                            NMDevice *parent_device,
-                           const struct in6_addr *vpn_gw)
+                           const struct in6_addr *vpn_gw,
+                           NMPlatform *platform)
 {
-	NMIP6Config *parent_config;
-	const struct in6_addr *parent_gw;
+	const struct in6_addr *parent_gw = NULL;
+	gboolean has_parent_gw = FALSE;
 	NMPlatformIP6Route route;
+	int ifindex;
 	guint32 route_metric;
+	nm_auto_nmpobj const NMPObject *route_resolved = NULL;
 
 	g_return_if_fail (NM_IS_IP6_CONFIG (config));
 	g_return_if_fail (NM_IS_DEVICE (parent_device));
 	g_return_if_fail (vpn_gw != NULL);
 
-	parent_config = nm_device_get_ip6_config (parent_device);
-	g_return_if_fail (parent_config != NULL);
-	parent_gw = nm_ip6_config_get_gateway (parent_config);
-	if (!parent_gw)
+	ifindex = nm_ip6_config_get_ifindex (config);
+
+	nm_assert (ifindex > 0);
+	nm_assert (ifindex == nm_device_get_ip_ifindex (parent_device));
+
+	/* Ask kernel how to reach @vpn_gw. We can only inject the route in
+	 * @parent_device, so whatever we resolve, it can only be on @ifindex. */
+	if (nm_platform_ip_route_get (platform,
+	                              AF_INET6,
+	                              vpn_gw,
+	                              ifindex,
+	                              (NMPObject **) &route_resolved) == NM_PLATFORM_ERROR_SUCCESS) {
+		const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE (route_resolved);
+
+		if (r->ifindex == ifindex) {
+			/* `ip route get` always resolves the route, even if the destination is unreachable.
+			 * In which case, it pretends the destination is directly reachable.
+			 *
+			 * So, only accept direct routes, if @vpn_gw is a private network. */
+			if (   nm_platform_route_table_is_main (r->table_coerced)
+			    && (   !IN6_IS_ADDR_UNSPECIFIED (&r->gateway)
+			        || nm_utils_ip_is_site_local (AF_INET6, &vpn_gw))) {
+				parent_gw = &r->gateway;
+				has_parent_gw = TRUE;
+			}
+		}
+	}
+
+	if (!has_parent_gw)
 		return;
 
-	route_metric = nm_device_get_ip6_route_metric (parent_device);
+	route_metric = nm_device_get_route_metric (parent_device, AF_INET6);
 
 	memset (&route, 0, sizeof (route));
+	route.ifindex = ifindex;
 	route.network = *vpn_gw;
 	route.plen = 128;
-	route.gateway = *parent_gw;
-
-	/* If the VPN gateway is in the same subnet as one of the parent device's
-	 * IP addresses, don't add the host route to it, but a route through the
-	 * parent device.
-	 */
-	if (nm_ip6_config_destination_is_direct (parent_config, vpn_gw, 128))
-		route.gateway = in6addr_any;
-
+	if (parent_gw)
+		route.gateway = *parent_gw;
 	route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
 	route.metric = route_metric;
-	nm_ip6_config_add_route (config, &route);
+	nm_ip6_config_add_route (config, &route, NULL);
 
 	/* Ensure there's a route to the parent device's gateway through the
 	 * parent device, since if the VPN claims the default route and the VPN
 	 * routes include a subnet that matches the parent device's subnet,
 	 * the parent device's gateway would get routed through the VPN and fail.
 	 */
-	memset (&route, 0, sizeof (route));
-	route.network = *parent_gw;
-	route.plen = 128;
-	route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-	route.metric = route_metric;
-
-	nm_ip6_config_add_route (config, &route);
+	if (parent_gw && !IN6_IS_ADDR_UNSPECIFIED (parent_gw)) {
+		memset (&route, 0, sizeof (route));
+		route.network = *parent_gw;
+		route.plen = 128;
+		route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
+		route.metric = route_metric;
+		nm_ip6_config_add_route (config, &route, NULL);
+	}
 }
 
 NMVpnConnection *
@@ -942,6 +985,7 @@ print_vpn_config (NMVpnConnection *self)
 	char *dns_domain = NULL;
 	guint32 num, i;
 	char buf[NM_UTILS_INET_ADDRSTRLEN];
+	NMDedupMultiIter ipconf_iter;
 
 	if (priv->ip4_external_gw) {
 		_LOGI ("Data: VPN Gateway: %s",
@@ -954,30 +998,26 @@ print_vpn_config (NMVpnConnection *self)
 	_LOGI ("Data: Tunnel Device: %s%s%s", NM_PRINT_FMT_QUOTE_STRING (priv->ip_iface));
 
 	if (priv->ip4_config) {
+		const NMPlatformIP4Route *route;
+
 		_LOGI ("Data: IPv4 configuration:");
 
-		address4 = nm_ip4_config_get_address (priv->ip4_config, 0);
+		address4 = nm_ip4_config_get_first_address (priv->ip4_config);
+		nm_assert (address4);
 
 		if (priv->ip4_internal_gw)
 			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet4_ntop (priv->ip4_internal_gw, NULL));
 		_LOGI ("Data:   Internal Address: %s", nm_utils_inet4_ntop (address4->address, NULL));
 		_LOGI ("Data:   Internal Prefix: %d", address4->plen);
 		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet4_ntop (address4->peer_address, NULL));
-		_LOGI ("Data:   Maximum Segment Size (MSS): %d", nm_ip4_config_get_mss (priv->ip4_config));
-
-		num = nm_ip4_config_get_num_routes (priv->ip4_config);
-		for (i = 0; i < num; i++) {
-			const NMPlatformIP4Route *route = nm_ip4_config_get_route (priv->ip4_config, i);
 
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->ip4_config, &route) {
 			_LOGI ("Data:   Static Route: %s/%d   Next Hop: %s",
 			       nm_utils_inet4_ntop (route->network, NULL),
 			       route->plen,
 			       nm_utils_inet4_ntop (route->gateway, buf));
 		}
 
-		_LOGI ("Data:   Forbid Default Route: %s",
-		       nm_ip4_config_get_never_default (priv->ip4_config) ? "yes" : "no");
-
 		num = nm_ip4_config_get_num_nameservers (priv->ip4_config);
 		for (i = 0; i < num; i++) {
 			_LOGI ("Data:   Internal DNS: %s",
@@ -992,30 +1032,26 @@ print_vpn_config (NMVpnConnection *self)
 		_LOGI ("Data: No IPv4 configuration");
 
 	if (priv->ip6_config) {
+		const NMPlatformIP6Route *route;
+
 		_LOGI ("Data: IPv6 configuration:");
 
-		address6 = nm_ip6_config_get_address (priv->ip6_config, 0);
+		address6 = nm_ip6_config_get_first_address (priv->ip6_config);
+		nm_assert (address6);
 
 		if (priv->ip6_internal_gw)
 			_LOGI ("Data:   Internal Gateway: %s", nm_utils_inet6_ntop (priv->ip6_internal_gw, NULL));
 		_LOGI ("Data:   Internal Address: %s", nm_utils_inet6_ntop (&address6->address, NULL));
 		_LOGI ("Data:   Internal Prefix: %d", address6->plen);
 		_LOGI ("Data:   Internal Point-to-Point Address: %s", nm_utils_inet6_ntop (&address6->peer_address, NULL));
-		_LOGI ("Data:   Maximum Segment Size (MSS): %d", nm_ip6_config_get_mss (priv->ip6_config));
-
-		num = nm_ip6_config_get_num_routes (priv->ip6_config);
-		for (i = 0; i < num; i++) {
-			const NMPlatformIP6Route *route = nm_ip6_config_get_route (priv->ip6_config, i);
 
+		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->ip6_config, &route) {
 			_LOGI ("Data:   Static Route: %s/%d   Next Hop: %s",
 			       nm_utils_inet6_ntop (&route->network, NULL),
 			       route->plen,
 			       nm_utils_inet6_ntop (&route->gateway, buf));
 		}
 
-		_LOGI ("Data:   Forbid Default Route: %s",
-		       nm_ip6_config_get_never_default (priv->ip6_config) ? "yes" : "no");
-
 		num = nm_ip6_config_get_num_nameservers (priv->ip6_config);
 		for (i = 0; i < num; i++) {
 			_LOGI ("Data:   Internal DNS: %s",
@@ -1042,42 +1078,45 @@ apply_parent_device_config (NMVpnConnection *self)
 {
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	NMDevice *parent_dev = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (self));
+	int ifindex;
 	NMIP4Config *vpn4_parent_config = NULL;
 	NMIP6Config *vpn6_parent_config = NULL;
 
-	if (priv->ip_ifindex > 0) {
-		if (priv->ip4_config)
-			vpn4_parent_config = nm_ip4_config_new (priv->ip_ifindex);
-		if (priv->ip6_config)
-			vpn6_parent_config = nm_ip6_config_new (priv->ip_ifindex);
-	} else {
-		int ifindex;
-
+	ifindex = nm_device_get_ip_ifindex (parent_dev);
+	if (ifindex > 0) {
 		/* If the VPN didn't return a network interface, it is a route-based
 		 * VPN (like kernel IPSec) and all IP addressing and routing should
 		 * be done on the parent interface instead.
 		 */
-
-		/* Also clear the gateway. We don't configure the gateway as part of the
-		 * vpn-config. Instead we tell NMDefaultRouteManager directly about the
-		 * default route. */
-		ifindex = nm_device_get_ip_ifindex (parent_dev);
 		if (priv->ip4_config) {
-			vpn4_parent_config = nm_ip4_config_new (ifindex);
-			nm_ip4_config_merge (vpn4_parent_config, priv->ip4_config, NM_IP_CONFIG_MERGE_NO_DNS);
+			vpn4_parent_config = nm_ip4_config_new (nm_netns_get_multi_idx (priv->netns),
+			                                        ifindex);
+			if (priv->ip_ifindex <= 0)
+				nm_ip4_config_merge (vpn4_parent_config, priv->ip4_config, NM_IP_CONFIG_MERGE_NO_DNS, 0);
 		}
 		if (priv->ip6_config) {
-			vpn6_parent_config = nm_ip6_config_new (ifindex);
-			nm_ip6_config_merge (vpn6_parent_config, priv->ip6_config, NM_IP_CONFIG_MERGE_NO_DNS);
-			nm_ip6_config_set_gateway (vpn6_parent_config, NULL);
+			vpn6_parent_config = nm_ip6_config_new (nm_netns_get_multi_idx (priv->netns),
+			                                        ifindex);
+			if (priv->ip_ifindex <= 0)
+				nm_ip6_config_merge (vpn6_parent_config, priv->ip6_config, NM_IP_CONFIG_MERGE_NO_DNS, 0);
 		}
 	}
 
 	/* Add any explicit route to the VPN gateway through the parent device */
-	if (vpn4_parent_config && priv->ip4_external_gw)
-		add_ip4_vpn_gateway_route (vpn4_parent_config, parent_dev, priv->ip4_external_gw);
-	if (vpn6_parent_config && priv->ip6_external_gw)
-		add_ip6_vpn_gateway_route (vpn6_parent_config, parent_dev, priv->ip6_external_gw);
+	if (   vpn4_parent_config
+	    && priv->ip4_external_gw) {
+		add_ip4_vpn_gateway_route (vpn4_parent_config,
+		                           parent_dev,
+		                           priv->ip4_external_gw,
+		                           nm_netns_get_platform (priv->netns));
+	}
+	if (   vpn6_parent_config
+	    && priv->ip6_external_gw) {
+		add_ip6_vpn_gateway_route (vpn6_parent_config,
+		                           parent_dev,
+		                           priv->ip6_external_gw,
+		                           nm_netns_get_platform (priv->netns));
+	}
 
 	nm_device_replace_vpn4_config (parent_dev, priv->last_device_ip4_config, vpn4_parent_config);
 	g_clear_object (&priv->last_device_ip4_config);
@@ -1093,25 +1132,32 @@ nm_vpn_connection_apply_config (NMVpnConnection *self)
 {
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
+	apply_parent_device_config (self);
+
 	if (priv->ip_ifindex > 0) {
 		nm_platform_link_set_up (nm_netns_get_platform (priv->netns), priv->ip_ifindex, NULL);
 
 		if (priv->ip4_config) {
+			nm_assert (priv->ip_ifindex == nm_ip4_config_get_ifindex (priv->ip4_config));
 			if (!nm_ip4_config_commit (priv->ip4_config,
 			                           nm_netns_get_platform (priv->netns),
-			                           nm_netns_get_route_manager (priv->netns),
-			                           priv->ip_ifindex,
-			                           TRUE,
-			                           nm_vpn_connection_get_ip4_route_metric (self)))
+			                           get_route_table (self, AF_INET, FALSE)
+			                             ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+			                             : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN))
 				return FALSE;
+			nm_platform_ip4_dev_route_blacklist_set (nm_netns_get_platform (priv->netns),
+			                                         priv->ip_ifindex,
+			                                         priv->ip4_dev_route_blacklist);
 		}
 
 		if (priv->ip6_config) {
+			nm_assert (priv->ip_ifindex == nm_ip6_config_get_ifindex (priv->ip6_config));
 			if (!nm_ip6_config_commit (priv->ip6_config,
 			                           nm_netns_get_platform (priv->netns),
-			                           nm_netns_get_route_manager (priv->netns),
-			                           priv->ip_ifindex,
-			                           TRUE))
+			                           get_route_table (self, AF_INET6, FALSE)
+			                             ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+			                             : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
+			                           NULL))
 				return FALSE;
 		}
 
@@ -1119,11 +1165,6 @@ nm_vpn_connection_apply_config (NMVpnConnection *self)
 			nm_platform_link_set_mtu (nm_netns_get_platform (priv->netns), priv->ip_ifindex, priv->mtu);
 	}
 
-	apply_parent_device_config (self);
-
-	nm_default_route_manager_ip4_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-	nm_default_route_manager_ip6_update_default_route (nm_netns_get_default_route_manager (priv->netns), self);
-
 	_LOGI ("VPN connection: (IP Config Get) complete");
 	if (priv->vpn_state < STATE_PRE_UP)
 		_set_vpn_state (self, STATE_PRE_UP, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE);
@@ -1387,19 +1428,47 @@ nm_vpn_connection_get_ip6_route_metric (NMVpnConnection *self)
 	return (route_metric >= 0) ? route_metric : NM_VPN_ROUTE_METRIC_DEFAULT;
 }
 
+static guint32
+get_route_table (NMVpnConnection *self,
+                 int addr_family,
+                 gboolean fallback_main)
+{
+	NMConnection *connection;
+	NMSettingIPConfig *s_ip;
+	guint32 route_table = 0;
+
+	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
+
+	connection = _get_applied_connection (self);
+	if (connection) {
+		if (addr_family == AF_INET)
+			s_ip = nm_connection_get_setting_ip4_config (connection);
+		else
+			s_ip = nm_connection_get_setting_ip6_config (connection);
+
+		if (s_ip)
+			route_table = nm_setting_ip_config_get_route_table  (s_ip);
+	}
+
+	return route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
+}
+
 static void
 nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 {
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	NMPlatformIP4Address address;
-	NMIP4Config *config;
 	guint32 u32, route_metric;
+	NMSettingIPConfig *s_ip;
+	guint32 route_table;
+	NMIP4Config *config;
 	GVariantIter *iter;
 	const char *str;
 	GVariant *v;
 	gboolean b;
-	guint i, n;
 	int ip_ifindex;
+	guint32 mss = 0;
+	gboolean never_default = FALSE;
 
 	g_return_if_fail (dict && g_variant_is_of_type (dict, G_VARIANT_TYPE_VARDICT));
 
@@ -1436,17 +1505,16 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 	if (ip_ifindex <= 0)
 		g_return_if_reached ();
 
-	config = nm_ip4_config_new (ip_ifindex);
+	config = nm_ip4_config_new (nm_netns_get_multi_idx (priv->netns),
+	                            ip_ifindex);
 	nm_ip4_config_set_dns_priority (config, NM_DNS_PRIORITY_DEFAULT_VPN);
 
 	memset (&address, 0, sizeof (address));
 	address.plen = 24;
 
 	/* Internal address of the VPN subnet's gateway */
-	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, "u", &u32)) {
+	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_INT_GATEWAY, "u", &u32))
 		priv->ip4_internal_gw = u32;
-		nm_ip4_config_set_gateway (config, priv->ip4_internal_gw);
-	}
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_ADDRESS, "u", &u32))
 		address.address = u32;
@@ -1482,7 +1550,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 	}
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_MSS, "u", &u32))
-		nm_ip4_config_set_mss (config, u32);
+		mss = u32;
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN, "&s", &str))
 		nm_ip4_config_add_domain (config, str);
@@ -1493,14 +1561,17 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 		g_variant_iter_free (iter);
 	}
 
+	route_table = get_route_table (self, AF_INET, TRUE);
 	route_metric = nm_vpn_connection_get_ip4_route_metric (self);
 
 	if (   g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_PRESERVE_ROUTES, "b", &b)
 	    && b) {
 		if (priv->ip4_config) {
-			n = nm_ip4_config_get_num_routes (priv->ip4_config);
-			for (i = 0; i < n; i++)
-				nm_ip4_config_add_route (config, nm_ip4_config_get_route (priv->ip4_config, i));
+			NMDedupMultiIter ipconf_iter;
+			const NMPlatformIP4Route *route;
+
+			nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->ip4_config, &route)
+				nm_ip4_config_add_route (config, route, NULL);
 		}
 	} else if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_ROUTES, "aau", &iter)) {
 		while (g_variant_iter_next (iter, "@au", &v)) {
@@ -1516,12 +1587,14 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 				g_variant_get_child (v, 1, "u", &plen);
 				g_variant_get_child (v, 2, "u", &route.gateway);
 				/* 4th item is unused route metric */
+				route.table_coerced = nm_platform_route_table_coerce (route_table);
 				route.metric = route_metric;
 				route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
 
 				if (plen > 32 || plen == 0)
 					break;
 				route.plen = plen;
+				route.network = nm_utils_ip4_address_clear_host_address (route.network, plen);
 
 				/* Ignore host routes to the VPN gateway since NM adds one itself
 				 * below.  Since NM knows more about the routing situation than
@@ -1529,7 +1602,7 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 				 * whatever the server provides.
 				 */
 				if (!(priv->ip4_external_gw && route.network == priv->ip4_external_gw && route.plen == 32))
-					nm_ip4_config_add_route (config, &route);
+					nm_ip4_config_add_route (config, &route, NULL);
 				break;
 			default:
 				break;
@@ -1540,13 +1613,36 @@ nm_vpn_connection_ip4_config_get (NMVpnConnection *self, GVariant *dict)
 	}
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT, "b", &b))
-		nm_ip4_config_set_never_default (config, b);
+		never_default = b;
 
 	/* Merge in user overrides from the NMConnection's IPv4 setting */
+	s_ip = nm_connection_get_setting_ip4_config (_get_applied_connection (self));
 	nm_ip4_config_merge_setting (config,
-	                             nm_connection_get_setting_ip4_config (_get_applied_connection (self)),
+	                             s_ip,
+	                             route_table,
 	                             route_metric);
 
+	if (   !never_default
+	    && !nm_setting_ip_config_get_never_default (s_ip)) {
+		const NMPlatformIP4Route r = {
+			.ifindex   = ip_ifindex,
+			.rt_source = NM_IP_CONFIG_SOURCE_VPN,
+			.gateway   = priv->ip4_internal_gw,
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric    = route_metric,
+			.mss       = mss,
+		};
+
+		nm_ip4_config_add_route (config, &r, NULL);
+	}
+
+	g_clear_pointer (&priv->ip4_dev_route_blacklist, g_ptr_array_unref);
+
+	nm_ip4_config_add_dependent_routes (config,
+	                                    route_table,
+	                                    nm_vpn_connection_get_ip4_route_metric (self),
+	                                    &priv->ip4_dev_route_blacklist);
+
 	if (priv->ip4_config) {
 		nm_ip4_config_replace (priv->ip4_config, config, NULL);
 		g_object_unref (config);
@@ -1565,13 +1661,16 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	NMPlatformIP6Address address;
 	guint32 u32, route_metric;
+	NMSettingIPConfig *s_ip;
+	guint32 route_table;
 	NMIP6Config *config;
 	GVariantIter *iter;
 	const char *str;
 	GVariant *v;
 	gboolean b;
-	guint i, n;
 	int ip_ifindex;
+	guint32 mss = 0;
+	gboolean never_default = FALSE;
 
 	g_return_if_fail (dict && g_variant_is_of_type (dict, G_VARIANT_TYPE_VARDICT));
 
@@ -1595,7 +1694,8 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 	if (ip_ifindex <= 0)
 		g_return_if_reached ();
 
-	config = nm_ip6_config_new (ip_ifindex);
+	config = nm_ip6_config_new (nm_netns_get_multi_idx (priv->netns),
+	                            ip_ifindex);
 	nm_ip6_config_set_dns_priority (config, NM_DNS_PRIORITY_DEFAULT_VPN);
 
 	memset (&address, 0, sizeof (address));
@@ -1605,7 +1705,6 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 	g_clear_pointer (&priv->ip6_internal_gw, g_free);
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP6_CONFIG_INT_GATEWAY, "@ay", &v)) {
 		priv->ip6_internal_gw = ip6_addr_dup_from_variant (v);
-		nm_ip6_config_set_gateway (config, priv->ip6_internal_gw);
 		g_variant_unref (v);
 	}
 
@@ -1644,7 +1743,7 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 	}
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP6_CONFIG_MSS, "u", &u32))
-		nm_ip6_config_set_mss (config, u32);
+		mss = u32;
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP6_CONFIG_DOMAIN, "&s", &str))
 		nm_ip6_config_add_domain (config, str);
@@ -1655,14 +1754,17 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 		g_variant_iter_free (iter);
 	}
 
+	route_table = get_route_table (self, AF_INET6, TRUE);
 	route_metric = nm_vpn_connection_get_ip6_route_metric (self);
 
 	if (   g_variant_lookup (dict, NM_VPN_PLUGIN_IP6_CONFIG_PRESERVE_ROUTES, "b", &b)
 	    && b) {
 		if (priv->ip6_config) {
-			n = nm_ip6_config_get_num_routes (priv->ip6_config);
-			for (i = 0; i < n; i++)
-				nm_ip6_config_add_route (config, nm_ip6_config_get_route (priv->ip6_config, i));
+			NMDedupMultiIter ipconf_iter;
+			const NMPlatformIP6Route *route;
+
+			nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->ip6_config, &route)
+				nm_ip6_config_add_route (config, route, NULL);
 		}
 	} else if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP6_CONFIG_ROUTES, "a(ayuayu)", &iter)) {
 		GVariant *dest, *next_hop;
@@ -1681,6 +1783,7 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 
 			route.plen = prefix;
 			ip6_addr_from_variant (next_hop, &route.gateway);
+			route.table_coerced = nm_platform_route_table_coerce (route_table);
 			route.metric = route_metric;
 			route.rt_source = NM_IP_CONFIG_SOURCE_VPN;
 
@@ -1690,7 +1793,7 @@ nm_vpn_connection_ip6_config_get (NMVpnConnection *self, GVariant *dict)
 			 * the server provides.
 			 */
 			if (!(priv->ip6_external_gw && IN6_ARE_ADDR_EQUAL (&route.network, priv->ip6_external_gw) && route.plen == 128))
-				nm_ip6_config_add_route (config, &route);
+				nm_ip6_config_add_route (config, &route, NULL);
 
 next:
 			g_variant_unref (dest);
@@ -1700,13 +1803,33 @@ next:
 	}
 
 	if (g_variant_lookup (dict, NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT, "b", &b))
-		nm_ip6_config_set_never_default (config, b);
+		never_default = b;
 
 	/* Merge in user overrides from the NMConnection's IPv6 setting */
+	s_ip = nm_connection_get_setting_ip6_config (_get_applied_connection (self));
 	nm_ip6_config_merge_setting (config,
-	                             nm_connection_get_setting_ip6_config (_get_applied_connection (self)),
+	                             s_ip,
+	                             route_table,
 	                             route_metric);
 
+	if (   !never_default
+	    && !nm_setting_ip_config_get_never_default (s_ip)) {
+		const NMPlatformIP6Route r = {
+			.ifindex   = ip_ifindex,
+			.rt_source = NM_IP_CONFIG_SOURCE_VPN,
+			.gateway   = *(priv->ip6_internal_gw ?: &in6addr_any),
+			.table_coerced = nm_platform_route_table_coerce (route_table),
+			.metric    = route_metric,
+			.mss       = mss,
+		};
+
+		nm_ip6_config_add_route (config, &r, NULL);
+	}
+
+	nm_ip6_config_add_dependent_routes (config,
+	                                    route_table,
+	                                    route_metric);
+
 	if (priv->ip6_config) {
 		nm_ip6_config_replace (priv->ip6_config, config, NULL);
 		g_object_unref (config);
@@ -2636,6 +2759,8 @@ dispose (GObject *object)
 
 	g_clear_pointer (&priv->connect_hash, g_variant_unref);
 
+	g_clear_pointer (&priv->ip4_dev_route_blacklist, g_ptr_array_unref);
+
 	nm_clear_g_source (&priv->connect_timeout);
 
 	dispatcher_cleanup (self);
diff --git a/src/vpn/nm-vpn-manager.c b/src/vpn/nm-vpn-manager.c
index 8e708d12..d0639168 100644
--- a/src/vpn/nm-vpn-manager.c
+++ b/src/vpn/nm-vpn-manager.c
@@ -255,7 +255,7 @@ nm_vpn_manager_init (NMVpnManager *self)
 		try_add_plugin (self, info->data);
 	g_slist_free_full (infos, g_object_unref);
 
-	priv->active_services = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	priv->active_services = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 }
 
 static void