about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
committerMichael Biebl <biebl@debian.org>2019-07-31 10:51:42 +0200
commit2e5fa45ddfbb5cffa1e78221f1cea706e2f298af (patch)
tree86f69d36c56de3074280456eddc854a780b8e04b /src
parent85563b7fc7ec2cd21e38debb9b28db342e2e8e7c (diff)
New upstream version 1.19.90 upstream/1.19.90
Diffstat (limited to 'src')
-rw-r--r--src/NetworkManagerUtils.c91
-rw-r--r--src/NetworkManagerUtils.h11
-rw-r--r--src/devices/adsl/nm-atm-manager.c1
-rw-r--r--src/devices/adsl/nm-device-adsl.c6
-rw-r--r--src/devices/adsl/nm-device-adsl.h1
-rw-r--r--src/devices/bluetooth/nm-bluez-common.h1
-rw-r--r--src/devices/bluetooth/nm-bluez-device.c16
-rw-r--r--src/devices/bluetooth/nm-bluez-device.h1
-rw-r--r--src/devices/bluetooth/nm-bluez-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.c1
-rw-r--r--src/devices/bluetooth/nm-bluez4-adapter.h1
-rw-r--r--src/devices/bluetooth/nm-bluez4-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.c1
-rw-r--r--src/devices/bluetooth/nm-bluez5-dun.h1
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.c1
-rw-r--r--src/devices/bluetooth/nm-bluez5-manager.h1
-rw-r--r--src/devices/bluetooth/nm-bt-error.c1
-rw-r--r--src/devices/bluetooth/nm-bt-error.h1
-rw-r--r--src/devices/bluetooth/nm-device-bt.c2
-rw-r--r--src/devices/bluetooth/nm-device-bt.h1
-rw-r--r--src/devices/nm-acd-manager.c1
-rw-r--r--src/devices/nm-acd-manager.h1
-rw-r--r--src/devices/nm-device-6lowpan.c1
-rw-r--r--src/devices/nm-device-bond.c42
-rw-r--r--src/devices/nm-device-bond.h1
-rw-r--r--src/devices/nm-device-bridge.c12
-rw-r--r--src/devices/nm-device-bridge.h1
-rw-r--r--src/devices/nm-device-dummy.c2
-rw-r--r--src/devices/nm-device-dummy.h1
-rw-r--r--src/devices/nm-device-ethernet-utils.c1
-rw-r--r--src/devices/nm-device-ethernet-utils.h1
-rw-r--r--src/devices/nm-device-ethernet.c64
-rw-r--r--src/devices/nm-device-ethernet.h1
-rw-r--r--src/devices/nm-device-factory.c1
-rw-r--r--src/devices/nm-device-factory.h1
-rw-r--r--src/devices/nm-device-generic.c1
-rw-r--r--src/devices/nm-device-generic.h1
-rw-r--r--src/devices/nm-device-infiniband.c33
-rw-r--r--src/devices/nm-device-infiniband.h1
-rw-r--r--src/devices/nm-device-ip-tunnel.c2
-rw-r--r--src/devices/nm-device-ip-tunnel.h1
-rw-r--r--src/devices/nm-device-logging.h1
-rw-r--r--src/devices/nm-device-macsec.c3
-rw-r--r--src/devices/nm-device-macsec.h1
-rw-r--r--src/devices/nm-device-macvlan.c2
-rw-r--r--src/devices/nm-device-macvlan.h1
-rw-r--r--src/devices/nm-device-ppp.c25
-rw-r--r--src/devices/nm-device-ppp.h1
-rw-r--r--src/devices/nm-device-private.h1
-rw-r--r--src/devices/nm-device-tun.c2
-rw-r--r--src/devices/nm-device-tun.h1
-rw-r--r--src/devices/nm-device-veth.c1
-rw-r--r--src/devices/nm-device-veth.h1
-rw-r--r--src/devices/nm-device-vlan.c2
-rw-r--r--src/devices/nm-device-vlan.h1
-rw-r--r--src/devices/nm-device-vxlan.c2
-rw-r--r--src/devices/nm-device-vxlan.h1
-rw-r--r--src/devices/nm-device-wireguard.c473
-rw-r--r--src/devices/nm-device-wpan.c1
-rw-r--r--src/devices/nm-device.c836
-rw-r--r--src/devices/nm-device.h16
-rw-r--r--src/devices/nm-lldp-listener.c1
-rw-r--r--src/devices/nm-lldp-listener.h1
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c1
-rw-r--r--src/devices/ovs/nm-device-ovs-port.c19
-rw-r--r--src/devices/ovs/nm-ovs-factory.c41
-rw-r--r--src/devices/ovs/nm-ovsdb.c105
-rw-r--r--src/devices/ovs/nm-ovsdb.h6
-rw-r--r--src/devices/team/nm-device-team.c18
-rw-r--r--src/devices/team/nm-device-team.h1
-rw-r--r--src/devices/team/nm-team-factory.c1
-rw-r--r--src/devices/tests/meson.build1
-rw-r--r--src/devices/tests/test-acd.c1
-rw-r--r--src/devices/tests/test-lldp.c3
-rw-r--r--src/devices/wifi/nm-device-iwd.c32
-rw-r--r--src/devices/wifi/nm-device-iwd.h1
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.c2
-rw-r--r--src/devices/wifi/nm-device-olpc-mesh.h2
-rw-r--r--src/devices/wifi/nm-device-wifi-p2p.c1
-rw-r--r--src/devices/wifi/nm-device-wifi.c166
-rw-r--r--src/devices/wifi/nm-device-wifi.h1
-rw-r--r--src/devices/wifi/nm-iwd-manager.c20
-rw-r--r--src/devices/wifi/nm-iwd-manager.h1
-rw-r--r--src/devices/wifi/nm-wifi-ap.c19
-rw-r--r--src/devices/wifi/nm-wifi-ap.h1
-rw-r--r--src/devices/wifi/nm-wifi-common.c1
-rw-r--r--src/devices/wifi/nm-wifi-common.h1
-rw-r--r--src/devices/wifi/nm-wifi-factory.c1
-rw-r--r--src/devices/wifi/nm-wifi-utils.c55
-rw-r--r--src/devices/wifi/nm-wifi-utils.h2
-rw-r--r--src/devices/wifi/tests/meson.build6
-rw-r--r--src/devices/wifi/tests/test-devices-wifi.c (renamed from src/devices/wifi/tests/test-general.c)2
-rw-r--r--src/devices/wwan/libnm-wwan.ver3
-rw-r--r--src/devices/wwan/nm-device-modem.c86
-rw-r--r--src/devices/wwan/nm-device-modem.h4
-rw-r--r--src/devices/wwan/nm-modem-broadband.c71
-rw-r--r--src/devices/wwan/nm-modem-broadband.h1
-rw-r--r--src/devices/wwan/nm-modem-manager.c1
-rw-r--r--src/devices/wwan/nm-modem-manager.h1
-rw-r--r--src/devices/wwan/nm-modem-ofono.c1
-rw-r--r--src/devices/wwan/nm-modem-ofono.h1
-rw-r--r--src/devices/wwan/nm-modem.c80
-rw-r--r--src/devices/wwan/nm-modem.h10
-rw-r--r--src/devices/wwan/nm-wwan-factory.c1
-rw-r--r--src/dhcp/nm-dhcp-client-logging.h1
-rw-r--r--src/dhcp/nm-dhcp-client.c108
-rw-r--r--src/dhcp/nm-dhcp-client.h41
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c1
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.h1
-rw-r--r--src/dhcp/nm-dhcp-dhclient.c3
-rw-r--r--src/dhcp/nm-dhcp-dhcpcanon.c14
-rw-r--r--src/dhcp/nm-dhcp-dhcpcd.c18
-rw-r--r--src/dhcp/nm-dhcp-helper-api.h1
-rw-r--r--src/dhcp/nm-dhcp-helper.c1
-rw-r--r--src/dhcp/nm-dhcp-listener.c17
-rw-r--r--src/dhcp/nm-dhcp-listener.h1
-rw-r--r--src/dhcp/nm-dhcp-manager.c73
-rw-r--r--src/dhcp/nm-dhcp-manager.h5
-rw-r--r--src/dhcp/nm-dhcp-nettools.c1278
-rw-r--r--src/dhcp/nm-dhcp-options.c278
-rw-r--r--src/dhcp/nm-dhcp-options.h204
-rw-r--r--src/dhcp/nm-dhcp-systemd.c425
-rw-r--r--src/dhcp/nm-dhcp-utils.c1
-rw-r--r--src/dhcp/nm-dhcp-utils.h1
-rw-r--r--src/dhcp/tests/test-dhcp-dhclient.c1
-rw-r--r--src/dhcp/tests/test-dhcp-utils.c1
-rw-r--r--src/dns/nm-dns-dnsmasq.c1
-rw-r--r--src/dns/nm-dns-dnsmasq.h1
-rw-r--r--src/dns/nm-dns-manager.c1
-rw-r--r--src/dns/nm-dns-manager.h1
-rw-r--r--src/dns/nm-dns-plugin.c1
-rw-r--r--src/dns/nm-dns-plugin.h1
-rw-r--r--src/dns/nm-dns-systemd-resolved.c76
-rw-r--r--src/dns/nm-dns-systemd-resolved.h1
-rw-r--r--src/dns/nm-dns-unbound.c1
-rw-r--r--src/dns/nm-dns-unbound.h1
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.c1
-rw-r--r--src/dnsmasq/nm-dnsmasq-manager.h1
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.c1
-rw-r--r--src/dnsmasq/nm-dnsmasq-utils.h1
-rw-r--r--src/dnsmasq/tests/test-dnsmasq-utils.c1
-rw-r--r--src/initrd/nm-initrd-generator.c2
-rw-r--r--src/initrd/nmi-cmdline-reader.c61
-rw-r--r--src/initrd/nmi-ibft-reader.c2
-rw-r--r--src/initrd/tests/meson.build1
-rw-r--r--src/initrd/tests/test-cmdline-reader.c48
-rw-r--r--src/initrd/tests/test-ibft-reader.c6
-rw-r--r--src/main-utils.c1
-rw-r--r--src/main-utils.h1
-rw-r--r--src/main.c47
-rw-r--r--src/meson.build7
-rw-r--r--src/ndisc/nm-fake-ndisc.c1
-rw-r--r--src/ndisc/nm-fake-ndisc.h1
-rw-r--r--src/ndisc/nm-lndp-ndisc.c1
-rw-r--r--src/ndisc/nm-lndp-ndisc.h1
-rw-r--r--src/ndisc/nm-ndisc-private.h1
-rw-r--r--src/ndisc/nm-ndisc.c1
-rw-r--r--src/ndisc/nm-ndisc.h1
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c1
-rw-r--r--src/ndisc/tests/test-ndisc-linux.c1
-rw-r--r--src/nm-act-request.c1
-rw-r--r--src/nm-act-request.h1
-rw-r--r--src/nm-active-connection.c13
-rw-r--r--src/nm-active-connection.h1
-rw-r--r--src/nm-audit-manager.c26
-rw-r--r--src/nm-audit-manager.h4
-rw-r--r--src/nm-auth-manager.c312
-rw-r--r--src/nm-auth-manager.h1
-rw-r--r--src/nm-auth-subject.c1
-rw-r--r--src/nm-auth-subject.h1
-rw-r--r--src/nm-auth-utils.c390
-rw-r--r--src/nm-auth-utils.h43
-rw-r--r--src/nm-checkpoint-manager.c1
-rw-r--r--src/nm-checkpoint-manager.h1
-rw-r--r--src/nm-checkpoint.c42
-rw-r--r--src/nm-checkpoint.h1
-rw-r--r--src/nm-config-data.c68
-rw-r--r--src/nm-config-data.h1
-rw-r--r--src/nm-config.c146
-rw-r--r--src/nm-config.h3
-rw-r--r--src/nm-connectivity.c126
-rw-r--r--src/nm-connectivity.h2
-rw-r--r--src/nm-core-utils.c44
-rw-r--r--src/nm-core-utils.h13
-rw-r--r--src/nm-dbus-manager.c80
-rw-r--r--src/nm-dbus-manager.h6
-rw-r--r--src/nm-dbus-object.c1
-rw-r--r--src/nm-dbus-object.h1
-rw-r--r--src/nm-dbus-utils.c1
-rw-r--r--src/nm-dbus-utils.h1
-rw-r--r--src/nm-dcb.c1
-rw-r--r--src/nm-dcb.h1
-rw-r--r--src/nm-dhcp4-config.c1
-rw-r--r--src/nm-dhcp4-config.h1
-rw-r--r--src/nm-dhcp6-config.c1
-rw-r--r--src/nm-dhcp6-config.h1
-rw-r--r--src/nm-dispatcher.c599
-rw-r--r--src/nm-dispatcher.h17
-rw-r--r--src/nm-firewall-manager.c1
-rw-r--r--src/nm-firewall-manager.h1
-rw-r--r--src/nm-hostname-manager.c16
-rw-r--r--src/nm-hostname-manager.h1
-rw-r--r--src/nm-iface-helper.c13
-rw-r--r--src/nm-ip4-config.c1
-rw-r--r--src/nm-ip4-config.h1
-rw-r--r--src/nm-ip6-config.c33
-rw-r--r--src/nm-ip6-config.h1
-rw-r--r--src/nm-keep-alive.c86
-rw-r--r--src/nm-keep-alive.h2
-rw-r--r--src/nm-logging.c110
-rw-r--r--src/nm-logging.h5
-rw-r--r--src/nm-manager.c588
-rw-r--r--src/nm-manager.h2
-rw-r--r--src/nm-netns.c17
-rw-r--r--src/nm-netns.h1
-rw-r--r--src/nm-pacrunner-manager.c779
-rw-r--r--src/nm-pacrunner-manager.h19
-rw-r--r--src/nm-policy.c33
-rw-r--r--src/nm-policy.h1
-rw-r--r--src/nm-proxy-config.c1
-rw-r--r--src/nm-proxy-config.h1
-rw-r--r--src/nm-rfkill-manager.c1
-rw-r--r--src/nm-rfkill-manager.h1
-rw-r--r--src/nm-session-monitor.c3
-rw-r--r--src/nm-session-monitor.h1
-rw-r--r--src/nm-sleep-monitor.c1
-rw-r--r--src/nm-sleep-monitor.h1
-rw-r--r--src/nm-test-utils-core.h1
-rw-r--r--src/nm-types.h1
-rw-r--r--src/platform/nm-fake-platform.c13
-rw-r--r--src/platform/nm-fake-platform.h1
-rw-r--r--src/platform/nm-linux-platform.c607
-rw-r--r--src/platform/nm-linux-platform.h1
-rw-r--r--src/platform/nm-netlink.c3
-rw-r--r--src/platform/nm-netlink.h1
-rw-r--r--src/platform/nm-platform-private.h1
-rw-r--r--src/platform/nm-platform-utils.c481
-rw-r--r--src/platform/nm-platform-utils.h1
-rw-r--r--src/platform/nm-platform.c413
-rw-r--r--src/platform/nm-platform.h345
-rw-r--r--src/platform/nmp-netns.c1
-rw-r--r--src/platform/nmp-netns.h1
-rw-r--r--src/platform/nmp-object.c10
-rw-r--r--src/platform/nmp-object.h120
-rw-r--r--src/platform/nmp-rules-manager.c118
-rw-r--r--src/platform/nmp-rules-manager.h5
-rw-r--r--src/platform/tests/meson.build29
-rw-r--r--src/platform/tests/monitor.c1
-rw-r--r--src/platform/tests/test-address.c1
-rw-r--r--src/platform/tests/test-cleanup.c1
-rw-r--r--src/platform/tests/test-common.c9
-rw-r--r--src/platform/tests/test-link.c155
-rw-r--r--src/platform/tests/test-nmp-object.c5
-rw-r--r--src/platform/tests/test-platform-general.c (renamed from src/platform/tests/test-general.c)28
-rw-r--r--src/platform/tests/test-route.c84
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.c28
-rw-r--r--src/platform/wifi/nm-wifi-utils-nl80211.h1
-rw-r--r--src/platform/wifi/nm-wifi-utils-private.h1
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.c1
-rw-r--r--src/platform/wifi/nm-wifi-utils-wext.h1
-rw-r--r--src/platform/wifi/nm-wifi-utils.c4
-rw-r--r--src/platform/wifi/nm-wifi-utils.h1
-rw-r--r--src/platform/wpan/nm-wpan-utils.c8
-rw-r--r--src/ppp/nm-ppp-manager-call.c1
-rw-r--r--src/ppp/nm-ppp-manager-call.h1
-rw-r--r--src/ppp/nm-ppp-manager.c1
-rw-r--r--src/ppp/nm-ppp-manager.h1
-rw-r--r--src/ppp/nm-ppp-plugin-api.h1
-rw-r--r--src/ppp/nm-ppp-status.h1
-rw-r--r--src/ppp/nm-pppd-plugin.c228
-rw-r--r--src/ppp/nm-pppd-plugin.h1
-rw-r--r--src/settings/nm-agent-manager.c117
-rw-r--r--src/settings/nm-agent-manager.h1
-rw-r--r--src/settings/nm-secret-agent.c45
-rw-r--r--src/settings/nm-secret-agent.h1
-rw-r--r--src/settings/nm-settings-connection.c1693
-rw-r--r--src/settings/nm-settings-connection.h273
-rw-r--r--src/settings/nm-settings-plugin.c252
-rw-r--r--src/settings/nm-settings-plugin.h187
-rw-r--r--src/settings/nm-settings-storage.c178
-rw-r--r--src/settings/nm-settings-storage.h122
-rw-r--r--src/settings/nm-settings-utils.c175
-rw-r--r--src/settings/nm-settings-utils.h110
-rw-r--r--src/settings/nm-settings.c3806
-rw-r--r--src/settings/nm-settings.h39
-rw-r--r--src/settings/plugins/ibft/meson.build41
-rw-r--r--src/settings/plugins/ibft/nms-ibft-connection.c77
-rw-r--r--src/settings/plugins/ibft/nms-ibft-connection.h41
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.c155
-rw-r--r--src/settings/plugins/ibft/nms-ibft-plugin.h36
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.c535
-rw-r--r--src/settings/plugins/ibft/nms-ibft-reader.h42
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-dns121
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-dns221
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-entry20
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway21
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr21
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-bad-record18
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-dhcp33
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-static35
-rwxr-xr-xsrc/settings/plugins/ibft/tests/iscsiadm-test-vlan19
-rw-r--r--src/settings/plugins/ibft/tests/meson.build16
-rw-r--r--src/settings/plugins/ibft/tests/test-ibft.c292
-rw-r--r--src/settings/plugins/ifcfg-rh/meson.build3
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.c213
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.h59
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h1
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c511
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h54
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c1374
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h25
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c428
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h9
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c198
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h93
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c52
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h5
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c262
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h7
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.c1
-rw-r--r--src/settings/plugins/ifcfg-rh/shvar.h1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-team-slave-enp31s0f1-142.cexpected1
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected11
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert13
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12bin0 -> 2848 bytes
-rw-r--r--src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c122
-rw-r--r--src/settings/plugins/ifupdown/meson.build1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-connection.c106
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-connection.h45
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h1
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.c43
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-parser.h16
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.c358
-rw-r--r--src/settings/plugins/ifupdown/nms-ifupdown-plugin.h26
-rw-r--r--src/settings/plugins/ifupdown/tests/test-ifupdown.c77
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c186
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.h44
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c1502
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.h38
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.c31
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-reader.h8
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.c277
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-storage.h264
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.c282
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-utils.h75
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.c173
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-writer.h14
-rw-r--r--src/settings/plugins/keyfile/tests/meson.build6
-rw-r--r--src/settings/plugins/keyfile/tests/test-keyfile-settings.c (renamed from src/settings/plugins/keyfile/tests/test-keyfile.c)61
-rw-r--r--src/settings/plugins/meson.build4
-rw-r--r--src/supplicant/nm-supplicant-config.c62
-rw-r--r--src/supplicant/nm-supplicant-config.h4
-rw-r--r--src/supplicant/nm-supplicant-interface.c156
-rw-r--r--src/supplicant/nm-supplicant-interface.h31
-rw-r--r--src/supplicant/nm-supplicant-manager.c44
-rw-r--r--src/supplicant/nm-supplicant-manager.h1
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.c12
-rw-r--r--src/supplicant/nm-supplicant-settings-verify.h1
-rw-r--r--src/supplicant/nm-supplicant-types.h1
-rw-r--r--src/supplicant/tests/test-supplicant-config.c3
-rw-r--r--src/systemd/meson.build3
-rw-r--r--src/systemd/nm-sd-utils-dhcp.c54
-rw-r--r--src/systemd/nm-sd-utils-dhcp.h34
-rw-r--r--src/systemd/nm-sd.c5
-rw-r--r--src/systemd/nm-sd.h1
-rw-r--r--src/systemd/sd-adapt-core/nm-sd-adapt-core.c1
-rw-r--r--src/systemd/sd-adapt-core/nm-sd-adapt-core.h2
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.c3
-rw-r--r--src/systemd/src/libsystemd-network/arp-util.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-identifier.c2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-internal.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-network.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-option.c3
-rw-r--r--src/systemd/src/libsystemd-network/dhcp-packet.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-internal.h2
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-network.c1
-rw-r--r--src/systemd/src/libsystemd-network/dhcp6-option.c1
-rw-r--r--src/systemd/src/libsystemd-network/lldp-internal.h2
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.c253
-rw-r--r--src/systemd/src/libsystemd-network/network-internal.h15
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c131
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-lease.c5
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-client.c2
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp6-lease.c17
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4acd.c4
-rw-r--r--src/systemd/src/libsystemd-network/sd-ipv4ll.c3
-rw-r--r--src/systemd/src/libsystemd/sd-event/event-util.c1
-rw-r--r--src/systemd/src/libsystemd/sd-event/sd-event.c22
-rw-r--r--src/systemd/src/libsystemd/sd-id128/id128-util.c1
-rw-r--r--src/systemd/src/libsystemd/sd-id128/sd-id128.c1
-rw-r--r--src/systemd/src/shared/dns-domain.c1387
-rw-r--r--src/systemd/src/shared/dns-domain.h113
-rw-r--r--src/systemd/src/systemd/sd-dhcp-client.h7
-rw-r--r--src/systemd/src/systemd/sd-event.h2
-rw-r--r--src/systemd/src/systemd/sd-id128.h1
-rw-r--r--src/tests/config/nm-test-device.c1
-rw-r--r--src/tests/config/nm-test-device.h1
-rw-r--r--src/tests/config/test-config.c1
-rw-r--r--src/tests/meson.build10
-rw-r--r--src/tests/test-core-with-expect.c (renamed from src/tests/test-general-with-expect.c)10
-rw-r--r--src/tests/test-core.c (renamed from src/tests/test-general.c)34
-rw-r--r--src/tests/test-dcb.c1
-rw-r--r--src/tests/test-ip4-config.c1
-rw-r--r--src/tests/test-ip6-config.c1
-rwxr-xr-xsrc/tests/test-secret-agent.py1
-rw-r--r--src/tests/test-systemd.c11
-rw-r--r--src/tests/test-utils.c1
-rw-r--r--src/tests/test-wired-defname.c1
-rw-r--r--src/vpn/nm-vpn-connection.c51
-rw-r--r--src/vpn/nm-vpn-connection.h1
-rw-r--r--src/vpn/nm-vpn-manager.c1
-rw-r--r--src/vpn/nm-vpn-manager.h1
416 files changed, 17461 insertions, 12192 deletions
diff --git a/src/NetworkManagerUtils.c b/src/NetworkManagerUtils.c
index 13cb1dae..1773405d 100644
--- a/src/NetworkManagerUtils.c
+++ b/src/NetworkManagerUtils.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -229,6 +228,7 @@ nm_utils_connection_has_default_route (NMConnection *connection,
 			goto out;
 	} else {
 		if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+		                          NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
 		                          NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
 			goto out;
 	}
@@ -249,13 +249,15 @@ nm_utils_complete_generic (NMPlatform *platform,
                            const char *preferred_id,
                            const char *fallback_id_prefix,
                            const char *ifname_prefix,
+                           const char *ifname,
                            gboolean default_enable_ipv6)
 {
 	NMSettingConnection *s_con;
-	char *id, *ifname;
+	char *id, *generated_ifname;
 	GHashTable *parameters;
 
 	g_assert (fallback_id_prefix);
+	g_return_if_fail (ifname_prefix == NULL || ifname == NULL);
 
 	s_con = nm_connection_get_setting_connection (connection);
 	if (!s_con) {
@@ -278,10 +280,12 @@ nm_utils_complete_generic (NMPlatform *platform,
 	}
 
 	/* Add an interface name, if requested */
-	if (ifname_prefix && !nm_setting_connection_get_interface_name (s_con)) {
-		ifname = get_new_connection_ifname (platform, existing_connections, ifname_prefix);
+	if (ifname) {
 		g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL);
-		g_free (ifname);
+	} else if (ifname_prefix && !nm_setting_connection_get_interface_name (s_con)) {
+		generated_ifname = get_new_connection_ifname (platform, existing_connections, ifname_prefix);
+		g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, generated_ifname, NULL);
+		g_free (generated_ifname);
 	}
 
 	/* Normalize */
@@ -919,29 +923,30 @@ nm_ip_routing_rule_to_platform (const NMIPRoutingRule *rule,
 	nm_assert (out_pl);
 
 	*out_pl = (NMPlatformRoutingRule) {
-		.addr_family = nm_ip_routing_rule_get_addr_family (rule),
-		.flags       = (  nm_ip_routing_rule_get_invert (rule)
-		                ? FIB_RULE_INVERT
-		                : 0),
-		.priority    = nm_ip_routing_rule_get_priority (rule),
-		.tos         = nm_ip_routing_rule_get_tos (rule),
-		.ip_proto    = nm_ip_routing_rule_get_ipproto (rule),
-		.fwmark      = nm_ip_routing_rule_get_fwmark (rule),
-		.fwmask      = nm_ip_routing_rule_get_fwmask (rule),
-		.sport_range = {
-		    .start   = nm_ip_routing_rule_get_source_port_start (rule),
-		    .end     = nm_ip_routing_rule_get_source_port_end (rule),
+		.addr_family                = nm_ip_routing_rule_get_addr_family (rule),
+		.flags                      = (  nm_ip_routing_rule_get_invert (rule)
+		                               ? FIB_RULE_INVERT
+		                               : 0),
+		.priority                   = nm_ip_routing_rule_get_priority (rule),
+		.tos                        = nm_ip_routing_rule_get_tos (rule),
+		.ip_proto                   = nm_ip_routing_rule_get_ipproto (rule),
+		.fwmark                     = nm_ip_routing_rule_get_fwmark (rule),
+		.fwmask                     = nm_ip_routing_rule_get_fwmask (rule),
+		.sport_range                = {
+		    .start                  = nm_ip_routing_rule_get_source_port_start (rule),
+		    .end                    = nm_ip_routing_rule_get_source_port_end (rule),
 		},
-		.dport_range = {
-		    .start   = nm_ip_routing_rule_get_destination_port_start (rule),
-		    .end     = nm_ip_routing_rule_get_destination_port_end (rule),
+		.dport_range                = {
+		    .start                  = nm_ip_routing_rule_get_destination_port_start (rule),
+		    .end                    = nm_ip_routing_rule_get_destination_port_end (rule),
 		},
-		.src         = *(nm_ip_routing_rule_get_from_bin (rule) ?: &nm_ip_addr_zero),
-		.dst         = *(nm_ip_routing_rule_get_to_bin (rule)   ?: &nm_ip_addr_zero),
-		.src_len     = nm_ip_routing_rule_get_from_len (rule),
-		.dst_len     = nm_ip_routing_rule_get_to_len (rule),
-		.action      = nm_ip_routing_rule_get_action (rule),
-		.table       = nm_ip_routing_rule_get_table (rule),
+		.src                        = *(nm_ip_routing_rule_get_from_bin (rule) ?: &nm_ip_addr_zero),
+		.dst                        = *(nm_ip_routing_rule_get_to_bin (rule)   ?: &nm_ip_addr_zero),
+		.src_len                    = nm_ip_routing_rule_get_from_len (rule),
+		.dst_len                    = nm_ip_routing_rule_get_to_len (rule),
+		.action                     = nm_ip_routing_rule_get_action (rule),
+		.table                      = nm_ip_routing_rule_get_table (rule),
+		.suppress_prefixlen_inverse = ~((guint32) nm_ip_routing_rule_get_suppress_prefixlength (rule)),
 	};
 
 	nm_ip_routing_rule_get_xifname_bin (rule, TRUE,  out_pl->iifname);
@@ -955,7 +960,8 @@ nm_ip_routing_rule_to_platform (const NMIPRoutingRule *rule,
 struct _NMShutdownWaitObjHandle {
 	CList lst;
 	GObject *watched_obj;
-	const char *msg_reason;
+	char *msg_reason;
+	bool free_msg_reason:1;
 };
 
 static CList _shutdown_waitobj_lst_head;
@@ -964,6 +970,8 @@ static void
 _shutdown_waitobj_unregister (NMShutdownWaitObjHandle *handle)
 {
 	c_list_unlink_stale (&handle->lst);
+	if (handle->free_msg_reason)
+		g_free (handle->msg_reason);
 	g_slice_free (NMShutdownWaitObjHandle, handle);
 
 	/* FIXME(shutdown): check whether the object list is empty, and
@@ -982,13 +990,14 @@ _shutdown_waitobj_cb (gpointer user_data,
 }
 
 /**
- * _nm_shutdown_wait_obj_register:
+ * nm_shutdown_wait_obj_register_full:
  * @watched_obj: the object to watch. Takes a weak reference on the object
  *   to be notified when it gets destroyed.
- * @msg_reason: a reason message, for debugging and logging purposes. It
- *   must be a static string. Or at least, be alive at least as long as
- *   @watched_obj. So, theoretically, if you need a dynamic @msg_reason,
- *   you could attach it to @watched_obj's user-data.
+ * @msg_reason: a reason message, for debugging and logging purposes.
+ * @free_msg_reason: if %TRUE, then ownership of @msg_reason will be taken
+ *   and the string will be freed with g_free() afterwards. If %FALSE,
+ *   the caller must ensure that @msg_reason string outlives the watched
+ *   objects (e.g. being a static strings).
  *
  * Keep track of @watched_obj until it gets destroyed. During shutdown,
  * we wait until all watched objects are destroyed. This is useful, if
@@ -1005,8 +1014,9 @@ _shutdown_waitobj_cb (gpointer user_data,
  *   once it gets destroyed.
  */
 NMShutdownWaitObjHandle *
-_nm_shutdown_wait_obj_register (GObject *watched_obj,
-                                const char *msg_reason)
+nm_shutdown_wait_obj_register_full (GObject *watched_obj,
+                                    char *msg_reason,
+                                    gboolean free_msg_reason)
 {
 	NMShutdownWaitObjHandle *handle;
 
@@ -1016,11 +1026,14 @@ _nm_shutdown_wait_obj_register (GObject *watched_obj,
 		c_list_init (&_shutdown_waitobj_lst_head);
 
 	handle = g_slice_new (NMShutdownWaitObjHandle);
-	handle->watched_obj = watched_obj;
-	/* we don't clone the string. We require the caller to use pass a static message.
-	 * If he really cannot do that, he should attach the string to the watched_obj
-	 * as user-data. */
-	handle->msg_reason = msg_reason;
+	*handle = (NMShutdownWaitObjHandle) {
+		/* depending on @free_msg_reason, we take ownership of @msg_reason.
+		 * In either case, we just reference the string without cloning
+		 * it. */
+		.watched_obj     = watched_obj,
+		.msg_reason      = msg_reason,
+		.free_msg_reason = free_msg_reason,
+	};
 	c_list_link_tail (&_shutdown_waitobj_lst_head, &handle->lst);
 	g_object_weak_ref (watched_obj, _shutdown_waitobj_cb, handle);
 	return handle;
diff --git a/src/NetworkManagerUtils.h b/src/NetworkManagerUtils.h
index 2b1f5ed8..778dc001 100644
--- a/src/NetworkManagerUtils.h
+++ b/src/NetworkManagerUtils.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -41,6 +40,7 @@ void nm_utils_complete_generic (NMPlatform *platform,
                                 const char *preferred_id,
                                 const char *fallback_id_prefix,
                                 const char *ifname_prefix,
+                                const char *ifname,
                                 gboolean default_enable_ipv6);
 
 typedef gboolean (NMUtilsMatchFilterFunc) (NMConnection *connection, gpointer user_data);
@@ -78,7 +78,7 @@ NMPlatformRoutingRule *nm_ip_routing_rule_to_platform (const NMIPRoutingRule *ru
  * SIGKILL.
  *
  * After NM_SHUTDOWN_TIMEOUT_MS, NetworkManager will however not yet terminate right
- * away. It iterates the mainloop for another NM_SHUTDOWN_TIMEOUT_MS_EXTRA. This
+ * away. It iterates the mainloop for another NM_SHUTDOWN_TIMEOUT_MS_WATCHDOG. This
  * should give time to reap the child process (after SIGKILL).
  *
  * So, the maximum time we should wait before sending SIGKILL should be at most
@@ -89,10 +89,11 @@ NMPlatformRoutingRule *nm_ip_routing_rule_to_platform (const NMIPRoutingRule *ru
 
 typedef struct _NMShutdownWaitObjHandle NMShutdownWaitObjHandle;
 
-NMShutdownWaitObjHandle *_nm_shutdown_wait_obj_register (GObject *watched_obj,
-                                                        const char *msg_reason);
+NMShutdownWaitObjHandle *nm_shutdown_wait_obj_register_full (GObject *watched_obj,
+                                                             char *msg_reason,
+                                                             gboolean free_msg_reason);
 
-#define nm_shutdown_wait_obj_register(watched_obj, msg_reason) _nm_shutdown_wait_obj_register((watched_obj), (""msg_reason""))
+#define nm_shutdown_wait_obj_register(watched_obj, msg_reason) nm_shutdown_wait_obj_register_full((watched_obj), (""msg_reason""), FALSE)
 
 void nm_shutdown_wait_obj_unregister (NMShutdownWaitObjHandle *handle);
 
diff --git a/src/devices/adsl/nm-atm-manager.c b/src/devices/adsl/nm-atm-manager.c
index f312b5a3..bc0bf5c6 100644
--- a/src/devices/adsl/nm-atm-manager.c
+++ b/src/devices/adsl/nm-atm-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/adsl/nm-device-adsl.c b/src/devices/adsl/nm-device-adsl.c
index b3b87dc7..59c87851 100644
--- a/src/devices/adsl/nm-device-adsl.c
+++ b/src/devices/adsl/nm-device-adsl.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -131,6 +130,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("ADSL connection"),
 	                           NULL,
+	                           NULL,
 	                           FALSE); /* No IPv6 yet by default */
 	return TRUE;
 }
@@ -487,9 +487,9 @@ act_stage3_ip4_config_start (NMDevice *device,
 
 	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_table (device, AF_INET),
 		                                     nm_device_get_route_metric (device, AF_INET),
-		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_table (device, AF_INET6),
 		                                     nm_device_get_route_metric (device, AF_INET6));
 	}
 
diff --git a/src/devices/adsl/nm-device-adsl.h b/src/devices/adsl/nm-device-adsl.h
index 351c8e7a..2d3a9fc7 100644
--- a/src/devices/adsl/nm-device-adsl.h
+++ b/src/devices/adsl/nm-device-adsl.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez-common.h b/src/devices/bluetooth/nm-bluez-common.h
index d72bea81..956375bb 100644
--- a/src/devices/bluetooth/nm-bluez-common.h
+++ b/src/devices/bluetooth/nm-bluez-common.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez-device.c b/src/devices/bluetooth/nm-bluez-device.c
index 377ee478..fb0a72c4 100644
--- a/src/devices/bluetooth/nm-bluez-device.c
+++ b/src/devices/bluetooth/nm-bluez-device.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -236,15 +235,18 @@ pan_connection_check_create (NMBluezDevice *self)
 	 * which then already finds the suitable connection in priv->connections. This is confusing,
 	 * so block the signal. check_emit_usable will succeed after this function call returns. */
 	g_signal_handlers_block_by_func (priv->settings, cp_connection_added, self);
-	added = nm_settings_add_connection (priv->settings, connection, FALSE, &error);
+	nm_settings_add_connection (priv->settings,
+	                            connection,
+	                            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+	                            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                            &added,
+	                            &error);
 	g_signal_handlers_unblock_by_func (priv->settings, cp_connection_added, self);
 
 	if (added) {
 		nm_assert (!g_slist_find (priv->connections, added));
 		nm_assert (connection_compatible (self, added));
-
-		nm_settings_connection_set_flags (added, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, TRUE);
-
 		priv->connections = g_slist_prepend (priv->connections, g_object_ref (added));
 		priv->pan_connection = added;
 		nm_log_dbg (LOGD_BT, "bluez[%s] added new Bluetooth connection for NAP device: '%s' (%s)", priv->path, id, uuid);
@@ -393,7 +395,7 @@ cp_connection_removed (NMSettings *settings,
 static void
 cp_connection_updated (NMSettings *settings,
                        NMSettingsConnection *sett_conn,
-                       gboolean by_user,
+                       guint update_reason_u,
                        NMBluezDevice *self)
 {
 	if (_internal_track_connection (self, sett_conn,
@@ -1226,7 +1228,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_settings_connection_get_id (to_delete), nm_settings_connection_get_uuid (to_delete));
-		nm_settings_connection_delete (to_delete, NULL);
+		nm_settings_connection_delete (to_delete, FALSE);
 		g_object_unref (to_delete);
 	}
 
diff --git a/src/devices/bluetooth/nm-bluez-device.h b/src/devices/bluetooth/nm-bluez-device.h
index d2d0beb0..e8dd2ced 100644
--- a/src/devices/bluetooth/nm-bluez-device.h
+++ b/src/devices/bluetooth/nm-bluez-device.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez-manager.c b/src/devices/bluetooth/nm-bluez-manager.c
index 9cd3a519..7577ab8b 100644
--- a/src/devices/bluetooth/nm-bluez-manager.c
+++ b/src/devices/bluetooth/nm-bluez-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.c b/src/devices/bluetooth/nm-bluez4-adapter.c
index 3a456deb..bd230e90 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.c
+++ b/src/devices/bluetooth/nm-bluez4-adapter.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez4-adapter.h b/src/devices/bluetooth/nm-bluez4-adapter.h
index 0aa4ff91..82bd2de8 100644
--- a/src/devices/bluetooth/nm-bluez4-adapter.h
+++ b/src/devices/bluetooth/nm-bluez4-adapter.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez4-manager.c b/src/devices/bluetooth/nm-bluez4-manager.c
index 82d995be..8327776d 100644
--- a/src/devices/bluetooth/nm-bluez4-manager.c
+++ b/src/devices/bluetooth/nm-bluez4-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez5-dun.c b/src/devices/bluetooth/nm-bluez5-dun.c
index ff3a0da9..04859f9a 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/devices/bluetooth/nm-bluez5-dun.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez5-dun.h b/src/devices/bluetooth/nm-bluez5-dun.h
index b75e4399..b605414b 100644
--- a/src/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/devices/bluetooth/nm-bluez5-dun.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez5-manager.c b/src/devices/bluetooth/nm-bluez5-manager.c
index be15d824..7bcd04e5 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.c
+++ b/src/devices/bluetooth/nm-bluez5-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bluez5-manager.h b/src/devices/bluetooth/nm-bluez5-manager.h
index 43694435..14ac842e 100644
--- a/src/devices/bluetooth/nm-bluez5-manager.h
+++ b/src/devices/bluetooth/nm-bluez5-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bt-error.c b/src/devices/bluetooth/nm-bt-error.c
index 66c65b6d..bc9e5aa4 100644
--- a/src/devices/bluetooth/nm-bt-error.c
+++ b/src/devices/bluetooth/nm-bt-error.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-bt-error.h b/src/devices/bluetooth/nm-bt-error.h
index fa3a9570..ed7ed90d 100644
--- a/src/devices/bluetooth/nm-bt-error.h
+++ b/src/devices/bluetooth/nm-bt-error.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/bluetooth/nm-device-bt.c b/src/devices/bluetooth/nm-device-bt.c
index e79251ce..0853c926 100644
--- a/src/devices/bluetooth/nm-device-bt.c
+++ b/src/devices/bluetooth/nm-device-bt.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -341,6 +340,7 @@ complete_connection (NMDevice *device,
 	                           preferred,
 	                           fallback_prefix,
 	                           NULL,
+	                           NULL,
 	                           is_dun ? FALSE : TRUE); /* No IPv6 yet for DUN */
 
 	setting_bdaddr = nm_setting_bluetooth_get_bdaddr (s_bt);
diff --git a/src/devices/bluetooth/nm-device-bt.h b/src/devices/bluetooth/nm-device-bt.h
index b90dbd2a..6c8a5773 100644
--- a/src/devices/bluetooth/nm-device-bt.h
+++ b/src/devices/bluetooth/nm-device-bt.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-acd-manager.c b/src/devices/nm-acd-manager.c
index f437ce3a..16d3df8a 100644
--- a/src/devices/nm-acd-manager.c
+++ b/src/devices/nm-acd-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-acd-manager.h b/src/devices/nm-acd-manager.h
index 08c0b798..53c88e4b 100644
--- a/src/devices/nm-acd-manager.h
+++ b/src/devices/nm-acd-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-6lowpan.c b/src/devices/nm-device-6lowpan.c
index 40103747..7a279431 100644
--- a/src/devices/nm-device-6lowpan.c
+++ b/src/devices/nm-device-6lowpan.c
@@ -190,6 +190,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("6LOWPAN connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_6lowpan = NM_SETTING_6LOWPAN (nm_connection_get_setting (connection, NM_TYPE_SETTING_6LOWPAN));
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index 37159fca..fd79348d 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -70,6 +69,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("Bond connection"),
 	                           "bond",
+	                           NULL,
 	                           TRUE);
 
 	s_bond = nm_connection_get_setting_bond (connection);
@@ -413,6 +413,12 @@ release_slave (NMDevice *device,
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
 	gboolean success;
 	gs_free char *address = NULL;
+	int ifindex_slave;
+
+	ifindex_slave = nm_device_get_ip_ifindex (slave);
+
+	if (ifindex_slave <= 0)
+		_LOGD (LOGD_TEAM, "bond slave %s is already released", nm_device_get_ip_iface (slave));
 
 	if (configure) {
 		/* When the last slave is released the bond MAC will be set to a random
@@ -420,16 +426,18 @@ release_slave (NMDevice *device,
 		 */
 		address = g_strdup (nm_device_get_hw_address (device));
 
-		success = nm_platform_link_release (nm_device_get_platform (device),
-		                                    nm_device_get_ip_ifindex (device),
-		                                    nm_device_get_ip_ifindex (slave));
-
-		if (success) {
-			_LOGI (LOGD_BOND, "released bond slave %s",
-			       nm_device_get_ip_iface (slave));
-		} else {
-			_LOGW (LOGD_BOND, "failed to release bond slave %s",
-			       nm_device_get_ip_iface (slave));
+		if (ifindex_slave > 0) {
+			success = nm_platform_link_release (nm_device_get_platform (device),
+			                                    nm_device_get_ip_ifindex (device),
+			                                    ifindex_slave);
+
+			if (success) {
+				_LOGI (LOGD_BOND, "released bond slave %s",
+				       nm_device_get_ip_iface (slave));
+			} else {
+				_LOGW (LOGD_BOND, "failed to release bond slave %s",
+				       nm_device_get_ip_iface (slave));
+			}
 		}
 
 		nm_platform_process_events (nm_device_get_platform (device));
@@ -440,11 +448,15 @@ release_slave (NMDevice *device,
 		 * IFF_UP), so we must bring it back up here to ensure carrier changes and
 		 * other state is noticed by the now-released slave.
 		 */
-		if (!nm_device_bring_up (slave, TRUE, NULL))
-			_LOGW (LOGD_BOND, "released bond slave could not be brought up.");
+		if (ifindex_slave > 0) {
+			if (!nm_device_bring_up (slave, TRUE, NULL))
+				_LOGW (LOGD_BOND, "released bond slave could not be brought up.");
+		}
 	} else {
-		_LOGI (LOGD_BOND, "bond slave %s was released",
-		       nm_device_get_ip_iface (slave));
+		if (ifindex_slave > 0) {
+			_LOGI (LOGD_BOND, "bond slave %s was released",
+			       nm_device_get_ip_iface (slave));
+		}
 	}
 }
 
diff --git a/src/devices/nm-device-bond.h b/src/devices/nm-device-bond.h
index b4e9b0e8..9448950b 100644
--- a/src/devices/nm-device-bond.h
+++ b/src/devices/nm-device-bond.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-bridge.c b/src/devices/nm-device-bridge.c
index 4275af91..ade9eb0d 100644
--- a/src/devices/nm-device-bridge.c
+++ b/src/devices/nm-device-bridge.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -153,6 +152,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("Bridge connection"),
 	                           "bridge",
+	                           NULL,
 	                           TRUE);
 
 	s_bridge = nm_connection_get_setting_bridge (connection);
@@ -625,11 +625,19 @@ release_slave (NMDevice *device,
 {
 	NMDeviceBridge *self = NM_DEVICE_BRIDGE (device);
 	gboolean success;
+	int ifindex_slave;
+
+	ifindex_slave = nm_device_get_ip_ifindex (slave);
+
+	if (ifindex_slave <= 0) {
+		_LOGD (LOGD_TEAM, "bond slave %s is already released", nm_device_get_ip_iface (slave));
+		return;
+	}
 
 	if (configure) {
 		success = nm_platform_link_release (nm_device_get_platform (device),
 		                                    nm_device_get_ip_ifindex (device),
-		                                    nm_device_get_ip_ifindex (slave));
+		                                    ifindex_slave);
 
 		if (success) {
 			_LOGI (LOGD_BRIDGE, "detached bridge port %s",
diff --git a/src/devices/nm-device-bridge.h b/src/devices/nm-device-bridge.h
index 44b4ed72..bc5ed04c 100644
--- a/src/devices/nm-device-bridge.h
+++ b/src/devices/nm-device-bridge.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-dummy.c b/src/devices/nm-device-dummy.c
index 47a45342..9c0c3035 100644
--- a/src/devices/nm-device-dummy.c
+++ b/src/devices/nm-device-dummy.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -66,6 +65,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("Dummy connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_dummy = nm_connection_get_setting_dummy (connection);
diff --git a/src/devices/nm-device-dummy.h b/src/devices/nm-device-dummy.h
index cc89a847..a380d366 100644
--- a/src/devices/nm-device-dummy.h
+++ b/src/devices/nm-device-dummy.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-ethernet-utils.c b/src/devices/nm-device-ethernet-utils.c
index b18eadfa..1d7a060e 100644
--- a/src/devices/nm-device-ethernet-utils.c
+++ b/src/devices/nm-device-ethernet-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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 of the License, or
diff --git a/src/devices/nm-device-ethernet-utils.h b/src/devices/nm-device-ethernet-utils.h
index 7e5c8b31..6355c4ec 100644
--- a/src/devices/nm-device-ethernet-utils.h
+++ b/src/devices/nm-device-ethernet-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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 of the License, or
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 170c7e28..3e84847e 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -561,7 +560,7 @@ build_supplicant_config (NMDeviceEthernet *self,
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
 
-	config = nm_supplicant_config_new (FALSE, FALSE);
+	config = nm_supplicant_config_new (FALSE, FALSE, FALSE, FALSE);
 
 	security = nm_connection_get_setting_802_1x (connection);
 	if (!nm_supplicant_config_add_setting_8021x (config, security, con_uuid, mtu, TRUE, error)) {
@@ -1008,9 +1007,9 @@ pppoe_stage3_ip4_config_start (NMDeviceEthernet *self, NMDeviceStateReason *out_
 
 	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_table (device, AF_INET),
 		                                     nm_device_get_route_metric (device, AF_INET),
-		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_table (device, AF_INET6),
 		                                     nm_device_get_route_metric (device, AF_INET6));
 	}
 
@@ -1390,9 +1389,6 @@ complete_connection (NMDevice *device,
 {
 	NMSettingWired *s_wired;
 	NMSettingPppoe *s_pppoe;
-	const char *setting_mac;
-	const char *perm_hw_addr;
-	gboolean perm_hw_addr_is_fake;
 
 	s_pppoe = nm_connection_get_setting_pppoe (connection);
 
@@ -1402,6 +1398,12 @@ complete_connection (NMDevice *device,
 	if (s_pppoe && !nm_setting_verify (NM_SETTING (s_pppoe), NULL, error))
 		return FALSE;
 
+	s_wired = nm_connection_get_setting_wired (connection);
+	if (!s_wired) {
+		s_wired = (NMSettingWired *) nm_setting_wired_new ();
+		nm_connection_add_setting (connection, NM_SETTING (s_wired));
+	}
+
 	/* Default to an ethernet-only connection, but if a PPPoE setting was given
 	 * then PPPoE should be our connection type.
 	 */
@@ -1412,34 +1414,9 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           s_pppoe ? _("PPPoE connection") : _("Wired connection"),
 	                           NULL,
+	                           nm_setting_wired_get_mac_address (s_wired) ? NULL : nm_device_get_iface (device),
 	                           s_pppoe ? FALSE : TRUE); /* No IPv6 by default yet for PPPoE */
 
-	s_wired = nm_connection_get_setting_wired (connection);
-	if (!s_wired) {
-		s_wired = (NMSettingWired *) nm_setting_wired_new ();
-		nm_connection_add_setting (connection, NM_SETTING (s_wired));
-	}
-
-	perm_hw_addr = nm_device_get_permanent_hw_address_full (device, TRUE, &perm_hw_addr_is_fake);
-	if (perm_hw_addr && !perm_hw_addr_is_fake) {
-		setting_mac = nm_setting_wired_get_mac_address (s_wired);
-		if (setting_mac) {
-			/* Make sure the setting MAC (if any) matches the device's permanent MAC */
-			if (!nm_utils_hwaddr_matches (setting_mac, -1, perm_hw_addr, -1)) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("connection does not match device"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MAC_ADDRESS);
-				return FALSE;
-			}
-		} else {
-			g_object_set (G_OBJECT (s_wired),
-			              NM_SETTING_WIRED_MAC_ADDRESS, perm_hw_addr,
-			              NULL);
-		}
-	}
-
 	return TRUE;
 }
 
@@ -1452,17 +1429,14 @@ new_default_connection (NMDevice *self)
 	gs_unref_hashtable GHashTable *existing_ids = NULL;
 	struct udev_device *dev;
 	const char *perm_hw_addr;
+	const char *iface;
 	const char *uprop = "0";
 	gs_free char *defname = NULL;
 	gs_free char *uuid = NULL;
 	guint i, n_connections;
 
-	if (nm_config_get_no_auto_default_for_device (nm_config_get (), self))
-		return NULL;
-
 	perm_hw_addr = nm_device_get_permanent_hw_address (self);
-	if (!perm_hw_addr)
-		return NULL;
+	iface = nm_device_get_iface (self);
 
 	connection = nm_simple_connection_new ();
 	setting = nm_setting_connection_new ();
@@ -1483,7 +1457,7 @@ new_default_connection (NMDevice *self)
 	uuid = _nm_utils_uuid_generate_from_strings ("default-wired",
 	                                             nm_utils_machine_id_str (),
 	                                             defname,
-	                                             perm_hw_addr,
+	                                             perm_hw_addr ?: iface,
 	                                             NULL);
 
 	g_object_set (setting,
@@ -1493,13 +1467,9 @@ new_default_connection (NMDevice *self)
 	              NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN,
 	              NM_SETTING_CONNECTION_UUID, uuid,
 	              NM_SETTING_CONNECTION_TIMESTAMP, (guint64) time (NULL),
+	              NM_SETTING_CONNECTION_INTERFACE_NAME, iface,
 	              NULL);
 
-	/* Lock the connection to the device */
-	setting = nm_setting_wired_new ();
-	g_object_set (setting, NM_SETTING_WIRED_MAC_ADDRESS, perm_hw_addr, NULL);
-	nm_connection_add_setting (connection, setting);
-
 	/* Check if we should create a Link-Local only connection */
 	dev = nm_platform_link_get_udev_device (nm_device_get_platform (NM_DEVICE (self)), nm_device_get_ip_ifindex (self));
 	if (dev)
@@ -1575,11 +1545,11 @@ update_connection (NMDevice *device, NMConnection *connection)
 		g_object_set (s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, priv->subchannels_dbus, NULL);
 	if (priv->s390_nettype)
 		g_object_set (s_wired, NM_SETTING_WIRED_S390_NETTYPE, priv->s390_nettype, NULL);
+
+	_nm_setting_wired_clear_s390_options (s_wired);
 	g_hash_table_iter_init (&iter, priv->s390_options);
-	while (g_hash_table_iter_next (&iter, &key, &value)) {
+	while (g_hash_table_iter_next (&iter, &key, &value))
 		nm_setting_wired_add_s390_option (s_wired, (const char *) key, (const char *) value);
-	}
-
 }
 
 static void
diff --git a/src/devices/nm-device-ethernet.h b/src/devices/nm-device-ethernet.h
index 22fae293..d7de519c 100644
--- a/src/devices/nm-device-ethernet.h
+++ b/src/devices/nm-device-ethernet.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-factory.c b/src/devices/nm-device-factory.c
index 30aca038..c0ec1d2e 100644
--- a/src/devices/nm-device-factory.c
+++ b/src/devices/nm-device-factory.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-factory.h b/src/devices/nm-device-factory.h
index 33b596e6..7fe371ad 100644
--- a/src/devices/nm-device-factory.h
+++ b/src/devices/nm-device-factory.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-generic.c b/src/devices/nm-device-generic.c
index d00aa93b..39232acf 100644
--- a/src/devices/nm-device-generic.c
+++ b/src/devices/nm-device-generic.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-generic.h b/src/devices/nm-device-generic.h
index 42442180..9c59851a 100644
--- a/src/devices/nm-device-generic.h
+++ b/src/devices/nm-device-generic.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 4db7d8a7..89db2af2 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -161,8 +160,12 @@ complete_connection (NMDevice *device,
                      GError **error)
 {
 	NMSettingInfiniband *s_infiniband;
-	const char *setting_mac;
-	const char *hw_address;
+
+	s_infiniband = nm_connection_get_setting_infiniband (connection);
+	if (!s_infiniband) {
+		s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new ();
+		nm_connection_add_setting (connection, NM_SETTING (s_infiniband));
+	}
 
 	nm_utils_complete_generic (nm_device_get_platform (device),
 	                           connection,
@@ -171,31 +174,9 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("InfiniBand connection"),
 	                           NULL,
+	                           nm_setting_infiniband_get_mac_address (s_infiniband) ? NULL : nm_device_get_iface (device),
 	                           TRUE);
 
-	s_infiniband = nm_connection_get_setting_infiniband (connection);
-	if (!s_infiniband) {
-		s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new ();
-		nm_connection_add_setting (connection, NM_SETTING (s_infiniband));
-	}
-
-	setting_mac = nm_setting_infiniband_get_mac_address (s_infiniband);
-	hw_address = nm_device_get_permanent_hw_address (device);
-	if (setting_mac) {
-		/* Make sure the setting MAC (if any) matches the device's MAC */
-		if (!nm_utils_hwaddr_matches (setting_mac, -1, hw_address, -1)) {
-			g_set_error_literal (error,
-			                     NM_CONNECTION_ERROR,
-			                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-			                     _("connection does not match device"));
-			g_prefix_error (error, "%s.%s: ", NM_SETTING_INFINIBAND_SETTING_NAME, NM_SETTING_INFINIBAND_MAC_ADDRESS);
-			return FALSE;
-		}
-	} else {
-		/* Lock the connection to this device by default */
-		g_object_set (G_OBJECT (s_infiniband), NM_SETTING_INFINIBAND_MAC_ADDRESS, hw_address, NULL);
-	}
-
 	if (!nm_setting_infiniband_get_transport_mode (s_infiniband))
 		g_object_set (G_OBJECT (s_infiniband), NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
 
diff --git a/src/devices/nm-device-infiniband.h b/src/devices/nm-device-infiniband.h
index 719c9cc9..55d0aadc 100644
--- a/src/devices/nm-device-infiniband.h
+++ b/src/devices/nm-device-infiniband.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index 96275947..ede4487e 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -421,6 +420,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("IP tunnel connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_ip_tunnel = nm_connection_get_setting_ip_tunnel (connection);
diff --git a/src/devices/nm-device-ip-tunnel.h b/src/devices/nm-device-ip-tunnel.h
index 1109ace4..e38d36eb 100644
--- a/src/devices/nm-device-ip-tunnel.h
+++ b/src/devices/nm-device-ip-tunnel.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-logging.h b/src/devices/nm-device-logging.h
index f0c7e591..c45a0552 100644
--- a/src/devices/nm-device-logging.h
+++ b/src/devices/nm-device-logging.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index 1a6b64a4..e3e3a895 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -225,7 +224,7 @@ build_supplicant_config (NMDeviceMacsec *self, GError **error)
 	mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
 	                                nm_device_get_ifindex (NM_DEVICE (self)));
 
-	config = nm_supplicant_config_new (FALSE, FALSE);
+	config = nm_supplicant_config_new (FALSE, FALSE, FALSE, FALSE);
 
 	s_macsec = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
 
diff --git a/src/devices/nm-device-macsec.h b/src/devices/nm-device-macsec.h
index 23e9d2c9..e6919d4c 100644
--- a/src/devices/nm-device-macsec.h
+++ b/src/devices/nm-device-macsec.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index aa2a0ac0..709f98da 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -364,6 +363,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("MACVLAN connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_macvlan = nm_connection_get_setting_macvlan (connection);
diff --git a/src/devices/nm-device-macvlan.h b/src/devices/nm-device-macvlan.h
index c7d4be7c..9e76f5c9 100644
--- a/src/devices/nm-device-macvlan.h
+++ b/src/devices/nm-device-macvlan.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 3c310146..f2e68f73 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -117,6 +116,25 @@ ppp_ip4_config (NMPPPManager *ppp_manager,
 	}
 }
 
+static gboolean
+check_connection_compatible (NMDevice *device, NMConnection *connection, GError **error)
+{
+	NMSettingPppoe *s_pppoe;
+
+	if (!NM_DEVICE_CLASS (nm_device_ppp_parent_class)->check_connection_compatible (device, connection, error))
+		return FALSE;
+
+	s_pppoe = nm_connection_get_setting_pppoe (connection);
+	if (   !s_pppoe
+	    || !nm_setting_pppoe_get_parent (s_pppoe)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+		                            "the connection doesn't specify a PPPoE parent interface");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 static NMActStageReturn
 act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -140,9 +158,9 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	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_table (device, AF_INET),
 		                                     nm_device_get_route_metric (device, AF_INET),
-		                                     nm_device_get_route_table (device, AF_INET6, TRUE),
+		                                     nm_device_get_route_table (device, AF_INET6),
 		                                     nm_device_get_route_metric (device, AF_INET6));
 	}
 
@@ -280,6 +298,7 @@ nm_device_ppp_class_init (NMDevicePppClass *klass)
 
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->create_and_realize = create_and_realize;
 	device_class->deactivate = deactivate;
 	device_class->get_generic_capabilities = get_generic_capabilities;
diff --git a/src/devices/nm-device-ppp.h b/src/devices/nm-device-ppp.h
index aaa18b9b..097f4b97 100644
--- a/src/devices/nm-device-ppp.h
+++ b/src/devices/nm-device-ppp.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index dd98e92f..0a414af9 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index 90360c9e..afe83f50 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -156,6 +155,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("TUN connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_tun = nm_connection_get_setting_tun (connection);
diff --git a/src/devices/nm-device-tun.h b/src/devices/nm-device-tun.h
index b0d309bb..f665b942 100644
--- a/src/devices/nm-device-tun.h
+++ b/src/devices/nm-device-tun.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-veth.c b/src/devices/nm-device-veth.c
index d7a59bae..0d6425d4 100644
--- a/src/devices/nm-device-veth.c
+++ b/src/devices/nm-device-veth.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-veth.h b/src/devices/nm-device-veth.h
index 53ccf925..07217ff9 100644
--- a/src/devices/nm-device-veth.h
+++ b/src/devices/nm-device-veth.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index ace6a24b..0467a6e4 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -399,6 +398,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("VLAN connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_vlan = nm_connection_get_setting_vlan (connection);
diff --git a/src/devices/nm-device-vlan.h b/src/devices/nm-device-vlan.h
index 375e8fa4..c463db58 100644
--- a/src/devices/nm-device-vlan.h
+++ b/src/devices/nm-device-vlan.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-vxlan.c b/src/devices/nm-device-vxlan.c
index fc73c099..1a700ba4 100644
--- a/src/devices/nm-device-vxlan.c
+++ b/src/devices/nm-device-vxlan.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -368,6 +367,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("VXLAN connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_vxlan = nm_connection_get_setting_vxlan (connection);
diff --git a/src/devices/nm-device-vxlan.h b/src/devices/nm-device-vxlan.h
index 511b7156..dd14d910 100644
--- a/src/devices/nm-device-vxlan.h
+++ b/src/devices/nm-device-vxlan.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index f8c7d1c3..d36573cf 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -21,12 +21,16 @@
 
 #include "nm-device-wireguard.h"
 
+#include <linux/rtnetlink.h>
+#include <linux/fib_rules.h>
+
 #include "nm-setting-wireguard.h"
 #include "nm-core-internal.h"
 #include "nm-glib-aux/nm-secret-utils.h"
 #include "nm-device-private.h"
 #include "platform/nm-platform.h"
 #include "platform/nmp-object.h"
+#include "platform/nmp-rules-manager.h"
 #include "nm-device-factory.h"
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
@@ -134,10 +138,21 @@ typedef struct {
 	GHashTable *peers;
 
 	gint64 resolve_next_try_at;
-	guint  resolve_next_try_id;
-
 	gint64 link_config_last_at;
+
+	guint  resolve_next_try_id;
 	guint  link_config_delayed_id;
+
+	guint32 auto_default_route_fwmark;
+
+	guint32 auto_default_route_priority;
+
+	bool auto_default_route_enabled_4:1;
+	bool auto_default_route_enabled_6:1;
+	bool auto_default_route_initialized:1;
+	bool auto_default_route_refresh:1;
+	bool auto_default_route_priority_initialized:1;
+
 } NMDeviceWireGuardPrivate;
 
 struct _NMDeviceWireGuard {
@@ -177,6 +192,394 @@ NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_link_config_mode_to_string, LinkConfigMode,
 
 /*****************************************************************************/
 
+static void
+_auto_default_route_get_enabled (NMSettingWireGuard *s_wg,
+                                 NMConnection *connection,
+                                 gboolean *out_enabled_v4,
+                                 gboolean *out_enabled_v6)
+{
+	NMTernary enabled_v4;
+	NMTernary enabled_v6;
+
+	enabled_v4 = nm_setting_wireguard_get_ip4_auto_default_route (s_wg);
+	enabled_v6 = nm_setting_wireguard_get_ip6_auto_default_route (s_wg);
+
+	if (enabled_v4 == NM_TERNARY_DEFAULT) {
+		if (nm_setting_ip_config_get_never_default (nm_connection_get_setting_ip_config (connection, AF_INET)))
+			enabled_v4 = FALSE;
+	}
+	if (enabled_v6 == NM_TERNARY_DEFAULT) {
+		if (nm_setting_ip_config_get_never_default (nm_connection_get_setting_ip_config (connection, AF_INET6)))
+			enabled_v6 = FALSE;
+	}
+
+	if (   enabled_v4 == NM_TERNARY_DEFAULT
+	    || enabled_v6 == NM_TERNARY_DEFAULT) {
+		guint i, n_peers;
+
+		n_peers = nm_setting_wireguard_get_peers_len (s_wg);
+		for (i = 0; i < n_peers; i++) {
+			NMWireGuardPeer *peer = nm_setting_wireguard_get_peer (s_wg, i);
+			guint n_aips;
+			guint j;
+
+			n_aips = nm_wireguard_peer_get_allowed_ips_len (peer);
+			for (j = 0; j < n_aips; j++) {
+				const char *aip;
+				gboolean valid;
+				int prefix;
+				int addr_family;
+
+				aip = nm_wireguard_peer_get_allowed_ip (peer, j, &valid);
+				if (!valid)
+					continue;
+				if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
+				                                       aip,
+				                                       &addr_family,
+				                                       NULL,
+				                                       &prefix))
+					continue;
+				if (prefix != 0)
+					continue;
+
+				if (addr_family == AF_INET) {
+					if (enabled_v4 == NM_TERNARY_DEFAULT) {
+						enabled_v4 = TRUE;
+						if (enabled_v6 != NM_TERNARY_DEFAULT)
+							goto done;
+					}
+				} else {
+					if (enabled_v6 == NM_TERNARY_DEFAULT) {
+						enabled_v6 = TRUE;
+						if (enabled_v4 != NM_TERNARY_DEFAULT)
+							goto done;
+					}
+				}
+			}
+		}
+done:
+		;
+	}
+
+	*out_enabled_v4 = (enabled_v4 == TRUE);
+	*out_enabled_v6 = (enabled_v6 == TRUE);
+}
+
+static guint32
+_auto_default_route_find_unused_table (NMPlatform *platform)
+{
+	guint32 table;
+	int is_ipv4;
+
+	for (table = 51820; TRUE; table++) {
+		const NMDedupMultiHeadEntry *head_entry;
+		const guint32 table_coerced = nm_platform_route_table_coerce (table);
+		NMDedupMultiIter iter;
+		const NMPObject *plobj;
+
+		/* find a table/fwmark that is not yet in use. */
+
+		for (is_ipv4 = 0; is_ipv4 < 2; is_ipv4++) {
+			head_entry = nm_platform_lookup_object (platform,
+			                                          is_ipv4
+			                                        ? NMP_OBJECT_TYPE_IP4_ROUTE
+			                                        : NMP_OBJECT_TYPE_IP6_ROUTE,
+			                                        -1);
+			nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+				if (NMP_OBJECT_CAST_IP_ROUTE (plobj)->table_coerced == table_coerced)
+					goto try_next_table;
+			}
+		}
+
+		head_entry = nm_platform_lookup_object_by_addr_family (platform,
+		                                                       NMP_OBJECT_TYPE_ROUTING_RULE,
+		                                                       AF_UNSPEC);
+		nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+			const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE (plobj);
+
+			if (rr->fwmark == table)
+				goto try_next_table;
+		}
+
+		head_entry = nm_platform_lookup_obj_type (platform, NMP_OBJECT_TYPE_LINK);
+		nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+			const NMPObject *lnk_wg;
+
+			if (plobj->link.type != NM_LINK_TYPE_WIREGUARD)
+				continue;
+
+			lnk_wg = plobj->_link.netlink.lnk;
+
+			if (!lnk_wg)
+				continue;
+
+			if (NMP_OBJECT_GET_TYPE (lnk_wg) != NMP_OBJECT_TYPE_LNK_WIREGUARD)
+				continue;
+
+			if (NMP_OBJECT_CAST_LNK_WIREGUARD (lnk_wg)->fwmark == table)
+				goto try_next_table;
+		}
+
+		return table;
+try_next_table:
+		;
+	}
+}
+
+#define PRIO_WIDTH ((guint32) 2)
+
+static gboolean
+_auto_default_route_find_priority_exists (const NMDedupMultiHeadEntry *head_entry,
+                                          guint32 priority)
+{
+	NMDedupMultiIter iter;
+	const NMPObject *plobj;
+
+	nmp_cache_iter_for_each (&iter, head_entry, &plobj) {
+		const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE (plobj);
+
+		/* we don't differenciate between IPv4 vs. IPv6. There should be no
+		 * conflicting rules with the same priority. */
+		if (   rr->priority >= priority
+		    && rr->priority < priority + PRIO_WIDTH)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static guint32
+_auto_default_route_find_priority (NMPlatform *platform,
+                                   const char *uuid)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	guint64 rnd_seed;
+	const guint32 PRIME_NUMBER = 1111567573u;
+	const guint32 RANGE_TOP = ((32766u - 2u * PRIO_WIDTH) / PRIO_WIDTH);
+	const guint32 RANGE_LEN1 = 200u;
+	const guint32 RANGE_LEN2 = (RANGE_TOP - 100u) - RANGE_LEN1;
+	guint32 range_len;
+	guint32 range_top;
+	guint32 prio_candidate = 0;
+	guint32 i_step;
+	guint32 i;
+
+	/* For the auto-default-route policy routing rule we add 4 rules (2 Ipv4 and 2 IPv6).
+	 * Hence, we choose a priority for the first (of the two rules) and the second
+	 * rule gets priority + 1.
+	 * We want a priority that is
+	 *   - unused so far.
+	 *   - smaller than 32766u (which is the priority of the default rules for IPv4 and IPv6)
+	 *   - stable for each connection but different between connections (we hash the UUID
+	 *     as a "random" seed)
+	 *   - if possible, close to 32766u (RANGE_LEN1). Only otherwise fallback to the entire
+	 *     range (RANGE_LEN2).
+	 */
+
+	rnd_seed = c_siphash_hash ((const guint8 [16]) { 0xb9, 0x39, 0x8e, 0xed, 0x15, 0xb3, 0xd1, 0xc4, 0x5f, 0x45, 0x00, 0x4f, 0xec, 0xc2, 0x2b, 0x7e },
+	                           (const guint8 *) uuid,
+	                           uuid ? strlen (uuid) + 1u : 0u);
+
+	head_entry = nm_platform_lookup_object_by_addr_family (platform,
+	                                                       NMP_OBJECT_TYPE_ROUTING_RULE,
+	                                                       AF_UNSPEC);
+
+	range_len = RANGE_LEN1;
+	range_top = RANGE_TOP;
+
+again:
+	i_step = ((guint32) rnd_seed) % range_len;
+	for (i = 0; i < range_len; i++) {
+
+		/* we sample the range in a stable, but somewhat arbitrary order to
+		 * find an unused priority. */
+		i_step = (i_step + PRIME_NUMBER) % range_len;
+
+		nm_assert (i_step < range_top);
+
+		prio_candidate = (range_top - i_step) * PRIO_WIDTH;
+
+		nm_assert (prio_candidate < 32766u);
+
+		if (!_auto_default_route_find_priority_exists (head_entry, prio_candidate))
+			return prio_candidate;
+	}
+
+	if (range_len == RANGE_LEN1) {
+		/* within the narrow range close to RANGE_TOP we couldn't find any unused
+		 * priority. Retry with the entire range... */
+		range_len = RANGE_LEN2;
+		range_top -= RANGE_LEN1;
+		goto again;
+	}
+
+	/* Couldn't find an unused one? Very odd, this really should not happen unless there
+	 * are thousands of rules already. Just pick the last one we sampled. */
+	return prio_candidate;
+}
+
+static void
+_auto_default_route_init (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingWireGuard *s_wg;
+	gboolean enabled_v4;
+	gboolean enabled_v6;
+	gboolean refreshing_only;
+	guint32 old_fwmark;
+	char sbuf1[100];
+
+	if (G_LIKELY (   priv->auto_default_route_initialized
+	              && !priv->auto_default_route_refresh))
+		return;
+
+	refreshing_only =    priv->auto_default_route_initialized
+	                  && priv->auto_default_route_refresh;
+	priv->auto_default_route_refresh = FALSE;
+
+	connection = nm_device_get_applied_connection (NM_DEVICE (self));
+
+	s_wg = _nm_connection_get_setting (connection, NM_TYPE_SETTING_WIREGUARD);
+
+	old_fwmark = priv->auto_default_route_fwmark;
+
+	priv->auto_default_route_fwmark = nm_setting_wireguard_get_fwmark (s_wg);
+
+	_auto_default_route_get_enabled (s_wg,
+	                                 connection,
+	                                 &enabled_v4,
+	                                 &enabled_v6);
+	priv->auto_default_route_enabled_4 = enabled_v4;
+	priv->auto_default_route_enabled_6 = enabled_v6;
+	priv->auto_default_route_initialized = TRUE;
+
+	if (   (   priv->auto_default_route_enabled_4
+	        || priv->auto_default_route_enabled_6)
+	    && priv->auto_default_route_fwmark == 0u) {
+		if (refreshing_only)
+			priv->auto_default_route_fwmark = old_fwmark;
+		else
+			priv->auto_default_route_fwmark = _auto_default_route_find_unused_table (nm_device_get_platform (NM_DEVICE (self)));
+	}
+
+	_LOGT (LOGD_DEVICE,
+	       "auto-default-route is %s for IPv4 and %s for IPv6%s",
+	       priv->auto_default_route_enabled_4 ? "enabled" : "disabled",
+	       priv->auto_default_route_enabled_6 ? "enabled" : "disabled",
+	         priv->auto_default_route_enabled_4 || priv->auto_default_route_enabled_6
+	       ? nm_sprintf_buf (sbuf1, " (fwmark 0x%x)", priv->auto_default_route_fwmark)
+	       : "");
+}
+
+static GPtrArray *
+get_extra_rules (NMDevice *device)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	gs_unref_ptrarray GPtrArray *extra_rules = NULL;
+	guint32 priority = 0;
+	int is_ipv4;
+	NMConnection *connection;
+
+	_auto_default_route_init (self);
+
+	connection = nm_device_get_applied_connection (device);
+	if (!connection)
+		return NULL;
+
+	for (is_ipv4 = 0; is_ipv4 < 2; is_ipv4++) {
+		NMSettingIPConfig *s_ip;
+		int addr_family = is_ipv4 ? AF_INET : AF_INET6;
+		guint32 table_main;
+		guint32 fwmark;
+
+		if (is_ipv4) {
+			if (!priv->auto_default_route_enabled_4)
+				continue;
+		} else {
+			if (!priv->auto_default_route_enabled_6)
+				continue;
+		}
+
+		if (!extra_rules) {
+			if (priv->auto_default_route_priority_initialized)
+				priority = priv->auto_default_route_priority;
+			else {
+				priority = _auto_default_route_find_priority (nm_device_get_platform (device),
+				                                              nm_connection_get_uuid (connection));
+				priv->auto_default_route_priority = priority;
+				priv->auto_default_route_priority_initialized = TRUE;
+			}
+			extra_rules = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+		}
+
+		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
+		table_main = nm_setting_ip_config_get_route_table (s_ip);
+		if (table_main == 0)
+			table_main = RT_TABLE_MAIN;
+
+		fwmark = priv->auto_default_route_fwmark;
+
+		G_STATIC_ASSERT_EXPR (PRIO_WIDTH == 2);
+
+		g_ptr_array_add (extra_rules,
+		                 nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE,
+		                                 &((const NMPlatformRoutingRule) {
+		                                       .priority                   = priority,
+		                                       .addr_family                = addr_family,
+		                                       .action                     = FR_ACT_TO_TBL,
+		                                       .table                      = table_main,
+		                                       .suppress_prefixlen_inverse = ~((guint32) 0u),
+		                                   })));
+
+		g_ptr_array_add (extra_rules,
+		                 nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE,
+		                                 &((const NMPlatformRoutingRule) {
+		                                       .priority    = priority + 1u,
+		                                       .addr_family = addr_family,
+		                                       .action      = FR_ACT_TO_TBL,
+		                                       .table       = fwmark,
+		                                       .flags       = FIB_RULE_INVERT,
+		                                       .fwmark      = fwmark,
+		                                       .fwmask      = 0xFFFFFFFFu,
+		                                   })));
+	}
+
+	return g_steal_pointer (&extra_rules);
+}
+
+static guint32
+coerce_route_table (NMDevice *device,
+                    int addr_family,
+                    guint32 route_table,
+                    gboolean is_user_config)
+{
+	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+	gboolean auto_default_route_enabled;
+
+	if (route_table != 0u)
+		return route_table;
+
+	_auto_default_route_init (self);
+
+	auto_default_route_enabled =   (addr_family == AF_INET)
+	                             ? priv->auto_default_route_enabled_4
+	                             : priv->auto_default_route_enabled_6;
+
+	if (auto_default_route_enabled) {
+		/* we need to enable full-sync mode of all routing tables. */
+		_LOGT (LOGD_DEVICE, "coerce ipv%c.route-table setting to \"main\" (table 254) as we enable auto-default-route handling",
+		       nm_utils_addr_family_to_char (addr_family));
+		return RT_TABLE_MAIN;
+	}
+
+	return 0;
+}
+
+/*****************************************************************************/
+
 static gboolean
 _peer_data_equal (gconstpointer ptr_a, gconstpointer ptr_b)
 {
@@ -1084,6 +1487,8 @@ link_config (NMDeviceWireGuard *self,
 	_LOGT (LOGD_DEVICE, "wireguard link config (%s, %s)...",
 	       reason, _link_config_mode_to_string (config_mode));
 
+	_auto_default_route_init (self);
+
 	if (!priv->dns_manager) {
 		priv->dns_manager = g_object_ref (nm_dns_manager_get ());
 		g_signal_connect (priv->dns_manager, NM_DNS_MANAGER_CONFIG_CHANGED, G_CALLBACK (_dns_config_changed), self);
@@ -1127,10 +1532,10 @@ link_config (NMDeviceWireGuard *self,
 	if (NM_IN_SET (config_mode, LINK_CONFIG_MODE_FULL,
 	                            LINK_CONFIG_MODE_REAPPLY)) {
 
-		wg_lnk.listen_port = nm_setting_wireguard_get_listen_port (s_wg),
+		wg_lnk.listen_port = nm_setting_wireguard_get_listen_port (s_wg);
 		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT;
 
-		wg_lnk.fwmark = nm_setting_wireguard_get_fwmark (s_wg),
+		wg_lnk.fwmark = priv->auto_default_route_fwmark;
 		wg_change_flags |= NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK;
 
 		if (nm_utils_base64secret_decode (nm_setting_wireguard_get_private_key (s_wg),
@@ -1256,6 +1661,7 @@ static NMIPConfig *
 _get_dev2_ip_config (NMDeviceWireGuard *self,
                      int addr_family)
 {
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 	gs_unref_object NMIPConfig *ip_config = NULL;
 	NMConnection *connection;
 	NMSettingWireGuard *s_wg;
@@ -1264,6 +1670,9 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
 	int ip_ifindex;
 	guint32 route_metric;
 	guint32 route_table_coerced;
+	gboolean auto_default_route_enabled;
+
+	_auto_default_route_init (self);
 
 	connection = nm_device_get_applied_connection (NM_DEVICE (self));
 
@@ -1301,7 +1710,11 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
 
 	route_metric = nm_device_get_route_metric (NM_DEVICE (self), addr_family);
 
-	route_table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (NM_DEVICE (self), addr_family, TRUE));
+	route_table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (NM_DEVICE (self), addr_family));
+
+	auto_default_route_enabled =   (addr_family == AF_INET)
+	                             ? priv->auto_default_route_enabled_4
+	                             : priv->auto_default_route_enabled_6;
 
 	n_peers = nm_setting_wireguard_get_peers_len (s_wg);
 	for (i = 0; i < n_peers; i++) {
@@ -1316,6 +1729,7 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
 			const char *aip;
 			gboolean valid;
 			int prefix;
+			guint32 rtable_coerced;
 
 			aip = nm_wireguard_peer_get_allowed_ip (peer, j, &valid);
 
@@ -1335,13 +1749,24 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
 
 			nm_utils_ipx_address_clear_host_address (addr_family, &addrbin, NULL, prefix);
 
+			rtable_coerced = route_table_coerced;
+
+			if (   prefix == 0
+			    && auto_default_route_enabled) {
+				/* In auto-default-route mode, we place the default route in a table that
+				 * has the same number as the fwmark. wg-quick does that too. If you don't
+				 * like that, configure the rules and the default-route explicitly in the
+				 * connection profile. */
+				rtable_coerced = nm_platform_route_table_coerce (priv->auto_default_route_fwmark);
+			}
+
 			if (addr_family == AF_INET) {
 				rt.r4 = (NMPlatformIP4Route) {
 					.network       = addrbin.addr4,
 					.plen          = prefix,
 					.ifindex       = ip_ifindex,
 					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
-					.table_coerced = route_table_coerced,
+					.table_coerced = rtable_coerced,
 					.metric        = route_metric,
 				};
 			} else {
@@ -1350,7 +1775,7 @@ _get_dev2_ip_config (NMDeviceWireGuard *self,
 					.plen          = prefix,
 					.ifindex       = ip_ifindex,
 					.rt_source     = NM_IP_CONFIG_SOURCE_USER,
-					.table_coerced = route_table_coerced,
+					.table_coerced = rtable_coerced,
 					.metric        = route_metric,
 				};
 			}
@@ -1406,20 +1831,28 @@ get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
 }
 
 static void
+_device_cleanup (NMDeviceWireGuard *self)
+{
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
+
+	_peers_remove_all (priv);
+
+	_secrets_cancel (self);
+
+	priv->auto_default_route_initialized = FALSE;
+	priv->auto_default_route_priority_initialized = FALSE;
+}
+
+static void
 device_state_changed (NMDevice *device,
                       NMDeviceState new_state,
                       NMDeviceState old_state,
                       NMDeviceStateReason reason)
 {
-	NMDeviceWireGuardPrivate *priv;
-
 	if (new_state <= NM_DEVICE_STATE_ACTIVATED)
 		return;
 
-	priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (device);
-
-	_peers_remove_all (priv);
-	_secrets_cancel (NM_DEVICE_WIREGUARD (device));
+	_device_cleanup (NM_DEVICE_WIREGUARD (device));
 }
 
 /*****************************************************************************/
@@ -1440,6 +1873,8 @@ can_reapply_change (NMDevice *device,
 		                                          NM_SETTING_WIREGUARD_SETTING_NAME,
 		                                          error,
 		                                          NM_SETTING_WIREGUARD_FWMARK,
+		                                          NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE,
+		                                          NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE,
 		                                          NM_SETTING_WIREGUARD_LISTEN_PORT,
 		                                          NM_SETTING_WIREGUARD_PEERS,
 		                                          NM_SETTING_WIREGUARD_PEER_ROUTES,
@@ -1461,9 +1896,12 @@ reapply_connection (NMDevice *device,
                     NMConnection *con_new)
 {
 	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (device);
+	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 	gs_unref_object NMIPConfig *ip4_config = NULL;
 	gs_unref_object NMIPConfig *ip6_config = NULL;
 
+	priv->auto_default_route_refresh = TRUE;
+
 	ip4_config = _get_dev2_ip_config (self, AF_INET);
 	ip6_config = _get_dev2_ip_config (self, AF_INET6);
 
@@ -1567,11 +2005,8 @@ static void
 dispose (GObject *object)
 {
 	NMDeviceWireGuard *self = NM_DEVICE_WIREGUARD (object);
-	NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE (self);
 
-	_secrets_cancel (self);
-
-	_peers_remove_all (priv);
+	_device_cleanup (self);
 
 	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->dispose (object);
 }
@@ -1589,6 +2024,8 @@ finalize (GObject *object)
 		g_object_unref (priv->dns_manager);
 	}
 
+	g_hash_table_destroy (priv->peers);
+
 	G_OBJECT_CLASS (nm_device_wireguard_parent_class)->finalize (object);
 }
 
@@ -1631,6 +2068,8 @@ nm_device_wireguard_class_init (NMDeviceWireGuardClass *klass)
 	device_class->can_reapply_change = can_reapply_change;
 	device_class->reapply_connection = reapply_connection;
 	device_class->get_configured_mtu = get_configured_mtu;
+	device_class->get_extra_rules = get_extra_rules;
+	device_class->coerce_route_table = coerce_route_table;
 
 	obj_properties[PROP_PUBLIC_KEY] =
 	    g_param_spec_variant (NM_DEVICE_WIREGUARD_PUBLIC_KEY,
diff --git a/src/devices/nm-device-wpan.c b/src/devices/nm-device-wpan.c
index cdfd1f70..88234412 100644
--- a/src/devices/nm-device-wpan.c
+++ b/src/devices/nm-device-wpan.c
@@ -67,6 +67,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("WPAN connection"),
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	s_wpan = NM_SETTING_WPAN (nm_connection_get_setting (connection, NM_TYPE_SETTING_WPAN));
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index bd4fbcc3..feb5110d 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -128,6 +127,15 @@ typedef struct {
 	int ifindex;
 } DeleteOnDeactivateData;
 
+typedef struct {
+	NMDevice *device;
+	GCancellable *cancellable;
+	NMPlatformAsyncCallback callback;
+	gpointer callback_data;
+	guint num_vfs;
+	NMTernary autoprobe;
+} SriovOp;
+
 typedef void (*AcdCallback) (NMDevice *, NMIP4Config **, gboolean);
 
 typedef struct {
@@ -328,16 +336,18 @@ typedef struct _NMDevicePrivate {
 	ActivationHandleData act_handle4; /* for layer2 and IPv4. */
 	ActivationHandleData act_handle6;
 	guint           recheck_assume_id;
+
 	struct {
 		guint               call_id;
 		NMDeviceStateReason available_reason;
 		NMDeviceStateReason unavailable_reason;
-	}               recheck_available;
+	} recheck_available;
+
 	struct {
-		guint               call_id;
+		NMDispatcherCallId *call_id;
 		NMDeviceState       post_state;
 		NMDeviceStateReason post_state_reason;
-	}               dispatcher;
+	} dispatcher;
 
 	/* Link stuff */
 	guint           link_connected_id;
@@ -378,6 +388,9 @@ typedef struct _NMDevicePrivate {
 	bool            v4_route_table_initialized:1;
 	bool            v6_route_table_initialized:1;
 
+	bool            v4_route_table_full_sync_before:1;
+	bool            v6_route_table_full_sync_before:1;
+
 	NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags:5;
 
 	bool            is_enslaved:1;
@@ -397,8 +410,7 @@ typedef struct _NMDevicePrivate {
 
 	/* Proxy Configuration */
 	NMProxyConfig *proxy_config;
-	NMPacrunnerManager *pacrunner_manager;
-	NMPacrunnerCallId *pacrunner_call_id;
+	NMPacrunnerConfId *pacrunner_conf_id;
 
 	/* IP configuration info. Combined config from VPN, settings, and device */
 	union {
@@ -575,12 +587,16 @@ typedef struct _NMDevicePrivate {
 	guint check_delete_unrealized_id;
 
 	struct {
+		SriovOp *pending;    /* SR-IOV operation currently running */
+		SriovOp *next;       /* next SR-IOV operation scheduled */
+	} sriov;
+
+	struct {
 		guint timeout_id;
 		guint refresh_rate_ms;
 		guint64 tx_bytes;
 		guint64 rx_bytes;
 	} stats;
-
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE (NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -636,7 +652,8 @@ static void realize_start_setup (NMDevice *self,
                                  gboolean assume_state_guess_assume,
                                  const char *assume_state_connection_uuid,
                                  gboolean set_nm_owned,
-                                 NMUnmanFlagOp unmanaged_user_explicit);
+                                 NMUnmanFlagOp unmanaged_user_explicit,
+                                 gboolean force_platform_init);
 static void _set_mtu (NMDevice *self, guint32 mtu);
 static void _commit_mtu (NMDevice *self, const NMIP4Config *config);
 static void _cancel_activation (NMDevice *self);
@@ -1898,7 +1915,7 @@ get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 	const NMPlatformLink *pllink;
 	const guint8 *hwaddr;
 	guint8 pseudo_hwaddr[ETH_ALEN];
-	guint hwaddr_len;
+	gsize hwaddr_len;
 	int ifindex;
 	gboolean success;
 
@@ -1911,13 +1928,9 @@ get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 	    || NM_IN_SET (pllink->type, NM_LINK_TYPE_NONE, NM_LINK_TYPE_UNKNOWN))
 		return FALSE;
 
-	if (pllink->addr.len <= 0)
+	hwaddr = nmp_link_address_get (&pllink->l_address, &hwaddr_len);
+	if (hwaddr_len <= 0)
 		return FALSE;
-	if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX)
-		g_return_val_if_reached (FALSE);
-
-	hwaddr = pllink->addr.data;
-	hwaddr_len = pllink->addr.len;
 
 	if (pllink->type == NM_LINK_TYPE_6LOWPAN) {
 		/* If the underlying IEEE 802.15.4 device has a short address we generate
@@ -1949,7 +1962,7 @@ get_ip_iface_identifier (NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 	                                                  out_iid);
 	if (!success) {
 		_LOGW (LOGD_PLATFORM, "failed to generate interface identifier "
-		       "for link type %u hwaddr_len %u", pllink->type, (unsigned) pllink->addr.len);
+		       "for link type %u hwaddr_len %zu", pllink->type, hwaddr_len);
 	}
 	return success;
 }
@@ -2247,30 +2260,27 @@ _get_llmnr (NMDevice *self)
 	                                                    NM_SETTING_CONNECTION_LLMNR_DEFAULT);
 }
 
-guint32
-nm_device_get_route_table (NMDevice *self,
-                           int addr_family,
-                           gboolean fallback_main)
+static guint32
+_get_route_table (NMDevice *self,
+                  int addr_family)
 {
-	NMDevicePrivate *priv;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceClass *klass;
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip;
 	guint32 route_table = 0;
+	gboolean is_user_config = TRUE;
 
 	nm_assert_addr_family (addr_family);
 
-	g_return_val_if_fail (NM_IS_DEVICE (self), RT_TABLE_MAIN);
-
-	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) {
 		if (priv->v4_route_table_initialized)
-			return priv->v4_route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
+			return priv->v4_route_table;
 	} else {
 		if (priv->v6_route_table_initialized)
-			return priv->v6_route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
+			return priv->v6_route_table;
 	}
 
 	connection = nm_device_get_applied_connection (self);
@@ -2278,23 +2288,28 @@ nm_device_get_route_table (NMDevice *self,
 		s_ip = nm_connection_get_setting_ip_config (connection, addr_family);
 		if (s_ip)
 			route_table = nm_setting_ip_config_get_route_table (s_ip);
-
-		/* 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) {
-			const char *property;
-
-			property = addr_family == AF_INET
-			    ? NM_CON_DEFAULT ("ipv4.route-table")
-			    : NM_CON_DEFAULT ("ipv6.route-table");
-			route_table = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
-			                                                           property,
-			                                                           self,
-			                                                           0, G_MAXUINT32, 0);
+	}
+	if (route_table == 0u) {
+		gint64 v;
+
+		v = nm_config_data_get_connection_default_int64 (NM_CONFIG_GET_DATA,
+		                                                   addr_family == AF_INET
+		                                                 ? NM_CON_DEFAULT ("ipv4.route-table")
+		                                                 : NM_CON_DEFAULT ("ipv6.route-table"),
+		                                                 self,
+		                                                 0,
+		                                                 G_MAXUINT32,
+		                                                 -1);
+		if (v != -1) {
+			route_table = v;
+			is_user_config = FALSE;
 		}
 	}
 
+	klass = NM_DEVICE_GET_CLASS (self);
+	if (klass->coerce_route_table)
+		route_table = klass->coerce_route_table (self, addr_family, route_table, is_user_config);
+
 	if (addr_family == AF_INET) {
 		priv->v4_route_table_initialized = TRUE;
 		priv->v4_route_table = route_table;
@@ -2307,9 +2322,57 @@ nm_device_get_route_table (NMDevice *self,
 	       "ipv%c.route-table = %u%s",
 	       addr_family == AF_INET ? '4' : '6',
 	       (guint) (route_table ?: RT_TABLE_MAIN),
-	       route_table ? "" : " (policy routing not enabled)");
+	       route_table != 0u ? "" : " (policy routing not enabled)");
 
-	return route_table ?: (fallback_main ? RT_TABLE_MAIN : 0);
+	return route_table;
+}
+
+guint32
+nm_device_get_route_table (NMDevice *self,
+                           int addr_family)
+{
+	guint32 route_table;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), RT_TABLE_MAIN);
+
+	route_table = _get_route_table (self, addr_family);
+	return route_table ?: (guint32) RT_TABLE_MAIN;
+}
+
+static NMIPRouteTableSyncMode
+_get_route_table_sync_mode_stateful (NMDevice *self,
+                                     int addr_family)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean full_sync_now;
+	gboolean full_sync_eff;
+
+	full_sync_now = _get_route_table (self, addr_family) != 0u;
+
+	if (full_sync_now)
+		full_sync_eff = TRUE;
+	else {
+		/* When we change from full-sync to no full-sync, we do a last full-sync one
+		 * more time. For that, we determine the effective full-state based on the
+		 * cached/previous full-sync flag.
+		 *
+		 * The purpose of this is to support reapply of route-table (and thus the
+		 * full-sync mode). If reapply toggles from full-sync to no-full-sync, we must
+		 * sync one last time. */
+		if (addr_family == AF_INET)
+			full_sync_eff = priv->v4_route_table_full_sync_before;
+		else
+			full_sync_eff = priv->v6_route_table_full_sync_before;
+	}
+
+	if (addr_family == AF_INET)
+		priv->v4_route_table_full_sync_before = full_sync_now;
+	else
+		priv->v6_route_table_full_sync_before = full_sync_now;
+
+	return   full_sync_eff
+	       ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+	       : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN;
 }
 
 const NMPObject *
@@ -2801,6 +2864,7 @@ concheck_update_state (NMDevice *self,
 	                             NM_CONNECTIVITY_PORTAL,
 	                             NM_CONNECTIVITY_FULL,
 	                             NM_CONNECTIVITY_FAKE,
+	                             NM_CONNECTIVITY_NONE,
 	                             NM_CONNECTIVITY_ERROR));
 
 	if (state == NM_CONNECTIVITY_ERROR) {
@@ -2823,14 +2887,6 @@ concheck_update_state (NMDevice *self,
 				state = NM_CONNECTIVITY_LIMITED;
 		} else
 			state = NM_CONNECTIVITY_NONE;
-	} else if (state == NM_CONNECTIVITY_LIMITED) {
-		/* NMConnectivity cannot distinguish between NONE and LIMITED connectivity. In both
-		 * cases, it just failed to fetch the URL.
-		 *
-		 * NMDevice coerces a LIMITED state to NONE here, if the logical state of the device
-		 * is disconnected. */
-		if (priv->state <= NM_DEVICE_STATE_DISCONNECTED)
-			state = NM_CONNECTIVITY_NONE;
 	}
 
 	if (priv->concheck_x[IS_IPv4].state == state) {
@@ -3096,28 +3152,17 @@ concheck_start (NMDevice *self,
 	    && !priv->concheck_rp_filter_checked) {
 
 		if ((ifname = nm_device_get_ip_iface_from_platform (self))) {
-			int val, val_all;
-
-			val = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
-			                                                  AF_INET,
-			                                                  ifname,
-			                                                  "rp_filter",
-			                                                  10, 0, 2, 3);
-			if (val < 2) {
-				val_all = nm_platform_sysctl_ip_conf_get_int_checked (nm_device_get_platform (self),
-				                                                      AF_INET,
-				                                                      "all",
-				                                                      "rp_filter",
-				                                                      10, 0, 2, val);
-				if (val_all > val) {
-					val = val_all;
-					ifname = "all";
-				}
-			}
+			gboolean due_to_all;
+			int val;
 
+			val = nm_platform_sysctl_ip_conf_get_rp_filter_ipv4 (nm_device_get_platform (self),
+			                                                     ifname,
+			                                                     TRUE,
+			                                                     &due_to_all);
 			if (val == 1) {
 				_LOGW (LOGD_CONCHECK, "connectivity: \"/proc/sys/net/ipv4/conf/%s/rp_filter\" is set to \"1\". "
-				       "This might break connectivity checking for IPv4 on this device", ifname);
+				       "This might break connectivity checking for IPv4 on this device",
+				       due_to_all ? "all" : ifname);
 			}
 		}
 
@@ -3127,6 +3172,7 @@ concheck_start (NMDevice *self,
 
 	handle->c_handle = nm_connectivity_check_start (concheck_get_mgr (self),
 	                                                handle->addr_family,
+	                                                nm_device_get_platform (self),
 	                                                nm_device_get_ip_ifindex (self),
 	                                                nm_device_get_ip_iface (self),
 	                                                concheck_cb,
@@ -3588,7 +3634,7 @@ nm_device_set_carrier (NMDevice *self, gboolean carrier)
 			now_ms = nm_utils_get_monotonic_timestamp_ms ();
 			until_ms = NM_MAX (now_ms + _get_carrier_wait_ms (self), priv->carrier_wait_until_ms);
 			priv->carrier_defer_id = g_timeout_add (until_ms - now_ms, carrier_disconnected_action_cb, self);
-			_LOGD (LOGD_DEVICE, "carrier: link disconnected (deferring action for %ld milli seconds) (id=%u)",
+			_LOGD (LOGD_DEVICE, "carrier: link disconnected (deferring action for %ld milliseconds) (id=%u)",
 			       (long) (until_ms - now_ms), priv->carrier_defer_id);
 		}
 	}
@@ -3890,16 +3936,12 @@ device_link_changed (NMDevice *self)
 	if (priv->up && (!was_up || seen_down)) {
 		/* the link was down and just came up. That happens for example, while changing MTU.
 		 * We must restore IP configuration. */
-		if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
-			if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
-				_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
-		}
+		if (!ip_config_merge_and_apply (self, AF_INET, TRUE))
+			_LOGW (LOGD_IP4, "failed applying IP4 config after link comes up again");
 
 		priv->linklocal6_dad_counter = 0;
-		if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
-			if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
-				_LOGW (LOGD_IP6, "failed applying IP6 config after link comes up again");
-		}
+		if (!ip_config_merge_and_apply (self, AF_INET6, TRUE))
+			_LOGW (LOGD_IP6, "failed applying IP6 config after link comes up again");
 	}
 
 	if (update_unmanaged_specs)
@@ -4086,7 +4128,8 @@ nm_device_realize_start (NMDevice *self,
 	                     assume_state_guess_assume,
 	                     assume_state_connection_uuid,
 	                     set_nm_owned,
-	                     unmanaged_user_explicit);
+	                     unmanaged_user_explicit,
+	                     FALSE);
 	return TRUE;
 }
 
@@ -4131,7 +4174,8 @@ nm_device_create_and_realize (NMDevice *self,
 	                     plink,
 	                     FALSE, /* assume_state_guess_assume */
 	                     NULL,  /* assume_state_connection_uuid */
-	                     FALSE, NM_UNMAN_FLAG_OP_FORGET);
+	                     FALSE, NM_UNMAN_FLAG_OP_FORGET,
+	                     TRUE);
 	nm_device_realize_finish (self, plink);
 
 	if (nm_device_get_managed (self, FALSE)) {
@@ -4192,6 +4236,86 @@ nm_device_update_from_platform_link (NMDevice *self, const NMPlatformLink *plink
 	}
 }
 
+static void sriov_op_cb (GError *error, gpointer user_data);
+
+static void
+sriov_op_start (NMDevice *self, SriovOp *op)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert (!priv->sriov.pending);
+
+	op->cancellable = g_cancellable_new ();
+	op->device = g_object_ref (self);
+	priv->sriov.pending = op;
+
+	nm_platform_link_set_sriov_params_async (nm_device_get_platform (self),
+	                                         priv->ifindex,
+	                                         op->num_vfs,
+	                                         op->autoprobe,
+	                                         sriov_op_cb,
+	                                         op,
+	                                         op->cancellable);
+}
+
+static void
+sriov_op_cb (GError *error, gpointer user_data)
+{
+	SriovOp *op = user_data;
+	gs_unref_object NMDevice *self = op->device;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert (op == priv->sriov.pending);
+
+	priv->sriov.pending = NULL;
+
+	if (op->callback)
+		op->callback (error, op->callback_data);
+
+	g_clear_object (&op->cancellable);
+	g_slice_free (SriovOp, op);
+
+	if (priv->sriov.next) {
+		sriov_op_start (self,
+		                g_steal_pointer (&priv->sriov.next));
+	}
+}
+
+static void
+sriov_op_queue (NMDevice *self,
+                guint num_vfs,
+                NMTernary autoprobe,
+                NMPlatformAsyncCallback callback,
+                gpointer callback_data)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	GError *error = NULL;
+	SriovOp *op;
+
+	op = g_slice_new0 (SriovOp);
+	op->num_vfs = num_vfs;
+	op->autoprobe = autoprobe;
+	op->callback = callback;
+	op->callback_data = callback_data;
+
+	if (priv->sriov.next) {
+		/* Cancel the next operation immediately */
+		if (priv->sriov.next->callback) {
+			nm_utils_error_set_cancelled (&error, FALSE, NULL);
+			priv->sriov.next->callback (error, priv->sriov.next->callback_data);
+			g_clear_error (&error);
+		}
+		g_slice_free (SriovOp, priv->sriov.next);
+		priv->sriov.next = NULL;
+	}
+
+	if (priv->sriov.pending) {
+		priv->sriov.next = op;
+		g_cancellable_cancel (priv->sriov.pending->cancellable);
+	} else
+		sriov_op_start (self, op);
+}
+
 static void
 device_init_static_sriov_num_vfs (NMDevice *self)
 {
@@ -4206,10 +4330,8 @@ device_init_static_sriov_num_vfs (NMDevice *self)
 		                                          self,
 		                                          NULL);
 		num_vfs = _nm_utils_ascii_str_to_int64 (value, 10, 0, G_MAXINT32, -1);
-		if (num_vfs >= 0) {
-			nm_platform_link_set_sriov_params (nm_device_get_platform (self),
-			                                   priv->ifindex, num_vfs, NM_TERNARY_DEFAULT);
-		}
+		if (num_vfs >= 0)
+			sriov_op_queue (self, num_vfs, NM_TERNARY_DEFAULT, NULL, NULL);
 	}
 }
 
@@ -4223,11 +4345,11 @@ config_changed (NMConfig *config,
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	if (   priv->state <= NM_DEVICE_STATE_DISCONNECTED
-	    || priv->state > NM_DEVICE_STATE_ACTIVATED)
+	    || priv->state > NM_DEVICE_STATE_ACTIVATED) {
 		priv->ignore_carrier = nm_config_data_get_ignore_carrier (config_data, self);
-
-	if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_VALUES))
-		device_init_static_sriov_num_vfs (self);
+		if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_VALUES))
+			device_init_static_sriov_num_vfs (self);
+	}
 }
 
 static void
@@ -4248,6 +4370,8 @@ realize_start_notify (NMDevice *self,
  * @set_nm_owned: if TRUE and device is a software-device, set nm-owned.
  *    TRUE.
  * @unmanaged_user_explicit: the user-explict unmanaged flag to set.
+ * @force_platform_init: if TRUE the platform-init unmanaged flag is
+ *    forcefully cleared.
  *
  * Update the device from backing resource properties (like hardware
  * addresses, carrier states, driver/firmware info, etc).  This function
@@ -4261,14 +4385,17 @@ realize_start_setup (NMDevice *self,
                      gboolean assume_state_guess_assume,
                      const char *assume_state_connection_uuid,
                      gboolean set_nm_owned,
-                     NMUnmanFlagOp unmanaged_user_explicit)
+                     NMUnmanFlagOp unmanaged_user_explicit,
+                     gboolean force_platform_init)
 {
 	NMDevicePrivate *priv;
 	NMDeviceClass *klass;
+	NMPlatform *platform;
 	static guint32 id = 0;
 	NMDeviceCapabilities capabilities = 0;
 	NMConfig *config;
 	guint real_rate;
+	gboolean unmanaged;
 
 	/* plink is a NMPlatformLink type, however, we require it to come from the platform
 	 * cache (where else would it come from?). */
@@ -4289,6 +4416,7 @@ realize_start_setup (NMDevice *self,
 	_LOGD (LOGD_DEVICE, "start setup of %s, kernel ifindex %d", G_OBJECT_TYPE_NAME (self), plink ? plink->ifindex : 0);
 
 	klass = NM_DEVICE_GET_CLASS (self);
+	platform = nm_device_get_platform (self);
 
 	/* Balanced by a thaw in nm_device_realize_finish() */
 	g_object_freeze_notify (G_OBJECT (self));
@@ -4307,19 +4435,19 @@ realize_start_setup (NMDevice *self,
 		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);
+		priv->physical_port_id = nm_platform_link_get_physical_port_id (platform, priv->ifindex);
 		_notify (self, PROP_PHYSICAL_PORT_ID);
 
-		priv->dev_id = nm_platform_link_get_dev_id (nm_device_get_platform (self), priv->ifindex);
+		priv->dev_id = nm_platform_link_get_dev_id (platform, priv->ifindex);
 
-		if (nm_platform_link_is_software (nm_device_get_platform (self), priv->ifindex))
+		if (nm_platform_link_is_software (platform, priv->ifindex))
 			capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
 
 		_set_mtu (self,
-		          nm_platform_link_get_mtu (nm_device_get_platform (self),
+		          nm_platform_link_get_mtu (platform,
 		                                    priv->ifindex));
 
-		nm_platform_link_get_driver_info (nm_device_get_platform (self),
+		nm_platform_link_get_driver_info (platform,
 		                                  priv->ifindex,
 		                                  NULL,
 		                                  &priv->driver_version,
@@ -4330,9 +4458,9 @@ realize_start_setup (NMDevice *self,
 			_notify (self, PROP_FIRMWARE_VERSION);
 
 		if (nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
-			priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled (nm_device_get_platform (self), priv->ifindex);
+			priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled (platform, priv->ifindex);
 
-		if (nm_platform_link_supports_sriov (nm_device_get_platform (self), priv->ifindex))
+		if (nm_platform_link_supports_sriov (platform, priv->ifindex))
 			capabilities |= NM_DEVICE_CAP_SRIOV;
 	}
 
@@ -4398,8 +4526,12 @@ realize_start_setup (NMDevice *self,
 	nm_device_set_unmanaged_by_user_udev (self);
 	nm_device_set_unmanaged_by_user_conf (self);
 
+	unmanaged =    plink
+	            && !plink->initialized
+	            && !force_platform_init;
+
 	nm_device_set_unmanaged_flags (self, NM_UNMANAGED_PLATFORM_INIT,
-	                               plink && !plink->initialized);
+	                               unmanaged);
 }
 
 /**
@@ -4941,7 +5073,7 @@ static void
 check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean ip4_disabled = FALSE, ip6_ignore = FALSE;
+	gboolean ip4_disabled = FALSE, ip6_disabled = FALSE;
 	NMSettingIPConfig *s_ip4, *s_ip6;
 	NMDeviceState state;
 
@@ -4961,9 +5093,10 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 		ip4_disabled = TRUE;
 
 	s_ip6 = 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 (s_ip6 && NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
+	                           NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                           NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
+		ip6_disabled = TRUE;
 
 	if (   priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
 	    && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
@@ -4973,7 +5106,7 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 	}
 
 	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL || (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE))
-	    && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL || (ip6_ignore && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE))) {
+	    && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL || (ip6_disabled && priv->ip_state_6 == NM_DEVICE_IP_STATE_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)) {
@@ -5010,7 +5143,7 @@ check_ip_state (NMDevice *self, gboolean may_fail, gboolean full_state_update)
 
 	/* If at least a method has completed, proceed with activation */
 	if (   (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !ip4_disabled)
-	    || (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_ignore)) {
+	    || (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_disabled)) {
 		if (full_state_update)
 			nm_device_state_changed (self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
 		return;
@@ -5565,8 +5698,9 @@ nm_device_generate_connection (NMDevice *self,
 	 */
 	ip4_method = nm_utils_get_ip_config_method (connection, AF_INET);
 	ip6_method = nm_utils_get_ip_config_method (connection, AF_INET6);
-	if (   g_strcmp0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
-	    && g_strcmp0 (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0
+	if (   nm_streq0 (ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
+	    && NM_IN_STRSET (ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                                 NM_SETTING_IP6_CONFIG_METHOD_DISABLED)
 	    && !nm_setting_connection_get_master (NM_SETTING_CONNECTION (s_con))
 	    && c_list_is_empty (&priv->slaves)) {
 		NM_SET_OUT (out_maybe_later, TRUE);
@@ -5631,7 +5765,10 @@ nm_device_complete_connection (NMDevice *self,
 	                                 error))
 		return FALSE;
 
-	return nm_connection_verify (connection, error);
+	if (!nm_connection_normalize (connection, NULL, NULL, error))
+		return FALSE;
+
+	return nm_device_check_connection_compatible (self, connection, error);
 }
 
 gboolean
@@ -5652,11 +5789,9 @@ nm_device_match_parent (NMDevice *self, const char *parent)
 		 * 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)))
+		connection = nm_device_get_applied_connection (parent_device);
+		if (   connection
+		    && !nm_streq0 (parent, nm_connection_get_uuid (connection)))
 			return FALSE;
 	} else {
 		/* Interface name */
@@ -6255,6 +6390,41 @@ sriov_vf_config_to_platform (NMDevice *self,
 	return g_steal_pointer (&plat_vf);
 }
 
+static void
+sriov_params_cb (GError *error, gpointer data)
+{
+	NMDevice *self;
+	NMDevicePrivate *priv;
+	nm_auto_freev NMPlatformVF **plat_vfs = NULL;
+
+	nm_utils_user_data_unpack (data, &self, &plat_vfs);
+
+	if (nm_utils_error_is_cancelled (error, TRUE))
+		return;
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (error) {
+		_LOGE (LOGD_DEVICE, "failed to set SR-IOV parameters: %s", error->message);
+		nm_device_state_changed (self,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
+		return;
+	}
+
+	if (!nm_platform_link_set_sriov_vfs (nm_device_get_platform (self),
+	                                     priv->ifindex,
+	                                     (const NMPlatformVF *const *) plat_vfs)) {
+		_LOGE (LOGD_DEVICE, "failed to apply SR-IOV VFs");
+		nm_device_state_changed (self,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
+		return;
+	}
+
+	nm_device_activate_schedule_stage2_device_config (self);
+}
+
 static NMActStageReturn
 act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 {
@@ -6269,6 +6439,7 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 		gs_free_error GError *error = NULL;
 		NMSriovVF *vf;
 		NMTernary autoprobe;
+		gpointer *data;
 
 		autoprobe = nm_setting_sriov_get_autoprobe_drivers (s_sriov);
 		if (autoprobe == NM_TERNARY_DEFAULT) {
@@ -6295,24 +6466,19 @@ act_stage1_prepare (NMDevice *self, NMDeviceStateReason *out_failure_reason)
 			}
 		}
 
-		if (!nm_platform_link_set_sriov_params (nm_device_get_platform (self),
-		                                        priv->ifindex,
-		                                        nm_setting_sriov_get_total_vfs (s_sriov),
-		                                        autoprobe)) {
-			_LOGE (LOGD_DEVICE, "failed to apply SR-IOV parameters");
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		}
-
-		if (!nm_platform_link_set_sriov_vfs (nm_device_get_platform (self),
-		                                     priv->ifindex,
-		                                     (const NMPlatformVF *const *) plat_vfs)) {
-			_LOGE (LOGD_DEVICE, "failed to apply SR-IOV VFs");
-			NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
-			return NM_ACT_STAGE_RETURN_FAILURE;
-		}
+		/* When changing the number of VFs the kernel can block
+		 * for very long time in the write to sysfs, especially
+		 * if autoprobe-drivers is enabled. Do it asynchronously
+		 * to avoid blocking the entire NM process.
+		 */
+		data = nm_utils_user_data_pack (self, g_steal_pointer (&plat_vfs));
+		sriov_op_queue (self,
+		                nm_setting_sriov_get_total_vfs (s_sriov),
+		                autoprobe,
+		                sriov_params_cb,
+		                data);
+		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
-
 	return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
@@ -6328,6 +6494,9 @@ activate_stage1_device_prepare (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
 
+	priv->v4_route_table_initialized = FALSE;
+	priv->v6_route_table_initialized = FALSE;
+
 	_set_ip_state (self, AF_INET, NM_DEVICE_IP_STATE_NONE);
 	_set_ip_state (self, AF_INET6, NM_DEVICE_IP_STATE_NONE);
 
@@ -6430,11 +6599,15 @@ _routing_rules_sync (NMDevice *self,
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMPRulesManager *rules_manager = nm_netns_get_rules_manager (nm_device_get_netns (self));
+	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (self);
 	gboolean untrack_only_dirty = FALSE;
 	gboolean keep_deleted_rules;
-	gpointer user_tag;
+	gpointer user_tag_1;
+	gpointer user_tag_2;
 
-	user_tag = priv;
+	/* take two arbitrary user-tag pointers that belong to @self. */
+	user_tag_1 = &priv->v4_route_table;
+	user_tag_2 = &priv->v6_route_table;
 
 	if (set_mode == NM_TERNARY_TRUE) {
 		NMConnection *applied_connection;
@@ -6443,7 +6616,9 @@ _routing_rules_sync (NMDevice *self,
 		int is_ipv4;
 
 		untrack_only_dirty = TRUE;
-		nmp_rules_manager_set_dirty (rules_manager, user_tag);
+		nmp_rules_manager_set_dirty (rules_manager, user_tag_1);
+		if (klass->get_extra_rules)
+			nmp_rules_manager_set_dirty (rules_manager, user_tag_2);
 
 		applied_connection = nm_device_get_applied_connection (self);
 
@@ -6461,15 +6636,37 @@ _routing_rules_sync (NMDevice *self,
 
 				rule = nm_setting_ip_config_get_routing_rule (s_ip, i);
 				nm_ip_routing_rule_to_platform (rule, &plrule);
+
+				/* We track this rule, but we also make it explicitly not weakly-tracked
+				 * (meaning to untrack NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG at
+				 * the same time). */
 				nmp_rules_manager_track (rules_manager,
 				                         &plrule,
 				                         10,
-				                         user_tag);
+				                         user_tag_1,
+				                         NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
+			}
+		}
+
+		if (klass->get_extra_rules) {
+			gs_unref_ptrarray GPtrArray *extra_rules = NULL;
+
+			extra_rules = klass->get_extra_rules (self);
+			if (extra_rules) {
+				for (i = 0; i < extra_rules->len; i++) {
+					nmp_rules_manager_track (rules_manager,
+					                         NMP_OBJECT_CAST_ROUTING_RULE (extra_rules->pdata[i]),
+					                         10,
+					                         user_tag_2,
+					                         NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
+				}
 			}
 		}
 	}
 
-	nmp_rules_manager_untrack_all (rules_manager, user_tag, !untrack_only_dirty);
+	nmp_rules_manager_untrack_all (rules_manager, user_tag_1, !untrack_only_dirty);
+	if (klass->get_extra_rules)
+		nmp_rules_manager_untrack_all (rules_manager, user_tag_2, !untrack_only_dirty);
 
 	keep_deleted_rules = FALSE;
 	if (set_mode == NM_TERNARY_DEFAULT) {
@@ -6500,7 +6697,7 @@ tc_commit (NMDevice *self)
 	NMSettingTCConfig *s_tc = NULL;
 	int ip_ifindex;
 	guint nqdiscs, ntfilters;
-	int i;
+	guint i;
 
 	connection = nm_device_get_applied_connection (self);
 	if (connection)
@@ -6508,7 +6705,7 @@ tc_commit (NMDevice *self)
 
 	ip_ifindex = nm_device_get_ip_ifindex (self);
 	if (!ip_ifindex)
-	       return s_tc == NULL;
+		return s_tc == NULL;
 
 	if (s_tc) {
 		nqdiscs = nm_setting_tc_config_get_num_qdiscs (s_tc);
@@ -6520,12 +6717,40 @@ tc_commit (NMDevice *self)
 			NMPlatformQdisc *qdisc = NMP_OBJECT_CAST_QDISC (q);
 
 			qdisc->ifindex = ip_ifindex;
+
+			/* Note: kind string is still owned by NMTCTfilter.
+			 * This qdisc instance must not be kept alive beyond this function.
+			 * nm_platform_qdisc_sync() promises to do that. */
 			qdisc->kind = nm_tc_qdisc_get_kind (s_qdisc);
+
 			qdisc->addr_family = AF_UNSPEC;
 			qdisc->handle = nm_tc_qdisc_get_handle (s_qdisc);
 			qdisc->parent = nm_tc_qdisc_get_parent (s_qdisc);
 			qdisc->info = 0;
 
+#define GET_ATTR(name, dst, variant_type, type, dflt) G_STMT_START { \
+	GVariant *_variant = nm_tc_qdisc_get_attribute (s_qdisc, ""name""); \
+	\
+	if (   _variant \
+	    && g_variant_is_of_type (_variant, G_VARIANT_TYPE_ ## variant_type)) \
+		(dst) = g_variant_get_ ## type (_variant); \
+	else \
+		(dst) = (dflt); \
+} G_STMT_END
+
+			if (strcmp (qdisc->kind, "fq_codel") == 0) {
+				GET_ATTR ("limit",        qdisc->fq_codel.limit,        UINT32,  uint32,  0);
+				GET_ATTR ("flows",        qdisc->fq_codel.flows,        UINT32,  uint32,  0);
+				GET_ATTR ("target",       qdisc->fq_codel.target,       UINT32,  uint32,  0);
+				GET_ATTR ("interval",     qdisc->fq_codel.interval,     UINT32,  uint32,  0);
+				GET_ATTR ("quantum",      qdisc->fq_codel.quantum,      UINT32,  uint32,  0);
+				GET_ATTR ("ce_threshold", qdisc->fq_codel.ce_threshold, UINT32,  uint32,  NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED);
+				GET_ATTR ("memory_limit", qdisc->fq_codel.memory_limit, UINT32,  uint32,  NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET);
+				GET_ATTR ("ecn",          qdisc->fq_codel.ecn,          BOOLEAN, boolean, FALSE);
+			}
+
+#undef GET_ADDR
+
 			g_ptr_array_add (qdiscs, q);
 		}
 
@@ -6539,7 +6764,12 @@ tc_commit (NMDevice *self)
 			NMPlatformTfilter *tfilter = NMP_OBJECT_CAST_TFILTER (q);
 
 			tfilter->ifindex = ip_ifindex;
+
+			/* Note: kind string is still owned by NMTCTfilter.
+			 * This tfilter instance must not be kept alive beyond this function.
+			 * nm_platform_tfilter_sync() promises to do that. */
 			tfilter->kind = nm_tc_tfilter_get_kind (s_tfilter);
+
 			tfilter->addr_family = AF_UNSPEC;
 			tfilter->handle = nm_tc_tfilter_get_handle (s_tfilter);
 			tfilter->parent = nm_tc_tfilter_get_parent (s_tfilter);
@@ -6547,16 +6777,42 @@ tc_commit (NMDevice *self)
 
 			action = nm_tc_tfilter_get_action (s_tfilter);
 			if (action) {
+				GVariant *var;
+
+				/* Note: kind string is still owned by NMTCAction.
+				 * This tfilter instance must not be kept alive beyond this function.
+				 * nm_platform_tfilter_sync() promises to do that. */
 				tfilter->action.kind = nm_tc_action_get_kind (action);
-				if (strcmp (tfilter->action.kind, "simple") == 0) {
-					GVariant *sdata;
 
-					sdata = nm_tc_action_get_attribute (action, "sdata");
-					if (sdata && g_variant_is_of_type (sdata, G_VARIANT_TYPE_BYTESTRING)) {
+				if (strcmp (tfilter->action.kind, "simple") == 0) {
+					var = nm_tc_action_get_attribute (action, "sdata");
+					if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_BYTESTRING)) {
 						g_strlcpy (tfilter->action.simple.sdata,
-						           g_variant_get_bytestring (sdata),
+						           g_variant_get_bytestring (var),
 						           sizeof (tfilter->action.simple.sdata));
 					}
+				} else if (strcmp (tfilter->action.kind, "mirred") == 0) {
+					if (nm_tc_action_get_attribute (action, "egress"))
+						tfilter->action.mirred.egress = TRUE;
+
+					if (nm_tc_action_get_attribute (action, "ingress"))
+						tfilter->action.mirred.ingress = TRUE;
+
+					if (nm_tc_action_get_attribute (action, "mirror"))
+						tfilter->action.mirred.mirror = TRUE;
+
+					if (nm_tc_action_get_attribute (action, "redirect"))
+						tfilter->action.mirred.redirect = TRUE;
+
+					var = nm_tc_action_get_attribute (action, "dev");
+					if (var && g_variant_is_of_type (var, G_VARIANT_TYPE_STRING)) {
+						int ifindex;
+
+						ifindex = nm_platform_link_get_ifindex (nm_device_get_platform (self),
+						                                        g_variant_get_string (var, NULL));
+						if (ifindex > 0)
+							tfilter->action.mirred.ifindex = ifindex;
+					}
 				}
 			}
 
@@ -6941,7 +7197,7 @@ ipv4ll_get_ip4_config (NMDevice *self, guint32 lla)
 	route.network = htonl (0xE0000000L);
 	route.plen = 4;
 	route.rt_source = NM_IP_CONFIG_SOURCE_IP4LL;
-	route.table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (self, AF_INET, TRUE));
+	route.table_coerced = nm_platform_route_table_coerce (nm_device_get_route_table (self, AF_INET));
 	route.metric = nm_device_get_route_metric (self, AF_INET);
 	nm_ip4_config_add_route (config, &route, NULL);
 
@@ -7110,12 +7366,12 @@ ensure_con_ip_config (NMDevice *self, int addr_family)
 		                             nm_connection_get_setting_ip4_config (connection),
 		                             _get_mdns (self),
 		                             _get_llmnr (self),
-		                             nm_device_get_route_table (self, addr_family, TRUE),
+		                             nm_device_get_route_table (self, addr_family),
 		                             nm_device_get_route_metric (self, addr_family));
 	} else {
 		nm_ip6_config_merge_setting (NM_IP6_CONFIG (con_ip_config),
 		                             nm_connection_get_setting_ip6_config (connection),
-		                             nm_device_get_route_table (self, addr_family, TRUE),
+		                             nm_device_get_route_table (self, addr_family),
 		                             nm_device_get_route_metric (self, addr_family));
 	}
 
@@ -7332,12 +7588,12 @@ ip_config_merge_and_apply (NMDevice *self,
 	if (commit) {
 		if (IS_IPv4) {
 			nm_ip4_config_add_dependent_routes (NM_IP4_CONFIG (composite),
-			                                    nm_device_get_route_table (self, addr_family, TRUE),
+			                                    nm_device_get_route_table (self, addr_family),
 			                                    nm_device_get_route_metric (self, addr_family),
 			                                    &ip4_dev_route_blacklist);
 		} else {
 			nm_ip6_config_add_dependent_routes (NM_IP6_CONFIG (composite),
-			                                    nm_device_get_route_table (self, addr_family, TRUE),
+			                                    nm_device_get_route_table (self, addr_family),
 			                                    nm_device_get_route_metric (self, addr_family));
 		}
 	}
@@ -7350,15 +7606,23 @@ ip_config_merge_and_apply (NMDevice *self,
 	}
 
 	if (!IS_IPv4) {
+		const NMPlatformLink *link;
 		NMUtilsIPv6IfaceId iid;
+		NMPlatform *platform;
+		int ifindex;
 
 		if (   commit
 		    && priv->ndisc_started
 		    && ip6_addr_gen_token
 		    && nm_utils_ipv6_interface_identifier_get_from_token (&iid, ip6_addr_gen_token)) {
-			nm_platform_link_set_ipv6_token (nm_device_get_platform (self),
-			                                 nm_device_get_ip_ifindex (self),
-			                                 iid);
+			platform = nm_device_get_platform (self);
+			ifindex = nm_device_get_ip_ifindex (self);
+			link = nm_platform_link_get (platform, ifindex);
+
+			if (link && link->inet6_token.id == iid.id)
+				_LOGT (LOGD_DEVICE | LOGD_IP6, "token %s already set", ip6_addr_gen_token);
+			else
+				nm_platform_link_set_ipv6_token (platform, ifindex, iid);
 		}
 	}
 
@@ -7470,9 +7734,13 @@ clear_config:
 static void
 dhcp4_dad_cb (NMDevice *self, NMIP4Config **configs, gboolean success)
 {
-	if (success)
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (success) {
+		nm_dhcp_client_accept (priv->dhcp4.client, NULL);
 		nm_device_activate_schedule_ip_config_result (self, AF_INET, NM_IP_CONFIG_CAST (configs[1]));
-	else {
+	} else {
+		nm_dhcp_client_decline (priv->dhcp4.client, "Address conflict detected", NULL);
 		nm_device_ip_method_failed (self, AF_INET,
 		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
 	}
@@ -7531,7 +7799,7 @@ dhcp4_state_changed (NMDhcpClient *client,
 			                             nm_connection_get_setting_ip4_config (connection),
 			                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
 			                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
-			                             nm_device_get_route_table (self, AF_INET, TRUE),
+			                             nm_device_get_route_table (self, AF_INET),
 			                             nm_device_get_route_metric (self, AF_INET));
 
 			configs = g_new0 (NMIP4Config *, 3);
@@ -7738,9 +8006,11 @@ dhcp4_start (NMDevice *self)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip4;
 	gs_unref_bytes GBytes *hwaddr = NULL;
+	gs_unref_bytes GBytes *bcast_hwaddr = NULL;
 	gs_unref_bytes GBytes *client_id = NULL;
 	NMConnection *connection;
 	GError *error = NULL;
+	const NMPlatformLink *pllink;
 
 	connection = nm_device_get_applied_connection (self);
 	g_return_val_if_fail (connection, FALSE);
@@ -7751,8 +8021,11 @@ dhcp4_start (NMDevice *self)
 	nm_dbus_object_clear_and_unexport (&priv->dhcp4.config);
 	priv->dhcp4.config = nm_dhcp4_config_new ();
 
-	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
-	                                                nm_device_get_ip_ifindex (self));
+	pllink = nm_platform_link_get (nm_device_get_platform (self), nm_device_get_ip_ifindex (self));
+	if (pllink) {
+		hwaddr = nmp_link_address_get_as_bytes (&pllink->l_address);
+		bcast_hwaddr = nmp_link_address_get_as_bytes (&pllink->l_broadcast);
+	}
 
 	client_id = dhcp4_get_client_id (self, connection, hwaddr);
 
@@ -7762,8 +8035,9 @@ dhcp4_start (NMDevice *self)
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
 	                                                hwaddr,
+	                                                bcast_hwaddr,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_route_table (self, AF_INET, TRUE),
+	                                                nm_device_get_route_table (self, AF_INET),
 	                                                nm_device_get_route_metric (self, AF_INET),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip4),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip4),
@@ -7892,7 +8166,9 @@ connection_ip_method_requires_carrier (NMConnection *connection,
 		                             NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
 	}
 
-	NM_SET_OUT (out_ip_enabled, !nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE));
+	NM_SET_OUT (out_ip_enabled,
+	            !NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                                   NM_SETTING_IP6_CONFIG_METHOD_DISABLED));
 	return NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
 	                             NM_SETTING_IP6_CONFIG_METHOD_DHCP,
 	                             NM_SETTING_IP6_CONFIG_METHOD_SHARED,
@@ -8537,8 +8813,10 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	NMSettingIPConfig *s_ip6;
 	gs_unref_bytes GBytes *hwaddr = NULL;
+	gs_unref_bytes GBytes *bcast_hwaddr = NULL;
 	gs_unref_bytes GBytes *duid = NULL;
 	gboolean enforce_duid = FALSE;
+	const NMPlatformLink *pllink;
 	GError *error = NULL;
 
 	const NMPlatformIP6Address *ll_addr = NULL;
@@ -8558,8 +8836,11 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 		return FALSE;
 	}
 
-	hwaddr = nm_platform_link_get_address_as_bytes (nm_device_get_platform (self),
-	                                                nm_device_get_ip_ifindex (self));
+	pllink = nm_platform_link_get (nm_device_get_platform (self), nm_device_get_ip_ifindex (self));
+	if (pllink) {
+		hwaddr = nmp_link_address_get_as_bytes (&pllink->l_address);
+		bcast_hwaddr = nmp_link_address_get_as_bytes (&pllink->l_broadcast);
+	}
 
 	duid = dhcp6_get_duid (self, connection, hwaddr, &enforce_duid);
 	priv->dhcp6.client = nm_dhcp_manager_start_ip6 (nm_dhcp_manager_get (),
@@ -8567,9 +8848,10 @@ dhcp6_start_with_link_ready (NMDevice *self, NMConnection *connection)
 	                                                nm_device_get_ip_iface (self),
 	                                                nm_device_get_ip_ifindex (self),
 	                                                hwaddr,
+	                                                bcast_hwaddr,
 	                                                &ll_addr->address,
 	                                                nm_connection_get_uuid (connection),
-	                                                nm_device_get_route_table (self, AF_INET6, TRUE),
+	                                                nm_device_get_route_table (self, AF_INET6),
 	                                                nm_device_get_route_metric (self, AF_INET6),
 	                                                nm_setting_ip_config_get_dhcp_send_hostname (s_ip6),
 	                                                nm_setting_ip_config_get_dhcp_hostname (s_ip6),
@@ -9087,7 +9369,8 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		s_ip6 = nm_device_get_applied_setting (self, NM_TYPE_SETTING_IP6_CONFIG);
 		if (   s_ip6
 		    && !NM_IN_STRSET (nm_setting_ip_config_get_method (s_ip6),
-		                      NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+		                      NM_SETTING_IP6_CONFIG_METHOD_IGNORE
+		                      NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
 			/* the interface has IPv6 enabled. The MTU with IPv6 cannot be smaller
 			 * then 1280.
 			 *
@@ -9262,7 +9545,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 		                                  rdata->gateways_n,
 		                                  rdata->routes,
 		                                  rdata->routes_n,
-		                                  nm_device_get_route_table (self, AF_INET6, TRUE),
+		                                  nm_device_get_route_table (self, AF_INET6),
 		                                  nm_device_get_route_metric (self, AF_INET6),
 		                                  nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
 		if (priv->ac_ip6_config.current) {
@@ -9271,7 +9554,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in
 			                                  rdata->gateways_n,
 			                                  rdata->routes,
 			                                  rdata->routes_n,
-			                                  nm_device_get_route_table (self, AF_INET6, TRUE),
+			                                  nm_device_get_route_table (self, AF_INET6),
 			                                  nm_device_get_route_metric (self, AF_INET6),
 			                                  nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
 		}
@@ -9763,7 +10046,7 @@ act_stage3_ip_config_start (NMDevice *self,
 				                             nm_connection_get_setting_ip4_config (connection),
 				                             NM_SETTING_CONNECTION_MDNS_DEFAULT,
 				                             NM_SETTING_CONNECTION_LLMNR_DEFAULT,
-				                             nm_device_get_route_table (self, AF_INET, TRUE),
+				                             nm_device_get_route_table (self, AF_INET),
 				                             nm_device_get_route_metric (self, AF_INET));
 				configs = g_new0 (NMIP4Config *, 2);
 				configs[0] = config;
@@ -9795,6 +10078,11 @@ act_stage3_ip_config_start (NMDevice *self,
 		NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 		const char *ip6_privacy_str = "0";
 
+		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+			nm_device_sysctl_ip_conf_set (self, AF_INET6, "disable_ipv6", "1");
+			return NM_ACT_STAGE_RETURN_IP_DONE;
+		}
+
 		if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
 			if (   !priv->master
 			    && !nm_device_sys_iface_state_is_external (self)) {
@@ -10948,7 +11236,7 @@ nm_device_reactivate_ip4_config (NMDevice *self,
 		                             s_ip4_new,
 		                             _get_mdns (self),
 		                             _get_llmnr (self),
-		                             nm_device_get_route_table (self, AF_INET, TRUE),
+		                             nm_device_get_route_table (self, AF_INET),
 		                             nm_device_get_route_metric (self, AF_INET));
 
 		method_old = s_ip4_old
@@ -11022,7 +11310,7 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 		priv->con_ip_config_6 = nm_device_ip6_config_new (self);
 		nm_ip6_config_merge_setting (priv->con_ip_config_6,
 		                             s_ip6_new,
-		                             nm_device_get_route_table (self, AF_INET6, TRUE),
+		                             nm_device_get_route_table (self, AF_INET6),
 		                             nm_device_get_route_metric (self, AF_INET6));
 
 		method_old = s_ip6_old
@@ -11073,21 +11361,17 @@ nm_device_reactivate_ip6_config (NMDevice *self,
 }
 
 static void
-_pacrunner_manager_send (NMDevice *self)
+_pacrunner_manager_add (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	nm_pacrunner_manager_remove_clear (priv->pacrunner_manager,
-	                                   &priv->pacrunner_call_id);
+	nm_pacrunner_manager_remove_clear (&priv->pacrunner_conf_id);
 
-	if (!priv->pacrunner_manager)
-		priv->pacrunner_manager = g_object_ref (nm_pacrunner_manager_get ());
-
-	priv->pacrunner_call_id = nm_pacrunner_manager_send (priv->pacrunner_manager,
-	                                                     nm_device_get_ip_iface (self),
-	                                                     priv->proxy_config,
-	                                                     NULL,
-	                                                     NULL);
+	priv->pacrunner_conf_id = nm_pacrunner_manager_add (nm_pacrunner_manager_get (),
+	                                                    priv->proxy_config,
+	                                                    nm_device_get_ip_iface (self),
+	                                                    NULL,
+	                                                    NULL);
 }
 
 static void
@@ -11095,16 +11379,19 @@ reactivate_proxy_config (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (!priv->pacrunner_call_id)
+	if (!priv->pacrunner_conf_id)
 		return;
 	nm_device_set_proxy_config (self, priv->dhcp4.pac_url);
-	_pacrunner_manager_send (self);
+	_pacrunner_manager_add (self);
 }
 
 static gboolean
-can_reapply_change (NMDevice *self, const char *setting_name,
-                    NMSetting *s_old, NMSetting *s_new,
-                    GHashTable *diffs, GError **error)
+can_reapply_change (NMDevice *self,
+                    const char *setting_name,
+                    NMSetting *s_old,
+                    NMSetting *s_new,
+                    GHashTable *diffs,
+                    GError **error)
 {
 	if (nm_streq (setting_name, NM_SETTING_CONNECTION_SETTING_NAME)) {
 		/* Whitelist allowed properties from "connection" setting which are
@@ -11127,32 +11414,9 @@ can_reapply_change (NMDevice *self, const char *setting_name,
 		                                          NM_SETTING_CONNECTION_MDNS,
 		                                          NM_SETTING_CONNECTION_LLMNR);
 	} else if (NM_IN_STRSET (setting_name,
-	                         NM_SETTING_PROXY_SETTING_NAME)) {
-		return TRUE;
-	} else if (NM_IN_STRSET (setting_name,
+	                         NM_SETTING_PROXY_SETTING_NAME,
 	                         NM_SETTING_IP4_CONFIG_SETTING_NAME,
 	                         NM_SETTING_IP6_CONFIG_SETTING_NAME)) {
-		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,
@@ -11278,7 +11542,7 @@ check_and_reapply_connection (NMDevice *self,
 			    || !nm_streq0 (nm_setting_connection_get_stable_id (s_con_a), nm_setting_connection_get_stable_id (s_con_n))) {
 				connection_clean_free = nm_simple_connection_new_clone (connection);
 				connection_clean = connection_clean_free;
-				s_con_n = nm_connection_get_setting_connection (connection);
+				s_con_n = nm_connection_get_setting_connection (connection_clean);
 				g_object_set (s_con_n,
 				              NM_SETTING_CONNECTION_ID, nm_setting_connection_get_id (s_con_a),
 				              NM_SETTING_CONNECTION_UUID, nm_setting_connection_get_uuid (s_con_a),
@@ -11298,8 +11562,17 @@ check_and_reapply_connection (NMDevice *self,
 	priv->v4_commit_first_time = TRUE;
 	priv->v6_commit_first_time = TRUE;
 
+	priv->v4_route_table_initialized = FALSE;
+	priv->v6_route_table_initialized = FALSE;
+
 	/**************************************************************************
 	 * Reapply changes
+	 *
+	 * Note that reapply_connection() is called as very first. This is for example
+	 * important for NMDeviceWireGuard, which implements coerce_route_table()
+	 * and get_extra_rules().
+	 * That is because NMDeviceWireGuard caches settings, so during reapply that
+	 * cache must be updated *first*.
 	 *************************************************************************/
 	klass->reapply_connection (self, con_old, con_new);
 
@@ -11312,9 +11585,14 @@ check_and_reapply_connection (NMDevice *self,
 	s_ip6_old = nm_connection_get_setting_ip6_config (con_old);
 	s_ip6_new = nm_connection_get_setting_ip6_config (con_new);
 
+	/* Allow reapply of MTU */
+	priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
+
 	nm_device_reactivate_ip4_config (self, s_ip4_old, s_ip4_new);
 	nm_device_reactivate_ip6_config (self, s_ip6_old, s_ip6_new);
 
+	_routing_rules_sync (self, NM_TERNARY_TRUE);
+
 	reactivate_proxy_config (self);
 
 	return TRUE;
@@ -12097,9 +12375,7 @@ nm_device_set_ip_config (NMDevice *self,
 		if (IS_IPv4) {
 			success = nm_ip4_config_commit (NM_IP4_CONFIG (new_config),
 			                                nm_device_get_platform (self),
-			                                nm_device_get_route_table (self, addr_family, FALSE)
-			                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
-			                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN);
+			                                _get_route_table_sync_mode_stateful (self, addr_family));
 			nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self),
 			                                         nm_ip_config_get_ifindex (new_config),
 			                                         ip4_dev_route_blacklist);
@@ -12108,9 +12384,7 @@ nm_device_set_ip_config (NMDevice *self,
 
 			success = nm_ip6_config_commit (NM_IP6_CONFIG (new_config),
 			                                nm_device_get_platform (self),
-			                                nm_device_get_route_table (self, addr_family, FALSE)
-			                                  ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
-			                                  : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN,
+			                                _get_route_table_sync_mode_stateful (self, addr_family),
 			                                &temporary_not_available);
 
 			if (!_rt6_temporary_not_available_set (self, temporary_not_available))
@@ -12173,16 +12447,28 @@ nm_device_set_ip_config (NMDevice *self,
 
 		if (   nm_device_sys_iface_state_is_external (self)
 		    && (settings_connection = nm_device_get_settings_connection (self))
-		    && NM_FLAGS_HAS (nm_settings_connection_get_flags (settings_connection),
-		                     NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
+		    && NM_FLAGS_ALL (nm_settings_connection_get_flags (settings_connection),
+		                       NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED
+		                     | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+		                     | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
 		    && nm_active_connection_get_activation_type (NM_ACTIVE_CONNECTION (priv->act_request.obj)) == NM_ACTIVATION_TYPE_EXTERNAL) {
-			g_object_freeze_notify (G_OBJECT (settings_connection));
-			/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-			nm_connection_add_setting (nm_settings_connection_get_connection (settings_connection),
+			gs_unref_object NMConnection *new_connection = NULL;
+
+			new_connection = nm_simple_connection_new_clone (nm_settings_connection_get_connection (settings_connection));
+
+			nm_connection_add_setting (new_connection,
 			                           IS_IPv4
 			                             ? nm_ip4_config_create_setting (priv->ip_config_4)
 			                             : nm_ip6_config_create_setting (priv->ip_config_6));
-			g_object_thaw_notify (G_OBJECT (settings_connection));
+
+			nm_settings_connection_update (settings_connection,
+			                               new_connection,
+			                               NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+			                               NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+			                               NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+			                               NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
+			                               "update-external",
+			                               NULL);
 		}
 
 		nm_device_queue_recheck_assume (self);
@@ -12307,29 +12593,31 @@ nm_device_get_ip6_config (NMDevice *self)
 
 /*****************************************************************************/
 
-static void
+static gboolean
 dispatcher_cleanup (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->dispatcher.call_id) {
-		nm_dispatcher_call_cancel (priv->dispatcher.call_id);
-		priv->dispatcher.call_id = 0;
-		priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN;
-		priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
-	}
+	if (!priv->dispatcher.call_id)
+		return FALSE;
+
+	nm_dispatcher_call_cancel (g_steal_pointer (&priv->dispatcher.call_id));
+	priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN;
+	priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
+	return TRUE;
 }
 
 static void
-dispatcher_complete_proceed_state (guint call_id, gpointer user_data)
+dispatcher_complete_proceed_state (NMDispatcherCallId *call_id, gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
 	g_return_if_fail (call_id == priv->dispatcher.call_id);
 
-	priv->dispatcher.call_id = 0;
-	nm_device_queue_state (self, priv->dispatcher.post_state,
+	priv->dispatcher.call_id = NULL;
+	nm_device_queue_state (self,
+	                       priv->dispatcher.post_state,
 	                       priv->dispatcher.post_state_reason);
 	priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN;
 	priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
@@ -12342,10 +12630,8 @@ ip_check_pre_up (NMDevice *self)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 
-	if (priv->dispatcher.call_id != 0) {
-		g_warn_if_reached ();
-		dispatcher_cleanup (self);
-	}
+	if (dispatcher_cleanup (self))
+		nm_assert_not_reached ();
 
 	priv->dispatcher.post_state = NM_DEVICE_STATE_SECONDARIES;
 	priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
@@ -12883,7 +13169,8 @@ update_ext_ip_config (NMDevice *self, int addr_family, gboolean intersect_config
 				for (iter = priv->vpn_configs_6; iter; iter = iter->next)
 					nm_ip6_config_intersect (iter->data, priv->ext_ip_config_6, is_up, is_up, 0);
 
-				if (   priv->ipv6ll_has
+				if (   is_up
+				    && priv->ipv6ll_has
 				    && !nm_ip6_config_lookup_address (priv->ext_ip_config_6, &priv->ipv6ll_addr))
 					priv->ipv6ll_has = FALSE;
 			}
@@ -13218,7 +13505,7 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
 			return TRUE;
 
 		/* A for-user-request, is effectively the same as pretending
-		 * that user-dbus flag is cleared. */
+		 * that user-explicit flag is cleared. */
 		mask |= NM_UNMANAGED_USER_EXPLICIT;
 		flags &= ~NM_UNMANAGED_USER_EXPLICIT;
 	}
@@ -13270,6 +13557,9 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo
  * nm_device_get_managed:
  * @self: the #NMDevice
  * @for_user_request: whether to check the flags for an explicit user-request
+ *   Setting this to %TRUE has the same effect as if %NM_UNMANAGED_USER_EXPLICIT
+ *   unmanaged flag would be unset (meaning: explicitly not-unmanaged).
+ *   If this parameter is %TRUE, the device can only appear more managed.
  *
  * Whether the device is unmanaged according to the unmanaged flags.
  *
@@ -13839,13 +14129,17 @@ _nm_device_check_connection_available (NMDevice *self,
 		return FALSE;
 	}
 	if (state < NM_DEVICE_STATE_UNAVAILABLE) {
-		if (!nm_device_get_managed (self, TRUE)) {
-			if (!nm_device_get_managed (self, FALSE)) {
+		if (nm_device_get_managed (self, FALSE)) {
+			/* device is managed, both for user-requests and non-user-requests alike. */
+		} else {
+			if (!nm_device_get_managed (self, TRUE)) {
+				/* device is strictly unmanaged by authoritative unmanaged reasons. */
 				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
 				                            "device is strictly unmanaged");
 				return FALSE;
 			}
 			if (!NM_FLAGS_HAS (flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED)) {
+				/* device could be managed for an explict user-request, but this is not such a request. */
 				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
 				                            "device is currently unmanaged");
 				return FALSE;
@@ -14126,7 +14420,7 @@ cp_connection_added (NMSettings *settings, NMSettingsConnection *sett_conn, gpoi
 }
 
 static void
-cp_connection_updated (NMSettings *settings, NMSettingsConnection *sett_conn, gboolean by_user, gpointer user_data)
+cp_connection_updated (NMSettings *settings, NMSettingsConnection *sett_conn, guint update_reason_u, gpointer user_data)
 {
 	cp_connection_added_or_updated (user_data, sett_conn);
 }
@@ -14339,6 +14633,9 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	priv->v4_route_table_initialized = FALSE;
 	priv->v6_route_table_initialized = FALSE;
 
+	priv->v4_route_table_full_sync_before = FALSE;
+	priv->v6_route_table_full_sync_before = FALSE;
+
 	priv->default_route_metric_penalty_ip4_has = FALSE;
 	priv->default_route_metric_penalty_ip6_has = FALSE;
 
@@ -14732,6 +15029,34 @@ ip6_managed_setup (NMDevice *self)
 }
 
 static void
+deactivate_ready (NMDevice *self, NMDeviceStateReason reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->dispatcher.call_id)
+		return;
+
+	if (priv->sriov.pending)
+		return;
+	nm_assert (!priv->sriov.next);
+
+	nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
+}
+
+static void
+sriov_deactivate_cb (GError *error, gpointer user_data)
+{
+	NMDevice *self;
+	gpointer reason;
+
+	if (nm_utils_error_is_cancelled (error, TRUE))
+		return;
+
+	nm_utils_user_data_unpack (user_data, &self, &reason);
+	deactivate_ready (self, (NMDeviceStateReason) reason);
+}
+
+static void
 deactivate_async_ready (NMDevice *self,
                         GError *error,
                         gpointer user_data)
@@ -14751,11 +15076,12 @@ deactivate_async_ready (NMDevice *self,
 		_LOGW (LOGD_DEVICE, "Deactivation failed: %s",
 		       error->message);
 	}
-	nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
+
+	deactivate_ready (self, reason);
 }
 
 static void
-deactivate_dispatcher_complete (guint call_id, gpointer user_data)
+deactivate_dispatcher_complete (NMDispatcherCallId *call_id, gpointer user_data)
 {
 	NMDevice *self = NM_DEVICE (user_data);
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
@@ -14764,9 +15090,9 @@ deactivate_dispatcher_complete (guint call_id, gpointer user_data)
 	g_return_if_fail (call_id == priv->dispatcher.call_id);
 	g_return_if_fail (priv->dispatcher.post_state == NM_DEVICE_STATE_DISCONNECTED);
 
-	reason = priv->dispatcher.post_state_reason;
+	reason = priv->state_reason;
 
-	priv->dispatcher.call_id = 0;
+	priv->dispatcher.call_id = NULL;
 	priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN;
 	priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
 
@@ -14780,7 +15106,7 @@ deactivate_dispatcher_complete (guint call_id, gpointer user_data)
 		                                              deactivate_async_ready,
 		                                              GUINT_TO_POINTER (reason));
 	} else
-		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
+		deactivate_ready (self, reason);
 }
 
 static void
@@ -14993,12 +15319,6 @@ _set_state_full (NMDevice *self,
 		}
 		break;
 	case NM_DEVICE_STATE_DEACTIVATING:
-		if (   (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))
-		    && priv->ifindex > 0) {
-			nm_platform_link_set_sriov_params (nm_device_get_platform (self),
-			                                   priv->ifindex, 0, NM_TERNARY_TRUE);
-		}
-
 		_cancel_activation (self);
 
 		/* We cache the ignore_carrier state to not react on config-reloads while the connection
@@ -15020,10 +15340,18 @@ _set_state_full (NMDevice *self,
 				/* Just proceed on errors */
 				deactivate_dispatcher_complete (0, self);
 			}
+
+			if (   priv->ifindex > 0
+			    && (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
+				sriov_op_queue (self,
+				                0,
+				                NM_TERNARY_TRUE,
+				                sriov_deactivate_cb,
+				                nm_utils_user_data_pack (self, (gpointer) reason));
+			}
 		}
 
-		nm_pacrunner_manager_remove_clear (priv->pacrunner_manager,
-		                                   &priv->pacrunner_call_id);
+		nm_pacrunner_manager_remove_clear (&priv->pacrunner_conf_id);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		if (   priv->queued_act_request
@@ -15043,7 +15371,7 @@ _set_state_full (NMDevice *self,
 		                           NULL, NULL, NULL);
 
 		if (priv->proxy_config)
-			_pacrunner_manager_send (self);
+			_pacrunner_manager_add (self);
 		break;
 	case NM_DEVICE_STATE_FAILED:
 		/* Usually upon failure the activation chain is interrupted in
@@ -15076,7 +15404,7 @@ _set_state_full (NMDevice *self,
 		 * and those we haven't tried yet (no timestamp).
 		 */
 		if (sett_conn && !nm_settings_connection_get_timestamp (sett_conn, NULL))
-			nm_settings_connection_update_timestamp (sett_conn, (guint64) 0, TRUE);
+			nm_settings_connection_update_timestamp (sett_conn, (guint64) 0);
 
 		/* Schedule the transition to DISCONNECTED.  The device can't transition
 		 * immediately because we can't change states again from the state
@@ -16286,9 +16614,7 @@ dispose (GObject *object)
 
 	dispatcher_cleanup (self);
 
-	nm_pacrunner_manager_remove_clear (priv->pacrunner_manager,
-	                                   &priv->pacrunner_call_id);
-	g_clear_object (&priv->pacrunner_manager);
+	nm_pacrunner_manager_remove_clear (&priv->pacrunner_conf_id);
 
 	_cleanup_generic_pre (self, CLEANUP_TYPE_KEEP);
 
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index d11c33ab..ae6aab39 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -321,6 +320,12 @@ typedef struct _NMDeviceClass {
 
 	void        (* set_enabled) (NMDevice *self, gboolean enabled);
 
+	/* let the subclass return additional NMPlatformRoutingRule (in form of NMPObject
+	 * pointers) that shall be added to the rules provided by this device.
+	 * The returned GPtrArray will be g_ptr_array_unref()'ed. The subclass may or
+	 * may not keep an additional reference and return this array again and again. */
+	GPtrArray *(*get_extra_rules) (NMDevice *self);
+
 	/* allow derived classes to override the result of nm_device_autoconnect_allowed().
 	 * If the value changes, the class should call nm_device_emit_recheck_auto_activate(),
 	 * which emits NM_DEVICE_RECHECK_AUTO_ACTIVATE signal. */
@@ -332,6 +337,13 @@ typedef struct _NMDeviceClass {
 
 	guint32     (*get_configured_mtu) (NMDevice *self, NMDeviceMtuSource *out_source);
 
+	/* allow the subclass to overwrite the routing table. This is mainly useful
+	 * to change from partial mode (route-table=0) to full-sync mode (route-table=254). */
+	guint32     (*coerce_route_table) (NMDevice *self,
+	                                   int addr_family,
+	                                   guint32 route_table,
+	                                   gboolean is_user_config);
+
 	const char *(*get_auto_ip_config_method) (NMDevice *self, int addr_family);
 
 	/* Checks whether the connection is compatible with the device using
@@ -497,7 +509,7 @@ NMDeviceType    nm_device_get_device_type       (NMDevice *dev);
 NMLinkType      nm_device_get_link_type         (NMDevice *dev);
 NMMetered       nm_device_get_metered           (NMDevice *dev);
 
-guint32         nm_device_get_route_table       (NMDevice *self, int addr_family, gboolean fallback_main);
+guint32         nm_device_get_route_table       (NMDevice *self, int addr_family);
 guint32         nm_device_get_route_metric      (NMDevice *dev, int addr_family);
 
 guint32         nm_device_get_route_metric_default (NMDeviceType device_type);
diff --git a/src/devices/nm-lldp-listener.c b/src/devices/nm-lldp-listener.c
index 831054ba..c0d78315 100644
--- a/src/devices/nm-lldp-listener.c
+++ b/src/devices/nm-lldp-listener.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/nm-lldp-listener.h b/src/devices/nm-lldp-listener.h
index 1afa8456..c44ca59f 100644
--- a/src/devices/nm-lldp-listener.h
+++ b/src/devices/nm-lldp-listener.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index e3d3f9ee..83de3c3d 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -95,6 +95,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 	s_ovs_iface = nm_connection_get_setting_ovs_interface (connection);
 
 	if (!NM_IN_STRSET (nm_setting_ovs_interface_get_interface_type (s_ovs_iface),
+	                   "dpdk",
 	                   "internal",
 	                   "patch")) {
 		nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
diff --git a/src/devices/ovs/nm-device-ovs-port.c b/src/devices/ovs/nm-device-ovs-port.c
index 35eb739f..8a93a5a9 100644
--- a/src/devices/ovs/nm-device-ovs-port.c
+++ b/src/devices/ovs/nm-device-ovs-port.c
@@ -140,13 +140,18 @@ del_iface_cb (GError *error, gpointer user_data)
 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));
-
-	/* Open VSwitch is going to delete this one. We must ignore what happens
-	 * next with the interface. */
-	if (NM_IS_DEVICE_OVS_INTERFACE (slave))
-		nm_device_update_from_platform_link (slave, NULL);
+	NMDeviceOvsPort *self = NM_DEVICE_OVS_PORT (device);
+
+	if (configure) {
+		_LOGI (LOGD_DEVICE, "releasing ovs interface %s", nm_device_get_ip_iface (slave));
+		nm_ovsdb_del_interface (nm_ovsdb_get (), nm_device_get_iface (slave),
+		                        del_iface_cb, g_object_ref (slave));
+		/* Open VSwitch is going to delete this one. We must ignore what happens
+		 * next with the interface. */
+		if (NM_IS_DEVICE_OVS_INTERFACE (slave))
+			nm_device_update_from_platform_link (slave, NULL);
+	} else
+		_LOGI (LOGD_DEVICE, "ovs interface %s was released", nm_device_get_ip_iface (slave));
 }
 
 /*****************************************************************************/
diff --git a/src/devices/ovs/nm-ovs-factory.c b/src/devices/ovs/nm-ovs-factory.c
index 2124b2a0..fdf07bd3 100644
--- a/src/devices/ovs/nm-ovs-factory.c
+++ b/src/devices/ovs/nm-ovs-factory.c
@@ -26,7 +26,9 @@
 #include "nm-device-ovs-bridge.h"
 #include "platform/nm-platform.h"
 #include "nm-core-internal.h"
+#include "settings/nm-settings.h"
 #include "devices/nm-device-factory.h"
+#include "devices/nm-device-private.h"
 
 /*****************************************************************************/
 
@@ -51,7 +53,13 @@ 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__)
+#define _NMLOG(level, ifname, con_uuid, ...) \
+        G_STMT_START { \
+                nm_log ((level), _NMLOG_DOMAIN, (ifname), (con_uuid), \
+                        "ovs: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) \
+                        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } G_STMT_END
+
 
 /*****************************************************************************/
 
@@ -139,6 +147,36 @@ ovsdb_device_removed (NMOvsdb *ovsdb, const char *name, NMDeviceType device_type
 }
 
 static void
+ovsdb_interface_failed (NMOvsdb *ovsdb,
+                        const char *name,
+                        const char *connection_uuid,
+                        const char *error,
+                        NMDeviceFactory *self)
+{
+	NMDevice *device = NULL;
+	NMSettingsConnection *connection = NULL;
+
+	_LOGI (name, connection_uuid, "ovs interface \"%s\" (%s) failed: %s", name, connection_uuid, error);
+
+	device = nm_manager_get_device (nm_manager_get (), name, NM_DEVICE_TYPE_OVS_INTERFACE);
+	if (!device)
+		return;
+
+	if (connection_uuid)
+		connection = nm_settings_get_connection_by_uuid (nm_device_get_settings (device), connection_uuid);
+
+	if (connection) {
+		nm_settings_connection_autoconnect_blocked_reason_set (connection,
+		                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+		                                                       TRUE);
+	}
+
+	nm_device_state_changed (device,
+	                         NM_DEVICE_STATE_FAILED,
+	                         NM_DEVICE_STATE_REASON_OVSDB_FAILED);
+}
+
+static void
 start (NMDeviceFactory *self)
 {
 	NMOvsdb *ovsdb;
@@ -147,6 +185,7 @@ start (NMDeviceFactory *self)
 
 	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);
+	g_signal_connect_object (ovsdb, NM_OVSDB_INTERFACE_FAILED, G_CALLBACK (ovsdb_interface_failed), self, (GConnectFlags) 0);
 }
 
 static NMDevice *
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index 5b50f840..e5de466c 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -25,8 +25,7 @@
 #include <gio/gunixsocketaddress.h>
 
 #include "nm-glib-aux/nm-jansson.h"
-#include "devices/nm-device.h"
-#include "platform/nm-platform.h"
+#include "nm-core-utils.h"
 #include "nm-core-internal.h"
 
 /*****************************************************************************/
@@ -58,7 +57,7 @@ typedef struct {
 enum {
 	DEVICE_ADDED,
 	DEVICE_REMOVED,
-	DEVICE_CHANGED,
+	INTERFACE_FAILED,
 	LAST_SIGNAL
 };
 
@@ -341,6 +340,7 @@ _insert_interface (json_t *params, NMConnection *interface)
 {
 	const char *type = NULL;
 	NMSettingOvsInterface *s_ovs_iface;
+	NMSettingOvsDpdk *s_ovs_dpdk;
 	NMSettingOvsPatch *s_ovs_patch;
 	json_t *options = json_array ();
 
@@ -349,11 +349,20 @@ _insert_interface (json_t *params, NMConnection *interface)
 		type = nm_setting_ovs_interface_get_interface_type (s_ovs_iface);
 
 	json_array_append_new (options, json_string ("map"));
-	s_ovs_patch = nm_connection_get_setting_ovs_patch (interface);
-	if (s_ovs_patch) {
+
+	s_ovs_dpdk = (NMSettingOvsDpdk *) nm_connection_get_setting (interface,
+	                                                             NM_TYPE_SETTING_OVS_DPDK);
+	if (!s_ovs_dpdk)
+		s_ovs_patch = nm_connection_get_setting_ovs_patch (interface);
+
+	if (s_ovs_dpdk) {
+		json_array_append_new (options, json_pack ("[[s, s]]",
+		                                           "dpdk-devargs",
+		                                           nm_setting_ovs_dpdk_get_devargs (s_ovs_dpdk)));
+	} else if (s_ovs_patch) {
 		json_array_append_new (options, json_pack ("[[s, s]]",
-		                                       "peer",
-		                                        nm_setting_ovs_patch_get_peer (s_ovs_patch)));
+		                                           "peer",
+		                                           nm_setting_ovs_patch_get_peer (s_ovs_patch)));
 	} else {
 		json_array_append_new (options, json_array ());
 	}
@@ -436,6 +445,7 @@ _insert_bridge (json_t *params, NMConnection *bridge, json_t *new_ports)
 	gboolean mcast_snooping_enable = FALSE;
 	gboolean rstp_enable = FALSE;
 	gboolean stp_enable = FALSE;
+	const char *datapath_type = NULL;
 	json_t *row;
 
 	s_ovs_bridge = nm_connection_get_setting_ovs_bridge (bridge);
@@ -447,6 +457,7 @@ _insert_bridge (json_t *params, NMConnection *bridge, json_t *new_ports)
 		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);
+		datapath_type = nm_setting_ovs_bridge_get_datapath_type (s_ovs_bridge);
 	}
 
 	if (fail_mode)
@@ -457,6 +468,8 @@ _insert_bridge (json_t *params, NMConnection *bridge, json_t *new_ports)
 		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));
+	if (datapath_type)
+		json_object_set_new (row, "datapath_type", json_string (datapath_type));
 
 	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));
@@ -534,9 +547,14 @@ _add_interface (NMOvsdb *self, json_t *params,
 
 			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)
+			if (!ovs_port) {
+				/* This would be a violation of ovsdb's reference integrity (a bug). */
+				_LOGW ("Unknown port '%s' in bridge '%s'", port_uuid, bridge_uuid);
 				continue;
+			} else if (   strcmp (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);
@@ -544,9 +562,13 @@ _add_interface (NMOvsdb *self, json_t *params,
 
 				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)
+				if (!ovs_interface) {
+					/* This would be a violation of ovsdb's reference integrity (a bug). */
+					_LOGW ("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
+				} else if (   strcmp (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;
@@ -642,16 +664,27 @@ _delete_interface (NMOvsdb *self, json_t *params, const char *ifname)
 
 			interfaces_changed = FALSE;
 
+			if (!ovs_port) {
+				/* This would be a violation of ovsdb's reference integrity (a bug). */
+				_LOGW ("Unknown port '%s' in bridge '%s'", port_uuid, bridge_uuid);
+				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 (strcmp (ovs_interface->name, ifname) == 0) {
-					/* skip the interface */
-					interfaces_changed = TRUE;
-					continue;
+				if (ovs_interface) {
+					if (strcmp (ovs_interface->name, ifname) == 0) {
+						/* skip the interface */
+						interfaces_changed = TRUE;
+						continue;
+					}
+				} else {
+					/* This would be a violation of ovsdb's reference integrity (a bug). */
+					_LOGW ("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
 				}
 
 				json_array_append_new (new_interfaces, json_pack ("[s,s]", "uuid", interface_uuid));
@@ -718,14 +751,14 @@ ovsdb_next_command (NMOvsdb *self)
 		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:[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",
+		                 "Interface", "columns", "name", "type", "external_ids", "error",
 		                 "Open_vSwitch", "columns");
 		break;
 	case OVSDB_ADD_INTERFACE:
@@ -864,21 +897,25 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 
 	/* Interfaces */
 	json_object_foreach (interface, key, value) {
+		json_t *error = NULL;
 		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",
+		if (json_unpack (value, "{s:{s:s, s:s, s?:o, s:o}}", "new",
 		                 "name", &name,
 		                 "type", &type,
+		                 "error", &error,
 		                 "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) {
+			if (!ovs_interface) {
+				_LOGW ("Interface '%s' was not seen", key);
+			} else if (!new || strcmp (ovs_interface->name, name) != 0) {
 				old = FALSE;
 				_LOGT ("removed an '%s' interface: %s%s%s",
 				       ovs_interface->type, ovs_interface->name,
@@ -899,12 +936,11 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 			ovs_interface->name = g_strdup (name);
 			ovs_interface->type = g_strdup (type);
 			ovs_interface->connection_uuid = _connection_uuid_from_external_ids (external_ids);
+			g_hash_table_insert (priv->interfaces, g_strdup (key), ovs_interface);
 			if (old) {
 				_LOGT ("changed an '%s' interface: %s%s%s", type, ovs_interface->name,
 				       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,
@@ -917,7 +953,14 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 					               ovs_interface->name, NM_DEVICE_TYPE_OVS_INTERFACE);
 				}
 			}
-			g_hash_table_insert (priv->interfaces, g_strdup (key), ovs_interface);
+			/* The error is a string. No error is indicated by an empty set,
+			 * because why the fuck not: [ "set": [] ] */
+			if (error && json_is_string (error)) {
+				g_signal_emit (self, signals[INTERFACE_FAILED], 0,
+				               ovs_interface->name,
+				               ovs_interface->connection_uuid,
+				               json_string_value (error));
+			}
 		}
 	}
 
@@ -954,12 +997,11 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 			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);
+			g_hash_table_insert (priv->ports, g_strdup (key), ovs_port);
 			if (old) {
 				_LOGT ("changed a port: %s%s%s", ovs_port->name,
 				       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 ? ", " : "",
@@ -967,7 +1009,6 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 				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);
 		}
 	}
 
@@ -1004,12 +1045,11 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 			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);
+			g_hash_table_insert (priv->bridges, g_strdup (key), ovs_bridge);
 			if (old) {
 				_LOGT ("changed a bridge: %s%s%s", ovs_bridge->name,
 				       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 ? ", " : "",
@@ -1017,7 +1057,6 @@ ovsdb_got_update (NMOvsdb *self, json_t *msg)
 				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);
 		}
 	}
 
@@ -1562,19 +1601,19 @@ nm_ovsdb_class_init (NMOvsdbClass *klass)
 		              G_OBJECT_CLASS_TYPE (object_class),
 		              G_SIGNAL_RUN_LAST,
 		              0, NULL, NULL, NULL,
-		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+		              G_TYPE_NONE, 2, G_TYPE_STRING, 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);
+		              G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_UINT);
 
-	signals[DEVICE_CHANGED] =
-		g_signal_new (NM_OVSDB_DEVICE_CHANGED,
+	signals[INTERFACE_FAILED] =
+		g_signal_new (NM_OVSDB_INTERFACE_FAILED,
 		              G_OBJECT_CLASS_TYPE (object_class),
 		              G_SIGNAL_RUN_LAST,
 		              0, NULL, NULL, NULL,
-		              G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT);
+		              G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
 }
diff --git a/src/devices/ovs/nm-ovsdb.h b/src/devices/ovs/nm-ovsdb.h
index cf9fe2a2..279155a4 100644
--- a/src/devices/ovs/nm-ovsdb.h
+++ b/src/devices/ovs/nm-ovsdb.h
@@ -27,9 +27,9 @@
 #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"
+#define NM_OVSDB_DEVICE_ADDED      "device-added"
+#define NM_OVSDB_DEVICE_REMOVED    "device-removed"
+#define NM_OVSDB_INTERFACE_FAILED  "interface-failed"
 
 typedef struct _NMOvsdb NMOvsdb;
 typedef struct _NMOvsdbClass NMOvsdbClass;
diff --git a/src/devices/team/nm-device-team.c b/src/devices/team/nm-device-team.c
index 287f4d1b..a60a9fda 100644
--- a/src/devices/team/nm-device-team.c
+++ b/src/devices/team/nm-device-team.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -101,6 +100,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("Team connection"),
 	                           "team",
+	                           NULL,
 	                           TRUE);
 
 	s_team = nm_connection_get_setting_team (connection);
@@ -694,6 +694,9 @@ deactivate (NMDevice *device)
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 
+	if (nm_device_sys_iface_state_is_external (device))
+		return;
+
 	if (priv->teamd_pid || priv->tdc)
 		_LOGI (LOGD_TEAM, "deactivation: stopping teamd...");
 
@@ -771,11 +774,19 @@ release_slave (NMDevice *device,
 	NMDeviceTeam *self = NM_DEVICE_TEAM (device);
 	NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE (self);
 	gboolean success;
+	int ifindex_slave;
+
+	ifindex_slave = nm_device_get_ip_ifindex (slave);
+
+	if (ifindex_slave <= 0) {
+		_LOGD (LOGD_TEAM, "team port %s is already released", nm_device_get_ip_iface (slave));
+		return;
+	}
 
 	if (configure) {
 		success = nm_platform_link_release (nm_device_get_platform (device),
 		                                    nm_device_get_ip_ifindex (device),
-		                                    nm_device_get_ip_ifindex (slave));
+		                                    ifindex_slave);
 
 		if (success)
 			_LOGI (LOGD_TEAM, "released team port %s", nm_device_get_ip_iface (slave));
@@ -786,9 +797,10 @@ release_slave (NMDevice *device,
 		 * IFF_UP), so we must bring it back up here to ensure carrier changes and
 		 * other state is noticed by the now-released port.
 		 */
-		if (!nm_device_bring_up (slave, TRUE, NULL))
+		if (!nm_device_bring_up (slave, TRUE, NULL)) {
 			_LOGW (LOGD_TEAM, "released team port %s could not be brought up",
 			       nm_device_get_ip_iface (slave));
+		}
 
 		nm_clear_g_source (&priv->teamd_read_timeout);
 		priv->teamd_read_timeout = g_timeout_add_seconds (5,
diff --git a/src/devices/team/nm-device-team.h b/src/devices/team/nm-device-team.h
index bbb1417f..0c0b5101 100644
--- a/src/devices/team/nm-device-team.h
+++ b/src/devices/team/nm-device-team.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * Copyright (C) 2013 Jiri Pirko <jiri@resnulli.us>
diff --git a/src/devices/team/nm-team-factory.c b/src/devices/team/nm-team-factory.c
index f18b943d..21a85701 100644
--- a/src/devices/team/nm-team-factory.c
+++ b/src/devices/team/nm-team-factory.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/tests/meson.build b/src/devices/tests/meson.build
index 4702c656..4b4c5c7e 100644
--- a/src/devices/tests/meson.build
+++ b/src/devices/tests/meson.build
@@ -9,7 +9,6 @@ foreach test_unit: test_units
     test_unit + '.c',
     dependencies: test_nm_dep,
   )
-
   test(
     'devices/' + test_unit,
     test_script,
diff --git a/src/devices/tests/test-acd.c b/src/devices/tests/test-acd.c
index 8b52ee2c..42accfaf 100644
--- a/src/devices/tests/test-acd.c
+++ b/src/devices/tests/test-acd.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/tests/test-lldp.c b/src/devices/tests/test-lldp.c
index 7b135f58..ad157b7e 100644
--- a/src/devices/tests/test-lldp.c
+++ b/src/devices/tests/test-lldp.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -457,7 +456,7 @@ _test_recv_fixture_setup (TestRecvFixture *fixture, gconstpointer user_data)
 
 	fixture->ifindex = link->ifindex;
 	fixture->fd = nm_steal_fd (&fd);
-	memcpy (fixture->mac, link->addr.data, ETH_ALEN);
+	memcpy (fixture->mac, link->l_address.data, ETH_ALEN);
 }
 
 typedef struct {
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index da7d0685..9cfe5f70 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -780,12 +779,10 @@ complete_connection (NMDevice *device,
 	NMDeviceIwd *self = NM_DEVICE_IWD (device);
 	NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
-	const char *setting_mac;
 	gs_free char *ssid_utf8 = NULL;
 	NMWifiAP *ap;
 	GBytes *ssid;
 	GBytes *setting_ssid = NULL;
-	const char *perm_hw_addr;
 	const char *mode;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
@@ -875,36 +872,9 @@ complete_connection (NMDevice *device,
 	                           ssid_utf8,
 	                           ssid_utf8,
 	                           NULL,
+	                           nm_setting_wireless_get_mac_address (s_wifi) ? NULL : nm_device_get_iface (device),
 	                           TRUE);
 
-	perm_hw_addr = nm_device_get_permanent_hw_address (device);
-	if (perm_hw_addr) {
-		setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
-		if (setting_mac) {
-			/* Make sure the setting MAC (if any) matches the device's permanent MAC */
-			if (!nm_utils_hwaddr_matches (setting_mac, -1, perm_hw_addr, -1)) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     "connection does not match device");
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MAC_ADDRESS);
-				return FALSE;
-			}
-		} else {
-			guint8 tmp[ETH_ALEN];
-
-			/* Lock the connection to this device by default if it uses a
-			 * permanent MAC address (ie not a 'locally administered' one)
-			 */
-			nm_utils_hwaddr_aton (perm_hw_addr, tmp, ETH_ALEN);
-			if (!(tmp[0] & 0x02)) {
-				g_object_set (G_OBJECT (s_wifi),
-				              NM_SETTING_WIRELESS_MAC_ADDRESS, perm_hw_addr,
-				              NULL);
-			}
-		}
-	}
-
 	return TRUE;
 }
 
diff --git a/src/devices/wifi/nm-device-iwd.h b/src/devices/wifi/nm-device-iwd.h
index 4a2bd31e..aab45b7b 100644
--- a/src/devices/wifi/nm-device-iwd.h
+++ b/src/devices/wifi/nm-device-iwd.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wifi/nm-device-olpc-mesh.c b/src/devices/wifi/nm-device-olpc-mesh.c
index 1172a613..3c865c55 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/devices/wifi/nm-device-olpc-mesh.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * Dan Williams <dcbw@redhat.com>
@@ -133,6 +132,7 @@ complete_connection (NMDevice *device,
 	                           NULL,
 	                           _("Mesh"),
 	                           NULL,
+	                           NULL,
 	                           FALSE); /* No IPv6 by default */
 
 	return TRUE;
diff --git a/src/devices/wifi/nm-device-olpc-mesh.h b/src/devices/wifi/nm-device-olpc-mesh.h
index bf0e4da9..619fc46a 100644
--- a/src/devices/wifi/nm-device-olpc-mesh.h
+++ b/src/devices/wifi/nm-device-olpc-mesh.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager -- Network link manager
  *
  * Dan Williams <dcbw@redhat.com>
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
index 8381ebc7..c5826e24 100644
--- a/src/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -331,6 +331,7 @@ complete_connection (NMDevice *device,
 	                           setting_name,
 	                           setting_name,
 	                           NULL,
+	                           NULL,
 	                           TRUE);
 
 	return TRUE;
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 3a58c620..2ffba47a 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -545,6 +544,60 @@ wake_on_wlan_restore (NMDeviceWifi *self)
 }
 
 static void
+disconnect_cb (NMSupplicantInterface *iface, GError *error, gpointer user_data)
+{
+	gs_unref_object NMDeviceWifi *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
+
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
+
+	/* error will be freed by sup_iface */
+	callback (NM_DEVICE (self), error, callback_user_data);
+}
+
+static void
+disconnect_cb_on_idle (gpointer user_data,
+                       GCancellable *cancellable)
+{
+	gs_unref_object NMDeviceWifi *self = NULL;
+	NMDeviceDeactivateCallback callback;
+	gpointer callback_user_data;
+	gs_free_error GError *cancelled_error = NULL;
+
+	nm_utils_user_data_unpack (user_data, &self, &callback, &callback_user_data);
+
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	callback (NM_DEVICE (self), cancelled_error, callback_user_data);
+}
+
+static void
+deactivate_async (NMDevice *device,
+                  GCancellable *cancellable,
+                  NMDeviceDeactivateCallback callback,
+                  gpointer callback_user_data) {
+	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
+	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
+	gpointer user_data;
+
+	nm_assert (G_IS_CANCELLABLE (cancellable));
+	nm_assert (callback);
+
+	user_data = nm_utils_user_data_pack (g_object_ref (self), callback, callback_user_data);
+	if (!priv->sup_iface) {
+		nm_utils_invoke_on_idle (disconnect_cb_on_idle, user_data, cancellable);
+		return;
+	}
+
+	cleanup_association_attempt (self, FALSE);
+
+	nm_supplicant_interface_disconnect_async (priv->sup_iface,
+	                                          cancellable,
+	                                          disconnect_cb,
+	                                          user_data);
+}
+
+static void
 deactivate (NMDevice *device)
 {
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
@@ -695,6 +748,20 @@ check_connection_compatible (NMDevice *device, NMConnection *connection, GError
 				return FALSE;
 			}
 		}
+	} else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) {
+		if (!(priv->capabilities & NM_WIFI_DEVICE_CAP_MESH)) {
+			nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+			                            "the device does not support Mesh mode");
+			return FALSE;
+		}
+
+		if (priv->sup_iface) {
+			if (nm_supplicant_interface_get_mesh_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_NO) {
+				nm_utils_error_set_literal (error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+				                            "wpa_supplicant does not support Mesh mode");
+				return FALSE;
+			}
+		}
 	}
 
 	// FIXME: check channel/freq/band against bands the hardware supports
@@ -739,12 +806,13 @@ check_connection_available (NMDevice *device,
 		return TRUE;
 	}
 
-	/* Ad-Hoc and AP connections are always available because they may be
+	/* Ad-Hoc, AP and Mesh connections are always available because they may be
 	 * started at any time.
 	 */
 	mode = nm_setting_wireless_get_mode (s_wifi);
 	if (   g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) == 0
-	    || g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0)
+	    || g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_AP) == 0
+	    || g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_MESH) == 0)
 		return TRUE;
 
 	/* Hidden SSIDs obviously don't always appear in the scan list either.
@@ -778,13 +846,11 @@ complete_connection (NMDevice *device,
 	NMDeviceWifi *self = NM_DEVICE_WIFI (device);
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMSettingWireless *s_wifi;
-	const char *setting_mac;
 	gs_free char *ssid_utf8 = NULL;
 	NMWifiAP *ap;
 	GBytes *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);
@@ -908,27 +974,12 @@ complete_connection (NMDevice *device,
 	                           ssid_utf8,
 	                           ssid_utf8,
 	                           NULL,
+	                           nm_setting_wireless_get_mac_address (s_wifi) ? NULL : nm_device_get_iface (device),
 	                           TRUE);
 
 	if (hidden)
 		g_object_set (s_wifi, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
 
-	perm_hw_addr = nm_device_get_permanent_hw_address (device);
-	if (perm_hw_addr) {
-		setting_mac = nm_setting_wireless_get_mac_address (s_wifi);
-		if (setting_mac) {
-			/* Make sure the setting MAC (if any) matches the device's permanent MAC */
-			if (!nm_utils_hwaddr_matches (setting_mac, -1, perm_hw_addr, -1)) {
-				g_set_error_literal (error,
-				                     NM_CONNECTION_ERROR,
-				                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
-				                     _("connection does not match device"));
-				g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MAC_ADDRESS);
-				return FALSE;
-			}
-		}
-	}
-
 	return TRUE;
 }
 
@@ -972,7 +1023,8 @@ can_auto_connect (NMDevice *device,
 	NMConnection *connection;
 	NMSettingWireless *s_wifi;
 	NMWifiAP *ap;
-	const char *method, *mode;
+	const char *method6, *mode;
+	gboolean auto4, auto6;
 	guint64 timestamp = 0;
 
 	nm_assert (!specific_object || !*specific_object);
@@ -985,13 +1037,20 @@ can_auto_connect (NMDevice *device,
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_return_val_if_fail (s_wifi, FALSE);
 
-	/* Always allow autoconnect for AP and non-autoconf Ad-Hoc */
-	method = nm_utils_get_ip_config_method (connection, AF_INET);
+	/* Always allow autoconnect for AP and non-autoconf Ad-Hoc or Mesh */
+	auto4 = nm_streq0 (nm_utils_get_ip_config_method (connection, AF_INET),
+	                   NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	method6 = nm_utils_get_ip_config_method (connection, AF_INET6);
+	auto6 =    nm_streq0 (method6, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+	        || nm_streq0 (method6, NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+
 	mode = nm_setting_wireless_get_mode (s_wifi);
+
 	if (nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_AP))
 		return TRUE;
-	else if (   nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC)
-	         && !nm_streq0 (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+	else if (!auto4 && nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC))
+		return TRUE;
+	else if (!auto4 && !auto6 && nm_streq0 (mode, NM_SETTING_WIRELESS_MODE_MESH))
 		return TRUE;
 
 	/* Don't autoconnect to networks that have been tried at least once
@@ -1531,13 +1590,14 @@ try_fill_ssid_for_hidden_ap (NMDeviceWifi *self,
 		NMSettingsConnection *sett_conn = connections[i];
 		NMSettingWireless *s_wifi;
 
+		if (!nm_settings_connection_has_seen_bssid (sett_conn, bssid))
+			continue;
 		s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (sett_conn));
-		if (s_wifi) {
-			if (nm_settings_connection_has_seen_bssid (sett_conn, bssid)) {
-				nm_wifi_ap_set_ssid (ap, nm_setting_wireless_get_ssid (s_wifi));
-				break;
-			}
-		}
+		if (!s_wifi)
+			continue;
+
+		nm_wifi_ap_set_ssid (ap, nm_setting_wireless_get_ssid (s_wifi));
+		break;
 	}
 }
 
@@ -1777,9 +1837,10 @@ supplicant_iface_wps_credentials_cb (NMSupplicantInterface *iface,
 	}
 	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)) {
+		                                        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 {
@@ -2398,6 +2459,7 @@ supplicant_connection_timeout_cb (gpointer user_data)
 	g_assert (connection);
 
 	if (   priv->mode == NM_802_11_MODE_ADHOC
+	    || priv->mode == NM_802_11_MODE_MESH
 	    || priv->mode == NM_802_11_MODE_AP) {
 		/* In Ad-Hoc and AP modes there's nothing to check the encryption key
 		 * (if any), so supplicant timeouts here are almost certainly the wifi
@@ -2468,7 +2530,9 @@ build_supplicant_config (NMDeviceWifi *self,
 
 	config = nm_supplicant_config_new (
 		nm_supplicant_interface_get_pmf_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
-		nm_supplicant_interface_get_fils_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES);
+		nm_supplicant_interface_get_fils_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
+		nm_supplicant_interface_get_ft_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES,
+		nm_supplicant_interface_get_sha384_support (priv->sup_iface) == NM_SUPPLICANT_FEATURE_YES);
 
 	/* Warn if AP mode may not be supported */
 	if (   g_strcmp0 (nm_setting_wireless_get_mode (s_wireless), NM_SETTING_WIRELESS_MODE_AP) == 0
@@ -2635,7 +2699,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 		/* Scanning not done in AP mode; clear the scan list */
 		remove_all_aps (self);
-	}
+	} else if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_MESH) == 0)
+		priv->mode = NM_802_11_MODE_MESH;
 	_notify (self, PROP_MODE);
 
 	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
@@ -2655,8 +2720,8 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	if (!nm_device_hw_addr_set_cloned (device, connection, TRUE))
 		return NM_ACT_STAGE_RETURN_FAILURE;
 
-	/* AP mode never uses a specific object or existing scanned AP */
-	if (priv->mode != NM_802_11_MODE_AP) {
+	/* AP and Mesh modes never use a specific object or existing scanned AP */
+	if (priv->mode != NM_802_11_MODE_AP && priv->mode != NM_802_11_MODE_MESH) {
 		ap_path = nm_active_connection_get_specific_object (NM_ACTIVE_CONNECTION (req));
 		ap = ap_path ? nm_wifi_ap_lookup_for_device (NM_DEVICE (self), ap_path) : NULL;
 		if (ap)
@@ -2672,10 +2737,10 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	}
 
 	/* If the user is trying to connect to an AP that NM doesn't yet know about
-	 * (hidden network or something) or starting a Hotspot, create an fake AP
-	 * from the security settings in the connection.  This "fake" AP gets used
-	 * until the real one is found in the scan list (Ad-Hoc or Hidden), or until
-	 * the device is deactivated (Hotspot).
+	 * (hidden network or something), starting a Hotspot or joining a Mesh,
+	 * create a fake APfrom the security settings in the connection.  This "fake"
+	 * AP gets used until the real one is found in the scan list (Ad-Hoc or Hidden),
+	 * or until the device is deactivated (Hotspot).
 	 */
 	ap = nm_wifi_ap_new_fake_from_connection (connection);
 	g_return_val_if_fail (ap != NULL, NM_ACT_STAGE_RETURN_FAILURE);
@@ -2763,6 +2828,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self);
 	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
 	NMSupplicantConfig *config = NULL;
+	NM80211Mode ap_mode;
 	NMActRequest *req;
 	NMWifiAP *ap;
 	NMConnection *connection;
@@ -2783,6 +2849,7 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
 		goto out;
 	}
+	ap_mode = nm_wifi_ap_get_mode (ap);
 
 	connection = nm_act_request_get_applied_connection (req);
 	g_assert (connection);
@@ -2822,14 +2889,16 @@ act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 	priv->ssid_found = FALSE;
 
-	/* Supplicant requires an initial frequency for Ad-Hoc and Hotspot; if the user
-	 * didn't specify one and we didn't find an AP that matched the connection,
-	 * just pick a frequency the device supports.
+	/* Supplicant requires an initial frequency for Ad-Hoc, Hotspot and Mesh;
+	 * if the user didn't specify one and we didn't find an AP that matched
+	 * the connection, just pick a frequency the device supports.
 	 */
-	if ((nm_wifi_ap_get_mode (ap) == NM_802_11_MODE_ADHOC) || nm_wifi_ap_is_hotspot (ap))
+	if (   ap_mode == NM_802_11_MODE_ADHOC
+	    || ap_mode == NM_802_11_MODE_MESH
+	    || nm_wifi_ap_is_hotspot (ap))
 		ensure_hotspot_frequency (self, s_wireless, ap);
 
-	if (nm_wifi_ap_get_mode (ap) == NM_802_11_MODE_INFRA)
+	if (ap_mode == NM_802_11_MODE_INFRA)
 		set_powersave (device);
 
 	/* Build up the supplicant configuration */
@@ -3386,6 +3455,7 @@ nm_device_wifi_class_init (NMDeviceWifiClass *klass)
 	device_class->get_configured_mtu = get_configured_mtu;
 	device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
 	device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
+	device_class->deactivate_async = deactivate_async;
 	device_class->deactivate = deactivate;
 	device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
 	device_class->unmanaged_on_quit = unmanaged_on_quit;
diff --git a/src/devices/wifi/nm-device-wifi.h b/src/devices/wifi/nm-device-wifi.h
index 116ad11e..82f62be6 100644
--- a/src/devices/wifi/nm-device-wifi.h
+++ b/src/devices/wifi/nm-device-wifi.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index 494fca32..5b550ea4 100644
--- a/src/devices/wifi/nm-iwd-manager.c
+++ b/src/devices/wifi/nm-iwd-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -437,7 +436,6 @@ mirror_8021x_connection (NMIwdManager *self,
 	                                    NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME,
 	                                    NM_SETTING_CONNECTION_ID, name,
 	                                    NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_buf (uuid),
-	                                    NM_SETTING_CONNECTION_READ_ONLY, TRUE,
 	                                    NULL));
 	nm_connection_add_setting (connection, setting);
 
@@ -470,19 +468,19 @@ mirror_8021x_connection (NMIwdManager *self,
 	if (!nm_connection_normalize (connection, NULL, NULL, NULL))
 		return NULL;
 
-	settings_connection = nm_settings_add_connection (priv->settings, connection,
-	                                                  FALSE, &error);
-	if (!settings_connection) {
+	if (!nm_settings_add_connection (priv->settings,
+	                                 connection,
+	                                 NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+	                                 NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                                 NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                                 &settings_connection,
+	                                 &error)) {
 		_LOGW ("failed to add a mirror NMConnection for IWD's Known Network '%s': %s",
 		       name, error->message);
 		g_error_free (error);
 		return NULL;
 	}
 
-	nm_settings_connection_set_flags (settings_connection,
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED |
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED,
-	                                  TRUE);
 	return settings_connection;
 }
 
@@ -499,7 +497,7 @@ mirror_8021x_connection_take_and_delete (NMSettingsConnection *sett_conn)
 	/* If connection has not been saved since we created it
 	 * in interface_added it too can be removed now. */
 	if (NM_FLAGS_HAS (flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
-		nm_settings_connection_delete (sett_conn, NULL);
+		nm_settings_connection_delete (sett_conn, FALSE);
 
 	g_object_unref (sett_conn);
 }
@@ -900,7 +898,7 @@ nm_iwd_manager_init (NMIwdManager *self)
 	g_signal_connect (priv->manager, NM_MANAGER_DEVICE_ADDED,
 	                  G_CALLBACK (device_added), self);
 
-	priv->settings = g_object_ref (nm_settings_get ());
+	priv->settings = g_object_ref (NM_SETTINGS_GET);
 	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
 	                  G_CALLBACK (connection_removed), self);
 
diff --git a/src/devices/wifi/nm-iwd-manager.h b/src/devices/wifi/nm-iwd-manager.h
index 57b7007a..b410e4ce 100644
--- a/src/devices/wifi/nm-iwd-manager.h
+++ b/src/devices/wifi/nm-iwd-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c
index 87daff59..c7ab7f04 100644
--- a/src/devices/wifi/nm-wifi-ap.c
+++ b/src/devices/wifi/nm-wifi-ap.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -260,7 +259,8 @@ nm_wifi_ap_set_mode (NMWifiAP *ap, const NM80211Mode mode)
 
 	g_return_val_if_fail (NM_IS_WIFI_AP (ap), FALSE);
 	g_return_val_if_fail (   mode == NM_802_11_MODE_ADHOC
-	                     || mode == NM_802_11_MODE_INFRA, FALSE);
+	                      || mode == NM_802_11_MODE_INFRA
+	                      || mode == NM_802_11_MODE_MESH, FALSE);
 
 	priv = NM_WIFI_AP_GET_PRIVATE (ap);
 
@@ -418,9 +418,11 @@ security_from_vardict (GVariant *security)
 
 	if (   g_variant_lookup (security, "KeyMgmt", "^a&s", &array)
 	    && array) {
-		if (g_strv_contains (array, "wpa-psk"))
+		if (g_strv_contains (array, "wpa-psk") ||
+		    g_strv_contains (array, "wpa-ft-psk"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_PSK;
 		if (g_strv_contains (array, "wpa-eap") ||
+		    g_strv_contains (array, "wpa-ft-eap") ||
 		    g_strv_contains (array, "wpa-fils-sha256") ||
 		    g_strv_contains (array, "wpa-fils-sha384"))
 			flags |= NM_802_11_AP_SEC_KEY_MGMT_802_1X;
@@ -815,6 +817,8 @@ nm_wifi_ap_update_from_properties (NMWifiAP *ap,
 			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
 		else if (!g_strcmp0 (s, "ad-hoc"))
 			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_ADHOC);
+		else if (!g_strcmp0 (s, "mesh"))
+			changed |= nm_wifi_ap_set_mode (ap, NM_802_11_MODE_MESH);
 	}
 
 	if (g_variant_lookup (properties, "Signal", "n", &i16))
@@ -1007,7 +1011,9 @@ nm_wifi_ap_to_string (const NMWifiAP *self,
 	                        ? '#'
 	                        : (priv->fake
 	                               ? 'f'
-	                               : 'a'))),
+	                               : (priv->mode == NM_802_11_MODE_MESH
+	                                      ? 'm'
+	                                      : 'a')))),
 	            chan,
 	            priv->strength,
 	            priv->flags & NM_802_11_AP_FLAGS_PRIVACY ? 'P' : '_',
@@ -1072,6 +1078,8 @@ nm_wifi_ap_check_compatible (NMWifiAP *self,
 		if (   !strcmp (mode, "ap")
 		    && (priv->mode != NM_802_11_MODE_INFRA || priv->hotspot != TRUE))
 			return FALSE;
+		if (!strcmp (mode, "mesh") && (priv->mode != NM_802_11_MODE_MESH))
+			return FALSE;
 	}
 
 	band = nm_setting_wireless_get_band (s_wireless);
@@ -1115,6 +1123,7 @@ nm_wifi_ap_complete_connection (NMWifiAP *self,
 	return nm_wifi_utils_complete_connection (priv->ssid,
 	                                          priv->address,
 	                                          priv->mode,
+	                                          priv->freq,
 	                                          priv->flags,
 	                                          priv->wpa_flags,
 	                                          priv->rsn_flags,
@@ -1245,6 +1254,8 @@ nm_wifi_ap_new_fake_from_connection (NMConnection *connection)
 			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
 		else if (!strcmp (mode, "adhoc"))
 			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_ADHOC);
+		else if (!strcmp (mode, "mesh"))
+			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_MESH);
 		else if (!strcmp (mode, "ap")) {
 			nm_wifi_ap_set_mode (ap, NM_802_11_MODE_INFRA);
 			NM_WIFI_AP_GET_PRIVATE (ap)->hotspot = TRUE;
diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h
index 7462e9d1..755e722c 100644
--- a/src/devices/wifi/nm-wifi-ap.h
+++ b/src/devices/wifi/nm-wifi-ap.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wifi/nm-wifi-common.c b/src/devices/wifi/nm-wifi-common.c
index c95620e7..96828d59 100644
--- a/src/devices/wifi/nm-wifi-common.c
+++ b/src/devices/wifi/nm-wifi-common.c
@@ -1,4 +1,3 @@
-/*-*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/src/devices/wifi/nm-wifi-common.h b/src/devices/wifi/nm-wifi-common.h
index 91cbeb55..81d657ec 100644
--- a/src/devices/wifi/nm-wifi-common.h
+++ b/src/devices/wifi/nm-wifi-common.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/src/devices/wifi/nm-wifi-factory.c b/src/devices/wifi/nm-wifi-factory.c
index 9a89bbe9..2f069882 100644
--- a/src/devices/wifi/nm-wifi-factory.c
+++ b/src/devices/wifi/nm-wifi-factory.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c
index c6e8b3e0..426eeea8 100644
--- a/src/devices/wifi/nm-wifi-utils.c
+++ b/src/devices/wifi/nm-wifi-utils.c
@@ -1,4 +1,3 @@
-/*-*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -25,6 +24,7 @@
 #include <stdlib.h>
 
 #include "nm-utils.h"
+#include "nm-core-internal.h"
 
 static gboolean
 verify_no_wep (NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error)
@@ -527,6 +527,7 @@ gboolean
 nm_wifi_utils_complete_connection (GBytes *ap_ssid,
                                    const char *bssid,
                                    NM80211Mode ap_mode,
+                                   guint32 ap_freq,
                                    guint32 ap_flags,
                                    guint32 ap_wpa_flags,
                                    guint32 ap_rsn_flags,
@@ -540,6 +541,7 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 	GBytes *ssid;
 	const char *mode, *key_mgmt, *auth_alg, *leap_username;
 	gboolean adhoc = FALSE;
+	gboolean mesh = FALSE;
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
@@ -576,6 +578,10 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 			if (ap_mode == NM_802_11_MODE_ADHOC)
 				valid = TRUE;
 			adhoc = TRUE;
+		} else if (!strcmp (mode, NM_SETTING_WIRELESS_MODE_MESH)) {
+			if (ap_mode == NM_802_11_MODE_MESH)
+				valid = TRUE;
+			mesh = TRUE;
 		}
 
 		if (valid == FALSE) {
@@ -591,10 +597,57 @@ nm_wifi_utils_complete_connection (GBytes *ap_ssid,
 		if (ap_mode == NM_802_11_MODE_ADHOC) {
 			mode = NM_SETTING_WIRELESS_MODE_ADHOC;
 			adhoc = TRUE;
+		} else if (ap_mode == NM_802_11_MODE_MESH) {
+			mode = NM_SETTING_WIRELESS_MODE_MESH;
+			mesh = TRUE;
 		}
 		g_object_set (G_OBJECT (s_wifi), NM_SETTING_WIRELESS_MODE, mode, NULL);
 	}
 
+	/* For now mesh requires channel and band, fill them only if both not present.
+	 * Do not check existing values against an existing ap/mesh point,
+	 * mesh join will start a new network if required */
+	if (mesh) {
+		const char *band;
+		guint32 channel;
+		gboolean band_valid = TRUE;
+		gboolean chan_valid = TRUE;
+		gboolean valid;
+
+		band = nm_setting_wireless_get_band (s_wifi);
+		channel = nm_setting_wireless_get_channel (s_wifi);
+
+		valid =    ((band == NULL) && (channel == 0))
+		        || ((band != NULL) && (channel != 0));
+
+		if ((band == NULL) && (channel == 0)) {
+			channel = nm_utils_wifi_freq_to_channel (ap_freq);
+			if (channel) {
+				g_object_set (s_wifi,
+				              NM_SETTING_WIRELESS_CHANNEL, channel,
+				              NULL);
+			} else {
+				chan_valid = FALSE;
+			}
+
+			band = nm_utils_wifi_freq_to_band (ap_freq);
+			if (band) {
+				g_object_set (s_wifi, NM_SETTING_WIRELESS_BAND, band, NULL);
+			} else {
+				band_valid = FALSE;
+			}
+		}
+
+		if (!valid || !chan_valid || !band_valid) {
+			g_set_error (error,
+			             NM_CONNECTION_ERROR,
+			             NM_CONNECTION_ERROR_INVALID_PROPERTY,
+			             _("connection does not match mesh point"));
+			g_prefix_error (error, "%s.%s: ", NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_WIRELESS_MODE);
+			return FALSE;
+		}
+	}
+
 	/* Security */
 
 	/* Open */
diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h
index 03238c24..251d122f 100644
--- a/src/devices/wifi/nm-wifi-utils.h
+++ b/src/devices/wifi/nm-wifi-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -37,6 +36,7 @@ typedef enum {
 gboolean nm_wifi_utils_complete_connection (GBytes *ssid,
                                             const char *bssid,
                                             NM80211Mode mode,
+                                            guint32 ap_freq,
                                             guint32 flags,
                                             guint32 wpa_flags,
                                             guint32 rsn_flags,
diff --git a/src/devices/wifi/tests/meson.build b/src/devices/wifi/tests/meson.build
index 895853c4..ba756d53 100644
--- a/src/devices/wifi/tests/meson.build
+++ b/src/devices/wifi/tests/meson.build
@@ -1,13 +1,13 @@
-test_unit = 'test-general'
+test_unit = 'test-devices-wifi'
 
 exe = executable(
-  'wifi-' + test_unit,
+  test_unit,
   [test_unit + '.c'] + common_sources,
   dependencies: test_nm_dep,
 )
 
 test(
-  'devices/wifi/' + test_unit,
+  test_unit,
   test_script,
   args: test_args + [exe.full_path()],
   timeout: default_test_timeout,
diff --git a/src/devices/wifi/tests/test-general.c b/src/devices/wifi/tests/test-devices-wifi.c
index e0899837..a0b3e17f 100644
--- a/src/devices/wifi/tests/test-general.c
+++ b/src/devices/wifi/tests/test-devices-wifi.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -86,6 +85,7 @@ complete_connection (const char *ssid,
 	return nm_wifi_utils_complete_connection (ssid_b,
 	                                          bssid,
 	                                          mode,
+	                                          0,
 	                                          flags,
 	                                          wpa_flags,
 	                                          rsn_flags,
diff --git a/src/devices/wwan/libnm-wwan.ver b/src/devices/wwan/libnm-wwan.ver
index ea966afe..7ccebcb5 100644
--- a/src/devices/wwan/libnm-wwan.ver
+++ b/src/devices/wwan/libnm-wwan.ver
@@ -7,13 +7,16 @@ global:
 	nm_modem_deactivate;
 	nm_modem_deactivate_async;
 	nm_modem_device_state_changed;
+	nm_modem_get_apn;
 	nm_modem_get_capabilities;
 	nm_modem_get_configured_mtu;
 	nm_modem_get_control_port;
+	nm_modem_get_device_id;
 	nm_modem_get_driver;
 	nm_modem_get_iid;
 	nm_modem_get_path;
 	nm_modem_get_ip_ifindex;
+	nm_modem_get_operator_code;
 	nm_modem_get_secrets;
 	nm_modem_get_state;
 	nm_modem_get_type;
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index 1e316280..042a6ca4 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -15,7 +14,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 - 2011 Red Hat, Inc.
+ * Copyright (C) 2009 - 2019 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -36,10 +35,13 @@ _LOG_DECLARE_SELF(NMDeviceModem);
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceModem,
 	PROP_MODEM,
 	PROP_CAPABILITIES,
 	PROP_CURRENT_CAPABILITIES,
+	PROP_DEVICE_ID,
+	PROP_OPERATOR_CODE,
+	PROP_APN,
 );
 
 typedef struct {
@@ -47,6 +49,9 @@ typedef struct {
 	NMDeviceModemCapabilities caps;
 	NMDeviceModemCapabilities current_caps;
 	gboolean rf_enabled;
+	char *device_id;
+	char *operator_code;
+	char *apn;
 } NMDeviceModemPrivate;
 
 struct _NMDeviceModem {
@@ -308,6 +313,34 @@ ip_ifindex_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 }
 
 static void
+operator_code_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
+{
+	NMDeviceModem *self = NM_DEVICE_MODEM (user_data);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self);
+	const char *operator_code = nm_modem_get_operator_code (modem);
+
+	if (g_strcmp0 (priv->operator_code, operator_code) != 0) {
+		g_free (priv->operator_code);
+		priv->operator_code = g_strdup (operator_code);
+		_notify (self, PROP_OPERATOR_CODE);
+	}
+}
+
+static void
+apn_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
+{
+	NMDeviceModem *self = NM_DEVICE_MODEM (user_data);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self);
+	const char *apn = nm_modem_get_apn (modem);
+
+	if (g_strcmp0 (priv->apn, apn) != 0) {
+		g_free (priv->apn);
+		priv->apn = g_strdup (apn);
+		_notify (self, PROP_APN);
+	}
+}
+
+static void
 ids_changed_cb (NMModem *modem, GParamSpec *pspec, gpointer user_data)
 {
 	nm_device_recheck_available_connections (NM_DEVICE (user_data));
@@ -499,7 +532,11 @@ complete_connection (NMDevice *device,
 {
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE ((NMDeviceModem *) device);
 
-	return nm_modem_complete_connection (priv->modem, connection, existing_connections, error);
+	return nm_modem_complete_connection (priv->modem,
+	                                     nm_device_get_iface (device),
+	                                     connection,
+	                                     existing_connections,
+	                                     error);
 }
 
 static void
@@ -688,6 +725,8 @@ set_modem (NMDeviceModem *self, NMModem *modem)
 	g_signal_connect (modem, "notify::" NM_MODEM_DEVICE_ID, G_CALLBACK (ids_changed_cb), self);
 	g_signal_connect (modem, "notify::" NM_MODEM_SIM_ID, G_CALLBACK (ids_changed_cb), self);
 	g_signal_connect (modem, "notify::" NM_MODEM_SIM_OPERATOR_ID, G_CALLBACK (ids_changed_cb), self);
+	g_signal_connect (modem, "notify::" NM_MODEM_OPERATOR_CODE, G_CALLBACK (operator_code_changed_cb), self);
+	g_signal_connect (modem, "notify::" NM_MODEM_APN, G_CALLBACK (apn_changed_cb), self);
 }
 
 static guint32
@@ -718,6 +757,15 @@ get_property (GObject *object, guint prop_id,
 	case PROP_CURRENT_CAPABILITIES:
 		g_value_set_uint (value, priv->current_caps);
 		break;
+	case PROP_DEVICE_ID:
+		g_value_set_string (value, priv->device_id);
+		break;
+	case PROP_OPERATOR_CODE:
+		g_value_set_string (value, priv->operator_code);
+		break;
+	case PROP_APN:
+		g_value_set_string (value, priv->apn);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -741,6 +789,10 @@ set_property (GObject *object, guint prop_id,
 	case PROP_CURRENT_CAPABILITIES:
 		priv->current_caps = g_value_get_uint (value);
 		break;
+	case PROP_DEVICE_ID:
+		/* construct-only */
+		priv->device_id = g_value_dup_string (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -775,6 +827,7 @@ nm_device_modem_new (NMModem *modem)
 	                     NM_DEVICE_MODEM_MODEM, modem,
 	                     NM_DEVICE_MODEM_CAPABILITIES, caps,
 	                     NM_DEVICE_MODEM_CURRENT_CAPABILITIES, current_caps,
+	                     NM_DEVICE_MODEM_DEVICE_ID, nm_modem_get_device_id (modem),
 	                     NULL);
 }
 
@@ -788,6 +841,10 @@ dispose (GObject *object)
 		g_clear_object (&priv->modem);
 	}
 
+	g_clear_pointer (&priv->device_id, g_free);
+	g_clear_pointer (&priv->operator_code, g_free);
+	g_clear_pointer (&priv->apn, g_free);
+
 	G_OBJECT_CLASS (nm_device_modem_parent_class)->dispose (object);
 }
 
@@ -800,6 +857,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_modem = {
 		.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ModemCapabilities",   "u",  NM_DEVICE_MODEM_CAPABILITIES),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("CurrentCapabilities", "u",  NM_DEVICE_MODEM_CURRENT_CAPABILITIES),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("DeviceId",            "s",  NM_DEVICE_MODEM_DEVICE_ID),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("OperatorCode",        "s",  NM_DEVICE_MODEM_OPERATOR_CODE),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE   ("Apn",                 "s",  NM_DEVICE_MODEM_APN),
 		),
 	),
 	.legacy_property_changed = TRUE,
@@ -857,5 +917,23 @@ nm_device_modem_class_init (NMDeviceModemClass *klass)
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_DEVICE_ID] =
+	     g_param_spec_string (NM_DEVICE_MODEM_DEVICE_ID, "", "",
+	                          NULL,
+	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_OPERATOR_CODE] =
+	     g_param_spec_string (NM_DEVICE_MODEM_OPERATOR_CODE, "", "",
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_APN] =
+	     g_param_spec_string (NM_DEVICE_MODEM_APN, "", "",
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 }
diff --git a/src/devices/wwan/nm-device-modem.h b/src/devices/wwan/nm-device-modem.h
index 65bda8b6..0a557f57 100644
--- a/src/devices/wwan/nm-device-modem.h
+++ b/src/devices/wwan/nm-device-modem.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -34,6 +33,9 @@
 #define NM_DEVICE_MODEM_MODEM "modem"
 #define NM_DEVICE_MODEM_CAPABILITIES "modem-capabilities"
 #define NM_DEVICE_MODEM_CURRENT_CAPABILITIES "current-capabilities"
+#define NM_DEVICE_MODEM_DEVICE_ID            "device-id"
+#define NM_DEVICE_MODEM_OPERATOR_CODE        "operator-code"
+#define NM_DEVICE_MODEM_APN                  "apn"
 
 typedef struct _NMDeviceModem NMDeviceModem;
 typedef struct _NMDeviceModemClass NMDeviceModemClass;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index ffdc61a6..5716500a 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -79,6 +78,7 @@ typedef struct {
 	MMObject *modem_object;
 	/* Per-interface objects */
 	MMModem *modem_iface;
+	MMModem3gpp *modem_3gpp_iface;
 	MMModemSimple *simple_iface;
 	MMSim *sim_iface;
 
@@ -213,12 +213,13 @@ owns_port (NMModem *_self, const char *iface)
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
 	const MMModemPortInfo *ports = NULL;
 	guint n_ports = 0, i;
-	gboolean owns = FALSE;
 
 	mm_modem_peek_ports (self->_priv.modem_iface, &ports, &n_ports);
-	for (i = 0; i < n_ports && !owns; i++)
-		owns = (g_strcmp0 (iface, ports[i].name) == 0);
-	return owns;
+	for (i = 0; i < n_ports; i++) {
+		if (nm_streq0 (iface, ports[i].name))
+			return TRUE;
+	}
+	return FALSE;
 }
 
 /*****************************************************************************/
@@ -547,6 +548,8 @@ connect_context_step (NMModemBroadband *self)
 			else
 				g_assert_not_reached ();
 
+			_nm_modem_set_apn (NM_MODEM (self), mm_simple_connect_properties_get_apn (ctx->connect_properties));
+
 			_LOGD ("launching connection with ip type '%s' (try %d)",
 			       nm_modem_ip_type_to_string (current),
 			       ctx->ip_type_tries + 1);
@@ -656,12 +659,13 @@ check_connection_compatible_with_modem (NMModem *_self, NMConnection *connection
 /*****************************************************************************/
 
 static gboolean
-complete_connection (NMModem *_self,
+complete_connection (NMModem *modem,
+                     const char *iface,
                      NMConnection *connection,
                      NMConnection *const*existing_connections,
                      GError **error)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
+	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 	MMModemCapability modem_caps;
 	NMSettingPpp *s_ppp;
 
@@ -679,6 +683,20 @@ complete_connection (NMModem *_self,
 	}
 
 	if (MODEM_CAPS_3GPP (modem_caps)) {
+		NMSettingGsm *s_gsm;
+
+		s_gsm = nm_connection_get_setting_gsm (connection);
+		if (!s_gsm) {
+			s_gsm = (NMSettingGsm *) nm_setting_gsm_new ();
+			nm_connection_add_setting (connection, NM_SETTING (s_gsm));
+		}
+
+		if (!nm_setting_gsm_get_device_id (s_gsm)) {
+			g_object_set (G_OBJECT (s_gsm),
+			              NM_SETTING_GSM_DEVICE_ID, nm_modem_get_device_id (modem),
+			              NULL);
+		}
+
 		nm_utils_complete_generic (NM_PLATFORM_GET,
 		                           connection,
 		                           NM_SETTING_GSM_SETTING_NAME,
@@ -686,6 +704,7 @@ complete_connection (NMModem *_self,
 		                           NULL,
 		                           _("GSM connection"),
 		                           NULL,
+		                           NULL,
 		                           FALSE); /* No IPv6 yet by default */
 
 		return TRUE;
@@ -710,6 +729,7 @@ complete_connection (NMModem *_self,
 		                           NULL,
 		                           _("CDMA connection"),
 		                           NULL,
+		                           iface,
 		                           FALSE); /* No IPv6 yet by default */
 
 		return TRUE;
@@ -1140,6 +1160,7 @@ disconnect (NMModem *modem,
 	DisconnectContext *ctx;
 
 	connect_context_clear (self);
+	_nm_modem_set_apn (NM_MODEM (self), NULL);
 
 	ctx = g_slice_new0 (DisconnectContext);
 	ctx->self = g_object_ref (self);
@@ -1320,6 +1341,15 @@ supported_ip_families_changed (MMModem *modem, GParamSpec *pspec, gpointer user_
 	              NULL);
 }
 
+static void
+operator_code_changed (MMModem3gpp *modem_3gpp, GParamSpec *pspec, gpointer user_data)
+{
+	NMModemBroadband *self = NM_MODEM_BROADBAND (user_data);
+
+	g_return_if_fail (modem_3gpp == self->_priv.modem_3gpp_iface);
+	_nm_modem_set_operator_code (NM_MODEM (self), mm_modem_3gpp_get_operator_code (modem_3gpp));
+}
+
 /*****************************************************************************/
 
 static void
@@ -1353,6 +1383,7 @@ set_property (GObject *object,
 		/* construct-only */
 		self->_priv.modem_object = g_value_dup_object (value);
 		self->_priv.modem_iface = mm_object_get_modem (self->_priv.modem_object);
+		self->_priv.modem_3gpp_iface = mm_object_get_modem_3gpp (self->_priv.modem_object);
 		g_assert (self->_priv.modem_iface != NULL);
 		g_signal_connect (self->_priv.modem_iface,
 		                  "state-changed",
@@ -1368,6 +1399,13 @@ set_property (GObject *object,
 		                  G_CALLBACK (supported_ip_families_changed),
 		                  self);
 
+		if (self->_priv.modem_3gpp_iface) {
+			g_signal_connect (self->_priv.modem_3gpp_iface,
+			                  "notify::operator-code",
+			                  G_CALLBACK (operator_code_changed),
+			                  self);
+		}
+
 		/* Note: don't grab the Simple iface here; the Modem interface is the
 		 * only one assumed to be always valid and available */
 		break;
@@ -1389,7 +1427,9 @@ nm_modem_broadband_new (GObject *object, GError **error)
 {
 	MMObject *modem_object;
 	MMModem *modem_iface;
+	MMModem3gpp *modem_3gpp_iface;
 	const char *const*drivers;
+	const char *operator_code = NULL;
 	gs_free char *driver = NULL;
 
 	g_return_val_if_fail (MM_IS_OBJECT (object), NULL);
@@ -1405,6 +1445,10 @@ nm_modem_broadband_new (GObject *object, GError **error)
 	if (drivers)
 		driver = g_strjoinv (", ", (char **) drivers);
 
+	modem_3gpp_iface = mm_object_peek_modem_3gpp (modem_object);
+	if (modem_3gpp_iface)
+		operator_code = mm_modem_3gpp_get_operator_code (modem_3gpp_iface);
+
 	return g_object_new (NM_TYPE_MODEM_BROADBAND,
 	                     NM_MODEM_PATH, mm_object_get_path (modem_object),
 	                     NM_MODEM_UID, mm_modem_get_primary_port (modem_iface),
@@ -1414,6 +1458,7 @@ nm_modem_broadband_new (GObject *object, GError **error)
 	                     NM_MODEM_DEVICE_ID, mm_modem_get_device_identifier (modem_iface),
 	                     NM_MODEM_BROADBAND_MODEM, modem_object,
 	                     NM_MODEM_DRIVER, driver,
+	                     NM_MODEM_OPERATOR_CODE, operator_code,
 	                     NULL);
 }
 
@@ -1430,7 +1475,17 @@ dispose (GObject *object)
 	g_clear_object (&self->_priv.ipv4_config);
 	g_clear_object (&self->_priv.ipv6_config);
 	g_clear_object (&self->_priv.bearer);
-	g_clear_object (&self->_priv.modem_iface);
+
+	if (self->_priv.modem_iface) {
+		g_signal_handlers_disconnect_by_data (self->_priv.modem_iface, self);
+		g_clear_object (&self->_priv.modem_iface);
+	}
+
+	if (self->_priv.modem_3gpp_iface) {
+		g_signal_handlers_disconnect_by_data (self->_priv.modem_3gpp_iface, self);
+		g_clear_object (&self->_priv.modem_3gpp_iface);
+	}
+
 	g_clear_object (&self->_priv.simple_iface);
 	g_clear_object (&self->_priv.sim_iface);
 	g_clear_object (&self->_priv.modem_object);
diff --git a/src/devices/wwan/nm-modem-broadband.h b/src/devices/wwan/nm-modem-broadband.h
index 4948cb3b..9404f0b9 100644
--- a/src/devices/wwan/nm-modem-broadband.h
+++ b/src/devices/wwan/nm-modem-broadband.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index 4be089c0..db2c0192 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wwan/nm-modem-manager.h b/src/devices/wwan/nm-modem-manager.h
index 5f913083..1a26fd9f 100644
--- a/src/devices/wwan/nm-modem-manager.h
+++ b/src/devices/wwan/nm-modem-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
index 78d9a9f0..31111b62 100644
--- a/src/devices/wwan/nm-modem-ofono.c
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wwan/nm-modem-ofono.h b/src/devices/wwan/nm-modem-ofono.h
index d9cb68ac..1dcd79b0 100644
--- a/src/devices/wwan/nm-modem-ofono.h
+++ b/src/devices/wwan/nm-modem-ofono.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 17794229..617096a7 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -52,6 +51,8 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMModem,
 	PROP_SIM_ID,
 	PROP_IP_TYPES,
 	PROP_SIM_OPERATOR_ID,
+	PROP_OPERATOR_CODE,
+	PROP_APN,
 );
 
 enum {
@@ -90,6 +91,8 @@ typedef struct _NMModemPrivate {
 	char *sim_id;
 	NMModemIPType ip_types;
 	char *sim_operator_id;
+	char *operator_code;
+	char *apn;
 
 	NMPPPManager *ppp_manager;
 
@@ -344,7 +347,8 @@ nm_modem_get_connection_ip_type (NMModem *self,
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	if (s_ip6) {
 		method = nm_setting_ip_config_get_method (s_ip6);
-		if (g_strcmp0 (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
+		if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+		                          NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
 			ip6 = FALSE;
 		ip6_may_fail = nm_setting_ip_config_get_may_fail (s_ip6);
 	}
@@ -436,6 +440,18 @@ nm_modem_get_sim_operator_id (NMModem *self)
 	return NM_MODEM_GET_PRIVATE (self)->sim_operator_id;
 }
 
+const char *
+nm_modem_get_operator_code (NMModem *self)
+{
+	return NM_MODEM_GET_PRIVATE (self)->operator_code;
+}
+
+const char *
+nm_modem_get_apn (NMModem *self)
+{
+	return NM_MODEM_GET_PRIVATE (self)->apn;
+}
+
 /*****************************************************************************/
 /* IP method PPP */
 
@@ -824,8 +840,9 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 
 	method = nm_utils_get_ip_config_method (connection, AF_INET6);
 
-	/* Only Ignore and Auto methods make sense for WWAN */
-	if (nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
+	/* Only Ignore, Disabled and Auto methods make sense for WWAN */
+	if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                          NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
 		return NM_ACT_STAGE_RETURN_IP_DONE;
 
 	if (!nm_streq (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
@@ -1086,6 +1103,7 @@ nm_modem_check_connection_compatible (NMModem *self, NMConnection *connection, G
 
 gboolean
 nm_modem_complete_connection (NMModem *self,
+                              const char *iface,
                               NMConnection *connection,
                               NMConnection *const*existing_connections,
                               GError **error)
@@ -1100,7 +1118,7 @@ nm_modem_complete_connection (NMModem *self,
 		return FALSE;
 	}
 
-	return klass->complete_connection (self, connection, existing_connections, error);
+	return klass->complete_connection (self, iface, connection, existing_connections, error);
 }
 
 /*****************************************************************************/
@@ -1556,9 +1574,9 @@ nm_modem_set_route_parameters_from_device (NMModem *self,
 	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_table (device, AF_INET),
 	                               nm_device_get_route_metric (device, AF_INET),
-	                               nm_device_get_route_table (device, AF_INET6, TRUE),
+	                               nm_device_get_route_table (device, AF_INET6),
 	                               nm_device_get_route_metric (device, AF_INET6));
 }
 
@@ -1576,6 +1594,30 @@ nm_modem_get_capabilities (NMModem *self,
 
 /*****************************************************************************/
 
+void
+_nm_modem_set_operator_code (NMModem *self, const char *operator_code)
+{
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
+
+	if (g_strcmp0 (priv->operator_code, operator_code) != 0) {
+		g_free (priv->operator_code);
+		priv->operator_code = g_strdup (operator_code);
+		_notify (self, PROP_OPERATOR_CODE);
+	}
+}
+
+void
+_nm_modem_set_apn (NMModem *self, const char *apn)
+{
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
+
+	if (g_strcmp0 (priv->apn, apn) != 0) {
+		g_free (priv->apn);
+		priv->apn = g_strdup (apn);
+		_notify (self, PROP_APN);
+	}
+}
+
 static void
 get_property (GObject *object, guint prop_id,
               GValue *value, GParamSpec *pspec)
@@ -1614,6 +1656,12 @@ get_property (GObject *object, guint prop_id,
 	case PROP_SIM_OPERATOR_ID:
 		g_value_set_string (value, priv->sim_operator_id);
 		break;
+	case PROP_OPERATOR_CODE:
+		g_value_set_string (value, priv->operator_code);
+		break;
+	case PROP_APN:
+		g_value_set_string (value, priv->apn);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1666,6 +1714,10 @@ set_property (GObject *object, guint prop_id,
 		if (s && s[0])
 			priv->sim_operator_id = g_strdup (s);
 		break;
+	case PROP_OPERATOR_CODE:
+		/* construct-only */
+		priv->operator_code = g_value_dup_string (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -1727,6 +1779,8 @@ finalize (GObject *object)
 	g_free (priv->device_id);
 	g_free (priv->sim_id);
 	g_free (priv->sim_operator_id);
+	g_free (priv->operator_code);
+	g_free (priv->apn);
 
 	G_OBJECT_CLASS (nm_modem_parent_class)->finalize (object);
 }
@@ -1810,6 +1864,18 @@ nm_modem_class_init (NMModemClass *klass)
 	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
 	                          G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_OPERATOR_CODE] =
+	     g_param_spec_string (NM_MODEM_OPERATOR_CODE, "", "",
+	                          NULL,
+	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
+	                          G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_APN] =
+	     g_param_spec_string (NM_MODEM_APN, "", "",
+	                          NULL,
+	                          G_PARAM_READABLE |
+	                          G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
 	signals[PPP_STATS] =
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index f7b6bfe9..f5b386e8 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -43,6 +42,8 @@
 #define NM_MODEM_SIM_ID          "sim-id"
 #define NM_MODEM_IP_TYPES        "ip-types"   /* Supported IP types */
 #define NM_MODEM_SIM_OPERATOR_ID "sim-operator-id"
+#define NM_MODEM_OPERATOR_CODE   "operator-code"
+#define NM_MODEM_APN             "apn"
 
 /* Signals */
 #define NM_MODEM_PPP_STATS         "ppp-stats"
@@ -130,6 +131,7 @@ typedef struct {
 	                                                    GError **error);
 
 	gboolean (*complete_connection)            (NMModem *modem,
+	                                            const char *iface,
 	                                            NMConnection *connection,
 	                                            NMConnection *const*existing_connections,
 	                                            GError **error);
@@ -174,6 +176,8 @@ const char *nm_modem_get_device_id       (NMModem *modem);
 const char *nm_modem_get_sim_id          (NMModem *modem);
 const char *nm_modem_get_sim_operator_id (NMModem *modem);
 gboolean    nm_modem_get_iid             (NMModem *modem, NMUtilsIPv6IfaceId *out_iid);
+const char *nm_modem_get_operator_code   (NMModem *modem);
+const char *nm_modem_get_apn             (NMModem *modem);
 
 gboolean    nm_modem_set_data_port (NMModem *self,
                                     NMPlatform *platform,
@@ -194,6 +198,7 @@ gboolean nm_modem_check_connection_compatible (NMModem *self,
                                                GError **error);
 
 gboolean nm_modem_complete_connection (NMModem *self,
+                                       const char *iface,
                                        NMConnection *connection,
                                        NMConnection *const*existing_connections,
                                        GError **error);
@@ -284,4 +289,7 @@ const char *nm_modem_ip_type_to_string (NMModemIPType ip_type);
 
 guint32 nm_modem_get_configured_mtu (NMDevice *self, NMDeviceMtuSource *out_source);
 
+void _nm_modem_set_operator_code (NMModem *self, const char *operator_code);
+void _nm_modem_set_apn           (NMModem *self, const char *apn);
+
 #endif /* __NETWORKMANAGER_MODEM_H__ */
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index c9ee27ff..a0e5c160 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dhcp/nm-dhcp-client-logging.h b/src/dhcp/nm-dhcp-client-logging.h
index 1ed47170..d9b53f58 100644
--- a/src/dhcp/nm-dhcp-client-logging.h
+++ b/src/dhcp/nm-dhcp-client-logging.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index d494eff0..9f585c5d 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -1,4 +1,3 @@
-/* -*- 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)
@@ -52,6 +51,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDhcpClient,
 	PROP_ADDR_FAMILY,
 	PROP_FLAGS,
 	PROP_HWADDR,
+	PROP_BROADCAST_HWADDR,
 	PROP_IFACE,
 	PROP_IFINDEX,
 	PROP_MULTI_IDX,
@@ -66,6 +66,7 @@ typedef struct _NMDhcpClientPrivate {
 	NMDedupMultiIndex *multi_idx;
 	char *       iface;
 	GBytes *     hwaddr;
+	GBytes *     bcast_hwaddr;
 	char *       uuid;
 	GBytes *     client_id;
 	char *       hostname;
@@ -144,6 +145,14 @@ nm_dhcp_client_get_hw_addr (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->hwaddr;
 }
 
+GBytes *
+nm_dhcp_client_get_broadcast_hw_addr (NMDhcpClient *self)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), NULL);
+
+	return NM_DHCP_CLIENT_GET_PRIVATE (self)->bcast_hwaddr;
+}
+
 guint32
 nm_dhcp_client_get_route_table (NMDhcpClient *self)
 {
@@ -476,8 +485,9 @@ nm_dhcp_client_start_timeout (NMDhcpClient *self)
 {
 	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
 
+	g_return_if_fail (priv->timeout_id == 0);
+
 	/* 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;
@@ -530,6 +540,36 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 	                                                   error);
 }
 
+gboolean
+nm_dhcp_client_accept (NMDhcpClient *self,
+                       GError **error)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	if (NM_DHCP_CLIENT_GET_CLASS (self)->accept) {
+		return NM_DHCP_CLIENT_GET_CLASS (self)->accept (self,
+		                                                error);
+	}
+
+	return TRUE;
+}
+
+gboolean
+nm_dhcp_client_decline (NMDhcpClient *self,
+                        const char *error_message,
+                        GError **error)
+{
+	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
+
+	if (NM_DHCP_CLIENT_GET_CLASS (self)->decline) {
+		return NM_DHCP_CLIENT_GET_CLASS (self)->decline (self,
+		                                                 error_message,
+		                                                 error);
+	}
+
+	return TRUE;
+}
+
 static GBytes *
 get_duid (NMDhcpClient *self)
 {
@@ -690,6 +730,23 @@ bytearray_variant_to_string (NMDhcpClient *self, GVariant *value, const char *ke
 	return converted;
 }
 
+static int
+label_is_unknown_xyz (const char *label)
+{
+	if (!NM_STR_HAS_PREFIX (label, "unknown_"))
+		return -EINVAL;
+
+	label += NM_STRLEN  ("unknown_");
+	if (   label[0] != '2'
+	    || !g_ascii_isdigit (label[1])
+	    || !g_ascii_isdigit (label[2])
+	    || label[3] != '\0')
+		return -EINVAL;
+
+	return _nm_utils_ascii_str_to_int64 (label, 10, 224, 254, -EINVAL);
+}
+
+
 #define OLD_TAG "old_"
 #define NEW_TAG "new_"
 
@@ -713,14 +770,41 @@ maybe_add_option (NMDhcpClient *self,
 	                       "dhcp_message_type"))
 		return;
 
-	if (g_str_has_prefix (key, NEW_TAG))
+	if (NM_STR_HAS_PREFIX (key, NEW_TAG))
 		key += NM_STRLEN (NEW_TAG);
-	if (!key[0])
+	if (NM_STR_HAS_PREFIX (key, "private_") || !key[0])
 		return;
 
 	str_value = bytearray_variant_to_string (self, value, key);
-	if (str_value)
+	if (str_value) {
+		int priv_opt_num;
+
 		g_hash_table_insert (hash, g_strdup (key), str_value);
+
+		/* dhclient has no special labels for private dhcp options: it uses "unknown_xyz"
+		 * labels for that. We need to identify those to alias them to our "private_xyz"
+		 * format unsed in the internal dchp plugins.
+		 */
+		if ((priv_opt_num = label_is_unknown_xyz (key)) > 0) {
+			gs_free guint8 *check_val = NULL;
+			char *hex_str = NULL;
+			gsize len;
+
+			/* dhclient passes values from dhcp private options in its own "string" format:
+			 * if the raw values are printable as ascii strings, it will pass the string
+			 * representation; if the values are not printable as an ascii string, it will
+			 * pass a string displaying the hex values (hex string). Try to enforce passing
+			 * always an hex string, converting string representation if needed.
+			 */
+			check_val = nm_utils_hexstr2bin_alloc (str_value, FALSE, TRUE, ":", 0, &len);
+			hex_str = nm_utils_bin2hexstr_full (check_val ?: (guint8 *) str_value,
+			                                    check_val ? len : strlen (str_value),
+			                                    ':', FALSE, NULL);
+			g_hash_table_insert (hash,
+			                     g_strdup_printf ("private_%d", priv_opt_num),
+			                     hex_str);
+		}
+	}
 }
 
 gboolean
@@ -839,6 +923,9 @@ get_property (GObject *object, guint prop_id,
 	case PROP_HWADDR:
 		g_value_set_boxed (value, priv->hwaddr);
 		break;
+	case PROP_BROADCAST_HWADDR:
+		g_value_set_boxed (value, priv->bcast_hwaddr);
+		break;
 	case PROP_ADDR_FAMILY:
 		g_value_set_int (value, priv->addr_family);
 		break;
@@ -900,6 +987,10 @@ set_property (GObject *object, guint prop_id,
 		/* construct-only */
 		priv->hwaddr = g_value_dup_boxed (value);
 		break;
+	case PROP_BROADCAST_HWADDR:
+		/* construct-only */
+		priv->bcast_hwaddr = g_value_dup_boxed (value);
+		break;
 	case PROP_ADDR_FAMILY:
 		/* construct-only */
 		priv->addr_family = g_value_get_int (value);
@@ -966,6 +1057,7 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->uuid, g_free);
 	g_clear_pointer (&priv->client_id, g_bytes_unref);
 	g_clear_pointer (&priv->hwaddr, g_bytes_unref);
+	g_clear_pointer (&priv->bcast_hwaddr, g_bytes_unref);
 
 	G_OBJECT_CLASS (nm_dhcp_client_parent_class)->dispose (object);
 
@@ -1010,6 +1102,12 @@ nm_dhcp_client_class_init (NMDhcpClientClass *client_class)
 	                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
 	                        G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_BROADCAST_HWADDR] =
+	    g_param_spec_boxed (NM_DHCP_CLIENT_BROADCAST_HWADDR, "", "",
+	                        G_TYPE_BYTES,
+	                        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,
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 1db7eac6..9eb76f33 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -1,4 +1,3 @@
-/* -*- 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)
@@ -35,17 +34,18 @@
 #define NM_IS_DHCP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_CLIENT))
 #define NM_DHCP_CLIENT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_CLIENT, NMDhcpClientClass))
 
-#define NM_DHCP_CLIENT_ADDR_FAMILY  "addr-family"
-#define NM_DHCP_CLIENT_FLAGS        "flags"
-#define NM_DHCP_CLIENT_HWADDR       "hwaddr"
-#define NM_DHCP_CLIENT_IFINDEX      "ifindex"
-#define NM_DHCP_CLIENT_INTERFACE    "iface"
-#define NM_DHCP_CLIENT_MULTI_IDX    "multi-idx"
-#define NM_DHCP_CLIENT_HOSTNAME     "hostname"
-#define NM_DHCP_CLIENT_ROUTE_METRIC "route-metric"
-#define NM_DHCP_CLIENT_ROUTE_TABLE  "route-table"
-#define NM_DHCP_CLIENT_TIMEOUT      "timeout"
-#define NM_DHCP_CLIENT_UUID         "uuid"
+#define NM_DHCP_CLIENT_ADDR_FAMILY      "addr-family"
+#define NM_DHCP_CLIENT_FLAGS            "flags"
+#define NM_DHCP_CLIENT_HWADDR           "hwaddr"
+#define NM_DHCP_CLIENT_BROADCAST_HWADDR "broadcast-hwaddr"
+#define NM_DHCP_CLIENT_IFINDEX          "ifindex"
+#define NM_DHCP_CLIENT_INTERFACE        "iface"
+#define NM_DHCP_CLIENT_MULTI_IDX        "multi-idx"
+#define NM_DHCP_CLIENT_HOSTNAME         "hostname"
+#define NM_DHCP_CLIENT_ROUTE_METRIC     "route-metric"
+#define NM_DHCP_CLIENT_ROUTE_TABLE      "route-table"
+#define NM_DHCP_CLIENT_TIMEOUT          "timeout"
+#define NM_DHCP_CLIENT_UUID             "uuid"
 
 #define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED "state-changed"
 #define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
@@ -83,6 +83,13 @@ typedef struct {
 	                           const char *last_ip4_address,
 	                           GError **error);
 
+	gboolean (*accept)        (NMDhcpClient *self,
+	                           GError **error);
+
+	gboolean (*decline)       (NMDhcpClient *self,
+	                           const char *error_message,
+	                           GError **error);
+
 	gboolean (*ip6_start)     (NMDhcpClient *self,
 	                           const char *anycast_addr,
 	                           const struct in6_addr *ll_addr,
@@ -123,6 +130,8 @@ GBytes *nm_dhcp_client_get_duid (NMDhcpClient *self);
 
 GBytes *nm_dhcp_client_get_hw_addr (NMDhcpClient *self);
 
+GBytes *nm_dhcp_client_get_broadcast_hw_addr (NMDhcpClient *self);
+
 guint32 nm_dhcp_client_get_route_table (NMDhcpClient *self);
 
 void nm_dhcp_client_set_route_table (NMDhcpClient *self, guint32 route_table);
@@ -156,6 +165,13 @@ gboolean nm_dhcp_client_start_ip6 (NMDhcpClient *self,
                                    guint needed_prefixes,
                                    GError **error);
 
+gboolean nm_dhcp_client_accept (NMDhcpClient *self,
+                                GError **error);
+
+gboolean nm_dhcp_client_decline (NMDhcpClient *self,
+                                 const char *error_message,
+                                 GError **error);
+
 void nm_dhcp_client_stop (NMDhcpClient *self, gboolean release);
 
 /* Backend helpers for subclasses */
@@ -200,5 +216,6 @@ 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;
+extern const NMDhcpClientFactory _nm_dhcp_client_factory_nettools;
 
 #endif /* __NETWORKMANAGER_DHCP_CLIENT_H__ */
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index 85ca3704..bf3df399 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.h b/src/dhcp/nm-dhcp-dhclient-utils.h
index 57a711db..8ca893c3 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.h
+++ b/src/dhcp/nm-dhcp-dhclient-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dhcp/nm-dhcp-dhclient.c b/src/dhcp/nm-dhcp-dhclient.c
index b655a1eb..54b50479 100644
--- a/src/dhcp/nm-dhcp-dhclient.c
+++ b/src/dhcp/nm-dhcp-dhclient.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-dhclient.c - dhclient specific hooks for NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -376,7 +375,7 @@ dhclient_start (NMDhcpClient *client,
 	iface = nm_dhcp_client_get_iface (client);
 	uuid = nm_dhcp_client_get_uuid (client);
 
-	pid_file = g_strdup_printf (RUNSTATEDIR "/dhclient%s-%s.pid",
+	pid_file = g_strdup_printf (NMRUNDIR "/dhclient%s-%s.pid",
 	                            _addr_family_to_path_part (addr_family),
 	                            iface);
 
diff --git a/src/dhcp/nm-dhcp-dhcpcanon.c b/src/dhcp/nm-dhcp-dhcpcanon.c
index 868cc9dd..2d2113cc 100644
--- a/src/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/dhcp/nm-dhcp-dhcpcanon.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -186,18 +185,6 @@ ip4_start (NMDhcpClient *client,
 	                        error);
 }
 
-static gboolean
-ip6_start (NMDhcpClient *client,
-           const char *dhcp_anycast_addr,
-           const struct in6_addr *ll_addr,
-           NMSettingIP6ConfigPrivacy privacy,
-           guint needed_prefixes,
-           GError **error)
-{
-	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcanon plugin does not support IPv6");
-	return FALSE;
-}
-
 static void
 stop (NMDhcpClient *client, gboolean release)
 {
@@ -257,7 +244,6 @@ nm_dhcp_dhcpcanon_class_init (NMDhcpDhcpcanonClass *dhcpcanon_class)
 	object_class->dispose = dispose;
 
 	client_class->ip4_start = ip4_start;
-	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
 }
 
diff --git a/src/dhcp/nm-dhcp-dhcpcd.c b/src/dhcp/nm-dhcp-dhcpcd.c
index 2a7482b1..c300bbe2 100644
--- a/src/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/dhcp/nm-dhcp-dhcpcd.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-dhcpcd.c - dhcpcd specific hooks for NetworkManager
  *
  * Copyright (C) 2008 Roy Marples
@@ -100,9 +99,9 @@ ip4_start (NMDhcpClient *client,
 	iface = nm_dhcp_client_get_iface (client);
 
 	/* dhcpcd does not allow custom pidfiles; the pidfile is always
-	 * RUNDIR "dhcpcd-<ifname>.pid".
+	 * RUNSTATEDIR "dhcpcd-<ifname>.pid".
 	 */
-	priv->pid_file = g_strdup_printf (RUNDIR "/dhcpcd-%s.pid", iface);
+	priv->pid_file = g_strdup_printf (RUNSTATEDIR "/dhcpcd-%s.pid", iface);
 
 	dhcpcd_path = nm_dhcp_dhcpcd_get_path ();
 	if (!dhcpcd_path) {
@@ -180,18 +179,6 @@ ip4_start (NMDhcpClient *client,
 	return TRUE;
 }
 
-static gboolean
-ip6_start (NMDhcpClient *client,
-           const char *dhcp_anycast_addr,
-           const struct in6_addr *ll_addr,
-           NMSettingIP6ConfigPrivacy privacy,
-           guint needed_prefixes,
-           GError **error)
-{
-	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "dhcpcd plugin does not support IPv6");
-	return FALSE;
-}
-
 static void
 stop (NMDhcpClient *client, gboolean release)
 {
@@ -251,7 +238,6 @@ nm_dhcp_dhcpcd_class_init (NMDhcpDhcpcdClass *dhcpcd_class)
 	object_class->dispose = dispose;
 
 	client_class->ip4_start = ip4_start;
-	client_class->ip6_start = ip6_start;
 	client_class->stop = stop;
 }
 
diff --git a/src/dhcp/nm-dhcp-helper-api.h b/src/dhcp/nm-dhcp-helper-api.h
index 58344215..c1a3c71c 100644
--- a/src/dhcp/nm-dhcp-helper-api.h
+++ b/src/dhcp/nm-dhcp-helper-api.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * This library is free software; you can redistribute it and/or
diff --git a/src/dhcp/nm-dhcp-helper.c b/src/dhcp/nm-dhcp-helper.c
index 8f753a61..9acc4045 100644
--- a/src/dhcp/nm-dhcp-helper.c
+++ b/src/dhcp/nm-dhcp-helper.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dhcp/nm-dhcp-listener.c b/src/dhcp/nm-dhcp-listener.c
index 049c4e55..88aafeb0 100644
--- a/src/dhcp/nm-dhcp-listener.c
+++ b/src/dhcp/nm-dhcp-listener.c
@@ -1,4 +1,3 @@
-/* -*- 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)
@@ -39,7 +38,7 @@
 
 /*****************************************************************************/
 
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5] = {
 	/* the order here matters, as we will try the plugins in this order to find
 	 * the first available plugin. */
 
@@ -53,15 +52,16 @@ const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
 	&_nm_dhcp_client_factory_dhcpcd,
 #endif
 	&_nm_dhcp_client_factory_internal,
+	&_nm_dhcp_client_factory_nettools,
 };
 
 /*****************************************************************************/
 
 typedef struct {
-	NMDBusManager *      dbus_mgr;
-	gulong              new_conn_id;
-	gulong              dis_conn_id;
-	GHashTable *        connections;
+	NMDBusManager *dbus_mgr;
+	gulong         new_conn_id;
+	gulong         dis_conn_id;
+	GHashTable    *connections;
 } NMDhcpListenerPrivate;
 
 struct _NMDhcpListener {
@@ -282,7 +282,7 @@ nm_dhcp_listener_init (NMDhcpListener *self)
 	/* Maps GDBusConnection :: signal-id */
 	priv->connections = g_hash_table_new (nm_direct_hash, NULL);
 
-	priv->dbus_mgr = nm_dbus_manager_get ();
+	priv->dbus_mgr = g_object_ref (nm_dbus_manager_get ());
 
 	/* Register the socket our DHCP clients will return lease info on */
 	nm_dbus_manager_private_server_register (priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG);
@@ -303,10 +303,11 @@ dispose (GObject *object)
 
 	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->new_conn_id);
 	nm_clear_g_signal_handler (priv->dbus_mgr, &priv->dis_conn_id);
-	priv->dbus_mgr = NULL;
 
 	g_clear_pointer (&priv->connections, g_hash_table_destroy);
 
+	g_clear_object (&priv->dbus_mgr);
+
 	G_OBJECT_CLASS (nm_dhcp_listener_parent_class)->dispose (object);
 }
 
diff --git a/src/dhcp/nm-dhcp-listener.h b/src/dhcp/nm-dhcp-listener.h
index 3018b97a..d9724062 100644
--- a/src/dhcp/nm-dhcp-listener.h
+++ b/src/dhcp/nm-dhcp-listener.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dhcp/nm-dhcp-manager.c b/src/dhcp/nm-dhcp-manager.c
index fe843a2c..304a7b99 100644
--- a/src/dhcp/nm-dhcp-manager.c
+++ b/src/dhcp/nm-dhcp-manager.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -90,6 +89,54 @@ _client_factory_available (const NMDhcpClientFactory *client_factory)
 	return NULL;
 }
 
+static const NMDhcpClientFactory *
+_client_factory_get_effective (const NMDhcpClientFactory *client_factory,
+                               int addr_family)
+{
+	nm_auto_unref_gtypeclass NMDhcpClientClass *klass = NULL;
+
+	nm_assert (client_factory);
+	nm_assert_addr_family (addr_family);
+
+	/* currently, the chosen DHCP plugin for IPv4 and IPv6 is configured in NetworkManager.conf
+	 * and cannot be reloaded. It would be nice to configure the plugin per address family
+	 * or to be able to reload it.
+	 *
+	 * Note that certain options in NetworkManager.conf depend on the chosen DHCP plugin.
+	 * See "dhcp-plugin:" in "Device List Format" (`man NetworkManager.conf`).
+	 * Supporting reloading the plugin would also require to re-evalate the decisions from
+	 * the "Device List Format". Likewise, having per-address family plugins would make the
+	 * "main.dhcp" setting and "dhcp-plugin:" match non-sensical because these configurations
+	 * currently are address family independet.
+	 *
+	 * So actually, we don't want that complexity. We want to phase out all plugins in favor
+	 * of the internal plugin.
+	 * However, certain existing plugins are well known to not support an address family.
+	 * In those cases, we should just silently fallback to the internal plugin.
+	 *
+	 * This could be a problem with forward compatibility if we ever intended to add IPv6 support
+	 * to those plugins. But we don't intend to do so. The internal plugin is the way forward and
+	 * not extending other plugins. */
+
+	if (client_factory == &_nm_dhcp_client_factory_internal) {
+		/* already using internal plugin. Nothing to do. */
+		return client_factory;
+	}
+
+	klass = g_type_class_ref (client_factory->get_type ());
+
+	nm_assert (NM_IS_DHCP_CLIENT_CLASS (klass));
+
+	if (addr_family == AF_INET6) {
+		return   klass->ip6_start
+		       ? client_factory
+		       : &_nm_dhcp_client_factory_internal;
+	}
+	return   klass->ip4_start
+	       ? client_factory
+	       : &_nm_dhcp_client_factory_internal;
+}
+
 /*****************************************************************************/
 
 static NMDhcpClient *
@@ -157,6 +204,7 @@ client_start (NMDhcpManager *self,
               const char *iface,
               int ifindex,
               GBytes *hwaddr,
+              GBytes *bcast_hwaddr,
               const char *uuid,
               guint32 route_table,
               guint32 route_metric,
@@ -177,6 +225,7 @@ client_start (NMDhcpManager *self,
 	NMDhcpClient *client;
 	gboolean success = FALSE;
 	gsize hwaddr_len;
+	const NMDhcpClientFactory *client_factory;
 
 	g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
 	g_return_val_if_fail (iface, NULL);
@@ -185,10 +234,11 @@ client_start (NMDhcpManager *self,
 	g_return_val_if_fail (!dhcp_client_id || g_bytes_get_size (dhcp_client_id) >= 2, NULL);
 	g_return_val_if_fail (!error || !*error, NULL);
 
-	if (!hwaddr) {
+	if (!hwaddr || !bcast_hwaddr) {
 		nm_utils_error_set (error,
 		                    NM_UTILS_ERROR_UNKNOWN,
-		                    "missing MAC address");
+		                    "missing %s address",
+		                    hwaddr ? "broadcast" : "MAC");
 		return NULL;
 	}
 
@@ -201,24 +251,33 @@ client_start (NMDhcpManager *self,
 		g_return_val_if_reached (NULL) ;
 	}
 
+	nm_assert (g_bytes_get_size (hwaddr) == g_bytes_get_size (bcast_hwaddr));
+
 	priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
 
-	nm_assert (priv->client_factory);
+	client_factory = _client_factory_get_effective (priv->client_factory, addr_family);
 
 	/* Kill any old client instance */
 	client = get_client_for_ifindex (self, addr_family, ifindex);
 	if (client) {
+		/* FIXME: we cannot just call synchronously "stop()" and forget about the client.
+		 * We need to wait for the client to be fully stopped because most/all clients
+		 * cannot quit right away.
+		 *
+		 * FIXME(shutdown): also fix this during shutdown, to wait for all DHCP clients
+		 * to be fully stopped. */
 		remove_client (self, client);
 		nm_dhcp_client_stop (client, FALSE);
 		g_object_unref (client);
 	}
 
-	client = g_object_new (priv->client_factory->get_type (),
+	client = g_object_new (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_BROADCAST_HWADDR, bcast_hwaddr,
 	                       NM_DHCP_CLIENT_UUID, uuid,
 	                       NM_DHCP_CLIENT_HOSTNAME, hostname,
 	                       NM_DHCP_CLIENT_ROUTE_TABLE, (guint) route_table,
@@ -291,6 +350,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
                            const char *iface,
                            int ifindex,
                            GBytes *hwaddr,
+                           GBytes *bcast_hwaddr,
                            const char *uuid,
                            guint32 route_table,
                            guint32 route_metric,
@@ -341,6 +401,7 @@ nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
 	                     iface,
 	                     ifindex,
 	                     hwaddr,
+	                     bcast_hwaddr,
 	                     uuid,
 	                     route_table,
 	                     route_metric,
@@ -365,6 +426,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
                            const char *iface,
                            int ifindex,
                            GBytes *hwaddr,
+                           GBytes *bcast_hwaddr,
                            const struct in6_addr *ll_addr,
                            const char *uuid,
                            guint32 route_table,
@@ -396,6 +458,7 @@ nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
 	                     iface,
 	                     ifindex,
 	                     hwaddr,
+	                     bcast_hwaddr,
 	                     uuid,
 	                     route_table,
 	                     route_metric,
diff --git a/src/dhcp/nm-dhcp-manager.h b/src/dhcp/nm-dhcp-manager.h
index f8f39e53..ff0d6f54 100644
--- a/src/dhcp/nm-dhcp-manager.h
+++ b/src/dhcp/nm-dhcp-manager.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -50,6 +49,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip4     (NMDhcpManager *manager,
                                               const char *iface,
                                               int ifindex,
                                               GBytes *hwaddr,
+                                              GBytes *bcast_hwaddr,
                                               const char *uuid,
                                               guint32 route_table,
                                               guint32 route_metric,
@@ -67,6 +67,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
                                               const char *iface,
                                               int ifindex,
                                               GBytes *hwaddr,
+                                              GBytes *bcast_hwaddr,
                                               const struct in6_addr *ll_addr,
                                               const char *uuid,
                                               guint32 route_table,
@@ -85,7 +86,7 @@ NMDhcpClient * nm_dhcp_manager_start_ip6     (NMDhcpManager *manager,
 /* For testing only */
 extern const char* nm_dhcp_helper_path;
 
-extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4];
+extern const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5];
 
 void nmtst_dhcp_manager_unget (gpointer singleton_instance);
 
diff --git a/src/dhcp/nm-dhcp-nettools.c b/src/dhcp/nm-dhcp-nettools.c
new file mode 100644
index 00000000..3aa92180
--- /dev/null
+++ b/src/dhcp/nm-dhcp-nettools.c
@@ -0,0 +1,1278 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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-2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <net/if_arp.h>
+
+#include "nm-sd-adapt-shared.h"
+#include "hostname-util.h"
+
+#include "nm-glib-aux/nm-dedup-multi.h"
+#include "nm-std-aux/unaligned.h"
+
+#include "nm-utils.h"
+#include "nm-config.h"
+#include "nm-dhcp-utils.h"
+#include "nm-dhcp-options.h"
+#include "nm-core-utils.h"
+#include "NetworkManagerUtils.h"
+#include "platform/nm-platform.h"
+#include "nm-dhcp-client-logging.h"
+#include "n-dhcp4/src/n-dhcp4.h"
+#include "systemd/nm-sd-utils-shared.h"
+
+/*****************************************************************************/
+
+#define NM_TYPE_DHCP_NETTOOLS            (nm_dhcp_nettools_get_type ())
+#define NM_DHCP_NETTOOLS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettools))
+#define NM_DHCP_NETTOOLS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass))
+#define NM_IS_DHCP_NETTOOLS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_NETTOOLS))
+#define NM_IS_DHCP_NETTOOLS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_NETTOOLS))
+#define NM_DHCP_NETTOOLS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_NETTOOLS, NMDhcpNettoolsClass))
+
+typedef struct _NMDhcpNettools NMDhcpNettools;
+typedef struct _NMDhcpNettoolsClass NMDhcpNettoolsClass;
+
+static GType nm_dhcp_nettools_get_type (void);
+
+/*****************************************************************************/
+
+typedef struct {
+	NDhcp4Client *client;
+	NDhcp4ClientProbe *probe;
+	NDhcp4ClientLease *lease;
+	GIOChannel *channel;
+	guint event_id;
+} NMDhcpNettoolsPrivate;
+
+struct _NMDhcpNettools {
+	NMDhcpClient parent;
+	NMDhcpNettoolsPrivate _priv;
+};
+
+struct _NMDhcpNettoolsClass {
+	NMDhcpClientClass parent;
+};
+
+G_DEFINE_TYPE (NMDhcpNettools, nm_dhcp_nettools, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_NETTOOLS_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDhcpNettools, NM_IS_DHCP_NETTOOLS)
+
+/*****************************************************************************/
+
+#define DHCP_MAX_FQDN_LENGTH 255
+
+enum {
+	DHCP_FQDN_FLAG_S = (1 << 0),
+	DHCP_FQDN_FLAG_O = (1 << 1),
+	DHCP_FQDN_FLAG_E = (1 << 2),
+	DHCP_FQDN_FLAG_N = (1 << 3),
+};
+
+enum {
+	NM_IN_ADDR_CLASS_A,
+	NM_IN_ADDR_CLASS_B,
+	NM_IN_ADDR_CLASS_C,
+	NM_IN_ADDR_CLASS_INVALID,
+};
+
+static int
+in_addr_class (struct in_addr addr)
+{
+	switch (ntohl (addr.s_addr) >> 24) {
+	case   0 ... 127:
+		return NM_IN_ADDR_CLASS_A;
+	case 128 ... 191:
+		return NM_IN_ADDR_CLASS_B;
+	case 192 ... 223:
+		return NM_IN_ADDR_CLASS_C;
+	default:
+		return NM_IN_ADDR_CLASS_INVALID;
+	}
+}
+
+static gboolean
+lease_option_consume (void *out,
+                      size_t n_out,
+                      uint8_t **datap,
+                      size_t *n_datap)
+{
+	if (*n_datap < n_out)
+		return FALSE;
+
+	memcpy (out, *datap, n_out);
+	*datap += n_out;
+	*n_datap -= n_out;
+	return TRUE;
+}
+
+static gboolean
+lease_option_next_in_addr (struct in_addr *addrp,
+                           uint8_t **datap,
+                           size_t *n_datap)
+{
+	return lease_option_consume (addrp, sizeof (struct in_addr), datap, n_datap);
+}
+
+static gboolean
+lease_option_next_route (struct in_addr *destp,
+                         uint8_t *plenp,
+                         struct in_addr *gatewayp,
+                         gboolean classless,
+                         uint8_t **datap,
+                         size_t *n_datap)
+{
+	struct in_addr dest = {}, gateway;
+	uint8_t *data = *datap;
+	size_t n_data = *n_datap;
+	uint8_t plen;
+
+	if (classless) {
+		if (!lease_option_consume (&plen, sizeof (plen), &data, &n_data))
+			return FALSE;
+
+		if (plen > 32)
+			return FALSE;
+
+		if (!lease_option_consume (&dest, plen / 8, &data, &n_data))
+			return FALSE;
+	} else {
+		if (!lease_option_next_in_addr (&dest, &data, &n_data))
+			return FALSE;
+
+		switch (in_addr_class (dest)) {
+		case NM_IN_ADDR_CLASS_A:
+			plen = 8;
+			break;
+		case NM_IN_ADDR_CLASS_B:
+			plen = 16;
+			break;
+		case NM_IN_ADDR_CLASS_C:
+			plen = 24;
+			break;
+		case NM_IN_ADDR_CLASS_INVALID:
+			return FALSE;
+		}
+	}
+
+	dest.s_addr = nm_utils_ip4_address_clear_host_address (dest.s_addr, plen);
+
+	if (!lease_option_next_in_addr (&gateway, &data, &n_data))
+		return FALSE;
+
+	*destp = dest;
+	*plenp = plen;
+	*gatewayp = gateway;
+	*datap = data;
+	*n_datap = n_data;
+	return TRUE;
+}
+
+static gboolean
+lease_option_print_label (GString *str, size_t n_label, uint8_t **datap, size_t *n_datap)
+{
+	for (size_t i = 0; i < n_label; ++i) {
+		uint8_t c;
+
+		if (!lease_option_consume(&c, sizeof (c), datap, n_datap))
+			return FALSE;
+
+		switch (c) {
+                case 'a' ... 'z':
+                case 'A' ... 'Z':
+		case '0' ... '9':
+                case '-':
+		case '_':
+			g_string_append_c(str, c);
+			break;
+		case '.':
+		case '\\':
+			g_string_append_printf(str, "\\%c", c);
+			break;
+		default:
+			g_string_append_printf(str, "\\%3d", c);
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean
+lease_option_print_domain_name (GString *str, uint8_t *cache, size_t *n_cachep, uint8_t **datap, size_t *n_datap)
+{
+	uint8_t *domain;
+	size_t n_domain, n_cache = *n_cachep;
+	uint8_t **domainp = datap;
+	size_t *n_domainp = n_datap;
+	gboolean first = TRUE;
+	uint8_t c;
+
+	/*
+	 * We are given two adjacent memory regions. The @cache contains alreday parsed
+	 * domain names, and the @datap contains the remaining data to parse.
+	 *
+	 * A domain name is formed from a sequence of labels. Each label start with
+	 * a length byte, where the two most significant bits are unset. A zero-length
+	 * label indicates the end of the domain name.
+	 *
+	 * Alternatively, a label can be followed by an offset (indicated by the two
+	 * most significant bits being set in the next byte that is read). The offset
+	 * is an offset into the cache, where the next label of the domain name can
+	 * be found.
+	 *
+	 * Note, that each time a jump to an offset is performed, the size of the
+	 * cache shrinks, so this is guaranteed to terminate.
+	 */
+	if (cache + n_cache != *datap)
+		return FALSE;
+
+	for (;;) {
+		if (!lease_option_consume(&c, sizeof (c), domainp, n_domainp))
+			return FALSE;
+
+		switch (c & 0xC0) {
+		case 0x00: /* label length */
+		{
+			size_t n_label = c;
+
+			if (n_label == 0) {
+				/*
+				 * We reached the final label of the domain name. Adjust
+				 * the cache to include the consumed data, and return.
+				 */
+				*n_cachep = *datap - cache;
+				return TRUE;
+			}
+
+			if (!first) {
+				g_string_append_c(str, '.');
+				first = FALSE;
+			}
+
+			if (!lease_option_print_label (str, n_label, domainp, n_domainp))
+				return FALSE;
+
+			break;
+		}
+		case 0xC0: /* back pointer */
+		{
+			size_t offset = (c & 0x3F) << 16;
+
+			/*
+			 * The offset is given as two bytes (in big endian), where the
+			 * two high bits are masked out.
+			 */
+
+			if (!lease_option_consume (&c, sizeof (c), domainp, n_domainp))
+				return FALSE;
+
+			offset += c;
+
+			if (offset >= n_cache)
+				return FALSE;
+
+			domain = cache + offset;
+			n_domain = n_cache - offset;
+			n_cache = offset;
+
+			domainp = &domain;
+			n_domainp = &n_domain;
+
+			break;
+		}
+		default:
+			return FALSE;
+		}
+	}
+}
+
+static gboolean
+lease_get_in_addr (NDhcp4ClientLease *lease,
+                   guint8 option,
+                   struct in_addr *addrp) {
+	struct in_addr addr;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, option, &data, &n_data);
+	if (r)
+		return FALSE;
+
+	if (!lease_option_next_in_addr (&addr, &data, &n_data))
+		return FALSE;
+
+	if (n_data != 0)
+		return FALSE;
+
+	*addrp = addr;
+	return TRUE;
+}
+
+static gboolean
+lease_get_u16 (NDhcp4ClientLease *lease,
+               uint8_t option,
+               uint16_t *u16p)
+{
+	uint8_t *data;
+	size_t n_data;
+	uint16_t be16;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, option, &data, &n_data);
+	if (r)
+		return FALSE;
+
+	if (n_data != sizeof (be16))
+		return FALSE;
+
+	memcpy (&be16, data, sizeof (be16));
+
+	*u16p = ntohs(be16);
+	return TRUE;
+}
+
+#define LOG_LEASE(domain, ...) \
+    G_STMT_START { \
+        _LOG2I ((domain), (iface), "  "__VA_ARGS__); \
+    } G_STMT_END
+
+static gboolean
+lease_parse_address (NDhcp4ClientLease *lease,
+                     const char *iface,
+                     NMIP4Config *ip4_config,
+                     GHashTable *options,
+                     GError **error)
+{
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+	const gint64 ts = nm_utils_get_monotonic_timestamp_ns ();
+	struct in_addr a_address;
+	struct in_addr a_netmask;
+	guint32 a_plen;
+	guint64 a_lifetime;
+
+	n_dhcp4_client_lease_get_yiaddr (lease, &a_address);
+	if (a_address.s_addr == INADDR_ANY) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get address from lease");
+		return FALSE;
+	}
+
+	/* n_dhcp4_client_lease_get_lifetime() never fails */
+	n_dhcp4_client_lease_get_lifetime (lease, &a_lifetime);
+
+	if (!lease_get_in_addr (lease, NM_DHCP_OPTION_DHCP4_SUBNET_MASK, &a_netmask)) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "could not get netmask from lease");
+		return FALSE;
+	}
+
+	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
+	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
+
+	LOG_LEASE (LOGD_DHCP4, "address %s/%u", addr_str, a_plen);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,
+	                           addr_str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
+	                           nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+
+	LOG_LEASE (LOGD_DHCP4, "expires in %u seconds",
+	           (guint) ((a_lifetime - ts)/1000000000));
+	nm_dhcp_option_add_option_u64 (options,
+	                               _nm_dhcp_option_dhcp4_options,
+	                               NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
+	                               (guint64) (a_lifetime / 1000000000));
+
+	nm_ip4_config_add_address (ip4_config,
+	                           &((const NMPlatformIP4Address) {
+	                                   .address      = a_address.s_addr,
+	                                   .peer_address = a_address.s_addr,
+	                                   .plen         = a_plen,
+	                                   .addr_source  = NM_IP_CONFIG_SOURCE_DHCP,
+	                                   .timestamp    = ts / 1000000000,
+	                                   .lifetime     = (a_lifetime - ts) / 1000000000,
+	                                   .preferred    = (a_lifetime - ts) / 1000000000,
+	                           }));
+
+	return TRUE;
+}
+
+static void
+lease_parse_domain_name_servers (NDhcp4ClientLease *lease,
+                                 const char *iface,
+                                 NMIP4Config *ip4_config,
+                                 GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+	struct in_addr addr;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER, &data, &n_data);
+	if (r)
+		return;
+
+	nm_gstring_prepare (&str);
+
+	while (lease_option_next_in_addr (&addr, &data, &n_data)) {
+
+		nm_utils_inet4_ntop (addr.s_addr, addr_str);
+		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
+
+		if (   addr.s_addr == 0
+		    || nm_ip4_addr_is_localhost (addr.s_addr)) {
+			/* Skip localhost addresses, like also networkd does.
+			 * See https://github.com/systemd/systemd/issues/4524. */
+			continue;
+		}
+		nm_ip4_config_add_nameserver (ip4_config, addr.s_addr);
+	}
+
+	LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,
+	                           str->str);
+}
+
+static void
+lease_parse_routes (NDhcp4ClientLease *lease,
+                    const char *iface,
+                    NMIP4Config *ip4_config,
+                    GHashTable *options,
+                    guint32 route_table,
+                    guint32 route_metric)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	char dest_str[NM_UTILS_INET_ADDRSTRLEN];
+	char gateway_str[NM_UTILS_INET_ADDRSTRLEN];
+	const char *s;
+	struct in_addr dest, gateway;
+	uint8_t plen;
+	guint32 m;
+	gboolean has_router_from_classless = FALSE, has_classless = FALSE;
+	guint32 default_route_metric = route_metric;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE, &data, &n_data);
+	if (!r) {
+		nm_gstring_prepare (&str);
+
+		has_classless = TRUE;
+
+		while (lease_option_next_route (&dest, &plen, &gateway, TRUE, &data, &n_data)) {
+
+			nm_utils_inet4_ntop (dest.s_addr, dest_str);
+			nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
+
+			LOG_LEASE (LOGD_DHCP4,
+			           "classless static route %s/%d gw %s",
+			           dest_str,
+			           (int) plen,
+			           gateway_str);
+			g_string_append_printf (nm_gstring_add_space_delimiter (str),
+			                        "%s/%d %s",
+			                        dest_str,
+			                        (int) plen,
+			                        gateway_str);
+
+			if (plen == 0) {
+				/* if there are multiple default routes, we add them with differing
+				 * metrics. */
+				m = default_route_metric;
+				if (default_route_metric < G_MAXUINT32)
+					default_route_metric++;
+
+				has_router_from_classless = TRUE;
+			} else {
+				m = route_metric;
+                        }
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .network       = dest.s_addr,
+			                             .plen          = plen,
+			                             .gateway       = gateway.s_addr,
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .metric        = m,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                         }),
+			                         NULL);
+		}
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
+		                           str->str);
+	}
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_STATIC_ROUTE, &data, &n_data);
+	if (!r) {
+		nm_gstring_prepare (&str);
+
+		while (lease_option_next_route (&dest, &plen, &gateway, FALSE, &data, &n_data)) {
+
+			nm_utils_inet4_ntop (dest.s_addr, dest_str);
+			nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
+
+			LOG_LEASE (LOGD_DHCP4,
+			           "static route %s/%d gw %s",
+			           dest_str,
+			           (int) plen,
+			           gateway_str);
+			g_string_append_printf (nm_gstring_add_space_delimiter (str),
+			                        "%s/%d %s",
+			                        dest_str,
+			                        (int) plen,
+			                        gateway_str);
+
+			if (has_classless) {
+				/* RFC 3443: if the DHCP server returns both a Classless Static Routes
+				 * option and a Static Routes option, the DHCP client MUST ignore the
+				 * Static Routes option. */
+				continue;
+			}
+
+			if (plen == 0) {
+				/* for option 33 (static route), RFC 2132 says:
+				 *
+				 * The default route (0.0.0.0) is an illegal destination for a static
+				 * route. */
+				continue;
+			}
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                             .network       = dest.s_addr,
+			                             .plen          = plen,
+			                             .gateway       = gateway.s_addr,
+			                             .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                             .metric        = route_metric,
+			                             .table_coerced = nm_platform_route_table_coerce (route_table),
+			                         }),
+			                         NULL);
+		}
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
+		                           str->str);
+	}
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_ROUTER, &data, &n_data);
+	if (!r) {
+		nm_gstring_prepare (&str);
+
+		while (lease_option_next_in_addr (&gateway, &data, &n_data)) {
+			s = nm_utils_inet4_ntop (gateway.s_addr, gateway_str);
+			g_string_append (nm_gstring_add_space_delimiter (str), s);
+
+			if (gateway.s_addr == 0) {
+				/* silently skip 0.0.0.0 */
+				continue;
+			}
+
+			if (has_router_from_classless) {
+				/* If the DHCP server returns both a Classless Static Routes option and a
+				 * Router option, the DHCP client MUST ignore the Router option [RFC 3442].
+				 *
+				 * Be more lenient and ignore the Router option only if Classless Static
+				 * Routes contain a default gateway (as other DHCP backends do).
+				 */
+				continue;
+			}
+
+			/* if there are multiple default routes, we add them with differing
+			 * metrics. */
+			m = default_route_metric;
+			if (default_route_metric < G_MAXUINT32)
+				default_route_metric++;
+
+			nm_ip4_config_add_route (ip4_config,
+			                         &((const NMPlatformIP4Route) {
+			                                 .rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			                                 .gateway       = gateway.s_addr,
+			                                 .table_coerced = nm_platform_route_table_coerce (route_table),
+			                                 .metric        = m,
+			                         }),
+			                         NULL);
+		}
+		LOG_LEASE (LOGD_DHCP4, "router %s", str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_ROUTER,
+		                           str->str);
+	}
+}
+
+static void
+lease_parse_mtu (NDhcp4ClientLease *lease,
+                 const char *iface,
+                 NMIP4Config *ip4_config,
+                 GHashTable *options)
+{
+	uint16_t mtu;
+
+	if (!lease_get_u16 (lease, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU, &mtu))
+		return;
+
+	if (mtu < 68)
+		return;
+
+	LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
+	nm_dhcp_option_add_option_u64 (options,
+	                               _nm_dhcp_option_dhcp4_options,
+	                               NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
+	                               mtu);
+	nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
+}
+
+static void
+lease_parse_metered (NDhcp4ClientLease *lease,
+                     const char *iface,
+                     NMIP4Config *ip4_config,
+                     GHashTable *options)
+{
+	gboolean metered = FALSE;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC, &data, &n_data);
+	if (r) {
+		metered = FALSE;
+	} else {
+		metered = !!memmem (data, n_data, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
+	}
+
+	LOG_LEASE (LOGD_DHCP4, "%s", metered ? "metered" : "unmetered");
+	nm_ip4_config_set_metered (ip4_config, metered);
+}
+
+static void
+lease_parse_ntps (NDhcp4ClientLease *lease,
+                  const char *iface,
+                  GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	char addr_str[NM_UTILS_INET_ADDRSTRLEN];
+	struct in_addr addr;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_NTP_SERVER, &data, &n_data);
+	if (r)
+		return;
+
+	nm_gstring_prepare (&str);
+
+	while (lease_option_next_in_addr (&addr, &data, &n_data)) {
+		nm_utils_inet4_ntop (addr.s_addr, addr_str);
+		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
+	}
+
+	LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", str->str);
+	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NTP_SERVER, str->str);
+}
+
+static void
+lease_parse_hostname (NDhcp4ClientLease *lease,
+                      const char *iface,
+                      GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_HOST_NAME, &data, &n_data);
+	if (r)
+		return;
+
+	str = g_string_new_len ((char *)data, n_data);
+
+	if (is_localhost(str->str))
+		return;
+
+	LOG_LEASE (LOGD_DHCP4, "hostname '%s'", str->str);
+	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_HOST_NAME, str->str);
+}
+
+static void
+lease_parse_domainname (NDhcp4ClientLease *lease,
+                        const char *iface,
+                        NMIP4Config *ip4_config,
+                        GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	gs_strfreev char **domains = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, &data, &n_data);
+	if (r)
+		return;
+
+	str = g_string_new_len ((char *)data, n_data);
+
+	/* Multiple domains sometimes stuffed into option 15 "Domain Name". */
+	domains = g_strsplit (str->str, " ", 0);
+	nm_gstring_prepare (&str);
+
+	for (char **d = domains; *d; d++) {
+		if (is_localhost(*d))
+			return;
+
+		g_string_append (nm_gstring_add_space_delimiter (str), *d);
+		nm_ip4_config_add_domain (ip4_config, *d);
+	}
+	LOG_LEASE (LOGD_DHCP4, "domain name '%s'", str->str);
+	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, str->str);
+}
+
+static void
+lease_parse_search_domains (NDhcp4ClientLease *lease,
+                            const char *iface,
+                            NMIP4Config *ip4_config,
+                            GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	uint8_t *data, *cache;
+	size_t n_data, n_cache = 0;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST, &data, &n_data);
+	if (r)
+		return;
+
+	cache = data;
+
+	nm_gstring_prepare (&str);
+
+	for (;;) {
+		nm_auto_free_gstring GString *domain = NULL;
+
+		nm_gstring_prepare (&domain);
+
+		if (!lease_option_print_domain_name (domain, cache, &n_cache, &data, &n_data))
+			break;
+
+		g_string_append (nm_gstring_add_space_delimiter (str), domain->str);
+		nm_ip4_config_add_search (ip4_config, domain->str);
+	}
+	LOG_LEASE (LOGD_DHCP4, "domain search '%s'", str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
+	                           str->str);
+}
+
+static void
+lease_parse_root_path (NDhcp4ClientLease *lease,
+                       const char *iface,
+                       GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_ROOT_PATH, &data, &n_data);
+	if (r)
+		return;
+
+	str = g_string_new_len ((char *)data, n_data);
+	LOG_LEASE (LOGD_DHCP4, "root path '%s'", str->str);
+	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROOT_PATH, str->str);
+}
+
+static void
+lease_parse_wpad (NDhcp4ClientLease *lease,
+                  const char *iface,
+                  GHashTable *options)
+{
+	nm_auto_free_gstring GString *str = NULL;
+	uint8_t *data;
+	size_t n_data;
+	int r;
+
+	r = n_dhcp4_client_lease_query (lease, NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY, &data, &n_data);
+	if (r)
+		return;
+
+	str = g_string_new_len ((char *)data, n_data);
+	LOG_LEASE (LOGD_DHCP4, "wpad '%s'", str->str);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,
+	                           str->str);
+}
+
+static NMIP4Config *
+lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
+                     const char *iface,
+                     int ifindex,
+                     NDhcp4ClientLease *lease,
+                     guint32 route_table,
+                     guint32 route_metric,
+                     GHashTable **out_options,
+                     GError **error)
+{
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
+
+	g_return_val_if_fail (lease != NULL, NULL);
+
+	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
+	options = out_options ? nm_dhcp_option_create_options_dict () : NULL;
+
+	if (!lease_parse_address (lease, iface, ip4_config, options, error))
+		return NULL;
+
+	lease_parse_routes (lease, iface, ip4_config, options, route_table, route_metric);
+	lease_parse_domain_name_servers (lease, iface, ip4_config, options);
+	lease_parse_domainname (lease, iface, ip4_config, options);
+	lease_parse_search_domains (lease, iface, ip4_config, options);
+	lease_parse_mtu (lease, iface, ip4_config, options);
+	lease_parse_metered (lease, iface, ip4_config, options);
+
+	lease_parse_hostname (lease, iface, options);
+	lease_parse_ntps (lease, iface, options);
+	lease_parse_root_path (lease, iface, options);
+	lease_parse_wpad (lease, iface, options);
+
+	NM_SET_OUT (out_options, g_steal_pointer (&options));
+	return g_steal_pointer (&ip4_config);
+}
+
+/*****************************************************************************/
+
+static void
+bound4_handle (NMDhcpNettools *self, NDhcp4ClientLease *lease)
+{
+	const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
+	gs_unref_object NMIP4Config *ip4_config = NULL;
+	gs_unref_hashtable GHashTable *options = NULL;
+	GError *error = NULL;
+
+	_LOGT ("lease available");
+
+	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,
+	                                  nm_dhcp_client_get_route_table (NM_DHCP_CLIENT (self)),
+	                                  nm_dhcp_client_get_route_metric (NM_DHCP_CLIENT (self)),
+	                                  &options,
+	                                  &error);
+	if (!ip4_config) {
+		_LOGW ("%s", error->message);
+		g_clear_error (&error);
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		return;
+	}
+
+	nm_dhcp_option_add_requests_to_options (options, _nm_dhcp_option_dhcp4_options);
+
+	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+	                          NM_DHCP_STATE_BOUND,
+	                          NM_IP_CONFIG_CAST (ip4_config),
+	                          options);
+}
+
+static gboolean
+dhcp4_event_handle (NMDhcpNettools *self,
+                    NDhcp4ClientEvent *event)
+{
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	int r;
+
+	_LOGT ("client event %d", event->event);
+
+	switch (event->event) {
+	case N_DHCP4_CLIENT_EVENT_OFFER:
+		/* always accept the first lease */
+		r = n_dhcp4_client_lease_select (event->offer.lease);
+		if (r) {
+			_LOGW ("selecting lease failed: %d", r);
+		}
+		break;
+	case N_DHCP4_CLIENT_EVENT_EXPIRED:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_EXPIRE, NULL, NULL);
+		break;
+	case N_DHCP4_CLIENT_EVENT_RETRACTED:
+	case N_DHCP4_CLIENT_EVENT_CANCELLED:
+		nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+		break;
+	case N_DHCP4_CLIENT_EVENT_GRANTED:
+		priv->lease = n_dhcp4_client_lease_ref (event->granted.lease);
+		bound4_handle (self, event->granted.lease);
+		break;
+	case N_DHCP4_CLIENT_EVENT_EXTENDED:
+		bound4_handle (self, event->extended.lease);
+		break;
+	case N_DHCP4_CLIENT_EVENT_DOWN:
+		/* ignore down events, they are purely informational */
+		break;
+	default:
+		_LOGW ("unhandled DHCP event %d", event->event);
+		break;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+dhcp4_event_cb (GIOChannel *source,
+                GIOCondition condition,
+                gpointer data)
+{
+	NMDhcpNettools *self = data;
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	NDhcp4ClientEvent *event;
+	int r;
+
+	r = n_dhcp4_client_dispatch (priv->client);
+	if (r < 0)
+		return G_SOURCE_CONTINUE;
+
+	while (!n_dhcp4_client_pop_event (priv->client, &event) && event) {
+		dhcp4_event_handle (self, event);
+	}
+
+	return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+nettools_create (NMDhcpNettools *self,
+                 const char *dhcp_anycast_addr,
+                 GError **error)
+{
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	nm_auto (n_dhcp4_client_config_freep) NDhcp4ClientConfig *config = NULL;
+	nm_auto (n_dhcp4_client_unrefp) NDhcp4Client *client = NULL;
+	GBytes *hwaddr;
+	GBytes *bcast_hwaddr;
+	const uint8_t *hwaddr_arr;
+	const uint8_t *bcast_hwaddr_arr;
+	gsize hwaddr_len;
+	gsize bcast_hwaddr_len;
+	GBytes *client_id;
+	gs_unref_bytes GBytes *client_id_new = NULL;
+	const uint8_t *client_id_arr;
+	size_t client_id_len;
+	int r, fd, arp_type, transport;
+
+	g_return_val_if_fail (!priv->client, FALSE);
+
+	hwaddr = nm_dhcp_client_get_hw_addr (NM_DHCP_CLIENT (self));
+	if (   !hwaddr
+	    || !(hwaddr_arr = g_bytes_get_data (hwaddr, &hwaddr_len))
+	    || (arp_type = nm_utils_arp_type_detect_from_hwaddrlen (hwaddr_len)) < 0) {
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "invalid MAC address");
+		return FALSE;
+	}
+
+	bcast_hwaddr = nm_dhcp_client_get_broadcast_hw_addr (NM_DHCP_CLIENT (self));
+	bcast_hwaddr_arr = g_bytes_get_data (bcast_hwaddr, &bcast_hwaddr_len);
+
+	switch (arp_type) {
+	case ARPHRD_ETHER:
+		transport = N_DHCP4_TRANSPORT_ETHERNET;
+		break;
+	case ARPHRD_INFINIBAND:
+		transport = N_DHCP4_TRANSPORT_INFINIBAND;
+		break;
+	default:
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "unsupported ARP type");
+		return FALSE;
+	}
+
+	/* Note that we always set a client-id. In particular for infiniband that is necessary,
+	 * see https://tools.ietf.org/html/rfc4390#section-2.1 . */
+	client_id = nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self));
+	if (!client_id) {
+		client_id_new = nm_utils_dhcp_client_id_mac (arp_type, hwaddr_arr, hwaddr_len);
+		client_id = client_id_new;
+	}
+
+	if (   !(client_id_arr = g_bytes_get_data (client_id, &client_id_len))
+	    || client_id_len < 2) {
+
+		/* invalid client-ids are not expected. */
+		nm_assert_not_reached ();
+
+		nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "no valid IPv4 client-id");
+		return FALSE;
+	}
+
+	r = n_dhcp4_client_config_new (&config);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to create client-config: %s");
+		return FALSE;
+	}
+
+	n_dhcp4_client_config_set_ifindex (config, nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)));
+	n_dhcp4_client_config_set_transport (config, transport);
+	n_dhcp4_client_config_set_mac (config, hwaddr_arr, hwaddr_len);
+	n_dhcp4_client_config_set_broadcast_mac (config, bcast_hwaddr_arr, bcast_hwaddr_len);
+	r = n_dhcp4_client_config_set_client_id (config, client_id_arr, client_id_len);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to set client-id: %s");
+		return FALSE;
+	}
+
+	r = n_dhcp4_client_new (&client, config);
+	if (r) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "failed to create client: error %d", r);
+		return FALSE;
+	}
+
+	priv->client = client;
+	client = NULL;
+
+	n_dhcp4_client_get_fd (priv->client, &fd);
+	priv->channel = g_io_channel_unix_new (fd);
+	priv->event_id = g_io_add_watch (priv->channel, G_IO_IN, dhcp4_event_cb, self);
+
+	return TRUE;
+}
+
+static gboolean
+_accept (NMDhcpClient *client,
+         GError **error)
+{
+	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	int r;
+
+	g_return_val_if_fail (priv->lease, FALSE);
+
+	_LOGT ("accept");
+
+	r = n_dhcp4_client_lease_accept (priv->lease);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to accept lease: %s");
+		return FALSE;
+	}
+
+	priv->lease = n_dhcp4_client_lease_unref (priv->lease);
+
+	return TRUE;
+}
+
+static gboolean
+decline (NMDhcpClient *client,
+         const char *error_message,
+         GError **error)
+{
+	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	int r;
+
+	g_return_val_if_fail (priv->lease, FALSE);
+
+	_LOGT ("dhcp4-client: decline");
+
+	r = n_dhcp4_client_lease_decline (priv->lease, error_message);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to decline lease: %s");
+		return FALSE;
+	}
+
+	priv->lease = n_dhcp4_client_lease_unref (priv->lease);
+
+	return TRUE;
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const char *last_ip4_address,
+           GError **error)
+{
+	nm_auto (n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *config = NULL;
+	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+	struct in_addr last_addr = { 0 };
+	const char *hostname;
+	int r, i;
+
+	g_return_val_if_fail (!priv->probe, FALSE);
+
+	if (!nettools_create (self, dhcp_anycast_addr, error))
+		return FALSE;
+
+	r = n_dhcp4_client_probe_config_new (&config);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to create dhcp-client-probe-config: %s");
+		return FALSE;
+	}
+
+	/*
+	 * FIXME:
+	 * Select, or configure, a reasonable start delay, to protect poor servers beeing flooded.
+	 */
+	n_dhcp4_client_probe_config_set_start_delay (config, 1);
+
+	if (last_ip4_address) {
+		inet_pton (AF_INET, last_ip4_address, &last_addr);
+		n_dhcp4_client_probe_config_set_requested_ip (config, last_addr);
+	}
+
+	/* Add requested options */
+	for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) {
+		if (_nm_dhcp_option_dhcp4_options[i].include) {
+			nm_assert (_nm_dhcp_option_dhcp4_options[i].option_num <= 255);
+			n_dhcp4_client_probe_config_request_option (config,
+			                                            _nm_dhcp_option_dhcp4_options[i].option_num);
+		}
+	}
+
+	hostname = nm_dhcp_client_get_hostname (client);
+	if (hostname) {
+		if (nm_dhcp_client_get_use_fqdn (client)) {
+			uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH];
+
+			buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */
+			            DHCP_FQDN_FLAG_E;  /* Canonical wire format */
+			buffer[1] = 0;                 /* RCODE1 (deprecated) */
+			buffer[2] = 0;                 /* RCODE2 (deprecated) */
+
+			r = nm_sd_dns_name_to_wire_format (hostname,
+			                                   buffer + 3,
+			                                   sizeof (buffer) - 3,
+			                                   FALSE);
+			if (r < 0) {
+				nm_utils_error_set_errno (error, r, "failed to convert DHCP FQDN: %s");
+				return FALSE;
+			}
+
+			r = n_dhcp4_client_probe_config_append_option (config,
+			                                               NM_DHCP_OPTION_DHCP4_CLIENT_FQDN,
+			                                               buffer,
+			                                               3 + r);
+			if (r) {
+				nm_utils_error_set_errno (error, r, "failed to set DHCP FQDN: %s");
+				return FALSE;
+			}
+		} else {
+			r = n_dhcp4_client_probe_config_append_option (config,
+			                                               NM_DHCP_OPTION_DHCP4_HOST_NAME,
+			                                               hostname,
+			                                               strlen (hostname));
+			if (r) {
+				nm_utils_error_set_errno (error, r, "failed to set DHCP hostname: %s");
+				return FALSE;
+			}
+		}
+	}
+
+	r = n_dhcp4_client_probe (priv->client, &priv->probe, config);
+	if (r) {
+		nm_utils_error_set_errno (error, r, "failed to start DHCP client: %s");
+		return FALSE;
+	}
+
+	_LOGT ("dhcp-client4: start %p", (gpointer) priv->client);
+
+	nm_dhcp_client_start_timeout (client);
+	return TRUE;
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+           const char *dhcp_anycast_addr,
+           const struct in6_addr *ll_addr,
+           NMSettingIP6ConfigPrivacy privacy,
+           guint needed_prefixes,
+           GError **error)
+{
+	nm_utils_error_set_literal (error, NM_UTILS_ERROR_UNKNOWN, "nettools plugin does not support IPv6");
+	return FALSE;
+}
+
+static void
+stop (NMDhcpClient *client,
+      gboolean release)
+{
+	NMDhcpNettools *self = NM_DHCP_NETTOOLS (client);
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE (self);
+
+	NM_DHCP_CLIENT_CLASS (nm_dhcp_nettools_parent_class)->stop (client, release);
+
+	_LOGT ("dhcp-client4: stop %p",
+	       (gpointer) priv->client);
+
+	priv->probe = n_dhcp4_client_probe_free (priv->probe);
+}
+
+/*****************************************************************************/
+
+static void
+nm_dhcp_nettools_init (NMDhcpNettools *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDhcpNettoolsPrivate *priv = NM_DHCP_NETTOOLS_GET_PRIVATE ((NMDhcpNettools *) object);
+
+	nm_clear_pointer (&priv->channel, g_io_channel_unref);
+	nm_clear_g_source (&priv->event_id);
+	nm_clear_pointer (&priv->lease, n_dhcp4_client_lease_unref);
+	nm_clear_pointer (&priv->probe, n_dhcp4_client_probe_free);
+	nm_clear_pointer (&priv->client, n_dhcp4_client_unref);
+
+	G_OBJECT_CLASS (nm_dhcp_nettools_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_nettools_class_init (NMDhcpNettoolsClass *class)
+{
+	NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (class);
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+	object_class->dispose = dispose;
+
+	client_class->ip4_start = ip4_start;
+	client_class->ip6_start = ip6_start;
+	client_class->accept = _accept;
+	client_class->decline = decline;
+	client_class->stop = stop;
+}
+
+const NMDhcpClientFactory _nm_dhcp_client_factory_nettools = {
+	.name = "nettools",
+	.get_type = nm_dhcp_nettools_get_type,
+	.get_path = NULL,
+};
diff --git a/src/dhcp/nm-dhcp-options.c b/src/dhcp/nm-dhcp-options.c
new file mode 100644
index 00000000..21963070
--- /dev/null
+++ b/src/dhcp/nm-dhcp-options.c
@@ -0,0 +1,278 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-dhcp-options.h"
+
+
+#define REQPREFIX "requested_"
+
+#define REQ(_num, _name, _include) \
+	{ \
+		.name = REQPREFIX""_name, \
+		.option_num = _num, \
+		.include = _include, \
+	}
+
+const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
+	REQ (NM_DHCP_OPTION_DHCP4_SUBNET_MASK,                       "subnet_mask",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TIME_OFFSET,                       "time_offset",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,                "domain_name_servers",             TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_HOST_NAME,                         "host_name",                       TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DOMAIN_NAME,                       "domain_name",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,                     "interface_mtu",                   TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BROADCAST,                         "broadcast_address",               TRUE ),
+	/* RFC 3442: The Classless Static Routes option code MUST appear in the parameter
+	 *   request list prior to both the Router option code and the Static
+	 *   Routes option code, if present. */
+	REQ (NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,            "rfc3442_classless_static_routes", TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROUTER,                            "routers",                         TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,                      "static_routes",                   TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NIS_DOMAIN,                        "nis_domain",                      TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NIS_SERVERS,                       "nis_servers",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NTP_SERVER,                        "ntp_servers",                     TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SERVER_ID,                         "dhcp_server_identifier",          TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,                "domain_search",                   TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE,    "ms_classless_static_routes",      TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY,       "wpad",                            TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROOT_PATH,                         "root_path",                       TRUE ),
+
+	REQ (NM_DHCP_OPTION_DHCP4_TIME_SERVERS,                      "time_servers",                    FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IEN116_NAME_SERVERS,               "ien116_name_servers",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOG_SERVERS,                       "log_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_COOKIE_SERVERS,                    "cookie_servers",                  FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LPR_SERVERS,                       "lpr_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IMPRESS_SERVERS,                   "impress_servers",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RESOURCE_LOCATION_SERVERS,         "resource_location_servers",       FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BOOT_FILE_SIZE,                    "boot_size",                       FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_MERIT_DUMP,                        "merit_dump",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SWAP_SERVER,                       "swap_server",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_EXTENSIONS_PATH,                   "extensions_path",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ENABLE_IP_FORWARDING,              "ip_forwarding",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ENABLE_SRC_ROUTING,                "non_local_source_routing",        FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_POLICY_FILTER,                     "policy_filter",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_MDR,                     "max_dgram_reassembly",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_TTL,                     "default_ip_ttl",                  FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_INTERFACE_MTU_AGING_TIMEOUT,       "path_mtu_aging_timeout",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PATH_MTU_PLATEAU_TABLE,            "path_mtu_plateau_table",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ALL_SUBNETS_LOCAL,                 "all_subnets_local",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PERFORM_MASK_DISCOVERY,            "perform_mask_discovery",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_MASK_SUPPLIER,                     "mask_supplier",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROUTER_DISCOVERY,                  "router_discovery",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ROUTER_SOLICITATION_ADDR,          "router_solicitation_address",     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TRAILER_ENCAPSULATION,             "trailer_encapsulation",           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT,                 "arp_cache_timeout",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION,           "ieee802_3_encapsulation",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL,                   "default_tcp_ttl",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL,            "tcp_keepalive_internal",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE,             "tcp_keepalive_garbage",           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC,                   "vendor_encapsulated_options",     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER,                "netbios_name_servers",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETBIOS_DD_SERVER,                 "netbios_dd_server",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_FONT_SERVERS,                      "font_servers",                    FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_X_DISPLAY_MANAGER,                 "x_display_manager",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,             "dhcp_lease_time",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME,                   "dhcp_renewal_time",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME,                 "dhcp_rebinding_time",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_CLIENT_ID,                         "dhcp_client_identifier",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE,                 "tcode",                           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NWIP_DOMAIN,                       "nwip_domain",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NWIP_SUBOPTIONS,                   "nwip_suboptions",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NISPLUS_DOMAIN,                    "nisplus_domain",                  FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NISPLUS_SERVERS,                   "nisplus_servers",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TFTP_SERVER_NAME,                  "tftp_server_name",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME,                     "bootfile_name",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_MOBILE_IP_HOME_AGENT,              "mobile_ip_home_agent",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SMTP_SERVER,                       "smtp_server",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_POP_SERVER,                        "pop_server",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NNTP_SERVER,                       "nntp_server",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_WWW_SERVER,                        "www_server",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_FINGER_SERVER,                     "finger_server",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IRC_SERVER,                        "irc_server",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_STREETTALK_SERVER,                 "streettalk_server",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_STREETTALK_DIR_ASSIST_SERVER,      "streettalk_directory_assistance_server", FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SLP_DIRECTORY_AGENT,               "slp_directory_agent",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SLP_SERVICE_SCOPE,                 "slp_service_scope",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_CLIENT_FQDN,                       "fqdn",                            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RELAY_AGENT_INFORMATION,           "relay_agent_information",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NDS_SERVERS,                       "nds_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NDS_TREE_NAME,                     "nds_tree_name",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NDS_CONTEXT,                       "nds_context",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_NAMES,             "bcms_controller_names",           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_ADDRESS,           "bcms_controller_address",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_CLIENT_LAST_TRANSACTION,           "client_last_transaction_time",    FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_ASSOCIATED_IP,                     "associated_ip",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PXE_SYSTEM_TYPE,                   "pxe_system_type",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PXE_INTERFACE_ID,                  "pxe_interface_id",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID,                     "pxe_client_id",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_UAP_SERVERS,                       "uap_servers",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC,                     "geoconf_civic",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS,            "netinfo_server_address",          FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG,                "netinfo_server_tag",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_DEFAULT_URL,                       "default_url",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_AUTO_CONFIG,                       "auto_config",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NAME_SERVICE_SEARCH,               "name_service_search",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SUBNET_SELECTION,                  "subnet_selection",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_VIVCO,                             "vivco",                           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_VIVSO,                             "vivso",                           FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PANA_AGENT,                        "pana_agent",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_LOST,                           "v4_lost",                         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_SIP_UA_CS_DOMAINS,                 "sip_ua_cs_domains",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_IPV4_ADDRESS_ANDSF,                "ipv4_address_andsf",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_RDNSS_SELECTION,                   "rndss_selection",                 FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS,               "tftp_server_address",             FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS,                     "v4_portparams",                   FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL,                 "v4_captive_portal",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE,                 "loader_configfile",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX,                 "loader_pathprefix",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME,                 "loader_reboottime",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_OPTION_6RD,                        "option_6rd",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_V4_ACCESS_DOMAIN,                  "v4_access_domain",                FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_224,                       "private_224",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_225,                       "private_225",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_226,                       "private_226",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_227,                       "private_227",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_228,                       "private_228",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_229,                       "private_229",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_230,                       "private_230",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_231,                       "private_231",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_232,                       "private_232",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_233,                       "private_233",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_234,                       "private_234",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_235,                       "private_235",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_236,                       "private_236",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_237,                       "private_237",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_238,                       "private_238",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_239,                       "private_239",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_240,                       "private_240",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_241,                       "private_241",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_242,                       "private_242",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_243,                       "private_243",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_244,                       "private_244",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_245,                       "private_245",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_246,                       "private_246",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_247,                       "private_247",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_248,                       "private_248",                     FALSE ),
+	/* NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE */
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_250,                       "private_250",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_251,                       "private_251",                     FALSE ),
+	/* NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY */
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_253,                       "private_253",                     FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_PRIVATE_254,                       "private_254",                     FALSE ),
+
+	/* Internal values */
+	REQ (NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS,                     "ip_address",                      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP4_NM_EXPIRY,                         "expiry",                          FALSE ),
+
+	{ 0 }
+};
+
+const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = {
+	REQ (NM_DHCP_OPTION_DHCP6_CLIENTID,                         "dhcp6_client_id",     FALSE ),
+
+	/* Don't request server ID by default; some servers don't reply to
+	 * Information Requests that request the Server ID.
+	 */
+	REQ (NM_DHCP_OPTION_DHCP6_SERVERID,                         "dhcp6_server_id",     FALSE ),
+
+	REQ (NM_DHCP_OPTION_DHCP6_DNS_SERVERS,                      "dhcp6_name_servers",  TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP6_DOMAIN_LIST,                      "dhcp6_domain_search", TRUE ),
+	REQ (NM_DHCP_OPTION_DHCP6_SNTP_SERVERS,                     "dhcp6_sntp_servers",  TRUE ),
+
+	/* Internal values */
+	REQ (NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,                    "ip6_address",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN,                     "ip6_prefixlen",       FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_PREFERRED_LIFE,                "preferred_life",      FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_MAX_LIFE,                      "max_life",            FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_STARTS,                        "starts",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_LIFE_STARTS,                   "life_starts",         FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_RENEW,                         "renew",               FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_REBIND,                        "rebind",              FALSE ),
+	REQ (NM_DHCP_OPTION_DHCP6_NM_IAID,                          "iaid",                FALSE ),
+
+	{ 0 }
+};
+
+
+const char *
+nm_dhcp_option_request_string (const NMDhcpOption *requests, guint option)
+{
+	guint i = 0;
+
+	while (requests[i].name) {
+		if (requests[i].option_num == option)
+			return requests[i].name + NM_STRLEN (REQPREFIX);
+		i++;
+	}
+
+	/* Option should always be found */
+	nm_assert_not_reached ();
+	return NULL;
+}
+
+void
+nm_dhcp_option_take_option (GHashTable *options,
+             const NMDhcpOption *requests,
+             guint option,
+             char *value)
+{
+	nm_assert (options);
+	nm_assert (requests);
+	nm_assert (value);
+
+	g_hash_table_insert (options,
+	                     (gpointer) nm_dhcp_option_request_string (requests, option),
+	                     value);
+}
+
+void
+nm_dhcp_option_add_option (GHashTable *options, const NMDhcpOption *requests, guint option, const char *value)
+{
+	if (options)
+		nm_dhcp_option_take_option (options, requests, option, g_strdup (value));
+}
+
+void
+nm_dhcp_option_add_option_u64 (GHashTable *options, const NMDhcpOption *requests, guint option, guint64 value)
+{
+	if (options)
+		nm_dhcp_option_take_option (options, requests, option, g_strdup_printf ("%" G_GUINT64_FORMAT, value));
+}
+
+void
+nm_dhcp_option_add_requests_to_options (GHashTable *options, const NMDhcpOption *requests)
+{
+	guint i;
+
+	if (!options)
+		return;
+
+	for (i = 0; requests[i].name; i++) {
+		if (requests[i].include)
+			g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
+	}
+}
+
+GHashTable *
+nm_dhcp_option_create_options_dict (void)
+{
+	return g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+}
+
diff --git a/src/dhcp/nm-dhcp-options.h b/src/dhcp/nm-dhcp-options.h
new file mode 100644
index 00000000..0dc7a276
--- /dev/null
+++ b/src/dhcp/nm-dhcp-options.h
@@ -0,0 +1,204 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_DHCP_OPTIONS_H__
+#define __NM_DHCP_OPTIONS_H__
+
+typedef enum {
+	NM_DHCP_OPTION_DHCP4_PAD                            = 0,
+	NM_DHCP_OPTION_DHCP4_SUBNET_MASK                    = 1,
+	NM_DHCP_OPTION_DHCP4_TIME_OFFSET                    = 2,
+	NM_DHCP_OPTION_DHCP4_ROUTER                         = 3,
+	NM_DHCP_OPTION_DHCP4_TIME_SERVERS                   = 4,
+	NM_DHCP_OPTION_DHCP4_IEN116_NAME_SERVERS            = 5,
+	NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER             = 6,
+	NM_DHCP_OPTION_DHCP4_LOG_SERVERS                    = 7,
+	NM_DHCP_OPTION_DHCP4_COOKIE_SERVERS                 = 8,
+	NM_DHCP_OPTION_DHCP4_LPR_SERVERS                    = 9,
+	NM_DHCP_OPTION_DHCP4_IMPRESS_SERVERS                = 10,
+	NM_DHCP_OPTION_DHCP4_RESOURCE_LOCATION_SERVERS      = 11,
+	NM_DHCP_OPTION_DHCP4_HOST_NAME                      = 12,
+	NM_DHCP_OPTION_DHCP4_BOOT_FILE_SIZE                 = 13,
+	NM_DHCP_OPTION_DHCP4_MERIT_DUMP                     = 14,
+	NM_DHCP_OPTION_DHCP4_DOMAIN_NAME                    = 15,
+	NM_DHCP_OPTION_DHCP4_SWAP_SERVER                    = 16,
+	NM_DHCP_OPTION_DHCP4_ROOT_PATH                      = 17,
+	NM_DHCP_OPTION_DHCP4_EXTENSIONS_PATH                = 18,
+	NM_DHCP_OPTION_DHCP4_ENABLE_IP_FORWARDING           = 19,
+	NM_DHCP_OPTION_DHCP4_ENABLE_SRC_ROUTING             = 20,
+	NM_DHCP_OPTION_DHCP4_POLICY_FILTER                  = 21,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_MDR                  = 22,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_TTL                  = 23,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_MTU_AGING_TIMEOUT    = 24,
+	NM_DHCP_OPTION_DHCP4_PATH_MTU_PLATEAU_TABLE         = 25,
+	NM_DHCP_OPTION_DHCP4_INTERFACE_MTU                  = 26,
+	NM_DHCP_OPTION_DHCP4_ALL_SUBNETS_LOCAL              = 27,
+	NM_DHCP_OPTION_DHCP4_BROADCAST                      = 28,
+	NM_DHCP_OPTION_DHCP4_PERFORM_MASK_DISCOVERY         = 29,
+	NM_DHCP_OPTION_DHCP4_MASK_SUPPLIER                  = 30,
+	NM_DHCP_OPTION_DHCP4_ROUTER_DISCOVERY               = 31,
+	NM_DHCP_OPTION_DHCP4_ROUTER_SOLICITATION_ADDR       = 32,
+	NM_DHCP_OPTION_DHCP4_STATIC_ROUTE                   = 33,
+	NM_DHCP_OPTION_DHCP4_TRAILER_ENCAPSULATION          = 34,
+	NM_DHCP_OPTION_DHCP4_ARP_CACHE_TIMEOUT              = 35,
+	NM_DHCP_OPTION_DHCP4_IEEE802_3_ENCAPSULATION        = 36,
+	NM_DHCP_OPTION_DHCP4_DEFAULT_TCP_TTL                = 37,
+	NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_INTERVAL         = 38,
+	NM_DHCP_OPTION_DHCP4_TCP_KEEPALIVE_GARBAGE          = 39,
+	NM_DHCP_OPTION_DHCP4_NIS_DOMAIN                     = 40,
+	NM_DHCP_OPTION_DHCP4_NIS_SERVERS                    = 41,
+	NM_DHCP_OPTION_DHCP4_NTP_SERVER                     = 42,
+	NM_DHCP_OPTION_DHCP4_VENDOR_SPECIFIC                = 43,
+	NM_DHCP_OPTION_DHCP4_NETBIOS_NAMESERVER             = 44,
+	NM_DHCP_OPTION_DHCP4_NETBIOS_DD_SERVER              = 45,
+	NM_DHCP_OPTION_DHCP4_FONT_SERVERS                   = 48,
+	NM_DHCP_OPTION_DHCP4_X_DISPLAY_MANAGER              = 49,
+	NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME          = 51,
+	NM_DHCP_OPTION_DHCP4_SERVER_ID                      = 54,
+	NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME                = 58,
+	NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME              = 59,
+	NM_DHCP_OPTION_DHCP4_CLIENT_ID                      = 61,
+	NM_DHCP_OPTION_DHCP4_NWIP_DOMAIN                    = 62,
+	NM_DHCP_OPTION_DHCP4_NWIP_SUBOPTIONS                = 63,
+	NM_DHCP_OPTION_DHCP4_NISPLUS_DOMAIN                 = 64,
+	NM_DHCP_OPTION_DHCP4_NISPLUS_SERVERS                = 65,
+	NM_DHCP_OPTION_DHCP4_TFTP_SERVER_NAME               = 66,
+	NM_DHCP_OPTION_DHCP4_BOOTFILE_NAME                  = 67,
+	NM_DHCP_OPTION_DHCP4_MOBILE_IP_HOME_AGENT           = 68,
+	NM_DHCP_OPTION_DHCP4_SMTP_SERVER                    = 69,
+	NM_DHCP_OPTION_DHCP4_POP_SERVER                     = 70,
+	NM_DHCP_OPTION_DHCP4_NNTP_SERVER                    = 71,
+	NM_DHCP_OPTION_DHCP4_WWW_SERVER                     = 72,
+	NM_DHCP_OPTION_DHCP4_FINGER_SERVER                  = 73,
+	NM_DHCP_OPTION_DHCP4_IRC_SERVER                     = 74,
+	NM_DHCP_OPTION_DHCP4_STREETTALK_SERVER              = 75,
+	NM_DHCP_OPTION_DHCP4_STREETTALK_DIR_ASSIST_SERVER   = 76,
+	NM_DHCP_OPTION_DHCP4_SLP_DIRECTORY_AGENT            = 78,
+	NM_DHCP_OPTION_DHCP4_SLP_SERVICE_SCOPE              = 79,
+	NM_DHCP_OPTION_DHCP4_CLIENT_FQDN                    = 81,
+	NM_DHCP_OPTION_DHCP4_RELAY_AGENT_INFORMATION        = 82,
+	NM_DHCP_OPTION_DHCP4_NDS_SERVERS                    = 85,
+	NM_DHCP_OPTION_DHCP4_NDS_TREE_NAME                  = 86,
+	NM_DHCP_OPTION_DHCP4_NDS_CONTEXT                    = 87,
+	NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_NAMES          = 88,
+	NM_DHCP_OPTION_DHCP4_BCMS_CONTROLLER_ADDRESS        = 89,
+	NM_DHCP_OPTION_DHCP4_CLIENT_LAST_TRANSACTION        = 91,
+	NM_DHCP_OPTION_DHCP4_ASSOCIATED_IP                  = 92,
+	NM_DHCP_OPTION_DHCP4_PXE_SYSTEM_TYPE                = 93,
+	NM_DHCP_OPTION_DHCP4_PXE_INTERFACE_ID               = 94,
+	NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID                  = 97,
+	NM_DHCP_OPTION_DHCP4_UAP_SERVERS                    = 98,
+	NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC                  = 99,
+	NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE              = 101,
+	NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS         = 112,
+	NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG             = 113,
+	NM_DHCP_OPTION_DHCP4_DEFAULT_URL                    = 114,
+	NM_DHCP_OPTION_DHCP4_AUTO_CONFIG                    = 116,
+	NM_DHCP_OPTION_DHCP4_NAME_SERVICE_SEARCH            = 117,
+	NM_DHCP_OPTION_DHCP4_SUBNET_SELECTION               = 118,
+	NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST             = 119,
+	NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE         = 121,
+	NM_DHCP_OPTION_DHCP4_VIVCO                          = 124,
+	NM_DHCP_OPTION_DHCP4_VIVSO                          = 125,
+	NM_DHCP_OPTION_DHCP4_PANA_AGENT                     = 136,
+	NM_DHCP_OPTION_DHCP4_V4_LOST                        = 137,
+	NM_DHCP_OPTION_DHCP4_SIP_UA_CS_DOMAINS              = 141,
+	NM_DHCP_OPTION_DHCP4_IPV4_ADDRESS_ANDSF             = 142,
+	NM_DHCP_OPTION_DHCP4_RDNSS_SELECTION                = 146,
+	NM_DHCP_OPTION_DHCP4_TFTP_SERVER_ADDRESS            = 150,
+	NM_DHCP_OPTION_DHCP4_V4_PORTPARAMS                  = 159,
+	NM_DHCP_OPTION_DHCP4_V4_CAPTIVE_PORTAL              = 160,
+	NM_DHCP_OPTION_DHCP4_LOADER_CONFIGFILE              = 209,
+	NM_DHCP_OPTION_DHCP4_LOADER_PATHPREFIX              = 210,
+	NM_DHCP_OPTION_DHCP4_LOADER_REBOOTTIME              = 211,
+	NM_DHCP_OPTION_DHCP4_OPTION_6RD                     = 212,
+	NM_DHCP_OPTION_DHCP4_V4_ACCESS_DOMAIN               = 213,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_224                    = 224,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_225                    = 225,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_226                    = 226,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_227                    = 227,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_228                    = 228,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_229                    = 229,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_230                    = 230,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_231                    = 231,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_232                    = 232,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_233                    = 233,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_234                    = 234,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_235                    = 235,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_236                    = 236,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_237                    = 237,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_238                    = 238,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_239                    = 239,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_240                    = 240,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_241                    = 241,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_242                    = 242,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_243                    = 243,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_244                    = 244,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_245                    = 245,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_246                    = 246,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_247                    = 247,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_248                    = 248,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_CLASSLESS_STATIC_ROUTE = 249,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_250                    = 250,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_251                    = 251,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_PROXY_AUTODISCOVERY    = 252,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_253                    = 253,
+	NM_DHCP_OPTION_DHCP4_PRIVATE_254                    = 254,
+	NM_DHCP_OPTION_DHCP4_END                            = 255,
+	/* Internal values */
+	NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS                  = 1024,
+	NM_DHCP_OPTION_DHCP4_NM_EXPIRY                      = 1025,
+} NMDhcpOptionDhcp4Options;
+
+typedef enum {
+	NM_DHCP_OPTION_DHCP6_CLIENTID          = 1,
+	NM_DHCP_OPTION_DHCP6_SERVERID          = 2,
+	NM_DHCP_OPTION_DHCP6_DNS_SERVERS       = 23,
+	NM_DHCP_OPTION_DHCP6_DOMAIN_LIST       = 24,
+	NM_DHCP_OPTION_DHCP6_SNTP_SERVERS      = 31,
+	/* Internal values */
+	NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS     = 1026,
+	NM_DHCP_OPTION_DHCP6_NM_PREFIXLEN      = 1027,
+	NM_DHCP_OPTION_DHCP6_NM_PREFERRED_LIFE = 1028,
+	NM_DHCP_OPTION_DHCP6_NM_MAX_LIFE       = 1029,
+	NM_DHCP_OPTION_DHCP6_NM_STARTS         = 1030,
+	NM_DHCP_OPTION_DHCP6_NM_LIFE_STARTS    = 1031,
+	NM_DHCP_OPTION_DHCP6_NM_RENEW          = 1032,
+	NM_DHCP_OPTION_DHCP6_NM_REBIND         = 1033,
+	NM_DHCP_OPTION_DHCP6_NM_IAID           = 1034,
+
+} NMDhcpOptionDhcp6Options;
+
+typedef struct {
+	const char *name;
+	uint16_t option_num;
+	bool include;
+} NMDhcpOption;
+
+extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[];
+extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[];
+
+const char *nm_dhcp_option_request_string (const NMDhcpOption *requests, guint option);
+void nm_dhcp_option_take_option (GHashTable *options, const NMDhcpOption *requests, guint option, char *value);
+void nm_dhcp_option_add_option (GHashTable *options, const NMDhcpOption *requests, guint option, const char *value);
+void nm_dhcp_option_add_option_u64 (GHashTable *options, const NMDhcpOption *requests, guint option, guint64 value);
+void nm_dhcp_option_add_requests_to_options (GHashTable *options, const NMDhcpOption *requests);
+GHashTable *nm_dhcp_option_create_options_dict (void);
+
+#endif /* __NM_DHCP_OPTIONS_H__ */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 1cd5ba27..073a6da0 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -1,8 +1,8 @@
-/* -*- 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 free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library 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
@@ -32,11 +32,13 @@
 #include "nm-utils.h"
 #include "nm-config.h"
 #include "nm-dhcp-utils.h"
+#include "nm-dhcp-options.h"
 #include "nm-core-utils.h"
 #include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
 #include "nm-dhcp-client-logging.h"
 #include "systemd/nm-sd.h"
+#include "systemd/nm-sd-utils-dhcp.h"
 
 /*****************************************************************************/
 
@@ -79,160 +81,10 @@ G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
 
 /*****************************************************************************/
 
-#define DHCP_OPTION_NIS_DOMAIN         40
-#define DHCP_OPTION_NIS_SERVERS        41
-
-/* Internal values */
-#define DHCP_OPTION_IP_ADDRESS       1024
-#define DHCP_OPTION_EXPIRY           1025
-#define DHCP6_OPTION_IP_ADDRESS      1026
-#define DHCP6_OPTION_PREFIXLEN       1027
-#define DHCP6_OPTION_PREFERRED_LIFE  1028
-#define DHCP6_OPTION_MAX_LIFE        1029
-#define DHCP6_OPTION_STARTS          1030
-#define DHCP6_OPTION_LIFE_STARTS     1031
-#define DHCP6_OPTION_RENEW           1032
-#define DHCP6_OPTION_REBIND          1033
-#define DHCP6_OPTION_IAID            1034
-
-typedef struct {
-	const char *name;
-	uint16_t option_num;
-	bool include;
-} ReqOption;
-
-#define REQPREFIX "requested_"
-
-#define REQ(_num, _name, _include) \
-	{ \
-		.name = REQPREFIX""_name, \
-		.option_num = _num, \
-		.include = _include, \
-	}
-
-static const ReqOption dhcp4_requests[] = {
-	REQ (SD_DHCP_OPTION_SUBNET_MASK,                    "subnet_mask",                     TRUE ),
-	REQ (SD_DHCP_OPTION_TIME_OFFSET,                    "time_offset",                     TRUE ),
-	REQ (SD_DHCP_OPTION_DOMAIN_NAME_SERVER,             "domain_name_servers",             TRUE ),
-	REQ (SD_DHCP_OPTION_HOST_NAME,                      "host_name",                       TRUE ),
-	REQ (SD_DHCP_OPTION_DOMAIN_NAME,                    "domain_name",                     TRUE ),
-	REQ (SD_DHCP_OPTION_INTERFACE_MTU,                  "interface_mtu",                   TRUE ),
-	REQ (SD_DHCP_OPTION_BROADCAST,                      "broadcast_address",               TRUE ),
-
-	/* RFC 3442: The Classless Static Routes option code MUST appear in the parameter
-	 *   request list prior to both the Router option code and the Static
-	 *   Routes option code, if present. */
-	REQ (SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,         "rfc3442_classless_static_routes", TRUE ),
-	REQ (SD_DHCP_OPTION_ROUTER,                         "routers",                         TRUE ),
-	REQ (SD_DHCP_OPTION_STATIC_ROUTE,                   "static_routes",                   TRUE ),
-
-	REQ (DHCP_OPTION_NIS_DOMAIN,                        "nis_domain",                      TRUE ),
-	REQ (DHCP_OPTION_NIS_SERVERS,                       "nis_servers",                     TRUE ),
-	REQ (SD_DHCP_OPTION_NTP_SERVER,                     "ntp_servers",                     TRUE ),
-	REQ (SD_DHCP_OPTION_SERVER_IDENTIFIER,              "dhcp_server_identifier",          TRUE ),
-	REQ (SD_DHCP_OPTION_DOMAIN_SEARCH_LIST,             "domain_search",                   TRUE ),
-	REQ (SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, "ms_classless_static_routes",      TRUE ),
-	REQ (SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY,    "wpad",                            TRUE ),
-	REQ (SD_DHCP_OPTION_ROOT_PATH,                      "root_path",                       TRUE ),
-
-	/* Internal values */
-	REQ (SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,          "expiry",                          FALSE ),
-	REQ (SD_DHCP_OPTION_CLIENT_IDENTIFIER,              "dhcp_client_identifier",          FALSE ),
-	REQ (DHCP_OPTION_IP_ADDRESS,                        "ip_address",                      FALSE ),
-
-	{ 0 }
-};
-
-static const ReqOption dhcp6_requests[] = {
-	REQ (SD_DHCP6_OPTION_CLIENTID,                      "dhcp6_client_id",     FALSE ),
-
-	/* Don't request server ID by default; some servers don't reply to
-	 * Information Requests that request the Server ID.
-	 */
-	REQ (SD_DHCP6_OPTION_SERVERID,                      "dhcp6_server_id",     FALSE ),
-
-	REQ (SD_DHCP6_OPTION_DNS_SERVERS,                   "dhcp6_name_servers",  TRUE ),
-	REQ (SD_DHCP6_OPTION_DOMAIN_LIST,                   "dhcp6_domain_search", TRUE ),
-	REQ (SD_DHCP6_OPTION_SNTP_SERVERS,                  "dhcp6_sntp_servers",  TRUE ),
-
-	/* Internal values */
-	REQ (DHCP6_OPTION_IP_ADDRESS,                       "ip6_address",         FALSE ),
-	REQ (DHCP6_OPTION_PREFIXLEN,                        "ip6_prefixlen",       FALSE ),
-	REQ (DHCP6_OPTION_PREFERRED_LIFE,                   "preferred_life",      FALSE ),
-	REQ (DHCP6_OPTION_MAX_LIFE,                         "max_life",            FALSE ),
-	REQ (DHCP6_OPTION_STARTS,                           "starts",              FALSE ),
-	REQ (DHCP6_OPTION_LIFE_STARTS,                      "life_starts",         FALSE ),
-	REQ (DHCP6_OPTION_RENEW,                            "renew",               FALSE ),
-	REQ (DHCP6_OPTION_REBIND,                           "rebind",              FALSE ),
-	REQ (DHCP6_OPTION_IAID,                             "iaid",                FALSE ),
-
-	{ 0 }
-};
-
-static void
-take_option (GHashTable *options,
-             const ReqOption *requests,
-             guint option,
-             char *value)
-{
-	guint i;
-
-	nm_assert (options);
-	nm_assert (requests);
-	nm_assert (value);
-
-	for (i = 0; requests[i].name; i++) {
-		nm_assert (g_str_has_prefix (requests[i].name, REQPREFIX));
-		if (requests[i].option_num == option) {
-			g_hash_table_insert (options,
-			                     (gpointer) (requests[i].name + NM_STRLEN (REQPREFIX)),
-			                     value);
-			return;
-		}
-	}
-
-	/* Option should always be found */
-	nm_assert_not_reached ();
-}
-
-static void
-add_option (GHashTable *options, const ReqOption *requests, guint option, const char *value)
-{
-	if (options)
-		take_option (options, requests, option, g_strdup (value));
-}
-
-static void
-add_option_u64 (GHashTable *options, const ReqOption *requests, guint option, guint64 value)
-{
-	if (options)
-		take_option (options, requests, option, g_strdup_printf ("%" G_GUINT64_FORMAT, value));
-}
-
-static void
-add_requests_to_options (GHashTable *options, const ReqOption *requests)
-{
-	guint i;
-
-	if (!options)
-		return;
-
-	for (i = 0; requests[i].name; i++) {
-		if (requests[i].include)
-			g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
-	}
-}
-
-static GHashTable *
-create_options_dict (void)
-{
-	return g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
-}
-
 #define LOG_LEASE(domain, ...) \
 G_STMT_START { \
 	if (log_lease) { \
-		_LOG2I ((domain), (iface), "  "__VA_ARGS__); \
+		_LOG2D ((domain), (iface), "  "__VA_ARGS__); \
 	} \
 } G_STMT_END
 
@@ -267,9 +119,14 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	gint64 ts_time = time (NULL);
 	struct in_addr a_address;
 	struct in_addr a_netmask;
+	struct in_addr server_id;
+	struct in_addr broadcast;
 	const struct in_addr *a_router;
 	guint32 a_plen;
 	guint32 a_lifetime;
+	guint32 renewal;
+	guint32 rebinding;
+	gs_free nm_sd_dhcp_option *private_options = NULL;
 
 	g_return_val_if_fail (lease != NULL, NULL);
 
@@ -290,26 +147,36 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 
 	ip4_config = nm_ip4_config_new (multi_idx, ifindex);
 
-	options = out_options ? create_options_dict () : NULL;
+	options = out_options ? nm_dhcp_option_create_options_dict () : NULL;
 
 	nm_utils_inet4_ntop (a_address.s_addr, addr_str);
-	LOG_LEASE (LOGD_DHCP4, "address %s", addr_str);
-	add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, addr_str);
+	LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+	           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS),
+	           addr_str);
+	nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NM_IP_ADDRESS, addr_str);
 
 	a_plen = nm_utils_ip4_netmask_to_prefix (a_netmask.s_addr);
-	LOG_LEASE (LOGD_DHCP4, "plen %u", (guint) a_plen);
-	add_option (options,
-	            dhcp4_requests,
-	            SD_DHCP_OPTION_SUBNET_MASK,
-	            nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
-
-	LOG_LEASE (LOGD_DHCP4, "expires in %u seconds (at %lld)",
+	LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+	           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_SUBNET_MASK),
+	           (guint) a_plen);
+	nm_dhcp_option_add_option (options,
+	                           _nm_dhcp_option_dhcp4_options,
+	                           NM_DHCP_OPTION_DHCP4_SUBNET_MASK,
+	                           nm_utils_inet4_ntop (a_netmask.s_addr, addr_str));
+
+	LOG_LEASE (LOGD_DHCP4, "%s '%u' seconds (at %lld)",
+	           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options,
+	                                          NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME),
 	           (guint) a_lifetime,
 	           (long long) (ts_time + a_lifetime));
-	add_option_u64 (options,
-	                dhcp4_requests,
-	                SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
-	                (guint64) (ts_time + a_lifetime));
+	nm_dhcp_option_add_option_u64 (options,
+	                               _nm_dhcp_option_dhcp4_options,
+	                               NM_DHCP_OPTION_DHCP4_IP_ADDRESS_LEASE_TIME,
+	                               a_lifetime);
+	nm_dhcp_option_add_option_u64 (options,
+	                               _nm_dhcp_option_dhcp4_options,
+	                               NM_DHCP_OPTION_DHCP4_NM_EXPIRY,
+	                               (guint64) (ts_time + a_lifetime));
 
 	nm_ip4_config_add_address (ip4_config,
 	                           &((const NMPlatformIP4Address) {
@@ -322,6 +189,28 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	                               .preferred    = a_lifetime,
 	                           }));
 
+	if (sd_dhcp_lease_get_server_identifier (lease, &server_id) >= 0) {
+		nm_utils_inet4_ntop (server_id.s_addr, addr_str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_SERVER_ID),
+		           addr_str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_SERVER_ID,
+		                           addr_str);
+	}
+
+	if (sd_dhcp_lease_get_broadcast (lease, &broadcast) >= 0) {
+		nm_utils_inet4_ntop (broadcast.s_addr, addr_str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_BROADCAST),
+		           addr_str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_BROADCAST,
+		                           addr_str);
+	}
+
 	num = sd_dhcp_lease_get_dns (lease, &addr_list);
 	if (num > 0) {
 		nm_gstring_prepare (&str);
@@ -337,8 +226,13 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			}
 			nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
 		}
-		LOG_LEASE (LOGD_DHCP4, "nameserver '%s'", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME_SERVER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_DOMAIN_NAME_SERVER,
+		                           str->str);
 	}
 
 	num = sd_dhcp_lease_get_search_domains (lease, (char ***) &search_domains);
@@ -348,16 +242,23 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			g_string_append (nm_gstring_add_space_delimiter (str), search_domains[i]);
 			nm_ip4_config_add_search (ip4_config, search_domains[i]);
 		}
-		LOG_LEASE (LOGD_DHCP4, "domain search '%s'", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_SEARCH_LIST, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_DOMAIN_SEARCH_LIST,
+		                           str->str);
 	}
 
 	if (sd_dhcp_lease_get_domainname (lease, &s) >= 0) {
 		gs_strfreev char **domains = NULL;
 		char **d;
 
-		LOG_LEASE (LOGD_DHCP4, "domain name '%s'", s);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_DOMAIN_NAME, s);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME),
+		           s);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_DOMAIN_NAME, s);
 
 		/* Multiple domains sometimes stuffed into option 15 "Domain Name".
 		 * As systemd escapes such characters, split them at \\032. */
@@ -367,8 +268,10 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 	}
 
 	if (sd_dhcp_lease_get_hostname (lease, &s) >= 0) {
-		LOG_LEASE (LOGD_DHCP4, "hostname '%s'", s);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_HOST_NAME, s);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_HOST_NAME),
+		           s);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_HOST_NAME, s);
 	}
 
 	num = sd_dhcp_lease_get_routes (lease, &routes);
@@ -379,10 +282,10 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 
 		for (i = 0; i < num; i++) {
 			switch (sd_dhcp_route_get_option (routes[i])) {
-			case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+			case NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE:
 				has_classless_route = TRUE;
 				break;
-			case SD_DHCP_OPTION_STATIC_ROUTE:
+			case NM_DHCP_OPTION_DHCP4_STATIC_ROUTE:
 				has_static_route = TRUE;
 				break;
 			}
@@ -404,8 +307,8 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			guint32 m;
 
 			option = sd_dhcp_route_get_option (routes[i]);
-			if (!NM_IN_SET (option, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE,
-			                        SD_DHCP_OPTION_STATIC_ROUTE))
+			if (!NM_IN_SET (option, NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
+			                        NM_DHCP_OPTION_DHCP4_STATIC_ROUTE))
 				continue;
 
 			if (sd_dhcp_route_get_destination (routes[i], &r_network) < 0)
@@ -422,14 +325,14 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			nm_utils_inet4_ntop (r_gateway.s_addr, gateway_str);
 
 			LOG_LEASE (LOGD_DHCP4,
-			           "%sstatic route %s/%d gw %s",
-			             option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
-			           ? "classless "
+			           "%sstatic_route %s/%d gw %s",
+			             option == NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE
+			           ? "rfc3442_classless_"
 			           : "",
 			           network_net_str,
 			           (int) r_plen,
 			           gateway_str);
-			g_string_append_printf (nm_gstring_add_space_delimiter (  option == SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE
+			g_string_append_printf (nm_gstring_add_space_delimiter (  option == NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE
 			                                                        ? str_classless
 			                                                        : str_static),
 			                        "%s/%d %s",
@@ -437,7 +340,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			                        (int) r_plen,
 			                        gateway_str);
 
-			if (   option == SD_DHCP_OPTION_STATIC_ROUTE
+			if (   option == NM_DHCP_OPTION_DHCP4_STATIC_ROUTE
 			    && has_classless_route) {
 				/* RFC 3443: if the DHCP server returns both a Classless Static Routes
 				 * option and a Static Routes option, the DHCP client MUST ignore the
@@ -446,7 +349,7 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			}
 
 			if (   r_plen == 0
-			    && option == SD_DHCP_OPTION_STATIC_ROUTE) {
+			    && option == NM_DHCP_OPTION_DHCP4_STATIC_ROUTE) {
 				/* for option 33 (static route), RFC 2132 says:
 				 *
 				 * The default route (0.0.0.0) is an illegal destination for a static
@@ -478,9 +381,15 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 		}
 
 		if (str_classless && str_classless->len > 0)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, str_classless->str);
+			nm_dhcp_option_add_option (options,
+		                                   _nm_dhcp_option_dhcp4_options,
+		                                   NM_DHCP_OPTION_DHCP4_CLASSLESS_STATIC_ROUTE,
+		                                   str_classless->str);
 		if (str_static && str_static->len > 0)
-			add_option (options, dhcp4_requests, SD_DHCP_OPTION_STATIC_ROUTE, str_static->str);
+			nm_dhcp_option_add_option (options,
+			                           _nm_dhcp_option_dhcp4_options,
+			                           NM_DHCP_OPTION_DHCP4_STATIC_ROUTE,
+			                           str_static->str);
 	}
 
 	num = sd_dhcp_lease_get_router (lease, &a_router);
@@ -524,14 +433,21 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			                         }),
 			                         NULL);
 		}
-		LOG_LEASE (LOGD_DHCP4, "router %s", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROUTER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROUTER),
+		           str->str);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROUTER, str->str);
 	}
 
 	if (   sd_dhcp_lease_get_mtu (lease, &mtu) >= 0
 	    && mtu) {
-		LOG_LEASE (LOGD_DHCP4, "mtu %u", mtu);
-		add_option_u64 (options, dhcp4_requests, SD_DHCP_OPTION_INTERFACE_MTU, mtu);
+		LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_INTERFACE_MTU),
+		           mtu);
+		nm_dhcp_option_add_option_u64 (options,
+		                               _nm_dhcp_option_dhcp4_options,
+		                               NM_DHCP_OPTION_DHCP4_INTERFACE_MTU,
+		                               mtu);
 		nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
 	}
 
@@ -542,19 +458,77 @@ lease_to_ip4_config (NMDedupMultiIndex *multi_idx,
 			nm_utils_inet4_ntop (addr_list[i].s_addr, addr_str);
 			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 		}
-		LOG_LEASE (LOGD_DHCP4, "ntp server '%s'", str->str);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_NTP_SERVER, str->str);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NTP_SERVER),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_NTP_SERVER,
+		                           str->str);
 	}
 
 	if (sd_dhcp_lease_get_root_path (lease, &s) >= 0) {
-		LOG_LEASE (LOGD_DHCP4, "root path '%s'", s);
-		add_option (options, dhcp4_requests, SD_DHCP_OPTION_ROOT_PATH, s);
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROOT_PATH),
+		           s);
+		nm_dhcp_option_add_option (options, _nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_ROOT_PATH, s);
+	}
+
+	if (sd_dhcp_lease_get_t1 (lease, &renewal) >= 0) {
+		LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME),
+		           renewal);
+		nm_dhcp_option_add_option_u64 (options,
+		                               _nm_dhcp_option_dhcp4_options,
+		                               NM_DHCP_OPTION_DHCP4_RENEWAL_T1_TIME,
+		                               renewal);
+	}
+
+	if (sd_dhcp_lease_get_t2 (lease, &rebinding) >= 0) {
+		LOG_LEASE (LOGD_DHCP4, "%s '%u'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME),
+		           rebinding);
+		nm_dhcp_option_add_option_u64 (options,
+		                               _nm_dhcp_option_dhcp4_options,
+		                               NM_DHCP_OPTION_DHCP4_REBINDING_T2_TIME,
+		                               rebinding);
+	}
+
+	if (sd_dhcp_lease_get_timezone (lease, &s) >= 0) {
+		LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE),
+		           s);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp4_options,
+		                           NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE,
+		                           s);
 	}
 
 	if (sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len) >= 0)
 		metered = !!memmem (data, data_len, "ANDROID_METERED", NM_STRLEN ("ANDROID_METERED"));
 	nm_ip4_config_set_metered (ip4_config, metered);
 
+	num =  nm_sd_dhcp_lease_get_private_options (lease, &private_options);
+	if (num > 0) {
+		for (i = 0; i < num; i++) {
+			char *option_string;
+
+			option_string = nm_utils_bin2hexstr_full (private_options[i].data,
+			                                          private_options[i].data_len,
+			                                          ':', FALSE, NULL);
+			LOG_LEASE (LOGD_DHCP4, "%s '%s'",
+			           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp4_options, private_options[i].code),
+			           option_string);
+			if (!options) {
+				g_free (option_string);
+				continue;
+			}
+			nm_dhcp_option_take_option (options,
+			                            _nm_dhcp_option_dhcp4_options,
+			                            private_options[i].code,
+			                            option_string);
+		}
+	}
 	NM_SET_OUT (out_options, g_steal_pointer (&options));
 	return g_steal_pointer (&ip4_config);
 }
@@ -627,7 +601,7 @@ bound4_handle (NMDhcpSystemd *self)
 		return;
 	}
 
-	add_requests_to_options (options, dhcp4_requests);
+	nm_dhcp_option_add_requests_to_options (options, _nm_dhcp_option_dhcp4_options);
 	dhcp_lease_save (lease, priv->lease_file);
 
 	nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
@@ -636,13 +610,13 @@ bound4_handle (NMDhcpSystemd *self)
 	                          options);
 }
 
-static void
+static int
 dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 {
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 
-	g_assert (priv->client4 == client);
+	nm_assert (priv->client4 == client);
 
 	_LOGD ("client event %d", event);
 
@@ -658,10 +632,14 @@ dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
 	case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE:
 		bound4_handle (self);
 		break;
+	case SD_DHCP_CLIENT_EVENT_SELECTING:
+		break;
 	default:
 		_LOGW ("unhandled DHCP event %d", event);
 		break;
 	}
+
+	return 0;
 }
 
 static gboolean
@@ -776,10 +754,10 @@ ip4_start (NMDhcpClient *client,
 	}
 
 	/* Add requested options */
-	for (i = 0; dhcp4_requests[i].name; i++) {
-		if (dhcp4_requests[i].include) {
-			nm_assert (dhcp4_requests[i].option_num <= 255);
-			r = sd_dhcp_client_set_request_option (sd_client, dhcp4_requests[i].option_num);
+	for (i = 0; _nm_dhcp_option_dhcp4_options[i].name; i++) {
+		if (_nm_dhcp_option_dhcp4_options[i].include) {
+			nm_assert (_nm_dhcp_option_dhcp4_options[i].option_num <= 255);
+			r = sd_dhcp_client_set_request_option (sd_client, _nm_dhcp_option_dhcp4_options[i].option_num);
 			nm_assert (r >= 0 || r == -EEXIST);
 		}
 	}
@@ -846,7 +824,7 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 
 	ip6_config = nm_ip6_config_new (multi_idx, ifindex);
 
-	options = out_options ? create_options_dict () : NULL;
+	options = out_options ? nm_dhcp_option_create_options_dict () : NULL;
 
 	sd_dhcp6_lease_reset_address_iter (lease);
 	nm_gstring_prepare (&str);
@@ -866,12 +844,15 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 		nm_utils_inet6_ntop (&tmp_addr, addr_str);
 		g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 
-		LOG_LEASE (LOGD_DHCP6,
-		           "address %s",
+		LOG_LEASE (LOGD_DHCP6, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp6_options, NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS),
 		           nm_platform_ip6_address_to_string (&address, sbuf, sizeof (sbuf)));
 	};
 	if (str->len)
-		add_option (options, dhcp6_requests, DHCP6_OPTION_IP_ADDRESS, str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp6_options,
+		                           NM_DHCP_OPTION_DHCP6_NM_IP_ADDRESS,
+		                           str->str);
 
 	if (   !info_only
 	    && nm_ip6_config_get_num_addresses (ip6_config) == 0) {
@@ -890,8 +871,13 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 			g_string_append (nm_gstring_add_space_delimiter (str), addr_str);
 			nm_ip6_config_add_nameserver (ip6_config, &dns[i]);
 		}
-		LOG_LEASE (LOGD_DHCP6, "nameserver %s", str->str);
-		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DNS_SERVERS, str->str);
+		LOG_LEASE (LOGD_DHCP6, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp6_options, NM_DHCP_OPTION_DHCP6_DNS_SERVERS),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp6_options,
+		                           NM_DHCP_OPTION_DHCP6_DNS_SERVERS,
+		                           str->str);
 	}
 
 	num = sd_dhcp6_lease_get_domains (lease, &domains);
@@ -901,8 +887,13 @@ lease_to_ip6_config (NMDedupMultiIndex *multi_idx,
 			g_string_append (nm_gstring_add_space_delimiter (str), domains[i]);
 			nm_ip6_config_add_search (ip6_config, domains[i]);
 		}
-		LOG_LEASE (LOGD_DHCP6, "domain name '%s'", str->str);
-		add_option (options, dhcp6_requests, SD_DHCP6_OPTION_DOMAIN_LIST, str->str);
+		LOG_LEASE (LOGD_DHCP6, "%s '%s'",
+		           nm_dhcp_option_request_string (_nm_dhcp_option_dhcp6_options, NM_DHCP_OPTION_DHCP6_DOMAIN_LIST),
+		           str->str);
+		nm_dhcp_option_add_option (options,
+		                           _nm_dhcp_option_dhcp6_options,
+		                           NM_DHCP_OPTION_DHCP6_DOMAIN_LIST,
+		                           str->str);
 	}
 
 	NM_SET_OUT (out_options, g_steal_pointer (&options));
@@ -955,7 +946,7 @@ dhcp6_event_cb (sd_dhcp6_client *client, int event, gpointer user_data)
 	NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
 	NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
 
-	g_assert (priv->client6 == client);
+	nm_assert (priv->client6 == client);
 
 	_LOGD ("client event %d", event);
 
@@ -1075,9 +1066,9 @@ ip6_start (NMDhcpClient *client,
 	}
 
 	/* Add requested options */
-	for (i = 0; dhcp6_requests[i].name; i++) {
-		if (dhcp6_requests[i].include) {
-			r = sd_dhcp6_client_set_request_option (sd_client, dhcp6_requests[i].option_num);
+	for (i = 0; _nm_dhcp_option_dhcp6_options[i].name; i++) {
+		if (_nm_dhcp_option_dhcp6_options[i].include) {
+			r = sd_dhcp6_client_set_request_option (sd_client, _nm_dhcp_option_dhcp6_options[i].option_num);
 			nm_assert (r >= 0 || r == -EEXIST);
 		}
 	}
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 7aa867c0..3f9368f2 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dhcp/nm-dhcp-utils.h b/src/dhcp/nm-dhcp-utils.h
index 5c127bd1..39ae7693 100644
--- a/src/dhcp/nm-dhcp-utils.h
+++ b/src/dhcp/nm-dhcp-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dhcp/tests/test-dhcp-dhclient.c b/src/dhcp/tests/test-dhcp-dhclient.c
index 1eac3643..2cc7e5ca 100644
--- a/src/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/dhcp/tests/test-dhcp-dhclient.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dhcp/tests/test-dhcp-utils.c b/src/dhcp/tests/test-dhcp-utils.c
index 118082a5..e4d4c348 100644
--- a/src/dhcp/tests/test-dhcp-utils.c
+++ b/src/dhcp/tests/test-dhcp-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dns/nm-dns-dnsmasq.c b/src/dns/nm-dns-dnsmasq.c
index b54df730..13576c92 100644
--- a/src/dns/nm-dns-dnsmasq.c
+++ b/src/dns/nm-dns-dnsmasq.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
  *
diff --git a/src/dns/nm-dns-dnsmasq.h b/src/dns/nm-dns-dnsmasq.h
index cbba434b..7623d167 100644
--- a/src/dns/nm-dns-dnsmasq.h
+++ b/src/dns/nm-dns-dnsmasq.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dns/nm-dns-manager.c b/src/dns/nm-dns-manager.c
index 27c3e710..2c55bee9 100644
--- a/src/dns/nm-dns-manager.c
+++ b/src/dns/nm-dns-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dns/nm-dns-manager.h b/src/dns/nm-dns-manager.h
index 7f6ed3ed..1cb2c669 100644
--- a/src/dns/nm-dns-manager.h
+++ b/src/dns/nm-dns-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dns/nm-dns-plugin.c b/src/dns/nm-dns-plugin.c
index ce814186..582abc62 100644
--- a/src/dns/nm-dns-plugin.c
+++ b/src/dns/nm-dns-plugin.c
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dns/nm-dns-plugin.h b/src/dns/nm-dns-plugin.h
index 8a55921d..98bda7cf 100644
--- a/src/dns/nm-dns-plugin.h
+++ b/src/dns/nm-dns-plugin.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dns/nm-dns-systemd-resolved.c b/src/dns/nm-dns-systemd-resolved.c
index 5d262ba3..69b3e45e 100644
--- a/src/dns/nm-dns-systemd-resolved.c
+++ b/src/dns/nm-dns-systemd-resolved.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * Copyright (C) 2010 Dan Williams <dcbw@redhat.com>
  * Copyright (C) 2016 Sjoerd Simons <sjoerd@luon.net>
@@ -32,6 +31,7 @@
 #include <linux/if.h>
 
 #include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-dbus-aux.h"
 #include "nm-core-internal.h"
 #include "platform/nm-platform.h"
 #include "nm-utils.h"
@@ -304,18 +304,12 @@ send_updates (NMDnsSystemdResolved *self)
 		_LOGT ("send-updates: no name owner. Try start service...");
 		priv->try_start_blocked = TRUE;
 
-		g_dbus_connection_call (priv->dbus_connection,
-		                        DBUS_SERVICE_DBUS,
-		                        DBUS_PATH_DBUS,
-		                        DBUS_INTERFACE_DBUS,
-		                        "StartServiceByName",
-		                        g_variant_new ("(su)", SYSTEMD_RESOLVED_DBUS_SERVICE, 0u),
-		                        G_VARIANT_TYPE ("(u)"),
-		                        G_DBUS_CALL_FLAGS_NONE,
-		                        -1,
-		                        NULL,
-		                        NULL,
-		                        NULL);
+		nm_dbus_connection_call_start_service_by_name (priv->dbus_connection,
+		                                               SYSTEMD_RESOLVED_DBUS_SERVICE,
+		                                               -1,
+		                                               NULL,
+		                                               NULL,
+		                                               NULL);
 		return;
 	}
 
@@ -474,24 +468,17 @@ name_owner_changed_cb (GDBusConnection *connection,
 }
 
 static void
-get_name_owner_cb (GObject *source,
-                   GAsyncResult *res,
+get_name_owner_cb (const char *name_owner,
+                   GError *error,
                    gpointer user_data)
 {
 	NMDnsSystemdResolved *self;
 	NMDnsSystemdResolvedPrivate *priv;
-	gs_unref_variant GVariant *ret = NULL;
-	gs_free_error GError *error = NULL;
-	const char *owner = NULL;
 
-	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
-	if (   !ret
+	if (   !name_owner
 	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
-	if (ret)
-		g_variant_get (ret, "(&s)", &owner);
-
 	self = user_data;
 	priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE (self);
 
@@ -499,7 +486,7 @@ get_name_owner_cb (GObject *source,
 
 	priv->dbus_initied = TRUE;
 
-	name_owner_changed (self, owner);
+	name_owner_changed (self, name_owner);
 }
 
 /*****************************************************************************/
@@ -527,35 +514,24 @@ nm_dns_systemd_resolved_init (NMDnsSystemdResolved *self)
 
 	c_list_init (&priv->request_queue_lst_head);
 
-	priv->dbus_connection = nm_g_object_ref (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ()));
+	priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
 	if (!priv->dbus_connection) {
 		_LOGD ("no D-Bus connection");
 		return;
 	}
 
-	priv->name_owner_changed_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
-	                                                                  DBUS_SERVICE_DBUS,
-	                                                                  DBUS_INTERFACE_DBUS,
-	                                                                  "NameOwnerChanged",
-	                                                                  DBUS_PATH_DBUS,
-	                                                                  SYSTEMD_RESOLVED_DBUS_SERVICE,
-	                                                                  G_DBUS_SIGNAL_FLAGS_NONE,
-	                                                                  name_owner_changed_cb,
-	                                                                  self,
-	                                                                  NULL);
+	priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+	                                                                                      SYSTEMD_RESOLVED_DBUS_SERVICE,
+	                                                                                      name_owner_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
 	priv->cancellable = g_cancellable_new ();
-	g_dbus_connection_call (priv->dbus_connection,
-	                        DBUS_SERVICE_DBUS,
-	                        DBUS_PATH_DBUS,
-	                        DBUS_INTERFACE_DBUS,
-	                        "GetNameOwner",
-	                        g_variant_new ("(s)", SYSTEMD_RESOLVED_DBUS_SERVICE),
-	                        G_VARIANT_TYPE ("(s)"),
-	                        G_DBUS_CALL_FLAGS_NONE,
-	                        -1,
-	                        priv->cancellable,
-	                        get_name_owner_cb,
-	                        self);
+	nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+	                                        SYSTEMD_RESOLVED_DBUS_SERVICE,
+	                                        -1,
+	                                        priv->cancellable,
+	                                        get_name_owner_cb,
+	                                        self);
 }
 
 NMDnsPlugin *
@@ -572,10 +548,8 @@ dispose (GObject *object)
 
 	free_pending_updates (self);
 
-	if (priv->name_owner_changed_id != 0) {
-		g_dbus_connection_signal_unsubscribe (priv->dbus_connection,
-		                                      nm_steal_int (&priv->name_owner_changed_id));
-	}
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->name_owner_changed_id);
 
 	nm_clear_g_cancellable (&priv->cancellable);
 
diff --git a/src/dns/nm-dns-systemd-resolved.h b/src/dns/nm-dns-systemd-resolved.h
index b79ff5e4..14e31e2b 100644
--- a/src/dns/nm-dns-systemd-resolved.h
+++ b/src/dns/nm-dns-systemd-resolved.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dns/nm-dns-unbound.c b/src/dns/nm-dns-unbound.c
index b900f29e..9e10950a 100644
--- a/src/dns/nm-dns-unbound.c
+++ b/src/dns/nm-dns-unbound.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dns/nm-dns-unbound.h b/src/dns/nm-dns-unbound.h
index 100ff2c4..743699ea 100644
--- a/src/dns/nm-dns-unbound.h
+++ b/src/dns/nm-dns-unbound.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/dnsmasq/nm-dnsmasq-manager.c b/src/dnsmasq/nm-dnsmasq-manager.c
index 43bc66fc..b60b2952 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.c
+++ b/src/dnsmasq/nm-dnsmasq-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dnsmasq/nm-dnsmasq-manager.h b/src/dnsmasq/nm-dnsmasq-manager.h
index dd4a9069..7d4d9c9b 100644
--- a/src/dnsmasq/nm-dnsmasq-manager.h
+++ b/src/dnsmasq/nm-dnsmasq-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dnsmasq/nm-dnsmasq-utils.c b/src/dnsmasq/nm-dnsmasq-utils.c
index ec5545d3..089b8ce0 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.c
+++ b/src/dnsmasq/nm-dnsmasq-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dnsmasq/nm-dnsmasq-utils.h b/src/dnsmasq/nm-dnsmasq-utils.h
index 54b90309..393e7236 100644
--- a/src/dnsmasq/nm-dnsmasq-utils.h
+++ b/src/dnsmasq/nm-dnsmasq-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/dnsmasq/tests/test-dnsmasq-utils.c b/src/dnsmasq/tests/test-dnsmasq-utils.c
index b311ccb4..6d8e5a1f 100644
--- a/src/dnsmasq/tests/test-dnsmasq-utils.c
+++ b/src/dnsmasq/tests/test-dnsmasq-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/initrd/nm-initrd-generator.c b/src/initrd/nm-initrd-generator.c
index 4c1c4037..c916459d 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/initrd/nm-initrd-generator.c
@@ -66,7 +66,7 @@ output_conn (gpointer key, gpointer value, gpointer user_data)
 		if (!nm_utils_file_set_contents (full_filename, data, len, 0600, &error))
 			goto err_out;
 	} else
-		g_print ("\n*** Connection '%s' ***\n\n%s\n", basename, data);
+		g_print ("\n*** Connection '%s' ***\n\n%s", basename, data);
 
 	return;
 err_out:
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index b9c75c1b..ccdd1f29 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -32,6 +32,20 @@
 
 /*****************************************************************************/
 
+static gboolean
+_connection_matches_type (gpointer key, gpointer value, gpointer user_data)
+{
+	NMConnection *connection = value;
+	const char *type_name = user_data;
+	NMSettingConnection *s_con;
+
+	s_con = nm_connection_get_setting_connection (connection);
+	if (type_name == NULL)
+		return nm_setting_connection_get_master (s_con) == NULL;
+	else
+		return strcmp (nm_setting_connection_get_connection_type (s_con), type_name) == 0;
+}
+
 static NMConnection *
 get_conn (GHashTable *connections, const char *ifname, const char *type_name)
 {
@@ -49,7 +63,18 @@ get_conn (GHashTable *connections, const char *ifname, const char *type_name)
 		multi_connect = NM_CONNECTION_MULTI_CONNECT_MULTIPLE;
 	}
 
-	connection = g_hash_table_lookup (connections, (gpointer)basename);
+	connection = g_hash_table_lookup (connections, (gpointer) basename);
+	if (!connection && !ifname) {
+		/*
+		 * If ifname was not given, we'll match the connection by type.
+		 * If the type was not given either, then we're happy with any connection but slaves.
+		 * This is so that things like "bond=bond0:eth1,eth2 nameserver=1.3.3.7 end up
+		 * slapping the nameserver to the most reasonable connection (bond0).
+		 */
+		connection = g_hash_table_find (connections,
+		                                _connection_matches_type,
+		                                (gpointer) type_name);
+	}
 
 	if (connection) {
 		setting = (NMSetting *)nm_connection_get_setting_connection (connection);
@@ -270,11 +295,13 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 	if (netmask && *netmask) {
 		NMIPAddr addr;
 
-		if (nm_utils_parse_inaddr_bin (AF_INET, netmask, NULL, &addr)) {
+		if (nm_utils_parse_inaddr_bin (AF_INET, netmask, NULL, &addr))
 			client_ip_prefix = nm_utils_ip4_netmask_to_prefix (addr.addr4);
-		} else {
-			_LOGW (LOGD_CORE, "Unrecognized address: %s", client_ip);
-		}
+		else
+			client_ip_prefix = _nm_utils_ascii_str_to_int64 (netmask, 10, 0, 32, -1);
+
+		if (client_ip_prefix == -1)
+			_LOGW (LOGD_CORE, "Invalid IP mask: %s", netmask);
 	}
 
 	/* Static IP configuration might be present. */
@@ -332,7 +359,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 	if (g_strcmp0 (kind, "none") == 0 || (g_strcmp0 (kind, "off") == 0)) {
 		if (nm_setting_ip_config_get_num_addresses (s_ip6) == 0) {
 			g_object_set (s_ip6,
-			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
 			              NULL);
 		}
 		if (nm_setting_ip_config_get_num_addresses (s_ip4) == 0) {
@@ -347,7 +374,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 		              NULL);
 		if (nm_setting_ip_config_get_num_addresses (s_ip6) == 0) {
 			g_object_set (s_ip6,
-			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
 			              NULL);
 		}
 	} else if (g_strcmp0 (kind, "dhcp6") == 0) {
@@ -398,7 +425,7 @@ parse_ip (GHashTable *connections, const char *sysfs_dir, char *argument)
 	}
 
 	if (peer && *peer)
-		_LOGW (LOGD_CORE, "Ignoring peer: %s (not implemented)\b", peer);
+		_LOGW (LOGD_CORE, "Ignoring peer: %s (not implemented)\n", peer);
 
 	if (gateway_ip && *gateway_ip) {
 		int addr_family = guess_ip_address_family (gateway_ip);
@@ -656,7 +683,6 @@ parse_rd_peerdns (GHashTable *connections, char *argument)
 	              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, auto_dns,
 	              NULL);
 
-
 	s_ip = nm_connection_get_setting_ip6_config (connection);
 	g_object_set (s_ip,
 	              NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, auto_dns,
@@ -678,6 +704,7 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, char **argv)
 	const char *tag;
 	char *argument;
 	gboolean ignore_bootif = FALSE;
+	gboolean neednet = FALSE;
 	char *bootif = NULL;
 	int i;
 
@@ -706,6 +733,8 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, char **argv)
 			parse_rd_peerdns (connections, argument);
 		else if (strcmp (tag, "rd.bootif") == 0)
 			ignore_bootif = !_nm_utils_ascii_str_to_bool (argument, TRUE);
+		else if (strcmp (tag, "rd.neednet") == 0)
+			neednet = _nm_utils_ascii_str_to_bool (argument, TRUE);
 		else if (strcasecmp (tag, "BOOTIF") == 0)
 			bootif = argument;
 	}
@@ -716,6 +745,16 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, char **argv)
 		NMConnection *connection;
 		NMSettingWired *s_wired;
 
+		if (   !nm_utils_hwaddr_valid (bootif, ETH_ALEN)
+		    && g_str_has_prefix (bootif, "01-")
+		    && nm_utils_hwaddr_valid (&bootif[3], ETH_ALEN)) {
+			/*
+			 * BOOTIF MAC address can be prefixed with a hardware type identifier.
+			 * "01" stays for "wired", no other are known.
+			 */
+			bootif += 3;
+		}
+
 		connection = get_conn (connections, NULL, NM_SETTING_WIRED_SETTING_NAME);
 
 		s_wired = nm_connection_get_setting_wired (connection);
@@ -723,6 +762,10 @@ nmi_cmdline_reader_parse (const char *sysfs_dir, char **argv)
 		              NM_SETTING_WIRED_MAC_ADDRESS, bootif,
 		              NULL);
 	}
+	if (neednet && g_hash_table_size (connections) == 0) {
+		/* Make sure there's some connection. */
+		get_conn (connections, NULL, NM_SETTING_WIRED_SETTING_NAME);
+	}
 
 	g_hash_table_foreach (connections, _normalize_conn, NULL);
 
diff --git a/src/initrd/nmi-ibft-reader.c b/src/initrd/nmi-ibft-reader.c
index efac5307..2db38e2c 100644
--- a/src/initrd/nmi-ibft-reader.c
+++ b/src/initrd/nmi-ibft-reader.c
@@ -182,7 +182,7 @@ ip_setting_add_from_block (GHashTable *nic,
 	case AF_INET:
 		s_ip = s_ip4;
 		g_object_set (s_ip6, NM_SETTING_IP_CONFIG_METHOD,
-		              NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+		              NM_SETTING_IP6_CONFIG_METHOD_DISABLED, NULL);
 		break;
 	case AF_INET6:
 		s_ip = s_ip6;
diff --git a/src/initrd/tests/meson.build b/src/initrd/tests/meson.build
index 0ef72fff..20cf6af2 100644
--- a/src/initrd/tests/meson.build
+++ b/src/initrd/tests/meson.build
@@ -18,6 +18,7 @@ foreach test_unit : test_units
   test(
     'initrd/' + test_unit,
     test_script,
+    timeout: default_test_timeout,
     args: test_args + [exe.full_path()],
   )
 endforeach
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index 1a87505a..9bae73a2 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -193,14 +193,16 @@ test_if_ip4_manual (void)
 	gs_unref_hashtable GHashTable *connections = NULL;
 	gs_strfreev char **argv = g_strdupv ((char *[]){
 		"ip=192.0.2.2::192.0.2.1:255.255.255.0:"
-		"hostname0.example.com:eth3::192.0.2.53", NULL });
+		"hostname0.example.com:eth3::192.0.2.53",
+		"ip=203.0.113.2::203.0.113.1:26:"
+		"hostname1.example.com:eth4", NULL });
 	NMConnection *connection;
 	NMSettingIPConfig *s_ip4;
 	NMIPAddress *ip_addr;
 
 	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", argv);
 	g_assert (connections);
-	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 2);
 
 	connection = g_hash_table_lookup (connections, "eth3");
 	g_assert (connection);
@@ -221,8 +223,26 @@ test_if_ip4_manual (void)
 	g_assert_cmpint (nm_ip_address_get_prefix (ip_addr), ==, 24);
 	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "192.0.2.1");
 	g_assert_cmpstr (nm_setting_ip_config_get_dhcp_hostname (s_ip4), ==, "hostname0.example.com");
-}
 
+	connection = g_hash_table_lookup (connections, "eth4");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+	g_assert_cmpstr (nm_connection_get_id (connection), ==, "eth4");
+
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	g_assert (s_ip4);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
+	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip4));
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 0);
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 0);
+	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 1);
+	ip_addr = nm_setting_ip_config_get_address (s_ip4, 0);
+	g_assert (ip_addr);
+	g_assert_cmpstr (nm_ip_address_get_address (ip_addr), ==, "203.0.113.2");
+	g_assert_cmpint (nm_ip_address_get_prefix (ip_addr), ==, 26);
+	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "203.0.113.1");
+	g_assert_cmpstr (nm_setting_ip_config_get_dhcp_hostname (s_ip4), ==, "hostname1.example.com");
+}
 
 static void
 test_if_ip6_manual (void)
@@ -266,8 +286,12 @@ static void
 test_multiple (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
-	gs_strfreev char **argv = g_strdupv ((char *[]){ "ip=192.0.2.2:::::eth0", "ip=[2001:db8::2]:::::eth0", NULL });
+	gs_strfreev char **argv = g_strdupv ((char *[]){ "ip=192.0.2.2:::::eth0",
+	                                                 "ip=[2001:db8::2]:::::eth0",
+	                                                 "BOOTIF=00:53:AB:cd:02:03",
+	                                                 NULL });
 	NMConnection *connection;
+	NMSettingWired *s_wired;
 	NMSettingIPConfig *s_ip4;
 	NMSettingIPConfig *s_ip6;
 	NMIPAddress *ip_addr;
@@ -281,6 +305,10 @@ test_multiple (void)
 	nmtst_assert_connection_verifies_without_normalization (connection);
 	g_assert_cmpstr (nm_connection_get_id (connection), ==, "eth0");
 
+	s_wired = nm_connection_get_setting_wired (connection);
+	g_assert (s_wired);
+	g_assert_cmpstr (nm_setting_wired_get_mac_address (s_wired), ==, "00:53:AB:CD:02:03");
+
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
 	g_assert (s_ip4);
 	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -306,7 +334,7 @@ test_some_more (void)
 	gs_unref_hashtable GHashTable *connections = NULL;
 	gs_strfreev char **argv = g_strdupv ((char *[]){ "bootdev=eth1", "hail", "nameserver=[2001:DB8:3::53]",
 	                                                 "satan", "nameserver=192.0.2.53", "worship",
-	                                                 "BOOTIF=00-53-AB-cd-02-03", "doom", "rd.peerdns=0",
+	                                                 "BOOTIF=01-00-53-AB-cd-02-03", "doom", "rd.peerdns=0",
 	                                                 "rd.route=[2001:DB8:3::/48]:[2001:DB8:2::1]:ens10",
 	                                                 NULL });
 	NMConnection *connection;
@@ -387,7 +415,7 @@ static void
 test_no_bootif (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
-	gs_strfreev char **argv = g_strdupv ((char *[]){ "BOOTIF=00-53-AB-cd-02-03", "rd.bootif=0", NULL });
+	gs_strfreev char **argv = g_strdupv ((char *[]){ "BOOTIF=01-00-53-AB-cd-02-03", "rd.bootif=0", NULL });
 
 	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", argv);
 	g_assert (connections);
@@ -398,7 +426,10 @@ static void
 test_bond (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
-	gs_strfreev char **argv = g_strdupv ((char *[]){ "rd.route=192.0.2.53::bong0", "bond=bong0:eth0,eth1:mode=balance-rr", NULL });
+	gs_strfreev char **argv = g_strdupv ((char *[]){ "rd.route=192.0.2.53::bong0",
+	                                                 "bond=bong0:eth0,eth1:mode=balance-rr",
+	                                                 "nameserver=203.0.113.53",
+	                                                 NULL });
 	NMConnection *connection;
 	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip4;
@@ -423,7 +454,8 @@ test_bond (void)
 	g_assert (s_ip4);
 	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip4));
-	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 0);
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 1);
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 0), ==, "203.0.113.53");
 	g_assert (!nm_setting_ip_config_get_gateway (s_ip4));
 	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 1);
 	ip_route = nm_setting_ip_config_get_route (s_ip4, 0);
diff --git a/src/initrd/tests/test-ibft-reader.c b/src/initrd/tests/test-ibft-reader.c
index 64362d18..a49d0abf 100644
--- a/src/initrd/tests/test-ibft-reader.c
+++ b/src/initrd/tests/test-ibft-reader.c
@@ -96,7 +96,7 @@ test_read_ibft_dhcp (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
 
 	g_object_unref (connection);
 }
@@ -152,7 +152,7 @@ test_read_ibft_static (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
 
 	g_object_unref (connection);
 }
@@ -263,7 +263,7 @@ test_read_ibft (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (nm_setting_ip_config_get_num_addresses (s_ip6) == 0);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
 	g_object_unref (connection);
 }
 
diff --git a/src/main-utils.c b/src/main-utils.c
index f3a2edf5..38fd519a 100644
--- a/src/main-utils.c
+++ b/src/main-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/main-utils.h b/src/main-utils.h
index dbbdb85f..d4da6052 100644
--- a/src/main-utils.h
+++ b/src/main-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/main.c b/src/main.c
index 9f979cf4..f1e9690f 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -213,6 +212,35 @@ do_early_setup (int *argc, char **argv[], NMConfigCmdLineOptions *config_cli)
 	global_opt.pidfile = global_opt.pidfile ?: g_strdup(NM_DEFAULT_PID_FILE);
 }
 
+static gboolean
+_dbus_manager_init (NMConfig *config)
+{
+	NMDBusManager *busmgr;
+	NMConfigConfigureAndQuitType c_a_q_type;
+
+	busmgr = nm_dbus_manager_get ();
+
+	c_a_q_type = nm_config_get_configure_and_quit (config);
+
+	if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED)
+		return nm_dbus_manager_acquire_bus (busmgr, TRUE);
+
+	if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_ENABLED) {
+		/* D-Bus is useless in configure and quit mode -- we're eventually dropping
+		 * off and potential clients would have no way of knowing whether we're
+		 * finished already or didn't start yet.
+		 *
+		 * But we still create a nm_dbus_manager_get_dbus_connection() D-Bus connection
+		 * so that we can talk to other services like firewalld. */
+		return nm_dbus_manager_acquire_bus (busmgr, FALSE);
+	}
+
+	nm_assert (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD);
+	/* in initrd we don't have D-Bus at all. Don't even try to get the G_BUS_TYPE_SYSTEM
+	 * connection. And of course don't claim the D-Bus name. */
+	return TRUE;
+}
+
 /*
  * main
  *
@@ -389,7 +417,9 @@ main (int argc, char *argv[])
 #endif
 	             );
 
-	/* Set up platform interaction layer */
+	if (!_dbus_manager_init (config))
+		goto done_no_manager;
+
 	nm_linux_platform_setup ();
 
 	NM_UTILS_KEEP_ALIVE (config, nm_netns_get (), "NMConfig-depends-on-NMNetns");
@@ -399,21 +429,12 @@ main (int argc, char *argv[])
 	                                                         NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT,
 	                                                         NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT_BOOL));
 
-	if (!nm_config_get_configure_and_quit (config)) {
-		/* D-Bus is useless in configure and quit mode -- we're eventually dropping
-		 * off and potential clients would have no way of knowing whether we're
-		 * finished already or didn't start yet. */
-		if (!nm_dbus_manager_acquire_bus (nm_dbus_manager_get ()))
-			goto done_no_manager;
-	}
-
 	manager = nm_manager_setup ();
+
 	nm_dbus_manager_start (nm_dbus_manager_get(),
 	                       nm_manager_dbus_set_property_handle,
 	                       manager);
 
-	nm_dispatcher_init ();
-
 	g_signal_connect (manager, NM_MANAGER_CONFIGURE_QUIT, G_CALLBACK (manager_configure_quit), config);
 
 	if (!nm_manager_start (manager, &error)) {
@@ -458,6 +479,8 @@ done:
 
 	nm_dns_manager_stop (nm_dns_manager_get ());
 
+	nm_settings_kf_db_write (NM_SETTINGS_GET);
+
 done_no_manager:
 	if (global_opt.pidfile && wrote_pidfile)
 		unlink (global_opt.pidfile);
diff --git a/src/meson.build b/src/meson.build
index a3ac4625..f3f5ee58 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,8 +22,10 @@ cflags = nm_cflags
 sources = files(
   'dhcp/nm-dhcp-client.c',
   'dhcp/nm-dhcp-manager.c',
+  'dhcp/nm-dhcp-nettools.c',
   'dhcp/nm-dhcp-systemd.c',
   'dhcp/nm-dhcp-utils.c',
+  'dhcp/nm-dhcp-options.c',
   'ndisc/nm-lndp-ndisc.c',
   'ndisc/nm-ndisc.c',
   'platform/nm-netlink.c',
@@ -50,6 +52,7 @@ deps = [
   libsystemd_dep,
   libudev_dep,
   libnm_core_dep,
+  shared_n_dhcp4_dep,
 ]
 
 if enable_wext
@@ -100,7 +103,7 @@ sources = files(
   'dnsmasq/nm-dnsmasq-manager.c',
   'dnsmasq/nm-dnsmasq-utils.c',
   'ppp/nm-ppp-manager-call.c',
-  'settings/plugins/keyfile/nms-keyfile-connection.c',
+  'settings/plugins/keyfile/nms-keyfile-storage.c',
   'settings/plugins/keyfile/nms-keyfile-plugin.c',
   'settings/plugins/keyfile/nms-keyfile-reader.c',
   'settings/plugins/keyfile/nms-keyfile-utils.c',
@@ -110,6 +113,8 @@ sources = files(
   'settings/nm-settings.c',
   'settings/nm-settings-connection.c',
   'settings/nm-settings-plugin.c',
+  'settings/nm-settings-storage.c',
+  'settings/nm-settings-utils.c',
   'supplicant/nm-supplicant-config.c',
   'supplicant/nm-supplicant-interface.c',
   'supplicant/nm-supplicant-manager.c',
diff --git a/src/ndisc/nm-fake-ndisc.c b/src/ndisc/nm-fake-ndisc.c
index f4719231..dadd1743 100644
--- a/src/ndisc/nm-fake-ndisc.c
+++ b/src/ndisc/nm-fake-ndisc.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-fake-ndisc.c - Fake implementation of neighbor discovery
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/nm-fake-ndisc.h b/src/ndisc/nm-fake-ndisc.h
index 3266dc89..5cf65bc1 100644
--- a/src/ndisc/nm-fake-ndisc.h
+++ b/src/ndisc/nm-fake-ndisc.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-fake-ndisc.h - Fake implementation of neighbor discovery
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/nm-lndp-ndisc.c b/src/ndisc/nm-lndp-ndisc.c
index 2dd7e7d8..5643c192 100644
--- a/src/ndisc/nm-lndp-ndisc.c
+++ b/src/ndisc/nm-lndp-ndisc.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-lndp-ndisc.c - Router discovery implementation using libndp
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/nm-lndp-ndisc.h b/src/ndisc/nm-lndp-ndisc.h
index f042fb74..f95d5d0c 100644
--- a/src/ndisc/nm-lndp-ndisc.h
+++ b/src/ndisc/nm-lndp-ndisc.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-lndp-ndisc.h - Implementation of neighbor discovery using libndp
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/nm-ndisc-private.h b/src/ndisc/nm-ndisc-private.h
index ae03c0ee..450def27 100644
--- a/src/ndisc/nm-ndisc-private.h
+++ b/src/ndisc/nm-ndisc-private.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-ndisc.h - Perform IPv6 neighbor discovery
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/nm-ndisc.c b/src/ndisc/nm-ndisc.c
index dd535630..1acf5ba9 100644
--- a/src/ndisc/nm-ndisc.c
+++ b/src/ndisc/nm-ndisc.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-ndisc.c - Perform IPv6 neighbor discovery
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/nm-ndisc.h b/src/ndisc/nm-ndisc.h
index 6c0c0264..766d4ab9 100644
--- a/src/ndisc/nm-ndisc.h
+++ b/src/ndisc/nm-ndisc.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-ndisc.h - Perform IPv6 neighbor discovery
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index d2291d44..f752376d 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* ndisc.c - test program
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/ndisc/tests/test-ndisc-linux.c b/src/ndisc/tests/test-ndisc-linux.c
index d47c0018..715d4037 100644
--- a/src/ndisc/tests/test-ndisc-linux.c
+++ b/src/ndisc/tests/test-ndisc-linux.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* ndisc.c - test program
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/nm-act-request.c b/src/nm-act-request.c
index a79167f2..763b1503 100644
--- a/src/nm-act-request.c
+++ b/src/nm-act-request.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-act-request.h b/src/nm-act-request.h
index e16e1ecf..055b2591 100644
--- a/src/nm-act-request.h
+++ b/src/nm-act-request.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c
index 17568c30..b937f732 100644
--- a/src/nm-active-connection.c
+++ b/src/nm-active-connection.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -182,7 +181,7 @@ NM_UTILS_FLAGS2STR_DEFINE_STATIC (_state_flags_to_string, NMActivationStateFlags
 
 static void
 _settings_connection_updated (NMSettingsConnection *sett_conn,
-                              gboolean by_user,
+                              guint update_reason_u,
                               gpointer user_data)
 {
 	NMActiveConnection *self = user_data;
@@ -285,7 +284,7 @@ nm_active_connection_set_state (NMActiveConnection *self,
 	if (   new_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED
 	    || old_state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
 		nm_settings_connection_update_timestamp (priv->settings_connection.obj,
-		                                         (guint64) time (NULL), TRUE);
+		                                         (guint64) time (NULL));
 	}
 
 	if (priv->device) {
@@ -523,8 +522,9 @@ nm_active_connection_clear_secrets (NMActiveConnection *self)
 	if (nm_settings_connection_has_unmodified_applied_connection (priv->settings_connection.obj,
 	                                                              priv->applied_connection,
 	                                                              NM_SETTING_COMPARE_FLAG_NONE)) {
-		/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-		nm_connection_clear_secrets (nm_settings_connection_get_connection (priv->settings_connection.obj));
+		nm_settings_connection_clear_secrets (priv->settings_connection.obj,
+		                                      FALSE,
+		                                      FALSE);
 	}
 	nm_connection_clear_secrets (priv->applied_connection);
 }
@@ -1534,8 +1534,7 @@ finalize (GObject *object)
 
 	nm_dbus_track_obj_path_set (&priv->settings_connection, NULL, FALSE);
 
-	_nm_keep_alive_set_owner (priv->keep_alive, NULL);
-	g_clear_object (&priv->keep_alive);
+	nm_clear_pointer (&priv->keep_alive, nm_keep_alive_destroy);
 
 	G_OBJECT_CLASS (nm_active_connection_parent_class)->finalize (object);
 }
diff --git a/src/nm-active-connection.h b/src/nm-active-connection.h
index 1d4ce29e..d9d28378 100644
--- a/src/nm-active-connection.h
+++ b/src/nm-active-connection.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-audit-manager.c b/src/nm-audit-manager.c
index 9f868508..fe7645f8 100644
--- a/src/nm-audit-manager.c
+++ b/src/nm-audit-manager.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager audit support
  *
  * This program is free software; you can redistribute it and/or modify
@@ -185,9 +184,15 @@ nm_audit_log (NMAuditManager *self, GPtrArray *fields, const char *file,
 }
 
 static void
-_audit_log_helper (NMAuditManager *self, GPtrArray *fields, const char *file,
-                   guint line, const char *func, const char *op, gboolean result,
-                   gpointer subject_context, const char *reason)
+_audit_log_helper (NMAuditManager *self,
+                   GPtrArray *fields,
+                   const char *file,
+                   guint line,
+                   const char *func,
+                   const char *op,
+                   gboolean result,
+                   gpointer subject_context,
+                   const char *reason)
 {
 	AuditField op_field = { }, pid_field = { }, uid_field = { };
 	AuditField result_field = { }, reason_field = { };
@@ -247,9 +252,16 @@ nm_audit_manager_audit_enabled (NMAuditManager *self)
 }
 
 void
-_nm_audit_manager_log_connection_op (NMAuditManager *self, const char *file, guint line,
-                                     const char *func, const char *op, NMSettingsConnection *connection,
-                                     gboolean result, const char *args, gpointer subject_context, const char *reason)
+_nm_audit_manager_log_connection_op (NMAuditManager *self,
+                                     const char *file,
+                                     guint line,
+                                     const char *func,
+                                     const char *op,
+                                     NMSettingsConnection *connection,
+                                     gboolean result,
+                                     const char *args,
+                                     gpointer subject_context,
+                                     const char *reason)
 {
 	gs_unref_ptrarray GPtrArray *fields = NULL;
 	AuditField uuid_field = { }, name_field = { }, args_field = { };
diff --git a/src/nm-audit-manager.h b/src/nm-audit-manager.h
index 59b8048f..b8678227 100644
--- a/src/nm-audit-manager.h
+++ b/src/nm-audit-manager.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager audit support
  *
  * This program is free software; you can redistribute it and/or modify
@@ -42,6 +41,9 @@ typedef struct _NMAuditManagerClass NMAuditManagerClass;
 #define NM_AUDIT_OP_CONN_DEACTIVATE         "connection-deactivate"
 #define NM_AUDIT_OP_CONN_CLEAR_SECRETS      "connection-clear-secrets"
 
+#define NM_AUDIT_OP_CONNS_RELOAD            "connections-reload"
+#define NM_AUDIT_OP_CONNS_LOAD              "connections-load"
+
 #define NM_AUDIT_OP_RELOAD                  "reload"
 #define NM_AUDIT_OP_SLEEP_CONTROL           "sleep-control"
 #define NM_AUDIT_OP_NET_CONTROL             "networking-control"
diff --git a/src/nm-auth-manager.c b/src/nm-auth-manager.c
index 09a217ea..0663e207 100644
--- a/src/nm-auth-manager.c
+++ b/src/nm-auth-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -23,8 +22,10 @@
 #include "nm-auth-manager.h"
 
 #include "c-list/src/c-list.h"
+#include "nm-glib-aux/nm-dbus-aux.h"
 #include "nm-errors.h"
 #include "nm-core-internal.h"
+#include "nm-dbus-manager.h"
 #include "NetworkManagerUtils.h"
 
 #define POLKIT_SERVICE                      "org.freedesktop.PolicyKit1"
@@ -49,13 +50,13 @@ static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
 	CList calls_lst_head;
-	GDBusProxy *proxy;
-	GCancellable *new_proxy_cancellable;
-	GCancellable *cancel_cancellable;
+	GDBusConnection *dbus_connection;
+	GCancellable *shutdown_cancellable;
 	guint64 call_numid_counter;
-	bool polkit_enabled:1;
+	guint changed_signal_id;
 	bool disposing:1;
 	bool shutting_down:1;
+	bool polkit_enabled_construct_only:1;
 } NMAuthManagerPrivate;
 
 struct _NMAuthManager {
@@ -113,7 +114,7 @@ nm_auth_manager_get_polkit_enabled (NMAuthManager *self)
 {
 	g_return_val_if_fail (NM_IS_AUTH_MANAGER (self), FALSE);
 
-	return NM_AUTH_MANAGER_GET_PRIVATE (self)->polkit_enabled;
+	return NM_AUTH_MANAGER_GET_PRIVATE (self)->dbus_connection != NULL;
 }
 
 /*****************************************************************************/
@@ -131,7 +132,6 @@ typedef enum {
 struct _NMAuthManagerCallId {
 	CList calls_lst;
 	NMAuthManager *self;
-	GVariant *dbus_parameters;
 	GCancellable *dbus_cancellable;
 	NMAuthManagerCheckAuthorizationCallback callback;
 	gpointer user_data;
@@ -141,7 +141,7 @@ struct _NMAuthManagerCallId {
 };
 
 #define cancellation_id_to_str_a(call_numid) \
-	nm_sprintf_bufa (NM_STRLEN (CANCELLATION_ID_PREFIX) + 20, \
+	nm_sprintf_bufa (NM_STRLEN (CANCELLATION_ID_PREFIX) + 60, \
 	                 CANCELLATION_ID_PREFIX"%"G_GUINT64_FORMAT, \
 	                 (call_numid))
 
@@ -150,8 +150,6 @@ _call_id_free (NMAuthManagerCallId *call_id)
 {
 	c_list_unlink (&call_id->calls_lst);
 	nm_clear_g_source (&call_id->idle_id);
-	if (call_id->dbus_parameters)
-		g_variant_unref (g_steal_pointer (&call_id->dbus_parameters));
 
 	if (call_id->dbus_cancellable) {
 		/* we have a pending D-Bus call. We keep the call-id instance alive
@@ -182,7 +180,7 @@ _call_id_invoke_callback (NMAuthManagerCallId *call_id,
 }
 
 static void
-cancel_check_authorization_cb (GObject *proxy,
+cancel_check_authorization_cb (GObject *source,
                                GAsyncResult *res,
                                gpointer user_data)
 {
@@ -190,7 +188,7 @@ cancel_check_authorization_cb (GObject *proxy,
 	gs_unref_variant GVariant *value = NULL;
 	gs_free_error GError *error= NULL;
 
-	value = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
+	value = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		_LOG2T (call_id, "cancel request was cancelled");
 	else if (error)
@@ -224,18 +222,18 @@ _call_check_authorize_cb (GObject *proxy,
 	self = call_id->self;
 	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
 
-	value = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, G_VARIANT_TYPE ("((bba{ss}))"), &error);
+	value = g_dbus_connection_call_finish (G_DBUS_CONNECTION (proxy), res, &error);
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
 		/* call_id was cancelled externally, but _call_id_free() kept call_id
 		 * alive (and it has still the reference on @self. */
 
-		if (!priv->cancel_cancellable) {
+		if (!priv->shutdown_cancellable) {
 			/* we do a forced shutdown. There is no more time for cancelling... */
 			_call_id_free (call_id);
 
 			/* this shouldn't really happen, because:
 			 * _call_check_authorize() only scheduled the D-Bus request at a time when
-			 * cancel_cancellable was still set. It means, somebody called force-shutdown
+			 * shutdown_cancellable was still set. It means, somebody called force-shutdown
 			 * after call-id was schedule.
 			 * force-shutdown should only be called after:
 			 *   - cancel all pending requests
@@ -244,15 +242,19 @@ _call_check_authorize_cb (GObject *proxy,
 			g_return_if_reached ();
 		}
 
-		g_dbus_proxy_call (priv->proxy,
-		                   "CancelCheckAuthorization",
-		                   g_variant_new ("(s)",
-		                                  cancellation_id_to_str_a (call_id->call_numid)),
-		                   G_DBUS_CALL_FLAGS_NONE,
-		                   CANCELLATION_TIMEOUT_MS,
-		                   priv->cancel_cancellable,
-		                   cancel_check_authorization_cb,
-		                   call_id);
+		g_dbus_connection_call (priv->dbus_connection,
+		                        POLKIT_SERVICE,
+		                        POLKIT_OBJECT_PATH,
+		                        POLKIT_INTERFACE,
+		                        "CancelCheckAuthorization",
+		                        g_variant_new ("(s)",
+		                                       cancellation_id_to_str_a (call_id->call_numid)),
+		                        G_VARIANT_TYPE ("()"),
+		                        G_DBUS_CALL_FLAGS_NONE,
+		                        CANCELLATION_TIMEOUT_MS,
+		                        priv->shutdown_cancellable,
+		                        cancel_check_authorization_cb,
+		                        call_id);
 		return;
 	}
 
@@ -270,30 +272,6 @@ _call_check_authorize_cb (GObject *proxy,
 	_call_id_invoke_callback (call_id, is_authorized, is_challenge, error);
 }
 
-static void
-_call_check_authorize (NMAuthManagerCallId *call_id)
-{
-	NMAuthManager *self = call_id->self;
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-
-	nm_assert (call_id->dbus_parameters);
-	nm_assert (g_variant_is_floating (call_id->dbus_parameters));
-	nm_assert (!call_id->dbus_cancellable);
-
-	call_id->dbus_cancellable = g_cancellable_new ();
-
-	nm_assert (priv->cancel_cancellable);
-
-	g_dbus_proxy_call (priv->proxy,
-	                   "CheckAuthorization",
-	                   g_steal_pointer (&call_id->dbus_parameters),
-	                   G_DBUS_CALL_FLAGS_NONE,
-	                   G_MAXINT, /* no timeout */
-	                   call_id->dbus_cancellable,
-	                   _call_check_authorize_cb,
-	                   call_id);
-}
-
 static gboolean
 _call_on_idle (gpointer user_data)
 {
@@ -344,9 +322,6 @@ nm_auth_manager_check_authorization (NMAuthManager *self,
 	NMAuthManagerPrivate *priv;
 	PolkitCheckAuthorizationFlags flags;
 	char subject_buf[64];
-	GVariantBuilder builder;
-	GVariant *subject_value;
-	GVariant *details_value;
 	NMAuthManagerCallId *call_id;
 
 	g_return_val_if_fail (NM_IS_AUTH_MANAGER (self), NULL);
@@ -365,14 +340,16 @@ nm_auth_manager_check_authorization (NMAuthManager *self,
 	    ? POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION
 	    : POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE;
 
-	call_id = g_slice_new0 (NMAuthManagerCallId);
-	call_id->self = g_object_ref (self);
-	call_id->callback = callback;
-	call_id->user_data = user_data;
-	call_id->call_numid = ++priv->call_numid_counter;
+	call_id = g_slice_new (NMAuthManagerCallId);
+	*call_id = (NMAuthManagerCallId) {
+		.self       = g_object_ref (self),
+		.callback   = callback,
+		.user_data  = user_data,
+		.call_numid = ++priv->call_numid_counter,
+	};
 	c_list_link_tail (&priv->calls_lst_head, &call_id->calls_lst);
 
-	if (!priv->polkit_enabled) {
+	if (!priv->dbus_connection) {
 		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (succeeding due to polkit authorization disabled)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
 		call_id->idle_reason = IDLE_REASON_AUTHORIZED;
 		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
@@ -384,12 +361,12 @@ nm_auth_manager_check_authorization (NMAuthManager *self,
 		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (succeeding for root)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
 		call_id->idle_reason = IDLE_REASON_AUTHORIZED;
 		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
-	} else if (   !priv->proxy
-	           && !priv->new_proxy_cancellable) {
-		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (failing due to invalid DBUS proxy)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-		call_id->idle_reason = IDLE_REASON_NO_DBUS;
-		call_id->idle_id = g_idle_add (_call_on_idle, call_id);
 	} else {
+		GVariant *parameters;
+		GVariantBuilder builder;
+		GVariant *subject_value;
+		GVariant *details_value;
+
 		subject_value = nm_auth_subject_unix_process_to_polkit_gvariant (subject);
 		nm_assert (g_variant_is_floating (subject_value));
 
@@ -397,18 +374,31 @@ nm_auth_manager_check_authorization (NMAuthManager *self,
 		g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
 		details_value = g_variant_builder_end (&builder);
 
-		call_id->dbus_parameters = g_variant_new ("(@(sa{sv})s@a{ss}us)",
-		                                          subject_value,
-		                                          action_id,
-		                                          details_value,
-		                                          (guint32) flags,
-		                                          cancellation_id_to_str_a (call_id->call_numid));
-		if (!priv->proxy) {
-			_LOG2T (call_id, "CheckAuthorization(%s), subject=%s (wait for proxy)", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-		} else {
-			_LOG2T (call_id, "CheckAuthorization(%s), subject=%s", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
-			_call_check_authorize (call_id);
-		}
+		parameters = g_variant_new ("(@(sa{sv})s@a{ss}us)",
+		                            subject_value,
+		                            action_id,
+		                            details_value,
+		                            (guint32) flags,
+		                            cancellation_id_to_str_a (call_id->call_numid));
+
+		_LOG2T (call_id, "CheckAuthorization(%s), subject=%s", action_id, nm_auth_subject_to_string (subject, subject_buf, sizeof (subject_buf)));
+
+		call_id->dbus_cancellable = g_cancellable_new ();
+
+		nm_assert (priv->shutdown_cancellable);
+
+		g_dbus_connection_call (priv->dbus_connection,
+		                        POLKIT_SERVICE,
+		                        POLKIT_OBJECT_PATH,
+		                        POLKIT_INTERFACE,
+		                        "CheckAuthorization",
+		                        parameters,
+		                        G_VARIANT_TYPE ("((bba{ss}))"),
+		                        G_DBUS_CALL_FLAGS_NONE,
+		                        G_MAXINT, /* no timeout */
+		                        call_id->dbus_cancellable,
+		                        _call_check_authorize_cb,
+		                        call_id);
 	}
 
 	return call_id;
@@ -440,113 +430,18 @@ nm_auth_manager_check_authorization_cancel (NMAuthManagerCallId *call_id)
 /*****************************************************************************/
 
 static void
-_emit_changed_signal (NMAuthManager *self)
-{
-	_LOGD ("emit changed signal");
-	g_signal_emit (self, signals[CHANGED_SIGNAL], 0);
-}
-
-static void
-_log_name_owner (NMAuthManager *self, char **out_name_owner)
-{
-	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-	gs_free char *name_owner = NULL;
-
-	name_owner = g_dbus_proxy_get_name_owner (priv->proxy);
-	if (name_owner)
-		_LOGD ("dbus name owner: '%s'", name_owner);
-	else
-		_LOGD ("dbus name owner: none");
-
-	NM_SET_OUT (out_name_owner, g_steal_pointer (&name_owner));
-}
-
-static void
-_dbus_on_name_owner_notify_cb (GObject    *object,
-                               GParamSpec *pspec,
-                               gpointer    user_data)
-{
-	NMAuthManager *self = user_data;
-	gs_free char *name_owner = NULL;
-
-	nm_assert (NM_AUTH_MANAGER_GET_PRIVATE (self)->proxy == (GDBusProxy *) object);
-
-	_log_name_owner (self, &name_owner);
-	if (!name_owner) {
-		/* when the name disappears, we also want to raise a emit signal.
-		 * When it appears, we raise one already. */
-		_emit_changed_signal (self);
-	}
-}
-
-static void
-_dbus_on_changed_signal_cb (GDBusProxy *proxy,
-                            gpointer    user_data)
+changed_signal_cb (GDBusConnection *connection,
+                   const char *sender_name,
+                   const char *object_path,
+                   const char *interface_name,
+                   const char *signal_name,
+                   GVariant *parameters,
+                   gpointer user_data)
 {
 	NMAuthManager *self = user_data;
 
-	nm_assert (NM_AUTH_MANAGER_GET_PRIVATE (self)->proxy == proxy);
-
 	_LOGD ("dbus signal: \"Changed\"");
-	_emit_changed_signal (self);
-}
-
-static void
-_dbus_new_proxy_cb (GObject *source_object,
-                    GAsyncResult *res,
-                    gpointer user_data)
-{
-	NMAuthManager *self;
-	NMAuthManagerPrivate *priv;
-	gs_free_error GError *error = NULL;
-	GDBusProxy *proxy;
-	NMAuthManagerCallId *call_id;
-
-	proxy = g_dbus_proxy_new_for_bus_finish  (res, &error);
-
-	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-		return;
-
-	self = user_data;
-	priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
-
-	priv->proxy = proxy;
-	g_clear_object (&priv->new_proxy_cancellable);
-
-	if (!priv->proxy) {
-		_LOGE ("could not create polkit proxy: %s", error->message);
-
-again:
-		c_list_for_each_entry (call_id, &priv->calls_lst_head, calls_lst) {
-			if (call_id->dbus_parameters) {
-				_LOG2T (call_id, "completed: failed due to no D-Bus proxy after startup");
-				_call_id_invoke_callback (call_id, FALSE, FALSE, error);
-				goto again;
-			}
-		}
-		return;
-	}
-
-	priv->cancel_cancellable = g_cancellable_new ();
-
-	g_signal_connect (priv->proxy,
-	                  "notify::g-name-owner",
-	                  G_CALLBACK (_dbus_on_name_owner_notify_cb),
-	                  self);
-	_nm_dbus_signal_connect (priv->proxy, "Changed", NULL,
-	                         G_CALLBACK (_dbus_on_changed_signal_cb),
-	                         self);
-
-	_log_name_owner (self, NULL);
-
-	c_list_for_each_entry (call_id, &priv->calls_lst_head, calls_lst) {
-		if (call_id->dbus_parameters) {
-			_LOG2T (call_id, "CheckAuthorization invoke now");
-			_call_check_authorize (call_id);
-		}
-	}
-
-	_emit_changed_signal (self);
+	g_signal_emit (self, signals[CHANGED_SIGNAL], 0);
 }
 
 /*****************************************************************************/
@@ -590,7 +485,7 @@ nm_auth_manager_force_shutdown (NMAuthManager *self)
 	 */
 
 	priv->shutting_down = TRUE;
-	nm_clear_g_cancellable (&priv->cancel_cancellable);
+	nm_clear_g_cancellable (&priv->shutdown_cancellable);
 }
 
 /*****************************************************************************/
@@ -603,7 +498,7 @@ set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *p
 	switch (prop_id) {
 	case PROP_POLKIT_ENABLED:
 		/* construct-only */
-		priv->polkit_enabled = !!g_value_get_boolean (value);
+		priv->polkit_enabled_construct_only = !!g_value_get_boolean (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -626,23 +521,42 @@ constructed (GObject *object)
 {
 	NMAuthManager *self = NM_AUTH_MANAGER (object);
 	NMAuthManagerPrivate *priv = NM_AUTH_MANAGER_GET_PRIVATE (self);
+	NMLogLevel logl = LOGL_DEBUG;
+	const char *create_message;
 
 	G_OBJECT_CLASS (nm_auth_manager_parent_class)->constructed (object);
 
-	_LOGD ("create auth-manager: polkit %s", priv->polkit_enabled ? "enabled" : "disabled");
-
-	if (priv->polkit_enabled) {
-		priv->new_proxy_cancellable = g_cancellable_new ();
-		g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
-		                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
-		                          NULL,
-		                          POLKIT_SERVICE,
-		                          POLKIT_OBJECT_PATH,
-		                          POLKIT_INTERFACE,
-		                          priv->new_proxy_cancellable,
-		                          _dbus_new_proxy_cb,
-		                          self);
+	if (!priv->polkit_enabled_construct_only) {
+		create_message = "polkit disabled";
+		goto out;
 	}
+
+	priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
+
+	if (!priv->dbus_connection) {
+		/* This warrants an info level message. */
+		logl = LOGL_INFO;
+		create_message = "D-Bus connection not available. Polkit is disabled and all requests are authenticated.";
+		goto out;
+	}
+
+	priv->shutdown_cancellable = g_cancellable_new ();
+
+	priv->changed_signal_id = g_dbus_connection_signal_subscribe (priv->dbus_connection,
+	                                                              POLKIT_SERVICE,
+	                                                              POLKIT_INTERFACE,
+	                                                              "Changed",
+	                                                              POLKIT_OBJECT_PATH,
+	                                                              NULL,
+	                                                              G_DBUS_SIGNAL_FLAGS_NONE,
+	                                                              changed_signal_cb,
+	                                                              self,
+	                                                              NULL);
+
+	create_message = "polkit enabled";
+
+out:
+	_NMLOG (logl, "create auth-manager: %s", create_message);
 }
 
 NMAuthManager *
@@ -660,7 +574,8 @@ nm_auth_manager_setup (gboolean polkit_enabled)
 	singleton_instance = self;
 	nm_singleton_instance_register ();
 
-	nm_log_dbg (LOGD_CORE, "setup %s singleton (%p)", "NMAuthManager", singleton_instance);
+	nm_log_dbg (LOGD_CORE, "setup %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")",
+	            "NMAuthManager", NM_HASH_OBFUSCATE_PTR (singleton_instance));
 
 	return self;
 }
@@ -677,15 +592,14 @@ dispose (GObject *object)
 
 	priv->disposing = TRUE;
 
-	nm_clear_g_cancellable (&priv->new_proxy_cancellable);
-	nm_clear_g_cancellable (&priv->cancel_cancellable);
+	nm_clear_g_cancellable (&priv->shutdown_cancellable);
 
-	if (priv->proxy) {
-		g_signal_handlers_disconnect_by_data (priv->proxy, self);
-		g_clear_object (&priv->proxy);
-	}
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->changed_signal_id);
 
 	G_OBJECT_CLASS (nm_auth_manager_parent_class)->dispose (object);
+
+	g_clear_object (&priv->dbus_connection);
 }
 
 static void
diff --git a/src/nm-auth-manager.h b/src/nm-auth-manager.h
index fe7ee787..86746d09 100644
--- a/src/nm-auth-manager.h
+++ b/src/nm-auth-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-auth-subject.c b/src/nm-auth-subject.c
index 5599201e..fd39bff1 100644
--- a/src/nm-auth-subject.c
+++ b/src/nm-auth-subject.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-auth-subject.h b/src/nm-auth-subject.h
index 9267dfd4..7af76018 100644
--- a/src/nm-auth-subject.h
+++ b/src/nm-auth-subject.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-auth-utils.c b/src/nm-auth-utils.c
index a0ad84c1..7235cba1 100644
--- a/src/nm-auth-utils.c
+++ b/src/nm-auth-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -31,7 +30,10 @@
 /*****************************************************************************/
 
 struct NMAuthChain {
-	GHashTable *data_hash;
+
+	CList parent_lst;
+
+	CList data_lst_head;
 
 	CList auth_call_lst_head;
 
@@ -41,26 +43,49 @@ struct NMAuthChain {
 	NMAuthChainResultFunc done_func;
 	gpointer user_data;
 
-	guint32 refcount;
+	guint num_pending_auth_calls;
 
-	bool done:1;
+	bool is_destroyed:1;
+	bool is_finishing:1;
 };
 
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMAuthChain, parent_lst) == 0);
+
 typedef struct {
 	CList auth_call_lst;
 	NMAuthChain *chain;
 	NMAuthManagerCallId *call_id;
-	char *permission;
+	const char *permission;
+	NMAuthCallResult result;
 } AuthCall;
 
 /*****************************************************************************/
 
+static void _auth_chain_destroy (NMAuthChain *self);
+
+/*****************************************************************************/
+
 static void
 _ASSERT_call (AuthCall *call)
 {
 	nm_assert (call);
 	nm_assert (call->chain);
+	nm_assert (call->permission && strlen (call->permission) > 0);
 	nm_assert (nm_c_list_contains_entry (&call->chain->auth_call_lst_head, call, auth_call_lst));
+#if NM_MORE_ASSERTS > 5
+	{
+		AuthCall *auth_call;
+		guint n = 0;
+
+		c_list_for_each_entry (auth_call, &call->chain->auth_call_lst_head, auth_call_lst) {
+			nm_assert (   auth_call->result == NM_AUTH_CALL_RESULT_UNKNOWN
+			           || !auth_call->call_id);
+			if (auth_call->call_id)
+				n++;
+		}
+		nm_assert (n == call->chain->num_pending_auth_calls);
+	}
+#endif
 }
 
 /*****************************************************************************/
@@ -68,67 +93,68 @@ _ASSERT_call (AuthCall *call)
 static void
 auth_call_free (AuthCall *call)
 {
-	if (call->call_id)
-		nm_auth_manager_check_authorization_cancel (call->call_id);
+	_ASSERT_call (call);
+
 	c_list_unlink_stale (&call->auth_call_lst);
-	g_free (call->permission);
+	if (call->call_id) {
+		call->chain->num_pending_auth_calls--;
+		nm_auth_manager_check_authorization_cancel (call->call_id);
+	}
 	g_slice_free (AuthCall, call);
 }
 
+static AuthCall *
+_find_auth_call (NMAuthChain *self, const char *permission)
+{
+	AuthCall *auth_call;
+
+	c_list_for_each_entry (auth_call, &self->auth_call_lst_head, auth_call_lst) {
+		if (nm_streq (auth_call->permission, permission))
+			return auth_call;
+	}
+	return NULL;
+}
+
 /*****************************************************************************/
 
 typedef struct {
-
-	/* must be the first field. */
+	CList data_lst;
 	const char *tag;
-
 	gpointer data;
 	GDestroyNotify destroy;
-	char tag_data[];
 } ChainData;
 
-static ChainData *
-chain_data_new (const char *tag, gpointer data, GDestroyNotify destroy)
-{
-	ChainData *tmp;
-	gsize l = strlen (tag);
-
-	tmp = g_malloc (sizeof (ChainData) + l + 1);
-	tmp->tag = &tmp->tag_data[0];
-	tmp->data = data;
-	tmp->destroy = destroy;
-	memcpy (&tmp->tag_data[0], tag, l + 1);
-	return tmp;
-}
-
 static void
-chain_data_free (gpointer data)
+chain_data_free (ChainData *chain_data)
 {
-	ChainData *tmp = data;
-
-	if (tmp->destroy)
-		tmp->destroy (tmp->data);
-	g_free (tmp);
+	c_list_unlink_stale (&chain_data->data_lst);
+	if (chain_data->destroy)
+		chain_data->destroy (chain_data->data);
+	g_slice_free (ChainData, chain_data);
 }
 
-static gpointer
+static ChainData *
 _get_data (NMAuthChain *self, const char *tag)
 {
-	ChainData *tmp;
+	ChainData *chain_data;
 
-	if (!self->data_hash)
-		return NULL;
-	tmp = g_hash_table_lookup (self->data_hash, &tag);
-	return tmp ? tmp->data : NULL;
+	c_list_for_each_entry (chain_data, &self->data_lst_head, data_lst) {
+		if (nm_streq (chain_data->tag, tag))
+			return chain_data;
+	}
+	return NULL;
 }
 
 gpointer
 nm_auth_chain_get_data (NMAuthChain *self, const char *tag)
 {
+	ChainData *chain_data;
+
 	g_return_val_if_fail (self, NULL);
 	g_return_val_if_fail (tag, NULL);
 
-	return _get_data (self, tag);
+	chain_data = _get_data (self, tag);
+	return chain_data ? chain_data->data : NULL;
 }
 
 /**
@@ -145,47 +171,77 @@ nm_auth_chain_get_data (NMAuthChain *self, const char *tag)
 gpointer
 nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
 {
-	ChainData *tmp;
-	gpointer value = NULL;
+	ChainData *chain_data;
+	gpointer value;
 
 	g_return_val_if_fail (self, NULL);
 	g_return_val_if_fail (tag, NULL);
 
-	if (!self->data_hash)
-		return NULL;
-
-	tmp = g_hash_table_lookup (self->data_hash, &tag);
-	if (!tmp)
+	chain_data = _get_data (self, tag);
+	if (!chain_data)
 		return NULL;
 
-	value = tmp->data;
+	value = chain_data->data;
 
 	/* Make sure the destroy handler isn't called when freeing */
-	tmp->destroy = NULL;
-	g_hash_table_remove (self->data_hash, tmp);
+	chain_data->destroy = NULL;
+	chain_data_free (chain_data);
 	return value;
 }
 
+/**
+ * nm_auth_chain_set_data_unsafe:
+ * @self: the #NMAuthChain
+ * @tag: the tag for referencing the attached data.
+ * @data: the data to attach. If %NULL, this call has no effect
+ *   and nothing is attached.
+ * @data_destroy: (allow-none): the destroy function for the data pointer.
+ *
+ * @tag string is not cloned and must outlife @self. That is why
+ * the function is "unsafe". Use nm_auth_chain_set_data() with a C literal
+ * instead.
+ *
+ * It is a bug to add the same tag more than once.
+ */
 void
-nm_auth_chain_set_data (NMAuthChain *self,
-                        const char *tag,
-                        gpointer data,
-                        GDestroyNotify data_destroy)
+nm_auth_chain_set_data_unsafe (NMAuthChain *self,
+                               const char *tag,
+                               gpointer data,
+                               GDestroyNotify data_destroy)
 {
+	ChainData *chain_data;
+
 	g_return_if_fail (self);
 	g_return_if_fail (tag);
 
-	if (data == NULL) {
-		if (self->data_hash)
-			g_hash_table_remove (self->data_hash, &tag);
-	} else {
-		if (!self->data_hash) {
-			self->data_hash = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal,
-			                                         NULL, chain_data_free);
-		}
-		g_hash_table_add (self->data_hash,
-		                  chain_data_new (tag, data, data_destroy));
+	/* we should not track a large number of elements via a linked list. If this becomes
+	 * necessary, revert the code to use GHashTable again. */
+	nm_assert (c_list_length (&self->data_lst_head) < 25);
+
+	/* The tag must not yet exist. Otherwise we'd have to first search the linked
+	 * list for an existing entry. */
+	nm_assert (!_get_data (self, tag));
+
+	if (!data) {
+		/* we don't track user data of %NULL.
+		 *
+		 * In the past this had also the meaning of removing a user-data. But since
+		 * nm_auth_chain_set_data() does not allow being called more than once
+		 * for the same tag, we don't need to remove anything. */
+		return;
 	}
+
+	chain_data = g_slice_new (ChainData);
+	*chain_data = (ChainData) {
+		.tag     = tag,
+		.data    = data,
+		.destroy = data_destroy,
+	};
+
+	/* we assert that no duplicate tags are added. But still, add the new
+	 * element to the front, so that it would shadow the duplicate element
+	 * in the list. */
+	c_list_link_front (&self->data_lst_head, &chain_data->data_lst);
 }
 
 /*****************************************************************************/
@@ -193,13 +249,26 @@ nm_auth_chain_set_data (NMAuthChain *self,
 NMAuthCallResult
 nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
 {
-	gpointer data;
+	AuthCall *auth_call;
 
 	g_return_val_if_fail (self, NM_AUTH_CALL_RESULT_UNKNOWN);
 	g_return_val_if_fail (permission, NM_AUTH_CALL_RESULT_UNKNOWN);
 
-	data = _get_data (self, permission);
-	return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN;
+	/* it is a bug to request the result other than from the done_func()
+	 * callback. You are not supposed to poll for the result but request
+	 * it upon notification. */
+	nm_assert (self->is_finishing);
+
+	auth_call = _find_auth_call (self, permission);
+
+	/* it is a bug to request a permission result that was not
+	 * previously requested or which did not complete yet. */
+	if (!auth_call)
+		g_return_val_if_reached (NM_AUTH_CALL_RESULT_UNKNOWN);
+
+	nm_assert (!auth_call->call_id);
+
+	return auth_call->result;
 }
 
 NMAuthSubject *
@@ -212,39 +281,6 @@ nm_auth_chain_get_subject (NMAuthChain *self)
 
 /*****************************************************************************/
 
-static gboolean
-auth_chain_finish (NMAuthChain *self)
-{
-	self->done = TRUE;
-
-	/* Ensure we stay alive across the callback */
-	nm_assert (self->refcount == 1);
-	self->refcount++;
-	self->done_func (self, NULL, self->context, self->user_data);
-	nm_assert (NM_IN_SET (self->refcount, 1, 2));
-	nm_auth_chain_destroy (self);
-	return FALSE;
-}
-
-static void
-auth_call_complete (AuthCall *call)
-{
-	NMAuthChain *self;
-
-	_ASSERT_call (call);
-
-	self = call->chain;
-
-	nm_assert (!self->done);
-
-	auth_call_free (call);
-
-	if (c_list_is_empty (&self->auth_call_lst_head)) {
-		/* we are on an idle-handler or a clean call-stack (non-reentrant). */
-		auth_chain_finish (self);
-	}
-}
-
 static void
 pk_call_cb (NMAuthManager *auth_manager,
             NMAuthManagerCallId *call_id,
@@ -253,49 +289,110 @@ pk_call_cb (NMAuthManager *auth_manager,
             GError *error,
             gpointer user_data)
 {
+	NMAuthChain *self;
 	AuthCall *call;
-	NMAuthCallResult call_result;
+
+	nm_assert (call_id);
 
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
 	call = user_data;
 
+	_ASSERT_call (call);
 	nm_assert (call->call_id == call_id);
+	nm_assert (call->result == NM_AUTH_CALL_RESULT_UNKNOWN);
+
+	self = call->chain;
+
+	nm_assert (!self->is_destroyed);
+	nm_assert (!self->is_finishing);
 
 	call->call_id = NULL;
 
-	call_result = nm_auth_call_result_eval (is_authorized, is_challenge, error);
+	call->result = nm_auth_call_result_eval (is_authorized, is_challenge, error);
+
+	call->chain->num_pending_auth_calls--;
 
-	nm_auth_chain_set_data (call->chain, call->permission, GUINT_TO_POINTER (call_result), NULL);
+	_ASSERT_call (call);
 
-	auth_call_complete (call);
+	if (call->chain->num_pending_auth_calls == 0) {
+		/* we are on an idle-handler or a clean call-stack (non-reentrant) so it's safe
+		 * to invoke the callback right away. */
+		self->is_finishing = TRUE;
+		self->done_func (self, self->context, self->user_data);
+		nm_assert (self->is_finishing);
+		_auth_chain_destroy (self);
+	}
 }
 
+/**
+ * nm_auth_chain_add_call_unsafe:
+ * @self: the #NMAuthChain
+ * @permission: the permission string. This string is kept by reference
+ *   and you must make sure that it's lifetime lasts until the NMAuthChain
+ *   gets destroyed. That's why the function is "unsafe". Use
+ *   nm_auth_chain_add_call() instead.
+ * @allow_interaction: flag
+ *
+ * It's "unsafe" because @permission is not copied. It's the callers responsibility
+ * that the permission string stays valid as long as NMAuthChain.
+ *
+ * If you can, use nm_auth_chain_add_call() instead!
+ *
+ * If you have a non-static string, you may attach the permission string as
+ * user-data via nm_auth_chain_set_data().
+ */
 void
-nm_auth_chain_add_call (NMAuthChain *self,
-                        const char *permission,
-                        gboolean allow_interaction)
+nm_auth_chain_add_call_unsafe (NMAuthChain *self,
+                               const char *permission,
+                               gboolean allow_interaction)
 {
 	AuthCall *call;
-	NMAuthManager *auth_manager = nm_auth_manager_get ();
 
 	g_return_if_fail (self);
 	g_return_if_fail (self->subject);
-	g_return_if_fail (!self->done);
+	g_return_if_fail (!self->is_finishing);
+	g_return_if_fail (!self->is_destroyed);
 	g_return_if_fail (permission && *permission);
-	g_return_if_fail (nm_auth_subject_is_unix_process (self->subject) || nm_auth_subject_is_internal (self->subject));
+	nm_assert (   nm_auth_subject_is_unix_process (self->subject)
+	           || nm_auth_subject_is_internal (self->subject));
+
+	/* duplicate permissions are not supported, also because nm_auth_chain_get_result()
+	 * can only return one-permission. */
+	nm_assert (!_find_auth_call (self, permission));
+
+	call = g_slice_new (AuthCall);
 
-	call = g_slice_new0 (AuthCall);
-	call->chain = self;
-	call->permission = g_strdup (permission);
-	c_list_link_tail (&self->auth_call_lst_head, &call->auth_call_lst);
-	call->call_id = nm_auth_manager_check_authorization (auth_manager,
+	*call = (AuthCall) {
+		.chain      = self,
+		.call_id    = NULL,
+		.result     = NM_AUTH_CALL_RESULT_UNKNOWN,
+
+		/* we don't clone the permission string. It's the callers responsiblity. */
+		.permission = permission,
+	};
+
+	/* above we assert that no duplicate permissions are added. Still, track the
+	 * new request to the front of the list so that it would shadow an earlier
+	 * call. */
+	c_list_link_front (&self->auth_call_lst_head, &call->auth_call_lst);
+
+	call->call_id = nm_auth_manager_check_authorization (nm_auth_manager_get (),
 	                                                     self->subject,
 	                                                     permission,
 	                                                     allow_interaction,
 	                                                     pk_call_cb,
 	                                                     call);
+
+	self->num_pending_auth_calls++;
+
+	_ASSERT_call (call);
+
+	/* we track auth-calls in a linked list. If we end up requesting too many permissions this
+	 * becomes inefficient. If that ever happens, consider a more efficient data structure for
+	 * a large number of requests. */
+	nm_assert (self->num_pending_auth_calls < 25);
 }
 
 /*****************************************************************************/
@@ -310,6 +407,7 @@ nm_auth_chain_new_context (GDBusMethodInvocation *context,
 	NMAuthChain *chain;
 
 	g_return_val_if_fail (context, NULL);
+	nm_assert (done_func);
 
 	subject = nm_auth_subject_new_unix_process_from_context (context);
 	if (!subject)
@@ -323,7 +421,6 @@ nm_auth_chain_new_context (GDBusMethodInvocation *context,
 	return chain;
 }
 
-/* Requires an NMAuthSubject */
 NMAuthChain *
 nm_auth_chain_new_subject (NMAuthSubject *subject,
                            GDBusMethodInvocation *context,
@@ -333,15 +430,20 @@ nm_auth_chain_new_subject (NMAuthSubject *subject,
 	NMAuthChain *self;
 
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
-	nm_assert (nm_auth_subject_is_unix_process (subject) || nm_auth_subject_is_internal (subject));
-
-	self = g_slice_new0 (NMAuthChain);
-	c_list_init (&self->auth_call_lst_head);
-	self->refcount = 1;
-	self->done_func = done_func;
-	self->user_data = user_data;
-	self->context = context ? g_object_ref (context) : NULL;
-	self->subject = g_object_ref (subject);
+	nm_assert (   nm_auth_subject_is_unix_process (subject)
+	           || nm_auth_subject_is_internal (subject));
+	nm_assert (done_func);
+
+	self = g_slice_new (NMAuthChain);
+	*self = (NMAuthChain) {
+		.done_func          = done_func,
+		.user_data          = user_data,
+		.context            = nm_g_object_ref (context),
+		.subject            = g_object_ref (subject),
+		.parent_lst         = C_LIST_INIT (self->parent_lst),
+		.data_lst_head      = C_LIST_INIT (self->data_lst_head),
+		.auth_call_lst_head = C_LIST_INIT (self->auth_call_lst_head),
+	};
 	return self;
 }
 
@@ -352,30 +454,51 @@ nm_auth_chain_new_subject (NMAuthSubject *subject,
  * Destroys the auth-chain. By destroying the auth-chain, you also cancel
  * the receipt of the done-callback. IOW, the callback will not be invoked.
  *
- * The only exception is, if may call nm_auth_chain_destroy() from inside
- * the callback. In this case, @self stays alive until the callback returns.
+ * The only exception is, you may call nm_auth_chain_destroy() from inside
+ * the callback. In this case the call has no effect and @self stays alive
+ * until the callback returns.
  *
  * Note that you might only destroy an auth-chain exactly once, and never
- * after the callback was handled.
+ * after the callback was handled. After the callback returns, the auth chain
+ * always gets automatically destroyed. So you only need to explicitly destroy
+ * it, if you want to abort it before the callback complets.
  */
 void
 nm_auth_chain_destroy (NMAuthChain *self)
 {
-	AuthCall *call;
-
 	g_return_if_fail (self);
-	g_return_if_fail (NM_IN_SET (self->refcount, 1, 2));
+	g_return_if_fail (!self->is_destroyed);
 
-	if (--self->refcount > 0)
+	self->is_destroyed = TRUE;
+
+	if (self->is_finishing) {
+		/* we are called from inside the callback. Keep the instance alive for the moment. */
 		return;
+	}
+
+	_auth_chain_destroy (self);
+}
+
+static void
+_auth_chain_destroy (NMAuthChain *self)
+{
+	AuthCall *call;
+	ChainData *chain_data;
+
+	c_list_unlink (&self->parent_lst);
 
 	nm_clear_g_object (&self->subject);
 	nm_clear_g_object (&self->context);
 
+	/* we must first destry all AuthCall instances before ChainData. The reason is
+	 * that AuthData.permission is not cloned and the lifetime of the string must
+	 * be ensured by the caller. A sensible thing to do for the caller is attach the
+	 * permission string via nm_auth_chain_set_data(). Hence, first free the AuthCall. */
 	while ((call = c_list_first_entry (&self->auth_call_lst_head, AuthCall, auth_call_lst)))
 		auth_call_free (call);
 
-	nm_clear_pointer (&self->data_hash, g_hash_table_destroy);
+	while ((chain_data = c_list_first_entry (&self->data_lst_head, ChainData, data_lst)))
+		chain_data_free (chain_data);
 
 	g_slice_free (NMAuthChain, self);
 }
@@ -395,7 +518,8 @@ nm_auth_is_subject_in_acl (NMConnection *connection,
 
 	g_return_val_if_fail (connection, FALSE);
 	g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), FALSE);
-	g_return_val_if_fail (nm_auth_subject_is_internal (subject) || nm_auth_subject_is_unix_process (subject), FALSE);
+	nm_assert (   nm_auth_subject_is_internal (subject)
+	           || nm_auth_subject_is_unix_process (subject));
 
 	if (nm_auth_subject_is_internal (subject))
 		return TRUE;
diff --git a/src/nm-auth-utils.h b/src/nm-auth-utils.h
index 5f9823b6..5201d260 100644
--- a/src/nm-auth-utils.h
+++ b/src/nm-auth-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -25,10 +24,11 @@
 
 #include "nm-auth-manager.h"
 
+/*****************************************************************************/
+
 typedef struct NMAuthChain NMAuthChain;
 
 typedef void (*NMAuthChainResultFunc) (NMAuthChain *chain,
-                                       GError *error,
                                        GDBusMethodInvocation *context,
                                        gpointer user_data);
 
@@ -45,22 +45,47 @@ gpointer nm_auth_chain_get_data (NMAuthChain *chain, const char *tag);
 
 gpointer nm_auth_chain_steal_data (NMAuthChain *chain, const char *tag);
 
-void nm_auth_chain_set_data (NMAuthChain *chain,
-                             const char *tag,
-                             gpointer data,
-                             GDestroyNotify data_destroy);
+void nm_auth_chain_set_data_unsafe (NMAuthChain *chain,
+                                    const char *tag,
+                                    gpointer data,
+                                    GDestroyNotify data_destroy);
+
+#define nm_auth_chain_set_data(chain, tag, data, data_destroy) \
+	nm_auth_chain_set_data_unsafe ((chain), ""tag"", (data), (data_destroy))
 
 NMAuthCallResult nm_auth_chain_get_result (NMAuthChain *chain,
                                            const char *permission);
 
-void nm_auth_chain_add_call (NMAuthChain *chain,
-                             const char *permission,
-                             gboolean allow_interaction);
+void nm_auth_chain_add_call_unsafe (NMAuthChain *chain,
+                                    const char *permission,
+                                    gboolean allow_interaction);
+
+#define nm_auth_chain_add_call(chain, permission, allow_interaction) \
+	nm_auth_chain_add_call_unsafe ((chain), ""permission"", (allow_interaction))
 
 void nm_auth_chain_destroy (NMAuthChain *chain);
 
 NMAuthSubject *nm_auth_chain_get_subject (NMAuthChain *self);
 
+/*****************************************************************************/
+
+struct CList;
+
+static inline NMAuthChain *
+nm_auth_chain_parent_lst_entry (struct CList *parent_lst_self)
+{
+	return (NMAuthChain *) ((void *) parent_lst_self);
+}
+
+static inline struct CList *
+nm_auth_chain_parent_lst_list (NMAuthChain *self)
+{
+	return (struct CList *) ((void *) self);
+}
+
+/*****************************************************************************/
+
+
 /* Caller must free returned error description */
 gboolean nm_auth_is_subject_in_acl (NMConnection *connection,
                                     NMAuthSubject *subect,
diff --git a/src/nm-checkpoint-manager.c b/src/nm-checkpoint-manager.c
index 0c2af831..7c50d61f 100644
--- a/src/nm-checkpoint-manager.c
+++ b/src/nm-checkpoint-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-checkpoint-manager.h b/src/nm-checkpoint-manager.h
index ca66ef16..46590a0c 100644
--- a/src/nm-checkpoint-manager.h
+++ b/src/nm-checkpoint-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-checkpoint.c b/src/nm-checkpoint.c
index f715eea6..9bc979bb 100644
--- a/src/nm-checkpoint.c
+++ b/src/nm-checkpoint.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -171,7 +170,7 @@ find_settings_connection (NMCheckpoint *self,
 	*need_update = FALSE;
 
 	uuid = nm_connection_get_uuid (dev_checkpoint->settings_connection);
-	sett_conn = nm_settings_get_connection_by_uuid (nm_settings_get (), uuid);
+	sett_conn = nm_settings_get_connection_by_uuid (NM_SETTINGS_GET, uuid);
 
 	if (!sett_conn)
 		return NULL;
@@ -218,19 +217,35 @@ restore_and_activate_connection (NMCheckpoint *self,
 	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *local_error = NULL;
 	gboolean need_update, need_activation;
+	NMSettingsConnectionPersistMode persist_mode;
+	NMSettingsConnectionIntFlags sett_flags;
+	NMSettingsConnectionIntFlags sett_mask;
 
 	connection = find_settings_connection (self,
 	                                       dev_checkpoint,
 	                                       &need_update,
 	                                       &need_activation);
+
+	/* FIXME: we need to ensure to re-create/update the profile for the
+	 *   same settings plugin. E.g. if it was a keyfile in /run or /etc,
+	 *   it must be again. If it was previously handled by a certain settings plugin,
+	 *   so it must again.
+	 *
+	 * FIXME: preserve and restore the right settings flags (volatile, nm-generated). */
+	sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
+	sett_mask = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
+
 	if (connection) {
 		if (need_update) {
 			_LOGD ("rollback: updating connection %s",
 			       nm_settings_connection_get_uuid (connection));
+			persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP;
 			nm_settings_connection_update (connection,
 			                               dev_checkpoint->settings_connection,
-			                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-			                               NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
+			                               persist_mode,
+			                               sett_flags,
+			                               sett_mask,
+			                               NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
 			                               "checkpoint-rollback",
 			                               NULL);
 		}
@@ -239,11 +254,14 @@ restore_and_activate_connection (NMCheckpoint *self,
 		_LOGD ("rollback: adding connection %s again",
 		       nm_connection_get_uuid (dev_checkpoint->settings_connection));
 
-		connection = nm_settings_add_connection (nm_settings_get (),
-		                                         dev_checkpoint->settings_connection,
-		                                         TRUE,
-		                                         &local_error);
-		if (!connection) {
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+		if (!nm_settings_add_connection (NM_SETTINGS_GET,
+		                                 dev_checkpoint->settings_connection,
+		                                 persist_mode,
+		                                 NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+		                                 sett_flags,
+		                                 &connection,
+		                                 &local_error)) {
 			_LOGD ("rollback: connection add failure: %s", local_error->message);
 			g_clear_error (&local_error);
 			return FALSE;
@@ -419,7 +437,7 @@ next_dev:
 		gs_free NMSettingsConnection **list = NULL;
 
 		g_return_val_if_fail (priv->connection_uuids, NULL);
-		list = nm_settings_get_connections_clone (nm_settings_get (), NULL,
+		list = nm_settings_get_connections_clone (NM_SETTINGS_GET, NULL,
 		                                          NULL, NULL,
 		                                          nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
 
@@ -429,7 +447,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);
+				nm_settings_connection_delete (con, FALSE);
 			}
 		}
 	}
@@ -687,7 +705,7 @@ nm_checkpoint_new (NMManager *manager, GPtrArray *devices, guint32 rollback_time
 
 	if (NM_FLAGS_HAS (flags, NM_CHECKPOINT_CREATE_FLAG_DELETE_NEW_CONNECTIONS)) {
 		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++) {
+		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-checkpoint.h b/src/nm-checkpoint.h
index c8598f38..708d8dd6 100644
--- a/src/nm-checkpoint.h
+++ b/src/nm-checkpoint.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-config-data.c b/src/nm-config-data.c
index 608b7e58..5655c8fd 100644
--- a/src/nm-config-data.c
+++ b/src/nm-config-data.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -1504,16 +1503,6 @@ _match_section_infos_construct (GKeyFile *keyfile, const char *prefix)
 
 /*****************************************************************************/
 
-static gboolean
-_slist_str_equals (GSList *a, GSList *b)
-{
-	while (a && b && g_strcmp0 (a->data, b->data) == 0) {
-		a = a->next;
-		b = b->next;
-	}
-	return !a && !b;
-}
-
 NMConfigChangeFlags
 nm_config_data_diff (NMConfigData *old_data, NMConfigData *new_data)
 {
@@ -1542,8 +1531,8 @@ nm_config_data_diff (NMConfigData *old_data, NMConfigData *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;
 
-	if (   !_slist_str_equals (priv_old->no_auto_default.specs, priv_new->no_auto_default.specs)
-	    || !_slist_str_equals (priv_old->no_auto_default.specs_config, priv_new->no_auto_default.specs_config))
+	if (   nm_utils_g_slist_strlist_cmp (priv_old->no_auto_default.specs,        priv_new->no_auto_default.specs)        != 0
+	    || nm_utils_g_slist_strlist_cmp (priv_old->no_auto_default.specs_config, priv_new->no_auto_default.specs_config) != 0)
 		changes |= NM_CONFIG_CHANGE_NO_AUTO_DEFAULT;
 
 	if (g_strcmp0 (nm_config_data_get_dns_mode (old_data), nm_config_data_get_dns_mode (new_data)))
@@ -1634,22 +1623,47 @@ set_property (GObject *object,
 	case PROP_NO_AUTO_DEFAULT:
 		/* construct-only */
 		{
-			char **value_arr = g_value_get_boxed (value);
-			guint i, j = 0;
-
-			priv->no_auto_default.arr = g_new (char *, g_strv_length (value_arr) + 1);
-			priv->no_auto_default.specs = NULL;
-
-			for (i = 0; value_arr && value_arr[i]; i++) {
-				if (   *value_arr[i]
-				    && nm_utils_hwaddr_valid (value_arr[i], -1)
-				    && nm_utils_strv_find_first (value_arr, i, value_arr[i]) < 0) {
-					priv->no_auto_default.arr[j++] = g_strdup (value_arr[i]);
-					priv->no_auto_default.specs = g_slist_prepend (priv->no_auto_default.specs, g_strdup_printf ("mac:%s", value_arr[i]));
+			const char *const*value_arr_orig = g_value_get_boxed (value);
+			gs_free const char **value_arr = NULL;
+			GSList *specs = NULL;
+			gsize i, j;
+			gsize len;
+
+			len = NM_PTRARRAY_LEN (value_arr_orig);
+
+			/* sort entries, remove duplicates and empty words. */
+			value_arr =   len == 0
+			            ? NULL
+			            : nm_memdup (value_arr_orig, sizeof (const char *) * (len + 1));
+			nm_utils_strv_sort (value_arr, len);
+			_nm_utils_strv_cleanup ((char **) value_arr, FALSE, TRUE, TRUE);
+
+			len = NM_PTRARRAY_LEN (value_arr);
+			j = 0;
+			for (i = 0; i < len; i++) {
+				const char *s = value_arr[i];
+				gboolean is_mac;
+				char *spec;
+
+				if (NM_STR_HAS_PREFIX (s, NM_MATCH_SPEC_INTERFACE_NAME_TAG"="))
+					is_mac = FALSE;
+				else if (nm_utils_hwaddr_valid (s, -1))
+					is_mac = TRUE;
+				else {
+					/* we drop all lines that we don't understand. */
+					continue;
 				}
+
+				value_arr[j++] = s;
+
+				spec = is_mac
+				       ? g_strdup_printf (NM_MATCH_SPEC_MAC_TAG"%s", s)
+				       : g_strdup (s);
+				specs = g_slist_prepend (specs, spec);
 			}
-			priv->no_auto_default.arr[j++] = NULL;
-			priv->no_auto_default.specs = g_slist_reverse (priv->no_auto_default.specs);
+
+			priv->no_auto_default.arr = nm_utils_strv_dup (value_arr, j);
+			priv->no_auto_default.specs = g_slist_reverse (specs);
 		}
 		break;
 	default:
diff --git a/src/nm-config-data.h b/src/nm-config-data.h
index c043aa35..8f925a6c 100644
--- a/src/nm-config-data.h
+++ b/src/nm-config-data.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-config.c b/src/nm-config.c
index 3e82bdec..d1279814 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -67,7 +66,7 @@ struct NMConfigCmdLineOptions {
 	 *
 	 * It is true, if NM is started the first time -- contrary to a restart
 	 * during the same boot up. That is determined by the content of the
-	 * /var/run/NetworManager state directory. */
+	 * /run/NetworManager state directory. */
 	bool first_start;
 };
 
@@ -100,8 +99,6 @@ typedef struct {
 	char *no_auto_default_file;
 	char *intern_config_file;
 
-	gboolean monitor_connection_files;
-
 	char *log_level;
 	char *log_domains;
 
@@ -313,14 +310,6 @@ nm_config_get_data_orig (NMConfig *config)
 	return NM_CONFIG_GET_PRIVATE (config)->config_data_orig;
 }
 
-gboolean
-nm_config_get_monitor_connection_files (NMConfig *config)
-{
-	g_return_val_if_fail (config != NULL, FALSE);
-
-	return NM_CONFIG_GET_PRIVATE (config)->monitor_connection_files;
-}
-
 const char *
 nm_config_get_log_level (NMConfig *config)
 {
@@ -360,47 +349,44 @@ nm_config_get_first_start (NMConfig *config)
 static char **
 no_auto_default_from_file (const char *no_auto_default_file)
 {
-	GPtrArray *no_auto_default_new;
-	char **list;
-	guint i;
-	char *data;
-
-	no_auto_default_new = g_ptr_array_new ();
+	gs_free char *data = NULL;
+	const char **list = NULL;
+	gsize i;
 
 	if (   no_auto_default_file
-	    && g_file_get_contents (no_auto_default_file, &data, NULL, NULL)) {
-		list = g_strsplit (data, "\n", -1);
-		for (i = 0; list[i]; i++) {
-			if (   *list[i]
-			    && nm_utils_hwaddr_valid (list[i], -1)
-			    && nm_utils_strv_find_first (list, i, list[i]) < 0)
-				g_ptr_array_add (no_auto_default_new, list[i]);
-			else
-				g_free (list[i]);
-		}
-		g_free (list);
-		g_free (data);
+	    && g_file_get_contents (no_auto_default_file, &data, NULL, NULL))
+		list = nm_utils_strsplit_set (data, "\n");
+
+	if (list) {
+		for (i = 0; list[i]; i++)
+			list[i] = nm_utils_str_utf8safe_unescape_cp (list[i]);
 	}
 
-	g_ptr_array_add (no_auto_default_new, NULL);
-	return (char **) g_ptr_array_free (no_auto_default_new, FALSE);
+	/* The returned buffer here is not at all compact. That means, it has additional
+	 * memory allocations and is larger than needed. That means, you should not keep
+	 * this result around, only process it further and free it. */
+	return (char **) list;
 }
 
 static gboolean
 no_auto_default_to_file (const char *no_auto_default_file, const char *const*no_auto_default, GError **error)
 {
-	GString *data;
-	gboolean success;
-	guint i;
+	nm_auto_free_gstring GString *data = NULL;
+	gsize i;
 
 	data = g_string_new ("");
 	for (i = 0; no_auto_default && no_auto_default[i]; i++) {
-		g_string_append (data, no_auto_default[i]);
+		gs_free char *s_to_free = NULL;
+		const char *s = no_auto_default[i];
+
+		s = nm_utils_str_utf8safe_escape (s,
+		                                    NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
+		                                  | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
+		                                  &s_to_free);
+		g_string_append (data, s);
 		g_string_append_c (data, '\n');
 	}
-	success = g_file_set_contents (no_auto_default_file, data->str, data->len, error);
-	g_string_free (data, TRUE);
-	return success;
+	return  g_file_set_contents (no_auto_default_file, data->str, data->len, error);
 }
 
 gboolean
@@ -424,44 +410,76 @@ nm_config_set_no_auto_default_for_device (NMConfig *self, NMDevice *device)
 	NMConfigPrivate *priv;
 	GError *error = NULL;
 	NMConfigData *new_data = NULL;
+	gs_free char *spec_to_free = NULL;
+	const char *ifname;
 	const char *hw_address;
+	const char *spec;
 	const char *const*no_auto_default_current;
-	GPtrArray *no_auto_default_new = NULL;
-	guint i;
+	gs_free const char **no_auto_default_new = NULL;
+	gboolean is_fake;
+	gsize len;
+	gssize idx;
 
 	g_return_if_fail (NM_IS_CONFIG (self));
 	g_return_if_fail (NM_IS_DEVICE (device));
 
 	priv = NM_CONFIG_GET_PRIVATE (self);
 
-	hw_address = nm_device_get_permanent_hw_address (device);
-	if (!hw_address)
+	hw_address = nm_device_get_permanent_hw_address_full (device, TRUE, &is_fake);
+
+	if (!hw_address) {
+		/* No MAC address, not even a fake one. We don't do anything for this device. */
 		return;
+	}
+
+	if (is_fake) {
+		/* A fake MAC address, no point in storing it to the file.
+		 * Also, nm_match_spec_device() would ignore fake MAC addresses.
+		 *
+		 * Instead, try the interface-name...  */
+		ifname = nm_device_get_ip_iface (device);
+		if (!nm_utils_is_valid_iface_name (ifname, NULL))
+			return;
+
+		spec_to_free = g_strdup_printf (NM_MATCH_SPEC_INTERFACE_NAME_TAG"=%s", ifname);
+		spec = spec_to_free;
+	} else
+		spec = hw_address;
 
 	no_auto_default_current = nm_config_data_get_no_auto_default (priv->config_data);
 
-	if (nm_utils_strv_find_first ((char **) no_auto_default_current, -1, hw_address) >= 0) {
-		/* @hw_address is already blocked. We don't have to update our in-memory representation.
+	len = NM_PTRARRAY_LEN (no_auto_default_current);
+
+	idx = nm_utils_ptrarray_find_binary_search ((gconstpointer *) no_auto_default_current,
+	                                            len,
+	                                            spec,
+	                                            nm_strcmp_with_data,
+	                                            NULL,
+	                                            NULL,
+	                                            NULL);
+	if (idx >= 0) {
+		/* @spec is already blocked. We don't have to update our in-memory representation.
 		 * Maybe we should write to no_auto_default_file anew, but let's save that too. */
 		return;
 	}
 
-	no_auto_default_new = g_ptr_array_new ();
-	for (i = 0; no_auto_default_current && no_auto_default_current[i]; i++)
-		g_ptr_array_add (no_auto_default_new, (char *) no_auto_default_current[i]);
-	g_ptr_array_add (no_auto_default_new, (char *) hw_address);
-	g_ptr_array_add (no_auto_default_new, NULL);
+	idx = ~idx;
+
+	no_auto_default_new = g_new (const char *, len + 2);
+	if (idx > 0)
+		memcpy (no_auto_default_new, no_auto_default_current, sizeof (const char *) * idx);
+	no_auto_default_new[idx] = spec;
+	if (idx < len)
+		memcpy (&no_auto_default_new[idx + 1], &no_auto_default_current[idx], sizeof (const char *) * (len - idx));
+	no_auto_default_new[len + 1] = NULL;
 
-	if (!no_auto_default_to_file (priv->no_auto_default_file, (const char *const*) no_auto_default_new->pdata, &error)) {
+	if (!no_auto_default_to_file (priv->no_auto_default_file, no_auto_default_new, &error)) {
 		_LOGW ("Could not update no-auto-default.state file: %s",
 		       error->message);
 		g_error_free (error);
 	}
 
-	new_data = nm_config_data_new_update_no_auto_default (priv->config_data, (const char *const*) no_auto_default_new->pdata);
-
-	/* unref no_auto_default_set here. Note that _set_config_data() probably invalidates the content of the array. */
-	g_ptr_array_unref (no_auto_default_new);
+	new_data = nm_config_data_new_update_no_auto_default (priv->config_data, no_auto_default_new);
 
 	_set_config_data (self, new_data, NM_CONFIG_CHANGE_CAUSE_NO_AUTO_DEFAULT);
 }
@@ -2187,6 +2205,7 @@ _nm_config_state_set (NMConfig *self,
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED   "route-metric-default-aspired"
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE "route-metric-default-effective"
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROOT_PATH           "root-path"
+#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NEXT_SERVER         "next-server"
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
@@ -2385,6 +2404,7 @@ nm_config_device_state_write (int ifindex,
                               int nm_owned,
                               guint32 route_metric_default_aspired,
                               guint32 route_metric_default_effective,
+                              const char *next_server,
                               const char *root_path)
 {
 	char path[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR) + 60];
@@ -2439,6 +2459,12 @@ nm_config_device_state_write (int ifindex,
 			                      route_metric_default_aspired);
 		}
 	}
+	if (next_server) {
+		g_key_file_set_string (kf,
+		                       DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+		                       DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NEXT_SERVER,
+		                       next_server);
+	}
 	if (root_path) {
 		g_key_file_set_string (kf,
 		                       DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
@@ -2451,13 +2477,14 @@ nm_config_device_state_write (int ifindex,
 		g_error_free (local);
 		return FALSE;
 	}
-	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT"-%"G_GUINT32_FORMAT"%s%s%s",
+	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT"-%"G_GUINT32_FORMAT"%s%s%s%s%s%s",
 	       ifindex, path,
 	       _device_state_managed_type_to_str (managed),
 	       NM_PRINT_FMT_QUOTED (connection_uuid, ", connection-uuid=", connection_uuid, "", ""),
 	       NM_PRINT_FMT_QUOTED (perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""),
 	       route_metric_default_aspired,
 	       route_metric_default_effective,
+	       NM_PRINT_FMT_QUOTED (next_server, ", next-server=", next_server, "", ""),
 	       NM_PRINT_FMT_QUOTED (root_path, ", root-path=", root_path, "", ""));
 	return TRUE;
 }
@@ -2709,7 +2736,8 @@ nm_config_setup (const NMConfigCmdLineOptions *cli, char **atomic_section_prefix
 
 		/* usually, you would not see this logging line because when creating the
 		 * NMConfig instance, the logging is not yet set up to print debug message. */
-		nm_log_dbg (LOGD_CORE, "setup %s singleton (%p)", "NMConfig", singleton_instance);
+		nm_log_dbg (LOGD_CORE, "setup %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")",
+		            "NMConfig", NM_HASH_OBFUSCATE_PTR (singleton_instance));
 	}
 	return singleton_instance;
 }
@@ -2803,10 +2831,6 @@ init_sync (GInitable *initable, GCancellable *cancellable, GError **error)
 	else
 		priv->no_auto_default_file = g_strdup (DEFAULT_NO_AUTO_DEFAULT_FILE);
 
-	priv->monitor_connection_files = nm_config_keyfile_get_boolean (keyfile,
-	                                                                NM_CONFIG_KEYFILE_GROUP_MAIN,
-	                                                                NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
-	                                                                FALSE);
 	priv->log_level = nm_strstrip (g_key_file_get_string (keyfile,
 	                                                      NM_CONFIG_KEYFILE_GROUP_LOGGING,
 	                                                      NM_CONFIG_KEYFILE_KEY_LOGGING_LEVEL,
diff --git a/src/nm-config.h b/src/nm-config.h
index 66f1b69c..32ce236f 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -157,7 +156,6 @@ NMConfigData *nm_config_get_data_orig (NMConfig *config);
 #define NM_CONFIG_GET_DATA      (nm_config_get_data (nm_config_get ()))
 #define NM_CONFIG_GET_DATA_ORIG (nm_config_get_data_orig (nm_config_get ()))
 
-gboolean nm_config_get_monitor_connection_files (NMConfig *config);
 const char *nm_config_get_log_level (NMConfig *config);
 const char *nm_config_get_log_domains (NMConfig *config);
 NMConfigConfigureAndQuitType nm_config_get_configure_and_quit (NMConfig *config);
@@ -270,6 +268,7 @@ gboolean nm_config_device_state_write (int ifindex,
                                        int nm_owned,
                                        guint32 route_metric_default_aspired,
                                        guint32 route_metric_default_effective,
+                                       const char *next_server,
                                        const char *root_path);
 
 void nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes);
diff --git a/src/nm-connectivity.c b/src/nm-connectivity.c
index 2816e76a..694733f6 100644
--- a/src/nm-connectivity.c
+++ b/src/nm-connectivity.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -27,6 +26,7 @@
 #if WITH_CONCHECK
 #include <curl/curl.h>
 #endif
+#include <linux/rtnetlink.h>
 
 #include "c-list/src/c-list.h"
 #include "nm-core-internal.h"
@@ -104,8 +104,7 @@ struct _NMConnectivityCheckHandle {
 	guint timeout_id;
 
 	NMConnectivityState completed_state;
-
-	bool fail_reason_no_dbus_connection:1;
+	const char *completed_reason;
 };
 
 enum {
@@ -657,23 +656,10 @@ _idle_cb (gpointer user_data)
 
 	nm_assert (NM_IS_CONNECTIVITY (cb_data->self));
 	nm_assert (c_list_contains (&NM_CONNECTIVITY_GET_PRIVATE (cb_data->self)->handles_lst_head, &cb_data->handles_lst));
+	nm_assert (cb_data->completed_reason);
 
 	cb_data->timeout_id = 0;
-	if (!cb_data->ifspec) {
-		gs_free_error GError *error = NULL;
-
-		/* the invocation was with an invalid ifname. It is a fail. */
-		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
-		             "no interface specified for connectivity check");
-		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "missing interface");
-	} else if (cb_data->fail_reason_no_dbus_connection) {
-		gs_free_error GError *error = NULL;
-
-		g_set_error (&error, NM_UTILS_ERROR, NM_UTILS_ERROR_INVALID_ARGUMENT,
-		             "no D-Bus connection");
-		cb_data_complete (cb_data, NM_CONNECTIVITY_ERROR, "no D-Bus connection");
-	} else
-		cb_data_complete (cb_data, NM_CONNECTIVITY_FAKE, "fake result");
+	cb_data_complete (cb_data, cb_data->completed_state, cb_data->completed_reason);
 	return G_SOURCE_REMOVE;
 }
 
@@ -800,9 +786,78 @@ resolve_cb (GObject *object, GAsyncResult *res, gpointer user_data)
 
 #define SD_RESOLVED_DNS ((guint64) (1LL << 0))
 
+static NMConnectivityState
+check_platform_config (NMConnectivity *self,
+                       NMPlatform *platform,
+                       int ifindex,
+                       int addr_family,
+                       const char **reason)
+{
+	const NMDedupMultiHeadEntry *addresses;
+	const NMDedupMultiHeadEntry *routes;
+
+	if (!nm_platform_link_is_connected (platform, ifindex)) {
+		NM_SET_OUT (reason, "no carrier");
+		return NM_CONNECTIVITY_NONE;
+	}
+
+	addresses = nm_platform_lookup_object (platform,
+	                                         addr_family == AF_INET
+	                                       ? NMP_OBJECT_TYPE_IP4_ADDRESS
+	                                       : NMP_OBJECT_TYPE_IP6_ADDRESS,
+	                                       ifindex);
+	if (!addresses || addresses->len == 0) {
+		NM_SET_OUT (reason, "no IP address configured");
+		return NM_CONNECTIVITY_NONE;
+	}
+
+	routes = nm_platform_lookup_object (platform,
+	                                      addr_family == AF_INET
+	                                    ? NMP_OBJECT_TYPE_IP4_ROUTE
+	                                    : NMP_OBJECT_TYPE_IP6_ROUTE,
+	                                    ifindex);
+	if (!routes || routes->len == 0) {
+		NM_SET_OUT (reason, "no IP route configured");
+		return NM_CONNECTIVITY_NONE;
+	}
+
+	switch (addr_family) {
+	case AF_INET: {
+		const NMPlatformIP4Route *route;
+		gboolean found_global = FALSE;
+		NMDedupMultiIter iter;
+		const NMPObject *plobj;
+
+		/* For IPv4 also require a route with global scope. */
+		nmp_cache_iter_for_each (&iter, routes, &plobj) {
+			route = NMP_OBJECT_CAST_IP4_ROUTE (plobj);
+			if (nm_platform_route_scope_inv (route->scope_inv) == RT_SCOPE_UNIVERSE) {
+				found_global = TRUE;
+				break;
+			}
+		}
+
+		if (!found_global) {
+			NM_SET_OUT (reason, "no global route configured");
+			return NM_CONNECTIVITY_LIMITED;
+		}
+		break;
+	}
+	case AF_INET6:
+		/* Route scopes aren't meaningful for IPv6 so any route is fine. */
+		break;
+	default:
+		g_return_val_if_reached (FALSE);
+	}
+
+	NM_SET_OUT (reason, NULL);
+	return NM_CONNECTIVITY_UNKNOWN;
+}
+
 NMConnectivityCheckHandle *
 nm_connectivity_check_start (NMConnectivity *self,
                              int addr_family,
+                             NMPlatform *platform,
                              int ifindex,
                              const char *iface,
                              NMConnectivityCheckCallback callback,
@@ -814,6 +869,7 @@ nm_connectivity_check_start (NMConnectivity *self,
 
 	g_return_val_if_fail (NM_IS_CONNECTIVITY (self), NULL);
 	g_return_val_if_fail (callback, NULL);
+	nm_assert (!platform || NM_IS_PLATFORM (platform));
 
 	priv = NM_CONNECTIVITY_GET_PRIVATE (self);
 
@@ -837,9 +893,27 @@ nm_connectivity_check_start (NMConnectivity *self,
 	    && priv->enabled
 	    && priv->uri_valid) {
 		gboolean has_systemd_resolved;
+		NMConnectivityState state;
+		const char *reason;
 
 		cb_data->concheck.ch_ifindex = ifindex;
 
+		if (platform) {
+			state = check_platform_config (self,
+			                               platform,
+			                               ifindex,
+			                               addr_family,
+			                               &reason);
+			nm_assert ((state == NM_CONNECTIVITY_UNKNOWN) == !reason);
+			if (state != NM_CONNECTIVITY_UNKNOWN) {
+				_LOG2D ("skip connectivity check due to %s", reason);
+				cb_data->completed_state = state;
+				cb_data->completed_reason = reason;
+				cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
+				return cb_data;
+			}
+		}
+
 		/* note that we pick up support for systemd-resolved right away when we need it.
 		 * We don't need to remember the setting, because we can (cheaply) check anew
 		 * on each request.
@@ -863,20 +937,21 @@ nm_connectivity_check_start (NMConnectivity *self,
 		if (has_systemd_resolved) {
 			GDBusConnection *dbus_connection;
 
-			dbus_connection = nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ());
+			dbus_connection = NM_MAIN_DBUS_CONNECTION_GET;
 			if (!dbus_connection) {
 				/* we have no D-Bus connection? That might happen in configure and quit mode.
 				 *
 				 * Anyway, something is very odd, just fail connectivity check. */
 				_LOG2D ("start fake request (fail due to no D-Bus connection)");
-				cb_data->fail_reason_no_dbus_connection = TRUE;
+				cb_data->completed_state = NM_CONNECTIVITY_ERROR;
+				cb_data->completed_reason = "no D-Bus connection";
 				cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
 				return cb_data;
 			}
 
 			cb_data->concheck.resolve_cancellable = g_cancellable_new ();
 
-			g_dbus_connection_call (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ()),
+			g_dbus_connection_call (dbus_connection,
 			                        "org.freedesktop.resolve1",
 			                        "/org/freedesktop/resolve1",
 			                        "org.freedesktop.resolve1.Manager",
@@ -905,7 +980,14 @@ nm_connectivity_check_start (NMConnectivity *self,
 	}
 #endif
 
-	_LOG2D ("start fake request");
+	if (!cb_data->ifspec) {
+		cb_data->completed_state = NM_CONNECTIVITY_ERROR;
+		cb_data->completed_reason = "missing interface";
+	} else {
+		cb_data->completed_state = NM_CONNECTIVITY_FAKE;
+		cb_data->completed_reason = "fake result";
+	}
+	_LOG2D ("start fake request (%s)", cb_data->completed_reason);
 	cb_data->timeout_id = g_idle_add (_idle_cb, cb_data);
 
 	return cb_data;
diff --git a/src/nm-connectivity.h b/src/nm-connectivity.h
index f262298a..00d0e642 100644
--- a/src/nm-connectivity.h
+++ b/src/nm-connectivity.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -74,6 +73,7 @@ typedef void (*NMConnectivityCheckCallback) (NMConnectivity *self,
 
 NMConnectivityCheckHandle *nm_connectivity_check_start (NMConnectivity *self,
                                                         int family,
+                                                        NMPlatform *platform,
                                                         int ifindex,
                                                         const char *iface,
                                                         NMConnectivityCheckCallback callback,
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index b0cc914e..d896d4d3 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -41,6 +40,7 @@
 #include "nm-glib-aux/nm-random-utils.h"
 #include "nm-glib-aux/nm-io-utils.h"
 #include "nm-glib-aux/nm-secret-utils.h"
+#include "nm-glib-aux/nm-time-utils.h"
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 #include "nm-setting-connection.h"
@@ -127,6 +127,8 @@ static void
 _nm_singleton_instance_weak_cb (gpointer data,
                                 GObject *where_the_object_was)
 {
+	nm_assert (g_slist_find (_singletons, where_the_object_was));
+
 	_singletons = g_slist_remove (_singletons, where_the_object_was);
 }
 
@@ -142,8 +144,10 @@ _nm_singleton_instance_destroy (void)
 
 		g_object_weak_unref (instance, _nm_singleton_instance_weak_cb, NULL);
 
-		if (instance->ref_count > 1)
-			nm_log_dbg (LOGD_CORE, "disown %s singleton (%p)", G_OBJECT_TYPE_NAME (instance), instance);
+		if (instance->ref_count > 1) {
+			nm_log_dbg (LOGD_CORE, "disown %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")",
+			            G_OBJECT_TYPE_NAME (instance), NM_HASH_OBFUSCATE_PTR (instance));
+		}
 
 		g_object_unref (instance);
 	}
@@ -1154,13 +1158,10 @@ nm_utils_read_link_absolute (const char *link_file, GError **error)
 
 /*****************************************************************************/
 
-#define MAC_TAG "mac:"
-#define INTERFACE_NAME_TAG "interface-name:"
-#define DEVICE_TYPE_TAG "type:"
-#define DRIVER_TAG "driver:"
-#define SUBCHAN_TAG "s390-subchannels:"
-#define DHCP_PLUGIN_TAG "dhcp-plugin:"
-#define EXCEPT_TAG "except:"
+#define DEVICE_TYPE_TAG                         "type:"
+#define DRIVER_TAG                              "driver:"
+#define DHCP_PLUGIN_TAG                         "dhcp-plugin:"
+#define EXCEPT_TAG                              "except:"
 #define MATCH_TAG_CONFIG_NM_VERSION             "nm-version:"
 #define MATCH_TAG_CONFIG_NM_VERSION_MIN         "nm-version-min:"
 #define MATCH_TAG_CONFIG_NM_VERSION_MAX         "nm-version-max:"
@@ -1359,10 +1360,10 @@ match_device_eval (const char *spec_str,
 		       && nm_streq (spec_str, match_data->device_type);
 	}
 
-	if (_MATCH_CHECK (spec_str, MAC_TAG))
+	if (_MATCH_CHECK (spec_str, NM_MATCH_SPEC_MAC_TAG))
 		return match_device_hwaddr_eval (spec_str, match_data);
 
-	if (_MATCH_CHECK (spec_str, INTERFACE_NAME_TAG)) {
+	if (_MATCH_CHECK (spec_str, NM_MATCH_SPEC_INTERFACE_NAME_TAG)) {
 		gboolean use_pattern = FALSE;
 
 		if (spec_str[0] == '=')
@@ -1414,7 +1415,7 @@ match_device_eval (const char *spec_str,
 		                                  match_data->driver_version ?: "");
 	}
 
-	if (_MATCH_CHECK (spec_str, SUBCHAN_TAG))
+	if (_MATCH_CHECK (spec_str, NM_MATCH_SPEC_S390_SUBCHANNELS_TAG))
 		return match_data_s390_subchannels_eval (spec_str, match_data);
 
 	if (_MATCH_CHECK (spec_str, DHCP_PLUGIN_TAG))
@@ -2543,7 +2544,7 @@ _host_id_read_timestamp (gboolean use_secret_key_file,
 	    && stat (SECRET_KEY_FILE, &st) == 0) {
 		/* don't check for overflow or timestamps in the future. We get whatever
 		 * (bogus) date is on the file. */
-		*out_timestamp_ns = (st.st_mtim.tv_sec * NM_UTILS_NS_PER_SECOND) + st.st_mtim.tv_nsec;
+		*out_timestamp_ns = nm_utils_timespec_to_ns (&st.st_mtim);
 		return TRUE;
 	}
 
@@ -2697,7 +2698,7 @@ _host_id_read (guint8 **out_host_id,
 		} else if (!nm_utils_file_set_contents (SECRET_KEY_FILE,
 		                                        (const char *) new_content,
 		                                        len,
-		                                        0077,
+		                                        0600,
 		                                        &error)) {
 			nm_log_warn (LOGD_CORE, "secret-key: failure to persist secret key in \"%s\" (%s) (use non-persistent key)",
 			             SECRET_KEY_FILE, error->message);
@@ -3813,13 +3814,22 @@ nm_utils_parse_debug_string (const char *string,
 void
 nm_utils_ifname_cpy (char *dst, const char *name)
 {
+	int i;
+
 	g_return_if_fail (dst);
 	g_return_if_fail (name && name[0]);
 
 	nm_assert (nm_utils_is_valid_iface_name (name, NULL));
 
-	if (g_strlcpy (dst, name, IFNAMSIZ) >= IFNAMSIZ)
-		g_return_if_reached ();
+	/* ensures NUL padding of the entire IFNAMSIZ buffer. */
+
+	for (i = 0; i < (int) IFNAMSIZ && name[i] != '\0'; i++)
+		dst[i] = name[i];
+
+	nm_assert (name[i] == '\0');
+
+	for (; i < (int) IFNAMSIZ; i++)
+		dst[i] = '\0';
 }
 
 /*****************************************************************************/
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index 1f9996ca..855a5d08 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -42,7 +41,9 @@ static void \
 _singleton_instance_weak_ref_cb (gpointer data, \
                                  GObject *where_the_object_was) \
 { \
-	nm_log_dbg (LOGD_CORE, "disposing %s singleton (%p)", G_STRINGIFY (TYPE), singleton_instance); \
+	nm_log_dbg (LOGD_CORE, "disposing %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")", \
+	            G_STRINGIFY (TYPE), \
+	            NM_HASH_OBFUSCATE_PTR (singleton_instance)); \
 	singleton_instance = NULL; \
 } \
 static inline void \
@@ -73,7 +74,9 @@ GETTER (void) \
 		singleton_instance = (g_object_new (GTYPE, ##__VA_ARGS__, NULL)); \
 		g_assert (singleton_instance); \
 		nm_singleton_instance_register (); \
-		nm_log_dbg (LOGD_CORE, "create %s singleton (%p)", G_STRINGIFY (TYPE), singleton_instance); \
+		nm_log_dbg (LOGD_CORE, "create %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")", \
+		            G_STRINGIFY (TYPE), \
+		            NM_HASH_OBFUSCATE_PTR (singleton_instance)); \
 	} \
 	return singleton_instance; \
 } \
@@ -213,6 +216,10 @@ const char *nm_utils_find_helper (const char *progname,
 
 char *nm_utils_read_link_absolute (const char *link_file, GError **error);
 
+#define NM_MATCH_SPEC_MAC_TAG                    "mac:"
+#define NM_MATCH_SPEC_S390_SUBCHANNELS_TAG       "s390-subchannels:"
+#define NM_MATCH_SPEC_INTERFACE_NAME_TAG         "interface-name:"
+
 typedef enum {
 	NM_MATCH_SPEC_NO_MATCH  = 0,
 	NM_MATCH_SPEC_MATCH     = 1,
diff --git a/src/nm-dbus-manager.c b/src/nm-dbus-manager.c
index 1a455c42..a14ea120 100644
--- a/src/nm-dbus-manager.c
+++ b/src/nm-dbus-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -471,6 +470,9 @@ _bus_get_unix_pid (NMDBusManager *self,
 	guint32 unix_pid = G_MAXUINT32;
 	gs_unref_variant GVariant *ret = NULL;
 
+	if (!priv->main_dbus_connection)
+		return FALSE;
+
 	ret = g_dbus_connection_call_sync (priv->main_dbus_connection,
 	                                   DBUS_SERVICE_DBUS,
 	                                   DBUS_PATH_DBUS,
@@ -500,6 +502,9 @@ _bus_get_unix_user (NMDBusManager *self,
 	guint32 unix_uid = G_MAXUINT32;
 	gs_unref_variant GVariant *ret = NULL;
 
+	if (!priv->main_dbus_connection)
+		return FALSE;
+
 	ret = g_dbus_connection_call_sync (priv->main_dbus_connection,
 	                                   DBUS_SERVICE_DBUS,
 	                                   DBUS_PATH_DBUS,
@@ -1024,6 +1029,7 @@ _obj_register (NMDBusManager *self,
 
 	nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
 	nm_assert (priv->main_dbus_connection);
+	nm_assert (priv->objmgr_registration_id != 0);
 	nm_assert (priv->started);
 
 	n_klasses = 0;
@@ -1119,15 +1125,10 @@ _obj_unregister (NMDBusManager *self,
 	GVariantBuilder builder;
 
 	nm_assert (NM_IS_DBUS_OBJECT (obj));
-
-	if (!priv->main_dbus_connection) {
-		/* nothing to do for the moment. */
-		nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
-		return;
-	}
-
+	nm_assert (priv->main_dbus_connection);
+	nm_assert (priv->objmgr_registration_id != 0);
+	nm_assert (priv->started);
 	nm_assert (!c_list_is_empty (&obj->internal.registration_lst_head));
-	nm_assert (priv->objmgr_registration_id);
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
 
@@ -1202,7 +1203,7 @@ _nm_dbus_manager_obj_export (NMDBusObject *obj)
 		nm_assert_not_reached ();
 	c_list_link_tail (&priv->objects_lst_head, &obj->internal.objects_lst);
 
-	if (priv->main_dbus_connection && priv->started)
+	if (priv->started)
 		_obj_register (self, obj);
 }
 
@@ -1223,7 +1224,10 @@ _nm_dbus_manager_obj_unexport (NMDBusObject *obj)
 	nm_assert (&obj->internal == g_hash_table_lookup (priv->objects_by_path, &obj->internal));
 	nm_assert (c_list_contains (&priv->objects_lst_head, &obj->internal.objects_lst));
 
-	_obj_unregister (self, obj);
+	if (priv->started)
+		_obj_unregister (self, obj);
+	else
+		nm_assert (c_list_is_empty (&obj->internal.registration_lst_head));
 
 	if (!g_hash_table_remove (priv->objects_by_path, &obj->internal))
 		nm_assert_not_reached ();
@@ -1249,6 +1253,16 @@ _nm_dbus_manager_obj_notify (NMDBusObject *obj,
 	nm_assert (NM_IS_DBUS_MANAGER (obj->internal.bus_manager));
 	nm_assert (!c_list_is_empty (&obj->internal.objects_lst));
 
+	self = obj->internal.bus_manager;
+	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
+
+	nm_assert (!priv->started || priv->objmgr_registration_id != 0);
+	nm_assert (priv->objmgr_registration_id == 0 || priv->main_dbus_connection);
+	nm_assert (c_list_is_empty (&obj->internal.registration_lst_head) != priv->started);
+
+	if (G_UNLIKELY (!priv->started))
+		return;
+
 	c_list_for_each_entry (reg_data, &obj->internal.registration_lst_head, registration_lst) {
 		if (_reg_data_get_interface_info (reg_data)->legacy_property_changed) {
 			any_legacy_signals = TRUE;
@@ -1256,9 +1270,6 @@ _nm_dbus_manager_obj_notify (NMDBusObject *obj,
 		}
 	}
 
-	self = obj->internal.bus_manager;
-	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
-
 	/* do a naive search for the matching NMDBusPropertyInfoExtended infos. Since the number of
 	 * (interfaces x properties) is static and possibly small, this naive search is effectively
 	 * O(1). We might wanna introduce some index to lookup the properties in question faster.
@@ -1401,7 +1412,7 @@ _nm_dbus_manager_obj_emit_signal (NMDBusObject *obj,
 	self = obj->internal.bus_manager;
 	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
 
-	if (!priv->main_dbus_connection || !priv->started) {
+	if (!priv->started) {
 		nm_g_variant_unref_floating (args);
 		return;
 	}
@@ -1565,9 +1576,12 @@ nm_dbus_manager_start (NMDBusManager *self,
 	NMDBusObject *obj;
 
 	g_return_if_fail (NM_IS_DBUS_MANAGER (self));
+
 	priv = NM_DBUS_MANAGER_GET_PRIVATE (self);
 
-	if (!priv->main_dbus_connection) {
+	nm_assert (!priv->started);
+
+	if (priv->objmgr_registration_id == 0) {
 		/* Do nothing. We're presumably in the configure-and-quit mode. */
 		return;
 	}
@@ -1581,12 +1595,12 @@ nm_dbus_manager_start (NMDBusManager *self,
 }
 
 gboolean
-nm_dbus_manager_acquire_bus (NMDBusManager *self)
+nm_dbus_manager_acquire_bus (NMDBusManager *self,
+                             gboolean request_name)
 {
 	NMDBusManagerPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *ret = NULL;
-	gs_unref_object GDBusConnection *connection = NULL;
 	guint32 result;
 	guint registration_id;
 
@@ -1599,17 +1613,22 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	 * acquire the name despite connecting to the bus successfully.
 	 * It means that something is gravely broken -- such as another NetworkManager
 	 * instance running. */
-	connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
-	                             NULL,
-	                             &error);
-	if (!connection) {
-		_LOGI ("cannot connect to D-Bus: %s", error->message);
+	priv->main_dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
+	                                             NULL,
+	                                             &error);
+	if (!priv->main_dbus_connection) {
+		_LOGE ("cannot connect to D-Bus: %s", error->message);
 		return FALSE;
 	}
 
-	g_dbus_connection_set_exit_on_close (connection, FALSE);
+	g_dbus_connection_set_exit_on_close (priv->main_dbus_connection, FALSE);
+
+	if (!request_name) {
+		_LOGD ("D-Bus connection created");
+		return TRUE;
+	}
 
-	registration_id = g_dbus_connection_register_object (connection,
+	registration_id = g_dbus_connection_register_object (priv->main_dbus_connection,
 	                                                     OBJECT_MANAGER_SERVER_BASE_PATH,
 	                                                     NM_UNCONST_PTR (GDBusInterfaceInfo, &interface_info_objmgr),
 	                                                     &dbus_vtable_objmgr,
@@ -1621,7 +1640,7 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 		return FALSE;
 	}
 
-	ret = g_dbus_connection_call_sync (connection,
+	ret = g_dbus_connection_call_sync (priv->main_dbus_connection,
 	                                   DBUS_SERVICE_DBUS,
 	                                   DBUS_PATH_DBUS,
 	                                   DBUS_INTERFACE_DBUS,
@@ -1634,13 +1653,10 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	                                   -1,
 	                                   NULL,
 	                                   &error);
-	if (!ret)
-		return FALSE;
-
 	if (!ret) {
 		_LOGE ("fatal failure to acquire D-Bus service \"%s"": %s",
 		       NM_DBUS_SERVICE, error->message);
-		g_dbus_connection_unregister_object(connection, registration_id);
+		g_dbus_connection_unregister_object (priv->main_dbus_connection, registration_id);
 		return FALSE;
 	}
 
@@ -1648,12 +1664,11 @@ nm_dbus_manager_acquire_bus (NMDBusManager *self)
 	if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
 		_LOGE ("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken",
 		       NM_DBUS_SERVICE, (guint) result);
-		g_dbus_connection_unregister_object(connection, registration_id);
+		g_dbus_connection_unregister_object (priv->main_dbus_connection, registration_id);
 		return FALSE;
 	}
 
 	priv->objmgr_registration_id = registration_id;
-	priv->main_dbus_connection = g_steal_pointer (&connection);
 
 	_LOGI ("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE);
 
@@ -1690,6 +1705,7 @@ nm_dbus_manager_init (NMDBusManager *self)
 
 	c_list_init (&priv->private_servers_lst_head);
 	c_list_init (&priv->objects_lst_head);
+
 	priv->objects_by_path = g_hash_table_new ((GHashFunc) _objects_by_path_hash, (GEqualFunc) _objects_by_path_equal);
 
 	c_list_init (&priv->caller_info_lst_head);
diff --git a/src/nm-dbus-manager.h b/src/nm-dbus-manager.h
index 2de6ff0b..c4a99563 100644
--- a/src/nm-dbus-manager.h
+++ b/src/nm-dbus-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -49,10 +48,13 @@ typedef void (*NMDBusManagerSetPropertyHandler) (NMDBusObject *obj,
                                                  GVariant *value,
                                                  gpointer user_data);
 
-gboolean nm_dbus_manager_acquire_bus (NMDBusManager *self);
+gboolean nm_dbus_manager_acquire_bus (NMDBusManager *self,
+                                      gboolean request_name);
 
 GDBusConnection *nm_dbus_manager_get_dbus_connection (NMDBusManager *self);
 
+#define NM_MAIN_DBUS_CONNECTION_GET (nm_dbus_manager_get_dbus_connection (nm_dbus_manager_get ()))
+
 void nm_dbus_manager_start (NMDBusManager *self,
                             NMDBusManagerSetPropertyHandler set_property_handler,
                             gpointer set_property_handler_data);
diff --git a/src/nm-dbus-object.c b/src/nm-dbus-object.c
index e92a8f5f..abf1aa57 100644
--- a/src/nm-dbus-object.c
+++ b/src/nm-dbus-object.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dbus-object.h b/src/nm-dbus-object.h
index 8b3ffdbd..574107a1 100644
--- a/src/nm-dbus-object.h
+++ b/src/nm-dbus-object.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dbus-utils.c b/src/nm-dbus-utils.c
index 7b9c56b6..fe3eff7a 100644
--- a/src/nm-dbus-utils.c
+++ b/src/nm-dbus-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dbus-utils.h b/src/nm-dbus-utils.h
index 1f8e96c4..cf372e88 100644
--- a/src/nm-dbus-utils.h
+++ b/src/nm-dbus-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dcb.c b/src/nm-dcb.c
index fdcdd8dc..d6a9946b 100644
--- a/src/nm-dcb.c
+++ b/src/nm-dcb.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dcb.h b/src/nm-dcb.h
index 8bc08470..425e3e23 100644
--- a/src/nm-dcb.h
+++ b/src/nm-dcb.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dhcp4-config.c b/src/nm-dhcp4-config.c
index fe0df3a7..8390a654 100644
--- a/src/nm-dhcp4-config.c
+++ b/src/nm-dhcp4-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dhcp4-config.h b/src/nm-dhcp4-config.h
index 7fdf0653..9853f4ff 100644
--- a/src/nm-dhcp4-config.h
+++ b/src/nm-dhcp4-config.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dhcp6-config.c b/src/nm-dhcp6-config.c
index 59266d55..5bbc20ef 100644
--- a/src/nm-dhcp6-config.c
+++ b/src/nm-dhcp6-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dhcp6-config.h b/src/nm-dhcp6-config.h
index 6241845a..ffe7b18d 100644
--- a/src/nm-dhcp6-config.h
+++ b/src/nm-dhcp6-config.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-dispatcher.c b/src/nm-dispatcher.c
index d656edc8..652b9838 100644
--- a/src/nm-dispatcher.c
+++ b/src/nm-dispatcher.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -15,7 +14,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 - 2017 Red Hat, Inc.
+ * Copyright (C) 2004 - 2018 Red Hat, Inc.
  * Copyright (C) 2005 - 2008 Novell, Inc.
  */
 
@@ -44,45 +43,122 @@
 #define _NMLOG_DOMAIN      LOGD_DISPATCH
 #define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "dispatcher", __VA_ARGS__)
 
-static GDBusProxy *dispatcher_proxy;
-static GHashTable *requests = NULL;
-
-typedef struct {
-	GFileMonitor *monitor;
-	const char *const description;
-	const char *const dir;
-	const guint16 dir_len;
-	char has_scripts;
-} Monitor;
-
-enum {
-	MONITOR_INDEX_DEFAULT,
-	MONITOR_INDEX_PRE_UP,
-	MONITOR_INDEX_PRE_DOWN,
+#define _NMLOG2_DOMAIN     LOGD_DISPATCH
+#define _NMLOG2(level, request_id, log_ifname, log_con_uuid, ...) \
+    nm_log ((level), \
+            _NMLOG2_DOMAIN, \
+            (log_ifname), \
+            (log_con_uuid), \
+            "dispatcher: (%u) " \
+            _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
+            (request_id) \
+            _NM_UTILS_MACRO_REST (__VA_ARGS__))
+
+#define _NMLOG3_DOMAIN     LOGD_DISPATCH
+#define _NMLOG3(level, call_id, ...) \
+	G_STMT_START { \
+		const NMDispatcherCallId *const _call_id = (call_id); \
+		\
+		_NMLOG2 (level, _call_id->request_id, _call_id->log_ifname, _call_id->log_con_uuid, __VA_ARGS__); \
+	} G_STMT_END
+
+/*****************************************************************************/
+
+struct NMDispatcherCallId {
+	NMDispatcherFunc callback;
+	gpointer user_data;
+	const char *log_ifname;
+	const char *log_con_uuid;
+	NMDispatcherAction action;
+	guint idle_id;
+	guint32 request_id;
+	char extra_strings[];
 };
 
-static Monitor monitors[3] = {
-#define MONITORS_INIT_SET(INDEX, USE, SCRIPT_DIR)   [INDEX] = { .dir_len = NM_STRLEN (SCRIPT_DIR), .dir = SCRIPT_DIR, .description = ("" USE), .has_scripts = TRUE }
-	MONITORS_INIT_SET (MONITOR_INDEX_DEFAULT,  "default",  NMD_SCRIPT_DIR_DEFAULT),
-	MONITORS_INIT_SET (MONITOR_INDEX_PRE_UP,   "pre-up",   NMD_SCRIPT_DIR_PRE_UP),
-	MONITORS_INIT_SET (MONITOR_INDEX_PRE_DOWN, "pre-down", NMD_SCRIPT_DIR_PRE_DOWN),
-};
+/*****************************************************************************/
 
-static const Monitor*
-_get_monitor_by_action (NMDispatcherAction action)
+/* FIXME(shutdown): on shutdown, we should not run dispatcher scripts synchronously.
+ *   Instead, we should of course still run them asynchronously.
+ *
+ *   Also, we should wait for all pending requests to complete before exiting the main-loop
+ *   (with a watchdog). If we hit a timeout, we log a warning and quit (but leave the scripts
+ *   running).
+ *
+ *   Finally, cleanup the global structures. */
+static struct {
+	GDBusConnection *dbus_connection;
+	GHashTable *requests;
+	guint request_id_counter;
+} gl;
+
+/*****************************************************************************/
+
+static NMDispatcherCallId *
+dispatcher_call_id_new (guint32 request_id,
+                        NMDispatcherAction action,
+                        NMDispatcherFunc callback,
+                        gpointer user_data,
+                        const char *log_ifname,
+                        const char *log_con_uuid)
 {
-	switch (action) {
-	case NM_DISPATCHER_ACTION_PRE_UP:
-	case NM_DISPATCHER_ACTION_VPN_PRE_UP:
-		return &monitors[MONITOR_INDEX_PRE_UP];
-	case NM_DISPATCHER_ACTION_PRE_DOWN:
-	case NM_DISPATCHER_ACTION_VPN_PRE_DOWN:
-		return &monitors[MONITOR_INDEX_PRE_DOWN];
-	default:
-		return &monitors[MONITOR_INDEX_DEFAULT];
+	NMDispatcherCallId *call_id;
+	gsize l_log_ifname;
+	gsize l_log_con_uuid;
+	char *extra_strings;
+
+	l_log_ifname   = log_ifname   ? (strlen (log_ifname)   + 1) : 0u;
+	l_log_con_uuid = log_con_uuid ? (strlen (log_con_uuid) + 1) : 0u;
+
+	call_id = g_malloc (sizeof (NMDispatcherCallId) + l_log_ifname + l_log_con_uuid);
+
+	call_id->action       = action;
+	call_id->request_id   = request_id;
+	call_id->callback     = callback;
+	call_id->user_data    = user_data;
+	call_id->idle_id      = 0;
+
+	extra_strings = &call_id->extra_strings[0];
+
+	if (log_ifname) {
+		call_id->log_ifname = extra_strings;
+		memcpy (extra_strings, log_ifname, l_log_ifname);
+		extra_strings += l_log_ifname;
+	} else
+		call_id->log_ifname = NULL;
+
+	if (log_con_uuid) {
+		call_id->log_con_uuid = extra_strings;
+		memcpy (extra_strings, log_con_uuid, l_log_con_uuid);
+	} else
+		call_id->log_con_uuid = NULL;
+
+	return call_id;
+}
+
+static void
+dispatcher_call_id_free (NMDispatcherCallId *call_id)
+{
+	nm_clear_g_source (&call_id->idle_id);
+	g_free (call_id);
+}
+
+/*****************************************************************************/
+
+
+static void
+_init_dispatcher (void)
+{
+	if (G_UNLIKELY (gl.requests == NULL)) {
+		gl.requests = g_hash_table_new (nm_direct_hash, NULL);
+		gl.dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
+
+		if (!gl.dbus_connection)
+			_LOGD ("No D-Bus connection to talk with NetworkManager-dispatcher service");
 	}
 }
 
+/*****************************************************************************/
+
 static void
 dump_proxy_to_props (NMProxyConfig *proxy, GVariantBuilder *builder)
 {
@@ -323,39 +399,6 @@ fill_vpn_props (NMProxyConfig *proxy_config,
 		dump_ip6_to_props (ip6_config, ip6_builder);
 }
 
-typedef struct {
-	NMDispatcherAction action;
-	guint request_id;
-	NMDispatcherFunc callback;
-	gpointer user_data;
-	guint idle_id;
-} DispatchInfo;
-
-static void
-dispatcher_info_free (DispatchInfo *info)
-{
-	if (info->idle_id)
-		g_source_remove (info->idle_id);
-	g_free (info);
-}
-
-static void
-_ensure_requests (void)
-{
-	if (G_UNLIKELY (requests == NULL)) {
-		requests = g_hash_table_new_full (nm_direct_hash,
-		                                  NULL,
-		                                  NULL,
-		                                  (GDestroyNotify) dispatcher_info_free);
-	}
-}
-
-static void
-dispatcher_info_cleanup (DispatchInfo *info)
-{
-	g_hash_table_remove (requests, GUINT_TO_POINTER (info->request_id));
-}
-
 static const char *
 dispatch_result_to_string (DispatchResult result)
 {
@@ -375,81 +418,71 @@ dispatch_result_to_string (DispatchResult result)
 }
 
 static void
-dispatcher_results_process (guint request_id, NMDispatcherAction action, GVariantIter *results)
+dispatcher_results_process (guint32 request_id,
+                            const char *log_ifname,
+                            const char *log_con_uuid,
+                            GVariant *v_results)
 {
+	nm_auto_free_variant_iter GVariantIter *results = NULL;
 	const char *script, *err;
 	guint32 result;
-	const Monitor *monitor = _get_monitor_by_action (action);
 
-	g_return_if_fail (results != NULL);
+	g_variant_get (v_results, "(a(sus))", &results);
 
 	if (g_variant_iter_n_children (results) == 0) {
-		_LOGD ("(%u) succeeded but no scripts invoked", request_id);
+		_LOG2D (request_id, log_ifname, log_con_uuid, "succeeded but no scripts invoked");
 		return;
 	}
 
 	while (g_variant_iter_next (results, "(&su&s)", &script, &result, &err)) {
-		const char *script_validation_msg = "";
-
-		if (!*script) {
-			script_validation_msg = " (path is NULL)";
-			script = "(unknown)";
-		} else if (!strncmp (script, monitor->dir, monitor->dir_len)            /* check: prefixed by script directory */
-		    && script[monitor->dir_len] == '/' && script[monitor->dir_len+1]    /* check: with additional "/?" */
-		    && !strchr (&script[monitor->dir_len+1], '/')) {                    /* check: and no further '/' */
-			/* we expect the script to lie inside monitor->dir. If it does,
-			 * strip the directory name. Otherwise show the full path and a warning. */
-			script += monitor->dir_len + 1;
-		} else
-			script_validation_msg = " (unexpected path)";
-
 		if (result == DISPATCH_RESULT_SUCCESS) {
-			_LOGD ("(%u) %s succeeded%s",
-			       request_id,
-			       script, script_validation_msg);
+			_LOG2D (request_id, log_ifname, log_con_uuid, "%s succeeded", script);
 		} else {
-			_LOGW ("(%u) %s failed (%s): %s%s",
-			       request_id,
-			       script,
-			       dispatch_result_to_string (result),
-			       err,
-			       script_validation_msg);
+			_LOG2W (request_id,
+			        log_ifname,
+			        log_con_uuid,
+			        "%s failed (%s): %s",
+			        script,
+			        dispatch_result_to_string (result),
+			        err);
 		}
 	}
 }
 
 static void
-dispatcher_done_cb (GObject *proxy, GAsyncResult *result, gpointer user_data)
+dispatcher_done_cb (GObject *source, GAsyncResult *result, gpointer user_data)
 {
-	DispatchInfo *info = user_data;
-	GVariant *ret;
-	GVariantIter *results;
-	GError *error = NULL;
-
-	ret = _nm_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result,
-	                                  G_VARIANT_TYPE ("(a(sus))"),
-	                                  &error);
-	if (ret) {
-		g_variant_get (ret, "(a(sus))", &results);
-		dispatcher_results_process (info->request_id, info->action, results);
-		g_variant_iter_free (results);
-		g_variant_unref (ret);
-	} else {
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	NMDispatcherCallId *call_id = user_data;
+
+	nm_assert ((gpointer) source == gl.dbus_connection);
+
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source),
+	                                     result,
+	                                     &error);
+	if (!ret) {
 		if (_nm_dbus_error_has_name (error, "org.freedesktop.systemd1.LoadFailed")) {
 			g_dbus_error_strip_remote_error (error);
-			_LOGW ("(%u) failed to call dispatcher scripts: %s",
-			       info->request_id, error->message);
+			_LOG3W (call_id, "failed to call dispatcher scripts: %s",
+			        error->message);
 		} else {
-			_LOGD ("(%u) failed to call dispatcher scripts: %s",
-			       info->request_id, error->message);
+			_LOG3D (call_id, "failed to call dispatcher scripts: %s",
+			        error->message);
 		}
-		g_clear_error (&error);
+	} else {
+		dispatcher_results_process (call_id->request_id,
+		                            call_id->log_ifname,
+		                            call_id->log_con_uuid,
+		                            ret);
 	}
 
-	if (info->callback)
-		info->callback (info->request_id, info->user_data);
+	g_hash_table_remove (gl.requests, call_id);
 
-	dispatcher_info_cleanup (info);
+	if (call_id->callback)
+		call_id->callback (call_id, call_id->user_data);
+
+	dispatcher_call_id_free (call_id);
 }
 
 static const char *action_table[] = {
@@ -470,20 +503,9 @@ static const char *action_table[] = {
 static const char *
 action_to_string (NMDispatcherAction action)
 {
-	g_assert ((gsize) action < G_N_ELEMENTS (action_table));
-	return action_table[action];
-}
-
-static gboolean
-dispatcher_idle_cb (gpointer user_data)
-{
-	DispatchInfo *info = user_data;
-
-	info->idle_id = 0;
-	if (info->callback)
-		info->callback (info->request_id, info->user_data);
-	dispatcher_info_cleanup (info);
-	return G_SOURCE_REMOVE;
+	if (G_UNLIKELY ((gsize) action >= G_N_ELEMENTS (action_table)))
+		g_return_val_if_reached (NULL);
+	return action_table[(gsize) action];
 }
 
 static gboolean
@@ -500,7 +522,7 @@ _dispatcher_call (NMDispatcherAction action,
                   NMIP6Config *vpn_ip6_config,
                   NMDispatcherFunc callback,
                   gpointer user_data,
-                  guint *out_call_id)
+                  NMDispatcherCallId **out_call_id)
 {
 	GVariant *connection_dict;
 	GVariantBuilder connection_props;
@@ -508,62 +530,57 @@ _dispatcher_call (NMDispatcherAction action,
 	GVariantBuilder device_proxy_props;
 	GVariantBuilder device_ip4_props;
 	GVariantBuilder device_ip6_props;
-	GVariant *device_dhcp4_props = NULL;
-	GVariant *device_dhcp6_props = NULL;
+	gs_unref_variant GVariant *parameters_floating = NULL;
+	gs_unref_variant GVariant *device_dhcp4_props = NULL;
+	gs_unref_variant GVariant *device_dhcp6_props = NULL;
 	GVariantBuilder vpn_proxy_props;
 	GVariantBuilder vpn_ip4_props;
 	GVariantBuilder vpn_ip6_props;
-	DispatchInfo *info = NULL;
-	gboolean success = FALSE;
-	GError *error = NULL;
-	static guint request_counter = 0;
-	guint reqid = ++request_counter;
+	NMDispatcherCallId *call_id;
+	guint request_id;
 	const char *connectivity_state_string = "UNKNOWN";
+	const char *log_ifname;
+	const char *log_con_uuid;
 
-	if (!dispatcher_proxy)
-		return FALSE;
+	g_return_val_if_fail (!blocking || (!callback && !user_data), FALSE);
+
+	NM_SET_OUT (out_call_id, NULL);
 
-	/* Wrapping protection */
-	if (G_UNLIKELY (!reqid))
-		reqid = ++request_counter;
+	_init_dispatcher ();
 
-	g_assert (!blocking || (!callback && !user_data));
+	if (!gl.dbus_connection)
+		return FALSE;
+
+	log_ifname = device ? nm_device_get_iface (device) : NULL;
+	log_con_uuid = settings_connection ? nm_settings_connection_get_uuid (settings_connection) : NULL;
 
-	_ensure_requests ();
+	request_id = ++gl.request_id_counter;
+	if (G_UNLIKELY (!request_id))
+		request_id = ++gl.request_id_counter;
 
 	/* All actions except 'hostname' and 'connectivity-change' require a device */
 	if (   action == NM_DISPATCHER_ACTION_HOSTNAME
 	    || action == NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE) {
-		_LOGD ("(%u) dispatching action '%s'%s",
-		       reqid, action_to_string (action),
-		       blocking
+		_LOG2D (request_id,
+		        log_ifname,
+		        log_con_uuid,
+		        "dispatching action '%s'%s",
+		        action_to_string (action),
+		        blocking
 		           ? " (blocking)"
 		           : (callback ? " (with callback)" : ""));
 	} else {
 		g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
 
-		_LOGD ("(%u) (%s) dispatching action '%s'%s",
-		       reqid,
-		       vpn_iface ?: nm_device_get_iface(device),
-		       action_to_string (action),
-		       blocking
-		           ? " (blocking)"
-		           : (callback ? " (with callback)" : ""));
-	}
-
-	if (!_get_monitor_by_action(action)->has_scripts) {
-		if (blocking == FALSE && (out_call_id || callback)) {
-			info = g_malloc0 (sizeof (*info));
-			info->action = action;
-			info->request_id = reqid;
-			info->callback = callback;
-			info->user_data = user_data;
-			info->idle_id = g_idle_add (dispatcher_idle_cb, info);
-			_LOGD ("(%u) simulate request; no scripts in %s",  reqid, _get_monitor_by_action(action)->dir);
-		} else
-			_LOGD ("(%u) ignoring request; no scripts in %s", reqid, _get_monitor_by_action(action)->dir);
-		success = TRUE;
-		goto done;
+		_LOG2D (request_id,
+		        log_ifname,
+		        log_con_uuid,
+		        "(%s) dispatching action '%s'%s",
+		        vpn_iface ?: nm_device_get_iface (device),
+		        action_to_string (action),
+		          blocking
+		        ? " (blocking)"
+		        : (callback ? " (with callback)" : ""));
 	}
 
 	if (applied_connection)
@@ -623,91 +640,72 @@ _dispatcher_call (NMDispatcherAction action,
 		}
 	}
 
-	if (!device_dhcp4_props)
-		device_dhcp4_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
-	if (!device_dhcp6_props)
-		device_dhcp6_props = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0));
-
 	connectivity_state_string = nm_connectivity_state_to_string (connectivity_state);
 
+	parameters_floating = g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)",
+	                                     action_to_string (action),
+	                                     connection_dict,
+	                                     &connection_props,
+	                                     &device_props,
+	                                     &device_proxy_props,
+	                                     &device_ip4_props,
+	                                     &device_ip6_props,
+	                                     device_dhcp4_props ?: g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0),
+	                                     device_dhcp6_props ?: g_variant_new_array (G_VARIANT_TYPE ("{sv}"), NULL, 0),
+	                                     connectivity_state_string,
+	                                     vpn_iface ?: "",
+	                                     &vpn_proxy_props,
+	                                     &vpn_ip4_props,
+	                                     &vpn_ip6_props,
+	                                     nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH));
+
 	/* Send the action to the dispatcher */
 	if (blocking) {
-		GVariant *ret;
-		GVariantIter *results;
-
-		ret = _nm_dbus_proxy_call_sync (dispatcher_proxy, "Action",
-		                                g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)",
-		                                               action_to_string (action),
-		                                               connection_dict,
-		                                               &connection_props,
-		                                               &device_props,
-		                                               &device_proxy_props,
-		                                               &device_ip4_props,
-		                                               &device_ip6_props,
-		                                               device_dhcp4_props,
-		                                               device_dhcp6_props,
-		                                               connectivity_state_string,
-		                                               vpn_iface ?: "",
-		                                               &vpn_proxy_props,
-		                                               &vpn_ip4_props,
-		                                               &vpn_ip6_props,
-		                                               nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
-		                                G_VARIANT_TYPE ("(a(sus))"),
-		                                G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
-		                                NULL, &error);
-		if (ret) {
-			g_variant_get (ret, "(a(sus))", &results);
-			dispatcher_results_process (reqid, action, results);
-			g_variant_iter_free (results);
-			g_variant_unref (ret);
-			success = TRUE;
-		} else {
+		gs_unref_variant GVariant *ret = NULL;
+		gs_free_error GError *error = NULL;
+
+		ret = g_dbus_connection_call_sync (gl.dbus_connection,
+		                                   NM_DISPATCHER_DBUS_SERVICE,
+		                                   NM_DISPATCHER_DBUS_PATH,
+		                                   NM_DISPATCHER_DBUS_INTERFACE,
+		                                   "Action",
+		                                   g_steal_pointer (&parameters_floating),
+		                                   G_VARIANT_TYPE ("(a(sus))"),
+		                                   G_DBUS_CALL_FLAGS_NONE,
+		                                   CALL_TIMEOUT,
+		                                   NULL,
+		                                   &error);
+		if (!ret) {
 			g_dbus_error_strip_remote_error (error);
-			_LOGW ("(%u) failed: %s", reqid, error->message);
-			g_clear_error (&error);
-			success = FALSE;
+			_LOG2W (request_id, log_ifname, log_con_uuid, "failed: %s", error->message);
+			return FALSE;
 		}
-	} else {
-		info = g_malloc0 (sizeof (*info));
-		info->action = action;
-		info->request_id = reqid;
-		info->callback = callback;
-		info->user_data = user_data;
-		g_dbus_proxy_call (dispatcher_proxy, "Action",
-		                   g_variant_new ("(s@a{sa{sv}}a{sv}a{sv}a{sv}a{sv}a{sv}@a{sv}@a{sv}ssa{sv}a{sv}a{sv}b)",
-		                                  action_to_string (action),
-		                                  connection_dict,
-		                                  &connection_props,
-		                                  &device_props,
-		                                  &device_proxy_props,
-		                                  &device_ip4_props,
-		                                  &device_ip6_props,
-		                                  device_dhcp4_props,
-		                                  device_dhcp6_props,
-		                                  connectivity_state_string,
-		                                  vpn_iface ?: "",
-		                                  &vpn_proxy_props,
-		                                  &vpn_ip4_props,
-		                                  &vpn_ip6_props,
-		                                  nm_logging_enabled (LOGL_DEBUG, LOGD_DISPATCH)),
-		                   G_DBUS_CALL_FLAGS_NONE, CALL_TIMEOUT,
-		                   NULL, dispatcher_done_cb, info);
-		success = TRUE;
+		dispatcher_results_process (request_id, log_ifname, log_con_uuid, ret);
+		return TRUE;
 	}
 
-	g_variant_unref (device_dhcp4_props);
-	g_variant_unref (device_dhcp6_props);
-
-done:
-	if (success && info) {
-		/* Track the request in case of cancelation */
-		g_hash_table_insert (requests, GUINT_TO_POINTER (info->request_id), info);
-		if (out_call_id)
-			*out_call_id = info->request_id;
-	} else if (out_call_id)
-		*out_call_id = 0;
-
-	return success;
+	call_id = dispatcher_call_id_new (request_id,
+	                                  action,
+	                                  callback,
+	                                  user_data,
+	                                  log_ifname,
+	                                  log_con_uuid);
+
+	g_dbus_connection_call (gl.dbus_connection,
+	                        NM_DISPATCHER_DBUS_SERVICE,
+	                        NM_DISPATCHER_DBUS_PATH,
+	                        NM_DISPATCHER_DBUS_INTERFACE,
+	                        "Action",
+	                        g_steal_pointer (&parameters_floating),
+	                        G_VARIANT_TYPE ("(a(sus))"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        CALL_TIMEOUT,
+	                        NULL,
+	                        dispatcher_done_cb,
+	                        call_id);
+	g_hash_table_add (gl.requests, call_id);
+	NM_SET_OUT (out_call_id, call_id);
+	return TRUE;
 }
 
 /**
@@ -724,7 +722,7 @@ done:
 gboolean
 nm_dispatcher_call_hostname (NMDispatcherFunc callback,
                              gpointer user_data,
-                             guint *out_call_id)
+                             NMDispatcherCallId **out_call_id)
 {
 	return _dispatcher_call (NM_DISPATCHER_ACTION_HOSTNAME, FALSE,
 	                         NULL, NULL, NULL, FALSE,
@@ -755,7 +753,7 @@ nm_dispatcher_call_device (NMDispatcherAction action,
                            NMActRequest *act_request,
                            NMDispatcherFunc callback,
                            gpointer user_data,
-                           guint *out_call_id)
+                           NMDispatcherCallId **out_call_id)
 {
 	nm_assert (NM_IS_DEVICE (device));
 	if (!act_request) {
@@ -839,7 +837,7 @@ nm_dispatcher_call_vpn (NMDispatcherAction action,
                         NMIP6Config *vpn_ip6_config,
                         NMDispatcherFunc callback,
                         gpointer user_data,
-                        guint *out_call_id)
+                        NMDispatcherCallId **out_call_id)
 {
 	return _dispatcher_call (action, FALSE,
 	                         parent_device,
@@ -903,7 +901,7 @@ gboolean
 nm_dispatcher_call_connectivity (NMConnectivityState connectivity_state,
                                  NMDispatcherFunc callback,
                                  gpointer user_data,
-                                 guint *out_call_id)
+                                 NMDispatcherCallId **out_call_id)
 {
 	return _dispatcher_call (NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE, FALSE,
 	                         NULL, NULL, NULL, FALSE,
@@ -913,99 +911,16 @@ nm_dispatcher_call_connectivity (NMConnectivityState connectivity_state,
 }
 
 void
-nm_dispatcher_call_cancel (guint call_id)
+nm_dispatcher_call_cancel (NMDispatcherCallId *call_id)
 {
-	DispatchInfo *info;
-
-	_ensure_requests ();
+	if (   !call_id
+	    || g_hash_table_lookup (gl.requests, call_id) != call_id
+	    || !call_id->callback)
+		g_return_if_reached ();
 
 	/* Canceling just means the callback doesn't get called, so set the
 	 * DispatcherInfo's callback to NULL.
 	 */
-	info = g_hash_table_lookup (requests, GUINT_TO_POINTER (call_id));
-	g_return_if_fail (info);
-
-	if (info && info->callback) {
-		_LOGD ("(%u) cancelling dispatcher callback action", call_id);
-		info->callback = NULL;
-	}
-}
-
-static void
-dispatcher_dir_changed (GFileMonitor *monitor,
-                        GFile *file,
-                        GFile *other_file,
-                        GFileMonitorEvent event_type,
-                        Monitor *item)
-{
-	const char *name;
-	char *full_name;
-	GDir *dir;
-	GError *error = NULL;
-
-	dir = g_dir_open (item->dir, 0, &error);
-	if (dir) {
-		int errsv = 0;
-
-		item->has_scripts = FALSE;
-		errno = 0;
-		while (!item->has_scripts
-		    && (name = g_dir_read_name (dir))) {
-			full_name = g_build_filename (item->dir, name, NULL);
-			item->has_scripts = g_file_test (full_name, G_FILE_TEST_IS_EXECUTABLE);
-			g_free (full_name);
-		}
-		errsv = errno;
-		g_dir_close (dir);
-		if (item->has_scripts)
-			_LOGD ("%s script directory '%s' has scripts", item->description, item->dir);
-		else if (errsv == 0)
-			_LOGD ("%s script directory '%s' has no scripts", item->description, item->dir);
-		else {
-			_LOGD ("%s script directory '%s' error reading (%s)", item->description, item->dir, nm_strerror_native (errsv));
-			item->has_scripts = TRUE;
-		}
-	} else {
-		if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
-			_LOGD ("%s script directory '%s' does not exist", item->description, item->dir);
-			item->has_scripts = FALSE;
-		} else {
-			_LOGD ("%s script directory '%s' error (%s)", item->description, item->dir, error->message);
-			item->has_scripts = TRUE;
-		}
-		g_error_free (error);
-	}
-
+	_LOG3D (call_id, "cancelling dispatcher callback action");
+	call_id->callback = NULL;
 }
-
-void
-nm_dispatcher_init (void)
-{
-	GFile *file;
-	guint i;
-	GError *error = NULL;
-
-	for (i = 0; i < G_N_ELEMENTS (monitors); i++) {
-		file = g_file_new_for_path (monitors[i].dir);
-		monitors[i].monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
-		if (monitors[i].monitor) {
-			g_signal_connect (monitors[i].monitor, "changed", G_CALLBACK (dispatcher_dir_changed), &monitors[i]);
-			dispatcher_dir_changed (monitors[i].monitor, file, NULL, 0, &monitors[i]);
-		}
-		g_object_unref (file);
-	}
-
-	dispatcher_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,
-	                                                  NULL,
-	                                                  NM_DISPATCHER_DBUS_SERVICE,
-	                                                  NM_DISPATCHER_DBUS_PATH,
-	                                                  NM_DISPATCHER_DBUS_INTERFACE,
-	                                                  NULL, &error);
-	if (!dispatcher_proxy) {
-		_LOGE ("could not get dispatcher proxy! %s", error->message);
-		g_clear_error (&error);
-	}
-}
-
diff --git a/src/nm-dispatcher.h b/src/nm-dispatcher.h
index 1cdeeb88..5c6c5805 100644
--- a/src/nm-dispatcher.h
+++ b/src/nm-dispatcher.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -39,18 +38,20 @@ typedef enum {
 	NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE
 } NMDispatcherAction;
 
-typedef void (*NMDispatcherFunc) (guint call_id, gpointer user_data);
+typedef struct NMDispatcherCallId NMDispatcherCallId;
+
+typedef void (*NMDispatcherFunc) (NMDispatcherCallId *call_id, gpointer user_data);
 
 gboolean nm_dispatcher_call_hostname (NMDispatcherFunc callback,
                                       gpointer user_data,
-                                      guint *out_call_id);
+                                      NMDispatcherCallId **out_call_id);
 
 gboolean nm_dispatcher_call_device (NMDispatcherAction action,
                                     NMDevice *device,
                                     NMActRequest *act_request,
                                     NMDispatcherFunc callback,
                                     gpointer user_data,
-                                    guint *out_call_id);
+                                    NMDispatcherCallId **out_call_id);
 
 gboolean nm_dispatcher_call_device_sync (NMDispatcherAction action,
                                          NMDevice *device,
@@ -66,7 +67,7 @@ gboolean nm_dispatcher_call_vpn (NMDispatcherAction action,
                                  NMIP6Config *vpn_ip6_config,
                                  NMDispatcherFunc callback,
                                  gpointer user_data,
-                                 guint *out_call_id);
+                                 NMDispatcherCallId **out_call_id);
 
 gboolean nm_dispatcher_call_vpn_sync (NMDispatcherAction action,
                                       NMSettingsConnection *settings_connection,
@@ -80,10 +81,8 @@ gboolean nm_dispatcher_call_vpn_sync (NMDispatcherAction action,
 gboolean nm_dispatcher_call_connectivity (NMConnectivityState state,
                                           NMDispatcherFunc callback,
                                           gpointer user_data,
-                                          guint *out_call_id);
-
-void nm_dispatcher_call_cancel (guint call_id);
+                                          NMDispatcherCallId **out_call_id);
 
-void nm_dispatcher_init (void);
+void nm_dispatcher_call_cancel (NMDispatcherCallId *call_id);
 
 #endif /* __NM_DISPATCHER_H__ */
diff --git a/src/nm-firewall-manager.c b/src/nm-firewall-manager.c
index a4b079b4..9b0d49c9 100644
--- a/src/nm-firewall-manager.c
+++ b/src/nm-firewall-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-firewall-manager.h b/src/nm-firewall-manager.h
index 8bbf82a7..ceca58be 100644
--- a/src/nm-firewall-manager.h
+++ b/src/nm-firewall-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-hostname-manager.c b/src/nm-hostname-manager.c
index 3fcd8482..86da0168 100644
--- a/src/nm-hostname-manager.c
+++ b/src/nm-hostname-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -131,8 +130,7 @@ read_hostname_slackware (const char *path)
 {
 	gs_free char *contents = NULL;
 	gs_strfreev char **all_lines = NULL;
-	char *tmp;
-	guint i, j = 0;
+	guint i = 0;
 
 	if (!g_file_get_contents (path, &contents, NULL, NULL))
 		return NULL;
@@ -142,17 +140,7 @@ read_hostname_slackware (const char *path)
 		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 g_shell_unquote (&all_lines[i][0], NULL);
 	}
 	return NULL;
 }
diff --git a/src/nm-hostname-manager.h b/src/nm-hostname-manager.h
index a837e9b9..84ee7cb2 100644
--- a/src/nm-hostname-manager.h
+++ b/src/nm-hostname-manager.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager
  *
  * Søren Sandmann <sandmann@daimi.au.dk>
diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c
index 812ab83e..dd5bb327 100644
--- a/src/nm-iface-helper.c
+++ b/src/nm-iface-helper.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -382,8 +381,10 @@ main (int argc, char *argv[])
 	gs_unref_object NMDhcpClient *dhcp4_client = NULL;
 	gs_unref_object NMNDisc *ndisc = NULL;
 	gs_unref_bytes GBytes *hwaddr = NULL;
+	gs_unref_bytes GBytes *bcast_hwaddr = NULL;
 	gs_unref_bytes GBytes *client_id = NULL;
 	gs_free NMUtilsIPv6IfaceId *iid = NULL;
+	const NMPlatformLink *pllink;
 	guint sd_id;
 	int errsv;
 
@@ -470,7 +471,11 @@ main (int argc, char *argv[])
 	/* Set up platform interaction layer */
 	nm_linux_platform_setup ();
 
-	hwaddr = nm_platform_link_get_address_as_bytes (NM_PLATFORM_GET, gl.ifindex);
+	pllink = nm_platform_link_get (NM_PLATFORM_GET, gl.ifindex);
+	if (pllink) {
+		hwaddr = nmp_link_address_get_as_bytes (&pllink->l_address);
+		bcast_hwaddr = nmp_link_address_get_as_bytes (&pllink->l_broadcast);
+	}
 
 	if (global_opt.iid_str) {
 		GBytes *bytes;
@@ -506,6 +511,7 @@ main (int argc, char *argv[])
 		                                          global_opt.ifname,
 		                                          gl.ifindex,
 		                                          hwaddr,
+		                                          bcast_hwaddr,
 		                                          global_opt.uuid,
 		                                          RT_TABLE_MAIN,
 		                                          global_opt.priority_v4,
@@ -590,8 +596,9 @@ main (int argc, char *argv[])
 
 /*****************************************************************************/
 
-const NMDhcpClientFactory *const _nm_dhcp_manager_factories[4] = {
+const NMDhcpClientFactory *const _nm_dhcp_manager_factories[5] = {
 	&_nm_dhcp_client_factory_internal,
+	&_nm_dhcp_client_factory_nettools,
 };
 
 /*****************************************************************************/
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 15088c60..86c82925 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-ip4-config.h b/src/nm-ip4-config.h
index 6d89903f..fe0a085b 100644
--- a/src/nm-ip4-config.h
+++ b/src/nm-ip4-config.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index baef60bd..1096fb42 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -26,6 +25,7 @@
 #include <arpa/inet.h>
 #include <resolv.h>
 #include <linux/rtnetlink.h>
+#include <linux/if.h>
 
 #include "nm-glib-aux/nm-dedup-multi.h"
 
@@ -77,6 +77,7 @@ typedef struct {
 		NMIPConfigDedupMultiIdxType idx_ip6_routes_;
 		NMDedupMultiIdxType idx_ip6_routes;
 	};
+	bool ipv6_disabled;
 } NMIP6ConfigPrivate;
 
 struct _NMIP6Config {
@@ -382,6 +383,8 @@ nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 	const NMDedupMultiHeadEntry *head_entry;
 	NMDedupMultiIter iter;
 	const NMPObject *plobj = NULL;
+	char ifname[IFNAMSIZ];
+	char *path;
 
 	nm_assert (ifindex > 0);
 
@@ -423,6 +426,12 @@ nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i
 	nmp_cache_iter_for_each (&iter, head_entry, &plobj)
 		_add_route (self, plobj, NULL, NULL);
 
+	if (nm_platform_if_indextoname (platform, ifindex, ifname)) {
+		path = nm_sprintf_bufa (128, "/proc/sys/net/ipv6/conf/%s/disable_ipv6", ifname);
+		if (nm_platform_sysctl_get_int32 (platform, NMP_SYSCTL_PATHID_ABSOLUTE (path), 0) != 0)
+			priv->ipv6_disabled = TRUE;
+	}
+
 	return self;
 }
 
@@ -672,7 +681,7 @@ nm_ip6_config_merge_setting (NMIP6Config *self,
 
 		nm_utils_ip6_address_clear_host_address (&route.network, &route.network, route.plen);
 
-		_nm_ip_config_merge_route_attributes (AF_INET,
+		_nm_ip_config_merge_route_attributes (AF_INET6,
 		                                      s_route,
 		                                      NM_PLATFORM_IP_ROUTE_CAST (&route),
 		                                      route_table);
@@ -772,8 +781,11 @@ nm_ip6_config_create_setting (const NMIP6Config *self)
 	}
 
 	/* Use 'ignore' if the method wasn't previously set */
-	if (!method)
-		method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
+	if (!method) {
+		method =   priv->ipv6_disabled
+		         ? NM_SETTING_IP6_CONFIG_METHOD_DISABLED
+		         : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
+	}
 
 	g_object_set (s_ip6,
 	              NM_SETTING_IP_CONFIG_METHOD, method,
@@ -838,10 +850,15 @@ nm_ip6_config_merge (NMIP6Config *dst,
 	guint32 i;
 	NMDedupMultiIter ipconf_iter;
 	const NMPlatformIP6Address *address = NULL;
+	const NMIP6ConfigPrivate *src_priv;
+	NMIP6ConfigPrivate *dst_priv;
 
 	g_return_if_fail (src != NULL);
 	g_return_if_fail (dst != NULL);
 
+	src_priv = NM_IP6_CONFIG_GET_PRIVATE (src);
+	dst_priv = NM_IP6_CONFIG_GET_PRIVATE (dst);
+
 	g_object_freeze_notify (G_OBJECT (dst));
 
 	/* addresses */
@@ -896,6 +913,9 @@ nm_ip6_config_merge (NMIP6Config *dst,
 	if (nm_ip6_config_get_dns_priority (src))
 		nm_ip6_config_set_dns_priority (dst, nm_ip6_config_get_dns_priority (src));
 
+	if (src_priv->ipv6_disabled)
+		dst_priv->ipv6_disabled = src_priv->ipv6_disabled;
+
 	g_object_thaw_notify (G_OBJECT (dst));
 }
 
@@ -1501,6 +1521,11 @@ nm_ip6_config_replace (NMIP6Config *dst, const NMIP6Config *src, gboolean *relev
 		has_minor_changes = TRUE;
 	}
 
+	if (src_priv->ipv6_disabled != dst_priv->ipv6_disabled) {
+		dst_priv->ipv6_disabled = src_priv->ipv6_disabled;
+		has_minor_changes = TRUE;
+	}
+
 #if NM_MORE_ASSERTS
 	/* config_equal does not compare *all* the fields, therefore, we might have has_minor_changes
 	 * regardless of config_equal. But config_equal must correspond to has_relevant_changes. */
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index 86786ea8..6c7b4cf4 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-keep-alive.c b/src/nm-keep-alive.c
index cfec138a..ba080a81 100644
--- a/src/nm-keep-alive.c
+++ b/src/nm-keep-alive.c
@@ -24,6 +24,7 @@
 #include "nm-keep-alive.h"
 
 #include "settings/nm-settings-connection.h"
+#include "nm-glib-aux/nm-dbus-aux.h"
 
 /*****************************************************************************/
 
@@ -211,32 +212,24 @@ nm_keep_alive_set_settings_connection_watch_visible (NMKeepAlive         *self,
 /*****************************************************************************/
 
 static void
-get_name_owner_cb (GObject *source_object,
-                   GAsyncResult *res,
+get_name_owner_cb (const char *name_owner,
+                   GError *error,
                    gpointer user_data)
 {
-	NMKeepAlive *self = user_data;
+	NMKeepAlive *self;
 	NMKeepAlivePrivate *priv;
-	gs_free_error GError *error = NULL;
-	gs_unref_variant GVariant *result = NULL;
-	const char *name_owner;
-
-	result = g_dbus_connection_call_finish ((GDBusConnection *) source_object,
-	                                        res,
-	                                        &error);
-	if (   !result
+
+	if (   !name_owner
 	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
-	if (result) {
-		g_variant_get (result, "(&s)", &name_owner);
-
-		priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
+	self = user_data;
+	priv = NM_KEEP_ALIVE_GET_PRIVATE (self);
 
-		if (nm_streq (name_owner, priv->dbus_client)) {
-			/* all good, the name is confirmed. */
-			return;
-		}
+	if (   name_owner
+	    && nm_streq (name_owner, priv->dbus_client)) {
+		/* all good, the name is confirmed. */
+		return;
 	}
 
 	_LOGD ("DBus client for keep alive is not on the bus");
@@ -259,18 +252,12 @@ _is_alive_dbus_client (NMKeepAlive *self)
 		priv->dbus_client_confirmed = TRUE;
 		priv->dbus_client_confirm_cancellable = g_cancellable_new ();
 
-		g_dbus_connection_call (priv->dbus_connection,
-		                        "org.freedesktop.DBus",
-		                        "/org/freedesktop/DBus",
-		                        "org.freedesktop.DBus",
-		                        "GetNameOwner",
-		                        g_variant_new ("(s)", priv->dbus_client),
-		                        G_VARIANT_TYPE ("(s)"),
-		                        G_DBUS_CALL_FLAGS_NONE,
-		                        -1,
-		                        priv->dbus_client_confirm_cancellable,
-		                        get_name_owner_cb,
-		                        self);
+		nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+		                                        priv->dbus_client,
+		                                        -1,
+		                                        priv->dbus_client_confirm_cancellable,
+		                                        get_name_owner_cb,
+		                                        self);
 	}
 	return TRUE;
 }
@@ -289,7 +276,7 @@ cleanup_dbus_watch (NMKeepAlive *self)
 	nm_clear_g_free (&priv->dbus_client);
 	if (priv->dbus_connection) {
 		g_dbus_connection_signal_unsubscribe (priv->dbus_connection,
-		                                      priv->subscription_id);
+		                                      nm_steal_int (&priv->subscription_id));
 		g_clear_object (&priv->dbus_connection);
 	}
 }
@@ -336,16 +323,11 @@ nm_keep_alive_set_dbus_client_watch (NMKeepAlive *self,
 		priv->dbus_client_watching = TRUE;
 		priv->dbus_client_confirmed = FALSE;
 		priv->dbus_connection = g_object_ref (connection);
-		priv->subscription_id = g_dbus_connection_signal_subscribe (connection,
-		                                                            "org.freedesktop.DBus",
-		                                                            "org.freedesktop.DBus",
-		                                                            "NameOwnerChanged",
-		                                                            "/org/freedesktop/DBus",
-		                                                            priv->dbus_client,
-		                                                            G_DBUS_SIGNAL_FLAGS_NONE,
-		                                                            name_owner_changed_cb,
-		                                                            self,
-		                                                            NULL);
+		priv->subscription_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+		                                                                                priv->dbus_client,
+		                                                                                name_owner_changed_cb,
+		                                                                                self,
+		                                                                                NULL);
 	} else
 		priv->dbus_client_watching = FALSE;
 
@@ -403,6 +385,26 @@ nm_keep_alive_disarm (NMKeepAlive *self)
 	cleanup_dbus_watch (self);
 }
 
+/**
+ * nm_keep_alive_destroy:
+ * @self: (allow-none): the #NMKeepAlive instance to destroy.
+ *
+ * This does 3 things in one:
+ *
+ *   - set owner to %NULL
+ *   - disarm the instance.
+ *   - unref @self.
+ */
+void
+nm_keep_alive_destroy (NMKeepAlive *self)
+{
+	if (!self)
+		return;
+	_nm_keep_alive_set_owner (self, NULL);
+	nm_keep_alive_disarm (self);
+	g_object_unref (self);
+}
+
 /*****************************************************************************/
 
 static void
diff --git a/src/nm-keep-alive.h b/src/nm-keep-alive.h
index 160b2adb..fcee9d6d 100644
--- a/src/nm-keep-alive.h
+++ b/src/nm-keep-alive.h
@@ -43,6 +43,8 @@ gboolean nm_keep_alive_is_alive (NMKeepAlive *self);
 void nm_keep_alive_arm (NMKeepAlive *self);
 void nm_keep_alive_disarm (NMKeepAlive *self);
 
+void nm_keep_alive_destroy (NMKeepAlive *self);
+
 void nm_keep_alive_set_settings_connection_watch_visible (NMKeepAlive         *self,
                                                           NMSettingsConnection *connection);
 
diff --git a/src/nm-logging.c b/src/nm-logging.c
index 48e51421..ea505a81 100644
--- a/src/nm-logging.c
+++ b/src/nm-logging.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -68,6 +67,15 @@
 
 /*****************************************************************************/
 
+G_STATIC_ASSERT (LOG_EMERG   == 0);
+G_STATIC_ASSERT (LOG_ALERT   == 1);
+G_STATIC_ASSERT (LOG_CRIT    == 2);
+G_STATIC_ASSERT (LOG_ERR     == 3);
+G_STATIC_ASSERT (LOG_WARNING == 4);
+G_STATIC_ASSERT (LOG_NOTICE  == 5);
+G_STATIC_ASSERT (LOG_INFO    == 6);
+G_STATIC_ASSERT (LOG_DEBUG   == 7);
+
 /* We have more then 32 logging domains. Assert that it compiles to a 64 bit sized enum */
 G_STATIC_ASSERT (sizeof (NMLogDomain) >= sizeof (guint64));
 
@@ -540,27 +548,50 @@ _domains_to_string (gboolean include_level_override,
 	return g_string_free (str, FALSE);
 }
 
+static char _all_logging_domains_to_str[273];
+
 const char *
 nm_logging_all_domains_to_string (void)
 {
-	static GString *str;
+	static const char *volatile str = NULL;
+	const char *s;
 
-	if (G_UNLIKELY (!str)) {
+again:
+	s = g_atomic_pointer_get (&str);
+	if (G_UNLIKELY (!s)) {
+		static gsize once = 0;
 		const LogDesc *diter;
+		gsize buf_l;
+		char *buf_p;
 
-		str = g_string_new (LOGD_DEFAULT_STRING);
+		if (!g_once_init_enter (&once))
+			goto again;
+
+		buf_p = _all_logging_domains_to_str;
+		buf_l = sizeof (_all_logging_domains_to_str);
+
+		nm_utils_strbuf_append_str (&buf_p, &buf_l, LOGD_DEFAULT_STRING);
 		for (diter = &domain_desc[0]; diter->name; diter++) {
-			g_string_append_c (str, ',');
-			g_string_append (str, diter->name);
+			nm_utils_strbuf_append_c (&buf_p, &buf_l, ',');
+			nm_utils_strbuf_append_str (&buf_p, &buf_l, diter->name);
 			if (diter->num == LOGD_DHCP6)
-				g_string_append (str, "," LOGD_DHCP_STRING);
+				nm_utils_strbuf_append_str (&buf_p, &buf_l, ","LOGD_DHCP_STRING);
 			else if (diter->num == LOGD_IP6)
-				g_string_append (str, "," LOGD_IP_STRING);
+				nm_utils_strbuf_append_str (&buf_p, &buf_l, ","LOGD_IP_STRING);
 		}
-		g_string_append (str, "," LOGD_ALL_STRING);
+		nm_utils_strbuf_append_str (&buf_p, &buf_l, LOGD_ALL_STRING);
+
+		/* Did you modify the logging domains (or their names)? Adjust the size of
+		 * _all_logging_domains_to_str buffer above to have the exact size. */
+		nm_assert (strlen (_all_logging_domains_to_str) == sizeof (_all_logging_domains_to_str) - 1);
+		nm_assert (buf_l == 1);
+
+		s = _all_logging_domains_to_str;
+		g_atomic_pointer_set (&str, s);
+		g_once_init_leave (&once, 1);
 	}
 
-	return str->str;
+	return s;
 }
 
 /**
@@ -618,9 +649,11 @@ _iovec_set_string (struct iovec *iov, const char *str)
 	_iovec_set (iov, str, strlen (str));
 }
 
+#define _iovec_set_string_literal(iov, str) _iovec_set ((iov), ""str"", NM_STRLEN (str))
+
 _nm_printf (3, 4)
 static void
-_iovec_set_format (struct iovec *iov, gpointer *iov_free, const char *format, ...)
+_iovec_set_format (struct iovec *iov, char **iov_free, const char *format, ...)
 {
 	va_list ap;
 	char *str;
@@ -737,12 +770,10 @@ _nm_log_impl (const char *file,
 	case LOG_BACKEND_JOURNAL:
 		{
 			gint64 now, boottime;
-#define _NUM_MAX_FIELDS_SYSLOG_FACILITY 10
-			struct iovec iov_data[12 + _NUM_MAX_FIELDS_SYSLOG_FACILITY];
+			struct iovec iov_data[15];
 			struct iovec *iov = iov_data;
-			gpointer iov_free_data[5];
-			gpointer *iov_free = iov_free_data;
-			nm_auto_free_gstring GString *s_domain_all = NULL;
+			char *iov_free_data[5];
+			char **iov_free = iov_free_data;
 
 			now = nm_utils_get_monotonic_timestamp_ns ();
 			boottime = nm_utils_monotonic_timestamp_as_boottime (now, 1);
@@ -753,46 +784,25 @@ _nm_log_impl (const char *file,
 			_iovec_set_format_a (iov++, 30, "SYSLOG_PID=%ld", (long) getpid ());
 			{
 				const LogDesc *diter;
-				int i_domain = _NUM_MAX_FIELDS_SYSLOG_FACILITY;
-				const char *s_domain_1 = NULL;
 				NMLogDomain dom_all = domain;
-				NMLogDomain dom = dom_all & cur_log_state[level];
+				char s_log_domains_buf[NM_STRLEN ("NM_LOG_DOMAINS=") + sizeof (_all_logging_domains_to_str)];
+				char *s_log_domains = s_log_domains_buf;
+				gsize l_log_domains = sizeof (s_log_domains_buf);
 
-				for (diter = &domain_desc[0]; diter->name; diter++) {
+				nm_utils_strbuf_append_str (&s_log_domains, &l_log_domains, "NM_LOG_DOMAINS=");
+				for (diter = &domain_desc[0]; dom_all != 0 && diter->name; diter++) {
 					if (!NM_FLAGS_ANY (dom_all, diter->num))
 						continue;
-
-					/* construct a list of all domains (not only the enabled ones).
-					 * Note that in by far most cases, there is only one domain present.
-					 * Hence, save the construction of the GString. */
+					if (dom_all != domain)
+						nm_utils_strbuf_append_c (&s_log_domains, &l_log_domains, ',');
+					nm_utils_strbuf_append_str (&s_log_domains, &l_log_domains, diter->name);
 					dom_all &= ~diter->num;
-					if (!s_domain_1)
-						s_domain_1 = diter->name;
-					else {
-						if (!s_domain_all) {
-							s_domain_all = g_string_new ("NM_LOG_DOMAINS=");
-							g_string_append (s_domain_all, s_domain_1);
-						}
-						g_string_append_c (s_domain_all, ',');
-						g_string_append (s_domain_all, diter->name);
-					}
-
-					if (NM_FLAGS_ANY (dom, diter->num)) {
-						if (i_domain > 0) {
-							/* SYSLOG_FACILITY is specified multiple times for each domain that is actually enabled. */
-							_iovec_set_format_str_a (iov++, 30, "SYSLOG_FACILITY=%s", diter->name);
-							i_domain--;
-						}
-						dom &= ~diter->num;
-					}
-					if (!dom && !dom_all)
-						break;
 				}
-				if (s_domain_all)
-					_iovec_set (iov++, s_domain_all->str, s_domain_all->len);
-				else
-					_iovec_set_format_str_a (iov++, 30, "NM_LOG_DOMAINS=%s", s_domain_1);
+				nm_assert (l_log_domains > 0);
+				_iovec_set (iov++, s_log_domains_buf, s_log_domains - s_log_domains_buf);
 			}
+			G_STATIC_ASSERT_EXPR (LOG_FAC (LOG_DAEMON) == 3);
+			_iovec_set_string_literal (iov++, "SYSLOG_FACILITY=3");
 			_iovec_set_format_str_a (iov++, 15, "NM_LOG_LEVEL=%s", level_desc[level].name);
 			if (func)
 				_iovec_set_format (iov++, iov_free++, "CODE_FUNC=%s", func);
@@ -907,7 +917,7 @@ nm_log_handler (const char *log_domain,
 			                 "MESSAGE=%s%s", gl.imm.prefix, message ?: "",
 			                 syslog_identifier_full (gl.imm.syslog_identifier),
 			                 "SYSLOG_PID=%ld", (long) getpid (),
-			                 "SYSLOG_FACILITY=GLIB",
+			                 "SYSLOG_FACILITY=3",
 			                 "GLIB_DOMAIN=%s", log_domain ?: "",
 			                 "GLIB_LEVEL=%d", (int) (level & G_LOG_LEVEL_MASK),
 			                 "TIMESTAMP_MONOTONIC=%lld.%06lld", (long long) (now / NM_UTILS_NS_PER_SECOND), (long long) ((now % NM_UTILS_NS_PER_SECOND) / 1000),
diff --git a/src/nm-logging.h b/src/nm-logging.h
index a0f4ad73..c604f493 100644
--- a/src/nm-logging.h
+++ b/src/nm-logging.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -310,9 +309,9 @@ gboolean nm_logging_syslog_enabled (void);
 #define __NMLOG_DEFAULT_WITH_ADDR(level, domain, prefix, ...) \
 	G_STMT_START { \
 		nm_log ((level), (domain), NULL, NULL, \
-		        "%s[%p]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        "%s["NM_HASH_OBFUSCATE_PTR_FMT"]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
 		        (prefix), \
-		        (self) \
+		        NM_HASH_OBFUSCATE_PTR (self) \
 		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
 	} G_STMT_END
 
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 40e068ac..6ea53502 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -95,7 +94,8 @@ typedef struct {
 				struct {
 					GDBusMethodInvocation *invocation;
 					NMConnection *connection;
-					NMSettingsConnectionPersistMode persist;
+					NMSettingsConnectionPersistMode persist_mode;
+					bool is_volatile:1;
 				} add_and_activate;
 			};
 		} ac_auth;
@@ -132,6 +132,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMManager,
 	PROP_CONNECTIVITY,
 	PROP_CONNECTIVITY_CHECK_AVAILABLE,
 	PROP_CONNECTIVITY_CHECK_ENABLED,
+	PROP_CONNECTIVITY_CHECK_URI,
 	PROP_PRIMARY_CONNECTION,
 	PROP_PRIMARY_CONNECTION_TYPE,
 	PROP_ACTIVATING_CONNECTION,
@@ -177,6 +178,9 @@ typedef struct {
 
 	NMSettings *settings;
 
+	CList connection_changed_on_idle_lst;
+	guint connection_changed_on_idle_id;
+
 	RadioState radio_states[RFKILL_TYPE_MAX];
 	NMVpnManager *vpn_manager;
 
@@ -186,7 +190,8 @@ typedef struct {
 
 	GHashTable *device_route_metrics;
 
-	GSList *auth_chains;
+	CList auth_lst_head;
+
 	GHashTable *sleep_devices;
 
 	/* Firmware dir monitor */
@@ -375,7 +380,8 @@ static void _add_and_activate_auth_done (NMManager *self,
                                          NMActiveConnection *active,
                                          NMConnection *connection,
                                          GDBusMethodInvocation *invocation,
-                                         NMSettingsConnectionPersistMode persist,
+                                         NMSettingsConnectionPersistMode persist_mode,
+                                         gboolean is_volatile,
                                          gboolean success,
                                          const char *error_desc);
 static void _activation_auth_done (NMManager *self,
@@ -492,7 +498,8 @@ _async_op_data_new_ac_auth_add_and_activate (NMManager *self,
                                              NMActiveConnection *active_take,
                                              GDBusMethodInvocation *invocation_take,
                                              NMConnection *connection_take,
-                                             NMSettingsConnectionPersistMode persist)
+                                             NMSettingsConnectionPersistMode persist_mode,
+                                             gboolean is_volatile)
 {
 	AsyncOpData *async_op_data;
 
@@ -505,7 +512,8 @@ _async_op_data_new_ac_auth_add_and_activate (NMManager *self,
 	async_op_data->ac_auth.active = active_take;
 	async_op_data->ac_auth.add_and_activate.invocation = invocation_take;
 	async_op_data->ac_auth.add_and_activate.connection = connection_take;
-	async_op_data->ac_auth.add_and_activate.persist = persist;
+	async_op_data->ac_auth.add_and_activate.persist_mode = persist_mode;
+	async_op_data->ac_auth.add_and_activate.is_volatile = is_volatile;
 	c_list_link_tail (&NM_MANAGER_GET_PRIVATE (self)->async_op_lst_head, &async_op_data->async_op_lst);
 	return async_op_data;
 }
@@ -547,7 +555,8 @@ _async_op_complete_ac_auth_cb (NMActiveConnection *active,
 		                             async_op_data->ac_auth.active,
 		                             async_op_data->ac_auth.add_and_activate.connection,
 		                             async_op_data->ac_auth.add_and_activate.invocation,
-		                             async_op_data->ac_auth.add_and_activate.persist,
+		                             async_op_data->ac_auth.add_and_activate.persist_mode,
+		                             async_op_data->ac_auth.add_and_activate.is_volatile,
 		                             success,
 		                             error_desc);
 		g_object_unref (async_op_data->ac_auth.add_and_activate.connection);
@@ -817,18 +826,18 @@ _delete_volatile_connection_do (NMManager *self,
 	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
 	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 		return;
+	if (!nm_settings_has_connection (priv->settings, connection))
+		return;
 	if (active_connection_find (self,
 	                            connection,
 	                            NULL,
 	                            NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
 	                            NULL))
 		return;
-	if (!nm_settings_has_connection (priv->settings, connection))
-		return;
 
 	_LOGD (LOGD_DEVICE, "volatile connection disconnected. Deleting connection '%s' (%s)",
 	       nm_settings_connection_get_id (connection), nm_settings_connection_get_uuid (connection));
-	nm_settings_connection_delete (connection, NULL);
+	nm_settings_connection_delete (connection, FALSE);
 }
 
 /* Returns: whether to notify D-Bus of the removal or not */
@@ -1121,15 +1130,19 @@ _config_changed_cb (NMConfig *config, NMConfigData *config_data, NMConfigChangeF
 
 	if (NM_FLAGS_HAS (changes, NM_CONFIG_CHANGE_GLOBAL_DNS_CONFIG))
 		_notify (self, PROP_GLOBAL_DNS_CONFIGURATION);
-	if ((!nm_config_data_get_connectivity_uri (config_data)) != (!nm_config_data_get_connectivity_uri (old_data)))
-		_notify (self, PROP_CONNECTIVITY_CHECK_AVAILABLE);
+
+	if (!nm_streq0 (nm_config_data_get_connectivity_uri (config_data),
+	                nm_config_data_get_connectivity_uri (old_data))) {
+		if ((!nm_config_data_get_connectivity_uri (config_data)) != (!nm_config_data_get_connectivity_uri (old_data)))
+			_notify (self, PROP_CONNECTIVITY_CHECK_AVAILABLE);
+		_notify (self, PROP_CONNECTIVITY_CHECK_URI);
+	}
 
 	g_object_thaw_notify (G_OBJECT (self));
 }
 
 static void
 _reload_auth_cb (NMAuthChain *chain,
-                 GError *error,
                  GDBusMethodInvocation *context,
                  gpointer user_data)
 {
@@ -1142,21 +1155,15 @@ _reload_auth_cb (NMAuthChain *chain,
 	char s_buf[60];
 	NMConfigChangeFlags reload_type = NM_CONFIG_CHANGE_NONE;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 	flags = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "flags"));
 
 	subject = nm_auth_chain_get_subject (chain);
 
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_RELOAD);
-	if (error) {
-		_LOGD (LOGD_CORE, "Reload request failed: %s", error->message);
-		ret_error = g_error_new (NM_MANAGER_ERROR,
-		                         NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                         "Reload request failed: %s",
-		                         error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		ret_error = g_error_new_literal (NM_MANAGER_ERROR,
 		                                 NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                                 "Not authorized to reload configuration");
@@ -1188,14 +1195,11 @@ _reload_auth_cb (NMAuthChain *chain,
 
 	if (ret_error) {
 		g_dbus_method_invocation_take_error (context, ret_error);
-		goto out;
+		return;
 	}
 
 	nm_config_reload (priv->config, reload_type, TRUE);
 	g_dbus_method_invocation_return_value (context, NULL);
-
-out:
-	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -1223,7 +1227,7 @@ impl_manager_reload (NMDBusObject *obj,
 		return;
 	}
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "flags", GUINT_TO_POINTER (flags), NULL);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_RELOAD, TRUE);
 }
@@ -1635,17 +1639,16 @@ device_is_wake_on_lan (NMPlatform *platform, NMDevice *device)
 static void
 remove_device (NMManager *self,
                NMDevice *device,
-               gboolean quitting,
-               gboolean allow_unmanage)
+               gboolean quitting)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	gboolean unmanage = FALSE;
 
-	_LOG2D (LOGD_DEVICE, device, "removing device (allow_unmanage %d, managed %d, wol %d)",
-	        allow_unmanage, nm_device_get_managed (device, FALSE),
+	_LOG2D (LOGD_DEVICE, device, "removing device (managed %d, wol %d)",
+	        nm_device_get_managed (device, FALSE),
 	        device_is_wake_on_lan (priv->platform, device));
 
-	if (allow_unmanage && nm_device_get_managed (device, FALSE)) {
+	if (nm_device_get_managed (device, FALSE)) {
 
 		if (quitting) {
 			/* Leave configured if wo(w)lan and quitting */
@@ -1712,7 +1715,7 @@ remove_device (NMManager *self,
 static void
 device_removed_cb (NMDevice *device, gpointer user_data)
 {
-	remove_device (NM_MANAGER (user_data), device, FALSE, TRUE);
+	remove_device (NM_MANAGER (user_data), device, FALSE);
 }
 
 NMState
@@ -1907,7 +1910,7 @@ nm_manager_remove_device (NMManager *self, const char *ifname, NMDeviceType devi
 	if (!d)
 		return FALSE;
 
-	remove_device (self, d, FALSE, FALSE);
+	remove_device (self, d, FALSE);
 	return TRUE;
 }
 
@@ -2029,7 +2032,7 @@ system_create_virtual_device (NMManager *self, NMConnection *connection)
 			         "couldn't create the device: %s",
 			         error->message);
 			g_error_free (error);
-			remove_device (self, device, FALSE, TRUE);
+			remove_device (self, device, FALSE);
 			return NULL;
 		}
 
@@ -2075,8 +2078,20 @@ static void
 connection_changed (NMManager *self,
                     NMSettingsConnection *sett_conn)
 {
+	NMManagerPrivate *priv;
+	NMConnection *connection;
 	NMDevice *device;
-	NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
+
+	if (NM_FLAGS_HAS (nm_settings_connection_get_flags (sett_conn),
+	                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
+		return;
+
+	priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (!nm_settings_has_connection (priv->settings, sett_conn))
+		return;
+
+	connection = nm_settings_connection_get_connection (sett_conn);
 
 	if (!nm_connection_is_virtual (connection))
 		return;
@@ -2091,48 +2106,69 @@ connection_changed (NMManager *self,
 	retry_connections_for_parent_device (self, device);
 }
 
+static gboolean
+connection_changed_on_idle_cb (gpointer user_data)
+{
+	NMManager *self = user_data;
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+	NMCListElem *elem;
+
+	priv->connection_changed_on_idle_id = 0;
+
+	while ((elem = c_list_first_entry (&priv->connection_changed_on_idle_lst, NMCListElem, lst))) {
+		gs_unref_object NMSettingsConnection *sett_conn = NULL;
+
+		sett_conn = nm_c_list_elem_free_steal (elem);
+		connection_changed (self, sett_conn);
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static void
+connection_changed_on_idle (NMManager *self,
+                            NMSettingsConnection *sett_conn)
+{
+	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (priv->connection_changed_on_idle_id == 0)
+		priv->connection_changed_on_idle_id = g_idle_add (connection_changed_on_idle_cb, self);
+
+	if (!nm_c_list_elem_find_first (&priv->connection_changed_on_idle_lst, sett_conn)) {
+		c_list_link_tail (&priv->connection_changed_on_idle_lst,
+		                  &nm_c_list_elem_new_stale (g_object_ref (sett_conn))->lst);
+	}
+}
+
 static void
 connection_added_cb (NMSettings *settings,
                      NMSettingsConnection *sett_conn,
                      NMManager *self)
 {
-	connection_changed (self, sett_conn);
+	connection_changed_on_idle (self, sett_conn);
 }
 
 static void
 connection_updated_cb (NMSettings *settings,
                        NMSettingsConnection *sett_conn,
-                       gboolean by_user,
+                       guint update_reason_u,
                        NMManager *self)
 {
-	if (by_user)
-		connection_changed (self, sett_conn);
+	connection_changed_on_idle (self, sett_conn);
 }
 
 /*****************************************************************************/
 
-typedef struct {
-	CList delete_volatile_connection_lst;
-	NMSettingsConnection *connection;
-} DeleteVolatileConnectionData;
-
 static void
 _delete_volatile_connection_all (NMManager *self, gboolean do_delete)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	CList *lst;
-	DeleteVolatileConnectionData *data;
+	NMCListElem *elem;
 
-	while ((lst = c_list_first (&priv->delete_volatile_connection_lst_head))) {
+	while ((elem = c_list_first_entry (&priv->delete_volatile_connection_lst_head, NMCListElem, lst))) {
 		gs_unref_object NMSettingsConnection *connection = NULL;
 
-		data = c_list_entry (lst,
-		                     DeleteVolatileConnectionData,
-		                     delete_volatile_connection_lst);
-		connection = data->connection;
-		c_list_unlink_stale (&data->delete_volatile_connection_lst);
-		g_slice_free (DeleteVolatileConnectionData, data);
-
+		connection = nm_c_list_elem_free_steal (elem);
 		if (do_delete)
 			_delete_volatile_connection_do (self, connection);
 	}
@@ -2156,7 +2192,6 @@ connection_flags_changed (NMSettings *settings,
 {
 	NMManager *self = user_data;
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	DeleteVolatileConnectionData *data;
 
 	if (!NM_FLAGS_HAS (nm_settings_connection_get_flags (connection),
 	                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
@@ -2168,9 +2203,8 @@ connection_flags_changed (NMSettings *settings,
 		return;
 	}
 
-	data = g_slice_new (DeleteVolatileConnectionData);
-	data->connection = g_object_ref (connection);
-	c_list_link_tail (&priv->delete_volatile_connection_lst_head, &data->delete_volatile_connection_lst);
+	c_list_link_tail (&priv->delete_volatile_connection_lst_head,
+	                  &nm_c_list_elem_new_stale (g_object_ref (connection))->lst);
 	if (!priv->delete_volatile_connection_idle_id)
 		priv->delete_volatile_connection_idle_id = g_idle_add (_delete_volatile_connection_cb, self);
 }
@@ -2338,41 +2372,32 @@ nm_manager_rfkill_update (NMManager *self, RfKillType rtype)
 
 static void
 device_auth_done_cb (NMAuthChain *chain,
-                     GError *auth_error,
                      GDBusMethodInvocation *context,
                      gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
 	NMAuthCallResult result;
 	NMDevice *device;
 	const char *permission;
 	NMDeviceAuthRequestFunc callback;
 	NMAuthSubject *subject;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
-	permission = nm_auth_chain_get_data (chain, "requested-permission");
-	g_assert (permission);
+	permission = nm_auth_chain_get_data (chain, "perm");
+	nm_assert (permission);
 	callback = nm_auth_chain_get_data (chain, "callback");
-	g_assert (callback);
+	nm_assert (callback);
 	device = nm_auth_chain_get_data (chain, "device");
-	g_assert (device);
+	nm_assert (NM_IS_DEVICE (device));
 
 	result = nm_auth_chain_get_result (chain, permission);
 	subject = nm_auth_chain_get_subject (chain);
 
-	if (auth_error) {
-		/* translate the auth error into a manager permission denied error */
-		_LOGD (LOGD_CORE, "%s request failed: %s", permission, auth_error->message);
-		error = g_error_new (NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     "%s request failed: %s",
-		                     permission, auth_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		_LOGD (LOGD_CORE, "%s request failed: not authorized", permission);
 		error = g_error_new (NM_MANAGER_ERROR,
 		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
@@ -2380,16 +2405,13 @@ device_auth_done_cb (NMAuthChain *chain,
 		                     permission);
 	}
 
-	g_assert (error || (result == NM_AUTH_CALL_RESULT_YES));
+	nm_assert (error || (result == NM_AUTH_CALL_RESULT_YES));
 
 	callback (device,
 	          context,
 	          subject,
 	          error,
 	          nm_auth_chain_get_data (chain, "user-data"));
-
-	g_clear_error (&error);
-	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -2406,6 +2428,7 @@ device_auth_request_cb (NMDevice *device,
 	GError *error = NULL;
 	NMAuthSubject *subject = NULL;
 	NMAuthChain *chain;
+	char *permission_dup;
 
 	/* Validate the caller */
 	subject = nm_auth_subject_new_unix_process_from_context (context);
@@ -2434,12 +2457,14 @@ device_auth_request_cb (NMDevice *device,
 		goto done;
 	}
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	permission_dup = g_strdup (permission);
+
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "device", g_object_ref (device), g_object_unref);
-	nm_auth_chain_set_data (chain, "requested-permission", g_strdup (permission), g_free);
 	nm_auth_chain_set_data (chain, "callback", callback, NULL);
 	nm_auth_chain_set_data (chain, "user-data", user_data, NULL);
-	nm_auth_chain_add_call (chain, permission, allow_interaction);
+	nm_auth_chain_set_data (chain, "perm", permission_dup /* transfer ownership */, g_free);
+	nm_auth_chain_add_call_unsafe (chain, permission_dup, allow_interaction);
 
 done:
 	if (error)
@@ -2646,8 +2671,14 @@ get_existing_connection (NMManager *self,
 
 	nm_device_assume_state_reset (device);
 
-	added = nm_settings_add_connection (priv->settings, connection, FALSE, &error);
-	if (!added) {
+	if (!nm_settings_add_connection (priv->settings,
+	                                 connection,
+	                                 NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+	                                 NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                                   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                 | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                                 &added,
+	                                 &error)) {
 		_LOG2W (LOGD_SETTINGS, device, "assume: failure to save generated connection '%s': %s",
 		       nm_connection_get_id (connection),
 		       error->message);
@@ -2655,10 +2686,6 @@ get_existing_connection (NMManager *self,
 		return NULL;
 	}
 
-	nm_settings_connection_set_flags (added,
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED |
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
-	                                  TRUE);
 	NM_SET_OUT (out_generated, TRUE);
 	return added;
 }
@@ -2760,7 +2787,7 @@ recheck_assume_connection (NMManager *self,
 
 			if (generated) {
 				_LOG2D (LOGD_DEVICE, device, "assume: deleting generated connection after assuming failed");
-				nm_settings_connection_delete (sett_conn, NULL);
+				nm_settings_connection_delete (sett_conn, FALSE);
 			} 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);
@@ -2821,7 +2848,7 @@ device_ip_iface_changed (NMDevice *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);
+			remove_device (self, candidate, FALSE);
 			break;
 		}
 	}
@@ -3026,7 +3053,7 @@ add_device (NMManager *self, NMDevice *device, GError **error)
 			remove = g_slist_prepend (remove, candidate);
 	}
 	for (iter = remove; iter; iter = iter->next)
-		remove_device (self, NM_DEVICE (iter->data), FALSE, FALSE);
+		remove_device (self, NM_DEVICE (iter->data), FALSE);
 	g_slist_free (remove);
 
 	g_object_ref (device);
@@ -3215,7 +3242,7 @@ platform_link_added (NMManager *self,
 				 */
 				_LOGD (LOGD_DEVICE, "(%s): removing old device %p after ifindex change from %d to %d",
 				       plink->name, candidate, nm_device_get_ifindex (candidate), ifindex);
-				remove_device (self, candidate, FALSE, TRUE);
+				remove_device (self, candidate, FALSE);
 				goto add;
 			}
 			return;
@@ -3348,13 +3375,13 @@ _platform_link_cb_idle (PlatformLinkCbData *data)
 				if (!nm_device_unrealize (device, FALSE, &error)) {
 					_LOG2W (LOGD_DEVICE, device, "failed to unrealize: %s", error->message);
 					g_clear_error (&error);
-					remove_device (self, device, FALSE, TRUE);
+					remove_device (self, device, FALSE);
 				} else {
 					nm_device_update_from_platform_link (device, NULL);
 				}
 			} else {
 				/* Hardware and external devices always get removed when their kernel link is gone */
-				remove_device (self, device, FALSE, TRUE);
+				remove_device (self, device, FALSE);
 			}
 		}
 	}
@@ -4153,7 +4180,8 @@ static SlaveConnectionInfo *
 find_slaves (NMManager *manager,
              NMSettingsConnection *sett_conn,
              NMDevice *device,
-             guint *out_n_slaves)
+             guint *out_n_slaves,
+             gboolean for_user_request)
 {
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
 	gs_free NMSettingsConnection **all_connections = NULL;
@@ -4195,7 +4223,7 @@ find_slaves (NMManager *manager,
 			slave_device = nm_manager_get_best_device_for_connection (manager,
 			                                                          candidate,
 			                                                          NULL,
-			                                                          FALSE,
+			                                                          for_user_request,
 			                                                          devices,
 			                                                          NULL);
 
@@ -4271,7 +4299,8 @@ static void
 autoconnect_slaves (NMManager *self,
                     NMSettingsConnection *master_connection,
                     NMDevice *master_device,
-                    NMAuthSubject *subject)
+                    NMAuthSubject *subject,
+                    gboolean for_user_request)
 {
 	GError *local_err = NULL;
 
@@ -4281,7 +4310,7 @@ autoconnect_slaves (NMManager *self,
 		guint i, n_slaves = 0;
 		gboolean bind_lifetime_to_profile_visibility;
 
-		slaves = find_slaves (self, master_connection, master_device, &n_slaves);
+		slaves = find_slaves (self, master_connection, master_device, &n_slaves, for_user_request);
 		if (n_slaves > 1) {
 			gs_free char *value = NULL;
 
@@ -4681,7 +4710,8 @@ _internal_activate_device (NMManager *self, NMActiveConnection *active, GError *
 	}
 
 	/* Check slaves for master connection and possibly activate them */
-	autoconnect_slaves (self, sett_conn, device, nm_active_connection_get_subject (active));
+	autoconnect_slaves (self, sett_conn, device, nm_active_connection_get_subject (active),
+	                    nm_active_connection_get_activation_reason (active) == NM_ACTIVATION_REASON_USER_REQUEST);
 
 	multi_connect = _nm_connection_get_multi_connect (nm_settings_connection_get_connection (sett_conn));
 	if (   multi_connect == NM_CONNECTION_MULTI_CONNECT_MULTIPLE
@@ -4892,9 +4922,7 @@ _internal_activation_auth_done (NMManager *self,
 		c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
 			if (   nm_active_connection_get_device (ac) == nm_active_connection_get_device (active)
 			    && nm_active_connection_get_settings_connection (ac) == nm_active_connection_get_settings_connection (active)
-			    && NM_IN_SET (nm_active_connection_get_state (ac),
-			                  NM_ACTIVE_CONNECTION_STATE_ACTIVATING,
-			                  NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
+			    && nm_active_connection_get_state (ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
 				g_set_error (&error,
 				             NM_MANAGER_ERROR,
 				             NM_MANAGER_ERROR_CONNECTION_ALREADY_ACTIVE,
@@ -5300,14 +5328,11 @@ activation_add_done (NMSettings *settings,
 	NMManager *self;
 	gs_unref_object NMActiveConnection *active = NULL;
 	gs_free_error GError *local = NULL;
-	gpointer persist_ptr;
-	NMSettingsConnectionPersistMode persist;
 	gpointer async_op_type_ptr;
 	AsyncOpType async_op_type;
 	GVariant *result_floating;
 
-	nm_utils_user_data_unpack (user_data, &self, &active, &persist_ptr, &async_op_type_ptr);
-	persist = GPOINTER_TO_INT (persist_ptr);
+	nm_utils_user_data_unpack (user_data, &self, &active, &async_op_type_ptr);
 	async_op_type = GPOINTER_TO_INT (async_op_type_ptr);
 
 	if (error)
@@ -5315,17 +5340,8 @@ activation_add_done (NMSettings *settings,
 
 	nm_active_connection_set_settings_connection (active, new_connection);
 
-	if (!_internal_activate_generic (self, active, &local)) {
-		error = local;
+	if (!_internal_activate_generic (self, active, &local))
 		goto fail;
-	}
-
-	nm_settings_connection_update (new_connection,
-	                               NULL,
-	                               persist,
-	                               NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION | NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED,
-	                               "add-and-activate",
-	                               NULL);
 
 	if (async_op_type == ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE) {
 		result_floating = g_variant_new ("(oo)",
@@ -5348,13 +5364,17 @@ activation_add_done (NMSettings *settings,
 	return;
 
 fail:
-	nm_assert (error);
+	if (local) {
+		nm_assert (!error);
+		error = local;
+	} else
+		nm_assert (error);
 
 	nm_active_connection_set_state_fail (active,
 	                                     NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
 	                                     error->message);
 	if (new_connection)
-		nm_settings_connection_delete (new_connection, NULL);
+		nm_settings_connection_delete (new_connection, FALSE);
 	g_dbus_method_invocation_return_gerror (context, error);
 	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD_ACTIVATE,
 	                            NULL,
@@ -5370,7 +5390,8 @@ _add_and_activate_auth_done (NMManager *self,
                              NMActiveConnection *active,
                              NMConnection *connection,
                              GDBusMethodInvocation *invocation,
-                             NMSettingsConnectionPersistMode persist,
+                             NMSettingsConnectionPersistMode persist_mode,
+                             gboolean is_volatile,
                              gboolean success,
                              const char *error_desc)
 {
@@ -5398,13 +5419,16 @@ _add_and_activate_auth_done (NMManager *self,
 	 * shutdown. */
 	nm_settings_add_connection_dbus (priv->settings,
 	                                 connection,
-	                                 FALSE,
+	                                 persist_mode,
+	                                 NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                                 (  is_volatile
+	                                  ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                  : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE),
 	                                 nm_active_connection_get_subject (active),
 	                                 invocation,
 	                                 activation_add_done,
 	                                 nm_utils_user_data_pack (self,
 	                                                          g_object_ref (active),
-	                                                          GINT_TO_POINTER (persist),
 	                                                          GINT_TO_POINTER (async_op_type)));
 }
 
@@ -5430,7 +5454,8 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	const char *device_path;
 	const char *specific_object_path;
 	gs_free NMConnection **conns = NULL;
-	NMSettingsConnectionPersistMode persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK;
+	NMSettingsConnectionPersistMode persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+	gboolean is_volatile = FALSE;
 	gboolean bind_dbus_client = FALSE;
 	AsyncOpType async_op_type;
 
@@ -5459,13 +5484,17 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 			    && g_variant_is_of_type (option_value, G_VARIANT_TYPE_STRING)) {
 				s = g_variant_get_string (option_value, NULL);
 
-				if (nm_streq (s, "volatile"))
-					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_ONLY;
-				else if (nm_streq (s, "memory"))
-					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
-				else if (nm_streq (s, "disk"))
-					persist = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK;
-				else {
+				is_volatile = FALSE;
+				persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+
+				if (nm_streq (s, "volatile")) {
+					persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+					is_volatile = TRUE;
+				} else if (nm_streq (s, "memory"))
+					persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+				else if (nm_streq (s, "disk")) {
+					/* pass */
+				} else {
 					error = g_error_new_literal (NM_MANAGER_ERROR,
 					                             NM_MANAGER_ERROR_INVALID_ARGUMENTS,
 					                             "Option \"persist\" must be one of \"volatile\", \"memory\" or \"disk\"");
@@ -5538,6 +5567,7 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 		                           NULL,
 		                           _("VPN connection"),
 		                           NULL,
+		                           NULL,
 		                           FALSE); /* No IPv6 by default for now */
 	} else {
 		conns = nm_settings_connections_array_to_connections (nm_settings_get_connections (priv->settings, NULL), -1);
@@ -5550,6 +5580,8 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 			goto error;
 	}
 
+	nm_assert (_nm_connection_verify (incompl_conn, NULL) == NM_SETTING_VERIFY_SUCCESS);
+
 	active = _new_active_connection (self,
 	                                 is_vpn,
 	                                 NULL,
@@ -5581,7 +5613,8 @@ impl_manager_add_and_activate_connection (NMDBusObject *obj,
 	                                                                             active,
 	                                                                             invocation,
 	                                                                             incompl_conn,
-	                                                                             persist));
+	                                                                             persist_mode,
+	                                                                             is_volatile));
 
 	/* we passed the pointers on to _async_op_data_new_ac_auth_add_and_activate() */
 	g_steal_pointer (&incompl_conn);
@@ -5625,32 +5658,24 @@ nm_manager_deactivate_connection (NMManager *manager,
 
 static void
 deactivate_net_auth_done_cb (NMAuthChain *chain,
-                             GError *auth_error,
                              GDBusMethodInvocation *context,
                              gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
 	GError *error = NULL;
 	NMAuthCallResult result;
 	NMActiveConnection *active;
 	char *path;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
 	path = nm_auth_chain_get_data (chain, "path");
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL);
 	active = active_connection_get_by_path (self, path);
 
-	if (auth_error) {
-		_LOGD (LOGD_CORE, "Disconnect request failed: %s", auth_error->message);
-		error = g_error_new (NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     "Deactivate request failed: %s",
-		                     auth_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                             "Not authorized to deactivate connections");
@@ -5680,8 +5705,6 @@ deactivate_net_auth_done_cb (NMAuthChain *chain,
 		g_dbus_method_invocation_take_error (context, error);
 	else
 		g_dbus_method_invocation_return_value (context, NULL);
-
-	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -5741,7 +5764,7 @@ impl_manager_deactivate_connection (NMDBusObject *obj,
 		goto done;
 	}
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "path", g_strdup (active_path), g_free);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
 
@@ -5866,9 +5889,15 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 		 * suspend/resume?
 		 */
 		c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
-			/* FIXME: shouldn't we be unmanaging software devices if !suspending? */
-			if (nm_device_is_software (device))
-				continue;
+			if (nm_device_is_software (device)) {
+				/* If a user disables networking we consider that as an
+				 * indication that also software devices must be disconnected.
+				 * But we don't want to destroy them for external events as
+				 * a system suspend.
+				 */
+				if (suspending)
+					continue;
+			}
 			/* Wake-on-LAN devices will be taken down post-suspend rather than pre- */
 			if (   suspending
 			    && device_is_wake_on_lan (priv->platform, device)) {
@@ -5923,10 +5952,10 @@ do_sleep_wake (NMManager *self, gboolean sleeping_changed)
 		c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
 			guint i;
 
-			if (nm_device_is_software (device)) {
-				/* We do not manage/unmanage software devices but
-				 * their dhcp leases could have gone stale so we need
-				 * to renew them */
+			if (   nm_device_is_software (device)
+			    && !nm_device_get_unmanaged_flags (device, NM_UNMANAGED_SLEEPING)) {
+				/* DHCP leases of software devices could have gone stale
+				 * so we need to renew them. */
 				nm_device_update_dynamic_ip_setup (device);
 				continue;
 			}
@@ -5978,45 +6007,6 @@ _internal_sleep (NMManager *self, gboolean do_sleep)
 	_notify (self, PROP_SLEEPING);
 }
 
-#if 0
-static void
-sleep_auth_done_cb (NMAuthChain *chain,
-                    GError *error,
-                    GDBusMethodInvocation *context,
-                    gpointer user_data)
-{
-	NMManager *self = NM_MANAGER (user_data);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GError *ret_error;
-	NMAuthCallResult result;
-	gboolean do_sleep;
-
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
-
-	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_SLEEP_WAKE);
-	if (error) {
-		_LOGD (LOGD_SUSPEND, "Sleep/wake request failed: %s", error->message);
-		ret_error = g_error_new (NM_MANAGER_ERROR,
-		                         NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                         "Sleep/wake request failed: %s",
-		                         error->message);
-		g_dbus_method_invocation_take_error (context, ret_error);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
-		ret_error = g_error_new_literal (NM_MANAGER_ERROR,
-		                                 NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                                 "Not authorized to sleep/wake");
-		g_dbus_method_invocation_take_error (context, ret_error);
-	} else {
-		/* Auth success */
-		do_sleep = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "sleep"));
-		_internal_sleep (self, do_sleep);
-		g_dbus_method_invocation_return_value (context, NULL);
-	}
-
-	nm_auth_chain_destroy (chain);
-}
-#endif
-
 static void
 impl_manager_sleep (NMDBusObject *obj,
                     const NMDBusInterfaceInfoExtended *interface_info,
@@ -6091,49 +6081,37 @@ _internal_enable (NMManager *self, gboolean enable)
 
 static void
 enable_net_done_cb (NMAuthChain *chain,
-                    GError *error,
                     GDBusMethodInvocation *context,
                     gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GError *ret_error = NULL;
 	NMAuthCallResult result;
 	gboolean enable;
 	NMAuthSubject *subject;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 	enable = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "enable"));
 	subject = nm_auth_chain_get_subject (chain);
 
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK);
-	if (error) {
-		_LOGD (LOGD_CORE, "Enable request failed: %s", error->message);
-		ret_error = g_error_new (NM_MANAGER_ERROR,
-		                         NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                         "Enable request failed: %s",
-		                         error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
+		GError *ret_error;
+
 		ret_error = g_error_new_literal (NM_MANAGER_ERROR,
 		                                 NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                                 "Not authorized to enable/disable networking");
-	} else {
-		/* Auth success */
-		_internal_enable (self, enable);
-		g_dbus_method_invocation_return_value (context, NULL);
-		nm_audit_log_control_op (NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", TRUE,
-		                         subject, NULL);
-	}
-
-	if (ret_error) {
 		nm_audit_log_control_op (NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", FALSE,
 		                         subject, ret_error->message);
 		g_dbus_method_invocation_take_error (context, ret_error);
+		return;
 	}
 
-	nm_auth_chain_destroy (chain);
+	_internal_enable (self, enable);
+	g_dbus_method_invocation_return_value (context, NULL);
+	nm_audit_log_control_op (NM_AUDIT_OP_NET_CONTROL, enable ? "on" : "off", TRUE,
+	                         subject, NULL);
 }
 
 static void
@@ -6168,7 +6146,7 @@ impl_manager_enable (NMDBusObject *obj,
 		goto done;
 	}
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "enable", GUINT_TO_POINTER (enable), NULL);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, TRUE);
 
@@ -6198,50 +6176,37 @@ get_perm_add_result (NMManager *self, NMAuthChain *chain, GVariantBuilder *resul
 
 static void
 get_permissions_done_cb (NMAuthChain *chain,
-                         GError *error,
                          GDBusMethodInvocation *context,
                          gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	GError *ret_error;
 	GVariantBuilder results;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
-	if (error) {
-		_LOGD (LOGD_CORE, "Permissions request failed: %s", error->message);
-		ret_error = g_error_new (NM_MANAGER_ERROR,
-		                         NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                         "Permissions request failed: %s",
-		                         error->message);
-		g_dbus_method_invocation_take_error (context, ret_error);
-	} else {
-		g_variant_builder_init (&results, G_VARIANT_TYPE ("a{ss}"));
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SLEEP_WAKE);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_NETWORK_CONTROL);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_RELOAD);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS);
-		get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK);
+	g_variant_builder_init (&results, G_VARIANT_TYPE ("a{ss}"));
 
-		g_dbus_method_invocation_return_value (context,
-		                                       g_variant_new ("(a{ss})", &results));
-	}
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SLEEP_WAKE);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WWAN);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIMAX);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_NETWORK_CONTROL);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_RELOAD);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS);
+	get_perm_add_result (self, chain, &results, NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK);
 
-	nm_auth_chain_destroy (chain);
+	g_dbus_method_invocation_return_value (context,
+	                                       g_variant_new ("(a{ss})", &results));
 }
 
 static void
@@ -6266,7 +6231,7 @@ impl_manager_get_permissions (NMDBusObject *obj,
 		return;
 	}
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_NETWORK, FALSE);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SLEEP_WAKE, FALSE);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_ENABLE_DISABLE_WIFI, FALSE);
@@ -6407,7 +6372,6 @@ device_connectivity_done (NMDevice *device,
 
 static void
 check_connectivity_auth_done_cb (NMAuthChain *chain,
-                                 GError *auth_error,
                                  GDBusMethodInvocation *context,
                                  gpointer user_data)
 {
@@ -6418,25 +6382,18 @@ check_connectivity_auth_done_cb (NMAuthChain *chain,
 	ConnectivityCheckData *data;
 	NMDevice *device;
 
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL);
 
-	if (auth_error) {
-		_LOGD (LOGD_CORE, "CheckConnectivity request failed: %s", auth_error->message);
-		error = g_error_new (NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     "Connectivity check request failed: %s",
-		                     auth_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                             "Not authorized to recheck connectivity");
 	}
-
 	if (error) {
 		g_dbus_method_invocation_take_error (context, error);
-		goto out;
+		return;
 	}
 
 	data = g_slice_new (ConnectivityCheckData);
@@ -6467,9 +6424,6 @@ check_connectivity_auth_done_cb (NMAuthChain *chain,
 		                          data);
 		/* @data got destroyed. */
 	}
-
-out:
-	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -6494,7 +6448,7 @@ impl_manager_check_connectivity (NMDBusObject *obj,
 		return;
 	}
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
 }
 
@@ -6518,6 +6472,7 @@ nm_manager_write_device_state (NMManager *self, NMDevice *device)
 	guint32 route_metric_default_effective;
 	int nm_owned;
 	NMDhcp4Config *dhcp4_config;
+	const char *next_server = NULL;
 	const char *root_path = NULL;
 
 	ifindex = nm_device_get_ip_ifindex (device);
@@ -6555,8 +6510,10 @@ nm_manager_write_device_state (NMManager *self, NMDevice *device)
 	                                                           TRUE, &route_metric_default_aspired);
 
 	dhcp4_config = nm_device_get_dhcp4_config (device);
-	if (dhcp4_config)
+	if (dhcp4_config) {
 		root_path = nm_dhcp4_config_get_option (dhcp4_config, "root_path");
+		next_server = nm_dhcp4_config_get_option (dhcp4_config, "next_server");
+	}
 
 	return nm_config_device_state_write (ifindex,
 	                                     managed_type,
@@ -6565,6 +6522,7 @@ nm_manager_write_device_state (NMManager *self, NMDevice *device)
 	                                     nm_owned,
 	                                     route_metric_default_aspired,
 	                                     route_metric_default_effective,
+	                                     next_server,
 	                                     root_path);
 }
 
@@ -6606,8 +6564,9 @@ nm_manager_start (NMManager *self, GError **error)
 	gs_free NMSettingsConnection **connections = NULL;
 	guint i;
 
-	if (!nm_settings_start (priv->settings, error))
-		return FALSE;
+	nm_device_factory_manager_load_factories (_register_device_factory, self);
+
+	nm_device_factory_manager_for_each_factory (start_factory, NULL);
 
 	/* Set initial radio enabled/disabled state */
 	for (i = 0; i < RFKILL_TYPE_MAX; i++) {
@@ -6630,16 +6589,15 @@ nm_manager_start (NMManager *self, GError **error)
 		manager_update_radio_enabled (self, rstate, enabled);
 	}
 
-	/* Log overall networking status - enabled/disabled */
 	_LOGI (LOGD_CORE, "Networking is %s by state file",
 	       priv->net_enabled ? "enabled" : "disabled");
 
 	system_unmanaged_devices_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);
+	if (!nm_settings_start (priv->settings, error))
+		return FALSE;
 
 	nm_platform_process_events (priv->platform);
 
@@ -6653,10 +6611,11 @@ nm_manager_start (NMManager *self, GError **error)
 	/* Load VPN plugins */
 	priv->vpn_manager = g_object_ref (nm_vpn_manager_get ());
 
-	/* Connections added before the manager is started do not emit
-	 * connection-added signals thus devices have to be created manually.
-	 */
 	_LOGD (LOGD_CORE, "creating virtual devices...");
+	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);
 	connections = nm_settings_get_connections_clone (priv->settings, NULL,
 	                                                 NULL, NULL,
 	                                                 nm_settings_connection_cmp_autoconnect_priority_p_with_data, NULL);
@@ -6691,7 +6650,7 @@ nm_manager_stop (NMManager *self)
 	nm_dbus_manager_stop (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)));
 
 	while ((device = c_list_first_entry (&priv->devices_lst_head, NMDevice, devices_lst)))
-		remove_device (self, device, TRUE, TRUE);
+		remove_device (self, device, TRUE);
 
 	_active_connection_cleanup (self);
 
@@ -6841,7 +6800,6 @@ typedef struct {
 
 static void
 _dbus_set_property_auth_cb (NMAuthChain *chain,
-                            GError *error,
                             GDBusMethodInvocation *invocation,
                             gpointer user_data)
 {
@@ -6861,13 +6819,12 @@ _dbus_set_property_auth_cb (NMAuthChain *chain,
 
 	g_slice_free (DBusSetPropertyHandle, handle_data);
 
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 	result = nm_auth_chain_get_result (chain, property_info->writable.permission);
 
-	if (   error
-	    || result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error_name = NM_PERM_DENIED_ERROR;
-		error_message = error ? error->message : "Not authorized to perform this operation";
+		error_message = "Not authorized to perform this operation";
 		goto out;
 	}
 
@@ -6908,7 +6865,6 @@ out:
 		g_dbus_method_invocation_return_dbus_error (invocation, error_name, error_message);
 	else
 		g_dbus_method_invocation_return_value (invocation, NULL);
-	nm_auth_chain_destroy (chain);
 }
 
 void
@@ -6943,8 +6899,8 @@ nm_manager_dbus_set_property_handle (NMDBusObject *obj,
 	handle_data->export_version_id = nm_dbus_object_get_export_version_id (obj);
 
 	chain = nm_auth_chain_new_subject (subject, invocation, _dbus_set_property_auth_cb, handle_data);
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
-	nm_auth_chain_add_call (chain, property_info->writable.permission, TRUE);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
+	nm_auth_chain_add_call_unsafe (chain, property_info->writable.permission, TRUE);
 	return;
 
 err:
@@ -6973,13 +6929,13 @@ _checkpoint_mgr_get (NMManager *self, gboolean create_as_needed)
 
 static void
 checkpoint_auth_done_cb (NMAuthChain *chain,
-                         GError *auth_error,
                          GDBusMethodInvocation *context,
                          gpointer user_data)
 {
 	NMManager *self = NM_MANAGER (user_data);
-	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	char *op, *checkpoint_path = NULL, **devices;
+	char *op;
+	char *checkpoint_path = NULL;
+	char **devices;
 	NMCheckpoint *checkpoint;
 	NMAuthCallResult result;
 	guint32 timeout, flags;
@@ -6989,7 +6945,7 @@ checkpoint_auth_done_cb (NMAuthChain *chain,
 	guint32 add_timeout;
 
 	op = nm_auth_chain_get_data (chain, "audit-op");
-	priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK);
 
 	if (NM_IN_STRSET (op, NM_AUDIT_OP_CHECKPOINT_DESTROY,
@@ -6997,12 +6953,7 @@ checkpoint_auth_done_cb (NMAuthChain *chain,
 	                      NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT))
 		arg = checkpoint_path = nm_auth_chain_get_data (chain, "checkpoint_path");
 
-	if (auth_error) {
-		error = g_error_new (NM_MANAGER_ERROR,
-		                     NM_MANAGER_ERROR_PERMISSION_DENIED,
-		                     "checkpoint check request failed: %s",
-		                     auth_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_MANAGER_ERROR,
 		                             NM_MANAGER_ERROR_PERMISSION_DENIED,
 		                             "Not authorized to checkpoint/rollback");
@@ -7042,8 +6993,6 @@ checkpoint_auth_done_cb (NMAuthChain *chain,
 		g_dbus_method_invocation_take_error (context, error);
 	else
 		g_dbus_method_invocation_return_value (context, variant);
-
-	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -7075,7 +7024,7 @@ impl_manager_checkpoint_create (NMDBusObject *obj,
 
 	g_variant_get (parameters, "(^aouu)", &devices, &rollback_timeout, &flags);
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_CREATE, NULL);
 	nm_auth_chain_set_data (chain, "devices", devices, (GDestroyNotify) g_strfreev);
 	nm_auth_chain_set_data (chain, "flags",  GUINT_TO_POINTER (flags), NULL);
@@ -7108,7 +7057,7 @@ impl_manager_checkpoint_destroy (NMDBusObject *obj,
 
 	g_variant_get (parameters, "(&o)", &checkpoint_path);
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_DESTROY, NULL);
 	nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
@@ -7139,7 +7088,7 @@ impl_manager_checkpoint_rollback (NMDBusObject *obj,
 
 	g_variant_get (parameters, "(&o)", &checkpoint_path);
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ROLLBACK, NULL);
 	nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free);
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_CHECKPOINT_ROLLBACK, TRUE);
@@ -7171,7 +7120,7 @@ impl_manager_checkpoint_adjust_rollback_timeout (NMDBusObject *obj,
 
 	g_variant_get (parameters, "(&ou)", &checkpoint_path, &add_timeout);
 
-	priv->auth_chains = g_slist_append (priv->auth_chains, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_set_data (chain, "audit-op", NM_AUDIT_OP_CHECKPOINT_ADJUST_ROLLBACK_TIMEOUT, NULL);
 	nm_auth_chain_set_data (chain, "checkpoint_path", g_strdup (checkpoint_path), g_free);
 	nm_auth_chain_set_data (chain, "add_timeout", GUINT_TO_POINTER (add_timeout), NULL);
@@ -7307,7 +7256,7 @@ periodic_update_active_connection_timestamps (gpointer user_data)
 	c_list_for_each_entry (ac, &priv->active_connections_lst_head, active_connections_lst) {
 		if (nm_active_connection_get_state (ac) == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
 			nm_settings_connection_update_timestamp (nm_active_connection_get_settings_connection (ac),
-			                                         (guint64) time (NULL), FALSE);
+			                                         (guint64) time (NULL));
 		}
 	}
 	return G_SOURCE_CONTINUE;
@@ -7377,7 +7326,8 @@ nm_manager_setup (void)
 	singleton_instance = self;
 
 	nm_singleton_instance_register ();
-	_LOGD (LOGD_CORE, "setup %s singleton (%p)", "NMManager", singleton_instance);
+	nm_log_dbg (LOGD_CORE, "setup %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")",
+	            "NMManager", NM_HASH_OBFUSCATE_PTR (singleton_instance));
 
 	nm_dbus_object_export (NM_DBUS_OBJECT (self));
 	return self;
@@ -7400,10 +7350,6 @@ 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, 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);
 	g_signal_connect (priv->settings, NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED, G_CALLBACK (connection_flags_changed), self);
 
 	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
@@ -7461,11 +7407,13 @@ nm_manager_init (NMManager *self)
 	guint i;
 	GFile *file;
 
+	c_list_init (&priv->auth_lst_head);
 	c_list_init (&priv->link_cb_lst);
 	c_list_init (&priv->devices_lst_head);
 	c_list_init (&priv->active_connections_lst_head);
 	c_list_init (&priv->async_op_lst_head);
 	c_list_init (&priv->delete_volatile_connection_lst_head);
+	c_list_init (&priv->connection_changed_on_idle_lst);
 
 	priv->platform = g_object_ref (NM_PLATFORM_GET);
 
@@ -7602,6 +7550,10 @@ get_property (GObject *object, guint prop_id,
 	case PROP_CONNECTIVITY_CHECK_ENABLED:
 		g_value_set_boolean (value, concheck_enabled (self, NULL));
 		break;
+	case PROP_CONNECTIVITY_CHECK_URI:
+		config_data = nm_config_get_data (priv->config);
+		g_value_set_string (value, nm_config_data_get_connectivity_uri (config_data));
+		break;
 	case PROP_PRIMARY_CONNECTION:
 		nm_dbus_utils_g_value_set_object_path (value, priv->primary_connection);
 		break;
@@ -7709,24 +7661,23 @@ dispose (GObject *object)
 {
 	NMManager *self = NM_MANAGER (object);
 	NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
-	CList *iter, *iter_safe;
-	NMActiveConnection *ac, *ac_safe;
+	CList *iter;
 
 	nm_assert (c_list_is_empty (&priv->async_op_lst_head));
 
 	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) {
+	while ((iter = c_list_first (&priv->link_cb_lst))) {
 		PlatformLinkCbData *data = c_list_entry (iter, PlatformLinkCbData, lst);
 
 		g_source_remove (data->idle_id);
-		c_list_unlink_stale (iter);
+		c_list_unlink_stale (&data->lst);
 		g_slice_free (PlatformLinkCbData, data);
 	}
 
-	g_slist_free_full (priv->auth_chains, (GDestroyNotify) nm_auth_chain_destroy);
-	priv->auth_chains = NULL;
+	while ((iter = c_list_first (&priv->auth_lst_head)))
+		nm_auth_chain_destroy (nm_auth_chain_parent_lst_entry (iter));
 
 	nm_clear_g_source (&priv->devices_inited_id);
 
@@ -7750,8 +7701,9 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&priv->ac_cleanup_id);
 
-	c_list_for_each_entry_safe (ac, ac_safe, &priv->active_connections_lst_head, active_connections_lst)
-		active_connection_remove (self, ac);
+	while ((iter = c_list_first (&priv->active_connections_lst_head)))
+		active_connection_remove (self, c_list_entry (iter, NMActiveConnection, active_connections_lst));
+
 	nm_assert (c_list_is_empty (&priv->active_connections_lst_head));
 	g_clear_object (&priv->primary_connection);
 	g_clear_object (&priv->activating_connection);
@@ -7767,6 +7719,9 @@ dispose (GObject *object)
 		g_clear_object (&priv->policy);
 	}
 
+	nm_clear_g_source (&priv->connection_changed_on_idle_id);
+	nm_c_list_elem_free_all (&priv->connection_changed_on_idle_lst, g_object_unref);
+
 	if (priv->settings) {
 		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);
@@ -8095,6 +8050,7 @@ static const NMDBusInterfaceInfoExtended interface_info_manager = {
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("Connectivity",               "u",     NM_MANAGER_CONNECTIVITY),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L     ("ConnectivityCheckAvailable", "b",     NM_MANAGER_CONNECTIVITY_CHECK_AVAILABLE),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("ConnectivityCheckEnabled",   "b",     NM_MANAGER_CONNECTIVITY_CHECK_ENABLED,    NM_AUTH_PERMISSION_ENABLE_DISABLE_CONNECTIVITY_CHECK, NM_AUDIT_OP_NET_CONTROL),
+			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE       ("ConnectivityCheckUri",       "s",     NM_MANAGER_CONNECTIVITY_CHECK_URI),
 			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L ("GlobalDnsConfiguration",     "a{sv}", NM_MANAGER_GLOBAL_DNS_CONFIGURATION,      NM_AUTH_PERMISSION_SETTINGS_MODIFY_GLOBAL_DNS,        NM_AUDIT_OP_NET_CONTROL),
 		),
 	),
@@ -8207,6 +8163,12 @@ nm_manager_class_init (NMManagerClass *manager_class)
 	                          G_PARAM_READWRITE |
 	                          G_PARAM_STATIC_STRINGS);
 
+	obj_properties[PROP_CONNECTIVITY_CHECK_URI] =
+	    g_param_spec_string (NM_MANAGER_CONNECTIVITY_CHECK_URI, "", "",
+	                         NULL,
+	                         G_PARAM_READABLE |
+	                         G_PARAM_STATIC_STRINGS);
+
 	obj_properties[PROP_PRIMARY_CONNECTION] =
 	    g_param_spec_string (NM_MANAGER_PRIMARY_CONNECTION, "", "",
 	                         NULL,
diff --git a/src/nm-manager.h b/src/nm-manager.h
index ecb4b017..966abce0 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -48,6 +47,7 @@
 #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_CONNECTIVITY_CHECK_URI "connectivity-check-uri"
 #define NM_MANAGER_PRIMARY_CONNECTION "primary-connection"
 #define NM_MANAGER_PRIMARY_CONNECTION_TYPE "primary-connection-type"
 #define NM_MANAGER_ACTIVATING_CONNECTION "activating-connection"
diff --git a/src/nm-netns.c b/src/nm-netns.c
index 650578af..c1ced153 100644
--- a/src/nm-netns.c
+++ b/src/nm-netns.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -128,17 +127,27 @@ constructed (GObject *object)
 
 	priv->rules_manager = nmp_rules_manager_new (priv->platform);
 
-	/* Weakly track the default rules and rules that were added
-	 * outside of NetworkManager. */
+	/* Weakly track the default rules with a dummy user-tag. These
+	 * rules are always weekly tracked... */
 	nmp_rules_manager_track_default (priv->rules_manager,
 	                                 AF_UNSPEC,
 	                                 0,
 	                                 nm_netns_parent_class /* static dummy user-tag */);
+
+	/* Also weakly track all existing rules. These were added before NetworkManager
+	 * starts, so they are probably none of NetworkManager's business.
+	 *
+	 * However note that during service restart, devices may stay up and rules kept.
+	 * That means, after restart such rules may have been added by a previous run
+	 * of NetworkManager, we just don't know.
+	 *
+	 * For that reason, whenever we will touch such rules later one, we make them
+	 * fully owned and no longer weekly tracked. See %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG. */
 	nmp_rules_manager_track_from_platform (priv->rules_manager,
 	                                       NULL,
 	                                       AF_UNSPEC,
 	                                       0,
-	                                       nm_netns_parent_class /* static dummy user-tag */);
+	                                       NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
 
 	G_OBJECT_CLASS (nm_netns_parent_class)->constructed (object);
 }
diff --git a/src/nm-netns.h b/src/nm-netns.h
index 250d1149..126ef375 100644
--- a/src/nm-netns.h
+++ b/src/nm-netns.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-pacrunner-manager.c b/src/nm-pacrunner-manager.c
index b9881f76..38b53eab 100644
--- a/src/nm-pacrunner-manager.c
+++ b/src/nm-pacrunner-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -23,11 +22,14 @@
 #include "nm-pacrunner-manager.h"
 
 #include "nm-utils.h"
+#include "NetworkManagerUtils.h"
 #include "platform/nm-platform.h"
+#include "nm-dbus-manager.h"
 #include "nm-proxy-config.h"
 #include "nm-ip4-config.h"
 #include "nm-ip6-config.h"
 #include "c-list/src/c-list.h"
+#include "nm-glib-aux/nm-dbus-aux.h"
 
 #define PACRUNNER_DBUS_SERVICE "org.pacrunner"
 #define PACRUNNER_DBUS_INTERFACE "org.pacrunner.Manager"
@@ -35,25 +37,27 @@
 
 /*****************************************************************************/
 
-struct _NMPacrunnerCallId {
-	CList lst;
+struct _NMPacrunnerConfId {
+	CList conf_id_lst;
 
-	/* this might be a dangling pointer after the async operation
-	 * is cancelled. */
-	NMPacrunnerManager *manager_maybe_dangling;
+	NMPacrunnerManager *self;
+
+	GVariant *parameters;
 
-	GVariant *args;
 	char *path;
+	guint64 log_id;
 	guint refcount;
 };
 
-typedef struct _NMPacrunnerCallId Config;
-
 typedef struct {
-	char *iface;
-	GDBusProxy *pacrunner;
+	GDBusConnection *dbus_connection;
 	GCancellable *cancellable;
-	CList configs;
+	CList conf_id_lst_head;
+	guint64 log_id_counter;
+	guint name_owner_changed_id;
+	bool dbus_initied:1;
+	bool has_name_owner:1;
+	bool try_start_blocked:1;
 } NMPacrunnerManagerPrivate;
 
 struct _NMPacrunnerManager {
@@ -79,469 +83,511 @@ NM_DEFINE_SINGLETON_GETTER (NMPacrunnerManager, nm_pacrunner_manager_get, NM_TYP
 #define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "pacrunner", __VA_ARGS__)
 
 #define _NMLOG2_PREFIX_NAME "pacrunner"
-#define _NMLOG2(level, config, ...) \
+#define _NMLOG2(level, conf_id, ...) \
 	G_STMT_START { \
 		nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
-		        "%s%p]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+		        "%s%"G_GUINT64_FORMAT"]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
 		        _NMLOG2_PREFIX_NAME": call[", \
-		        (config) \
-		        _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+		        (conf_id)->log_id \
+		        _NM_UTILS_MACRO_REST (__VA_ARGS__)); \
 	} G_STMT_END
 
 /*****************************************************************************/
 
-static void pacrunner_remove_done (GObject *source, GAsyncResult *res, gpointer user_data);
+static void _call_destroy_proxy_configuration (NMPacrunnerManager *self,
+                                               NMPacrunnerConfId *conf_id,
+                                               const char *path,
+                                               gboolean verbose_log);
 
 /*****************************************************************************/
 
-static Config *
-config_new (NMPacrunnerManager *manager, GVariant *args)
+static NMPacrunnerConfId *
+conf_id_ref (NMPacrunnerConfId *conf_id)
 {
-	Config *config;
-
-	config = g_slice_new0 (Config);
-	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);
+	nm_assert (conf_id);
+	nm_assert (conf_id->refcount > 0);
 
-	return config;
-}
-
-static Config *
-config_ref (Config *config)
-{
-	nm_assert (config);
-	nm_assert (config->refcount > 0);
-
-	config->refcount++;
-	return config;
+	conf_id->refcount++;
+	return conf_id;
 }
 
 static void
-config_unref (Config *config)
+conf_id_unref (NMPacrunnerConfId *conf_id)
 {
-	nm_assert (config);
-	nm_assert (config->refcount > 0);
-
-	if (config->refcount == 1) {
-		g_variant_unref (config->args);
-		g_free (config->path);
-		c_list_unlink_stale (&config->lst);
-		g_slice_free (Config, config);
+	nm_assert (conf_id);
+	nm_assert (conf_id->refcount > 0);
+
+	if (conf_id->refcount == 1) {
+		g_variant_unref (conf_id->parameters);
+		g_free (conf_id->path);
+		c_list_unlink_stale (&conf_id->conf_id_lst);
+		g_object_unref (conf_id->self);
+		g_slice_free (NMPacrunnerConfId, conf_id);
 	} else
-		config->refcount--;
+		conf_id->refcount--;
 }
 
+NM_AUTO_DEFINE_FCN0 (NMPacrunnerConfId *, _nm_auto_unref_conf_id, conf_id_unref);
+#define nm_auto_unref_conf_id nm_auto (_nm_auto_unref_conf_id)
+
 /*****************************************************************************/
 
 static void
-add_proxy_config (GVariantBuilder *proxy_data, const NMProxyConfig *proxy_config)
+get_ip_domains (GPtrArray *domains, NMIPConfig *ip_config)
 {
-	const char *pac_url, *pac_script;
+	NMDedupMultiIter ipconf_iter;
+	char *cidr;
+	guint i, num;
+	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+	int addr_family;
+
+	if (!ip_config)
+		return;
+
+	addr_family = nm_ip_config_get_addr_family (ip_config);
+
+	num = nm_ip_config_get_num_searches (ip_config);
+	for (i = 0; i < num; i++)
+		g_ptr_array_add (domains, g_strdup (nm_ip_config_get_search (ip_config, i)));
+
+	num = nm_ip_config_get_num_domains (ip_config);
+	for (i = 0; i < num; i++)
+		g_ptr_array_add (domains, g_strdup (nm_ip_config_get_domain (ip_config, i)));
+
+	if (addr_family == AF_INET) {
+		const NMPlatformIP4Address *address;
+
+		nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, (NMIP4Config *) ip_config, &address) {
+			cidr = g_strdup_printf ("%s/%u",
+			                        nm_utils_inet4_ntop (address->address, sbuf),
+			                        address->plen);
+			g_ptr_array_add (domains, cidr);
+		}
+	} else {
+		const NMPlatformIP6Address *address;
+
+		nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, (NMIP6Config *) ip_config, &address) {
+			cidr = g_strdup_printf ("%s/%u",
+			                        nm_utils_inet6_ntop (&address->address, sbuf),
+			                        address->plen);
+			g_ptr_array_add (domains, cidr);
+		}
+	}
+
+	if (addr_family == AF_INET) {
+		const NMPlatformIP4Route *routes;
+
+		nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, (NMIP4Config *) ip_config, &routes) {
+			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
+				continue;
+			cidr = g_strdup_printf ("%s/%u",
+			                        nm_utils_inet4_ntop (routes->network, sbuf),
+			                        routes->plen);
+			g_ptr_array_add (domains, cidr);
+		}
+	} else {
+		const NMPlatformIP6Route *routes;
+
+		nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, (NMIP6Config *) ip_config, &routes) {
+			if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (routes))
+				continue;
+			cidr = g_strdup_printf ("%s/%u",
+			                        nm_utils_inet6_ntop (&routes->network, sbuf),
+			                        routes->plen);
+			g_ptr_array_add (domains, cidr);
+		}
+	}
+}
+
+static GVariant *
+_make_request_create_proxy_configuration (NMProxyConfig *proxy_config,
+                                          const char *iface,
+                                          NMIP4Config *ip4_config,
+                                          NMIP6Config *ip6_config)
+{
+	GVariantBuilder builder;
 	NMProxyConfigMethod method;
+	const char *pac_url;
+	const char *pac_script;
+
+	nm_assert (NM_IS_PROXY_CONFIG (proxy_config));
+
+	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+	if (iface) {
+		g_variant_builder_add (&builder, "{sv}",
+		                       "Interface",
+		                       g_variant_new_string (iface));
+	}
 
 	method = nm_proxy_config_get_method (proxy_config);
+	switch (method) {
+	case NM_PROXY_CONFIG_METHOD_AUTO:
+		g_variant_builder_add (&builder, "{sv}",
+		                       "Method",
+		                       g_variant_new_string ("auto"));
 
-	if (method == NM_PROXY_CONFIG_METHOD_AUTO) {
 		pac_url = nm_proxy_config_get_pac_url (proxy_config);
 		if (pac_url) {
-			g_variant_builder_add (proxy_data, "{sv}",
+			g_variant_builder_add (&builder, "{sv}",
 			                       "URL",
 			                       g_variant_new_string (pac_url));
 		}
 
 		pac_script = nm_proxy_config_get_pac_script (proxy_config);
 		if (pac_script) {
-			g_variant_builder_add (proxy_data, "{sv}",
+			g_variant_builder_add (&builder, "{sv}",
 			                       "Script",
 			                       g_variant_new_string (pac_script));
 		}
+		break;
+	case NM_PROXY_CONFIG_METHOD_NONE:
+		g_variant_builder_add (&builder, "{sv}",
+		                       "Method",
+		                       g_variant_new_string ("direct"));
+		break;
 	}
 
-	g_variant_builder_add (proxy_data, "{sv}",
+	g_variant_builder_add (&builder, "{sv}",
 	                       "BrowserOnly",
 	                       g_variant_new_boolean (nm_proxy_config_get_browser_only (proxy_config)));
-}
-
-static void
-get_ip4_domains (GPtrArray *domains, NMIP4Config *ip4)
-{
-	NMDedupMultiIter ipconf_iter;
-	char *cidr;
-	const NMPlatformIP4Address *address;
-	const NMPlatformIP4Route *routes;
-	guint i;
-	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
-	/* Extract searches */
-	for (i = 0; i < nm_ip4_config_get_num_searches (ip4); i++)
-		g_ptr_array_add (domains, g_strdup (nm_ip4_config_get_search (ip4, i)));
+	if (ip4_config || ip6_config) {
+		gs_unref_ptrarray GPtrArray *domains = NULL;
 
-	/* Extract domains */
-	for (i = 0; i < nm_ip4_config_get_num_domains (ip4); i++)
-		g_ptr_array_add (domains, g_strdup (nm_ip4_config_get_domain (ip4, i)));
+		domains = g_ptr_array_new_with_free_func (g_free);
 
-	/* Add addresses and routes in CIDR form */
+		get_ip_domains (domains, NM_IP_CONFIG_CAST (ip4_config));
+		get_ip_domains (domains, NM_IP_CONFIG_CAST (ip6_config));
 
-	nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, ip4, &address) {
-		cidr = g_strdup_printf ("%s/%u",
-		                        nm_utils_inet4_ntop (address->address, sbuf),
-		                        address->plen);
-		g_ptr_array_add (domains, cidr);
+		if (domains->len > 0) {
+			g_variant_builder_add (&builder, "{sv}",
+			                       "Domains",
+			                       g_variant_new_strv ((const char *const*) domains->pdata,
+			                                           domains->len));
+		}
 	}
 
-	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, sbuf),
-		                        routes->plen);
-		g_ptr_array_add (domains, cidr);
-	}
+	return g_variant_new ("(a{sv})", &builder);
 }
 
+/*****************************************************************************/
+
 static void
-get_ip6_domains (GPtrArray *domains, NMIP6Config *ip6)
+_call_destroy_proxy_configuration_cb (GObject *source,
+                                      GAsyncResult *res,
+                                      gpointer user_data)
 {
-	NMDedupMultiIter ipconf_iter;
-	char *cidr;
-	const NMPlatformIP6Address *address;
-	const NMPlatformIP6Route *routes;
-	guint i;
-	char sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-	/* Extract searches */
-	for (i = 0; i < nm_ip6_config_get_num_searches (ip6); i++)
-		g_ptr_array_add (domains, g_strdup (nm_ip6_config_get_search (ip6, i)));
-
-	/* Extract domains */
-	for (i = 0; i < nm_ip6_config_get_num_domains (ip6); i++)
-		g_ptr_array_add (domains, g_strdup (nm_ip6_config_get_domain (ip6, i)));
-
-	/* Add addresses and routes in CIDR form */
-	nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6, &address) {
-		cidr = g_strdup_printf ("%s/%u",
-		                        nm_utils_inet6_ntop (&address->address, sbuf),
-		                        address->plen);
-		g_ptr_array_add (domains, cidr);
-	}
+	nm_auto_unref_conf_id NMPacrunnerConfId *conf_id = user_data;
+	gs_free_error GError *error = NULL;
+	gs_unref_variant GVariant *ret = NULL;
 
-	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, sbuf),
-		                        routes->plen);
-		g_ptr_array_add (domains, cidr);
+	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
+	if (!ret) {
+		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			_LOG2T (conf_id, "destroy proxy configuration: failed with %s", error->message);
+		else
+			_LOG2T (conf_id, "destroy proxy configuration: cancelled");
+		return;
 	}
-}
-
-/*****************************************************************************/
-
-static GCancellable *
-_ensure_cancellable (NMPacrunnerManagerPrivate *priv)
-{
-	if (G_UNLIKELY (!priv->cancellable))
-		priv->cancellable = g_cancellable_new ();
-	return priv->cancellable;
+	_LOG2T (conf_id, "destroy proxy configuration: success");
 }
 
 static void
-pacrunner_send_done (GObject *source, GAsyncResult *res, gpointer user_data)
+_call_create_proxy_configuration_cb (GObject *source,
+                                     GAsyncResult *res,
+                                     gpointer user_data)
 {
-	Config *config = user_data;
-	NMPacrunnerManager *self;
-	NMPacrunnerManagerPrivate *priv;
+	nm_auto_unref_conf_id NMPacrunnerConfId *conf_id = user_data;
+	NMPacrunnerManager *self = NM_PACRUNNER_MANAGER (conf_id->self);
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *variant = NULL;
 	const char *path = NULL;
 
-	nm_assert (!config->path);
+	nm_assert (!conf_id->path);
 
-	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;
+	variant = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
 
-	self = NM_PACRUNNER_MANAGER (config->manager_maybe_dangling);
-	priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
-
-	if (!variant)
-		_LOG2D (config, "sending failed: %s", error->message);
-	else {
-		g_variant_get (variant, "(&o)", &path);
-
-		if (c_list_is_empty (&config->lst)) {
-			_LOG2D (config, "sent (%s), but destroy it right away", path);
-			g_dbus_proxy_call (priv->pacrunner,
-			                   "DestroyProxyConfiguration",
-			                   g_variant_new ("(o)", path),
-			                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
-			                   -1,
-			                   _ensure_cancellable (priv),
-			                   pacrunner_remove_done,
-			                   config_ref (config));
-		} else {
-			_LOG2D (config, "sent (%s)", path);
-			config->path = g_strdup (path);
-		}
+	if (!variant) {
+		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+			_LOG2T (conf_id, "create proxy configuration failed: %s", error->message);
+		else
+			_LOG2T (conf_id, "create proxy configuration cancelled");
+		return;
 	}
 
-out:
-	config_unref (config);
+	g_variant_get (variant, "(&o)", &path);
+
+	if (c_list_is_empty (&conf_id->conf_id_lst)) {
+		_LOG2T (conf_id, "create proxy configuration succeeded (%s), but destroy it right away", path);
+		_call_destroy_proxy_configuration (self,
+		                                   conf_id,
+		                                   path,
+		                                   FALSE);
+	} else {
+		_LOG2T (conf_id, "create proxy configuration succeeded (%s)", path);
+		conf_id->path = g_strdup (path);
+	}
 }
 
 static void
-pacrunner_send_config (NMPacrunnerManager *self, Config *config)
+_call_destroy_proxy_configuration (NMPacrunnerManager *self,
+                                   NMPacrunnerConfId *conf_id,
+                                   const char *path,
+                                   gboolean verbose_log)
 {
 	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
-	if (priv->pacrunner) {
-		_LOG2T (config, "sending...");
-
-		nm_assert (!config->path);
-		g_dbus_proxy_call (priv->pacrunner,
-		                   "CreateProxyConfiguration",
-		                   config->args,
-		                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
-		                   -1,
-		                   _ensure_cancellable (priv),
-		                   pacrunner_send_done,
-		                   config_ref (config));
-	}
+	if (verbose_log)
+		_LOG2T (conf_id, "destroy proxy configuration %s...", path);
+
+	g_dbus_connection_call (priv->dbus_connection,
+	                        PACRUNNER_DBUS_SERVICE,
+	                        PACRUNNER_DBUS_PATH,
+	                        PACRUNNER_DBUS_INTERFACE,
+	                        "DestroyProxyConfiguration",
+	                        g_variant_new ("(o)", path),
+	                        G_VARIANT_TYPE ("()"),
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        NM_SHUTDOWN_TIMEOUT_MS,
+	                        priv->cancellable,
+	                        _call_destroy_proxy_configuration_cb,
+	                        conf_id_ref (conf_id));
 }
 
 static void
-name_owner_changed (NMPacrunnerManager *self)
+_call_create_proxy_configuration (NMPacrunnerManager *self,
+                                  NMPacrunnerConfId *conf_id,
+                                  gboolean verbose_log)
 {
 	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
-	gs_free char *owner = NULL;
-	CList *iter;
-
-	owner = g_dbus_proxy_get_name_owner (priv->pacrunner);
-	if (owner) {
-		_LOGD ("name owner appeared (%s)", owner);
-		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);
-	}
-}
 
-static void
-name_owner_changed_cb (GObject *object,
-                       GParamSpec *pspec,
-                       gpointer user_data)
-{
-	name_owner_changed (user_data);
+	if (verbose_log)
+		_LOG2T (conf_id, "create proxy configuration...");
+
+	g_dbus_connection_call (priv->dbus_connection,
+	                        PACRUNNER_DBUS_SERVICE,
+	                        PACRUNNER_DBUS_PATH,
+	                        PACRUNNER_DBUS_INTERFACE,
+	                        "CreateProxyConfiguration",
+	                        conf_id->parameters,
+	                        G_VARIANT_TYPE ("(o)"),
+	                        G_DBUS_CALL_FLAGS_NO_AUTO_START,
+	                        NM_SHUTDOWN_TIMEOUT_MS,
+	                        priv->cancellable,
+	                        _call_create_proxy_configuration_cb,
+	                        conf_id_ref (conf_id));
 }
 
-static void
-pacrunner_proxy_cb (GObject *source, GAsyncResult *res, gpointer user_data)
+static gboolean
+_try_start_service_by_name (NMPacrunnerManager *self)
 {
-	NMPacrunnerManager *self = user_data;
-	NMPacrunnerManagerPrivate *priv;
-	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))
-			_LOGE ("failed to create D-Bus proxy for pacrunner: %s", error->message);
-		return;
-	}
+	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
-	priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
+	if (   priv->try_start_blocked
+	    || !priv->dbus_initied)
+		return FALSE;
 
-	priv->pacrunner = proxy;
-	g_signal_connect (priv->pacrunner, "notify::g-name-owner",
-	                  G_CALLBACK (name_owner_changed_cb), self);
-	name_owner_changed (self);
+	_LOGD ("try D-Bus activating pacrunner...");
+	priv->try_start_blocked = TRUE;
+	nm_dbus_connection_call_start_service_by_name (priv->dbus_connection,
+	                                               PACRUNNER_DBUS_SERVICE,
+	                                               -1,
+	                                               NULL,
+	                                               NULL,
+	                                               NULL);
+	return TRUE;
 }
 
+/*****************************************************************************/
+
 /**
- * nm_pacrunner_manager_send:
+ * nm_pacrunner_manager_add:
  * @self: the #NMPacrunnerManager
- * @iface: the iface for the connection or %NULL
  * @proxy_config: proxy config of the connection
+ * @iface: the iface for the connection or %NULL
  * @ip4_config: IP4 config of the connection to extract domain info from
  * @ip6_config: IP6 config of the connection to extract domain info from
  *
- * Returns: a #NMPacrunnerCallId call id. The function cannot
- *  fail and always returns a non NULL pointer. The call-id may
+ * Returns: a #NMPacrunnerConfId id. The function cannot
+ *  fail and always returns a non NULL pointer. The conf-id may
  *  be used to remove the configuration later via nm_pacrunner_manager_remove().
- *  Note that the call-id does not keep the @self instance alive.
- *  If you plan to remove the configuration later, you must keep
- *  the instance alive long enough. You can remove the configuration
- *  at most once using this call call-id.
+ *  Note that the conf-id keeps the @self instance alive.
  */
-NMPacrunnerCallId *
-nm_pacrunner_manager_send (NMPacrunnerManager *self,
-                           const char *iface,
-                           NMProxyConfig *proxy_config,
-                           NMIP4Config *ip4_config,
-                           NMIP6Config *ip6_config)
+NMPacrunnerConfId *
+nm_pacrunner_manager_add (NMPacrunnerManager *self,
+                          NMProxyConfig *proxy_config,
+                          const char *iface,
+                          NMIP4Config *ip4_config,
+                          NMIP6Config *ip6_config)
 {
-	char **strv = NULL;
-	NMProxyConfigMethod method;
 	NMPacrunnerManagerPrivate *priv;
-	GVariantBuilder proxy_data;
-	GPtrArray *domains;
-	Config *config;
+	NMPacrunnerConfId *conf_id;
+	gs_free char *log_msg = NULL;
 
 	g_return_val_if_fail (NM_IS_PACRUNNER_MANAGER (self), NULL);
 	g_return_val_if_fail (proxy_config, NULL);
 
 	priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
-	g_free (priv->iface);
-	priv->iface = g_strdup (iface);
-
-	g_variant_builder_init (&proxy_data, G_VARIANT_TYPE_VARDICT);
-
-	if (iface) {
-		g_variant_builder_add (&proxy_data, "{sv}",
-		                       "Interface",
-		                       g_variant_new_string (iface));
+	conf_id = g_slice_new (NMPacrunnerConfId);
+	*conf_id = (NMPacrunnerConfId) {
+		.log_id     = ++priv->log_id_counter,
+		.refcount   = 1,
+		.self       = g_object_ref (self),
+		.parameters = g_variant_ref_sink (_make_request_create_proxy_configuration (proxy_config,
+		                                                                            iface,
+		                                                                            ip4_config,
+		                                                                            ip6_config)),
+	};
+	c_list_link_tail (&priv->conf_id_lst_head,
+	                  &conf_id->conf_id_lst);
+
+	if (!priv->has_name_owner) {
+		_LOG2T (conf_id, "add config: %s (%s)",
+		        (log_msg = g_variant_print (conf_id->parameters, FALSE)),
+		        "pacrunner D-Bus service not running");
+		_try_start_service_by_name (self);
+	} else {
+		_LOG2T (conf_id, "add config: %s (%s)",
+		        (log_msg = g_variant_print (conf_id->parameters, FALSE)),
+		        "create proxy configuration");
+		_call_create_proxy_configuration (self, conf_id, FALSE);
 	}
 
-	method = nm_proxy_config_get_method (proxy_config);
-	switch (method) {
-	case NM_PROXY_CONFIG_METHOD_AUTO:
-		g_variant_builder_add (&proxy_data, "{sv}",
-		                       "Method",
-		                       g_variant_new_string ("auto"));
+	return conf_id;
+}
 
-		break;
-	case NM_PROXY_CONFIG_METHOD_NONE:
-		g_variant_builder_add (&proxy_data, "{sv}",
-		                       "Method",
-		                       g_variant_new_string ("direct"));
-	}
+/**
+ * nm_pacrunner_manager_remove:
+ * @conf_id: the conf id obtained from nm_pacrunner_manager_add()
+ */
+void
+nm_pacrunner_manager_remove (NMPacrunnerConfId *conf_id)
+{
+	_nm_unused nm_auto_unref_conf_id NMPacrunnerConfId *conf_id_free = conf_id;
+	NMPacrunnerManager *self;
+	NMPacrunnerManagerPrivate *priv;
 
-	/* Extract stuff from configs */
-	add_proxy_config (&proxy_data, proxy_config);
+	g_return_if_fail (conf_id);
 
-	if (ip4_config || ip6_config) {
-		domains = g_ptr_array_new_with_free_func (g_free);
+	self = conf_id->self;
 
-		if (ip4_config)
-			get_ip4_domains (domains, ip4_config);
-		if (ip6_config)
-			get_ip6_domains (domains, ip6_config);
+	g_return_if_fail (NM_IS_PACRUNNER_MANAGER (self));
 
-		g_ptr_array_add (domains, NULL);
-		strv = (char **) g_ptr_array_free (domains, (domains->len == 1));
+	priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
-		if (strv) {
-			g_variant_builder_add (&proxy_data, "{sv}",
-			                       "Domains",
-			                       g_variant_new_strv ((const char *const *) strv, -1));
-			g_strfreev (strv);
-		}
+	_LOG2T (conf_id, "removing...");
+
+	nm_assert (c_list_contains (&priv->conf_id_lst_head, &conf_id->conf_id_lst));
+
+	c_list_unlink (&conf_id->conf_id_lst);
+
+	if (!conf_id->path) {
+		/* There is no ID to destroy the configuration.
+		 *
+		 * That can happen because:
+		 *
+		 *  - pacrunner D-Bus service is not running (no name owner) and we didn't call CreateProxyConfiguration.
+		 *  - CreateProxyConfiguration failed.
+		 *  - CreateProxyConfiguration is in progress.
+		 *
+		 * In all cases there is nothing to do. Note that if CreateProxyConfiguration is in progress
+		 * it has a reference on the conf-id and it will automatically destroy the configuration
+		 * when it completes.
+		 */
+		return;
 	}
 
-	config = config_new (self, g_variant_new ("(a{sv})", &proxy_data));
-
-	{
-		gs_free char *args_str = NULL;
-
-		_LOG2D (config, "send: new config %s",
-		        (args_str = g_variant_print (config->args, FALSE)));
-	}
+	_call_destroy_proxy_configuration (self, conf_id, conf_id->path, TRUE);
+}
 
-	/* Send if pacrunner is available on bus, otherwise
-	 * config has already been appended above to be
-	 * sent when pacrunner appears.
-	 */
-	pacrunner_send_config (self, config);
+gboolean
+nm_pacrunner_manager_remove_clear (NMPacrunnerConfId **p_conf_id)
+{
+	g_return_val_if_fail (p_conf_id, FALSE);
 
-	return config;
+	if (!*p_conf_id)
+		return FALSE;
+	nm_pacrunner_manager_remove (g_steal_pointer (p_conf_id));
+	return TRUE;
 }
 
+/*****************************************************************************/
+
 static void
-pacrunner_remove_done (GObject *source, GAsyncResult *res, gpointer user_data)
+name_owner_changed (NMPacrunnerManager *self,
+                    const char *name_owner)
 {
-	Config *config = user_data;
-	gs_free_error GError *error = NULL;
-	gs_unref_variant GVariant *ret = NULL;
+	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
+	NMPacrunnerConfId *conf_id;
+	gboolean has_name_owner;
 
-	ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (source), res, &error);
-	if (!ret) {
-		if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-			goto out;
-		_LOG2D (config, "remove failed: %s", error->message);
-		goto out;
-	}
+	has_name_owner = (name_owner && name_owner[0]);
+
+	if (   priv->dbus_initied
+	    && priv->has_name_owner == has_name_owner)
+		return;
+
+	priv->has_name_owner = has_name_owner;
 
-	_LOG2D (config, "removed");
+	nm_clear_g_cancellable (&priv->cancellable);
 
-out:
-	config_unref (config);
+	if (has_name_owner) {
+		priv->dbus_initied = TRUE;
+		priv->try_start_blocked = FALSE;
+		_LOGD ("pacrunner appeared on D-Bus (%s)", name_owner);
+		priv->cancellable = g_cancellable_new ();
+		c_list_for_each_entry (conf_id, &priv->conf_id_lst_head, conf_id_lst)
+			_call_create_proxy_configuration (self, conf_id, TRUE);
+	} else {
+		if (!priv->dbus_initied) {
+			priv->dbus_initied = TRUE;
+			nm_assert (!priv->try_start_blocked);
+			_LOGD ("pacrunner not on D-Bus");
+		} else
+			_LOGD ("pacrunner disappeared from D-Bus");
+		if (!c_list_is_empty (&priv->conf_id_lst_head)) {
+			c_list_for_each_entry (conf_id, &priv->conf_id_lst_head, conf_id_lst)
+				nm_clear_g_free (&conf_id->path);
+			_try_start_service_by_name (self);
+		}
+	}
 }
 
-/**
- * nm_pacrunner_manager_remove:
- * @self: the #NMPacrunnerManager
- * @call_id: the call-id obtained from nm_pacrunner_manager_send()
- */
-void
-nm_pacrunner_manager_remove (NMPacrunnerManager *self, NMPacrunnerCallId *call_id)
+static void
+name_owner_changed_cb (GDBusConnection *connection,
+                       const char *sender_name,
+                       const char *object_path,
+                       const char *interface_name,
+                       const char *signal_name,
+                       GVariant *parameters,
+                       gpointer user_data)
 {
-	NMPacrunnerManagerPrivate *priv;
-	Config *config;
+	const char *new_owner;
 
-	g_return_if_fail (NM_IS_PACRUNNER_MANAGER (self));
-	g_return_if_fail (call_id);
-
-	config = call_id;
-	priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
+	if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sss)")))
+		return;
 
-	_LOG2T (config, "removing...");
-
-	nm_assert (c_list_contains (&priv->configs, &config->lst));
-
-	if (priv->pacrunner) {
-		if (!config->path) {
-			/* send() failed or is still pending. The item is unlinked from
-			 * priv->configs, so pacrunner_send_done() knows to call
-			 * DestroyProxyConfiguration right away.
-			 */
-		} else {
-			g_dbus_proxy_call (priv->pacrunner,
-			                   "DestroyProxyConfiguration",
-			                   g_variant_new ("(o)", config->path),
-			                   G_DBUS_CALL_FLAGS_NO_AUTO_START,
-			                   -1,
-			                   _ensure_cancellable (priv),
-			                   pacrunner_remove_done,
-			                   config_ref (config));
-			nm_clear_g_free (&config->path);
-		}
-	}
+	g_variant_get (parameters,
+	               "(&s&s&s)",
+	               NULL,
+	               NULL,
+	               &new_owner);
 
-	c_list_unlink (&config->lst);
-	config_unref (config);
+	name_owner_changed (user_data, new_owner);
 }
 
-gboolean
-nm_pacrunner_manager_remove_clear (NMPacrunnerManager *self,
-                                   NMPacrunnerCallId **p_call_id)
+static void
+get_name_owner_cb (const char *name_owner,
+                   GError *error,
+                   gpointer user_data)
 {
-	g_return_val_if_fail (p_call_id, FALSE);
-
-	/* if we have no call-id, allow for %NULL */
-	g_return_val_if_fail ((!self && !*p_call_id) || NM_IS_PACRUNNER_MANAGER (self), FALSE);
+	if (   !name_owner
+	    && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+		return;
 
-	if (!*p_call_id)
-		return FALSE;
-	nm_pacrunner_manager_remove (self,
-	                             g_steal_pointer (p_call_id));
-	return TRUE;
+	name_owner_changed (user_data, name_owner);
 }
 
 /*****************************************************************************/
@@ -551,28 +597,36 @@ nm_pacrunner_manager_init (NMPacrunnerManager *self)
 {
 	NMPacrunnerManagerPrivate *priv = NM_PACRUNNER_MANAGER_GET_PRIVATE (self);
 
-	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,
-	                          _ensure_cancellable (priv),
-	                          pacrunner_proxy_cb,
-	                          self);
+	c_list_init (&priv->conf_id_lst_head);
+
+	priv->dbus_connection = nm_g_object_ref (NM_MAIN_DBUS_CONNECTION_GET);
+
+	if (!priv->dbus_connection) {
+		_LOGD ("no D-Bus connection to talk to pacrunner");
+		return;
+	}
+
+	priv->name_owner_changed_id = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->dbus_connection,
+	                                                                                      PACRUNNER_DBUS_SERVICE,
+	                                                                                      name_owner_changed_cb,
+	                                                                                      self,
+	                                                                                      NULL);
+	priv->cancellable = g_cancellable_new ();
+
+	nm_dbus_connection_call_get_name_owner (priv->dbus_connection,
+	                                        PACRUNNER_DBUS_SERVICE,
+	                                        -1,
+	                                        priv->cancellable,
+	                                        get_name_owner_cb,
+	                                        self);
 }
 
 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 (iter);
-		config_unref (c_list_entry (iter, Config, lst));
-	}
+	nm_assert (c_list_is_empty (&priv->conf_id_lst_head));
 
 	/* we cancel all pending operations. Note that pacrunner automatically
 	 * removes all configuration once NetworkManager disconnects from
@@ -580,8 +634,9 @@ dispose (GObject *object)
 	 */
 	nm_clear_g_cancellable (&priv->cancellable);
 
-	g_clear_pointer (&priv->iface, g_free);
-	g_clear_object (&priv->pacrunner);
+	nm_clear_g_dbus_connection_signal (priv->dbus_connection,
+	                                   &priv->name_owner_changed_id);
+	g_clear_object (&priv->dbus_connection);
 
 	G_OBJECT_CLASS (nm_pacrunner_manager_parent_class)->dispose (object);
 }
diff --git a/src/nm-pacrunner-manager.h b/src/nm-pacrunner-manager.h
index 3080c4f5..f3f41e9c 100644
--- a/src/nm-pacrunner-manager.h
+++ b/src/nm-pacrunner-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -31,22 +30,20 @@
 
 typedef struct _NMPacrunnerManagerClass NMPacrunnerManagerClass;
 
-typedef struct _NMPacrunnerCallId NMPacrunnerCallId;
+typedef struct _NMPacrunnerConfId NMPacrunnerConfId;
 
 GType nm_pacrunner_manager_get_type (void);
 
 NMPacrunnerManager *nm_pacrunner_manager_get (void);
 
-NMPacrunnerCallId *nm_pacrunner_manager_send (NMPacrunnerManager *self,
-                                              const char *iface,
-                                              NMProxyConfig *proxy_config,
-                                              NMIP4Config *ip4_config,
-                                              NMIP6Config *ip6_config);
+NMPacrunnerConfId *nm_pacrunner_manager_add (NMPacrunnerManager *self,
+                                             NMProxyConfig *proxy_config,
+                                             const char *iface,
+                                             NMIP4Config *ip4_config,
+                                             NMIP6Config *ip6_config);
 
-void nm_pacrunner_manager_remove (NMPacrunnerManager *self,
-                                  NMPacrunnerCallId *call_id);
+void nm_pacrunner_manager_remove (NMPacrunnerConfId *conf_id);
 
-gboolean nm_pacrunner_manager_remove_clear (NMPacrunnerManager *self,
-                                            NMPacrunnerCallId **p_call_id);
+gboolean nm_pacrunner_manager_remove_clear (NMPacrunnerConfId **p_conf_id);
 
 #endif /* __NETWORKMANAGER_PACRUNNER_MANAGER_H__ */
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 1faba5c7..559babed 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -1847,8 +1846,7 @@ device_state_changed (NMDevice *device,
 				}
 			}
 
-			/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-			nm_connection_clear_secrets (nm_settings_connection_get_connection (sett_conn));
+			nm_settings_connection_clear_secrets (sett_conn, FALSE, FALSE);
 		}
 		break;
 	case NM_DEVICE_STATE_ACTIVATED:
@@ -1859,9 +1857,7 @@ device_state_changed (NMDevice *device,
 			/* And clear secrets so they will always be requested from the
 			 * settings service when the next connection is made.
 			 */
-
-			/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-			nm_connection_clear_secrets (nm_settings_connection_get_connection (sett_conn));
+			nm_settings_connection_clear_secrets (sett_conn, FALSE, FALSE);
 		}
 
 		/* Add device's new IPv4 and IPv6 configs to DNS */
@@ -2400,29 +2396,22 @@ dns_config_changed (NMDnsManager *dns_manager, gpointer user_data)
 static void
 connection_updated (NMSettings *settings,
                     NMSettingsConnection *connection,
-                    gboolean by_user,
+                    guint update_reason_u,
                     gpointer user_data)
 {
 	NMPolicyPrivate *priv = user_data;
 	NMPolicy *self = _PRIV_TO_SELF (priv);
-	const CList *tmp_lst;
-	NMDevice *device = NULL;
-	NMDevice *dev;
+	NMSettingsConnectionUpdateReason update_reason = update_reason_u;
+
+	if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)) {
+		const CList *tmp_lst;
+		NMDevice *device;
 
-	if (by_user) {
 		/* find device with given connection */
-		nm_manager_for_each_device (priv->manager, dev, tmp_lst) {
-			if (nm_device_get_settings_connection (dev) == connection) {
-				device = dev;
-				break;
-			}
+		nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+			if (nm_device_get_settings_connection (device) == connection)
+				nm_device_reapply_settings_immediately (device);
 		}
-
-		if (device)
-			nm_device_reapply_settings_immediately (device);
-
-		/* Reset auto retries back to default since connection was updated */
-		nm_settings_connection_autoconnect_retries_reset (connection);
 	}
 
 	schedule_activate_all (self);
diff --git a/src/nm-policy.h b/src/nm-policy.h
index 916ede00..dea500bf 100644
--- a/src/nm-policy.h
+++ b/src/nm-policy.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-proxy-config.c b/src/nm-proxy-config.c
index e051367a..5a7a85c0 100644
--- a/src/nm-proxy-config.c
+++ b/src/nm-proxy-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-proxy-config.h b/src/nm-proxy-config.h
index 2712e43f..050e0d98 100644
--- a/src/nm-proxy-config.h
+++ b/src/nm-proxy-config.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-rfkill-manager.c b/src/nm-rfkill-manager.c
index cf2e7fe9..c276fc77 100644
--- a/src/nm-rfkill-manager.c
+++ b/src/nm-rfkill-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-rfkill-manager.h b/src/nm-rfkill-manager.h
index 490c1ca2..5f2d3adb 100644
--- a/src/nm-rfkill-manager.h
+++ b/src/nm-rfkill-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/nm-session-monitor.c b/src/nm-session-monitor.c
index e8c25fd6..8cb1b88d 100644
--- a/src/nm-session-monitor.c
+++ b/src/nm-session-monitor.c
@@ -1,4 +1,3 @@
-/* -*- 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 of the License, or
@@ -44,7 +43,7 @@
 
 #define SESSION_TRACKING_XLOGIND (SESSION_TRACKING_SYSTEMD || SESSION_TRACKING_ELOGIND)
 
-#define CKDB_PATH "/var/run/ConsoleKit/database"
+#define CKDB_PATH "/run/ConsoleKit/database"
 
 /*****************************************************************************/
 
diff --git a/src/nm-session-monitor.h b/src/nm-session-monitor.h
index d3d10e98..43f9eef9 100644
--- a/src/nm-session-monitor.h
+++ b/src/nm-session-monitor.h
@@ -1,4 +1,3 @@
-/* -*- 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 of the License, or
diff --git a/src/nm-sleep-monitor.c b/src/nm-sleep-monitor.c
index 7e0ebe6c..c97f6f7d 100644
--- a/src/nm-sleep-monitor.c
+++ b/src/nm-sleep-monitor.c
@@ -1,4 +1,3 @@
-/* -*- 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 of the License, or
diff --git a/src/nm-sleep-monitor.h b/src/nm-sleep-monitor.h
index 350f1eec..da1fd8cf 100644
--- a/src/nm-sleep-monitor.h
+++ b/src/nm-sleep-monitor.h
@@ -1,4 +1,3 @@
-/* -*- 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 of the License, or
diff --git a/src/nm-test-utils-core.h b/src/nm-test-utils-core.h
index 7f8b3327..1f4b87ef 100644
--- a/src/nm-test-utils-core.h
+++ b/src/nm-test-utils-core.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
diff --git a/src/nm-types.h b/src/nm-types.h
index 03ee9929..fae344f2 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nm-fake-platform.c b/src/platform/nm-fake-platform.c
index 40a85390..967aaee8 100644
--- a/src/platform/nm-fake-platform.c
+++ b/src/platform/nm-fake-platform.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-platform-fake.c - Fake platform interaction code for testing NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
@@ -276,9 +275,9 @@ link_add_pre (NMPlatform *platform,
 	o->_link.netlink.is_in_netlink = TRUE;
 
 	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;
+		g_assert (address_len > 0 && address_len <= sizeof (link->l_address.data));
+		memcpy (link->l_address.data, address, address_len);
+		link->l_address.len = address_len;
 	} else
 		g_assert (address_len == 0);
 
@@ -584,9 +583,9 @@ link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t
 		return -NME_PL_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);
+	obj_tmp->link.l_address.len = len;
+	memset (obj_tmp->link.l_address.data, 0, sizeof (obj_tmp->link.l_address.data));
+	memcpy (obj_tmp->link.l_address.data, addr, len);
 
 	link_set_obj (platform, device, obj_tmp);
 	return 0;
diff --git a/src/platform/nm-fake-platform.h b/src/platform/nm-fake-platform.h
index 8f42d11a..d5be8c65 100644
--- a/src/platform/nm-fake-platform.h
+++ b/src/platform/nm-fake-platform.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-fake-platform.h - Fake platform interaction code for testing NetworkManager
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index d4b01152..e2e1e581 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-linux-platform.c - Linux kernel & udev network configuration layer
  *
  * This program is free software; you can redistribute it and/or modify
@@ -34,6 +33,7 @@
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/ip6_tunnel.h>
+#include <linux/tc_act/tc_mirred.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
 #include <poll.h>
@@ -84,6 +84,13 @@ enum {
 
 /*****************************************************************************/
 
+/* Compat with older kernels. */
+
+#define TCA_FQ_CODEL_CE_THRESHOLD 7
+#define TCA_FQ_CODEL_MEMORY_LIMIT 9
+
+/*****************************************************************************/
+
 #define VLAN_FLAG_MVRP 0x8
 
 /*****************************************************************************/
@@ -732,29 +739,27 @@ _timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
 static guint32
 _addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 {
-	struct timespec tp;
-	gint64 now_nl, now_nm, result;
-	int err;
+	gint64 now_nl;
+	gint64 now_nm;
+	gint64 result;
 
 	/* timestamp is unset. Default to 1. */
 	if (!timestamp) {
-		if (out_now_nm)
-			*out_now_nm = 0;
+		NM_SET_OUT (out_now_nm, 0);
 		return 1;
 	}
 
 	/* do all the calculations in milliseconds scale */
 
-	err = clock_gettime (CLOCK_MONOTONIC, &tp);
-	g_assert (err == 0);
 	now_nm = nm_utils_get_monotonic_timestamp_ms ();
-	now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) +
-	         (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
+	now_nl = nm_utils_clock_gettime_ms (CLOCK_MONOTONIC);
+
+	nm_assert (now_nm >= 1000);
+	nm_assert (now_nl >= 0);
 
 	result = now_nm - (now_nl - _timestamp_nl_to_ms (timestamp, now_nl));
 
-	if (out_now_nm)
-		*out_now_nm = now_nm / 1000;
+	NM_SET_OUT (out_now_nm, now_nm / 1000);
 
 	/* converting the timestamp into nm_utils_get_monotonic_timestamp_ms() scale is
 	 * a good guess but fails in the following situations:
@@ -2579,6 +2584,25 @@ link_wireguard_change (NMPlatform *platform,
 
 /*****************************************************************************/
 
+static void
+_nmp_link_address_set (NMPLinkAddress *dst,
+                       const struct nlattr *nla)
+{
+	*dst = (NMPLinkAddress) {
+		.len = 0,
+	};
+	if (nla) {
+		int l = nla_len (nla);
+
+		if (   l > 0
+		    && l <= NM_UTILS_HWADDR_LEN_MAX) {
+			G_STATIC_ASSERT_EXPR (sizeof (dst->data) == NM_UTILS_HWADDR_LEN_MAX);
+			memcpy (dst->data, nla_data (nla), l);
+			dst->len = l;
+		}
+	}
+}
+
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
 static NMPObject *
 _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr *nlh, gboolean id_only)
@@ -2622,6 +2646,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	const NMPObject *link_cached = NULL;
 	const NMPObject *lnk_data = NULL;
 	gboolean address_complete_from_cache = TRUE;
+	gboolean broadcast_complete_from_cache = TRUE;
 	gboolean lnk_data_complete_from_cache = TRUE;
 	gboolean need_ext_data = FALSE;
 	gboolean af_inet6_token_valid = FALSE;
@@ -2720,16 +2745,15 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	}
 
 	if (tb[IFLA_ADDRESS]) {
-		int l = nla_len (tb[IFLA_ADDRESS]);
-
-		if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
-			G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->link.addr.data));
-			memcpy (obj->link.addr.data, nla_data (tb[IFLA_ADDRESS]), l);
-			obj->link.addr.len = l;
-		}
+		_nmp_link_address_set (&obj->link.l_address, tb[IFLA_ADDRESS]);
 		address_complete_from_cache = FALSE;
 	}
 
+	if (tb[IFLA_BROADCAST]) {
+		_nmp_link_address_set (&obj->link.l_broadcast, tb[IFLA_BROADCAST]);
+		broadcast_complete_from_cache = FALSE;
+	}
+
 	if (tb[IFLA_AF_SPEC]) {
 		struct nlattr *af_attr;
 		int remaining;
@@ -2803,6 +2827,7 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 	    && (   lnk_data_complete_from_cache
 	        || need_ext_data
 	        || address_complete_from_cache
+	        || broadcast_complete_from_cache
 	        || !af_inet6_token_valid
 	        || !af_inet6_addr_gen_mode_valid
 	        || !tb[IFLA_STATS64])) {
@@ -2832,7 +2857,9 @@ _new_from_nl_link (NMPlatform *platform, const NMPCache *cache, struct nlmsghdr
 			}
 
 			if (address_complete_from_cache)
-				obj->link.addr = link_cached->link.addr;
+				obj->link.l_address = link_cached->link.l_address;
+			if (broadcast_complete_from_cache)
+				obj->link.l_broadcast = link_cached->link.l_broadcast;
 			if (!af_inet6_token_valid)
 				obj->link.inet6_token = link_cached->link.inet6_token;
 			if (!af_inet6_addr_gen_mode_valid)
@@ -3481,6 +3508,7 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 {
 	static const struct nla_policy policy[] = {
 		[TCA_KIND] = { .type = NLA_STRING },
+		[TCA_OPTIONS] = { .type = NLA_NESTED },
 	};
 	struct nlattr *tb[G_N_ELEMENTS (policy)];
 	const struct tcmsg *tcm;
@@ -3506,6 +3534,50 @@ _new_from_nl_qdisc (struct nlmsghdr *nlh, gboolean id_only)
 	obj->qdisc.parent = tcm->tcm_parent;
 	obj->qdisc.info = tcm->tcm_info;
 
+	if (nm_streq0 (obj->qdisc.kind, "fq_codel")) {
+		obj->qdisc.fq_codel.memory_limit = NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET;
+		obj->qdisc.fq_codel.ce_threshold = NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED;
+	}
+
+	if (tb[TCA_OPTIONS]) {
+		struct nlattr *options_attr;
+		int remaining;
+
+		nla_for_each_nested (options_attr, tb[TCA_OPTIONS], remaining) {
+			if (nla_len (options_attr) < sizeof (uint32_t))
+				continue;
+
+			if (nm_streq0 (obj->qdisc.kind, "fq_codel")) {
+				switch (nla_type (options_attr)) {
+				case TCA_FQ_CODEL_LIMIT:
+					obj->qdisc.fq_codel.limit = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_FLOWS:
+					obj->qdisc.fq_codel.flows = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_TARGET:
+					obj->qdisc.fq_codel.target = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_INTERVAL:
+					obj->qdisc.fq_codel.interval = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_QUANTUM:
+					obj->qdisc.fq_codel.quantum = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_CE_THRESHOLD:
+					obj->qdisc.fq_codel.ce_threshold = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_MEMORY_LIMIT:
+					obj->qdisc.fq_codel.memory_limit = nla_get_u32 (options_attr);
+					break;
+				case TCA_FQ_CODEL_ECN:
+					obj->qdisc.fq_codel.ecn = !!nla_get_u32 (options_attr);
+					break;
+				}
+			}
+		}
+	}
+
 	return obj;
 }
 
@@ -3628,7 +3700,7 @@ _nl_msg_new_link_set_afspec (struct nl_msg *msg,
 
 	return TRUE;
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -3779,7 +3851,7 @@ _nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
 
 	return TRUE;
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static struct nl_msg *
@@ -4161,6 +4233,7 @@ _nl_msg_new_qdisc (int nlmsg_type,
                    const NMPlatformQdisc *qdisc)
 {
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
+	struct nlattr *tc_options;
 	const struct tcmsg tcm = {
 		.tcm_family = qdisc->addr_family,
 		.tcm_ifindex = qdisc->ifindex,
@@ -4176,55 +4249,34 @@ _nl_msg_new_qdisc (int nlmsg_type,
 
 	NLA_PUT_STRING (msg, TCA_KIND, qdisc->kind);
 
-	return g_steal_pointer (&msg);
-
-nla_put_failure:
-	g_return_val_if_reached (NULL);
-}
-
-static gboolean
-_add_action_simple (struct nl_msg *msg,
-                    const NMPlatformActionSimple *simple)
-{
-	struct nlattr *act_options;
-	struct tc_defact sel = { 0, };
-
-	if (!(act_options = nla_nest_start (msg, TCA_ACT_OPTIONS)))
-		goto nla_put_failure;
-
-	NLA_PUT (msg, TCA_DEF_PARMS, sizeof (sel), &sel);
-	NLA_PUT (msg, TCA_DEF_DATA, sizeof (simple->sdata), simple->sdata);
-
-	nla_nest_end (msg, act_options);
-
-	return TRUE;
-
-nla_put_failure:
-	return FALSE;
-}
-
-static gboolean
-_add_action (struct nl_msg *msg,
-             const NMPlatformAction *action)
-{
-	struct nlattr *prio;
-
-	nm_assert (action || action->kind);
-
-	if (!(prio = nla_nest_start (msg, 1 /* priority */)))
+	if (!(tc_options = nla_nest_start (msg, TCA_OPTIONS)))
 		goto nla_put_failure;
 
-	NLA_PUT_STRING (msg, TCA_ACT_KIND, action->kind);
-
-	if (nm_streq (action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
-		_add_action_simple (msg, &action->simple);
+	if (strcmp (qdisc->kind, "fq_codel") == 0) {
+		if (qdisc->fq_codel.limit)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_LIMIT, qdisc->fq_codel.limit);
+		if (qdisc->fq_codel.flows)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_FLOWS, qdisc->fq_codel.flows);
+		if (qdisc->fq_codel.target)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_TARGET, qdisc->fq_codel.target);
+		if (qdisc->fq_codel.interval)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_INTERVAL, qdisc->fq_codel.interval);
+		if (qdisc->fq_codel.quantum)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_QUANTUM, qdisc->fq_codel.quantum);
+		if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_CE_THRESHOLD, qdisc->fq_codel.ce_threshold);
+		if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_MEMORY_LIMIT, qdisc->fq_codel.memory_limit);
+		if (qdisc->fq_codel.ecn)
+			NLA_PUT_U32 (msg, TCA_FQ_CODEL_ECN, qdisc->fq_codel.ecn);
+	}
 
-	nla_nest_end (msg, prio);
+	nla_nest_end (msg, tc_options);
 
-	return TRUE;
+	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (NULL);
 }
 
 static struct nl_msg *
@@ -4256,8 +4308,52 @@ _nl_msg_new_tfilter (int nlmsg_type,
 	if (!(act_tab = nla_nest_start (msg, TCA_OPTIONS))) // 3 TCA_ACT_KIND TCA_ACT_KIND
 		goto nla_put_failure;
 
-	if (tfilter->action.kind)
-		_add_action (msg, &tfilter->action);
+	if (tfilter->action.kind) {
+		const NMPlatformAction *action = &tfilter->action;
+		struct nlattr *prio;
+		struct nlattr *act_options;
+
+		if (!(prio = nla_nest_start (msg, 1 /* priority */)))
+			goto nla_put_failure;
+
+		NLA_PUT_STRING (msg, TCA_ACT_KIND, action->kind);
+
+		if (nm_streq (action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
+			const NMPlatformActionSimple *simple = &action->simple;
+			struct tc_defact sel = { 0, };
+
+			if (!(act_options = nla_nest_start (msg, TCA_ACT_OPTIONS)))
+				goto nla_put_failure;
+
+			NLA_PUT (msg, TCA_DEF_PARMS, sizeof (sel), &sel);
+			NLA_PUT (msg, TCA_DEF_DATA, sizeof (simple->sdata), simple->sdata);
+
+			nla_nest_end (msg, act_options);
+
+		} else if (nm_streq (action->kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			const NMPlatformActionMirred *mirred = &action->mirred;
+			struct tc_mirred sel = { 0, };
+
+			if (!(act_options = nla_nest_start (msg, TCA_ACT_OPTIONS)))
+				goto nla_put_failure;
+
+			if (mirred->egress && mirred->redirect)
+				sel.eaction = TCA_EGRESS_REDIR;
+			else if (mirred->egress && mirred->mirror)
+				sel.eaction = TCA_EGRESS_MIRROR;
+			else if (mirred->ingress && mirred->redirect)
+				sel.eaction = TCA_INGRESS_REDIR;
+			else if (mirred->ingress && mirred->mirror)
+				sel.eaction = TCA_INGRESS_MIRROR;
+			sel.ifindex = mirred->ifindex;
+
+			NLA_PUT (msg, TCA_MIRRED_PARMS, sizeof (sel), &sel);
+
+			nla_nest_end (msg, act_options);
+		}
+
+		nla_nest_end (msg, prio);
+	}
 
 	nla_nest_end (msg, tc_options);
 
@@ -4298,6 +4394,14 @@ _genl_sock (NMLinuxPlatform *platform)
 		} \
 	} G_STMT_END
 
+/*****************************************************************************/
+
+/* core sysctl-set functions can be called from a non-main thread.
+ * Hence, we require locking from nm-logging. Indicate that by
+ * setting NM_THREAD_SAFE_ON_MAIN_THREAD to zero. */
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 0
+
 static void
 _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
 {
@@ -4308,18 +4412,18 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, c
 	if (nm_utils_file_get_contents (dirfd, path, 1*1024*1024,
 	                                NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE,
 	                                &contents, NULL, &error) < 0) {
-		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", pathid, value_escaped, error->message);
+		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", pathid ?: path, value_escaped, error->message);
 		g_clear_error (&error);
 		return;
 	}
 
 	g_strstrip (contents);
 	if (nm_streq (contents, value))
-		_LOGD ("sysctl: setting '%s' to '%s' (current value is identical)", pathid, value_escaped);
+		_LOGD ("sysctl: setting '%s' to '%s' (current value is identical)", pathid ?: path, value_escaped);
 	else {
 		gs_free char *contents_escaped = g_strescape (contents, NULL);
 
-		_LOGD ("sysctl: setting '%s' to '%s' (current value is '%s')", pathid, value_escaped, contents_escaped);
+		_LOGD ("sysctl: setting '%s' to '%s' (current value is '%s')", pathid ?: path, value_escaped, contents_escaped);
 	}
 	g_free (contents);
 }
@@ -4332,9 +4436,12 @@ _log_dbg_sysctl_set_impl (NMPlatform *platform, const char *pathid, int dirfd, c
 	} G_STMT_END
 
 static gboolean
-sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *path, const char *value)
+sysctl_set_internal (NMPlatform *platform,
+                     const char *pathid,
+                     int dirfd,
+                     const char *path,
+                     const char *value)
 {
-	nm_auto_pop_netns NMPNetns *netns = NULL;
 	int fd, tries;
 	gssize nwrote;
 	gssize len;
@@ -4342,17 +4449,7 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	gs_free char *actual_free = NULL;
 	int errsv;
 
-	g_return_val_if_fail (path != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-
-	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
-
 	if (dirfd < 0) {
-		if (!nm_platform_netns_push (platform, &netns)) {
-			errno = ENETDOWN;
-			return FALSE;
-		}
-
 		pathid = path;
 
 		fd = open (path, O_WRONLY | O_TRUNC | O_CLOEXEC);
@@ -4452,6 +4549,207 @@ sysctl_set (NMPlatform *platform, const char *pathid, int dirfd, const char *pat
 	return TRUE;
 }
 
+#undef NM_THREAD_SAFE_ON_MAIN_THREAD
+#define NM_THREAD_SAFE_ON_MAIN_THREAD 1
+
+/*****************************************************************************/
+
+static gboolean
+sysctl_set (NMPlatform *platform,
+            const char *pathid,
+            int dirfd,
+            const char *path,
+            const char *value)
+{
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+
+	g_return_val_if_fail (path, FALSE);
+	g_return_val_if_fail (value, FALSE);
+
+	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
+
+	if (   dirfd < 0
+	    && !nm_platform_netns_push (platform, &netns)) {
+		errno = ENETDOWN;
+		return FALSE;
+	}
+
+	return sysctl_set_internal (platform, pathid, dirfd, path, value);
+}
+
+typedef struct {
+	NMPlatform *platform;
+	char *pathid;
+	int dirfd;
+	char *path;
+	char **values;
+	GCancellable *cancellable;
+	NMPlatformAsyncCallback callback;
+	gpointer callback_data;
+} SysctlAsyncInfo;
+
+static void
+sysctl_async_info_free (SysctlAsyncInfo *info)
+{
+	g_object_unref (info->platform);
+	g_free (info->pathid);
+	if (info->dirfd >= 0)
+		nm_close (info->dirfd);
+	g_free (info->path);
+	g_strfreev (info->values);
+	g_object_unref (info->cancellable);
+	g_slice_free (SysctlAsyncInfo, info);
+}
+
+static void
+sysctl_async_cb (GObject *object,
+                 GAsyncResult *res,
+                 gpointer user_data)
+{
+	NMPlatform *platform;
+	GTask *task = G_TASK (res);
+	SysctlAsyncInfo *info;
+	gs_free_error GError *error = NULL;
+	gs_free char *values_str = NULL;
+
+	info = g_task_get_task_data (task);
+
+	if (g_task_propagate_boolean (task, &error)) {
+		platform = info->platform;
+		_LOGD ("sysctl: successfully set-async '%s' to values '%s'",
+		       info->pathid ?: info->path,
+		       (values_str = g_strjoinv (", ", info->values)));
+	}
+
+	if (info->callback)
+		info->callback (error, info->callback_data);
+}
+
+static void
+sysctl_async_thread_fn (GTask *task,
+                        gpointer source_object,
+                        gpointer task_data,
+                        GCancellable *cancellable)
+{
+	nm_auto_pop_netns NMPNetns *netns = NULL;
+	SysctlAsyncInfo *info = task_data;
+	GError *error = NULL;
+	char **value;
+
+	if (g_task_return_error_if_cancelled (task))
+		return;
+
+	if (   info->dirfd < 0
+	    && !nm_platform_netns_push (info->platform, &netns)) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     "sysctl: failed changing namespace");
+		g_task_return_error (task, error);
+		return;
+	}
+
+	for (value = info->values; *value; value++) {
+		if (!sysctl_set_internal (info->platform,
+		                          info->pathid,
+		                          info->dirfd,
+		                          info->path,
+		                          *value)) {
+			g_set_error (&error,
+			             NM_UTILS_ERROR,
+			             NM_UTILS_ERROR_UNKNOWN,
+			             "sysctl: failed setting '%s' to value '%s': %s",
+			             info->pathid ?: info->path,
+			             *value,
+			             nm_strerror_native (errno));
+			g_task_return_error (task, error);
+			return;
+		}
+		if (g_task_return_error_if_cancelled (task))
+			return;
+	}
+	g_task_return_boolean (task, TRUE);
+}
+
+static void
+sysctl_set_async_return_idle (gpointer user_data,
+                              GCancellable *cancellable)
+{
+	gs_unref_object NMPlatform *platform = NULL;
+	gs_free_error GError *cancelled_error = NULL;
+	gs_free_error GError *error = NULL;
+	NMPlatformAsyncCallback callback;
+	gpointer callback_data;
+
+	nm_utils_user_data_unpack (user_data, &platform, &callback, &callback_data, &error);
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	callback (cancelled_error ?: error, callback_data);
+}
+
+static void
+sysctl_set_async (NMPlatform *platform,
+                  const char *pathid,
+                  int dirfd,
+                  const char *path,
+                  const char *const *values,
+                  NMPlatformAsyncCallback callback,
+                  gpointer data,
+                  GCancellable *cancellable)
+{
+	SysctlAsyncInfo *info;
+	GTask *task;
+	int dirfd_dup, errsv;
+	gpointer packed;
+	GError *error = NULL;
+
+	g_return_if_fail (platform);
+	g_return_if_fail (path);
+	g_return_if_fail (values && values[0]);
+	g_return_if_fail (cancellable);
+	g_return_if_fail (!data || callback);
+
+	ASSERT_SYSCTL_ARGS (pathid, dirfd, path);
+
+	if (dirfd >= 0) {
+		dirfd_dup = fcntl (dirfd, F_DUPFD_CLOEXEC, 0);
+		if (dirfd_dup < 0) {
+			if (!callback)
+				return;
+			errsv = errno;
+			g_set_error (&error,
+			             NM_UTILS_ERROR,
+			             NM_UTILS_ERROR_UNKNOWN,
+			             "sysctl: failure duplicating directory fd: %s",
+			             nm_strerror_native (errsv));
+			packed = nm_utils_user_data_pack (g_object_ref (platform),
+			                                  callback,
+			                                  data,
+			                                  error);
+			nm_utils_invoke_on_idle (sysctl_set_async_return_idle,
+			                         packed,
+			                         cancellable);
+			return;
+		}
+	} else
+		dirfd_dup = -1;
+
+	info = g_slice_new0 (SysctlAsyncInfo);
+	info->platform = g_object_ref (platform);
+	info->pathid = g_strdup (pathid);
+	info->dirfd = dirfd_dup;
+	info->path = g_strdup (path);
+	info->values = g_strdupv ((char **) values);
+	info->callback = callback;
+	info->callback_data = data;
+	info->cancellable = g_object_ref (cancellable);
+
+	task = g_task_new (platform, cancellable, sysctl_async_cb, NULL);
+	g_task_set_task_data (task, info, (GDestroyNotify) sysctl_async_info_free);
+	g_task_set_return_on_cancel (task, FALSE);
+	g_task_run_in_thread (task, sysctl_async_thread_fn);
+	g_object_unref (task);
+}
+
 static GSList *sysctl_clear_cache_list;
 
 void
@@ -5310,7 +5608,7 @@ cache_on_change (NMPlatform *platform,
 				 * Request it again. */
 				re_request_link = TRUE;
 			} else if (   obj_new->link.type == NM_LINK_TYPE_ETHERNET
-			           && obj_new->link.addr.len == 0) {
+			           && obj_new->link.l_address.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
@@ -6170,8 +6468,8 @@ retry:
 	} 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) {
+	           && obj_cache->link.l_address.len == data->set_address.length
+	           && memcmp (obj_cache->link.l_address.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";
@@ -6409,7 +6707,8 @@ link_supports_carrier_detect (NMPlatform *platform, int ifindex)
 	 * us whether the device actually supports carrier detection in the first
 	 * place. We assume any device that does implements one of these two APIs.
 	 */
-	return nmp_utils_ethtool_supports_carrier_detect (ifindex) || nmp_utils_mii_supports_carrier_detect (ifindex);
+	return    nmp_utils_ethtool_supports_carrier_detect (ifindex)
+	       || nmp_utils_mii_supports_carrier_detect (ifindex);
 }
 
 static gboolean
@@ -6534,35 +6833,74 @@ nla_put_failure:
 	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-link_set_sriov_params (NMPlatform *platform,
-                       int ifindex,
-                       guint num_vfs,
-                       NMTernary autoprobe)
+static void
+sriov_idle_cb (gpointer user_data,
+               GCancellable *cancellable)
+{
+	gs_unref_object NMPlatform *platform = NULL;
+	gs_free_error GError *cancelled_error = NULL;
+	gs_free_error GError *error = NULL;
+	NMPlatformAsyncCallback callback;
+	gpointer callback_data;
+
+	g_cancellable_set_error_if_cancelled (cancellable, &cancelled_error);
+	nm_utils_user_data_unpack (user_data, &platform, &error, &callback, &callback_data);
+	callback (cancelled_error ?: error, callback_data);
+}
+
+static void
+link_set_sriov_params_async (NMPlatform *platform,
+                             int ifindex,
+                             guint num_vfs,
+                             NMTernary autoprobe,
+                             NMPlatformAsyncCallback callback,
+                             gpointer data,
+                             GCancellable *cancellable)
 {
 	nm_auto_pop_netns NMPNetns *netns = NULL;
+	gs_free_error GError *error = NULL;
 	nm_auto_close int dirfd = -1;
 	int current_autoprobe;
-	guint total;
+	guint i, total;
 	gint64 current_num;
 	char ifname[IFNAMSIZ];
+	gpointer packed;
+	const char *values[3];
 	char buf[64];
-	int errsv;
 
-	if (!nm_platform_netns_push (platform, &netns))
-		return FALSE;
+	g_return_if_fail (callback || !data);
+	g_return_if_fail (cancellable);
+
+	if (!nm_platform_netns_push (platform, &netns)) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     "couldn't change namespace");
+		goto out_idle;
+	}
 
 	dirfd = nm_platform_sysctl_open_netdir (platform, ifindex, ifname);
-	if (!dirfd)
-		return FALSE;
+	if (!dirfd) {
+		g_set_error_literal (&error,
+		                     NM_UTILS_ERROR,
+		                     NM_UTILS_ERROR_UNKNOWN,
+		                     "couldn't open netdir");
+		goto out_idle;
+	}
 
 	total = nm_platform_sysctl_get_int_checked (platform,
 	                                            NMP_SYSCTL_PATHID_NETDIR (dirfd,
 	                                                                      ifname,
 	                                                                      "device/sriov_totalvfs"),
 	                                            10, 0, G_MAXUINT, 0);
-	if (errno)
-		return FALSE;
+	if (errno) {
+		g_set_error (&error,
+		             NM_UTILS_ERROR,
+		             NM_UTILS_ERROR_UNKNOWN,
+		             "failed reading sriov_totalvfs value: %s",
+		             nm_strerror_native (errno));
+		goto out_idle;
+	}
 	if (num_vfs > total) {
 		_LOGW ("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs);
 		num_vfs = total;
@@ -6594,23 +6932,7 @@ link_set_sriov_params (NMPlatform *platform,
 
 	if (   current_num == num_vfs
 	    && (autoprobe == NM_TERNARY_DEFAULT || current_autoprobe == autoprobe))
-		return TRUE;
-
-	if (current_num != 0) {
-		/* We need to destroy all other VFs before changing any value */
-		if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
-		                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
-		                                                       ifname,
-		                                                      "device/sriov_numvfs"),
-		                             "0")) {
-			errsv = errno;
-			_LOGW ("link: couldn't reset SR-IOV num_vfs: %s", nm_strerror_native (errsv));
-			return FALSE;
-		}
-	}
-
-	if (num_vfs == 0)
-		return TRUE;
+		goto out_idle;
 
 	if (   NM_IN_SET (autoprobe, NM_TERNARY_TRUE, NM_TERNARY_FALSE)
 	    && current_autoprobe != autoprobe
@@ -6619,22 +6941,40 @@ link_set_sriov_params (NMPlatform *platform,
 	                                                          ifname,
 	                                                          "device/sriov_drivers_autoprobe"),
 	                                nm_sprintf_buf (buf, "%d", (int) autoprobe))) {
-		errsv = errno;
-		_LOGW ("link: couldn't set SR-IOV drivers-autoprobe to %d: %s", (int) autoprobe, nm_strerror_native (errsv));
-		return FALSE;
+		g_set_error (&error,
+		             NM_UTILS_ERROR,
+		             NM_UTILS_ERROR_UNKNOWN,
+		             "couldn't set SR-IOV drivers-autoprobe to %d: %s",
+		            (int) autoprobe, nm_strerror_native (errno));
+		goto out_idle;
 	}
 
-	if (!nm_platform_sysctl_set (NM_PLATFORM_GET,
-	                             NMP_SYSCTL_PATHID_NETDIR (dirfd,
-	                                                       ifname,
-	                                                       "device/sriov_numvfs"),
-	                             nm_sprintf_buf (buf, "%u", num_vfs))) {
-		errsv = errno;
-		_LOGW ("link: couldn't set SR-IOV num_vfs to %d: %s", num_vfs, nm_strerror_native (errsv));
-		return FALSE;
-	}
+	if (current_num == 0 && num_vfs == 0)
+		goto out_idle;
 
-	return TRUE;
+	i = 0;
+	if (current_num != 0)
+		values[i++] = "0";
+	if (num_vfs != 0)
+		values[i++] = nm_sprintf_bufa (32, "%u", num_vfs);
+	values[i++] = NULL;
+
+	sysctl_set_async (platform,
+	                  NMP_SYSCTL_PATHID_NETDIR (dirfd, ifname, "device/sriov_numvfs"),
+	                  values,
+	                  callback,
+	                  data,
+	                  cancellable);
+	return;
+
+out_idle:
+	if (callback) {
+		packed = nm_utils_user_data_pack (g_object_ref (platform),
+		                                  g_steal_pointer (&error),
+		                                  callback,
+		                                  data);
+		nm_utils_invoke_on_idle (sriov_idle_cb, packed, cancellable);
+	}
 }
 
 static gboolean
@@ -8167,6 +8507,9 @@ qdisc_add (NMPlatform *platform,
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
+	/* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind
+	 * is undefined. */
+
 	msg = _nl_msg_new_qdisc (RTM_NEWQDISC, flags, qdisc);
 
 	event_handler_read_netlink (platform, FALSE);
@@ -8208,6 +8551,9 @@ tfilter_add (NMPlatform *platform,
 	char s_buf[256];
 	nm_auto_nlmsg struct nl_msg *msg = NULL;
 
+	/* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind
+	 * and tfilter.action.kind is undefined. */
+
 	msg = _nl_msg_new_tfilter (RTM_NEWTFILTER, flags, tfilter);
 
 	event_handler_read_netlink (platform, FALSE);
@@ -8913,6 +9259,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	object_class->finalize = finalize;
 
 	platform_class->sysctl_set = sysctl_set;
+	platform_class->sysctl_set_async = sysctl_set_async;
 	platform_class->sysctl_get = sysctl_get;
 
 	platform_class->link_add = link_add;
@@ -8936,7 +9283,7 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->link_get_permanent_address = link_get_permanent_address;
 	platform_class->link_set_mtu = link_set_mtu;
 	platform_class->link_set_name = link_set_name;
-	platform_class->link_set_sriov_params = link_set_sriov_params;
+	platform_class->link_set_sriov_params_async = link_set_sriov_params_async;
 	platform_class->link_set_sriov_vfs = link_set_sriov_vfs;
 	platform_class->link_set_bridge_vlans = link_set_bridge_vlans;
 
diff --git a/src/platform/nm-linux-platform.h b/src/platform/nm-linux-platform.h
index bff6c00c..f08f91aa 100644
--- a/src/platform/nm-linux-platform.h
+++ b/src/platform/nm-linux-platform.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* nm-linux-platform.h - Linux kernel & udev network configuration layer
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/platform/nm-netlink.c b/src/platform/nm-netlink.c
index 71506a2c..da009d01 100644
--- a/src/platform/nm-netlink.c
+++ b/src/platform/nm-netlink.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -510,7 +509,7 @@ nla_nest_start (struct nl_msg *msg, int attrtype)
 {
 	struct nlattr *start = (struct nlattr *) nlmsg_tail (msg->nm_nlh);
 
-	if (nla_put (msg, attrtype, 0, NULL) < 0)
+	if (nla_put (msg, NLA_F_NESTED | attrtype, 0, NULL) < 0)
 		return NULL;
 
 	return start;
diff --git a/src/platform/nm-netlink.h b/src/platform/nm-netlink.h
index b12fd243..c793f04a 100644
--- a/src/platform/nm-netlink.h
+++ b/src/platform/nm-netlink.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nm-platform-private.h b/src/platform/nm-platform-private.h
index b6c94baa..5a24eda0 100644
--- a/src/platform/nm-platform-private.h
+++ b/src/platform/nm-platform-private.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nm-platform-utils.c b/src/platform/nm-platform-utils.c
index 456871a7..bc9ec11d 100644
--- a/src/platform/nm-platform-utils.c
+++ b/src/platform/nm-platform-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -69,37 +68,209 @@ nmp_utils_if_nametoindex (const char *ifname)
 
 typedef struct {
 	int fd;
-	int ifindex;
+	const int ifindex;
 	char ifname[IFNAMSIZ];
 } SocketHandle;
 
+#define SOCKET_HANDLE_INIT(_ifindex) \
+	{ \
+		.fd = -1, \
+		.ifindex = (_ifindex), \
+	}
+
+static void
+_nm_auto_socket_handle (SocketHandle *shandle)
+{
+	if (shandle->fd >= 0)
+		nm_close (shandle->fd);
+}
+
+#define nm_auto_socket_handle nm_auto(_nm_auto_socket_handle)
+
+/*****************************************************************************/
+
+typedef enum {
+	IOCTL_CALL_DATA_TYPE_NONE,
+	IOCTL_CALL_DATA_TYPE_IFRDATA,
+	IOCTL_CALL_DATA_TYPE_IFRU,
+} IoctlCallDataType;
+
 static int
-socket_handle_init (SocketHandle *shandle, int ifindex)
+_ioctl_call (const char *log_ioctl_type,
+             const char *log_subtype,
+             unsigned long int ioctl_request,
+             int ifindex,
+             int *inout_fd,
+             char *inout_ifname,
+             IoctlCallDataType edata_type,
+             gpointer edata,
+             gsize edata_size,
+             struct ifreq *out_ifreq)
 {
-	if (!nmp_utils_if_indextoname (ifindex, shandle->ifname)) {
-		shandle->ifindex = 0;
-		return -ENODEV;
+	nm_auto_close int fd_close = -1;
+	int fd;
+	int r;
+	gpointer edata_backup = NULL;
+	gs_free gpointer edata_backup_free = NULL;
+	guint try_count;
+	char known_ifnames[2][IFNAMSIZ];
+	const char *failure_reason = NULL;
+	struct ifreq ifr;
+
+	nm_assert (ifindex > 0);
+	nm_assert (NM_IN_SET (edata_type, IOCTL_CALL_DATA_TYPE_NONE,
+	                                  IOCTL_CALL_DATA_TYPE_IFRDATA,
+	                                  IOCTL_CALL_DATA_TYPE_IFRU));
+	nm_assert (edata_type != IOCTL_CALL_DATA_TYPE_NONE    || edata_size == 0);
+	nm_assert (edata_type != IOCTL_CALL_DATA_TYPE_IFRDATA || edata_size > 0);
+	nm_assert (edata_type != IOCTL_CALL_DATA_TYPE_IFRU    || (edata_size > 0 && edata_size <= sizeof (ifr.ifr_ifru)));
+	nm_assert (edata_size == 0 || edata);
+
+	/* open a file descriptor (or use the one provided). */
+	if (   inout_fd
+	    && *inout_fd >= 0)
+		fd = *inout_fd;
+	else {
+		fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+		if (fd < 0) {
+			r = -NM_ERRNO_NATIVE (errno);
+			failure_reason = "failed creating socket or ioctl";
+			goto out;
+		}
+		if (inout_fd)
+			*inout_fd = fd;
+		else
+			fd_close = fd;
 	}
 
-	shandle->fd = socket (PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-	if (shandle->fd < 0) {
-		shandle->ifindex = 0;
-		return -NM_ERRNO_NATIVE (errno);
+	/* resolve the ifindex to name (or use the one provided). */
+	if (   inout_ifname
+	    && inout_ifname[0])
+		nm_utils_ifname_cpy (known_ifnames[0], inout_ifname);
+	else {
+		if (!nmp_utils_if_indextoname (ifindex, known_ifnames[0])) {
+			failure_reason = "cannot resolve ifindex";
+			r = -ENODEV;
+			goto out;
+		}
+		if (inout_ifname)
+			nm_utils_ifname_cpy (inout_ifname, known_ifnames[0]);
 	}
 
-	shandle->ifindex = ifindex;
-	return 0;
-}
+	/* we might need to retry the request. Backup edata so that we can
+	 * restore it on retry. */
+	if (edata_size > 0)
+		edata_backup = nm_memdup_maybe_a (500, edata, edata_size, &edata_backup_free);
 
-static void
-socket_handle_destroy (SocketHandle *shandle)
-{
-	if (shandle->ifindex) {
-		shandle->ifindex = 0;
-		nm_close (shandle->fd);
+	try_count = 0;
+
+again:
+	{
+		const char *ifname = known_ifnames[try_count % 2];
+
+		nm_assert (ifindex > 0);
+		nm_assert (ifname && nm_utils_is_valid_iface_name (ifname, NULL));
+		nm_assert (fd >= 0);
+
+		memset (&ifr, 0, sizeof (ifr));
+		nm_utils_ifname_cpy (ifr.ifr_name, ifname);
+		if (edata_type == IOCTL_CALL_DATA_TYPE_IFRDATA)
+			ifr.ifr_data = edata;
+		else if (edata_type == IOCTL_CALL_DATA_TYPE_IFRU)
+			memcpy (&ifr.ifr_ifru, edata, NM_MIN (edata_size, sizeof (ifr.ifr_ifru)));
+
+		if (ioctl (fd, ioctl_request, &ifr) < 0) {
+			r = -NM_ERRNO_NATIVE (errno);
+			nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s, %s: failed: %s",
+			              log_ioctl_type,
+			              ifindex,
+			              log_subtype,
+			              ifname,
+			              nm_strerror_native (-r));
+		} else {
+			r = 0;
+			nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s, %s: success",
+			              log_ioctl_type,
+			              ifindex,
+			              log_subtype,
+			              ifname);
+		}
+	}
+
+	try_count++;
+
+	/* resolve the name again to see whether the ifindex still has the same name. */
+	if (!nmp_utils_if_indextoname (ifindex, known_ifnames[try_count % 2])) {
+		/* we could not find the ifindex again. Probably the device just got
+		 * removed.
+		 *
+		 * In both cases we return the error code we got from ioctl above.
+		 * Either it failed because the device was gone already or it still
+		 * managed to complete the call. In both cases, the error code is good. */
+		failure_reason = "cannot resolve ifindex after ioctl call. Probably the device was just removed";
+		goto out;
 	}
+
+	/* check whether the ifname changed in the meantime. If yes, would render the result
+	 * invalid. Note that this cannot detect every race regarding renames, for example:
+	 *
+	 *  - if_indextoname(#10) gives eth0
+	 *  - rename(#10) => eth0_tmp
+	 *  - rename(#11) => eth0
+	 *  - ioctl(eth0) (wrongly fetching #11, formerly eth1)
+	 *  - rename(#11) => eth_something
+	 *  - rename(#10) => eth0
+	 *  - if_indextoname(#10) gives eth0
+	 */
+	if (!nm_streq (known_ifnames[0], known_ifnames[1])) {
+		gboolean retry;
+
+		/* we detected a possible(!) rename.
+		 *
+		 * For getters it's straight forward to just retry the call.
+		 *
+		 * For setters we also always retry. If our previous call operated on the right device,
+		 * calling it again should have no bad effect (just setting the same thing more than once).
+		 *
+		 * The only potential bad thing is if there was a race involving swapping names, and we just
+		 * set the ioctl option on the wrong device. But then the bad thing already happend and
+		 * we cannot detect it (nor do anything about it). At least, we can retry and set the
+		 * option on the right interface. */
+		retry = (try_count < 5);
+
+		nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s: rename detected from \"%s\" to \"%s\". %s",
+		              log_ioctl_type,
+		              ifindex,
+		              log_subtype,
+		              known_ifnames[(try_count - 1) % 2],
+		              known_ifnames[ try_count      % 2],
+		                retry
+		              ? "Retry"
+		              : "No retry");
+		if (inout_ifname)
+			nm_utils_ifname_cpy (inout_ifname, known_ifnames[try_count % 2]);
+		if (retry) {
+			if (edata_size > 0)
+				memcpy (edata, edata_backup, edata_size);
+			goto again;
+		}
+	}
+
+out:
+	if (failure_reason) {
+		nm_log_trace (LOGD_PLATFORM, "%s[%d]: %s: %s: %s",
+		              log_ioctl_type,
+		              ifindex,
+		              log_subtype,
+		              failure_reason,
+		                r < 0
+		              ? nm_strerror_native (-r)
+		              : "assume success");
+	}
+	if (r >= 0)
+		NM_SET_OUT (out_ifreq, ifr);
+	return r;
 }
-#define nm_auto_socket_handle nm_auto(socket_handle_destroy)
 
 /******************************************************************************
  * ethtool
@@ -121,9 +292,13 @@ NM_UTILS_ENUM2STR_DEFINE_STATIC (_ethtool_cmd_to_string, guint32,
 );
 
 static const char *
-_ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
+_ethtool_edata_to_string (gpointer edata, gsize edata_size, char *sbuf, gsize sbuf_len)
 {
-	return _ethtool_cmd_to_string (*((guint32 *) edata), buf, len);
+	nm_assert (edata);
+	nm_assert (edata_size >= sizeof (guint32));
+	nm_assert ((((intptr_t) edata) % _nm_alignof (guint32)) == 0);
+
+	return _ethtool_cmd_to_string (*((guint32 *) edata), sbuf, sbuf_len);
 }
 
 /*****************************************************************************/
@@ -136,56 +311,37 @@ _ethtool_data_to_string (gconstpointer edata, char *buf, gsize len)
 #endif
 
 static int
-ethtool_call_handle (SocketHandle *shandle, gpointer edata)
+_ethtool_call_handle (SocketHandle *shandle, gpointer edata, gsize edata_size)
 {
-	struct ifreq ifr = {
-		.ifr_data = edata,
-	};
 	char sbuf[50];
-	int errsv;
-
-	nm_assert (shandle);
-	nm_assert (shandle->ifindex);
-	nm_assert (shandle->ifname[0]);
-	nm_assert (strlen (shandle->ifname) < IFNAMSIZ);
-	nm_assert (edata);
 
-	memcpy (ifr.ifr_name, shandle->ifname, IFNAMSIZ);
-	if (ioctl (shandle->fd, SIOCETHTOOL, &ifr) < 0) {
-		errsv = errno;
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: failed: %s",
-		              shandle->ifindex,
-		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              shandle->ifname,
-		              nm_strerror_native (errsv));
-		return -NM_ERRNO_NATIVE (errsv);
-	}
-
-	nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s, %s: success",
-	              shandle->ifindex,
-	              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-	              shandle->ifname);
-	return 0;
+	return _ioctl_call ("ethtool",
+	                    _ethtool_edata_to_string (edata, edata_size, sbuf, sizeof (sbuf)),
+	                    SIOCETHTOOL,
+	                    shandle->ifindex,
+	                    &shandle->fd,
+	                    shandle->ifname,
+	                    IOCTL_CALL_DATA_TYPE_IFRDATA,
+	                    edata,
+	                    edata_size,
+	                    NULL);
 }
 
 static int
-ethtool_call_ifindex (int ifindex, gpointer edata)
+_ethtool_call_once (int ifindex, gpointer edata, gsize edata_size)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	int r;
 	char sbuf[50];
 
-	nm_assert (edata);
-
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              _ethtool_data_to_string (edata, sbuf, sizeof (sbuf)),
-		              nm_strerror_native (-r));
-		return r;
-	}
-
-	return ethtool_call_handle (&shandle, edata);
+	return _ioctl_call ("ethtool",
+	                    _ethtool_edata_to_string (edata, edata_size, sbuf, sizeof (sbuf)),
+	                    SIOCETHTOOL,
+	                    ifindex,
+	                    NULL,
+	                    NULL,
+	                    IOCTL_CALL_DATA_TYPE_IFRDATA,
+	                    edata,
+	                    edata_size,
+	                    NULL);
 }
 
 /*****************************************************************************/
@@ -196,27 +352,29 @@ ethtool_get_stringset (SocketHandle *shandle, int stringset_id)
 	struct {
 		struct ethtool_sset_info info;
 		guint32 sentinel;
-	} sset_info = { };
+	} sset_info = {
+		.info.cmd = ETHTOOL_GSSET_INFO,
+		.info.reserved = 0,
+		.info.sset_mask = (1ULL << stringset_id),
+	};
 	gs_free struct ethtool_gstrings *gstrings = NULL;
+	gsize gstrings_len;
 	guint32 i, len;
 
-	sset_info.info.cmd = ETHTOOL_GSSET_INFO;
-	sset_info.info.reserved = 0;
-	sset_info.info.sset_mask = (1ULL << stringset_id);
-
-	if (ethtool_call_handle (shandle, &sset_info) < 0)
+	if (_ethtool_call_handle (shandle, &sset_info, sizeof (sset_info)) < 0)
 		return NULL;
 	if (!sset_info.info.sset_mask)
 		return NULL;
 
 	len = sset_info.info.data[0];
 
-	gstrings = g_malloc0 (sizeof (*gstrings) + (len * ETH_GSTRING_LEN));
+	gstrings_len = sizeof (*gstrings) + (len * ETH_GSTRING_LEN);
+	gstrings = g_malloc0 (gstrings_len);
 	gstrings->cmd = ETHTOOL_GSTRINGS;
 	gstrings->string_set = stringset_id;
 	gstrings->len = len;
 	if (gstrings->len > 0) {
-		if (ethtool_call_handle (shandle, gstrings) < 0)
+		if (_ethtool_call_handle (shandle, gstrings, gstrings_len) < 0)
 			return NULL;
 		for (i = 0; i < gstrings->len; i++) {
 			/* ensure NUL terminated */
@@ -402,18 +560,20 @@ ethtool_get_features (SocketHandle *shandle)
 		return NULL;
 
 	if (ss_features->len > 0) {
-		gs_free struct ethtool_gfeatures *gfeatures = NULL;
+		gs_free struct ethtool_gfeatures *gfeatures_free = NULL;
+		struct ethtool_gfeatures *gfeatures;
+		gsize gfeatures_len;
 		guint idx;
 		const NMEthtoolFeatureState *states_list0 = NULL;
 		const NMEthtoolFeatureState *const*states_plist0 = NULL;
 		guint states_plist_n = 0;
 
-		gfeatures = g_malloc0 (  sizeof (struct ethtool_gfeatures)
-		                       + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0])));
-
+		gfeatures_len =   sizeof (struct ethtool_gfeatures)
+		                + (NM_DIV_ROUND_UP (ss_features->len, 32u) * sizeof(gfeatures->features[0]));
+		gfeatures = nm_malloc0_maybe_a (300, gfeatures_len, &gfeatures_free);
 		gfeatures->cmd = ETHTOOL_GFEATURES;
 		gfeatures->size = NM_DIV_ROUND_UP (ss_features->len, 32u);
-		if (ethtool_call_handle (shandle, gfeatures) < 0)
+		if (_ethtool_call_handle (shandle, gfeatures, gfeatures_len) < 0)
 			return NULL;
 
 		for (idx = 0; idx < G_N_ELEMENTS (_ethtool_feature_infos); idx++) {
@@ -477,20 +637,11 @@ ethtool_get_features (SocketHandle *shandle)
 NMEthtoolFeatureStates *
 nmp_utils_ethtool_get_features (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
 	NMEthtoolFeatureStates *features;
-	int r;
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "get-features",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
 	features = ethtool_get_features (&shandle);
 
 	if (!features) {
@@ -530,8 +681,10 @@ nmp_utils_ethtool_set_features (int ifindex,
                                 const NMTernary *requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */,
                                 gboolean do_set /* or reset */)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	gs_free struct ethtool_sfeatures *sfeatures = NULL;
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+	gs_free struct ethtool_sfeatures *sfeatures_free = NULL;
+	struct ethtool_sfeatures *sfeatures;
+	gsize sfeatures_len;
 	int r;
 	guint i, j;
 	struct {
@@ -614,16 +767,9 @@ nmp_utils_ethtool_set_features (int ifindex,
 		return TRUE;
 	}
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "set-features",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
-	sfeatures = g_malloc0 (sizeof (struct ethtool_sfeatures)
-	                       + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0])));
+	sfeatures_len =   sizeof (struct ethtool_sfeatures)
+	                + (NM_DIV_ROUND_UP (features->n_ss_features, 32U) * sizeof(sfeatures->features[0]));
+	sfeatures = nm_malloc0_maybe_a (300, sfeatures_len, &sfeatures_free);
 	sfeatures->cmd = ETHTOOL_SFEATURES;
 	sfeatures->size = NM_DIV_ROUND_UP (features->n_ss_features, 32U);
 
@@ -649,7 +795,8 @@ nmp_utils_ethtool_set_features (int ifindex,
 			sfeatures->features[i_block].requested &= ~i_flag;
 	}
 
-	if ((r = ethtool_call_handle (&shandle, sfeatures)) < 0) {
+	r = _ethtool_call_handle (&shandle, sfeatures, sfeatures_len);
+	if (r < 0) {
 		success = FALSE;
 		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failure setting features (%s)",
 		              ifindex,
@@ -687,10 +834,10 @@ nmp_utils_ethtool_get_driver_info (int ifindex,
 	g_return_val_if_fail (data, FALSE);
 
 	drvinfo = (struct ethtool_drvinfo *) data;
-
-	memset (drvinfo, 0, sizeof (*drvinfo));
-	drvinfo->cmd = ETHTOOL_GDRVINFO;
-	return ethtool_call_ifindex (ifindex, drvinfo) >= 0;
+	*drvinfo = (struct ethtool_drvinfo) {
+		.cmd = ETHTOOL_GDRVINFO,
+	};
+	return _ethtool_call_once (ifindex, drvinfo, sizeof (*drvinfo)) >= 0;
 }
 
 gboolean
@@ -701,16 +848,16 @@ nmp_utils_ethtool_get_permanent_address (int ifindex,
 	struct {
 		struct ethtool_perm_addr e;
 		guint8 _extra_data[NM_UTILS_HWADDR_LEN_MAX + 1];
-	} edata;
+	} edata = {
+		.e.cmd = ETHTOOL_GPERMADDR,
+		.e.size = NM_UTILS_HWADDR_LEN_MAX,
+	};
+
 	guint i;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	memset (&edata, 0, sizeof (edata));
-	edata.e.cmd = ETHTOOL_GPERMADDR;
-	edata.e.size = NM_UTILS_HWADDR_LEN_MAX;
-
-	if (ethtool_call_ifindex (ifindex, &edata.e) < 0)
+	if (_ethtool_call_once (ifindex, &edata, sizeof (edata)) < 0)
 		return FALSE;
 
 	if (edata.e.size > NM_UTILS_HWADDR_LEN_MAX)
@@ -747,27 +894,20 @@ nmp_utils_ethtool_supports_carrier_detect (int ifindex)
 	 * assume the device supports carrier-detect, otherwise we assume it
 	 * doesn't.
 	 */
-	return ethtool_call_ifindex (ifindex, &edata) >= 0;
+	return _ethtool_call_once (ifindex, &edata, sizeof (edata)) >= 0;
 }
 
 gboolean
 nmp_utils_ethtool_supports_vlans (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	int r;
-	gs_free struct ethtool_gfeatures *features = NULL;
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+	gs_free struct ethtool_gfeatures *features_free = NULL;
+	struct ethtool_gfeatures *features;
+	gsize features_len;
 	int idx, block, bit, size;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "support-vlans",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
 	idx = ethtool_get_stringset_index (&shandle, ETH_SS_FEATURES, "vlan-challenged");
 	if (idx < 0) {
 		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: vlan-challenged ethtool feature does not exist?", ifindex);
@@ -778,11 +918,13 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 	bit = idx % 32;
 	size = block + 1;
 
-	features = g_malloc0 (sizeof (*features) + size * sizeof (struct ethtool_get_features_block));
+	features_len =   sizeof (*features)
+	               + (size * sizeof (struct ethtool_get_features_block));
+	features = nm_malloc0_maybe_a (300, features_len, &features_free);
 	features->cmd = ETHTOOL_GFEATURES;
 	features->size = size;
 
-	if (ethtool_call_handle (&shandle, features) < 0)
+	if (_ethtool_call_handle (&shandle, features, features_len) < 0)
 		return FALSE;
 
 	return !(features->features[block].active & (1 << bit));
@@ -791,32 +933,25 @@ nmp_utils_ethtool_supports_vlans (int ifindex)
 int
 nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
-	int r;
-
-	gs_free struct ethtool_stats *stats = NULL;
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
+	gsize stats_len;
+	gs_free struct ethtool_stats *stats_free = NULL;
+	struct ethtool_stats *stats;
 	int peer_ifindex_stat;
 
 	g_return_val_if_fail (ifindex > 0, 0);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "ethtool[%d]: %s: failed creating ethtool socket: %s",
-		              ifindex,
-		              "get-peer-ifindex",
-		              nm_strerror_native (-r));
-		return FALSE;
-	}
-
 	peer_ifindex_stat = ethtool_get_stringset_index (&shandle, ETH_SS_STATS, "peer_ifindex");
 	if (peer_ifindex_stat < 0) {
 		nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: peer_ifindex stat does not exist?", ifindex);
 		return FALSE;
 	}
 
-	stats = g_malloc0 (sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64));
+	stats_len = sizeof (*stats) + (peer_ifindex_stat + 1) * sizeof (guint64);
+	stats = nm_malloc0_maybe_a (300, stats_len, &stats_free);
 	stats->cmd = ETHTOOL_GSTATS;
 	stats->n_stats = peer_ifindex_stat + 1;
-	if (ethtool_call_ifindex (ifindex, stats) < 0)
+	if (_ethtool_call_handle (&shandle, stats, stats_len) < 0)
 		return 0;
 
 	return stats->data[peer_ifindex_stat];
@@ -825,13 +960,13 @@ nmp_utils_ethtool_get_peer_ifindex (int ifindex)
 gboolean
 nmp_utils_ethtool_get_wake_on_lan (int ifindex)
 {
-	struct ethtool_wolinfo wol;
+	struct ethtool_wolinfo wol = {
+		.cmd = ETHTOOL_GWOL,
+	};
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	memset (&wol, 0, sizeof (wol));
-	wol.cmd = ETHTOOL_GWOL;
-	if (ethtool_call_ifindex (ifindex, &wol) < 0)
+	if (_ethtool_call_once (ifindex, &wol, sizeof (wol)) < 0)
 		return FALSE;
 
 	return wol.wolopts != 0;
@@ -849,11 +984,10 @@ nmp_utils_ethtool_get_link_settings (int ifindex,
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if (ethtool_call_ifindex (ifindex, &edata) < 0)
+	if (_ethtool_call_once (ifindex, &edata, sizeof (edata)) < 0)
 		return FALSE;
 
-	if (out_autoneg)
-		*out_autoneg = (edata.autoneg == AUTONEG_ENABLE);
+	NM_SET_OUT (out_autoneg, (edata.autoneg == AUTONEG_ENABLE));
 
 	if (out_speed) {
 		guint32 speed;
@@ -922,6 +1056,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
                                      guint32 speed,
                                      NMPlatformLinkDuplexType duplex)
 {
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
 	struct ethtool_cmd edata = {
 		.cmd = ETHTOOL_GSET,
 	};
@@ -931,7 +1066,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 	                      || (!speed && duplex == NM_PLATFORM_LINK_DUPLEX_UNKNOWN), FALSE);
 
 	/* retrieve first current settings */
-	if (ethtool_call_ifindex (ifindex, &edata) < 0)
+	if (_ethtool_call_handle (&shandle, &edata, sizeof (edata)) < 0)
 		return FALSE;
 
 	/* FIXME: try first new ETHTOOL_GLINKSETTINGS/SLINKSETTINGS API
@@ -987,7 +1122,7 @@ nmp_utils_ethtool_set_link_settings (int ifindex,
 		}
 	}
 
-	return ethtool_call_ifindex (ifindex, &edata) >= 0;
+	return _ethtool_call_handle (&shandle, &edata, sizeof (edata)) >= 0;
 }
 
 gboolean
@@ -995,7 +1130,10 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
                                    NMSettingWiredWakeOnLan wol,
                                    const char *wol_password)
 {
-	struct ethtool_wolinfo wol_info = { };
+	struct ethtool_wolinfo wol_info = {
+		.cmd = ETHTOOL_SWOL,
+		.wolopts = 0,
+	};
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
@@ -1005,9 +1143,6 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 	nm_log_dbg (LOGD_PLATFORM, "ethtool[%d]: setting Wake-on-LAN options 0x%x, password '%s'",
 	            ifindex, (unsigned) wol, wol_password);
 
-	wol_info.cmd = ETHTOOL_SWOL;
-	wol_info.wolopts = 0;
-
 	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_PHY))
 		wol_info.wolopts |= WAKE_PHY;
 	if (NM_FLAGS_HAS (wol, NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST))
@@ -1029,7 +1164,7 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 		wol_info.wolopts |= WAKE_MAGICSECURE;
 	}
 
-	return ethtool_call_ifindex (ifindex, &wol_info) >= 0;
+	return _ethtool_call_once (ifindex, &wol_info, sizeof (wol_info)) >= 0;
 }
 
 /******************************************************************************
@@ -1039,40 +1174,44 @@ nmp_utils_ethtool_set_wake_on_lan (int ifindex,
 gboolean
 nmp_utils_mii_supports_carrier_detect (int ifindex)
 {
-	nm_auto_socket_handle SocketHandle shandle = { };
+	nm_auto_socket_handle SocketHandle shandle = SOCKET_HANDLE_INIT (ifindex);
 	int r;
 	struct ifreq ifr;
 	struct mii_ioctl_data *mii;
-	int errsv;
 
 	g_return_val_if_fail (ifindex > 0, FALSE);
 
-	if ((r = socket_handle_init (&shandle, ifindex)) < 0) {
-		nm_log_trace (LOGD_PLATFORM, "mii[%d]: carrier-detect no: failed creating ethtool socket: %s",
-		              ifindex,
-		              nm_strerror_native (-r));
+	r = _ioctl_call ("mii",
+	                 "SIOCGMIIPHY",
+	                 SIOCGMIIPHY,
+	                 shandle.ifindex,
+	                 &shandle.fd,
+	                 shandle.ifname,
+	                 IOCTL_CALL_DATA_TYPE_NONE,
+	                 NULL,
+	                 0,
+	                 &ifr);
+	if (r < 0)
 		return FALSE;
-	}
-
-	memset (&ifr, 0, sizeof (struct ifreq));
-	memcpy (ifr.ifr_name, shandle.ifname, IFNAMSIZ);
-
-	if (ioctl (shandle.fd, SIOCGMIIPHY, &ifr) < 0) {
-		errsv = errno;
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIPHY failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
-		return FALSE;
-	}
 
 	/* If we can read the BMSR register, we assume that the card supports MII link detection */
 	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
 	mii->reg_num = MII_BMSR;
 
-	if (ioctl (shandle.fd, SIOCGMIIREG, &ifr) != 0) {
-		errsv = errno;
-		nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect no: SIOCGMIIREG failed: %s", ifindex, shandle.ifname, nm_strerror_native (errsv));
+	r = _ioctl_call ("mii",
+	                 "SIOCGMIIREG",
+	                 SIOCGMIIREG,
+	                 shandle.ifindex,
+	                 &shandle.fd,
+	                 shandle.ifname,
+	                 IOCTL_CALL_DATA_TYPE_IFRU,
+	                 mii,
+	                 sizeof (*mii),
+	                 &ifr);
+	if (r < 0)
 		return FALSE;
-	}
 
+	mii = (struct mii_ioctl_data *) &ifr.ifr_ifru;
 	nm_log_trace (LOGD_PLATFORM, "mii[%d,%s]: carrier-detect yes: SIOCGMIIREG result 0x%X", ifindex, shandle.ifname, mii->val_out);
 	return TRUE;
 }
diff --git a/src/platform/nm-platform-utils.h b/src/platform/nm-platform-utils.h
index f5491679..ae3f51e2 100644
--- a/src/platform/nm-platform-utils.h
+++ b/src/platform/nm-platform-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 1fc0ccb7..6ead15ed 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -34,6 +33,7 @@
 #include <linux/if_tun.h>
 #include <linux/if_tunnel.h>
 #include <linux/rtnetlink.h>
+#include <linux/tc_act/tc_mirred.h>
 #include <libudev.h>
 
 #include "nm-utils.h"
@@ -50,12 +50,71 @@
 
 /*****************************************************************************/
 
-G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->addr.data ) == NM_UTILS_HWADDR_LEN_MAX);
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Address, address));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP4Route, network));
 G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
 
+/*****************************************************************************/
+
+G_STATIC_ASSERT (sizeof ( ((NMPLinkAddress *) NULL)->data ) == NM_UTILS_HWADDR_LEN_MAX);
+G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->l_address.data ) == NM_UTILS_HWADDR_LEN_MAX);
+G_STATIC_ASSERT (sizeof ( ((NMPlatformLink *) NULL)->l_broadcast.data ) == NM_UTILS_HWADDR_LEN_MAX);
+
+static const char *
+_nmp_link_address_to_string (const NMPLinkAddress *addr,
+                             char buf[static (NM_UTILS_HWADDR_LEN_MAX * 3)])
+{
+	nm_assert (addr);
+
+	if (addr->len > 0) {
+		if (!nm_utils_hwaddr_ntoa_buf (addr->data,
+		                               addr->len,
+		                               TRUE,
+		                               buf,
+		                               NM_UTILS_HWADDR_LEN_MAX * 3)) {
+			buf[0] = '\0';
+			g_return_val_if_reached (buf);
+		}
+	} else
+		buf[0] = '\0';
+
+	return buf;
+}
+
+gconstpointer
+nmp_link_address_get (const NMPLinkAddress *addr, size_t *length)
+{
+	if (   !addr
+	    || addr->len <= 0) {
+		NM_SET_OUT (length, 0);
+		return NULL;
+	}
+
+	if (addr->len > NM_UTILS_HWADDR_LEN_MAX) {
+		NM_SET_OUT (length, 0);
+		g_return_val_if_reached (NULL);
+	}
+
+	NM_SET_OUT (length, addr->len);
+	return addr->data;
+}
+
+GBytes *
+nmp_link_address_get_as_bytes (const NMPLinkAddress *addr)
+{
+	gconstpointer data;
+	size_t length;
+
+	data = nmp_link_address_get (addr, &length);
+
+	return   length > 0
+	       ? g_bytes_new (data, length)
+	       : NULL;
+}
+
+/*****************************************************************************/
+
 #define _NMLOG_DOMAIN           LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME      "platform"
 
@@ -220,7 +279,8 @@ nm_platform_setup (NMPlatform *instance)
 
 	nm_singleton_instance_register ();
 
-	nm_log_dbg (LOGD_CORE, "setup %s singleton (%p, %s)", "NMPlatform", singleton_instance, G_OBJECT_TYPE_NAME (instance));
+	nm_log_dbg (LOGD_CORE, "setup %s singleton ("NM_HASH_OBFUSCATE_PTR_FMT")",
+	            "NMPlatform", NM_HASH_OBFUSCATE_PTR (instance));
 }
 
 /**
@@ -479,6 +539,40 @@ nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const c
 	return klass->sysctl_set (self, pathid, dirfd, path, value);
 }
 
+/**
+ * nm_platform_sysctl_set_async:
+ * @self: platform instance
+ * @pathid: if @dirfd is present, this must be the full path that is looked up
+ * @dirfd: optional file descriptor for parent directory for openat()
+ * @path: absolute option path
+ * @values: NULL-terminated array of strings to be written
+ * @callback: function called on termination
+ * @data: data passed to callback function
+ * @cancellable: to cancel the operation
+ *
+ * This function is intended to be used for writing values to sysctl-style
+ * virtual runtime configuration files. This includes not only /proc/sys
+ * but also for example /sys/class. The function does not block and returns
+ * immediately. The callback is always invoked, and asynchronously. The file
+ * is closed after writing each value and reopened to write the next one so
+ * that the function can be used safely on all /proc and /sys files,
+ * independently of how /proc/sys/kernel/sysctl_writes_strict is configured.
+ */
+void nm_platform_sysctl_set_async (NMPlatform *self,
+                                   const char *pathid,
+                                   int dirfd,
+                                   const char *path,
+                                   const char *const *values,
+                                   NMPlatformAsyncCallback callback,
+                                   gpointer data,
+                                   GCancellable *cancellable)
+{
+	_CHECK_SELF_VOID (self, klass);
+
+	klass->sysctl_set_async (self, pathid, dirfd, path, values, callback, data, cancellable);
+}
+
+
 gboolean
 nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
                                                     const char *iface,
@@ -621,14 +715,14 @@ nm_platform_sysctl_get_int_checked (NMPlatform *self,
 /*****************************************************************************/
 
 char *
-nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_get (NMPlatform *self,
                                 int addr_family,
                                 const char *ifname,
                                 const char *property)
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
-	return nm_platform_sysctl_get (platform,
+	return nm_platform_sysctl_get (self,
 	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                         buf,
 	                                                                                         ifname,
@@ -636,7 +730,7 @@ nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
 }
 
 gint64
-nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *self,
                                             int addr_family,
                                             const char *ifname,
                                             const char *property,
@@ -647,7 +741,7 @@ nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
-	return nm_platform_sysctl_get_int_checked (platform,
+	return nm_platform_sysctl_get_int_checked (self,
 	                                           NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                                     buf,
 	                                                                                                     ifname,
@@ -659,7 +753,7 @@ nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
 }
 
 gboolean
-nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_set (NMPlatform *self,
                                 int addr_family,
                                 const char *ifname,
                                 const char *property,
@@ -667,7 +761,7 @@ nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
 {
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 
-	return nm_platform_sysctl_set (platform,
+	return nm_platform_sysctl_set (self,
 	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                         buf,
 	                                                                                         ifname,
@@ -676,7 +770,7 @@ nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
 }
 
 gboolean
-nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *self,
                                       int addr_family,
                                       const char *ifname,
                                       const char *property,
@@ -685,7 +779,7 @@ nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
 	char buf[NM_UTILS_SYSCTL_IP_CONF_PATH_BUFSIZE];
 	char s[64];
 
-	return nm_platform_sysctl_set (platform,
+	return nm_platform_sysctl_set (self,
 	                               NMP_SYSCTL_PATHID_ABSOLUTE (nm_utils_sysctl_ip_conf_path (addr_family,
 	                                                                                         buf,
 	                                                                                         ifname,
@@ -693,6 +787,47 @@ nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
 	                               nm_sprintf_buf (s, "%"G_GINT64_FORMAT, value));
 }
 
+int
+nm_platform_sysctl_ip_conf_get_rp_filter_ipv4 (NMPlatform *self,
+                                               const char *ifname,
+                                               gboolean consider_all,
+                                               gboolean *out_due_to_all)
+{
+	int val, val_all;
+
+	NM_SET_OUT (out_due_to_all, FALSE);
+
+	if (!ifname)
+		return -1;
+
+	val = nm_platform_sysctl_ip_conf_get_int_checked (self,
+	                                                  AF_INET,
+	                                                  ifname,
+	                                                  "rp_filter",
+	                                                  10, 0, 2, -1);
+	if (val == -1)
+		return -1;
+
+	/* the effectively used value is the rp_filter sysctl value of MAX(all,ifname).
+	 * Note that this is the numerical MAX(), despite rp_filter "1" being more strict
+	 * than "2". */
+	if (   val < 2
+	    && consider_all
+	    && !nm_streq (ifname, "all")) {
+		val_all = nm_platform_sysctl_ip_conf_get_int_checked (self,
+		                                                      AF_INET,
+		                                                      "all",
+		                                                      "rp_filter",
+		                                                      10, 0, 2, val);
+		if (val_all > val) {
+			val = val_all;
+			NM_SET_OUT (out_due_to_all, TRUE);
+		}
+	}
+
+	return val;
+}
+
 /*****************************************************************************/
 
 static int
@@ -905,14 +1040,15 @@ nm_platform_link_get_by_ifname (NMPlatform *self, const char *ifname)
 }
 
 struct _nm_platform_link_get_by_address_data {
-	gconstpointer address;
-	guint8 length;
+	gconstpointer data;
+	guint8 len;
 };
 
 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);
+	return    obj->link.l_address.len == d->len
+	       && !memcmp (obj->link.l_address.data, d->data, d->len);
 }
 
 /**
@@ -932,8 +1068,8 @@ nm_platform_link_get_by_address (NMPlatform *self,
 {
 	const NMPObject *obj;
 	struct _nm_platform_link_get_by_address_data d = {
-		.address = address,
-		.length = length,
+		.data = address,
+		.len  = length,
 	};
 
 	_CHECK_SELF (self, klass, NULL);
@@ -1471,19 +1607,7 @@ nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length)
 	const NMPlatformLink *pllink;
 
 	pllink = nm_platform_link_get (self, ifindex);
-	if (   !pllink
-	    || pllink->addr.len <= 0) {
-		NM_SET_OUT (length, 0);
-		return NULL;
-	}
-
-	if (pllink->addr.len > NM_UTILS_HWADDR_LEN_MAX) {
-		NM_SET_OUT (length, 0);
-		g_return_val_if_reached (NULL);
-	}
-
-	NM_SET_OUT (length, pllink->addr.len);
-	return pllink->addr.data;
+	return nmp_link_address_get (pllink ? &pllink->l_address : NULL, length);
 }
 
 /**
@@ -1551,19 +1675,35 @@ nm_platform_link_supports_sriov (NMPlatform *self, int ifindex)
  * @num_vfs: the number of VFs to create
  * @autoprobe: the new autoprobe-drivers value (pass
  *     %NM_TERNARY_DEFAULT to keep current value)
+ * @callback: called when the operation finishes
+ * @callback_data: data passed to @callback
+ * @cancellable: cancellable to abort the operation
+ *
+ * Sets SR-IOV parameters asynchronously without
+ * blocking the main thread. The callback function is
+ * always invoked, and asynchronously.
  */
-gboolean
-nm_platform_link_set_sriov_params (NMPlatform *self,
-                                   int ifindex,
-                                   guint num_vfs,
-                                   NMTernary autoprobe)
+void
+nm_platform_link_set_sriov_params_async (NMPlatform *self,
+                                         int ifindex,
+                                         guint num_vfs,
+                                         NMTernary autoprobe,
+                                         NMPlatformAsyncCallback callback,
+                                         gpointer callback_data,
+                                         GCancellable *cancellable)
 {
-	_CHECK_SELF (self, klass, FALSE);
+	_CHECK_SELF_VOID (self, klass);
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
+	g_return_if_fail (ifindex > 0);
 
 	_LOG3D ("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe);
-	return klass->link_set_sriov_params (self, ifindex, num_vfs, autoprobe);
+	klass->link_set_sriov_params_async (self,
+	                                    ifindex,
+	                                    num_vfs,
+	                                    autoprobe,
+	                                    callback,
+	                                    callback_data,
+	                                    cancellable);
 }
 
 gboolean
@@ -2369,9 +2509,10 @@ nm_platform_link_6lowpan_get_properties (NMPlatform *self, int ifindex, int *out
 	if (out_parent) {
 		const NMPlatformLink *parent_plink;
 
-		parent_plink = nm_platform_link_get_by_address (self, NM_LINK_TYPE_WPAN,
-		                                                plink->addr.data,
-		                                                plink->addr.len);
+		parent_plink = nm_platform_link_get_by_address (self,
+		                                                NM_LINK_TYPE_WPAN,
+		                                                plink->l_address.data,
+		                                                plink->l_address.len);
 		NM_SET_OUT (out_parent, parent_plink ? parent_plink->ifindex : -1);
 	}
 
@@ -3400,21 +3541,21 @@ nm_platform_ethtool_set_features (NMPlatform *self,
 /*****************************************************************************/
 
 const NMDedupMultiHeadEntry *
-nm_platform_lookup_all (NMPlatform *platform,
+nm_platform_lookup_all (NMPlatform *self,
                         NMPCacheIdType cache_id_type,
                         const NMPObject *obj)
 {
-	return nmp_cache_lookup_all (nm_platform_get_cache (platform),
+	return nmp_cache_lookup_all (nm_platform_get_cache (self),
 	                             cache_id_type,
 	                             obj);
 }
 
 const NMDedupMultiEntry *
-nm_platform_lookup_entry (NMPlatform *platform,
+nm_platform_lookup_entry (NMPlatform *self,
                           NMPCacheIdType cache_id_type,
                           const NMPObject *obj)
 {
-	return nmp_cache_lookup_entry_with_idx_type (nm_platform_get_cache (platform),
+	return nmp_cache_lookup_entry_with_idx_type (nm_platform_get_cache (self),
 	                                             cache_id_type,
 	                                             obj);
 }
@@ -5076,10 +5217,27 @@ nm_platform_qdisc_add (NMPlatform *self,
 	int ifindex = qdisc->ifindex;
 	_CHECK_SELF (self, klass, -NME_BUG);
 
+	/* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind
+	 * is undefined. */
+
 	_LOG3D ("adding or updating a qdisc: %s", nm_platform_qdisc_to_string (qdisc, NULL, 0));
 	return klass->qdisc_add (self, flags, qdisc);
 }
 
+/**
+ * nm_platform_qdisc_sync:
+ * @self: the #NMPlatform instance
+ * @ifindex: the ifindex where to configure the qdiscs.
+ * @known_qdiscs: the list of qdiscs (#NMPObject).
+ *
+ * The function promises not to take any reference to the qdisc
+ * instances from @known_qdiscs, nor to keep them around after
+ * the function returns. This is important, because it allows the
+ * caller to pass NMPlatformQdisc instances which "kind" string
+ * have a limited lifetime.
+ *
+ * Returns: %TRUE on success.
+ */
 gboolean
 nm_platform_qdisc_sync (NMPlatform *self,
                         int ifindex,
@@ -5142,10 +5300,27 @@ nm_platform_tfilter_add (NMPlatform *self,
 	int ifindex = tfilter->ifindex;
 	_CHECK_SELF (self, klass, -NME_BUG);
 
+	/* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind
+	 * and tfilter.action.kind is undefined. */
+
 	_LOG3D ("adding or updating a tfilter: %s", nm_platform_tfilter_to_string (tfilter, NULL, 0));
 	return klass->tfilter_add (self, flags, tfilter);
 }
 
+/**
+ * nm_platform_qdisc_sync:
+ * @self: the #NMPlatform instance
+ * @ifindex: the ifindex where to configure the qdiscs.
+ * @known_tfilters: the list of tfilters (#NMPObject).
+ *
+ * The function promises not to take any reference to the tfilter
+ * instances from @known_tfilters, nor to keep them around after
+ * the function returns. This is important, because it allows the
+ * caller to pass NMPlatformTfilter instances which "kind" string
+ * have a limited lifetime.
+ *
+ * Returns: %TRUE on success.
+ */
 gboolean
 nm_platform_tfilter_sync (NMPlatform *self,
                           int ifindex,
@@ -5268,31 +5443,36 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 {
 	char master[20];
 	char parent[20];
-	GString *str_flags;
+	char str_flags[1 + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 1];
+	char str_highlighted_flags[50];
+	char *s;
+	gsize l;
 	char str_addrmode[30];
-	gs_free char *str_addr = NULL;
+	char str_address[NM_UTILS_HWADDR_LEN_MAX * 3];
+	char str_broadcast[NM_UTILS_HWADDR_LEN_MAX * 3];
 	char str_inet6_token[NM_UTILS_INET_ADDRSTRLEN];
 	const char *str_link_type;
 
 	if (!nm_utils_to_string_buffer_init_null (link, &buf, &len))
 		return buf;
 
-	str_flags = g_string_new (NULL);
+	s = str_highlighted_flags;
+	l = sizeof (str_highlighted_flags);
 	if (NM_FLAGS_HAS (link->n_ifi_flags, IFF_NOARP))
-		g_string_append (str_flags, "NOARP,");
+		nm_utils_strbuf_append_str (&s, &l, "NOARP,");
 	if (NM_FLAGS_HAS (link->n_ifi_flags, IFF_UP))
-		g_string_append (str_flags, "UP");
+		nm_utils_strbuf_append_str (&s, &l, "UP");
 	else
-		g_string_append (str_flags, "DOWN");
+		nm_utils_strbuf_append_str (&s, &l, "DOWN");
 	if (link->connected)
-		g_string_append (str_flags, ",LOWER_UP");
+		nm_utils_strbuf_append_str (&s, &l, ",LOWER_UP");
+	nm_assert (s > str_highlighted_flags && l > 0);
 
 	if (link->n_ifi_flags) {
-		char str_flags_buf[64];
-
-		nm_platform_link_flags2str (link->n_ifi_flags, str_flags_buf, sizeof (str_flags_buf));
-		g_string_append_printf (str_flags, ";%s", str_flags_buf);
-	}
+		str_flags[0] = ';';
+		nm_platform_link_flags2str (link->n_ifi_flags, &str_flags[1], sizeof (str_flags) - 1);
+	} else
+		str_flags[0] = '\0';
 
 	if (link->master)
 		g_snprintf (master, sizeof (master), " master %d", link->master);
@@ -5306,8 +5486,8 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	else
 		parent[0] = 0;
 
-	if (link->addr.len)
-		str_addr = nm_utils_hwaddr_ntoa (link->addr.data, MIN (link->addr.len, sizeof (link->addr.data)));
+	_nmp_link_address_to_string (&link->l_address, str_address);
+	_nmp_link_address_to_string (&link->l_broadcast, str_broadcast);
 
 	str_link_type = nm_link_type_to_string (link->type);
 
@@ -5315,7 +5495,7 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            "%d: " /* ifindex */
 	            "%s" /* name */
 	            "%s" /* parent */
-	            " <%s>" /* flags */
+	            " <%s%s>" /* flags */
 	            " mtu %d"
 	            "%s" /* master */
 	            " arp %u" /* arptype */
@@ -5323,7 +5503,8 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            "%s%s" /* kind */
 	            "%s" /* is-in-udev */
 	            "%s%s" /* addr-gen-mode */
-	            "%s%s" /* addr */
+	            "%s%s" /* l_address */
+	            "%s%s" /* l_broadcast */
 	            "%s%s" /* inet6_token */
 	            "%s%s" /* driver */
 	            " rx:%"G_GUINT64_FORMAT",%"G_GUINT64_FORMAT
@@ -5332,7 +5513,8 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            link->ifindex,
 	            link->name,
 	            parent,
-	            str_flags->str,
+	            str_highlighted_flags,
+	            str_flags,
 	            link->mtu, master,
 	            link->arptype,
 	            str_link_type ?: "???",
@@ -5341,15 +5523,16 @@ nm_platform_link_to_string (const NMPlatformLink *link, char *buf, gsize len)
 	            link->initialized ? " init" : " not-init",
 	            link->inet6_addr_gen_mode_inv ? " addrgenmode " : "",
 	            link->inet6_addr_gen_mode_inv ? nm_platform_link_inet6_addrgenmode2str (_nm_platform_uint8_inv (link->inet6_addr_gen_mode_inv), str_addrmode, sizeof (str_addrmode)) : "",
-	            str_addr ? " addr " : "",
-	            str_addr ?: "",
+	            str_address[0] ? " addr " : "",
+	            str_address[0] ? str_address : "",
+	            str_broadcast[0] ? " brd " : "",
+	            str_broadcast[0] ? str_broadcast : "",
 	            link->inet6_token.id ? " inet6token " : "",
 	            link->inet6_token.id ? nm_utils_inet6_interface_identifier_to_token (link->inet6_token, str_inet6_token) : "",
 	            link->driver ? " driver " : "",
 	            link->driver ?: "",
 	            link->rx_packets, link->rx_bytes,
 	            link->tx_packets, link->tx_bytes);
-	g_string_free (str_flags, TRUE);
 	return buf;
 }
 
@@ -6426,19 +6609,41 @@ const char *
 nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len)
 {
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
+	const char *buf0;
 
 	if (!nm_utils_to_string_buffer_init_null (qdisc, &buf, &len))
 		return buf;
 
-	g_snprintf (buf, len, "%s%s family %d handle %x parent %x info %x",
-	            qdisc->kind,
-	            _to_string_dev (NULL, qdisc->ifindex, str_dev, sizeof (str_dev)),
-	            qdisc->addr_family,
-	            qdisc->handle,
-	            qdisc->parent,
-	            qdisc->info);
+	buf0 = buf;
 
-	return buf;
+	nm_utils_strbuf_append (&buf, &len, "%s%s family %u handle %x parent %x info %x",
+	                        qdisc->kind,
+	                        _to_string_dev (NULL, qdisc->ifindex, str_dev, sizeof (str_dev)),
+	                        qdisc->addr_family,
+	                        qdisc->handle,
+	                        qdisc->parent,
+	                        qdisc->info);
+
+	if (nm_streq0 (qdisc->kind, "fq_codel")) {
+		if (qdisc->fq_codel.limit)
+			nm_utils_strbuf_append (&buf, &len, " limit %u", qdisc->fq_codel.limit);
+		if (qdisc->fq_codel.flows)
+			nm_utils_strbuf_append (&buf, &len, " flows %u", qdisc->fq_codel.flows);
+		if (qdisc->fq_codel.target)
+			nm_utils_strbuf_append (&buf, &len, " target %u", qdisc->fq_codel.target);
+		if (qdisc->fq_codel.interval)
+			nm_utils_strbuf_append (&buf, &len, " interval %u", qdisc->fq_codel.interval);
+		if (qdisc->fq_codel.quantum)
+			nm_utils_strbuf_append (&buf, &len, " quantum %u", qdisc->fq_codel.quantum);
+		if (qdisc->fq_codel.ce_threshold != NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED)
+			nm_utils_strbuf_append (&buf, &len, " ce_threshold %u", qdisc->fq_codel.ce_threshold);
+		if (qdisc->fq_codel.memory_limit != NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET)
+			nm_utils_strbuf_append (&buf, &len, " memory_limit %u", qdisc->fq_codel.memory_limit);
+		if (qdisc->fq_codel.ecn)
+			nm_utils_strbuf_append (&buf, &len, " ecn");
+	}
+
+	return buf0;
 }
 
 void
@@ -6451,6 +6656,18 @@ nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h)
 	                     obj->handle,
 	                     obj->parent,
 	                     obj->info);
+	if (nm_streq0 (obj->kind, "fq_codel")) {
+		nm_hash_update_vals (h,
+		                     obj->fq_codel.limit,
+		                     obj->fq_codel.flows,
+		                     obj->fq_codel.target,
+		                     obj->fq_codel.interval,
+		                     obj->fq_codel.quantum,
+		                     obj->fq_codel.ce_threshold,
+		                     obj->fq_codel.memory_limit,
+		                     NM_HASH_COMBINE_BOOLS (guint8,
+		                                            obj->fq_codel.ecn));
+	}
 }
 
 int
@@ -6464,6 +6681,17 @@ nm_platform_qdisc_cmp (const NMPlatformQdisc *a, const NMPlatformQdisc *b)
 	NM_CMP_FIELD (a, b, handle);
 	NM_CMP_FIELD (a, b, info);
 
+	if (nm_streq0 (a->kind, "fq_codel")) {
+		NM_CMP_FIELD (a, b, fq_codel.limit);
+		NM_CMP_FIELD (a, b, fq_codel.flows);
+		NM_CMP_FIELD (a, b, fq_codel.target);
+		NM_CMP_FIELD (a, b, fq_codel.interval);
+		NM_CMP_FIELD (a, b, fq_codel.quantum);
+		NM_CMP_FIELD (a, b, fq_codel.ce_threshold);
+		NM_CMP_FIELD (a, b, fq_codel.memory_limit);
+		NM_CMP_FIELD_UNSAFE (a, b, fq_codel.ecn);
+	}
+
 	return 0;
 }
 
@@ -6492,11 +6720,18 @@ nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsiz
 			                                                        NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
 			                                                      | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII,
 			                                                      &t));
+		} else if (nm_streq (tfilter->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			nm_utils_strbuf_append (&p, &l, "%s%s%s%s dev %d",
+			                        tfilter->action.mirred.ingress ? " ingress" : "",
+			                        tfilter->action.mirred.egress ? " egress" : "",
+			                        tfilter->action.mirred.mirror ? " mirror" : "",
+			                        tfilter->action.mirred.redirect ? " redirect" : "",
+			                        tfilter->action.mirred.ifindex);
 		}
 	} else
 		act_buf[0] = '\0';
 
-	g_snprintf (buf, len, "%s%s family %d handle %x parent %x info %x%s",
+	g_snprintf (buf, len, "%s%s family %u handle %x parent %x info %x%s",
 	            tfilter->kind,
 	            _to_string_dev (NULL, tfilter->ifindex, str_dev, sizeof (str_dev)),
 	            tfilter->addr_family,
@@ -6520,8 +6755,17 @@ nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h)
 	                     obj->info);
 	if (obj->action.kind) {
 		nm_hash_update_str (h, obj->action.kind);
-		if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
+		if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
 			nm_hash_update_strarr (h, obj->action.simple.sdata);
+		} else if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			nm_hash_update_vals (h,
+			                     obj->action.mirred.ifindex,
+			                     NM_HASH_COMBINE_BOOLS (guint8,
+			                                            obj->action.mirred.ingress,
+			                                            obj->action.mirred.egress,
+			                                            obj->action.mirred.mirror,
+			                                            obj->action.mirred.redirect));
+		}
 	}
 }
 
@@ -6538,8 +6782,15 @@ nm_platform_tfilter_cmp (const NMPlatformTfilter *a, const NMPlatformTfilter *b)
 
 	NM_CMP_FIELD_STR_INTERNED (a, b, action.kind);
 	if (a->action.kind) {
-		if (nm_streq (a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
+		if (nm_streq (a->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
 			NM_CMP_FIELD_STR (a, b, action.simple.sdata);
+		} else if (nm_streq (a->action.kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
+			NM_CMP_FIELD (a, b, action.mirred.ifindex);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.ingress);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.egress);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.mirror);
+			NM_CMP_FIELD_UNSAFE (a, b, action.mirred.redirect);
+		}
 	}
 
 	return 0;
@@ -6641,7 +6892,8 @@ nm_platform_link_hash_update (const NMPlatformLink *obj, NMHashState *h)
 	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);
+	nm_hash_update_mem (h, obj->l_address.data, NM_MIN (obj->l_address.len, sizeof (obj->l_address.data)));
+	nm_hash_update_mem (h, obj->l_broadcast.data, NM_MIN (obj->l_broadcast.len, sizeof (obj->l_broadcast.data)));
 }
 
 int
@@ -6658,12 +6910,15 @@ nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b)
 	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, l_address.len);
+	NM_CMP_FIELD (a, b, l_broadcast.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)
-		NM_CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len);
+	if (a->l_address.len)
+		NM_CMP_FIELD_MEMCMP_LEN (a, b, l_address.data, a->l_address.len);
+	if (a->l_broadcast.len)
+		NM_CMP_FIELD_MEMCMP_LEN (a, b, l_broadcast.data, a->l_broadcast.len);
 	NM_CMP_FIELD_MEMCMP (a, b, inet6_token);
 	NM_CMP_FIELD (a, b, rx_packets);
 	NM_CMP_FIELD (a, b, rx_bytes);
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 97be8832..248eca44 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -159,6 +158,14 @@ typedef enum {
 	NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL,
 } NMPlatformRoutingRuleCmpType;
 
+typedef struct {
+	guint8 data[20 /* NM_UTILS_HWADDR_LEN_MAX */ ];
+	guint8 len;
+} NMPLinkAddress;
+
+gconstpointer nmp_link_address_get (const NMPLinkAddress *addr, size_t *length);
+GBytes       *nmp_link_address_get_as_bytes (const NMPLinkAddress *addr);
+
 typedef enum {
 
 	/* match-flags are strictly inclusive. That means,
@@ -230,11 +237,11 @@ struct _NMPlatformLink {
 	/* rtnl_link_get_arptype(), ifinfomsg.ifi_type. */
 	guint32 arptype;
 
-	/* rtnl_link_get_addr(), IFLA_ADDRESS */
-	struct {
-		guint8 data[20]; /* NM_UTILS_HWADDR_LEN_MAX */
-		guint8 len;
-	} addr;
+	/* IFLA_ADDRESS */
+	NMPLinkAddress l_address;
+
+	/* IFLA_BROADCAST */
+	NMPLinkAddress l_broadcast;
 
 	/* rtnl_link_inet6_get_token(), IFLA_INET6_TOKEN */
 	NMUtilsIPv6IfaceId inet6_token;
@@ -596,13 +603,48 @@ typedef struct {
 	bool     uid_range_has:1;            /* has(FRA_UID_RANGE) */
 } NMPlatformRoutingRule;
 
+#define NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET   (~((guint32) 0))
+
+#define NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED ((guint32) 0x83126E97u)
+
+G_STATIC_ASSERT (((((guint64) NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED) * 1000u) >> 10) == (guint64) INT_MAX);
+
+typedef struct {
+	guint32 limit;
+	guint32 flows;
+	guint32 target;
+	guint32 interval;
+	guint32 quantum;
+	guint32 ce_threshold; /* TCA_FQ_CODEL_CE_THRESHOLD: kernel internally stores this value as
+	                       *   ((val64 * NSEC_PER_USEC) >> CODEL_SHIFT). The default value (in
+	                       *   the domain with this coersion) is CODEL_DISABLED_THRESHOLD (INT_MAX).
+	                       *   That means, "disabled" is expressed on RTM_NEWQDISC netlink API by absence of the
+	                       *   netlink attribute but also as the special value 0x83126E97u
+	                       *   (NM_PLATFORM_FQ_CODEL_CE_THRESHOLD_DISABLED).
+	                       *   Beware: zero is not the default you must always explicitly set this value. */
+	guint32 memory_limit; /* TCA_FQ_CODEL_MEMORY_LIMIT: note that only values <= 2^31 are accepted by kernel
+	                       *   and kernel defaults to 32MB.
+	                       *   Note that we use the special value NM_PLATFORM_FQ_CODEL_MEMORY_LIMIT_UNSET
+	                       *   to indicate that no explicit limit is set (when we send a RTM_NEWQDISC request).
+	                       *   This will cause kernel to choose the default (32MB).
+	                       *   Beware: zero is not the default you must always explicitly set this value. */
+	bool ecn:1;
+} NMPlatformQdiscFqCodel;
+
 typedef struct {
 	__NMPlatformObjWithIfindex_COMMON;
+
+	/* beware, kind is embedded in an NMPObject, hence you must
+	 * take care of the lifetime of the string. */
 	const char *kind;
+
 	int addr_family;
 	guint32 handle;
 	guint32 parent;
 	guint32 info;
+	union {
+		NMPlatformQdiscFqCodel fq_codel;
+	};
 } NMPlatformQdisc;
 
 typedef struct {
@@ -610,17 +652,35 @@ typedef struct {
 } NMPlatformActionSimple;
 
 typedef struct {
+	int ifindex;
+	bool egress:1;
+	bool ingress:1;
+	bool mirror:1;
+	bool redirect:1;
+} NMPlatformActionMirred;
+
+typedef struct {
+
+	/* beware, kind is embedded in an NMPObject, hence you must
+	 * take care of the lifetime of the string. */
 	const char *kind;
+
 	union {
 		NMPlatformActionSimple simple;
+		NMPlatformActionMirred mirred;
 	};
 } NMPlatformAction;
 
 #define NM_PLATFORM_ACTION_KIND_SIMPLE "simple"
+#define NM_PLATFORM_ACTION_KIND_MIRRED "mirred"
 
 typedef struct {
 	__NMPlatformObjWithIfindex_COMMON;
+
+	/* beware, kind is embedded in an NMPObject, hence you must
+	 * take care of the lifetime of the string. */
 	const char *kind;
+
 	int addr_family;
 	guint32 handle;
 	guint32 parent;
@@ -842,6 +902,8 @@ typedef enum {
 
 } NMPlatformWireGuardChangePeerFlags;
 
+typedef void (*NMPlatformAsyncCallback) (GError *error, gpointer user_data);
+
 /*****************************************************************************/
 
 typedef enum {
@@ -864,28 +926,28 @@ extern volatile int _nm_platform_kernel_support_state[_NM_PLATFORM_KERNEL_SUPPOR
 int _nm_platform_kernel_support_init (NMPlatformKernelSupportType type,
                                       int value);
 
-#define _nm_platform_kernel_support_detected(type) \
-	G_LIKELY (({ \
-		const NMPlatformKernelSupportType _type = (type); \
-		\
-		nm_assert (_NM_INT_NOT_NEGATIVE (_type) && _type < G_N_ELEMENTS (_nm_platform_kernel_support_state)); \
-		\
-		(_nm_platform_kernel_support_state[_type] != 0); \
-	}))
-
-#define nm_platform_kernel_support_get(type) \
-	({ \
-		const NMPlatformKernelSupportType _type = (type); \
-		int _v; \
-		\
-		nm_assert (_NM_INT_NOT_NEGATIVE (_type) && _type < G_N_ELEMENTS (_nm_platform_kernel_support_state)); \
-		\
-		_v = _nm_platform_kernel_support_state[_type]; \
-		if (G_UNLIKELY (_v == 0)) \
-			_v = _nm_platform_kernel_support_init (_type, 0); \
-		\
-		(_v >= 0); \
-	})
+static inline gboolean
+_nm_platform_kernel_support_detected (NMPlatformKernelSupportType type)
+{
+	nm_assert (   _NM_INT_NOT_NEGATIVE (type)
+	           && type < G_N_ELEMENTS (_nm_platform_kernel_support_state));
+
+	return G_LIKELY (_nm_platform_kernel_support_state[type] != 0);
+}
+
+static inline gboolean
+nm_platform_kernel_support_get (NMPlatformKernelSupportType type)
+{
+	int v;
+
+	nm_assert (_NM_INT_NOT_NEGATIVE (type)
+	           && type < G_N_ELEMENTS (_nm_platform_kernel_support_state));
+
+	v = _nm_platform_kernel_support_state[type];
+	if (G_UNLIKELY (v == 0))
+		v = _nm_platform_kernel_support_init (type, 0);
+	return (v >= 0);
+}
 
 /*****************************************************************************/
 
@@ -900,64 +962,75 @@ struct _NMPlatform {
 typedef struct {
 	GObjectClass parent;
 
-	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);
+	gboolean (*sysctl_set) (NMPlatform *self, const char *pathid, int dirfd, const char *path, const char *value);
+	void (*sysctl_set_async)  (NMPlatform *self,
+	                           const char *pathid,
+	                           int dirfd,
+	                           const char *path,
+	                           const char *const *values,
+	                           NMPlatformAsyncCallback callback,
+	                           gpointer data,
+	                           GCancellable *cancellable);
+	char * (*sysctl_get) (NMPlatform *self, const char *pathid, int dirfd, const char *path);
+
+	void (*refresh_all) (NMPlatform *self, NMPObjectType obj_type);
+	void (*process_events) (NMPlatform *self);
 
-	int (*link_add) (NMPlatform *,
+	int (*link_add) (NMPlatform *self,
 	                 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);
-
-	gboolean (*link_refresh) (NMPlatform *, int ifindex);
-
-	gboolean (*link_set_netns) (NMPlatform *, int ifindex, int netns_fd);
-
-	void (*process_events) (NMPlatform *self);
-
-	gboolean (*link_set_up) (NMPlatform *, int ifindex, gboolean *out_no_firmware);
-	gboolean (*link_set_down) (NMPlatform *, int ifindex);
-	gboolean (*link_set_arp) (NMPlatform *, int ifindex);
-	gboolean (*link_set_noarp) (NMPlatform *, int ifindex);
+	gboolean (*link_delete) (NMPlatform *self, int ifindex);
+	gboolean (*link_refresh) (NMPlatform *self, int ifindex);
+	gboolean (*link_set_netns) (NMPlatform *self, int ifindex, int netns_fd);
+	gboolean (*link_set_up) (NMPlatform *self, int ifindex, gboolean *out_no_firmware);
+	gboolean (*link_set_down) (NMPlatform *self, int ifindex);
+	gboolean (*link_set_arp) (NMPlatform *self, int ifindex);
+	gboolean (*link_set_noarp) (NMPlatform *self, int ifindex);
 
 	const char *(*link_get_udi) (NMPlatform *self, int ifindex);
 	struct udev_device *(*link_get_udev_device) (NMPlatform *self, int ifindex);
 
-	int (*link_set_user_ipv6ll_enabled) (NMPlatform *, int ifindex, gboolean enabled);
-	gboolean (*link_set_token) (NMPlatform *, int ifindex, NMUtilsIPv6IfaceId iid);
+	int (*link_set_user_ipv6ll_enabled) (NMPlatform *self, int ifindex, gboolean enabled);
+	gboolean (*link_set_token) (NMPlatform *self, int ifindex, NMUtilsIPv6IfaceId iid);
 
-	gboolean (*link_get_permanent_address) (NMPlatform *,
+	gboolean (*link_get_permanent_address) (NMPlatform *self,
 	                                        int ifindex,
 	                                        guint8 *buf,
 	                                        size_t *length);
-	int (*link_set_address) (NMPlatform *, int ifindex, gconstpointer address, size_t length);
-	int (*link_set_mtu) (NMPlatform *, int ifindex, guint32 mtu);
-	gboolean (*link_set_name) (NMPlatform *, int ifindex, const char *name);
-	gboolean (*link_set_sriov_params) (NMPlatform *, int ifindex, guint num_vfs, int autoprobe);
+	int (*link_set_address) (NMPlatform *self, int ifindex, gconstpointer address, size_t length);
+	int (*link_set_mtu) (NMPlatform *self, int ifindex, guint32 mtu);
+	gboolean (*link_set_name) (NMPlatform *self, int ifindex, const char *name);
+	void (*link_set_sriov_params_async) (NMPlatform *self,
+	                                     int ifindex,
+	                                     guint num_vfs,
+	                                     int autoprobe,
+	                                     NMPlatformAsyncCallback callback,
+	                                     gpointer callback_data,
+	                                     GCancellable *cancellable);
 	gboolean (*link_set_sriov_vfs) (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 	gboolean (*link_set_bridge_vlans) (NMPlatform *self, int ifindex, gboolean on_master, const NMPlatformBridgeVlan *const *vlans);
 
-	char *   (*link_get_physical_port_id) (NMPlatform *, int ifindex);
-	guint    (*link_get_dev_id) (NMPlatform *, int ifindex);
-	gboolean (*link_get_wake_on_lan) (NMPlatform *, int ifindex);
-	gboolean (*link_get_driver_info) (NMPlatform *,
+	char *   (*link_get_physical_port_id) (NMPlatform *self, int ifindex);
+	guint    (*link_get_dev_id) (NMPlatform *self, int ifindex);
+	gboolean (*link_get_wake_on_lan) (NMPlatform *self, int ifindex);
+	gboolean (*link_get_driver_info) (NMPlatform *self,
 	                                  int ifindex,
 	                                  char **out_driver_name,
 	                                  char **out_driver_version,
 	                                  char **out_fw_version);
 
-	gboolean (*link_supports_carrier_detect) (NMPlatform *, int ifindex);
-	gboolean (*link_supports_vlans) (NMPlatform *, int ifindex);
-	gboolean (*link_supports_sriov) (NMPlatform *, int ifindex);
+	gboolean (*link_supports_carrier_detect) (NMPlatform *self, int ifindex);
+	gboolean (*link_supports_vlans) (NMPlatform *self, int ifindex);
+	gboolean (*link_supports_sriov) (NMPlatform *self, int ifindex);
 
-	gboolean (*link_enslave) (NMPlatform *, int master, int slave);
-	gboolean (*link_release) (NMPlatform *, int master, int slave);
+	gboolean (*link_enslave) (NMPlatform *self, int master, int slave);
+	gboolean (*link_release) (NMPlatform *self, int master, int slave);
 
-	gboolean (*link_can_assume) (NMPlatform *, int ifindex);
+	gboolean (*link_can_assume) (NMPlatform *self, int ifindex);
 
 	int (*link_wireguard_change) (NMPlatform *self,
 	                              int ifindex,
@@ -967,7 +1040,7 @@ typedef struct {
 	                              guint peers_len,
 	                              NMPlatformWireGuardChangeFlags change_flags);
 
-	gboolean (*vlan_add) (NMPlatform *, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
+	gboolean (*vlan_add) (NMPlatform *self, const char *name, int parent, int vlanid, guint32 vlanflags, const NMPlatformLink **out_link);
 	gboolean (*link_vlan_change) (NMPlatform *self,
 	                              int ifindex,
 	                              NMVlanFlags flags_mask,
@@ -978,81 +1051,79 @@ typedef struct {
 	                              gboolean egress_reset_all,
 	                              const NMVlanQosMapping *egress_map,
 	                              gsize n_egress_map);
-	gboolean (*link_vxlan_add) (NMPlatform *,
+	gboolean (*link_vxlan_add) (NMPlatform *self,
 	                            const char *name,
 	                            const NMPlatformLnkVxlan *props,
 	                            const NMPlatformLink **out_link);
-	gboolean (*link_gre_add) (NMPlatform *,
+	gboolean (*link_gre_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkGre *props,
 	                          const NMPlatformLink **out_link);
-	gboolean (*link_ip6tnl_add) (NMPlatform *,
+	gboolean (*link_ip6tnl_add) (NMPlatform *self,
 	                             const char *name,
 	                             const NMPlatformLnkIp6Tnl *props,
 	                             const NMPlatformLink **out_link);
-	gboolean (*link_ip6gre_add) (NMPlatform *,
+	gboolean (*link_ip6gre_add) (NMPlatform *self,
 	                             const char *name,
 	                             const NMPlatformLnkIp6Tnl *props,
 	                             const NMPlatformLink **out_link);
-	gboolean (*link_ipip_add) (NMPlatform *,
+	gboolean (*link_ipip_add) (NMPlatform *self,
 	                           const char *name,
 	                           const NMPlatformLnkIpIp *props,
 	                           const NMPlatformLink **out_link);
-	gboolean (*link_macsec_add) (NMPlatform *,
+	gboolean (*link_macsec_add) (NMPlatform *self,
 	                             const char *name,
 	                             int parent,
 	                             const NMPlatformLnkMacsec *props,
 	                             const NMPlatformLink **out_link);
-	gboolean (*link_macvlan_add) (NMPlatform *,
+	gboolean (*link_macvlan_add) (NMPlatform *self,
 	                              const char *name,
 	                              int parent,
 	                              const NMPlatformLnkMacvlan *props,
 	                              const NMPlatformLink **out_link);
-	gboolean (*link_sit_add) (NMPlatform *,
+	gboolean (*link_sit_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkSit *props,
 	                          const NMPlatformLink **out_link);
-
-	gboolean (*link_tun_add) (NMPlatform *platform,
+	gboolean (*link_tun_add) (NMPlatform *self,
 	                          const char *name,
 	                          const NMPlatformLnkTun *props,
 	                          const NMPlatformLink **out_link,
 	                          int *out_fd);
-
-	gboolean (*link_6lowpan_add) (NMPlatform *platform,
+	gboolean (*link_6lowpan_add) (NMPlatform *self,
 	                              const char *name,
 	                              int parent,
 	                              const NMPlatformLink **out_link);
 
-	gboolean (*infiniband_partition_add) (NMPlatform *, int parent, int p_key, const NMPlatformLink **out_link);
-	gboolean (*infiniband_partition_delete) (NMPlatform *, int parent, int p_key);
-
-	gboolean    (*wifi_get_capabilities) (NMPlatform *, int ifindex, NMDeviceWifiCapabilities *caps);
-	gboolean    (*wifi_get_bssid)        (NMPlatform *, int ifindex, guint8 *bssid);
-	guint32     (*wifi_get_frequency)    (NMPlatform *, int ifindex);
-	int         (*wifi_get_quality)      (NMPlatform *, int ifindex);
-	guint32     (*wifi_get_rate)         (NMPlatform *, int ifindex);
-	NM80211Mode (*wifi_get_mode)         (NMPlatform *, int ifindex);
-	void        (*wifi_set_mode)         (NMPlatform *, int ifindex, NM80211Mode mode);
-	void        (*wifi_set_powersave)    (NMPlatform *, int ifindex, guint32 powersave);
-	guint32     (*wifi_find_frequency)   (NMPlatform *, int ifindex, const guint32 *freqs);
-	void        (*wifi_indicate_addressing_running) (NMPlatform *, int ifindex, gboolean running);
-	NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan) (NMPlatform *, int ifindex);
-	gboolean    (*wifi_set_wake_on_wlan) (NMPlatform *, int ifindex, NMSettingWirelessWakeOnWLan wowl);
-
-	guint32     (*mesh_get_channel)      (NMPlatform *, int ifindex);
-	gboolean    (*mesh_set_channel)      (NMPlatform *, int ifindex, guint32 channel);
-	gboolean    (*mesh_set_ssid)         (NMPlatform *, int ifindex, const guint8 *ssid, gsize len);
-
-	guint16     (*wpan_get_pan_id)       (NMPlatform *, int ifindex);
-	gboolean    (*wpan_set_pan_id)       (NMPlatform *, int ifindex, guint16 pan_id);
-	guint16     (*wpan_get_short_addr)   (NMPlatform *, int ifindex);
-	gboolean    (*wpan_set_short_addr)   (NMPlatform *, int ifindex, guint16 short_addr);
-	gboolean    (*wpan_set_channel)      (NMPlatform *, int ifindex, guint8 page, guint8 channel);
-
-	gboolean (*object_delete) (NMPlatform *, const NMPObject *obj);
-
-	gboolean (*ip4_address_add) (NMPlatform *,
+	gboolean (*infiniband_partition_add) (NMPlatform *self, int parent, int p_key, const NMPlatformLink **out_link);
+	gboolean (*infiniband_partition_delete) (NMPlatform *self, int parent, int p_key);
+
+	gboolean    (*wifi_get_capabilities) (NMPlatform *self, int ifindex, NMDeviceWifiCapabilities *caps);
+	gboolean    (*wifi_get_bssid)        (NMPlatform *self, int ifindex, guint8 *bssid);
+	guint32     (*wifi_get_frequency)    (NMPlatform *self, int ifindex);
+	int         (*wifi_get_quality)      (NMPlatform *self, int ifindex);
+	guint32     (*wifi_get_rate)         (NMPlatform *self, int ifindex);
+	NM80211Mode (*wifi_get_mode)         (NMPlatform *self, int ifindex);
+	void        (*wifi_set_mode)         (NMPlatform *self, int ifindex, NM80211Mode mode);
+	void        (*wifi_set_powersave)    (NMPlatform *self, int ifindex, guint32 powersave);
+	guint32     (*wifi_find_frequency)   (NMPlatform *self, int ifindex, const guint32 *freqs);
+	void        (*wifi_indicate_addressing_running) (NMPlatform *self, int ifindex, gboolean running);
+	NMSettingWirelessWakeOnWLan (*wifi_get_wake_on_wlan) (NMPlatform *self, int ifindex);
+	gboolean    (*wifi_set_wake_on_wlan) (NMPlatform *self, int ifindex, NMSettingWirelessWakeOnWLan wowl);
+
+	guint32     (*mesh_get_channel)      (NMPlatform *self, int ifindex);
+	gboolean    (*mesh_set_channel)      (NMPlatform *self, int ifindex, guint32 channel);
+	gboolean    (*mesh_set_ssid)         (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
+
+	guint16     (*wpan_get_pan_id)       (NMPlatform *self, int ifindex);
+	gboolean    (*wpan_set_pan_id)       (NMPlatform *self, int ifindex, guint16 pan_id);
+	guint16     (*wpan_get_short_addr)   (NMPlatform *self, int ifindex);
+	gboolean    (*wpan_set_short_addr)   (NMPlatform *self, int ifindex, guint16 short_addr);
+	gboolean    (*wpan_set_channel)      (NMPlatform *self, int ifindex, guint8 page, guint8 channel);
+
+	gboolean (*object_delete) (NMPlatform *self, const NMPObject *obj);
+
+	gboolean (*ip4_address_add) (NMPlatform *self,
 	                             int ifindex,
 	                             in_addr_t address,
 	                             guint8 plen,
@@ -1061,7 +1132,7 @@ typedef struct {
 	                             guint32 preferred_lft,
 	                             guint32 flags,
 	                             const char *label);
-	gboolean (*ip6_address_add) (NMPlatform *,
+	gboolean (*ip6_address_add) (NMPlatform *self,
 	                             int ifindex,
 	                             struct in6_addr address,
 	                             guint8 plen,
@@ -1069,10 +1140,10 @@ typedef struct {
 	                             guint32 lifetime,
 	                             guint32 preferred_lft,
 	                             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);
+	gboolean (*ip4_address_delete) (NMPlatform *self, int ifindex, in_addr_t address, guint8 plen, in_addr_t peer_address);
+	gboolean (*ip6_address_delete) (NMPlatform *self, int ifindex, struct in6_addr address, guint8 plen);
 
-	int (*ip_route_add) (NMPlatform *,
+	int (*ip_route_add) (NMPlatform *self,
 	                     NMPNlmFlags flags,
 	                     int addr_family,
 	                     const NMPlatformIPRoute *route);
@@ -1215,7 +1286,7 @@ gboolean nm_platform_get_use_udev (NMPlatform *self);
 gboolean nm_platform_get_log_with_ptr (NMPlatform *self);
 
 NMPNetns *nm_platform_netns_get (NMPlatform *self);
-gboolean nm_platform_netns_push (NMPlatform *platform, NMPNetns **netns);
+gboolean nm_platform_netns_push (NMPlatform *self, NMPNetns **netns);
 
 const char *nm_link_type_to_string (NMLinkType link_type);
 
@@ -1241,16 +1312,24 @@ const char *nm_link_type_to_string (NMLinkType link_type);
 
 int nm_platform_sysctl_open_netdir (NMPlatform *self, int ifindex, char *out_ifname);
 gboolean nm_platform_sysctl_set (NMPlatform *self, const char *pathid, int dirfd, const char *path, const char *value);
+void nm_platform_sysctl_set_async (NMPlatform *self,
+                                   const char *pathid,
+                                   int dirfd,
+                                   const char *path,
+                                   const char *const *values,
+                                   NMPlatformAsyncCallback callback,
+                                   gpointer data,
+                                   GCancellable *cancellable);
 char *nm_platform_sysctl_get (NMPlatform *self, const char *pathid, int dirfd, const char *path);
 gint32 nm_platform_sysctl_get_int32 (NMPlatform *self, const char *pathid, int dirfd, const char *path, gint32 fallback);
 gint64 nm_platform_sysctl_get_int_checked (NMPlatform *self, const char *pathid, int dirfd, const char *path, guint base, gint64 min, gint64 max, gint64 fallback);
 
-char *nm_platform_sysctl_ip_conf_get (NMPlatform *platform,
+char *nm_platform_sysctl_ip_conf_get (NMPlatform *self,
                                       int addr_family,
                                       const char *ifname,
                                       const char *property);
 
-gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
+gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *self,
                                                    int addr_family,
                                                    const char *ifname,
                                                    const char *property,
@@ -1259,13 +1338,13 @@ gint64 nm_platform_sysctl_ip_conf_get_int_checked (NMPlatform *platform,
                                                    gint64 max,
                                                    gint64 fallback);
 
-gboolean nm_platform_sysctl_ip_conf_set (NMPlatform *platform,
+gboolean nm_platform_sysctl_ip_conf_set (NMPlatform *self,
                                          int addr_family,
                                          const char *ifname,
                                          const char *property,
                                          const char *value);
 
-gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
+gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *self,
                                                int addr_family,
                                                const char *ifname,
                                                const char *property,
@@ -1274,6 +1353,10 @@ gboolean nm_platform_sysctl_ip_conf_set_int64 (NMPlatform *platform,
 gboolean nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe (NMPlatform *self,
                                                              const char *iface,
                                                              int value);
+int nm_platform_sysctl_ip_conf_get_rp_filter_ipv4 (NMPlatform *platform,
+                                                   const char *iface,
+                                                   gboolean consider_all,
+                                                   gboolean *out_due_to_all);
 
 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);
@@ -1298,7 +1381,7 @@ 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 _NMDedupMultiHeadEntry *nm_platform_lookup (NMPlatform *self,
                                                          const struct _NMPLookup *lookup);
 
 gboolean nm_platform_lookup_predicate_routes_main (const NMPObject *obj,
@@ -1306,7 +1389,7 @@ gboolean nm_platform_lookup_predicate_routes_main (const NMPObject *obj,
 gboolean nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel (const NMPObject *obj,
                                                                       gpointer user_data);
 
-GPtrArray *nm_platform_lookup_clone (NMPlatform *platform,
+GPtrArray *nm_platform_lookup_clone (NMPlatform *self,
                                      const struct _NMPLookup *lookup,
                                      NMPObjectPredicateFunc predicate,
                                      gpointer user_data);
@@ -1325,18 +1408,6 @@ gboolean nm_platform_link_get_user_ipv6ll_enabled (NMPlatform *self, int ifindex
 
 gconstpointer nm_platform_link_get_address (NMPlatform *self, int ifindex, size_t *length);
 
-static inline GBytes *
-nm_platform_link_get_address_as_bytes (NMPlatform *self, int ifindex)
-{
-	gconstpointer p;
-	gsize l;
-
-	p = nm_platform_link_get_address (self, ifindex, &l);
-	return p
-	       ? g_bytes_new (p, l)
-	       : NULL;
-}
-
 int nm_platform_link_get_master (NMPlatform *self, int slave);
 
 gboolean nm_platform_link_can_assume (NMPlatform *self, int ifindex);
@@ -1368,7 +1439,15 @@ gboolean nm_platform_link_get_permanent_address (NMPlatform *self, int ifindex,
 int nm_platform_link_set_address (NMPlatform *self, int ifindex, const void *address, size_t length);
 int 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_params (NMPlatform *self, int ifindex, guint num_vfs, int autoprobe);
+
+void nm_platform_link_set_sriov_params_async (NMPlatform *self,
+                                              int ifindex,
+                                              guint num_vfs,
+                                              int autoprobe,
+                                              NMPlatformAsyncCallback callback,
+                                              gpointer callback_data,
+                                              GCancellable *cancellable);
+
 gboolean nm_platform_link_set_sriov_vfs (NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs);
 gboolean nm_platform_link_set_bridge_vlans (NMPlatform *self, int ifindex, gboolean on_master, const NMPlatformBridgeVlan *const *vlans);
 
@@ -1466,11 +1545,11 @@ guint32     nm_platform_mesh_get_channel      (NMPlatform *self, int ifindex);
 gboolean    nm_platform_mesh_set_channel      (NMPlatform *self, int ifindex, guint32 channel);
 gboolean    nm_platform_mesh_set_ssid         (NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);
 
-guint16     nm_platform_wpan_get_pan_id       (NMPlatform *platform, int ifindex);
-gboolean    nm_platform_wpan_set_pan_id       (NMPlatform *platform, int ifindex, guint16 pan_id);
-guint16     nm_platform_wpan_get_short_addr   (NMPlatform *platform, int ifindex);
-gboolean    nm_platform_wpan_set_short_addr   (NMPlatform *platform, int ifindex, guint16 short_addr);
-gboolean    nm_platform_wpan_set_channel      (NMPlatform *platform, int ifindex, guint8 page, guint8 channel);
+guint16     nm_platform_wpan_get_pan_id       (NMPlatform *self, int ifindex);
+gboolean    nm_platform_wpan_set_pan_id       (NMPlatform *self, int ifindex, guint16 pan_id);
+guint16     nm_platform_wpan_get_short_addr   (NMPlatform *self, int ifindex);
+gboolean    nm_platform_wpan_set_short_addr   (NMPlatform *self, int ifindex, guint16 short_addr);
+gboolean    nm_platform_wpan_set_channel      (NMPlatform *self, int ifindex, guint8 page, guint8 channel);
 
 void                   nm_platform_ip4_address_set_addr (NMPlatformIP4Address *addr, in_addr_t address, guint8 plen);
 const struct in6_addr *nm_platform_ip6_address_get_peer (const NMPlatformIP6Address *addr);
@@ -1704,6 +1783,8 @@ void nm_platform_lnk_wireguard_hash_update (const NMPlatformLnkWireGuard *obj, N
 void nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h);
 void nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h);
 
+#define NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN ((gsize) 162)
+
 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);
 const char *nm_platform_addr_flags2str (unsigned flags, char *buf, gsize len);
diff --git a/src/platform/nmp-netns.c b/src/platform/nmp-netns.c
index f34dc83d..b33d86fb 100644
--- a/src/platform/nmp-netns.c
+++ b/src/platform/nmp-netns.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nmp-netns.h b/src/platform/nmp-netns.h
index 55a4b95f..3c0ca74b 100644
--- a/src/platform/nmp-netns.h
+++ b/src/platform/nmp-netns.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index 0d20100c..5cbf9428 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -398,7 +397,7 @@ _idx_obj_part (const DedupMultiIdxType *idx_type,
 		if (h) {
 			nm_hash_update_vals (h,
 			                     idx_type->cache_id_type,
-			                     NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_a)->ifindex);
+			                     obj_a->obj_with_ifindex.ifindex);
 		}
 		return 1;
 
@@ -787,7 +786,7 @@ _nmp_object_new_from_class (const NMPClass *klass)
 }
 
 NMPObject *
-nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plobj)
+nmp_object_new (NMPObjectType obj_type, gconstpointer plobj)
 {
 	const NMPClass *klass = nmp_class_from_type (obj_type);
 	NMPObject *obj;
@@ -2147,9 +2146,12 @@ nmp_lookup_init_object_by_addr_family (NMPLookup *lookup,
 	NMPObject *o;
 
 	nm_assert (lookup);
-	nm_assert_addr_family (addr_family);
 	nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_ROUTING_RULE));
 
+	if (addr_family == AF_UNSPEC)
+		return nmp_lookup_init_obj_type (lookup, obj_type);
+
+	nm_assert_addr_family (addr_family);
 	o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type);
 	NMP_OBJECT_CAST_ROUTING_RULE (o)->addr_family = addr_family;
 	lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY;
diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h
index 3ce9db36..cea5f958 100644
--- a/src/platform/nmp-object.h
+++ b/src/platform/nmp-object.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -535,101 +534,27 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX (NMPObjectType obj_type)
 		_obj ? &NM_CONSTCAST (NMPObject, _obj)->obj_with_ifindex : NULL; \
 	})
 
-#define NMP_OBJECT_CAST_LINK(obj) \
+#define _NMP_OBJECT_CAST(obj, field, ...) \
 	({ \
 		typeof (obj) _obj = (obj); \
 		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_LINK); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->link : NULL; \
+		nm_assert (!_obj || NM_IN_SET (NMP_OBJECT_GET_TYPE (_obj), __VA_ARGS__)); \
+		_obj ? &NM_CONSTCAST (NMPObject, _obj)->field : 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 (_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 (_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 (_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 (_obj) == NMP_OBJECT_TYPE_IP6_ROUTE); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->ip6_route : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_ROUTING_RULE(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_ROUTING_RULE); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->routing_rule : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_QDISC(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_QDISC); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->qdisc : NULL; \
-	})
-
-#define NMP_OBJECT_CAST_TFILTER(obj) \
-	({ \
-		typeof (obj) _obj = (obj); \
-		\
-		nm_assert (!_obj || NMP_OBJECT_GET_TYPE (_obj) == NMP_OBJECT_TYPE_TFILTER); \
-		_obj ? &NM_CONSTCAST (NMPObject, _obj)->tfilter : NULL; \
-	})
+#define NMP_OBJECT_CAST_LINK(obj)          _NMP_OBJECT_CAST (obj, link,          NMP_OBJECT_TYPE_LINK)
+#define NMP_OBJECT_CAST_IP_ADDRESS(obj)    _NMP_OBJECT_CAST (obj, ip_address,    NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)
+#define NMP_OBJECT_CAST_IPX_ADDRESS(obj)   _NMP_OBJECT_CAST (obj, ipx_address,   NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)
+#define NMP_OBJECT_CAST_IP4_ADDRESS(obj)   _NMP_OBJECT_CAST (obj, ip4_address,   NMP_OBJECT_TYPE_IP4_ADDRESS)
+#define NMP_OBJECT_CAST_IP6_ADDRESS(obj)   _NMP_OBJECT_CAST (obj, ip6_address,   NMP_OBJECT_TYPE_IP6_ADDRESS)
+#define NMP_OBJECT_CAST_IP_ROUTE(obj)      _NMP_OBJECT_CAST (obj, ip_route,      NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
+#define NMP_OBJECT_CAST_IPX_ROUTE(obj)     _NMP_OBJECT_CAST (obj, ipx_route,     NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)
+#define NMP_OBJECT_CAST_IP4_ROUTE(obj)     _NMP_OBJECT_CAST (obj, ip4_route,     NMP_OBJECT_TYPE_IP4_ROUTE)
+#define NMP_OBJECT_CAST_IP6_ROUTE(obj)     _NMP_OBJECT_CAST (obj, ip6_route,     NMP_OBJECT_TYPE_IP6_ROUTE)
+#define NMP_OBJECT_CAST_ROUTING_RULE(obj)  _NMP_OBJECT_CAST (obj, routing_rule,  NMP_OBJECT_TYPE_ROUTING_RULE)
+#define NMP_OBJECT_CAST_QDISC(obj)         _NMP_OBJECT_CAST (obj, qdisc,         NMP_OBJECT_TYPE_QDISC)
+#define NMP_OBJECT_CAST_TFILTER(obj)       _NMP_OBJECT_CAST (obj, tfilter,       NMP_OBJECT_TYPE_TFILTER)
+#define NMP_OBJECT_CAST_LNK_WIREGUARD(obj) _NMP_OBJECT_CAST (obj, lnk_wireguard, NMP_OBJECT_TYPE_LNK_WIREGUARD)
 
 static inline const NMPObject *
 nmp_object_ref (const NMPObject *obj)
@@ -674,7 +599,7 @@ nmp_object_unref (const NMPObject *obj)
 		_changed; \
 	})
 
-NMPObject *nmp_object_new (NMPObjectType obj_type, const NMPlatformObject *plob);
+NMPObject *nmp_object_new (NMPObjectType obj_type, gconstpointer plobj);
 NMPObject *nmp_object_new_link (int ifindex);
 
 const NMPObject *nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj);
@@ -1059,4 +984,15 @@ nm_platform_lookup_ip6_route_by_weak_id (NMPlatform *platform,
 	return nm_platform_lookup (platform, &lookup);
 }
 
+static inline const NMDedupMultiHeadEntry *
+nm_platform_lookup_object_by_addr_family (NMPlatform *platform,
+                                          NMPObjectType obj_type,
+                                          int addr_family)
+{
+	NMPLookup lookup;
+
+	nmp_lookup_init_object_by_addr_family (&lookup, obj_type, addr_family);
+	return nm_platform_lookup (platform, &lookup);
+}
+
 #endif /* __NMP_OBJECT_H__ */
diff --git a/src/platform/nmp-rules-manager.c b/src/platform/nmp-rules-manager.c
index 970afcde..f982f04c 100644
--- a/src/platform/nmp-rules-manager.c
+++ b/src/platform/nmp-rules-manager.c
@@ -81,7 +81,7 @@ typedef struct {
 	 * But it will not remove or add them otherwise.
 	 *
 	 * Otherwise, the track_priority_val goes together with track_priority_present.
-	 * In case of one rule being tracked multile times (with different priorities),
+	 * In case of one rule being tracked multiple times (with different priorities),
 	 * the one with higher priority wins. See _rules_obj_get_best_data().
 	 * Then, the winning present state either enforces that the rule is present
 	 * or absent.
@@ -99,6 +99,17 @@ typedef enum {
 	CONFIG_STATE_NONE          = 0,
 	CONFIG_STATE_ADDED_BY_US   = 1,
 	CONFIG_STATE_REMOVED_BY_US = 2,
+
+	/* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE).
+	 *
+	 * Maybe we would only need to track whether we touched the rule at all. But we
+	 * track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US)
+	 * or did we remove it (CONFIG_STATE_REMOVED_BY_US)?
+	 * Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively
+	 * add/remove it, but whenever we are about to undo the add/remove, we need to do it.
+	 * In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally
+	 * force the state next time when necessary. */
+	CONFIG_STATE_OWNED_BY_US   = 3,
 } ConfigState;
 
 typedef struct {
@@ -111,8 +122,10 @@ typedef struct {
 	 * This makes NMPRulesManager stateful (beyond the configuration that indicates
 	 * which rules are tracked).
 	 * After a restart, NetworkManager would no longer remember which rules were added
-	 * by us. That would need to be fixed by persisting the state and reloading it after
-	 * restart. */
+	 * by us.
+	 *
+	 * That is partially fixed by NetworkManager taking over the rules that it
+	 * actively configures (see %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */
 	ConfigState config_state;
 } RulesObjData;
 
@@ -121,6 +134,15 @@ typedef struct {
 	CList user_tag_lst_head;
 } RulesUserTagData;
 
+/*****************************************************************************/
+
+static void _rules_data_untrack (NMPRulesManager *self,
+                                 RulesData *rules_data,
+                                 gboolean remove_user_tag_data,
+                                 gboolean make_owned_by_us);
+
+/*****************************************************************************/
+
 static void
 _rules_data_assert (const RulesData *rules_data, gboolean linked)
 {
@@ -278,11 +300,31 @@ _rules_data_lookup (GHashTable *by_data,
 	return g_hash_table_lookup (by_data, &rules_data_needle);
 }
 
+/**
+ * nmp_rules_manager_track:
+ * @self: the #NMPRulesManager instance
+ * @routing_rule: the #NMPlatformRoutingRule to track or untrack
+ * @track_priority: the priority for tracking the rule. Note that
+ *   negative values indicate a forced absence of the rule. Priorities
+ *   are compared with their absolute values (with higher absolute
+ *   value being more important). For example, if you track the same
+ *   rule twice, once with priority -5 and +10, then the rule is
+ *   present (because the positive number is more important).
+ *   The special value 0 indicates weakly-tracked rules.
+ * @user_tag: the tag associated with tracking this rule. The same tag
+ *   must be used to untrack the rule later.
+ * @user_tag_untrack: if not %NULL, at the same time untrack this user-tag
+ *   for the same rule. Note that this is different from a plain nmp_rules_manager_untrack(),
+ *   because it enforces ownership of the now tracked rule. On the other hand,
+ *   a plain nmp_rules_manager_untrack() merely forgets about the tracking.
+ *   The purpose here is to set this to %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG.
+ */
 void
 nmp_rules_manager_track (NMPRulesManager *self,
                          const NMPlatformRoutingRule *routing_rule,
                          gint32 track_priority,
-                         gconstpointer user_tag)
+                         gconstpointer user_tag,
+                         gconstpointer user_tag_untrack)
 {
 	NMPObject obj_stack;
 	const NMPObject *p_obj_stack;
@@ -359,6 +401,17 @@ nmp_rules_manager_track (NMPRulesManager *self,
 		}
 	}
 
+	if (user_tag_untrack) {
+		if (user_tag != user_tag_untrack) {
+			RulesData *rules_data_untrack;
+
+			rules_data_untrack = _rules_data_lookup (self->by_data, p_obj_stack, user_tag_untrack);
+			if (rules_data_untrack)
+				_rules_data_untrack (self, rules_data_untrack, FALSE, TRUE);
+		} else
+			nm_assert_not_reached ();
+	}
+
 	_rules_data_assert (rules_data, TRUE);
 
 	if (changed) {
@@ -377,7 +430,8 @@ nmp_rules_manager_track (NMPRulesManager *self,
 static void
 _rules_data_untrack (NMPRulesManager *self,
                      RulesData *rules_data,
-                     gboolean remove_user_tag_data)
+                     gboolean remove_user_tag_data,
+                     gboolean make_owned_by_us)
 {
 	RulesObjData *obj_data;
 
@@ -401,15 +455,22 @@ _rules_data_untrack (NMPRulesManager *self,
 #endif
 
 	nm_assert (!c_list_is_empty (&rules_data->user_tag_lst));
-	if (   remove_user_tag_data
-	    && c_list_length_is (&rules_data->user_tag_lst, 1))
-		g_hash_table_remove (self->by_user_tag, &rules_data->user_tag);
 
 	obj_data = g_hash_table_lookup (self->by_obj, &rules_data->obj);
 	nm_assert (obj_data);
 	nm_assert (c_list_contains (&obj_data->obj_lst_head, &rules_data->obj_lst));
 	nm_assert (obj_data == g_hash_table_lookup (self->by_obj, &rules_data->obj));
 
+	if (make_owned_by_us) {
+		if (obj_data->config_state == CONFIG_STATE_NONE) {
+			/* we need to mark this entry that it requires a touch on the next
+			 * sync. */
+			obj_data->config_state = CONFIG_STATE_OWNED_BY_US;
+		}
+	} else if (   remove_user_tag_data
+	           && c_list_length_is (&rules_data->user_tag_lst, 1))
+		g_hash_table_remove (self->by_user_tag, &rules_data->user_tag);
+
 	/* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry
 	 * around for the next sync -- so that we can undo what we did earlier. */
 	if (   obj_data->config_state == CONFIG_STATE_NONE
@@ -440,7 +501,7 @@ nmp_rules_manager_untrack (NMPRulesManager *self,
 
 	rules_data = _rules_data_lookup (self->by_data, p_obj_stack, user_tag);
 	if (rules_data)
-		_rules_data_untrack (self, rules_data, TRUE);
+		_rules_data_untrack (self, rules_data, TRUE, FALSE);
 }
 
 void
@@ -486,7 +547,7 @@ nmp_rules_manager_untrack_all (NMPRulesManager *self,
 	c_list_for_each_entry_safe (rules_data, rules_data_safe, &user_tag_data->user_tag_lst_head, user_tag_lst) {
 		if (   all
 		    || rules_data->dirty)
-			_rules_data_untrack (self, rules_data, FALSE);
+			_rules_data_untrack (self, rules_data, FALSE, FALSE);
 	}
 	if (c_list_is_empty (&user_tag_data->user_tag_lst_head))
 		g_hash_table_remove (self->by_user_tag, user_tag_data);
@@ -525,11 +586,17 @@ nmp_rules_manager_sync (NMPRulesManager *self,
 
 			rd_best = _rules_obj_get_best_data (obj_data);
 			if (rd_best) {
-				if (rd_best->track_priority_present)
+				if (rd_best->track_priority_present) {
+					if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
+						obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
 					continue;
+				}
 				if (rd_best->track_priority_val == 0) {
-					if (obj_data->config_state != CONFIG_STATE_ADDED_BY_US)
+					if (!NM_IN_SET (obj_data->config_state, CONFIG_STATE_ADDED_BY_US,
+					                                        CONFIG_STATE_OWNED_BY_US)) {
+						obj_data->config_state = CONFIG_STATE_NONE;
 						continue;
+					}
 					obj_data->config_state = CONFIG_STATE_NONE;
 				}
 			}
@@ -563,11 +630,17 @@ nmp_rules_manager_sync (NMPRulesManager *self,
 			continue;
 		}
 
-		if (!rd_best->track_priority_present)
+		if (!rd_best->track_priority_present) {
+			if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
+				obj_data->config_state = CONFIG_STATE_REMOVED_BY_US;
 			continue;
+		}
 		if (rd_best->track_priority_val == 0) {
-			if (obj_data->config_state != CONFIG_STATE_REMOVED_BY_US)
+			if (!NM_IN_SET (obj_data->config_state, CONFIG_STATE_REMOVED_BY_US,
+			                                        CONFIG_STATE_OWNED_BY_US)) {
+				obj_data->config_state = CONFIG_STATE_NONE;
 				continue;
+			}
 			obj_data->config_state = CONFIG_STATE_NONE;
 		}
 
@@ -610,7 +683,7 @@ nmp_rules_manager_track_from_platform (NMPRulesManager *self,
 		    && rr->addr_family != addr_family)
 			continue;
 
-		nmp_rules_manager_track (self, rr, tracking_priority, user_tag);
+		nmp_rules_manager_track (self, rr, tracking_priority, user_tag, NULL);
 	}
 }
 
@@ -638,7 +711,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 		nmp_rules_manager_track (self,
 		                         &((NMPlatformRoutingRule) {
 		                             .addr_family = AF_INET,
@@ -648,7 +722,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 		nmp_rules_manager_track (self,
 		                         &((NMPlatformRoutingRule) {
 		                             .addr_family = AF_INET,
@@ -658,7 +733,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 	}
 	if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) {
 		nmp_rules_manager_track (self,
@@ -670,7 +746,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 		nmp_rules_manager_track (self,
 		                         &((NMPlatformRoutingRule) {
 		                             .addr_family = AF_INET6,
@@ -680,7 +757,8 @@ nmp_rules_manager_track_default (NMPRulesManager *self,
 		                             .protocol    = RTPROT_KERNEL,
 		                         }),
 		                         track_priority,
-		                         user_tag);
+		                         user_tag,
+		                         NULL);
 	}
 }
 
diff --git a/src/platform/nmp-rules-manager.h b/src/platform/nmp-rules-manager.h
index 310c7971..645df5c2 100644
--- a/src/platform/nmp-rules-manager.h
+++ b/src/platform/nmp-rules-manager.h
@@ -22,6 +22,8 @@
 
 /*****************************************************************************/
 
+#define NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG ((const void *) nmp_rules_manager_new)
+
 typedef struct _NMPRulesManager NMPRulesManager;
 
 NMPRulesManager *nmp_rules_manager_new (NMPlatform *platform);
@@ -35,7 +37,8 @@ NM_AUTO_DEFINE_FCN0 (NMPRulesManager *, _nmp_rules_manager_unref, nmp_rules_mana
 void nmp_rules_manager_track (NMPRulesManager *self,
                               const NMPlatformRoutingRule *routing_rule,
                               gint32 track_priority,
-                              gconstpointer user_tag);
+                              gconstpointer user_tag,
+                              gconstpointer user_tag_untrack);
 
 void nmp_rules_manager_track_default (NMPRulesManager *self,
                                       int addr_family,
diff --git a/src/platform/tests/meson.build b/src/platform/tests/meson.build
index 8086a46c..aaee8964 100644
--- a/src/platform/tests/meson.build
+++ b/src/platform/tests/meson.build
@@ -1,23 +1,22 @@
 test_units = [
-  ['test-link-fake',     'test-link.c',       test_nm_dep_fake,  default_test_timeout],
-  ['test-link-linux',    'test-link.c',       test_nm_dep_linux, 900],
-  ['test-address-fake',  'test-address.c',    test_nm_dep_fake,  default_test_timeout],
-  ['test-address-linux', 'test-address.c',    test_nm_dep_linux, default_test_timeout],
-  ['test-general',       'test-general.c',    test_nm_dep,       default_test_timeout],
-  ['test-nmp-object',    'test-nmp-object.c', test_nm_dep,       default_test_timeout],
-  ['test-route-fake',    'test-route.c',      test_nm_dep_fake,  default_test_timeout],
-  ['test-route-linux',   'test-route.c',      test_nm_dep_linux, default_test_timeout],
-  ['test-cleanup-fake',  'test-cleanup.c',    test_nm_dep_fake,  default_test_timeout],
-  ['test-cleanup-linux', 'test-cleanup.c',    test_nm_dep_linux, default_test_timeout],
+  [ 'test-address-fake',     'test-address.c',          test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-address-linux',    'test-address.c',          test_nm_dep_linux, default_test_timeout ],
+  [ 'test-cleanup-fake',     'test-cleanup.c',          test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-cleanup-linux',    'test-cleanup.c',          test_nm_dep_linux, default_test_timeout ],
+  [ 'test-link-fake',        'test-link.c',             test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-link-linux',       'test-link.c',             test_nm_dep_linux, 900                  ],
+  [ 'test-nmp-object',       'test-nmp-object.c',       test_nm_dep,       default_test_timeout ],
+  [ 'test-platform-general', 'test-platform-general.c', test_nm_dep,       default_test_timeout ],
+  [ 'test-route-fake',       'test-route.c',            test_nm_dep_fake,  default_test_timeout ],
+  [ 'test-route-linux',      'test-route.c',            test_nm_dep_linux, default_test_timeout ],
 ]
 
 foreach test_unit: test_units
   exe = executable(
-    'platform-' + test_unit[0],
+    test_unit[0],
     test_unit[1],
     dependencies: test_unit[2],
   )
-
   test(
     'platform/' + test_unit[0],
     test_script,
@@ -26,10 +25,8 @@ foreach test_unit: test_units
   )
 endforeach
 
-test = 'monitor'
-
 executable(
-  test,
-  test + '.c',
+  'monitor',
+  'monitor.c',
   dependencies: test_nm_dep,
 )
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index fff5967b..6a5e0e16 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/tests/test-address.c b/src/platform/tests/test-address.c
index d84a806d..3a3e9009 100644
--- a/src/platform/tests/test-address.c
+++ b/src/platform/tests/test-address.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/tests/test-cleanup.c b/src/platform/tests/test-cleanup.c
index 6c73a63e..70d64c15 100644
--- a/src/platform/tests/test-cleanup.c
+++ b/src/platform/tests/test-cleanup.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index 7ebda9ff..cd0bef45 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -679,7 +678,7 @@ nmtstp_run_command_check_external_global (void)
 {
 	if (!nmtstp_is_root_test ())
 		return FALSE;
-	switch (nmtst_get_rand_int () % 3) {
+	switch (nmtst_get_rand_uint32 () % 3) {
 	case 0:
 		return -1;
 	case 1:
@@ -699,7 +698,7 @@ nmtstp_run_command_check_external (int external_command)
 	}
 	if (!nmtstp_is_root_test ())
 		return FALSE;
-	return (nmtst_get_rand_int () % 2) == 0;
+	return (nmtst_get_rand_uint32 () % 2) == 0;
 }
 
 /*****************************************************************************/
@@ -843,7 +842,7 @@ _ip_address_add (NMPlatform *platform,
 			g_assert (flags == 0);
 
 			if (   peer_address->addr4 != address->addr4
-			    || nmtst_get_rand_int () % 2) {
+			    || nmtst_get_rand_uint32 () % 2) {
 				/* If the peer is the same as the local address, we can omit it. The result should be identical */
 				nm_sprintf_buf (s_peer, " peer %s", nm_utils_inet4_ntop (peer_address->addr4, b2));
 			} else
@@ -1994,7 +1993,7 @@ nmtstp_netns_select_random (NMPlatform **platforms, gsize n_platforms, NMPNetns
 	for (i = 0; i < n_platforms; i++)
 		g_assert (NM_IS_PLATFORM (platforms[i]));
 
-	i = nmtst_get_rand_int () % (n_platforms + 1);
+	i = nmtst_get_rand_uint32 () % (n_platforms + 1);
 	if (i == 0)
 		return;
 	g_assert (nm_platform_netns_push (platforms[i - 1], netns));
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index a0650cc1..5926dac1 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -241,7 +240,7 @@ test_slave (int master, int type, SignalData *master_changed)
 	/* Set master up */
 	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, master, NULL));
 	g_assert (nm_platform_link_is_up (NM_PLATFORM_GET, master));
-	accept_signals (master_changed, 1, 2);
+	accept_signals (master_changed, 1, 3);
 
 	g_signal_handlers_disconnect_by_func (NM_PLATFORM_GET, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg1);
 	g_signal_handlers_disconnect_by_func (NM_PLATFORM_GET, G_CALLBACK (test_link_changed_signal_cb), &test_link_changed_signal_arg2);
@@ -287,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, 0, 2);
+	accept_signals (master_changed, 0, 3);
 
 	/* Enslave again
 	 *
@@ -329,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, 0, 2);
+	accept_signals (master_changed, 0, 3);
 
 	ensure_no_signal (master_changed);
 
@@ -515,8 +514,8 @@ test_bridge_addr (void)
 	link = *plink;
 	g_assert_cmpstr (link.name, ==, DEVICE_NAME);
 
-	g_assert_cmpint (link.addr.len, ==, sizeof (addr));
-	g_assert (!memcmp (link.addr.data, addr, sizeof (addr)));
+	g_assert_cmpint (link.l_address.len, ==, sizeof (addr));
+	g_assert (!memcmp (link.l_address.data, addr, sizeof (addr)));
 
 	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 	g_assert (plink);
@@ -538,8 +537,8 @@ test_bridge_addr (void)
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 	}
 
-	g_assert_cmpint (plink->addr.len, ==, sizeof (addr));
-	g_assert (!memcmp (plink->addr.data, addr, sizeof (addr)));
+	g_assert_cmpint (plink->l_address.len, ==, sizeof (addr));
+	g_assert (!memcmp (plink->l_address.data, addr, sizeof (addr)));
 
 	nmtstp_link_delete (NULL, -1, link.ifindex, link.name, TRUE);
 }
@@ -2100,7 +2099,7 @@ test_create_many_links_do (guint n_devices)
 	char name[64];
 	const NMPlatformLink *pllink;
 	gs_unref_array GArray *ifindexes = g_array_sized_new (FALSE, FALSE, sizeof (int), n_devices);
-	const int EX = ((int) (nmtst_get_rand_int () % 4)) - 1;
+	const int EX = ((int) (nmtst_get_rand_uint32 () % 4)) - 1;
 
 	g_assert (EX >= -1 && EX <= 2);
 
@@ -2459,17 +2458,17 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 		NMPlatform *p = (k == 0 ? platform_1 : platform_2);
 		const char *id = (k == 0 ? "a" : "b");
 
-		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+		for (i = 0, j = nmtst_get_rand_uint32 () % 5; i < j; i++)
 			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-a-%s-%02d", id, i));
 
 		_ADD_DUMMY (p, "dummy1_");
 
-		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+		for (i = 0, j = nmtst_get_rand_uint32 () % 5; i < j; i++)
 			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-b-%s-%02d", id, i));
 
 		_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "dummy2%s", id));
 
-		for (i = 0, j = nmtst_get_rand_int () % 5; i < j; i++)
+		for (i = 0, j = nmtst_get_rand_uint32 () % 5; i < j; i++)
 			_ADD_DUMMY (p, nm_sprintf_buf (sbuf, "other-c-%s-%02d", id, i));
 	}
 
@@ -2491,17 +2490,17 @@ test_netns_general (gpointer fixture, gconstpointer test_data)
 		NMPlatform *pl;
 		const char *path;
 
-		j = nmtst_get_rand_int () % 2;
+		j = nmtst_get_rand_uint32 () % 2;
 
-		if (nmtst_get_rand_int () % 2) {
+		if (nmtst_get_rand_uint32 () % 2) {
 			pl = platform_1;
-			if (nmtst_get_rand_int () % 2)
+			if (nmtst_get_rand_uint32 () % 2)
 				path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
 			else
 				path = "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6";
 		} else {
 			pl = platform_2;
-			if (nmtst_get_rand_int () % 2)
+			if (nmtst_get_rand_uint32 () % 2)
 				path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6";
 			else
 				path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6";
@@ -2654,7 +2653,7 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 	pl[2].platform = platform_2 = _test_netns_create_platform ();
 
 	pl_base = &pl[0];
-	i = nmtst_get_rand_int () % (G_N_ELEMENTS (pl) + 1);
+	i = nmtst_get_rand_uint32 () % (G_N_ELEMENTS (pl) + 1);
 	if (i < G_N_ELEMENTS (pl)) {
 		pl_base = &pl[i];
 		g_assert (nm_platform_netns_push (pl[i].platform, &netns_pop));
@@ -2674,7 +2673,7 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 		g_ptr_array_add (device_names, tmp);
 		pl[i].sysctl_path = tmp;
 
-		pl[i].sysctl_value = nmtst_get_rand_int () % 2 ? "1" : "0";
+		pl[i].sysctl_value = nmtst_get_rand_uint32 () % 2 ? "1" : "0";
 
 		_ADD_DUMMY (pl[i].platform, pl[i].device_name);
 
@@ -2689,16 +2688,16 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 		pl[i].ns_mnt = tmp;
 	}
 
-	nstack = nmtst_get_rand_int () % (G_N_ELEMENTS (stack) + 1);
+	nstack = nmtst_get_rand_uint32 () % (G_N_ELEMENTS (stack) + 1);
 	for (i = 0; i < nstack; i++) {
-		stack[i].pl = &pl[nmtst_get_rand_int () % G_N_ELEMENTS (pl)];
-		stack[i].ns_types = ns_types_list[nmtst_get_rand_int () % G_N_ELEMENTS (ns_types_list)];
+		stack[i].pl = &pl[nmtst_get_rand_uint32 () % G_N_ELEMENTS (pl)];
+		stack[i].ns_types = ns_types_list[nmtst_get_rand_uint32 () % G_N_ELEMENTS (ns_types_list)];
 
 		nmp_netns_push_type (nm_platform_netns_get (stack[i].pl->platform), stack[i].ns_types);
 	}
 
 	/* pop some again. */
-	for (i = nmtst_get_rand_int () % (nstack + 1); i > 0; i--) {
+	for (i = nmtst_get_rand_uint32 () % (nstack + 1); i > 0; i--) {
 		g_assert (nstack > 0);
 		nstack--;
 		nmp_netns_pop (nm_platform_netns_get (stack[nstack].pl->platform));
@@ -2765,9 +2764,9 @@ test_netns_push (gpointer fixture, gconstpointer test_data)
 static void
 test_netns_bind_to_path (gpointer fixture, gconstpointer test_data)
 {
-#define P_VAR_RUN                "/var/run"
-#define P_VAR_RUN_NETNS          "/var/run/netns"
-#define P_VAR_RUN_NETNS_BINDNAME "/var/run/netns/"P_NETNS_BINDNAME
+#define P_VAR_RUN                "/run"
+#define P_VAR_RUN_NETNS          "/run/netns"
+#define P_VAR_RUN_NETNS_BINDNAME "/run/netns/"P_NETNS_BINDNAME
 #define P_NETNS_BINDNAME         "nmtst-iproute2-netns"
 	gs_unref_object NMPlatform *platform_0 = NULL;
 	gs_unref_object NMPlatform *platform_1 = NULL;
@@ -2789,7 +2788,7 @@ test_netns_bind_to_path (gpointer fixture, gconstpointer test_data)
 	g_assert_cmpint (mount ("tmpfs", P_VAR_RUN, "tmpfs", MS_NOATIME | MS_NODEV | MS_NOSUID, "mode=0755,size=32K"), ==, 0);
 	g_assert_cmpint (mkdir (P_VAR_RUN_NETNS, 755), ==, 0);
 
-	i = (nmtst_get_rand_int () % 2) + 1;
+	i = (nmtst_get_rand_uint32 () % 2) + 1;
 	netns = nm_platform_netns_get (platforms[i]);
 
 	_ADD_DUMMY (platforms[i], "dummy2b");
@@ -2832,9 +2831,9 @@ test_sysctl_rename (void)
 	ifindex[0] = nmtstp_link_dummy_add (PL, -1, IFNAME[0])->ifindex;
 	ifindex[1] = nmtstp_link_dummy_add (PL, -1, IFNAME[1])->ifindex;
 
-	s = (nmtst_get_rand_int () % 2) ? NULL : ifname_buf;
+	s = (nmtst_get_rand_uint32 () % 2) ? NULL : ifname_buf;
 
-	if (nmtst_get_rand_int () % 2) {
+	if (nmtst_get_rand_uint32 () % 2) {
 		/* bring the platform cache out of sync */
 		nmtstp_run_command_check ("ip link set %s name %s", IFNAME[0], IFNAME[2]);
 		nm_platform_process_events (PL);
@@ -2847,7 +2846,7 @@ test_sysctl_rename (void)
 	}
 
 	/* open dirfd for IFNAME[0] */
-	i = nmtst_get_rand_int () % (2 + G_N_ELEMENTS (IFNAME));
+	i = nmtst_get_rand_uint32 () % (2 + G_N_ELEMENTS (IFNAME));
 	if (i == 0) {
 		dirfd = nm_platform_sysctl_open_netdir (PL,
 		                                        ifindex[0],
@@ -2866,7 +2865,7 @@ test_sysctl_rename (void)
 		g_assert_cmpstr (s, ==, IFNAME[0]);
 
 	/* possibly rename the interfaces. */
-	switch (nmtst_get_rand_int () % 4) {
+	switch (nmtst_get_rand_uint32 () % 4) {
 	case 0:
 		break;
 	case 1:
@@ -2879,11 +2878,11 @@ test_sysctl_rename (void)
 	}
 
 	/* possibly, resync platform cache (should make no difference). */
-	if (nmtst_get_rand_int () % 2)
+	if (nmtst_get_rand_uint32 () % 2)
 		nm_platform_process_events (PL);
 
 	/* check that we still read the same file. */
-	switch (nmtst_get_rand_int () % 2) {
+	switch (nmtst_get_rand_uint32 () % 2) {
 	case 0: {
 		gs_free char *c = NULL;
 
@@ -2930,7 +2929,7 @@ test_sysctl_netns_switch (void)
 	platforms[0] = platform_0 = nm_linux_platform_new (TRUE, TRUE);
 	platforms[1] = platform_1 = _test_netns_create_platform ();
 	platforms[2] = platform_2 = _test_netns_create_platform ();
-	PL = platforms[nmtst_get_rand_int () % 3];
+	PL = platforms[nmtst_get_rand_uint32 () % 3];
 
 	nmtstp_netns_select_random (platforms, G_N_ELEMENTS (platforms), &netns_pop_1);
 
@@ -2938,7 +2937,7 @@ test_sysctl_netns_switch (void)
 
 	nmtstp_netns_select_random (platforms, G_N_ELEMENTS (platforms), &netns_pop_2);
 
-	s = (nmtst_get_rand_int () % 2) ? NULL : ifname_buf;
+	s = (nmtst_get_rand_uint32 () % 2) ? NULL : ifname_buf;
 	dirfd = nm_platform_sysctl_open_netdir (PL,
 	                                        ifindex,
 	                                        s);
@@ -3015,6 +3014,92 @@ test_sysctl_netns_switch (void)
 	nmtstp_link_delete (PL, FALSE, ifindex, NULL, TRUE);
 }
 
+static void
+sysctl_set_async_cb_assert_success (GError *error, gpointer data)
+{
+	g_assert_no_error (error);
+	g_main_loop_quit (data);
+}
+
+static void
+sysctl_set_async_cb_assert_failure (GError *error, gpointer data)
+{
+	g_assert (error);
+	g_main_loop_quit (data);
+}
+
+static void
+test_sysctl_set_async (void)
+{
+	NMPlatform *const PL = NM_PLATFORM_GET;
+	const char *const IFNAME = "nm-dummy-0";
+	const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/rp_filter";
+	gs_free GMainLoop *loop = NULL;
+	gs_unref_object GCancellable *cancellable = NULL;
+	int ifindex;
+
+	ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex;
+	loop = g_main_loop_new (NULL, FALSE);
+	cancellable = g_cancellable_new ();
+
+	nm_platform_sysctl_set_async (PL,
+	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
+	                              (const char *[]) { "2", NULL},
+	                              sysctl_set_async_cb_assert_success,
+	                              loop,
+	                              cancellable);
+
+	if (!nmtst_main_loop_run (loop, 1000))
+		g_assert_not_reached ();
+
+	g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1),
+	                 ==,
+	                 2);
+
+	nm_platform_sysctl_set_async (PL,
+	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
+	                              (const char *[]) { "2", "0", "1", "0", "1", NULL},
+	                              sysctl_set_async_cb_assert_success,
+	                              loop,
+	                              cancellable);
+
+	if (!nmtst_main_loop_run (loop, 2000))
+		g_assert_not_reached ();
+
+	g_assert_cmpint (nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (PATH), -1),
+	                 ==,
+	                 1);
+
+	nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE);
+}
+
+static void
+test_sysctl_set_async_fail (void)
+{
+	NMPlatform *const PL = NM_PLATFORM_GET;
+	const char *const IFNAME = "nm-dummy-0";
+	const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/does-not-exist";
+	gs_free GMainLoop *loop = NULL;
+	gs_unref_object GCancellable *cancellable = NULL;
+	int ifindex;
+
+	ifindex = nmtstp_link_dummy_add (PL, -1, IFNAME)->ifindex;
+	loop = g_main_loop_new (NULL, FALSE);
+	cancellable = g_cancellable_new ();
+
+	nm_platform_sysctl_set_async (PL,
+	                              NMP_SYSCTL_PATHID_ABSOLUTE (PATH),
+	                              (const char *[]) { "2", NULL},
+	                              sysctl_set_async_cb_assert_failure,
+	                              loop,
+	                              cancellable);
+
+	if (!nmtst_main_loop_run (loop, 1000))
+		g_assert_not_reached ();
+
+	nmtstp_link_delete (NULL, -1, ifindex, IFNAME, TRUE);
+}
+
 /*****************************************************************************/
 
 static gpointer
@@ -3238,6 +3323,8 @@ _nmtstp_setup_tests (void)
 
 		g_test_add_func ("/general/sysctl/rename", test_sysctl_rename);
 		g_test_add_func ("/general/sysctl/netns-switch", test_sysctl_netns_switch);
+		g_test_add_func ("/general/sysctl/set-async", test_sysctl_set_async);
+		g_test_add_func ("/general/sysctl/set-async-fail", test_sysctl_set_async_fail);
 
 		g_test_add_func ("/link/ethtool/features/get", test_ethtool_features_get);
 	}
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index e2019e96..08bde437 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -267,7 +266,7 @@ test_cache_link (void)
 	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;
-	gboolean use_udev = nmtst_get_rand_int () % 2;
+	gboolean use_udev = nmtst_get_rand_uint32 () % 2;
 
 	multi_idx = nm_dedup_multi_index_new ();
 
@@ -503,7 +502,7 @@ test_cache_qdisc (void)
 	nm_auto_nmpobj NMPObject *obj2 = nmp_object_new (NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_2);
 
 	multi_idx = nm_dedup_multi_index_new ();
-	cache = nmp_cache_new (multi_idx, nmtst_get_rand_int () % 2);
+	cache = nmp_cache_new (multi_idx, nmtst_get_rand_uint32 () % 2);
 
 	g_assert (nmp_cache_lookup_obj (cache, obj1a) == NULL);
 
diff --git a/src/platform/tests/test-general.c b/src/platform/tests/test-platform-general.c
index eebc15d8..2b723810 100644
--- a/src/platform/tests/test-general.c
+++ b/src/platform/tests/test-platform-general.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -52,6 +51,31 @@ test_link_get_all (void)
 
 /*****************************************************************************/
 
+static void
+test_nm_platform_link_flags2str (void)
+{
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		char buf[NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 100];
+		const char *s;
+		const guint flags = ((i == 0) ? ~0u : nmtst_get_rand_uint ());
+		gsize l;
+
+		s = nm_platform_link_flags2str (flags, buf, sizeof (buf));
+		g_assert (s);
+
+		l = strlen (s);
+		if (l > NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN)
+			g_error ("nm_platform_link_flags2str(%x) produced a longer output than %zu chars: \"%s\"", flags, NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, s);
+		if (   flags == ~0u
+		    && l != NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN)
+			g_error ("nm_platform_link_flags2str(%x) is expected to produce %zu chars, but produced %zu: \"%s\"", flags, NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, l, s);
+	}
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE ();
 
 int
@@ -61,7 +85,7 @@ main (int argc, char **argv)
 
 	g_test_add_func ("/general/init_linux_platform", test_init_linux_platform);
 	g_test_add_func ("/general/link_get_all", test_link_get_all);
+	g_test_add_func ("/general/nm_platform_link_flags2str", test_nm_platform_link_flags2str);
 
 	return g_test_run ();
 }
-
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index e6ffe1ae..44bfbc58 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -445,7 +444,7 @@ test_ip4_route_get (void)
 	result = nm_platform_ip_route_get (NM_PLATFORM_GET,
 	                                   AF_INET,
 	                                   &a,
-	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
+	                                   nmtst_get_rand_uint32 () % 2 ? 0 : ifindex,
 	                                   &route);
 
 	g_assert (NMTST_NM_ERR_SUCCESS (result));
@@ -607,7 +606,7 @@ test_ip6_route_get (void)
 	result = nm_platform_ip_route_get (NM_PLATFORM_GET,
 	                                   AF_INET6,
 	                                   a,
-	                                   nmtst_get_rand_int () % 2 ? 0 : ifindex,
+	                                   nmtst_get_rand_uint32 () % 2 ? 0 : ifindex,
 	                                   &route);
 
 	g_assert (NMTST_NM_ERR_SUCCESS (result));
@@ -815,9 +814,9 @@ test_ip (gconstpointer test_data)
 
 		if (   order_len == 0
 		    || (   order_len < G_N_ELEMENTS (order_idx)
-		        && nmtst_get_rand_int () % 2)) {
+		        && nmtst_get_rand_uint32 () % 2)) {
 again_find_idx:
-			idx = nmtst_get_rand_int () % G_N_ELEMENTS (iface_data);
+			idx = nmtst_get_rand_uint32 () % G_N_ELEMENTS (iface_data);
 			for (i = 0; i < order_len; i++) {
 				if (order_idx[i] == idx)
 					goto again_find_idx;
@@ -827,7 +826,7 @@ again_find_idx:
 			r->ifindex = iface_data[idx].ifindex;
 			g_assert (NMTST_NM_ERR_SUCCESS (nm_platform_ip4_route_add (platform, NMP_NLM_FLAG_APPEND, r)));
 		} else {
-			i = nmtst_get_rand_int () % order_len;
+			i = nmtst_get_rand_uint32 () % order_len;
 			idx = order_idx[i];
 			for (i++; i < order_len; i++)
 				order_idx[i - 1] = order_idx[i];
@@ -1129,17 +1128,17 @@ _rule_create_random (NMPlatform *platform)
 
 	addr_size = nm_utils_addr_family_to_size (rr->addr_family);
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 50)
 		rr->priority = 10000 + ((~p) % 20u);
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 40)
 		nm_sprintf_buf (rr->iifname, "t-iif-%u", (~p) % 20);
 	else if ((p % 1000u) < 80)
 		nm_sprintf_buf (rr->iifname, "%s", DEVICE_NAME);
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 40)
 		nm_sprintf_buf (rr->oifname, "t-oif-%d", (~p) % 20);
 	else if ((p % 1000u) < 80)
@@ -1149,14 +1148,14 @@ _rule_create_random (NMPlatform *platform)
 		NMIPAddr *p_addr = i ? &rr->src     : &rr->dst;
 		guint8 *p_len    = i ? &rr->src_len : &rr->dst_len;
 
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		if ((p % 1000u) < 100) {
 			/* if we set src_len/dst_len to zero, the src/dst is actually ignored.
 			 *
 			 * For fuzzying, still set the address. It shall have no further effect.
 			 * */
 			*p_len = (~p) % (addr_size * 8 + 1);
-			p = nmtst_get_rand_int ();
+			p = nmtst_get_rand_uint32 ();
 			if ((p % 3u) == 0) {
 				if (rr->addr_family == AF_INET)
 					p_addr->addr4 = nmtst_inet4_from_string (nm_sprintf_buf (saddr, "192.192.5.%u", (~p) % 256u));
@@ -1167,12 +1166,12 @@ _rule_create_random (NMPlatform *platform)
 		}
 	}
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 50)
 		rr->tun_id = 10000 + ((~p) % 20);
 
 again_action:
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 500)
 		rr->action = FR_ACT_UNSPEC;
 	else if ((p % 1000u) < 750)
@@ -1180,22 +1179,22 @@ again_action:
 	else
 		rr->action = (~p) % 0x100u;
 
-	rr->priority = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->priority = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (   rr->action == FR_ACT_GOTO
 	    && rr->priority == G_MAXINT32)
 		goto again_action;
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 10000u) < 100)
 		rr->goto_target = rr->priority + 1;
 	else
-		rr->goto_target = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+		rr->goto_target = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 	if (   rr->action == FR_ACT_GOTO
 	    && rr->goto_target <= rr->priority)
 		goto again_action;
 
-	p = nmtst_get_rand_int ();
+	p = nmtst_get_rand_uint32 ();
 	if ((p % 1000u) < 25) {
 		if (_rule_check_kernel_support (platform, FRA_L3MDEV)) {
 			rr->l3mdev = TRUE;
@@ -1205,7 +1204,7 @@ again_action:
 
 again_table:
 	if (!rr->l3mdev) {
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		if ((p % 1000u) < 700)
 			rr->table = RT_TABLE_UNSPEC;
 		else if ((p % 1000u) < 850)
@@ -1217,33 +1216,33 @@ again_table:
 			goto again_table;
 	}
 
-	rr->fwmark = _rr_rand_choose_u32 (nmtst_get_rand_int ());
-	rr->fwmask = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->fwmark = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
+	rr->fwmask = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
-	rr->flow = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->flow = _rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_PROTOCOL))
-		rr->protocol = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+		rr->protocol = _rr_rand_choose_u8 (nmtst_get_rand_uint32 ());
 
 #define IPTOS_TOS_MASK 0x1E
 
 again_tos:
-	rr->tos = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+	rr->tos = _rr_rand_choose_u8 (nmtst_get_rand_uint32 ());
 	if (   rr->addr_family == AF_INET
 	    && rr->tos & ~IPTOS_TOS_MASK)
 		goto again_tos;
 
 	if (_rule_check_kernel_support (platform, FRA_IP_PROTO))
-		rr->ip_proto = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+		rr->ip_proto = _rr_rand_choose_u8 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_PREFIXLEN))
-		rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+		rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_IFGROUP))
-		rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+		rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_uint32 ());
 
 	if (_rule_check_kernel_support (platform, FRA_UID_RANGE)) {
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		rr->uid_range_has = (p % 10000u) < 200;
 	}
 
@@ -1265,10 +1264,10 @@ again_uid_range:
 		if (!_rule_check_kernel_support (platform, attribute))
 			continue;
 
-		p = nmtst_get_rand_int ();
+		p = nmtst_get_rand_uint32 ();
 		if ((p % 10000u) < 300) {
 			while (range->start == 0) {
-				p = p ^ nmtst_get_rand_int ();
+				p = p ^ nmtst_get_rand_uint32 ();
 				range->start = nmtst_rand_select (1u, 0xFFFEu, ((p      ) % 0xFFFEu) + 1);
 				range->end   = nmtst_rand_select (1u, 0xFFFEu, ((p >> 16) % 0xFFFEu) + 1, range->start);
 				if (range->end < range->start)
@@ -1277,7 +1276,7 @@ again_uid_range:
 		}
 	}
 
-	p = nmtst_get_rand_int () % 1000u;
+	p = nmtst_get_rand_uint32 () % 1000u;
 	if (p < 100)
 		rr->flags |= FIB_RULE_INVERT;
 
@@ -1345,8 +1344,7 @@ _rule_fuzzy_equal (const NMPObject *obj,
 			rr_co.tos = 0;
 		if (rr->ip_proto == 0)
 			rr_co.ip_proto = 0;
-		if (rr->suppress_prefixlen_inverse == 0)
-			rr_co.suppress_prefixlen_inverse = 0;
+		rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse;
 		if (rr->suppress_ifgroup_inverse == 0)
 			rr_co.suppress_ifgroup_inverse = 0;
 		if (!rr->uid_range_has)
@@ -1483,10 +1481,10 @@ test_rule (gconstpointer test_data)
 
 	if (TEST_IDX != 1) {
 		nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
-		g_ptr_array_set_size (objs, nmtst_get_rand_int () % (objs->len + 1));
+		g_ptr_array_set_size (objs, nmtst_get_rand_uint32 () % (objs->len + 1));
 	}
 
-	n = (TEST_IDX != 1) ? nmtst_get_rand_int () % 50u : 0u;
+	n = (TEST_IDX != 1) ? nmtst_get_rand_uint32 () % 50u : 0u;
 	for (i = 0; i < n; i++) {
 		nm_auto_nmpobj const NMPObject *o = NULL;
 		guint try = 0;
@@ -1536,16 +1534,18 @@ again:
 			nmp_rules_manager_track (rules_manager,
 			                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 			                         1,
-			                         USER_TAG_1);
+			                         USER_TAG_1,
+			                         NULL);
 			if (nmtst_get_rand_bool ()) {
 				/* this has no effect, because a negative priority (of same absolute value)
 				 * has lower priority than the positive priority above. */
 				nmp_rules_manager_track (rules_manager,
 				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 				                         -1,
-				                         USER_TAG_2);
+				                         USER_TAG_2,
+				                         NULL);
 			}
-			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+			if (nmtst_get_rand_uint32 () % objs_sync->len == 0) {
 				nmp_rules_manager_sync (rules_manager, FALSE);
 				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1);
 			}
@@ -1555,7 +1555,7 @@ again:
 		g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len);
 
 		for (i = 0; i < objs_sync->len; i++) {
-			switch (nmtst_get_rand_int () % 3) {
+			switch (nmtst_get_rand_uint32 () % 3) {
 			case 0:
 				nmp_rules_manager_untrack (rules_manager,
 				                           NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
@@ -1568,16 +1568,18 @@ again:
 				nmp_rules_manager_track (rules_manager,
 				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 				                         -1,
-				                         USER_TAG_1);
+				                         USER_TAG_1,
+				                         NULL);
 				break;
 			case 2:
 				nmp_rules_manager_track (rules_manager,
 				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
 				                         -2,
-				                         USER_TAG_2);
+				                         USER_TAG_2,
+				                         NULL);
 				break;
 			}
-			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+			if (nmtst_get_rand_uint32 () % objs_sync->len == 0) {
 				nmp_rules_manager_sync (rules_manager, FALSE);
 				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len - i - 1);
 			}
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.c b/src/platform/wifi/nm-wifi-utils-nl80211.c
index 4f7ede97..164dada4 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -103,7 +102,7 @@ _nl80211_alloc_msg (int id, int ifindex, int phy, guint32 cmd, guint32 flags)
 	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	return NULL;
+	g_return_val_if_reached (NULL);
 }
 
 static struct nl_msg *
@@ -200,6 +199,9 @@ nl80211_iface_info_handler (struct nl_msg *msg, void *arg)
 	case NL80211_IFTYPE_STATION:
 		info->mode = NM_802_11_MODE_INFRA;
 		break;
+	case NL80211_IFTYPE_MESH_POINT:
+		info->mode = NM_802_11_MODE_MESH;
+		break;
 	}
 
 	return NL_SKIP;
@@ -242,6 +244,9 @@ wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 	case NM_802_11_MODE_AP:
 		NLA_PUT_U32 (msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_AP);
 		break;
+	case NM_802_11_MODE_MESH:
+		NLA_PUT_U32 (msg, NL80211_ATTR_IFTYPE, NL80211_IFTYPE_MESH_POINT);
+		break;
 	default:
 		g_assert_not_reached ();
 	}
@@ -250,7 +255,7 @@ wifi_nl80211_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -267,7 +272,7 @@ wifi_nl80211_set_powersave (NMWifiUtils *data, guint32 powersave)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 static int
@@ -365,7 +370,7 @@ wifi_nl80211_set_wake_on_wlan (NMWifiUtils *data, NMSettingWirelessWakeOnWLan wo
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 /* @divisor: pass what value @xbm should be divided by to get dBm */
@@ -642,7 +647,7 @@ nl80211_get_ap_info (NMWifiUtilsNl80211 *self,
 	return;
 
 nla_put_failure:
-	return;
+	g_return_if_reached ();
 }
 
 static guint32
@@ -695,7 +700,7 @@ wifi_nl80211_indicate_addressing_running (NMWifiUtils *data, gboolean running)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 struct nl80211_device_info {
@@ -893,10 +898,11 @@ static int nl80211_wiphy_info_handler (struct nl_msg *msg, void *arg)
 		int i;
 
 		nla_for_each_nested (nl_mode, tb[NL80211_ATTR_SUPPORTED_IFTYPES], i) {
-			if (nla_type (nl_mode) == NL80211_IFTYPE_AP)
-				info->caps |= NM_WIFI_DEVICE_CAP_AP;
-			else if (nla_type (nl_mode) == NL80211_IFTYPE_ADHOC)
-				info->caps |= NM_WIFI_DEVICE_CAP_ADHOC;
+			switch (nla_type (nl_mode)) {
+			case NL80211_IFTYPE_AP:         info->caps |= NM_WIFI_DEVICE_CAP_AP;    break;
+			case NL80211_IFTYPE_ADHOC:      info->caps |= NM_WIFI_DEVICE_CAP_ADHOC; break;
+			case NL80211_IFTYPE_MESH_POINT: info->caps |= NM_WIFI_DEVICE_CAP_MESH;  break;
+			}
 		}
 	}
 
diff --git a/src/platform/wifi/nm-wifi-utils-nl80211.h b/src/platform/wifi/nm-wifi-utils-nl80211.h
index 27f67697..a4c9ca55 100644
--- a/src/platform/wifi/nm-wifi-utils-nl80211.h
+++ b/src/platform/wifi/nm-wifi-utils-nl80211.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/wifi/nm-wifi-utils-private.h b/src/platform/wifi/nm-wifi-utils-private.h
index bcbc1c51..4cae22e5 100644
--- a/src/platform/wifi/nm-wifi-utils-private.h
+++ b/src/platform/wifi/nm-wifi-utils-private.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/wifi/nm-wifi-utils-wext.c b/src/platform/wifi/nm-wifi-utils-wext.c
index 3aa1720a..d19cf8a5 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.c
+++ b/src/platform/wifi/nm-wifi-utils-wext.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/wifi/nm-wifi-utils-wext.h b/src/platform/wifi/nm-wifi-utils-wext.h
index 44b11afb..70cae966 100644
--- a/src/platform/wifi/nm-wifi-utils-wext.h
+++ b/src/platform/wifi/nm-wifi-utils-wext.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/wifi/nm-wifi-utils.c b/src/platform/wifi/nm-wifi-utils.c
index 96071faa..1e42db6a 100644
--- a/src/platform/wifi/nm-wifi-utils.c
+++ b/src/platform/wifi/nm-wifi-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -90,7 +89,8 @@ nm_wifi_utils_set_mode (NMWifiUtils *data, const NM80211Mode mode)
 	g_return_val_if_fail (data != NULL, FALSE);
 	g_return_val_if_fail (   (mode == NM_802_11_MODE_INFRA)
 	                      || (mode == NM_802_11_MODE_AP)
-	                      || (mode == NM_802_11_MODE_ADHOC), FALSE);
+	                      || (mode == NM_802_11_MODE_ADHOC)
+	                      || (mode == NM_802_11_MODE_MESH), FALSE);
 
 	klass = NM_WIFI_UTILS_GET_CLASS (data);
 
diff --git a/src/platform/wifi/nm-wifi-utils.h b/src/platform/wifi/nm-wifi-utils.h
index 36148b5a..2e89053b 100644
--- a/src/platform/wifi/nm-wifi-utils.h
+++ b/src/platform/wifi/nm-wifi-utils.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/platform/wpan/nm-wpan-utils.c b/src/platform/wpan/nm-wpan-utils.c
index b7a51e9b..0afc2a4d 100644
--- a/src/platform/wpan/nm-wpan-utils.c
+++ b/src/platform/wpan/nm-wpan-utils.c
@@ -92,7 +92,7 @@ _nl802154_alloc_msg (int id, int ifindex, guint32 cmd, guint32 flags)
 	return g_steal_pointer (&msg);
 
 nla_put_failure:
-	return NULL;
+	g_return_val_if_reached (NULL);
 }
 
 static struct nl_msg *
@@ -217,7 +217,7 @@ nm_wpan_utils_set_pan_id (NMWpanUtils *self, guint16 pan_id)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 guint16
@@ -244,7 +244,7 @@ nm_wpan_utils_set_short_addr (NMWpanUtils *self, guint16 short_addr)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 gboolean
@@ -262,7 +262,7 @@ nm_wpan_utils_set_channel (NMWpanUtils *self, guint8 page, guint8 channel)
 	return err >= 0;
 
 nla_put_failure:
-	return FALSE;
+	g_return_val_if_reached (FALSE);
 }
 
 /*****************************************************************************/
diff --git a/src/ppp/nm-ppp-manager-call.c b/src/ppp/nm-ppp-manager-call.c
index d951e4fd..c134a2b2 100644
--- a/src/ppp/nm-ppp-manager-call.c
+++ b/src/ppp/nm-ppp-manager-call.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/ppp/nm-ppp-manager-call.h b/src/ppp/nm-ppp-manager-call.h
index a93ed11f..6561a388 100644
--- a/src/ppp/nm-ppp-manager-call.h
+++ b/src/ppp/nm-ppp-manager-call.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/ppp/nm-ppp-manager.c b/src/ppp/nm-ppp-manager.c
index 04c14dfb..3708b940 100644
--- a/src/ppp/nm-ppp-manager.c
+++ b/src/ppp/nm-ppp-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/ppp/nm-ppp-manager.h b/src/ppp/nm-ppp-manager.h
index ec0ca46e..d2285e3e 100644
--- a/src/ppp/nm-ppp-manager.h
+++ b/src/ppp/nm-ppp-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/ppp/nm-ppp-plugin-api.h b/src/ppp/nm-ppp-plugin-api.h
index 95ddd211..7177a7d6 100644
--- a/src/ppp/nm-ppp-plugin-api.h
+++ b/src/ppp/nm-ppp-plugin-api.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/ppp/nm-ppp-status.h b/src/ppp/nm-ppp-status.h
index d785cc2c..a57685a1 100644
--- a/src/ppp/nm-ppp-status.h
+++ b/src/ppp/nm-ppp-status.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/ppp/nm-pppd-plugin.c b/src/ppp/nm-pppd-plugin.c
index a8d6749a..f0b9027c 100644
--- a/src/ppp/nm-pppd-plugin.c
+++ b/src/ppp/nm-pppd-plugin.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -46,17 +45,18 @@ int plugin_init (void);
 
 char pppd_version[] = VERSION;
 
-static GDBusProxy *proxy = NULL;
+static struct {
+	GDBusConnection *dbus_connection;
+	char *ipparam;
+} gl;
 
 static void
-nm_phasechange (void *data, int arg)
+nm_phasechange (int arg)
 {
 	NMPPPStatus ppp_status = NM_PPP_STATUS_UNKNOWN;
-	char new_name[IF_NAMESIZE];
 	char *ppp_phase;
-	int index;
 
-	g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+	g_return_if_fail (G_IS_DBUS_CONNECTION (gl.dbus_connection));
 
 	switch (arg) {
 	case PHASE_DEAD:
@@ -117,33 +117,49 @@ nm_phasechange (void *data, int arg)
 		break;
 	}
 
-	g_message ("nm-ppp-plugin: (%s): status %d / phase '%s'",
-	           __func__,
+	g_message ("nm-ppp-plugin: status %d / phase '%s'",
 	           ppp_status,
 	           ppp_phase);
 
 	if (ppp_status != NM_PPP_STATUS_UNKNOWN) {
-		g_dbus_proxy_call (proxy,
-		                   "SetState",
-		                   g_variant_new ("(u)", ppp_status),
-		                   G_DBUS_CALL_FLAGS_NONE, -1,
-		                   NULL,
-		                   NULL, NULL);
+		g_dbus_connection_call (gl.dbus_connection,
+		                        NM_DBUS_SERVICE,
+		                        gl.ipparam,
+		                        NM_DBUS_INTERFACE_PPP,
+		                        "SetState",
+		                        g_variant_new ("(u)", ppp_status),
+		                        G_VARIANT_TYPE ("()"),
+		                        G_DBUS_CALL_FLAGS_NONE,
+		                        -1,
+		                        NULL,
+		                        NULL,
+		                        NULL);
 	}
 
 	if (ppp_status == NM_PPP_STATUS_RUNNING) {
-		index = if_nametoindex (ifname);
+		gs_unref_variant GVariant *ret = NULL;
+		char new_name[IF_NAMESIZE];
+		int ifindex;
+
+		ifindex = if_nametoindex (ifname);
+
 		/* Make a sync call to ensure that when the call
 		 * terminates the interface already has its final
 		 * name. */
-		g_dbus_proxy_call_sync (proxy,
-		                        "SetIfindex",
-		                        g_variant_new ("(i)", index),
-		                        G_DBUS_CALL_FLAGS_NONE,
-		                        25000,
-		                        NULL, NULL);
+		ret = g_dbus_connection_call_sync (gl.dbus_connection,
+		                                   NM_DBUS_SERVICE,
+		                                   gl.ipparam,
+		                                   NM_DBUS_INTERFACE_PPP,
+		                                   "SetIfindex",
+		                                   g_variant_new ("(i)", ifindex),
+		                                   G_VARIANT_TYPE ("()"),
+		                                   G_DBUS_CALL_FLAGS_NONE,
+		                                   25000,
+		                                   NULL,
+		                                   NULL);
+
 		/* Update the name in pppd if NM changed it */
-		if (   if_indextoname (index, new_name)
+		if (   if_indextoname (ifindex, new_name)
 		    && !nm_streq0 (ifname, new_name)) {
 			g_message ("nm-ppp-plugin: interface name changed from '%s' to '%s'", ifname, new_name);
 			g_strlcpy (ifname, new_name, IF_NAMESIZE);
@@ -152,6 +168,16 @@ nm_phasechange (void *data, int arg)
 }
 
 static void
+nm_phasechange_hook (void *data, int arg)
+{
+	/* We send the nofication in exitnotify instead */
+	if (arg == PHASE_DEAD)
+		return;
+
+	nm_phasechange (arg);
+}
+
+static void
 nm_ip_up (void *data, int arg)
 {
 	ipcp_options opts = ipcp_gotoptions[0];
@@ -159,13 +185,13 @@ nm_ip_up (void *data, int arg)
 	GVariantBuilder builder;
 	guint32 pppd_made_up_address = htonl (0x0a404040 + ifunit);
 
-	g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+	g_return_if_fail (G_IS_DBUS_CONNECTION (gl.dbus_connection));
 
-	g_message ("nm-ppp-plugin: (%s): ip-up event", __func__);
+	g_message ("nm-ppp-plugin: ip-up event");
 
 	if (!opts.ouraddr) {
-		g_warning ("nm-ppp-plugin: (%s): didn't receive an internal IP from pppd!", __func__);
-		nm_phasechange (NULL, PHASE_DEAD);
+		g_warning ("nm-ppp-plugin: didn't receive an internal IP from pppd!");
+		nm_phasechange (PHASE_DEAD);
 		return;
 	}
 
@@ -235,14 +261,20 @@ nm_ip_up (void *data, int arg)
 		                                                  wins, len, sizeof (guint32)));
 	}
 
-	g_message ("nm-ppp-plugin: (%s): sending IPv4 config to NetworkManager...", __func__);
-
-	g_dbus_proxy_call (proxy,
-	                   "SetIp4Config",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   NULL,
-	                   NULL, NULL);
+	g_message ("nm-ppp-plugin: sending IPv4 config to NetworkManager...");
+
+	g_dbus_connection_call (gl.dbus_connection,
+	                        NM_DBUS_SERVICE,
+	                        gl.ipparam,
+	                        NM_DBUS_INTERFACE_PPP,
+	                        "SetIp4Config",
+	                        g_variant_new ("(a{sv})", &builder),
+	                        G_VARIANT_TYPE ("()"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1,
+	                        NULL,
+	                        NULL,
+	                        NULL);
 }
 
 static GVariant *
@@ -263,9 +295,9 @@ nm_ip6_up (void *data, int arg)
 	ipv6cp_options *go = &ipv6cp_gotoptions[0];
 	GVariantBuilder builder;
 
-	g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+	g_return_if_fail (G_IS_DBUS_CONNECTION (gl.dbus_connection));
 
-	g_message ("nm-ppp-plugin: (%s): ip6-up event", __func__);
+	g_message ("nm-ppp-plugin: ip6-up event");
 
 	g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
 	/* Keep sending the interface name to be backwards compatible
@@ -283,14 +315,20 @@ nm_ip6_up (void *data, int arg)
 
 	/* DNS is done via DHCPv6 or router advertisements */
 
-	g_message ("nm-ppp-plugin: (%s): sending IPv6 config to NetworkManager...", __func__);
-
-	g_dbus_proxy_call (proxy,
-	                   "SetIp6Config",
-	                   g_variant_new ("(a{sv})", &builder),
-	                   G_DBUS_CALL_FLAGS_NONE, -1,
-	                   NULL,
-	                   NULL, NULL);
+	g_message ("nm-ppp-plugin: sending IPv6 config to NetworkManager...");
+
+	g_dbus_connection_call (gl.dbus_connection,
+	                        NM_DBUS_SERVICE,
+	                        gl.ipparam,
+	                        NM_DBUS_INTERFACE_PPP,
+	                        "SetIp6Config",
+	                        g_variant_new ("(a{sv})", &builder),
+	                        G_VARIANT_TYPE ("()"),
+	                        G_DBUS_CALL_FLAGS_NONE,
+	                        -1,
+	                        NULL,
+	                        NULL,
+	                        NULL);
 }
 
 static int
@@ -308,10 +346,10 @@ get_pap_check (void)
 static int
 get_credentials (char *username, char *password)
 {
-	const char *my_username = NULL;
-	const char *my_password = NULL;
-	GVariant *ret;
-	GError *err = NULL;
+	gs_unref_variant GVariant *ret = NULL;
+	gs_free_error GError *error = NULL;
+	const char *my_username;
+	const char *my_password;
 
 	if (!password) {
 		/* pppd is checking pap support; return 1 for supported */
@@ -320,34 +358,33 @@ get_credentials (char *username, char *password)
 	}
 
 	g_return_val_if_fail (username, -1);
-	g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), -1);
-
-	g_message ("nm-ppp-plugin: (%s): passwd-hook, requesting credentials...", __func__);
-
-	ret = g_dbus_proxy_call_sync (proxy,
-	                              "NeedSecrets",
-	                              NULL,
-	                              G_DBUS_CALL_FLAGS_NONE, -1,
-	                              NULL, &err);
+	g_return_val_if_fail (G_IS_DBUS_CONNECTION (gl.dbus_connection), -1);
+
+	g_message ("nm-ppp-plugin: passwd-hook, requesting credentials...");
+
+	ret = g_dbus_connection_call_sync (gl.dbus_connection,
+	                                   NM_DBUS_SERVICE,
+	                                   gl.ipparam,
+	                                   NM_DBUS_INTERFACE_PPP,
+	                                   "NeedSecrets",
+	                                   NULL,
+	                                   G_VARIANT_TYPE ("(ss)"),
+	                                   G_DBUS_CALL_FLAGS_NONE,
+	                                   -1,
+	                                   NULL,
+	                                   &error);
 	if (!ret) {
-		g_warning ("nm-ppp-plugin: (%s): could not get secrets: %s",
-		           __func__,
-		           err->message);
-		g_error_free (err);
+		g_warning ("nm-ppp-plugin: could not get secrets: %s",
+		           error->message);
 		return -1;
 	}
 
-	g_message ("nm-ppp-plugin: (%s): got credentials from NetworkManager", __func__);
+	g_message ("nm-ppp-plugin: got credentials from NetworkManager");
 
 	g_variant_get (ret, "(&s&s)", &my_username, &my_password);
 
-	if (my_username)
-		g_strlcpy (username, my_username, MAXNAMELEN);
-
-	if (my_password)
-		g_strlcpy (password, my_password, MAXSECRETLEN);
-
-	g_variant_unref (ret);
+	g_strlcpy (username, my_username, MAXNAMELEN);
+	g_strlcpy (password, my_password, MAXSECRETLEN);
 
 	return 1;
 }
@@ -355,12 +392,17 @@ get_credentials (char *username, char *password)
 static void
 nm_exit_notify (void *data, int arg)
 {
-	g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+	g_return_if_fail (G_IS_DBUS_CONNECTION (gl.dbus_connection));
+
+	/* We wait until this point to notify dead phase to make sure that
+	 * the serial port has recovered already its original settings.
+	 */
+	nm_phasechange (PHASE_DEAD);
 
-	g_message ("nm-ppp-plugin: (%s): cleaning up", __func__);
+	g_message ("nm-ppp-plugin: cleaning up");
 
-	g_object_unref (proxy);
-	proxy = NULL;
+	g_clear_object (&gl.dbus_connection);
+	nm_clear_g_free (&gl.ipparam);
 }
 
 static void
@@ -387,46 +429,30 @@ add_ip6_notifier (void)
 int
 plugin_init (void)
 {
-	GDBusConnection *bus;
-	GError *err = NULL;
+	gs_free_error GError *err = NULL;
 
-	g_message ("nm-ppp-plugin: (%s): initializing", __func__);
+	g_message ("nm-ppp-plugin: initializing");
 
-	bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &err);
-	if (!bus) {
-		g_warning ("nm-pppd-plugin: (%s): couldn't connect to system bus: %s",
-		           __func__, err->message);
-		g_error_free (err);
-		return -1;
-	}
+	nm_assert (!gl.dbus_connection);
+	nm_assert (!gl.ipparam);
 
-	/* NM passes in the object path of the corresponding PPPManager
-	 * object as the 'ipparam' argument to pppd.
-	 */
-	proxy = g_dbus_proxy_new_sync (bus,
-	                               G_DBUS_PROXY_FLAGS_NONE,
-	                               NULL,
-	                               NM_DBUS_SERVICE,
-	                               ipparam,
-	                               NM_DBUS_INTERFACE_PPP,
-	                               NULL, &err);
-	g_object_unref (bus);
-
-	if (!proxy) {
-		g_warning ("nm-pppd-plugin: (%s): couldn't create D-Bus proxy: %s",
-		           __func__, err->message);
-		g_error_free (err);
+	gl.dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &err);
+	if (!gl.dbus_connection) {
+		g_warning ("nm-pppd-plugin: couldn't connect to system bus: %s",
+		           err->message);
 		return -1;
 	}
 
+	gl.ipparam = g_strdup (ipparam);
+
 	chap_passwd_hook = get_credentials;
 	chap_check_hook = get_chap_check;
 	pap_passwd_hook = get_credentials;
 	pap_check_hook = get_pap_check;
 
-	add_notifier (&phasechange, nm_phasechange, NULL);
+	add_notifier (&phasechange, nm_phasechange_hook, NULL);
 	add_notifier (&ip_up_notifier, nm_ip_up, NULL);
-	add_notifier (&exitnotify, nm_exit_notify, proxy);
+	add_notifier (&exitnotify, nm_exit_notify, NULL);
 	add_ip6_notifier ();
 
 	return 0;
diff --git a/src/ppp/nm-pppd-plugin.h b/src/ppp/nm-pppd-plugin.h
index 1a2ea09e..e0d691bf 100644
--- a/src/ppp/nm-pppd-plugin.h
+++ b/src/ppp/nm-pppd-plugin.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/settings/nm-agent-manager.c b/src/settings/nm-agent-manager.c
index 814dee85..d361c06a 100644
--- a/src/settings/nm-agent-manager.c
+++ b/src/settings/nm-agent-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -311,7 +310,6 @@ validate_identifier (const char *identifier, GError **error)
 
 static void
 agent_register_permissions_done (NMAuthChain *chain,
-                                 GError *error,
                                  GDBusMethodInvocation *context,
                                  gpointer user_data)
 {
@@ -319,47 +317,36 @@ agent_register_permissions_done (NMAuthChain *chain,
 	NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE (self);
 	NMSecretAgent *agent;
 	const char *sender;
-	GError *local = NULL;
 	NMAuthCallResult result;
 	CList *iter;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
 	priv->chains = g_slist_remove (priv->chains, chain);
 
-	if (error) {
-		local = g_error_new (NM_AGENT_MANAGER_ERROR,
-		                     NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
-		                     "Failed to request agent permissions: %s",
-		                     error->message);
-		g_dbus_method_invocation_take_error (context, local);
-	} else {
-		agent = nm_auth_chain_steal_data (chain, "agent");
-		g_assert (agent);
+	agent = nm_auth_chain_steal_data (chain, "agent");
+	nm_assert (agent);
 
-		result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
-		if (result == NM_AUTH_CALL_RESULT_YES)
-			nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, TRUE);
+	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED);
+	if (result == NM_AUTH_CALL_RESULT_YES)
+		nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, TRUE);
 
-		result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
-		if (result == NM_AUTH_CALL_RESULT_YES)
-			nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, TRUE);
+	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN);
+	if (result == NM_AUTH_CALL_RESULT_YES)
+		nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, TRUE);
 
-		priv->agent_version_id += 1;
-		sender = nm_secret_agent_get_dbus_owner (agent);
-		g_hash_table_insert (priv->agents, g_strdup (sender), agent);
-		_LOGI (agent, "agent registered");
-		g_dbus_method_invocation_return_value (context, NULL);
+	priv->agent_version_id += 1;
+	sender = nm_secret_agent_get_dbus_owner (agent);
+	g_hash_table_insert (priv->agents, g_strdup (sender), agent);
+	_LOGI (agent, "agent registered");
+	g_dbus_method_invocation_return_value (context, NULL);
 
-		/* Signal an agent was registered */
-		g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
+	/* Signal an agent was registered */
+	g_signal_emit (self, signals[AGENT_REGISTERED], 0, agent);
 
-		/* Add this agent to any in-progress secrets requests */
-		c_list_for_each (iter, &priv->requests)
-			request_add_agent (c_list_entry (iter, Request, lst_request), agent);
-	}
-
-	nm_auth_chain_destroy (chain);
+	/* Add this agent to any in-progress secrets requests */
+	c_list_for_each (iter, &priv->requests)
+		request_add_agent (c_list_entry (iter, Request, lst_request), agent);
 }
 
 static NMSecretAgent *
@@ -537,8 +524,7 @@ request_free (Request *req)
 	case REQUEST_TYPE_CON_DEL:
 		g_object_unref (req->con.connection);
 		g_free (req->con.path);
-		if (req->con.chain)
-			nm_auth_chain_destroy (req->con.chain);
+		nm_clear_pointer (&req->con.chain, nm_auth_chain_destroy);
 		if (req->request_type == REQUEST_TYPE_CON_GET) {
 			g_free (req->con.get.setting_name);
 			g_strfreev (req->con.get.hints);
@@ -815,11 +801,8 @@ request_remove_agent (Request *req, NMSecretAgent *agent)
 		case REQUEST_TYPE_CON_GET:
 		case REQUEST_TYPE_CON_SAVE:
 		case REQUEST_TYPE_CON_DEL:
-			if (req->con.chain) {
-				/* This cancels the pending authorization requests. */
-				nm_auth_chain_destroy (req->con.chain);
-				req->con.chain = NULL;
-			}
+			/* This cancels the pending authorization requests. */
+			nm_clear_pointer (&req->con.chain, nm_auth_chain_destroy);
 			break;
 		default:
 			g_assert_not_reached ();
@@ -1017,7 +1000,6 @@ _con_get_request_start_proceed (Request *req, gboolean include_system_secrets)
 
 static void
 _con_get_request_start_validated (NMAuthChain *chain,
-                                 GError *error,
                                  GDBusMethodInvocation *context,
                                  gpointer user_data)
 {
@@ -1031,30 +1013,20 @@ _con_get_request_start_validated (NMAuthChain *chain,
 
 	req->con.chain = NULL;
 
-	if (error) {
-		_LOGD (req->current, "agent "LOG_REQ_FMT" MODIFY check error: %s",
-		       LOG_REQ_ARG (req),
-		       error->message);
-		/* Try the next agent */
-		request_next_agent (req);
-	} else {
-		/* If the agent obtained the 'modify' permission, we send all system secrets
-		 * to it.  If it didn't, we still ask it for secrets, but we don't send
-		 * any system secrets.
-		 */
-		perm = nm_auth_chain_get_data (chain, "perm");
-		g_assert (perm);
-		if (nm_auth_chain_get_result (chain, perm) == NM_AUTH_CALL_RESULT_YES)
-			req->con.current_has_modify = TRUE;
-
-		_LOGD (req->current, "agent "LOG_REQ_FMT" MODIFY check result %s",
-		       LOG_REQ_ARG (req),
-		       req->con.current_has_modify ? "YES" : "NO");
+	/* If the agent obtained the 'modify' permission, we send all system secrets
+	 * to it.  If it didn't, we still ask it for secrets, but we don't send
+	 * any system secrets.
+	 */
+	perm = nm_auth_chain_get_data (chain, "perm");
+	g_assert (perm);
+	if (nm_auth_chain_get_result (chain, perm) == NM_AUTH_CALL_RESULT_YES)
+		req->con.current_has_modify = TRUE;
 
-		_con_get_request_start_proceed (req, req->con.current_has_modify);
-	}
+	_LOGD (req->current, "agent "LOG_REQ_FMT" MODIFY check result %s",
+	       LOG_REQ_ARG (req),
+	       req->con.current_has_modify ? "YES" : "NO");
 
-	nm_auth_chain_destroy (chain);
+	_con_get_request_start_proceed (req, req->con.current_has_modify);
 }
 
 static void
@@ -1100,7 +1072,7 @@ _con_get_request_start (Request *req)
 			perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
 		nm_auth_chain_set_data (req->con.chain, "perm", (gpointer) perm, NULL);
 
-		nm_auth_chain_add_call (req->con.chain, perm, TRUE);
+		nm_auth_chain_add_call_unsafe (req->con.chain, perm, TRUE);
 	} else {
 		_LOGD (NULL, "("LOG_REQ_FMT") requesting user-owned secrets from agent %s",
 		       LOG_REQ_ARG (req), agent_dbus_owner);
@@ -1478,7 +1450,6 @@ nm_agent_manager_all_agents_have_capability (NMAgentManager *manager,
 
 static void
 agent_permissions_changed_done (NMAuthChain *chain,
-                                GError *error,
                                 GDBusMethodInvocation *context,
                                 gpointer user_data)
 {
@@ -1492,21 +1463,15 @@ agent_permissions_changed_done (NMAuthChain *chain,
 	agent = nm_auth_chain_get_data (chain, "agent");
 	g_assert (agent);
 
-	if (error)
-		_LOGD (agent, "failed to request updated agent permissions");
-	else {
-		_LOGD (agent, "updated agent permissions");
-
-		if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) == NM_AUTH_CALL_RESULT_YES)
-			share_protected = TRUE;
-		if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN) == NM_AUTH_CALL_RESULT_YES)
-			share_open = TRUE;
-	}
+	_LOGD (agent, "updated agent permissions");
+
+	if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED) == NM_AUTH_CALL_RESULT_YES)
+		share_protected = TRUE;
+	if (nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN) == NM_AUTH_CALL_RESULT_YES)
+		share_open = TRUE;
 
 	nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, share_protected);
 	nm_secret_agent_add_permission (agent, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, share_open);
-
-	nm_auth_chain_destroy (chain);
 }
 
 static void
diff --git a/src/settings/nm-agent-manager.h b/src/settings/nm-agent-manager.h
index 8c27f26b..949ab6bc 100644
--- a/src/settings/nm-agent-manager.h
+++ b/src/settings/nm-agent-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/settings/nm-secret-agent.c b/src/settings/nm-secret-agent.c
index 836ab21d..2aa1476d 100644
--- a/src/settings/nm-secret-agent.c
+++ b/src/settings/nm-secret-agent.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -25,6 +24,7 @@
 #include <sys/types.h>
 #include <pwd.h>
 
+#include "nm-glib-aux/nm-dbus-aux.h"
 #include "nm-dbus-interface.h"
 #include "nm-dbus-manager.h"
 #include "nm-core-internal.h"
@@ -54,7 +54,10 @@ typedef struct {
 	NMDBusManager *bus_mgr;
 	GDBusConnection *connection;
 	CList requests;
-	gulong on_disconnected_id;
+	union {
+		gulong obj_signal;
+		guint dbus_signal;
+	} on_disconnected_id;
 	bool connection_is_private:1;
 } NMSecretAgentPrivate;
 
@@ -614,15 +617,12 @@ nm_secret_agent_delete_secrets (NMSecretAgent *self,
 static void
 _on_disconnected_cleanup (NMSecretAgentPrivate *priv)
 {
-	if (priv->on_disconnected_id) {
-		if (priv->connection_is_private) {
-			g_signal_handler_disconnect (priv->bus_mgr,
-			                             priv->on_disconnected_id);
-		} else {
-			g_dbus_connection_signal_unsubscribe (priv->connection,
-			                                      priv->on_disconnected_id);
-		}
-		priv->on_disconnected_id = 0;
+	if (priv->connection_is_private) {
+		nm_clear_g_signal_handler (priv->bus_mgr,
+		                           &priv->on_disconnected_id.obj_signal);
+	} else {
+		nm_clear_g_dbus_connection_signal (priv->connection,
+		                                   &priv->on_disconnected_id.dbus_signal);
 	}
 
 	g_clear_object (&priv->connection);
@@ -744,21 +744,16 @@ nm_secret_agent_new (GDBusMethodInvocation *context,
 	/* we cannot subscribe to notify::g-name-owner because that doesn't work
 	 * for unique names and it doesn't work for private connections. */
 	if (priv->connection_is_private) {
-		priv->on_disconnected_id = g_signal_connect (priv->bus_mgr,
-		                                             NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED,
-		                                             G_CALLBACK (_on_disconnected_private_connection),
-		                                             self);
+		priv->on_disconnected_id.obj_signal = g_signal_connect (priv->bus_mgr,
+		                                                        NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED,
+		                                                        G_CALLBACK (_on_disconnected_private_connection),
+		                                                        self);
 	} else {
-		priv->on_disconnected_id = g_dbus_connection_signal_subscribe (priv->connection,
-		                                                               "org.freedesktop.DBus",  /* name */
-		                                                               "org.freedesktop.DBus",  /* interface */
-		                                                               "NameOwnerChanged",      /* signal name */
-		                                                               "/org/freedesktop/DBus", /* path */
-		                                                               priv->dbus_owner,        /* arg0 */
-		                                                               G_DBUS_SIGNAL_FLAGS_NONE,
-		                                                               _on_disconnected_name_owner_changed,
-		                                                               self,
-		                                                               NULL);
+		priv->on_disconnected_id.dbus_signal = nm_dbus_connection_signal_subscribe_name_owner_changed (priv->connection,
+		                                                                                               priv->dbus_owner,
+		                                                                                               _on_disconnected_name_owner_changed,
+		                                                                                               self,
+		                                                                                               NULL);
 	}
 
 	return self;
diff --git a/src/settings/nm-secret-agent.h b/src/settings/nm-secret-agent.h
index 64b103aa..209a1009 100644
--- a/src/settings/nm-secret-agent.h
+++ b/src/settings/nm-secret-agent.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index 3fdaa598..25a27e68 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -25,6 +24,7 @@
 
 #include "c-list/src/c-list.h"
 
+#include "nm-glib-aux/nm-keyfile-aux.h"
 #include "nm-libnm-core-intern/nm-common-macros.h"
 #include "nm-config.h"
 #include "nm-config-data.h"
@@ -37,14 +37,18 @@
 #include "NetworkManagerUtils.h"
 #include "nm-core-internal.h"
 #include "nm-audit-manager.h"
-
-#define SETTINGS_TIMESTAMPS_FILE  NMSTATEDIR "/timestamps"
-#define SETTINGS_SEEN_BSSIDS_FILE NMSTATEDIR "/seen-bssids"
+#include "nm-settings.h"
+#include "settings/plugins/keyfile/nms-keyfile-storage.h"
 
 #define AUTOCONNECT_RETRIES_UNSET        -2
 #define AUTOCONNECT_RETRIES_FOREVER      -1
 #define AUTOCONNECT_RESET_RETRIES_TIMER 300
 
+#define _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES ((NMSettingsUpdate2Flags) (  NM_SETTINGS_UPDATE2_FLAG_TO_DISK \
+                                                                               | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY \
+                                                                               | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED \
+                                                                               | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))
+
 /*****************************************************************************/
 
 NMConnection **
@@ -70,13 +74,11 @@ nm_settings_connections_array_to_connections (NMSettingsConnection *const*connec
 
 NM_GOBJECT_PROPERTIES_DEFINE (NMSettingsConnection,
 	PROP_UNSAVED,
-	PROP_READY,
 	PROP_FLAGS,
 	PROP_FILENAME,
 );
 
 enum {
-	REMOVED,
 	UPDATED_INTERNAL,
 	FLAGS_CHANGED,
 	LAST_SIGNAL
@@ -86,18 +88,12 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct _NMSettingsConnectionPrivate {
 
-	NMAgentManager *agent_mgr;
-	NMSessionMonitor *session_monitor;
-	gulong session_changed_id;
-
-	NMSettingsConnectionIntFlags flags:5;
-
-	bool removed:1;
-	bool ready:1;
+	NMSettings *settings;
 
-	bool timestamp_set:1;
+	NMKeyFileDB *kf_db_timestamps;
+	NMKeyFileDB *kf_db_seen_bssids;
 
-	NMSettingsAutoconnectBlockedReason autoconnect_blocked_reason:4;
+	NMAgentManager *agent_mgr;
 
 	/* List of pending authentication requests */
 	CList auth_lst_head;
@@ -106,12 +102,18 @@ typedef struct _NMSettingsConnectionPrivate {
 
 	NMConnection *connection;
 
+	NMSettingsStorage *storage;
+
+	char *filename;
+
+	NMDevice *default_wired_device;
+
 	/* Caches secrets from on-disk connections; were they not cached any
 	 * call to nm_connection_clear_secrets() wipes them out and we'd have
 	 * to re-read them from disk which defeats the purpose of having the
 	 * connection in-memory at all.
 	 */
-	NMConnection *system_secrets;
+	GVariant *system_secrets;
 
 	/* Caches secrets from agents during the activation process; if new system
 	 * secrets are returned from an agent, they get written out to disk,
@@ -119,9 +121,7 @@ typedef struct _NMSettingsConnectionPrivate {
 	 * secrets, and would wipe out any agent-owned or not-saved secrets the
 	 * agent also returned.
 	 */
-	NMConnection *agent_secrets;
-
-	char *filename;
+	GVariant *agent_secrets;
 
 	GHashTable *seen_bssids; /* Up-to-date BSSIDs that's been seen for the connection */
 
@@ -130,10 +130,21 @@ typedef struct _NMSettingsConnectionPrivate {
 	guint64 last_secret_agent_version_id;
 
 	int autoconnect_retries;
+
 	gint32 autoconnect_retries_blocked_until;
 
+	bool timestamp_set:1;
+
+	NMSettingsAutoconnectBlockedReason autoconnect_blocked_reason:4;
+
+	NMSettingsConnectionIntFlags flags:5;
+
 } NMSettingsConnectionPrivate;
 
+struct _NMSettingsConnectionClass {
+	NMDBusObjectClass parent;
+};
+
 G_DEFINE_TYPE (NMSettingsConnection, nm_settings_connection, NM_TYPE_DBUS_OBJECT)
 
 #define NM_SETTINGS_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR (self, NMSettingsConnection, NM_IS_SETTINGS_CONNECTION)
@@ -152,7 +163,7 @@ G_DEFINE_TYPE (NMSettingsConnection, nm_settings_connection, NM_TYPE_DBUS_OBJECT
             const char *__uuid = (self) ? nm_settings_connection_get_uuid (self) : NULL; \
             \
             if (self) { \
-                g_snprintf (__prefix, sizeof (__prefix), "%s[%p%s%s]", _NMLOG_PREFIX_NAME, self, __uuid ? "," : "", __uuid ?: ""); \
+                g_snprintf (__prefix, sizeof (__prefix), "%s["NM_HASH_OBFUSCATE_PTR_FMT"%s%s]", _NMLOG_PREFIX_NAME, NM_HASH_OBFUSCATE_PTR (self), __uuid ? "," : "", __uuid ?: ""); \
                 __p_prefix = __prefix; \
             } \
             _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, __uuid, \
@@ -167,6 +178,92 @@ static const GDBusSignalInfo signal_info_updated;
 static const GDBusSignalInfo signal_info_removed;
 static const NMDBusInterfaceInfoExtended interface_info_settings_connection;
 
+static void update_system_secrets_cache (NMSettingsConnection *self, NMConnection *new);
+static void update_agent_secrets_cache (NMSettingsConnection *self, NMConnection *new);
+
+/*****************************************************************************/
+
+NMDevice *
+nm_settings_connection_default_wired_get_device (NMSettingsConnection *self)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	nm_assert (!priv->default_wired_device || NM_IS_DEVICE (priv->default_wired_device));
+
+	return priv->default_wired_device;
+}
+
+void
+nm_settings_connection_default_wired_set_device (NMSettingsConnection *self,
+                                                 NMDevice *device)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	nm_assert (!priv->default_wired_device || NM_IS_DEVICE (priv->default_wired_device));
+	nm_assert (!device || NM_IS_DEVICE (device));
+
+	nm_assert ((!!priv->default_wired_device) != (!!device));
+
+	priv->default_wired_device = device;
+}
+
+/*****************************************************************************/
+
+NMSettingsStorage *
+nm_settings_connection_get_storage (NMSettingsConnection *self)
+{
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NULL);
+
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->storage;
+}
+
+void
+_nm_settings_connection_set_storage (NMSettingsConnection *self,
+                                     NMSettingsStorage *storage)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	const char *filename;
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (storage));
+	nm_assert (   !priv->storage
+	           || nm_streq (nm_settings_storage_get_uuid (storage),
+	                        nm_settings_storage_get_uuid (priv->storage)));
+
+	nm_g_object_ref_set (&priv->storage, storage);
+
+	filename = nm_settings_storage_get_filename (priv->storage);
+
+	if (!nm_streq0 (priv->filename, filename)) {
+		g_free (priv->filename);
+		priv->filename = g_strdup (filename);
+		_notify (self, PROP_FILENAME);
+	}
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_connection_still_valid (NMSettingsConnection *self)
+{
+	gboolean valid;
+
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
+
+	valid = !c_list_is_empty (&self->_connections_lst);
+
+	nm_assert (valid == nm_settings_has_connection (NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->settings, self));
+
+	return valid;
+}
+
+/*****************************************************************************/
+
+static GHashTable *
+_seen_bssids_hash_new (void)
+{
+	return g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+}
+
 /*****************************************************************************/
 
 NMConnection *
@@ -177,6 +274,50 @@ nm_settings_connection_get_connection (NMSettingsConnection *self)
 	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->connection;
 }
 
+void
+_nm_settings_connection_set_connection (NMSettingsConnection *self,
+                                        NMConnection *new_connection,
+                                        NMConnection **out_connection_old,
+                                        NMSettingsConnectionUpdateReason update_reason)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection_old = NULL;
+
+	nm_assert (NM_IS_CONNECTION (new_connection));
+	nm_assert (NM_IS_SETTINGS_STORAGE (priv->storage));
+	nm_assert (nm_streq0 (nm_settings_storage_get_uuid (priv->storage), nm_connection_get_uuid (new_connection)));
+	nm_assert (!out_connection_old || !*out_connection_old);
+
+	if (   !priv->connection
+	    || !nm_connection_compare (priv->connection,
+	                               new_connection,
+	                               NM_SETTING_COMPARE_FLAG_EXACT)) {
+		connection_old = priv->connection;
+		priv->connection = g_object_ref (new_connection);
+		nmtst_connection_assert_unchanging (priv->connection);
+
+		/* note that we only return @connection_old if the new connection actually differs from
+		 * before.
+		 *
+		 * So, there are three cases:
+		 *
+		 *  - return %NULL when setting the connection the first time.
+		 *  - return %NULL if setting a profile with the same content that we already have.
+		 *  - return the previous pointer if the connection changed. */
+		NM_SET_OUT (out_connection_old, g_steal_pointer (&connection_old));
+	}
+
+	if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS))
+		update_system_secrets_cache (self, NULL);
+	else if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS))
+		update_system_secrets_cache (self, priv->connection);
+
+	if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS))
+		update_agent_secrets_cache (self, NULL);
+	else if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS))
+		update_agent_secrets_cache (self, priv->connection);
+}
+
 /*****************************************************************************/
 
 gboolean
@@ -206,34 +347,23 @@ nm_settings_connection_get_last_secret_agent_version_id (NMSettingsConnection *s
 
 /*****************************************************************************/
 
-static void
-set_visible (NMSettingsConnection *self, gboolean new_visible)
-{
-	nm_settings_connection_set_flags (self,
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,
-	                                  new_visible);
-}
-
-void
-nm_settings_connection_recheck_visibility (NMSettingsConnection *self)
+gboolean
+nm_settings_connection_check_visibility (NMSettingsConnection *self,
+                                         NMSessionMonitor *session_monitor)
 {
-	NMSettingsConnectionPrivate *priv;
 	NMSettingConnection *s_con;
 	guint32 num, i;
 
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
-	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	nm_assert (NM_IS_SESSION_MONITOR (session_monitor));
 
 	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (self));
 
 	/* Check every user in the ACL for a session */
 	num = nm_setting_connection_get_num_permissions (s_con);
-	if (num == 0) {
-		/* Visible to all */
-		set_visible (self, TRUE);
-		return;
-	}
+	if (num == 0)
+		return TRUE;
 
 	for (i = 0; i < num; i++) {
 		const char *user;
@@ -243,20 +373,13 @@ nm_settings_connection_recheck_visibility (NMSettingsConnection *self)
 			continue;
 		if (!nm_session_monitor_user_to_uid (user, &uid))
 			continue;
-		if (!nm_session_monitor_session_exists (priv->session_monitor, uid, FALSE))
+		if (!nm_session_monitor_session_exists (session_monitor, uid, FALSE))
 			continue;
 
-		set_visible (self, TRUE);
-		return;
+		return TRUE;
 	}
 
-	set_visible (self, FALSE);
-}
-
-static void
-session_changed_cb (NMSessionMonitor *self, NMSettingsConnection *sett_conn)
-{
-	nm_settings_connection_recheck_visibility (sett_conn);
+	return FALSE;
 }
 
 /*****************************************************************************/
@@ -302,7 +425,8 @@ nm_settings_connection_check_permission (NMSettingsConnection *self,
 		if (nm_setting_connection_get_permission (s_con, i, NULL, &puser, NULL)) {
 			NMSecretAgent *agent = nm_agent_manager_get_agent_by_user (priv->agent_mgr, puser);
 
-			if (agent && nm_secret_agent_has_permission (agent, permission))
+			if (   agent
+			    && nm_secret_agent_has_permission (agent, permission))
 				return TRUE;
 		}
 	}
@@ -312,421 +436,196 @@ nm_settings_connection_check_permission (NMSettingsConnection *self,
 
 /*****************************************************************************/
 
-static gboolean
-secrets_filter_cb (NMSetting *setting,
-                   const char *secret,
-                   NMSettingSecretFlags flags,
-                   gpointer user_data)
+static void
+update_system_secrets_cache (NMSettingsConnection *self, NMConnection *new)
 {
-	NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data);
-
-	/* Returns TRUE to remove the secret */
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection_cloned = NULL;
+	gs_unref_variant GVariant *old_secrets = NULL;
 
-	/* Can't use bitops with SECRET_FLAG_NONE so handle that specifically */
-	if (   (flags == NM_SETTING_SECRET_FLAG_NONE)
-	    && (filter_flags == NM_SETTING_SECRET_FLAG_NONE))
-		return FALSE;
+	old_secrets = g_steal_pointer (&priv->system_secrets);
 
-	/* Otherwise if the secret has at least one of the desired flags keep it */
-	return (flags & filter_flags) ? FALSE : TRUE;
-}
+	if (!new)
+		goto out;
 
-static void
-update_system_secrets_cache (NMSettingsConnection *self)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	/* FIXME: improve NMConnection API so we can avoid the overhead of cloning the connection,
+	 *   in particular if there are no secrets to begin with. */
 
-	if (priv->system_secrets)
-		g_object_unref (priv->system_secrets);
-	priv->system_secrets = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
+	connection_cloned = nm_simple_connection_new_clone (new);
 
 	/* Clear out non-system-owned and not-saved secrets */
-	nm_connection_clear_secrets_with_flags (priv->system_secrets,
-	                                        secrets_filter_cb,
-	                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_NONE));
+	_nm_connection_clear_secrets_by_secret_flags (connection_cloned,
+	                                              NM_SETTING_SECRET_FLAG_NONE);
+
+	priv->system_secrets = nm_g_variant_ref_sink (nm_connection_to_dbus (connection_cloned, NM_CONNECTION_SERIALIZE_ONLY_SECRETS));
+
+out:
+	if (_LOGT_ENABLED ()) {
+		if ((!!old_secrets) != (!!priv->system_secrets)) {
+			_LOGT ("update system secrets: secrets %s",
+			       old_secrets ? "cleared" : "set");
+		} else if (   priv->system_secrets
+		           && !g_variant_equal (old_secrets, priv->system_secrets))
+			_LOGT ("update system secrets: secrets updated");
+	}
 }
 
 static void
 update_agent_secrets_cache (NMSettingsConnection *self, NMConnection *new)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	NMSettingSecretFlags filter_flags = NM_SETTING_SECRET_FLAG_NOT_SAVED | NM_SETTING_SECRET_FLAG_AGENT_OWNED;
+	gs_unref_object NMConnection *connection_cloned = NULL;
+	gs_unref_variant GVariant *old_secrets = NULL;
 
-	if (priv->agent_secrets)
-		g_object_unref (priv->agent_secrets);
-	priv->agent_secrets = nm_simple_connection_new_clone (   new
-	                                                      ?: nm_settings_connection_get_connection (self));
+	old_secrets = g_steal_pointer (&priv->agent_secrets);
 
-	/* Clear out non-system-owned secrets */
-	nm_connection_clear_secrets_with_flags (priv->agent_secrets,
-	                                        secrets_filter_cb,
-	                                        GUINT_TO_POINTER (filter_flags));
-}
+	if (!new)
+		goto out;
 
-static void
-secrets_cleared_cb (NMConnection *connection, NMSettingsConnection *self)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	/* FIXME: improve NMConnection API so we can avoid the overhead of cloning the connection,
+	 *   in particular if there are no secrets to begin with. */
 
-	/* Clear agent secrets when connection's secrets are cleared since agent
-	 * secrets are transient.
-	 */
-	if (priv->agent_secrets)
-		g_object_unref (priv->agent_secrets);
-	priv->agent_secrets = NULL;
-}
+	connection_cloned = nm_simple_connection_new_clone (new);
 
-static void
-set_persist_mode (NMSettingsConnection *self, NMSettingsConnectionPersistMode persist_mode)
-{
-	NMSettingsConnectionIntFlags flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
-	const NMSettingsConnectionIntFlags ALL =   NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED
-	                                         | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
-	                                         | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+	/* Clear out non-system-owned secrets */
+	_nm_connection_clear_secrets_by_secret_flags (connection_cloned,
+	                                                NM_SETTING_SECRET_FLAG_NOT_SAVED
+	                                              | NM_SETTING_SECRET_FLAG_AGENT_OWNED);
 
-	switch (persist_mode) {
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK:
-		flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
-		break;
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY:
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED:
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY:
-		flags = NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
-		break;
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_DETACHED:
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_ONLY:
-		flags = NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED |
-		        NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
-		break;
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED:
-		/* only set the connection as unsaved, but preserve the nm-generated
-		 * and volatile flag. */
-		nm_settings_connection_set_flags (self,
-		                                  NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED,
-		                                  TRUE);
-		return;
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP:
-	case NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED:
-		/* Nothing to do */
-		return;
-	}
+	priv->agent_secrets = nm_g_variant_ref_sink (nm_connection_to_dbus (connection_cloned, NM_CONNECTION_SERIALIZE_ONLY_SECRETS));
 
-	nm_settings_connection_set_flags_full (self, ALL, flags);
+out:
+	if (_LOGT_ENABLED ()) {
+		if ((!!old_secrets) != (!!priv->agent_secrets)) {
+			_LOGT ("update agent secrets: secrets %s",
+			       old_secrets ? "cleared" : "set");
+		} else if (   priv->agent_secrets
+		           && !g_variant_equal (old_secrets, priv->agent_secrets))
+			_LOGT ("update agent secrets: secrets updated");
+	}
 }
 
-static void
-_emit_updated (NMSettingsConnection *self, gboolean by_user)
+void
+nm_settings_connection_clear_secrets (NMSettingsConnection *self,
+                                      gboolean clear_cached_system_secrets,
+                                      gboolean persist)
 {
-	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
-	                            &interface_info_settings_connection,
-	                            &signal_info_updated,
-	                            "()");
-	g_signal_emit (self, signals[UPDATED_INTERNAL], 0, by_user);
-}
+	gs_unref_object NMConnection *connection_cloned = NULL;
 
-static void
-connection_changed_cb (NMConnection *connection, NMSettingsConnection *self)
-{
-	set_persist_mode (self, NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED);
-	_emit_updated (self, FALSE);
-}
+	if (!nm_settings_connection_still_valid (self))
+		return;
 
-static gboolean
-_delete (NMSettingsConnection *self, GError **error)
-{
-	NMSettingsConnectionClass *klass;
-	GError *local = NULL;
-	const char *filename;
+	/* FIXME: add API to NMConnection so that we can clone a profile without secrets. */
 
-	nm_assert (NM_IS_SETTINGS_CONNECTION (self));
+	connection_cloned = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
 
-	klass = NM_SETTINGS_CONNECTION_GET_CLASS (self);
-	if (!klass->delete) {
-		g_set_error (&local,
-		             NM_SETTINGS_ERROR,
-		             NM_SETTINGS_ERROR_FAILED,
-		             "delete not supported");
-		goto fail;
-	}
-	if (!klass->delete (self,
-	                    &local))
-		goto fail;
-
-	filename = nm_settings_connection_get_filename (self);
-	if (filename) {
-		_LOGD ("delete: success deleting connection (\"%s\")", filename);
-		nm_settings_connection_set_filename (self, NULL);
-	} else
-		_LOGT ("delete: success deleting connection (no-file)");
-	return TRUE;
-fail:
-	_LOGD ("delete: failure deleting connection: %s", local->message);
-	g_propagate_error (error, local);
-	return FALSE;
+	nm_connection_clear_secrets (connection_cloned);
+
+	if (!nm_settings_connection_update (self,
+	                                    connection_cloned,
+	                                       persist
+	                                    ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP
+	                                    : NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                      NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE
+	                                    | (clear_cached_system_secrets ? NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE)
+	                                    | NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS,
+	                                    "clear-secrets",
+	                                    NULL))
+		nm_assert_not_reached ();
 }
 
 static gboolean
-_update_prepare (NMSettingsConnection *self,
-                 NMConnection *new_connection,
+_secrets_update (NMConnection *connection,
+                 const char *setting_name,
+                 GVariant *secrets,
+                 NMConnection **out_new_connection,
                  GError **error)
 {
-	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
-	g_return_val_if_fail (NM_IS_CONNECTION (new_connection), FALSE);
+	gs_unref_variant GVariant *secrets_setting = NULL;
 
-	if (!nm_connection_normalize (new_connection, NULL, NULL, error))
-		return FALSE;
+	nm_assert (NM_IS_CONNECTION (connection));
 
-	if (   nm_dbus_object_get_path (NM_DBUS_OBJECT (self))
-	    && g_strcmp0 (nm_settings_connection_get_uuid (self), nm_connection_get_uuid (new_connection)) != 0) {
-		/* Updating the UUID is not allowed once the path is exported. */
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "connection %s cannot change the UUID from %s to %s", nm_settings_connection_get_id (self),
-		             nm_settings_connection_get_uuid (self), nm_connection_get_uuid (new_connection));
+	if (   setting_name
+	    && !nm_connection_get_setting_by_name (connection, setting_name)) {
+		g_set_error_literal (error,
+		                     NM_CONNECTION_ERROR,
+		                     NM_CONNECTION_ERROR_SETTING_NOT_FOUND,
+		                     setting_name);
 		return FALSE;
 	}
 
-	return TRUE;
-}
-
-gboolean
-nm_settings_connection_update (NMSettingsConnection *self,
-                               NMConnection *new_connection,
-                               NMSettingsConnectionPersistMode persist_mode,
-                               NMSettingsConnectionCommitReason commit_reason,
-                               const char *log_diff_name,
-                               GError **error)
-{
-	NMSettingsConnectionPrivate *priv;
-	NMSettingsConnectionClass *klass = NULL;
-	gs_unref_object NMConnection *reread_connection = NULL;
-	NMConnection *replace_connection;
-	gboolean replaced = FALSE;
-	gs_free char *logmsg_change = NULL;
-	GError *local = NULL;
-	gs_unref_object NMConnection *simple = NULL;
-	gs_unref_variant GVariant *con_agent_secrets = NULL;
-	gs_unref_variant GVariant *new_agent_secrets = NULL;
-
-	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
-
-	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-
-	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK) {
-		klass = NM_SETTINGS_CONNECTION_GET_CLASS (self);
-		if (!klass->commit_changes) {
-			g_set_error (&local,
-			             NM_SETTINGS_ERROR,
-			             NM_SETTINGS_ERROR_FAILED,
-			             "writing settings not supported");
-			goto out;
-		}
-	}
-
-	if (   new_connection
-	    && !_update_prepare (self,
-	                         new_connection,
-	                         &local))
-		goto out;
-
-	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK) {
-		if (!klass->commit_changes (self,
-		                            new_connection ?: nm_settings_connection_get_connection (self),
-		                            commit_reason,
-		                            &reread_connection,
-		                            &logmsg_change,
-		                            &local))
-			goto out;
-
-		if (   reread_connection
-		    && !_update_prepare (self,
-		                         reread_connection,
-		                         &local))
-			goto out;
-	}
-
-	replace_connection = reread_connection ?: new_connection;
-
-	/* Save agent-owned secrets from the new connection for later use */
-	if (new_connection) {
-		simple = nm_simple_connection_new_clone (new_connection);
-		nm_connection_clear_secrets_with_flags (simple,
-		                                        secrets_filter_cb,
-		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
-		new_agent_secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-		g_clear_object (&simple);
-	}
-
-	/* Disconnect the changed signal to ensure we don't set Unsaved when
-	 * it's not required.
-	 */
-	g_signal_handlers_block_by_func (priv->connection, G_CALLBACK (connection_changed_cb), self);
-
-	/* Do nothing if there's nothing to update */
-	if (   replace_connection
-	    && !nm_connection_compare (nm_settings_connection_get_connection (self),
-	                               replace_connection,
-	                               NM_SETTING_COMPARE_FLAG_EXACT)) {
-
-		if (log_diff_name) {
-			nm_utils_log_connection_diff (replace_connection, nm_settings_connection_get_connection (self), LOGL_DEBUG, LOGD_CORE, log_diff_name, "++ ",
-			                              nm_dbus_object_get_path (NM_DBUS_OBJECT (self)));
-		}
-
-		/* Make a copy of agent-owned secrets because they won't be present in
-		 * the connection returned by plugins, as plugins return only what was
-		 * reread from the file. */
-		simple = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
-		nm_connection_clear_secrets_with_flags (simple,
-		                                        secrets_filter_cb,
-		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
-		con_agent_secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-
-		nm_connection_replace_settings_from_connection (nm_settings_connection_get_connection (self), replace_connection);
-
-		replaced = TRUE;
-	}
+	if (!secrets)
+		return TRUE;
 
-	nm_settings_connection_set_flags (self,
-	                                  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
-	                                  FALSE);
+	nm_assert (   g_variant_is_of_type (secrets, NM_VARIANT_TYPE_SETTING)
+	           || g_variant_is_of_type (secrets, NM_VARIANT_TYPE_CONNECTION));
 
-	if (replaced) {
-		/* Cache the just-updated system secrets in case something calls
-		 * nm_connection_clear_secrets() and clears them.
-		 */
-		update_system_secrets_cache (self);
-
-		/* Add agent and always-ask secrets back; they won't necessarily be
-		 * in the replacement connection data if it was eg reread from disk.
-		 */
-		if (priv->agent_secrets) {
-			GVariant *dict;
+	if (g_variant_n_children (secrets) == 0)
+		return TRUE;
 
-			dict = nm_connection_to_dbus (priv->agent_secrets, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-			if (dict) {
-				(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self), NULL, dict, NULL);
-				g_variant_unref (dict);
-			}
+	if (   setting_name
+	    && g_variant_is_of_type (secrets, NM_VARIANT_TYPE_CONNECTION)) {
+		secrets_setting = g_variant_lookup_value (secrets, setting_name, NM_VARIANT_TYPE_SETTING);
+		if (!secrets_setting) {
+			/* The connection dictionary didn't contain any secrets for
+			 * @setting_name; just return success.
+			 */
+			return TRUE;
 		}
-		if (con_agent_secrets)
-			(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self), NULL, con_agent_secrets, NULL);
+		secrets = secrets_setting;
 	}
 
-	/* Apply agent-owned secrets from the new connection so that
-	 * they can be sent to agents */
-	if (new_agent_secrets) {
-		(void) nm_connection_update_secrets (nm_settings_connection_get_connection (self),
-		                                     NULL,
-		                                     new_agent_secrets,
-		                                     NULL);
+	/* if @out_new_connection is provided, we don't modify @connection but clone
+	 * and return it. Otherwise, we update @connection inplace. */
+	if (out_new_connection) {
+		nm_assert (!*out_new_connection);
+		connection = nm_simple_connection_new_clone (connection);
+		*out_new_connection = connection;
 	}
 
-	nm_settings_connection_recheck_visibility (self);
-
-	if (   replaced
-	    && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP)
-		set_persist_mode (self, NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED);
-	else
-		set_persist_mode (self, persist_mode);
-
-	if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
-	                             NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_ONLY))
-		_delete (self, NULL);
-	else if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
-	                                  NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_DETACHED))
-		nm_settings_connection_set_filename (self, NULL);
-
-	g_signal_handlers_unblock_by_func (priv->connection, G_CALLBACK (connection_changed_cb), self);
-
-	_emit_updated (self, TRUE);
-
-out:
-	if (local) {
-		_LOGI ("write: failure to update connection: %s", local->message);
-		g_propagate_error (error, local);
+	if (!nm_connection_update_secrets (connection,
+	                                   setting_name,
+	                                   secrets,
+	                                   error))
 		return FALSE;
-	}
 
-	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK) {
-		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 committed (%s)", logmsg_change);
-	}
 	return TRUE;
 }
 
-static void
-remove_entry_from_db (NMSettingsConnection *self, const char* db_name)
-{
-	GKeyFile *key_file;
-	const char *db_file;
-
-	if (strcmp (db_name, "timestamps") == 0)
-		db_file = SETTINGS_TIMESTAMPS_FILE;
-	else if (strcmp (db_name, "seen-bssids") == 0)
-		db_file = SETTINGS_SEEN_BSSIDS_FILE;
-	else
-		return;
-
-	key_file = g_key_file_new ();
-	if (g_key_file_load_from_file (key_file, db_file, G_KEY_FILE_KEEP_COMMENTS, NULL)) {
-		const char *connection_uuid;
-		char *data;
-		gsize len;
-		GError *error = NULL;
-
-		connection_uuid = nm_settings_connection_get_uuid (self);
-
-		g_key_file_remove_key (key_file, db_name, connection_uuid, NULL);
-		data = g_key_file_to_data (key_file, &len, &error);
-		if (data) {
-			g_file_set_contents (db_file, data, len, &error);
-			g_free (data);
-		}
-		if (error) {
-			_LOGW ("error writing %s file '%s': %s", db_name, db_file, error->message);
-			g_error_free (error);
-		}
-	}
-	g_key_file_free (key_file);
-}
-
 gboolean
-nm_settings_connection_delete (NMSettingsConnection *self,
+nm_settings_connection_update (NMSettingsConnection *self,
+                               NMConnection *new_connection,
+                               NMSettingsConnectionPersistMode persist_mode,
+                               NMSettingsConnectionIntFlags sett_flags,
+                               NMSettingsConnectionIntFlags sett_mask,
+                               NMSettingsConnectionUpdateReason update_reason,
+                               const char *log_context_name,
                                GError **error)
 {
-	gs_unref_object NMSettingsConnection *self_keep_alive = NULL;
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	NMConnection *for_agents;
-
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
-	self_keep_alive = g_object_ref (self);
-
-	if (!_delete (self, error))
-		return FALSE;
-
-	set_visible (self, FALSE);
-
-	/* Tell agents to remove secrets for this connection */
-	for_agents = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
-	nm_connection_clear_secrets (for_agents);
-	nm_agent_manager_delete_secrets (priv->agent_mgr,
-	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
-	                                 for_agents);
-	g_object_unref (for_agents);
-
-	/* Remove timestamp from timestamps database file */
-	remove_entry_from_db (self, "timestamps");
+	return nm_settings_update_connection (NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->settings,
+	                                      self,
+	                                      new_connection,
+	                                      persist_mode,
+	                                      sett_flags,
+	                                      sett_mask,
+	                                      update_reason,
+	                                      log_context_name,
+	                                      error);
+}
 
-	/* Remove connection from seen-bssids database file */
-	remove_entry_from_db (self, "seen-bssids");
+void
+nm_settings_connection_delete (NMSettingsConnection *self,
+                               gboolean allow_add_to_no_auto_default)
+{
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
 
-	nm_settings_connection_signal_remove (self);
-	return TRUE;
+	nm_settings_delete_connection (NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->settings,
+	                               self,
+	                               allow_add_to_no_auto_default);
 }
 
 /*****************************************************************************/
@@ -788,13 +687,6 @@ _get_secrets_info_free (NMSettingsConnectionCallId *call_id)
 	g_slice_free (NMSettingsConnectionCallId, call_id);
 }
 
-static gboolean
-supports_secrets (NMSettingsConnection *self, const char *setting_name)
-{
-	/* All secrets supported */
-	return TRUE;
-}
-
 typedef struct {
 	NMSettingSecretFlags required;
 	NMSettingSecretFlags forbidden;
@@ -918,25 +810,37 @@ nm_settings_connection_new_secrets (NMSettingsConnection *self,
                                     GVariant *secrets,
                                     GError **error)
 {
-	if (!nm_settings_connection_has_unmodified_applied_connection (self, applied_connection,
-	                                                              NM_SETTING_COMPARE_FLAG_NONE)) {
+	gs_unref_object NMConnection *new_connection = NULL;
+	NMConnection *connection;
+
+	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_settings_connection_get_connection (self), setting_name, secrets, error))
-		return FALSE;
+	connection = nm_settings_connection_get_connection (self);
 
-	update_system_secrets_cache (self);
-	update_agent_secrets_cache (self, NULL);
+	if (!_secrets_update (connection,
+	                      setting_name,
+	                      secrets,
+	                      &new_connection,
+	                      error))
+		return FALSE;
 
-	nm_settings_connection_update (self,
-	                               NULL,
-	                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-	                               NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                               "new-secrets",
-	                               NULL);
+	if (!nm_settings_connection_update (self,
+	                                    new_connection ?: connection,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                      NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE
+	                                    | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+	                                    | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS,
+	                                    "new-secrets",
+	                                    NULL))
+		nm_assert_not_reached ();
 	return TRUE;
 }
 
@@ -957,9 +861,11 @@ get_secrets_done_cb (NMAgentManager *manager,
 	NMSettingsConnectionPrivate *priv;
 	NMConnection *applied_connection;
 	gs_free_error GError *local = NULL;
-	GVariant *dict = NULL;
+	gs_unref_variant GVariant *system_secrets = NULL;
+	gs_unref_object NMConnection *new_connection = NULL;
 	gboolean agent_had_system = FALSE;
 	ForEachSecretFlags cmp_flags = { NM_SETTING_SECRET_FLAG_NONE, NM_SETTING_SECRET_FLAG_NONE };
+	gs_unref_variant GVariant *filtered_secrets = NULL;
 
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
@@ -1020,62 +926,69 @@ get_secrets_done_cb (NMAgentManager *manager,
 	       setting_name,
 	       call_id);
 
-	if (priv->system_secrets)
-		dict = nm_connection_to_dbus (priv->system_secrets, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
+	system_secrets = nm_g_variant_ref (priv->system_secrets);
 
-	/* Update the connection with our existing secrets from backing storage */
-	nm_connection_clear_secrets (nm_settings_connection_get_connection (self));
-	if (!dict || nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, dict, &local)) {
-		gs_unref_variant GVariant *filtered_secrets = NULL;
-
-		/* Update the connection with the agent's secrets; by this point if any
-		 * system-owned secrets exist in 'secrets' the agent that provided them
-		 * will have been authenticated, so those secrets can replace the existing
-		 * system secrets.
-		 */
-		filtered_secrets = validate_secret_flags (nm_settings_connection_get_connection (self), secrets, &cmp_flags);
-		if (nm_connection_update_secrets (nm_settings_connection_get_connection (self), setting_name, filtered_secrets, &local)) {
-			/* Now that all secrets are updated, copy and cache new secrets,
-			 * then save them to backing storage.
-			 */
-			update_system_secrets_cache (self);
-			update_agent_secrets_cache (self, NULL);
+	new_connection = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
 
-			/* Only save secrets to backing storage if the agent returned any
-			 * new system secrets.  If it didn't, then the secrets are agent-
-			 * owned and there's no point to writing out the connection when
-			 * nothing has changed, since agent-owned secrets don't get saved here.
-			 */
-			if (agent_had_system) {
-				_LOGD ("(%s:%p) saving new secrets to backing storage",
-				       setting_name,
-				       call_id);
-
-				nm_settings_connection_update (self,
-				                               NULL,
-				                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-				                               NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-				                               "get-new-secrets",
-				                               NULL);
-			} else {
-				_LOGD ("(%s:%p) new agent secrets processed",
-				       setting_name,
-				       call_id);
-			}
+	nm_connection_clear_secrets (new_connection);
 
-		} else {
-			_LOGD ("(%s:%p) failed to update with agent secrets: %s",
-			       setting_name,
-			       call_id,
-			       local->message);
-		}
-	} else {
+	if (!_secrets_update (new_connection,
+	                      setting_name,
+	                      system_secrets,
+	                      NULL,
+	                      &local)) {
 		_LOGD ("(%s:%p) failed to update with existing secrets: %s",
 		       setting_name,
 		       call_id,
 		       local->message);
 	}
 
+	/* Update the connection with the agent's secrets; by this point if any
+	 * system-owned secrets exist in 'secrets' the agent that provided them
+	 * will have been authenticated, so those secrets can replace the existing
+	 * system secrets.
+	 */
+	filtered_secrets = validate_secret_flags (new_connection, secrets, &cmp_flags);
+
+	if (!_secrets_update (new_connection,
+	                      setting_name,
+	                      filtered_secrets,
+	                      NULL,
+	                      &local)) {
+		_LOGD ("(%s:%p) failed to update with agent secrets: %s",
+		       setting_name,
+		       call_id,
+		       local->message);
+	}
+
+	/* Only save secrets to backing storage if the agent returned any
+	 * new system secrets.  If it didn't, then the secrets are agent-
+	 * owned and there's no point to writing out the connection when
+	 * nothing has changed, since agent-owned secrets don't get saved here.
+	 */
+	if (agent_had_system) {
+		_LOGD ("(%s:%p) saving new secrets to backing storage",
+		       setting_name,
+		       call_id);
+	} else {
+		_LOGD ("(%s:%p) new agent secrets processed",
+		       setting_name,
+		       call_id);
+	}
+	if (!nm_settings_connection_update (self,
+	                                    new_connection,
+	                                      agent_had_system
+	                                    ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP
+	                                    : NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                      NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE
+	                                    | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+	                                    | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS,
+	                                    "get-new-secrets",
+	                                    NULL))
+		nm_assert_not_reached ();
+
 	applied_connection = call_id->applied_connection;
 	if (applied_connection) {
 		get_cmp_flags (self,
@@ -1091,18 +1004,17 @@ get_secrets_done_cb (NMAgentManager *manager,
 
 		nm_connection_clear_secrets (applied_connection);
 
-		if (!dict || nm_connection_update_secrets (applied_connection, setting_name, dict, NULL)) {
-			gs_unref_variant GVariant *filtered_secrets = NULL;
+		if (   !system_secrets
+		    || nm_connection_update_secrets (applied_connection, setting_name, system_secrets, NULL)) {
+			gs_unref_variant GVariant *filtered_secrets2 = NULL;
 
-			filtered_secrets = validate_secret_flags (applied_connection, secrets, &cmp_flags);
-			nm_connection_update_secrets (applied_connection, setting_name, filtered_secrets, NULL);
+			filtered_secrets2 = validate_secret_flags (applied_connection, secrets, &cmp_flags);
+			nm_connection_update_secrets (applied_connection, setting_name, filtered_secrets2, NULL);
 		}
 	}
 
 	_get_secrets_info_callback (call_id, agent_username, setting_name, local);
 	g_clear_error (&local);
-	if (dict)
-		g_variant_unref (dict);
 
 out:
 	_get_secrets_info_free (call_id);
@@ -1163,7 +1075,6 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
                                     gpointer callback_data)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	GVariant *existing_secrets = NULL;
 	NMAgentManagerCallId call_id_a;
 	gs_free char *joined_hints = NULL;
 	NMSettingsConnectionCallId *call_id;
@@ -1200,14 +1111,6 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 		goto schedule_dummy;
 	}
 
-	/* Use priv->system_secrets to work around the fact that nm_connection_clear_secrets()
-	 * will clear secrets on this object's settings.
-	 */
-	if (priv->system_secrets)
-		existing_secrets = nm_connection_to_dbus (priv->system_secrets, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-	if (existing_secrets)
-		g_variant_ref_sink (existing_secrets);
-
 	/* we remember the current version-id of the secret-agents. The version-id is strictly increasing,
 	 * as new agents register the number. We know hence, that this request was made against a certain
 	 * set of secret-agents.
@@ -1215,19 +1118,20 @@ nm_settings_connection_get_secrets (NMSettingsConnection *self,
 	 * Then we know that the this request probably did not yet include the latest secret-agent. */
 	priv->last_secret_agent_version_id = nm_agent_manager_get_agent_version_id (priv->agent_mgr);
 
+	/* Use priv->system_secrets to work around the fact that nm_connection_clear_secrets()
+	 * will clear secrets on this object's settings.
+	 */
 	call_id_a = nm_agent_manager_get_secrets (priv->agent_mgr,
 	                                          nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
 	                                          nm_settings_connection_get_connection (self),
 	                                          subject,
-	                                          existing_secrets,
+	                                          priv->system_secrets,
 	                                          setting_name,
 	                                          flags,
 	                                          hints,
 	                                          get_secrets_done_cb,
 	                                          call_id);
-	g_assert (call_id_a);
-	if (existing_secrets)
-		g_variant_unref (existing_secrets);
+	nm_assert (call_id_a);
 
 	_LOGD ("(%s:%p) secrets requested flags 0x%X hints '%s'",
 	       setting_name,
@@ -1420,37 +1324,6 @@ auth_start (NMSettingsConnection *self,
 
 /**** DBus method handlers ************************************/
 
-static gboolean
-check_writable (NMConnection *self, GError **error)
-{
-	NMSettingConnection *s_con;
-
-	g_return_val_if_fail (NM_IS_CONNECTION (self), FALSE);
-
-	s_con = nm_connection_get_setting_connection (self);
-	if (!s_con) {
-		g_set_error_literal (error,
-		                     NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "Connection did not have required 'connection' setting");
-		return FALSE;
-	}
-
-	/* If the connection is read-only, that has to be changed at the source of
-	 * the problem (ex a system settings plugin that can't write connections out)
-	 * instead of over D-Bus.
-	 */
-	if (nm_setting_connection_get_read_only (s_con)) {
-		g_set_error_literal (error,
-		                     NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_READ_ONLY_CONNECTION,
-		                     "Connection is read-only");
-		return FALSE;
-	}
-
-	return TRUE;
-}
-
 static void
 get_settings_auth_cb (NMSettingsConnection *self,
                       GDBusMethodInvocation *context,
@@ -1458,46 +1331,41 @@ get_settings_auth_cb (NMSettingsConnection *self,
                       GError *error,
                       gpointer data)
 {
-	if (error)
+	gs_free const char **seen_bssids = NULL;
+	NMConnectionSerializationOptions options = {
+	};
+	GVariant *settings;
+
+	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
-	else {
-		gs_unref_object NMConnection *dupl_con = NULL;
-		GVariant *settings;
-		NMSettingConnection *s_con;
-		NMSettingWireless *s_wifi;
-		guint64 timestamp = 0;
-		gs_free char **bssids = NULL;
-
-		dupl_con = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
-
-		/* Timestamp is not updated in connection's 'timestamp' property,
-		 * because it would force updating the connection and in turn
-		 * writing to /etc periodically, which we want to avoid. Rather real
-		 * timestamps are kept track of in a private variable. So, substitute
-		 * timestamp property with the real one here before returning the settings.
-		 */
-		nm_settings_connection_get_timestamp (self, &timestamp);
-		if (timestamp) {
-			s_con = nm_connection_get_setting_connection (dupl_con);
-			g_object_set (s_con, NM_SETTING_CONNECTION_TIMESTAMP, timestamp, NULL);
-		}
-		/* Seen BSSIDs are not updated in 802-11-wireless 'seen-bssids' property
-		 * from the same reason as timestamp. Thus we put it here to GetSettings()
-		 * return settings too.
-		 */
-		bssids = nm_settings_connection_get_seen_bssids (self);
-		s_wifi = nm_connection_get_setting_wireless (dupl_con);
-		if (bssids && bssids[0] && s_wifi)
-			g_object_set (s_wifi, NM_SETTING_WIRELESS_SEEN_BSSIDS, bssids, NULL);
-
-		/* Secrets should *never* be returned by the GetSettings method, they
-		 * get returned by the GetSecrets method which can be better
-		 * protected against leakage of secrets to unprivileged callers.
-		 */
-		settings = nm_connection_to_dbus (dupl_con, NM_CONNECTION_SERIALIZE_NO_SECRETS);
-		g_dbus_method_invocation_return_value (context,
-		                                       g_variant_new ("(@a{sa{sv}})", settings));
+		return;
 	}
+
+	/* Timestamp is not updated in connection's 'timestamp' property,
+	 * because it would force updating the connection and in turn
+	 * writing to /etc periodically, which we want to avoid. Rather real
+	 * timestamps are kept track of in a private variable. So, substitute
+	 * timestamp property with the real one here before returning the settings.
+	 */
+	options.timestamp.has = TRUE;
+	nm_settings_connection_get_timestamp (self, &options.timestamp.val);
+
+	/* Seen BSSIDs are not updated in 802-11-wireless 'seen-bssids' property
+	 * from the same reason as timestamp. Thus we put it here to GetSettings()
+	 * return settings too.
+	 */
+	seen_bssids = nm_settings_connection_get_seen_bssids (self);
+	options.seen_bssids = seen_bssids;
+
+	/* Secrets should *never* be returned by the GetSettings method, they
+	 * get returned by the GetSecrets method which can be better
+	 * protected against leakage of secrets to unprivileged callers.
+	 */
+	settings = nm_connection_to_dbus_full (nm_settings_connection_get_connection (self),
+	                                       NM_CONNECTION_SERIALIZE_NO_SECRETS,
+	                                       &options);
+	g_dbus_method_invocation_return_value (context,
+	                                       g_variant_new ("(@a{sa{sv}})", settings));
 }
 
 static void
@@ -1533,28 +1401,6 @@ typedef struct {
 } UpdateInfo;
 
 static void
-cached_secrets_to_connection (NMSettingsConnection *self, NMConnection *connection)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	GVariant *secrets_dict;
-
-	if (priv->agent_secrets) {
-		secrets_dict = nm_connection_to_dbus (priv->agent_secrets, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-		if (secrets_dict) {
-			(void) nm_connection_update_secrets (connection, NULL, secrets_dict, NULL);
-			g_variant_unref (secrets_dict);
-		}
-	}
-	if (priv->system_secrets) {
-		secrets_dict = nm_connection_to_dbus (priv->system_secrets, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-		if (secrets_dict) {
-			(void) nm_connection_update_secrets (connection, NULL, secrets_dict, NULL);
-			g_variant_unref (secrets_dict);
-		}
-	}
-}
-
-static void
 update_complete (NMSettingsConnection *self,
                  UpdateInfo *info,
                  GError *error)
@@ -1566,7 +1412,7 @@ update_complete (NMSettingsConnection *self,
 
 		g_variant_builder_init (&result, G_VARIANT_TYPE ("a{sv}"));
 		g_dbus_method_invocation_return_value (info->context,
-		                                       g_variant_new ("(@a{sv})", g_variant_builder_end (&result)));
+		                                       g_variant_new ("(a{sv})", &result));
 	} else
 		g_dbus_method_invocation_return_value (info->context, NULL);
 
@@ -1587,24 +1433,28 @@ update_auth_cb (NMSettingsConnection *self,
                 GError *error,
                 gpointer data)
 {
+	NMSettingsConnectionPrivate *priv;
 	UpdateInfo *info = data;
-	NMSettingsConnectionCommitReason commit_reason;
 	gs_free_error GError *local = NULL;
 	NMSettingsConnectionPersistMode persist_mode;
-	const char *log_diff_name;
 
 	if (error) {
 		update_complete (self, info, error);
 		return;
 	}
 
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
 	if (info->new_settings) {
 		if (!_nm_connection_aggregate (info->new_settings, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)) {
 			/* 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);
+			if (priv->agent_secrets)
+				nm_connection_update_secrets (info->new_settings, NULL, priv->agent_secrets, NULL);
+			if (priv->system_secrets)
+				nm_connection_update_secrets (info->new_settings, NULL, priv->system_secrets, NULL);
 		} 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
@@ -1628,45 +1478,38 @@ update_auth_cb (NMSettingsConnection *self,
 		}
 	}
 
-	commit_reason = NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION;
-	if (   info->new_settings
-	    && !nm_streq0 (nm_connection_get_id (nm_settings_connection_get_connection (self)),
-	                   nm_connection_get_id (info->new_settings)))
-		commit_reason |= NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED;
+	nm_assert (   !NM_FLAGS_ANY (info->flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)
+	           || nm_utils_is_power_of_two (info->flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES));
 
 	if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_TO_DISK))
-		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK;
-	else if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY))
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+	else if (NM_FLAGS_ANY (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY))
 		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY;
-	else if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED)) {
-		persist_mode = NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE)
-		               ? NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_DETACHED
-		               : NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED;
-	} else if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY)) {
-		persist_mode = NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE)
-		               ? NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_ONLY
-		               : NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+	else if (NM_FLAGS_ANY (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED))
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED;
+	else if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY)) {
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
 	} else
 		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP;
 
-	if (   persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK
-	    || (   persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP
-	        && !nm_settings_connection_get_unsaved (self)))
-		log_diff_name = info->new_settings ? "update-settings" : "write-out-to-disk";
-	else
-		log_diff_name = info->new_settings ? "update-unsaved" : "make-unsaved";
-
-	if (NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT)) {
-		nm_settings_connection_autoconnect_blocked_reason_set (self,
-		                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
-		                                                       TRUE);
-	}
-
 	nm_settings_connection_update (self,
 	                               info->new_settings,
 	                               persist_mode,
-	                               commit_reason,
-	                               log_diff_name,
+	                               (  NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE)
+	                                ? NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE),
+	                                 NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                               | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
+	                                 NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME
+	                               | (  NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)
+	                                  ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE
+	                                  : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)
+	                               | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+	                               | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
+	                               | (  NM_FLAGS_HAS (info->flags, NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT)
+	                                  ? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT
+	                                  : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE),
+	                               "update-from-dbus",
 	                               &local);
 
 	if (!local) {
@@ -1677,15 +1520,17 @@ update_auth_cb (NMSettingsConnection *self,
 		 * Only send secrets to agents of the same UID that called update too.
 		 */
 		for_agent = nm_simple_connection_new_clone (nm_settings_connection_get_connection (self));
-		nm_connection_clear_secrets_with_flags (for_agent,
-		                                        secrets_filter_cb,
-		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+		_nm_connection_clear_secrets_by_secret_flags (for_agent,
+		                                              NM_SETTING_SECRET_FLAG_AGENT_OWNED);
 		nm_agent_manager_save_secrets (info->agent_mgr,
 		                               nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
 		                               for_agent,
 		                               info->subject);
 	}
 
+	/* Reset auto retries back to default since connection was updated */
+	nm_settings_connection_autoconnect_retries_reset (self);
+
 	update_complete (self, info, local);
 }
 
@@ -1727,13 +1572,6 @@ settings_connection_update (NMSettingsConnection *self,
 	UpdateInfo *info;
 	const char *permission;
 
-	/* If the connection is read-only, that has to be changed at the source of
-	 * the problem (ex a system settings plugin that can't write connections out)
-	 * instead of over D-Bus.
-	 */
-	if (!check_writable (nm_settings_connection_get_connection (self), &error))
-		goto error;
-
 	/* Check if the settings are valid first */
 	if (new_settings) {
 		if (!g_variant_is_of_type (new_settings, NM_VARIANT_TYPE_CONNECTION)) {
@@ -1858,16 +1696,13 @@ impl_settings_connection_update2 (NMDBusObject *obj,
 	GVariantIter iter;
 	const char *args_name;
 	NMSettingsUpdate2Flags flags;
-	const NMSettingsUpdate2Flags ALL_PERSIST_MODES =   NM_SETTINGS_UPDATE2_FLAG_TO_DISK
-	                                                 | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY
-	                                                 | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED
-	                                                 | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY;
 
 	g_variant_get (parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args);
 
-	if (NM_FLAGS_ANY (flags_u, ~((guint32) (ALL_PERSIST_MODES |
-	                                        NM_SETTINGS_UPDATE2_FLAG_VOLATILE |
-	                                        NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT)))) {
+	if (NM_FLAGS_ANY (flags_u, ~((guint32) (  _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES
+	                                        | NM_SETTINGS_UPDATE2_FLAG_VOLATILE
+	                                        | NM_SETTINGS_UPDATE2_FLAG_BLOCK_AUTOCONNECT
+	                                        | NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)))) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
 		                             "Unknown flags");
@@ -1877,11 +1712,12 @@ impl_settings_connection_update2 (NMDBusObject *obj,
 
 	flags = (NMSettingsUpdate2Flags) flags_u;
 
-	if (   (   NM_FLAGS_ANY (flags, ALL_PERSIST_MODES)
-	        && !nm_utils_is_power_of_two (flags & ALL_PERSIST_MODES))
+	if (   (   NM_FLAGS_ANY (flags, _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES)
+	        && !nm_utils_is_power_of_two (flags & _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES))
 	    || (   NM_FLAGS_HAS (flags, NM_SETTINGS_UPDATE2_FLAG_VOLATILE)
-	        && !NM_FLAGS_ANY (flags, NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED |
-	                                 NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))) {
+	        && !NM_FLAGS_ANY (flags,   NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY
+	                                 | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED
+	                                 | NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY))) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
 		                             "Conflicting flags");
@@ -1889,13 +1725,7 @@ impl_settings_connection_update2 (NMDBusObject *obj,
 		return;
 	}
 
-	if (!g_variant_is_of_type (args, G_VARIANT_TYPE ("a{sv}"))) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
-		                             "args is of invalid type");
-		g_dbus_method_invocation_take_error (invocation, error);
-		return;
-	}
+	nm_assert (g_variant_is_of_type (args, G_VARIANT_TYPE ("a{sv}")));
 
 	g_variant_iter_init (&iter, args);
 	while (g_variant_iter_next (&iter, "{&sv}", &args_name, NULL)) {
@@ -1921,7 +1751,6 @@ delete_auth_cb (NMSettingsConnection *self,
                 gpointer data)
 {
 	gs_unref_object NMSettingsConnection *self_keep_alive = NULL;
-	gs_free_error GError *local = NULL;
 
 	self_keep_alive = g_object_ref (self);
 
@@ -1932,15 +1761,11 @@ delete_auth_cb (NMSettingsConnection *self,
 		return;
 	}
 
-	nm_settings_connection_delete (self, &local);
+	nm_settings_connection_delete (self, TRUE);
 
 	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);
+	                            TRUE, NULL, subject, NULL);
+	g_dbus_method_invocation_return_value (context, NULL);
 }
 
 static const char *
@@ -1972,8 +1797,7 @@ impl_settings_connection_delete (NMDBusObject *obj,
 	gs_unref_object NMAuthSubject *subject = NULL;
 	GError *error = NULL;
 
-	if (!check_writable (nm_settings_connection_get_connection (self), &error))
-		goto err;
+	nm_assert (nm_settings_connection_still_valid (self));
 
 	subject = _new_auth_subject (invocation, &error);
 	if (!subject)
@@ -2088,25 +1912,13 @@ dbus_clear_secrets_auth_cb (NMSettingsConnection *self,
 		return;
 	}
 
-	/* Clear secrets in connection and caches */
-	nm_connection_clear_secrets (nm_settings_connection_get_connection (self));
-	if (priv->system_secrets)
-		nm_connection_clear_secrets (priv->system_secrets);
-	if (priv->agent_secrets)
-		nm_connection_clear_secrets (priv->agent_secrets);
+	nm_settings_connection_clear_secrets (self, TRUE, TRUE);
 
 	/* Tell agents to remove secrets for this connection */
 	nm_agent_manager_delete_secrets (priv->agent_mgr,
 	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
 	                                 nm_settings_connection_get_connection (self));
 
-	nm_settings_connection_update (self,
-	                               NULL,
-	                               NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-	                               NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                               "clear-secrets",
-	                               &local);
-
 	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_CLEAR_SECRETS, self,
 	                            !local, NULL, subject, local ? local->message : NULL);
 
@@ -2147,40 +1959,28 @@ impl_settings_connection_clear_secrets (NMDBusObject *obj,
 /*****************************************************************************/
 
 void
-nm_settings_connection_added (NMSettingsConnection *self)
+_nm_settings_connection_emit_dbus_signal_updated (NMSettingsConnection *self)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-
-	/* FIXME: we should always dispose connections that are removed
-	 * and not reuse them, but currently plugins keep alive unmanaged
-	 * (e.g. NM_CONTROLLED=no) connections. */
-	priv->removed = FALSE;
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_settings_connection,
+	                            &signal_info_updated,
+	                            "()");
 }
 
 void
-nm_settings_connection_signal_remove (NMSettingsConnection *self)
+_nm_settings_connection_emit_dbus_signal_removed (NMSettingsConnection *self)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	AuthData *auth_data;
-
-	if (priv->removed)
-		return;
-	priv->removed = TRUE;
-
-	while ((auth_data = c_list_first_entry (&priv->auth_lst_head, AuthData, auth_lst)))
-		nm_auth_manager_check_authorization_cancel (auth_data->call_id);
-
 	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
 	                            &interface_info_settings_connection,
 	                            &signal_info_removed,
 	                            "()");
-	g_signal_emit (self, signals[REMOVED], 0);
 }
 
-gboolean
-nm_settings_connection_get_unsaved (NMSettingsConnection *self)
+void
+_nm_settings_connection_emit_signal_updated_internal (NMSettingsConnection *self,
+                                                      NMSettingsConnectionUpdateReason update_reason)
 {
-	return NM_FLAGS_HAS (nm_settings_connection_get_flags (self), NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED);
+	g_signal_emit (self, signals[UPDATED_INTERNAL], 0, (guint) update_reason);
 }
 
 /*****************************************************************************/
@@ -2202,14 +2002,6 @@ nm_settings_connection_get_flags (NMSettingsConnection *self)
 }
 
 NMSettingsConnectionIntFlags
-nm_settings_connection_set_flags (NMSettingsConnection *self, NMSettingsConnectionIntFlags flags, gboolean set)
-{
-	return nm_settings_connection_set_flags_full (self,
-	                                              flags,
-	                                              set ? flags : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
-}
-
-NMSettingsConnectionIntFlags
 nm_settings_connection_set_flags_full (NMSettingsConnection *self,
                                        NMSettingsConnectionIntFlags mask,
                                        NMSettingsConnectionIntFlags value)
@@ -2218,7 +2010,8 @@ nm_settings_connection_set_flags_full (NMSettingsConnection *self,
 	NMSettingsConnectionIntFlags old_flags;
 
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
-	nm_assert (mask && !NM_FLAGS_ANY (mask, ~NM_SETTINGS_CONNECTION_INT_FLAGS_ALL));
+
+	nm_assert (!NM_FLAGS_ANY (mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL));
 	nm_assert (!NM_FLAGS_ANY (value, ~mask));
 
 	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
@@ -2338,11 +2131,13 @@ gboolean
 nm_settings_connection_get_timestamp (NMSettingsConnection *self,
                                       guint64 *out_timestamp)
 {
+	NMSettingsConnectionPrivate *priv;
+
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
 
-	if (out_timestamp)
-		*out_timestamp = NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->timestamp;
-	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->timestamp_set;
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	NM_SET_OUT (out_timestamp, priv->timestamp);
+	return priv->timestamp_set;
 }
 
 /**
@@ -2350,98 +2145,113 @@ nm_settings_connection_get_timestamp (NMSettingsConnection *self,
  * @self: the #NMSettingsConnection
  * @timestamp: timestamp to set into the connection and to store into
  * the timestamps database
- * @flush_to_disk: if %TRUE, commit timestamp update to persistent storage
  *
  * Updates the connection and timestamps database with the provided timestamp.
  **/
 void
 nm_settings_connection_update_timestamp (NMSettingsConnection *self,
-                                         guint64 timestamp,
-                                         gboolean flush_to_disk)
+                                         guint64 timestamp)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 	const char *connection_uuid;
-	GKeyFile *timestamps_file;
-	char *data, *tmp;
-	gsize len;
-	GError *error = NULL;
+	char sbuf[60];
 
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
 
-	/* Update timestamp in private storage */
 	priv->timestamp = timestamp;
 	priv->timestamp_set = TRUE;
 
-	if (flush_to_disk == FALSE)
-		return;
-	if (nm_config_get_configure_and_quit (nm_config_get ()) == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD)
+	if (!priv->kf_db_timestamps)
 		return;
 
-	/* Save timestamp to timestamps database file */
-	timestamps_file = g_key_file_new ();
-	if (!g_key_file_load_from_file (timestamps_file, SETTINGS_TIMESTAMPS_FILE, G_KEY_FILE_KEEP_COMMENTS, &error)) {
-		if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
-			_LOGW ("error parsing timestamps file '%s': %s", SETTINGS_TIMESTAMPS_FILE, error->message);
-		g_clear_error (&error);
-	}
-
 	connection_uuid = nm_settings_connection_get_uuid (self);
-	tmp = g_strdup_printf ("%" G_GUINT64_FORMAT, timestamp);
-	g_key_file_set_value (timestamps_file, "timestamps", connection_uuid, tmp);
-	g_free (tmp);
-
-	data = g_key_file_to_data (timestamps_file, &len, &error);
-	if (data) {
-		g_file_set_contents (SETTINGS_TIMESTAMPS_FILE, data, len, &error);
-		g_free (data);
-	}
-	if (error) {
-		_LOGW ("error saving timestamp to file '%s': %s", SETTINGS_TIMESTAMPS_FILE, error->message);
-		g_error_free (error);
+	if (connection_uuid) {
+		nm_key_file_db_set_value (priv->kf_db_timestamps,
+		                          connection_uuid,
+		                          nm_sprintf_buf (sbuf, "%" G_GUINT64_FORMAT, timestamp));
 	}
-	g_key_file_free (timestamps_file);
 }
 
-/**
- * nm_settings_connection_read_and_fill_timestamp:
- * @self: the #NMSettingsConnection
- *
- * Retrieves timestamp of the connection's last usage from database file and
- * stores it into the connection private data.
- **/
 void
-nm_settings_connection_read_and_fill_timestamp (NMSettingsConnection *self)
+_nm_settings_connection_register_kf_dbs (NMSettingsConnection *self,
+                                         NMKeyFileDB *kf_db_timestamps,
+                                         NMKeyFileDB *kf_db_seen_bssids)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	gs_unref_keyfile GKeyFile *timestamps_file = NULL;
-	gs_free_error GError *error = NULL;
-	gs_free char *tmp_str = NULL;
+	NMSettingsConnectionPrivate *priv;
 	const char *connection_uuid;
-	gint64 timestamp;
 
 	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (self));
+	g_return_if_fail (kf_db_timestamps);
+	g_return_if_fail (kf_db_seen_bssids);
 
-	timestamps_file = g_key_file_new ();
-	if (!g_key_file_load_from_file (timestamps_file, SETTINGS_TIMESTAMPS_FILE, G_KEY_FILE_KEEP_COMMENTS, &error)) {
-		_LOGD ("failed to read connection timestamp: %s", error->message);
-		return;
-	}
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
 	connection_uuid = nm_settings_connection_get_uuid (self);
-	tmp_str = g_key_file_get_value (timestamps_file, "timestamps", connection_uuid, &error);
-	if (!tmp_str) {
-		_LOGD ("failed to read connection timestamp: %s", error->message);
-		return;
-	}
 
-	timestamp = _nm_utils_ascii_str_to_int64 (tmp_str, 10, 0, G_MAXINT64, -1);
-	if (timestamp < 0) {
-		_LOGD ("failed to read connection timestamp: %s", "invalid number");
-		return;
+	if (priv->kf_db_timestamps != kf_db_timestamps) {
+		gs_free char *tmp_str = NULL;
+		guint64 timestamp;
+
+		nm_key_file_db_unref (priv->kf_db_timestamps);
+		priv->kf_db_timestamps = nm_key_file_db_ref (kf_db_timestamps);
+
+		tmp_str = nm_key_file_db_get_value (priv->kf_db_timestamps, connection_uuid);
+
+		timestamp = _nm_utils_ascii_str_to_uint64 (tmp_str, 10, 0, G_MAXUINT64, G_MAXUINT64);
+		if (timestamp != G_MAXUINT64) {
+			priv->timestamp = timestamp;
+			priv->timestamp_set = TRUE;
+			_LOGT ("read timestamp %"G_GUINT64_FORMAT" from keyfile database \"%s\"",
+			       timestamp, nm_key_file_db_get_filename (priv->kf_db_timestamps));
+		} else
+			_LOGT ("no timestamp from keyfile database \"%s\"",
+			       nm_key_file_db_get_filename (priv->kf_db_timestamps));
 	}
 
-	priv->timestamp = timestamp;
-	priv->timestamp_set = TRUE;
+	if (priv->kf_db_seen_bssids != kf_db_seen_bssids) {
+		gs_strfreev char **tmp_strv = NULL;
+		gsize i, len;
+
+		nm_key_file_db_unref (priv->kf_db_seen_bssids);
+		priv->kf_db_seen_bssids = nm_key_file_db_ref (kf_db_seen_bssids);
+
+		tmp_strv = nm_key_file_db_get_string_list (priv->kf_db_seen_bssids, connection_uuid, &len);
+
+		nm_clear_pointer (&priv->seen_bssids, g_hash_table_unref);
+
+		if (len > 0) {
+			_LOGT ("read %zu seen-bssids from keyfile database \"%s\"",
+			       len,
+			       nm_key_file_db_get_filename (priv->kf_db_seen_bssids));
+			priv->seen_bssids = _seen_bssids_hash_new ();
+			for (i = len; i > 0; )
+				g_hash_table_add (priv->seen_bssids, g_steal_pointer (&tmp_strv[--i]));
+			nm_clear_g_free (&tmp_strv);
+		} else {
+			NMSettingWireless *s_wifi;
+
+			_LOGT ("no seen-bssids from keyfile database \"%s\"",
+			       nm_key_file_db_get_filename (priv->kf_db_seen_bssids));
+
+			/* If this connection didn't have an entry in the seen-bssids database,
+			 * maybe this is the first time we've read it in, so populate the
+			 * seen-bssids list from the deprecated seen-bssids property of the
+			 * wifi setting.
+			 */
+			s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (self));
+			if (s_wifi) {
+				len = nm_setting_wireless_get_num_seen_bssids (s_wifi);
+				if (len > 0) {
+					priv->seen_bssids = _seen_bssids_hash_new ();
+					for (i = 0; i < len; i++) {
+						const char *bssid = nm_setting_wireless_get_seen_bssid (s_wifi, i);
+
+						g_hash_table_add (priv->seen_bssids, g_strdup (bssid));
+					}
+				}
+			}
+		}
+	}
 }
 
 /**
@@ -2453,25 +2263,14 @@ nm_settings_connection_read_and_fill_timestamp (NMSettingsConnection *self)
  * Returns: (transfer container) list of seen BSSIDs (in the standard hex-digits-and-colons notation).
  * The caller is responsible for freeing the list, but not the content.
  **/
-char **
+const char **
 nm_settings_connection_get_seen_bssids (NMSettingsConnection *self)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	GHashTableIter iter;
-	char **bssids, *bssid;
-	int i;
-
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NULL);
 
-	bssids = g_new (char *, g_hash_table_size (priv->seen_bssids) + 1);
-
-	i = 0;
-	g_hash_table_iter_init (&iter, priv->seen_bssids);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &bssid))
-		bssids[i++] = bssid;
-	bssids[i] = NULL;
-
-	return bssids;
+	return nm_utils_strdict_get_keys (NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->seen_bssids,
+	                                  TRUE,
+	                                  NULL);
 }
 
 /**
@@ -2485,10 +2284,15 @@ gboolean
 nm_settings_connection_has_seen_bssid (NMSettingsConnection *self,
                                        const char *bssid)
 {
+	NMSettingsConnectionPrivate *priv;
+
 	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), FALSE);
-	g_return_val_if_fail (bssid != NULL, FALSE);
+	g_return_val_if_fail (bssid, FALSE);
+
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
-	return !!g_hash_table_lookup (NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->seen_bssids, bssid);
+	return    priv->seen_bssids
+	       && g_hash_table_contains (NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->seen_bssids, bssid);
 }
 
 /**
@@ -2504,108 +2308,29 @@ nm_settings_connection_add_seen_bssid (NMSettingsConnection *self,
                                        const char *seen_bssid)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	gs_free const char **strv = NULL;
 	const char *connection_uuid;
-	GKeyFile *seen_bssids_file;
-	char *data, *bssid_str;
-	const char **list;
-	gsize len;
-	GError *error = NULL;
-	GHashTableIter iter;
-	guint n;
 
 	g_return_if_fail (seen_bssid != NULL);
 
-	if (g_hash_table_lookup (priv->seen_bssids, seen_bssid))
-		return;  /* Already in the list */
-
-	/* Add the new BSSID; let the hash take ownership of the allocated BSSID string */
-	bssid_str = g_strdup (seen_bssid);
-	g_hash_table_insert (priv->seen_bssids, bssid_str, bssid_str);
-
-	/* Build up a list of all the BSSIDs in string form */
-	n = 0;
-	list = g_malloc0 (g_hash_table_size (priv->seen_bssids) * sizeof (char *));
-	g_hash_table_iter_init (&iter, priv->seen_bssids);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &bssid_str))
-		list[n++] = bssid_str;
-
-	/* Save BSSID to seen-bssids file */
-	seen_bssids_file = g_key_file_new ();
-	g_key_file_set_list_separator (seen_bssids_file, ',');
-	if (!g_key_file_load_from_file (seen_bssids_file, SETTINGS_SEEN_BSSIDS_FILE, G_KEY_FILE_KEEP_COMMENTS, &error)) {
-		if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
-			_LOGW ("error parsing seen-bssids file '%s': %s",
-			       SETTINGS_SEEN_BSSIDS_FILE, error->message);
-		}
-		g_clear_error (&error);
-	}
+	if (!priv->seen_bssids)
+		priv->seen_bssids = _seen_bssids_hash_new ();
 
-	connection_uuid = nm_settings_connection_get_uuid (self);
-	g_key_file_set_string_list (seen_bssids_file, "seen-bssids", connection_uuid, list, n);
-	g_free (list);
+	g_hash_table_add (priv->seen_bssids, g_strdup (seen_bssid));
 
-	data = g_key_file_to_data (seen_bssids_file, &len, &error);
-	if (data) {
-		g_file_set_contents (SETTINGS_SEEN_BSSIDS_FILE, data, len, &error);
-		g_free (data);
-	}
-	g_key_file_free (seen_bssids_file);
+	if (!priv->kf_db_seen_bssids)
+		return;
 
-	if (error) {
-		_LOGW ("error saving seen-bssids to file '%s': %s",
-		       SETTINGS_SEEN_BSSIDS_FILE, error->message);
-		g_error_free (error);
-	}
-}
+	connection_uuid = nm_settings_connection_get_uuid (self);
+	if (!connection_uuid)
+		return;
 
-/**
- * nm_settings_connection_read_and_fill_seen_bssids:
- * @self: the #NMSettingsConnection
- *
- * Retrieves seen BSSIDs of the connection from database file and stores then into the
- * connection private data.
- **/
-void
-nm_settings_connection_read_and_fill_seen_bssids (NMSettingsConnection *self)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-	const char *connection_uuid;
-	GKeyFile *seen_bssids_file;
-	char **tmp_strv = NULL;
-	gsize i, len = 0;
-	NMSettingWireless *s_wifi;
-
-	/* Get seen BSSIDs from database file */
-	seen_bssids_file = g_key_file_new ();
-	g_key_file_set_list_separator (seen_bssids_file, ',');
-	if (g_key_file_load_from_file (seen_bssids_file, SETTINGS_SEEN_BSSIDS_FILE, G_KEY_FILE_KEEP_COMMENTS, NULL)) {
-		connection_uuid = nm_settings_connection_get_uuid (self);
-		tmp_strv = g_key_file_get_string_list (seen_bssids_file, "seen-bssids", connection_uuid, &len, NULL);
-	}
-	g_key_file_free (seen_bssids_file);
-
-	/* Update connection's seen-bssids */
-	if (tmp_strv) {
-		g_hash_table_remove_all (priv->seen_bssids);
-		for (i = 0; i < len; i++)
-			g_hash_table_insert (priv->seen_bssids, tmp_strv[i], tmp_strv[i]);
-		g_free (tmp_strv);
-	} else {
-		/* If this connection didn't have an entry in the seen-bssids database,
-		 * maybe this is the first time we've read it in, so populate the
-		 * seen-bssids list from the deprecated seen-bssids property of the
-		 * wifi setting.
-		 */
-		s_wifi = nm_connection_get_setting_wireless (nm_settings_connection_get_connection (self));
-		if (s_wifi) {
-			len = nm_setting_wireless_get_num_seen_bssids (s_wifi);
-			for (i = 0; i < len; i++) {
-				char *bssid_dup = g_strdup (nm_setting_wireless_get_seen_bssid (s_wifi, i));
+	strv = nm_utils_strdict_get_keys (priv->seen_bssids, TRUE, NULL);
 
-				g_hash_table_insert (priv->seen_bssids, bssid_dup, bssid_dup);
-			}
-		}
-	}
+	nm_key_file_db_set_string_list (priv->kf_db_seen_bssids,
+	                                connection_uuid,
+	                                strv ?: NM_PTRARRAY_EMPTY (const char *),
+	                                -1);
 }
 
 /*****************************************************************************/
@@ -2767,46 +2492,6 @@ nm_settings_connection_autoconnect_is_blocked (NMSettingsConnection *self)
 
 /*****************************************************************************/
 
-gboolean
-nm_settings_connection_get_ready (NMSettingsConnection *self)
-{
-	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->ready;
-}
-
-void
-nm_settings_connection_set_ready (NMSettingsConnection *self,
-                                  gboolean ready)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-
-	ready = !!ready;
-	if (priv->ready != ready) {
-		priv->ready = ready;
-		_notify (self, PROP_READY);
-	}
-}
-
-/**
- * nm_settings_connection_set_filename:
- * @self: an #NMSettingsConnection
- * @filename: @self's filename
- *
- * Called by a backend to sets the filename that @self is read
- * from/written to.
- */
-void
-nm_settings_connection_set_filename (NMSettingsConnection *self,
-                                     const char *filename)
-{
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
-
-	if (g_strcmp0 (filename, priv->filename) != 0) {
-		g_free (priv->filename);
-		priv->filename = g_strdup (filename);
-		_notify (self, PROP_FILENAME);
-	}
-}
-
 /**
  * nm_settings_connection_get_filename:
  * @self: an #NMSettingsConnection
@@ -2820,9 +2505,9 @@ nm_settings_connection_set_filename (NMSettingsConnection *self,
 const char *
 nm_settings_connection_get_filename (NMSettingsConnection *self)
 {
-	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NULL);
 
-	return priv->filename;
+	return NM_SETTINGS_CONNECTION_GET_PRIVATE (self)->filename;
 }
 
 const char *
@@ -2834,7 +2519,18 @@ nm_settings_connection_get_id (NMSettingsConnection *self)
 const char *
 nm_settings_connection_get_uuid (NMSettingsConnection *self)
 {
-	return nm_connection_get_uuid (nm_settings_connection_get_connection (self));
+	NMSettingsConnectionPrivate *priv;
+	const char *uuid;
+
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (self), NULL);
+
+	priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	uuid = nm_settings_storage_get_uuid (priv->storage);
+
+	nm_assert (uuid && nm_streq0 (uuid, nm_connection_get_uuid (nm_settings_connection_get_connection (self))));
+
+	return uuid;
 }
 
 const char *
@@ -2845,6 +2541,43 @@ nm_settings_connection_get_connection_type (NMSettingsConnection *self)
 
 /*****************************************************************************/
 
+void
+_nm_settings_connection_cleanup_after_remove (NMSettingsConnection *self)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+	AuthData *auth_data;
+
+	while ((auth_data = c_list_first_entry (&priv->auth_lst_head, AuthData, auth_lst)))
+		nm_auth_manager_check_authorization_cancel (auth_data->call_id);
+}
+
+/*****************************************************************************/
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (object);
+
+	switch (prop_id) {
+	case PROP_UNSAVED:
+		g_value_set_boolean (value, nm_settings_connection_get_unsaved (self));
+		break;
+	case PROP_FLAGS:
+		g_value_set_uint (value,
+		                  nm_settings_connection_get_flags (self) & _NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK);
+		break;
+	case PROP_FILENAME:
+		g_value_set_string (value, nm_settings_connection_get_filename (self));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
 static void
 nm_settings_connection_init (NMSettingsConnection *self)
 {
@@ -2855,35 +2588,19 @@ nm_settings_connection_init (NMSettingsConnection *self)
 
 	c_list_init (&self->_connections_lst);
 
-	priv->ready = TRUE;
 	c_list_init (&priv->call_ids_lst_head);
 	c_list_init (&priv->auth_lst_head);
 
-	priv->session_monitor = g_object_ref (nm_session_monitor_get ());
-	priv->session_changed_id = g_signal_connect (priv->session_monitor,
-	                                             NM_SESSION_MONITOR_CHANGED,
-	                                             G_CALLBACK (session_changed_cb), self);
-
 	priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
-
-	priv->seen_bssids = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+	priv->settings = g_object_ref (nm_settings_get ());
 
 	priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET;
-
-	priv->connection = nm_simple_connection_new ();
-
-	g_signal_connect (priv->connection, NM_CONNECTION_SECRETS_CLEARED, G_CALLBACK (secrets_cleared_cb), self);
-	g_signal_connect (priv->connection, NM_CONNECTION_CHANGED, G_CALLBACK (connection_changed_cb), self);
 }
 
-static void
-constructed (GObject *object)
+NMSettingsConnection *
+nm_settings_connection_new (void)
 {
-	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (object);
-
-	_LOGD ("constructed (%s)", G_OBJECT_TYPE_NAME (self));
-
-	G_OBJECT_CLASS (nm_settings_connection_parent_class)->constructed (object);
+	return g_object_new (NM_TYPE_SETTINGS_CONNECTION, NULL);
 }
 
 static void
@@ -2895,6 +2612,8 @@ dispose (GObject *object)
 
 	_LOGD ("disposing");
 
+	nm_assert (!priv->default_wired_device);
+
 	nm_assert (c_list_is_empty (&self->_connections_lst));
 	nm_assert (c_list_is_empty (&priv->auth_lst_head));
 
@@ -2904,80 +2623,29 @@ dispose (GObject *object)
 			_get_secrets_cancel (self, call_id, TRUE);
 	}
 
-	set_visible (self, FALSE);
-
-	if (priv->connection) {
-		/* Disconnect handlers.
-		 * connection_changed_cb() has to be disconnected *before* nm_connection_clear_secrets(),
-		 * because nm_connection_clear_secrets() emits NM_CONNECTION_CHANGED signal.
-		 */
-		g_signal_handlers_disconnect_by_func (priv->connection, G_CALLBACK (secrets_cleared_cb), self);
-		g_signal_handlers_disconnect_by_func (priv->connection, G_CALLBACK (connection_changed_cb), self);
-
-		/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-		nm_connection_clear_secrets (priv->connection);
-	}
-
-	g_clear_object (&priv->system_secrets);
-	g_clear_object (&priv->agent_secrets);
+	nm_clear_pointer (&priv->system_secrets, g_variant_unref);
+	nm_clear_pointer (&priv->agent_secrets, g_variant_unref);
 
 	g_clear_pointer (&priv->seen_bssids, g_hash_table_destroy);
 
-	nm_clear_g_signal_handler (priv->session_monitor, &priv->session_changed_id);
-	g_clear_object (&priv->session_monitor);
-
 	g_clear_object (&priv->agent_mgr);
 
 	g_clear_object (&priv->connection);
 
-	g_clear_pointer (&priv->filename, g_free);
+	g_clear_pointer (&priv->kf_db_timestamps, nm_key_file_db_unref);
+	g_clear_pointer (&priv->kf_db_seen_bssids, nm_key_file_db_unref);
 
 	G_OBJECT_CLASS (nm_settings_connection_parent_class)->dispose (object);
-}
 
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (object);
+	g_clear_object (&priv->storage);
 
-	switch (prop_id) {
-	case PROP_UNSAVED:
-		g_value_set_boolean (value, nm_settings_connection_get_unsaved (self));
-		break;
-	case PROP_READY:
-		g_value_set_boolean (value, nm_settings_connection_get_ready (self));
-		break;
-	case PROP_FLAGS:
-		g_value_set_uint (value,
-		                  nm_settings_connection_get_flags (self) & NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK);
-		break;
-	case PROP_FILENAME:
-		g_value_set_string (value, nm_settings_connection_get_filename (self));
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMSettingsConnection *self = NM_SETTINGS_CONNECTION (object);
+	nm_clear_g_free (&priv->filename);
 
-	switch (prop_id) {
-	case PROP_FILENAME:
-		/* construct-only */
-		nm_settings_connection_set_filename (self, g_value_get_string (value));
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
+	g_clear_object (&priv->settings);
 }
 
+/*****************************************************************************/
+
 static const GDBusSignalInfo signal_info_updated = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
 	"Updated",
 );
@@ -3087,12 +2755,8 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *klass)
 	dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED (NM_DBUS_PATH_SETTINGS);
 	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_settings_connection);
 
-	object_class->constructed = constructed;
 	object_class->dispose = dispose;
 	object_class->get_property = get_property;
-	object_class->set_property = set_property;
-
-	klass->supports_secrets = supports_secrets;
 
 	obj_properties[PROP_UNSAVED] =
 	     g_param_spec_boolean (NM_SETTINGS_CONNECTION_UNSAVED, "", "",
@@ -3100,12 +2764,6 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *klass)
 	                           G_PARAM_READABLE |
 	                           G_PARAM_STATIC_STRINGS);
 
-	obj_properties[PROP_READY] =
-	     g_param_spec_boolean (NM_SETTINGS_CONNECTION_READY, "", "",
-	                           TRUE,
-	                           G_PARAM_READABLE |
-	                           G_PARAM_STATIC_STRINGS);
-
 	obj_properties[PROP_FLAGS] =
 	     g_param_spec_uint (NM_SETTINGS_CONNECTION_FLAGS, "", "",
 	                        0, G_MAXUINT32, 0,
@@ -3115,29 +2773,20 @@ nm_settings_connection_class_init (NMSettingsConnectionClass *klass)
 	obj_properties[PROP_FILENAME] =
 	     g_param_spec_string (NM_SETTINGS_CONNECTION_FILENAME, "", "",
 	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_READABLE |
 	                          G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-	/* internal signal, with an argument (gboolean by_user). */
+	/* internal signal, with an argument (NMSettingsConnectionUpdateReason update_reason) as
+	 * guint. */
 	signals[UPDATED_INTERNAL] =
 	    g_signal_new (NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
 	                  G_TYPE_FROM_CLASS (klass),
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL,
-	                  g_cclosure_marshal_VOID__BOOLEAN,
-	                  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
-
-	signals[REMOVED] =
-	    g_signal_new (NM_SETTINGS_CONNECTION_REMOVED,
-	                  G_TYPE_FROM_CLASS (klass),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0,
-	                  NULL, NULL,
-	                  g_cclosure_marshal_VOID__VOID,
-	                  G_TYPE_NONE, 0);
+	                  g_cclosure_marshal_VOID__UINT,
+	                  G_TYPE_NONE, 1, G_TYPE_UINT);
 
 	signals[FLAGS_CHANGED] =
 	    g_signal_new (NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index e796b716..61d5c246 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -22,11 +21,116 @@
 #ifndef __NETWORKMANAGER_SETTINGS_CONNECTION_H__
 #define __NETWORKMANAGER_SETTINGS_CONNECTION_H__
 
-#include <net/ethernet.h>
-
 #include "nm-dbus-object.h"
 #include "nm-connection.h"
 
+#include "nm-settings-storage.h"
+
+/*****************************************************************************/
+
+typedef enum {
+
+	NM_SETTINGS_CONNECTION_ADD_REASON_NONE                         = 0,
+
+	NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT            = (1u << 0),
+
+} NMSettingsConnectionAddReason;
+
+typedef enum {
+
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE                      = 0,
+
+	/* with persist-mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, and
+	 * update tries to update the profile on disk (which can always fail).
+	 * In some cases we want to ignore such failure and proceed. For example,
+	 * when we receive secrets from a secret-agent, we want to update the connection
+	 * at all cost and ignore failures to write them to disk. */
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE    = (1u << 0),
+
+	/* When updating the profile, force renaming the file on disk. That matters
+	 * only for keyfile plugin. Keyfile prefers a filename based on connection.id.
+	 * When the connection.id changes we might want to rename the file on disk
+	 * (that is, don't overwrite the existing file, but delete it and write it
+	 * with the new name).
+	 * This flag forces such rename. */
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME              = (1u << 1),
+
+	/* Usually, changing a profile that is currently active does not immediately
+	 * reapply the changes. The exception are connection.zone and connection.metered
+	 * properties. When this flag is set, then these two properties are reapplied
+	 * right away.
+	 *
+	 * See also %NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY flag, to prevent partial reapply
+	 * during Update2(). */
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL           = (1u << 2),
+
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_SYSTEM_SECRETS      = (1u << 3),
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS      = (1u << 4),
+
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_AGENT_SECRETS       = (1u << 5),
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS       = (1u << 6),
+
+	/* if a profile was greated as default-wired connection for a device, then
+	 * when the user modifies it via D-Bus, the profile should become persisted
+	 * to disk and it the purpose why the profile was created should be forgotten. */
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_CLEAR_DEFAULT_WIRED       = (1u << 7),
+
+	NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT         = (1u << 8),
+
+} NMSettingsConnectionUpdateReason;
+
+typedef enum {
+
+	/* if the profile is in-memory, update it in-memory and keep it.
+	 * if the profile is on-disk, update it on-disk, and keep it. */
+	NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+
+	/* persist to disk. If the profile is currently in-memory, remove
+	 * it from /run. Depending on the shadowed-storage, the pre-existing
+	 * file is reused when moving the storage.
+	 *
+	 * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_TO_DISK. */
+	NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+
+	/* Update in-memory (i.e. persist to /run). If the profile is currently on disk,
+	 * then a reference to the profile is remembered as "shadowed-storage".
+	 * Later, when storing again to persistant storage, the shawowed-storage is
+	 * updated. When deleting the profile, the shadowed-storage is also deleted
+	 * from disk.
+	 *
+	 * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY. */
+	NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+
+	/* Update in-memory (i.e. persist to /run). This is almost like
+	 * %NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY, except the in-memory profile
+	 * remembers not to own the shadowed-storage ("shadowed-owned").
+	 * The diffrence is that when deleting the in-memory profile, the original
+	 * profile is not deleted but instead the nmmeta tombstone remembers the
+	 * shadowed-storage and re-used it when re-adding the profile.
+	 *
+	 * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED. */
+	NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+
+	/* Update in-memory (i.e. persist to /run). If the profile is currently on disk,
+	 * delete it from disk.
+	 *
+	 * If the profile is in-memory and has a shadowed-storage, the original profile
+	 * will be deleted from disk.
+	 *
+	 * Corresponds to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. */
+	NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+
+	/* This only updates the connection in-memory. Note that "in-memory" above
+	 * means to write to keyfile in /run. This mode really means to not notify the
+	 * settings plugin about the change. This should be only used for updating
+	 * secrets.
+	 */
+	NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+
+} NMSettingsConnectionPersistMode;
+
+/*****************************************************************************/
+
 #define NM_TYPE_SETTINGS_CONNECTION            (nm_settings_connection_get_type ())
 #define NM_SETTINGS_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnection))
 #define NM_SETTINGS_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionClass))
@@ -34,7 +138,6 @@
 #define NM_IS_SETTINGS_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTINGS_CONNECTION))
 #define NM_SETTINGS_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTINGS_CONNECTION, NMSettingsConnectionClass))
 
-#define NM_SETTINGS_CONNECTION_REMOVED "removed"
 #define NM_SETTINGS_CONNECTION_GET_SECRETS "get-secrets"
 #define NM_SETTINGS_CONNECTION_CANCEL_SECRETS "cancel-secrets"
 #define NM_SETTINGS_CONNECTION_UPDATED_INTERNAL "updated-internal"
@@ -45,9 +148,6 @@
 #define NM_SETTINGS_CONNECTION_FLAGS    "flags"
 #define NM_SETTINGS_CONNECTION_FILENAME "filename"
 
-/* Internal properties */
-#define NM_SETTINGS_CONNECTION_READY    "ready"
-
 /**
  * NMSettingsConnectionIntFlags:
  * @NM_SETTINGS_CONNECTION_INT_FLAGS_NONE: no flag set
@@ -62,38 +162,37 @@
  *  currently active but cleanup on disconnect.
  *  See also #NM_SETTINGS_CONNECTION_FLAG_VOLATILE.
  * @NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE: The connection is visible
- * @NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK: the entire enum is
+ * @_NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK: the entire enum is
  *   internal, however, parts of it is public API as #NMSettingsConnectionFlags.
  *   This mask, are the public flags.
- * @NM_SETTINGS_CONNECTION_INT_FLAGS_ALL: special mask, for all known flags
+ * @_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL: special mask, for all known flags
  *
  * #NMSettingsConnection flags.
  **/
-typedef enum {
+typedef enum _NMSettingsConnectionIntFlags {
 	NM_SETTINGS_CONNECTION_INT_FLAGS_NONE                   = 0,
 
 	NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED                = NM_SETTINGS_CONNECTION_FLAG_UNSAVED,
 	NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED           = NM_SETTINGS_CONNECTION_FLAG_NM_GENERATED,
 	NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE               = NM_SETTINGS_CONNECTION_FLAG_VOLATILE,
 
-	NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE                = (1LL <<  3),
+	NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE                = 0x08,
 
-	__NM_SETTINGS_CONNECTION_INT_FLAGS_LAST,
+	_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST,
 
-	NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK          = 0
+	_NM_SETTINGS_CONNECTION_INT_FLAGS_EXPORTED_MASK         = 0
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
 	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
 	                                                          | 0,
 
-	NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((__NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1,
-} NMSettingsConnectionIntFlags;
+	_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK       = 0
+	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                                                          | 0,
 
-typedef enum {
-	NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE                       = 0,
-	NM_SETTINGS_CONNECTION_COMMIT_REASON_USER_ACTION                = (1LL << 0),
-	NM_SETTINGS_CONNECTION_COMMIT_REASON_ID_CHANGED                 = (1LL << 1),
-} NMSettingsConnectionCommitReason;
+	_NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1,
+} NMSettingsConnectionIntFlags;
 
 typedef enum {
 	NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE                    = 0,
@@ -107,7 +206,6 @@ typedef enum {
 	                                                                   | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS),
 } NMSettingsAutoconnectBlockedReason;
 
-struct _NMSettingsConnectionCallId;
 typedef struct _NMSettingsConnectionCallId NMSettingsConnectionCallId;
 
 typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass;
@@ -116,30 +214,29 @@ struct _NMSettingsConnectionPrivate;
 
 struct _NMSettingsConnection {
 	NMDBusObject parent;
-	struct _NMSettingsConnectionPrivate *_priv;
 	CList _connections_lst;
+	struct _NMSettingsConnectionPrivate *_priv;
 };
 
-struct _NMSettingsConnectionClass {
-	NMDBusObjectClass parent;
+GType nm_settings_connection_get_type (void);
 
-	gboolean (*commit_changes) (NMSettingsConnection *self,
-	                            NMConnection *new_connection,
-	                            NMSettingsConnectionCommitReason commit_reason,
-	                            NMConnection **out_reread_connection,
-	                            char **out_logmsg_change,
-	                            GError **error);
+NMSettingsConnection *nm_settings_connection_new (void);
 
-	gboolean (*delete) (NMSettingsConnection *self,
-	                    GError **error);
+NMConnection *nm_settings_connection_get_connection (NMSettingsConnection *self);
 
-	gboolean (*supports_secrets) (NMSettingsConnection *self,
-	                              const char *setting_name);
-};
+void _nm_settings_connection_set_connection (NMSettingsConnection *self,
+                                             NMConnection *new_connection,
+                                             NMConnection **out_old_connection,
+                                             NMSettingsConnectionUpdateReason update_reason);
 
-GType nm_settings_connection_get_type (void);
+NMSettingsStorage *nm_settings_connection_get_storage (NMSettingsConnection *self);
 
-NMConnection *nm_settings_connection_get_connection (NMSettingsConnection *self);
+void _nm_settings_connection_set_storage (NMSettingsConnection *self,
+                                          NMSettingsStorage *storage);
+
+gboolean nm_settings_connection_still_valid (NMSettingsConnection *self);
+
+const char *nm_settings_connection_get_filename (NMSettingsConnection *self);
 
 guint64 nm_settings_connection_get_last_secret_agent_version_id (NMSettingsConnection *self);
 
@@ -147,34 +244,18 @@ gboolean nm_settings_connection_has_unmodified_applied_connection (NMSettingsCon
                                                                    NMConnection *applied_connection,
                                                                    NMSettingCompareFlags compare_flage);
 
-typedef enum {
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
-
-	/* like KEEP, but always clears the UNSAVED flag */
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
-
-	/* unsaved, only sets the unsaved flag, but it doesn't touch
-	 * the NM_GENERATED nor VOLATILE flag. */
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED,
-
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_DETACHED,
-	NM_SETTINGS_CONNECTION_PERSIST_MODE_VOLATILE_ONLY,
-} NMSettingsConnectionPersistMode;
-
-gboolean  nm_settings_connection_update (NMSettingsConnection *self,
-                                         NMConnection *new_connection,
-                                         NMSettingsConnectionPersistMode persist_mode,
-                                         NMSettingsConnectionCommitReason commit_reason,
-                                         const char *log_diff_name,
-                                         GError **error);
-
-gboolean nm_settings_connection_delete (NMSettingsConnection *self,
+gboolean nm_settings_connection_update (NMSettingsConnection *self,
+                                        NMConnection *new_connection,
+                                        NMSettingsConnectionPersistMode persist_mode,
+                                        NMSettingsConnectionIntFlags sett_flags,
+                                        NMSettingsConnectionIntFlags sett_mask,
+                                        NMSettingsConnectionUpdateReason update_reason,
+                                        const char *log_context_name,
                                         GError **error);
 
+void nm_settings_connection_delete (NMSettingsConnection *self,
+                                    gboolean allow_add_to_no_auto_default);
+
 typedef void (*NMSettingsConnectionSecretsFunc) (NMSettingsConnection *self,
                                                  NMSettingsConnectionCallId *call_id,
                                                  const char *agent_username,
@@ -200,36 +281,62 @@ NMSettingsConnectionCallId *nm_settings_connection_get_secrets (NMSettingsConnec
 void nm_settings_connection_cancel_secrets (NMSettingsConnection *self,
                                             NMSettingsConnectionCallId *call_id);
 
-void nm_settings_connection_recheck_visibility (NMSettingsConnection *self);
+void nm_settings_connection_clear_secrets (NMSettingsConnection *self,
+                                           gboolean clear_cached_system_secrets,
+                                           gboolean persist);
+
+gboolean nm_settings_connection_check_visibility (NMSettingsConnection *self,
+                                                  NMSessionMonitor *session_monitor);
 
 gboolean nm_settings_connection_check_permission (NMSettingsConnection *self,
                                                   const char *permission);
 
-void nm_settings_connection_added (NMSettingsConnection *self);
+/*****************************************************************************/
 
-void nm_settings_connection_signal_remove (NMSettingsConnection *self);
+NMDevice *nm_settings_connection_default_wired_get_device (NMSettingsConnection *self);
+void      nm_settings_connection_default_wired_set_device (NMSettingsConnection *self,
+                                                           NMDevice *device);
 
-gboolean nm_settings_connection_get_unsaved (NMSettingsConnection *self);
+/*****************************************************************************/
 
 NMSettingsConnectionIntFlags nm_settings_connection_get_flags (NMSettingsConnection *self);
-NMSettingsConnectionIntFlags nm_settings_connection_set_flags (NMSettingsConnection *self, NMSettingsConnectionIntFlags flags, gboolean set);
+
+static inline gboolean
+nm_settings_connection_get_unsaved (NMSettingsConnection *self)
+{
+	return NM_FLAGS_HAS (nm_settings_connection_get_flags (self), NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED);
+}
+
 NMSettingsConnectionIntFlags nm_settings_connection_set_flags_full (NMSettingsConnection *self, NMSettingsConnectionIntFlags mask, NMSettingsConnectionIntFlags value);
 
+static inline NMSettingsConnectionIntFlags
+nm_settings_connection_set_flags (NMSettingsConnection *self, NMSettingsConnectionIntFlags flags, gboolean set)
+{
+	return nm_settings_connection_set_flags_full (self,
+	                                              flags,
+	                                              set ? flags : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE);
+}
+
+/*****************************************************************************/
+
 int nm_settings_connection_cmp_timestamp (NMSettingsConnection *ac, NMSettingsConnection *ab);
 int nm_settings_connection_cmp_timestamp_p_with_data (gconstpointer pa, gconstpointer pb, gpointer user_data);
 int nm_settings_connection_cmp_autoconnect_priority (NMSettingsConnection *a, NMSettingsConnection *b);
 int nm_settings_connection_cmp_autoconnect_priority_p_with_data (gconstpointer pa, gconstpointer pb, gpointer user_data);
 
+struct _NMKeyFileDB;
+
+void _nm_settings_connection_register_kf_dbs (NMSettingsConnection *self,
+                                              struct _NMKeyFileDB *kf_db_timestamps,
+                                              struct _NMKeyFileDB *kf_db_seen_bssids);
+
 gboolean nm_settings_connection_get_timestamp (NMSettingsConnection *self,
                                                guint64 *out_timestamp);
 
 void nm_settings_connection_update_timestamp (NMSettingsConnection *self,
-                                              guint64 timestamp,
-                                              gboolean flush_to_disk);
-
-void nm_settings_connection_read_and_fill_timestamp (NMSettingsConnection *self);
+                                              guint64 timestamp);
 
-char **nm_settings_connection_get_seen_bssids (NMSettingsConnection *self);
+const char **nm_settings_connection_get_seen_bssids (NMSettingsConnection *self);
 
 gboolean nm_settings_connection_has_seen_bssid (NMSettingsConnection *self,
                                                 const char *bssid);
@@ -237,8 +344,6 @@ gboolean nm_settings_connection_has_seen_bssid (NMSettingsConnection *self,
 void nm_settings_connection_add_seen_bssid (NMSettingsConnection *self,
                                             const char *seen_bssid);
 
-void nm_settings_connection_read_and_fill_seen_bssids (NMSettingsConnection *self);
-
 int nm_settings_connection_autoconnect_retries_get (NMSettingsConnection *self);
 void nm_settings_connection_autoconnect_retries_set (NMSettingsConnection *self,
                                                      int retries);
@@ -262,14 +367,6 @@ nm_settings_connection_autoconnect_blocked_reason_set (NMSettingsConnection *sel
 
 gboolean nm_settings_connection_autoconnect_is_blocked (NMSettingsConnection *self);
 
-gboolean nm_settings_connection_get_ready (NMSettingsConnection *self);
-void     nm_settings_connection_set_ready (NMSettingsConnection *self,
-                                           gboolean ready);
-
-void        nm_settings_connection_set_filename (NMSettingsConnection *self,
-                                                 const char *filename);
-const char *nm_settings_connection_get_filename (NMSettingsConnection *self);
-
 const char *nm_settings_connection_get_id              (NMSettingsConnection *connection);
 const char *nm_settings_connection_get_uuid            (NMSettingsConnection *connection);
 const char *nm_settings_connection_get_connection_type (NMSettingsConnection *connection);
@@ -279,4 +376,14 @@ const char *nm_settings_connection_get_connection_type (NMSettingsConnection *co
 NMConnection **nm_settings_connections_array_to_connections (NMSettingsConnection *const*connections,
                                                              gssize n_connections);
 
+/*****************************************************************************/
+
+void _nm_settings_connection_emit_dbus_signal_updated (NMSettingsConnection *self);
+void _nm_settings_connection_emit_dbus_signal_removed (NMSettingsConnection *self);
+
+void _nm_settings_connection_emit_signal_updated_internal (NMSettingsConnection *self,
+                                                           NMSettingsConnectionUpdateReason update_reason);
+
+void _nm_settings_connection_cleanup_after_remove (NMSettingsConnection *self);
+
 #endif /* __NETWORKMANAGER_SETTINGS_CONNECTION_H__ */
diff --git a/src/settings/nm-settings-plugin.c b/src/settings/nm-settings-plugin.c
index e7275631..09010931 100644
--- a/src/settings/nm-settings-plugin.c
+++ b/src/settings/nm-settings-plugin.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -23,12 +22,14 @@
 
 #include "nm-settings-plugin.h"
 
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+
 #include "nm-settings-connection.h"
 
 /*****************************************************************************/
 
 enum {
-	CONNECTION_ADDED,
 	UNMANAGED_SPECS_CHANGED,
 	UNRECOGNIZED_SPECS_CHANGED,
 
@@ -41,114 +42,228 @@ G_DEFINE_TYPE (NMSettingsPlugin, nm_settings_plugin, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-void
-nm_settings_plugin_initialize (NMSettingsPlugin *self)
+int
+nm_settings_plugin_cmp_by_priority (const NMSettingsPlugin *a,
+                                    const NMSettingsPlugin *b,
+                                    const GSList *plugin_list)
 {
-	g_return_if_fail (NM_IS_SETTINGS_PLUGIN (self));
+	nm_assert (NM_IS_SETTINGS_PLUGIN (a));
+	nm_assert (NM_IS_SETTINGS_PLUGIN (b));
 
-	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->initialize)
-		NM_SETTINGS_PLUGIN_GET_CLASS (self)->initialize (self);
+	if (a != b) {
+		int idx_a = g_slist_index ((GSList *) plugin_list, a);
+		int idx_b = g_slist_index ((GSList *) plugin_list, b);
+
+		/* the plugins must be found in the list. */
+		nm_assert (idx_a >= 0);
+		nm_assert (idx_b >= 0);
+
+		/* plugins that appear first in @plugin_list have higher priority.
+		 * That means: smaller index -> higher priority. Reverse sort. */
+		NM_CMP_DIRECT (idx_b, idx_a);
+	}
+
+	return 0;
 }
 
+/*****************************************************************************/
+
 GSList *
-nm_settings_plugin_get_connections (NMSettingsPlugin *self)
+nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *self)
 {
+	NMSettingsPluginClass *klass;
+
 	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
 
-	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_connections)
-		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_connections (self);
-	return NULL;
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+	if (!klass->get_unmanaged_specs)
+		return NULL;
+	return klass->get_unmanaged_specs (self);
 }
 
-gboolean
-nm_settings_plugin_load_connection (NMSettingsPlugin *self,
-                                    const char *filename)
+GSList *
+nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *self)
 {
-	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), FALSE);
+	NMSettingsPluginClass *klass;
 
-	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->load_connection)
-		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->load_connection (self, filename);
-	return FALSE;
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
+
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+	if (!klass->get_unrecognized_specs)
+		return NULL;
+	return klass->get_unrecognized_specs (self);
 }
 
 void
-nm_settings_plugin_reload_connections (NMSettingsPlugin *self)
+nm_settings_plugin_reload_connections (NMSettingsPlugin *self,
+                                       NMSettingsPluginConnectionLoadCallback callback,
+                                       gpointer user_data)
 {
+	NMSettingsPluginClass *klass;
+
 	g_return_if_fail (NM_IS_SETTINGS_PLUGIN (self));
+	g_return_if_fail (callback);
 
-	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->reload_connections)
-		NM_SETTINGS_PLUGIN_GET_CLASS (self)->reload_connections (self);
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+	if (klass->reload_connections)
+		klass->reload_connections (self, callback, user_data);
 }
 
-GSList *
-nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *self)
+NMSettingsPluginConnectionLoadEntry *
+nm_settings_plugin_create_connection_load_entries (const char *const*filenames,
+                                                   gsize *out_len)
 {
-	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
+	NMSettingsPluginConnectionLoadEntry *entries;
+	gsize len;
+	gsize i;
+
+	len = NM_PTRARRAY_LEN (filenames);
+	if (len == 0) {
+		*out_len = 0;
+		return NULL;
+	}
+
+	entries = g_new (NMSettingsPluginConnectionLoadEntry, len);
+	for (i = 0; i < len; i++) {
+		entries[i] = (NMSettingsPluginConnectionLoadEntry) {
+			.filename = filenames[i],
+			.error    = NULL,
+			.handled  = FALSE,
+		};
+	}
 
-	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unmanaged_specs)
-		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unmanaged_specs (self);
-	return NULL;
+	*out_len = len;
+	return entries;
 }
 
-GSList *
-nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *self)
+void
+nm_settings_plugin_load_connections (NMSettingsPlugin *self,
+                                     NMSettingsPluginConnectionLoadEntry *entries,
+                                     gsize n_entries,
+                                     NMSettingsPluginConnectionLoadCallback callback,
+                                     gpointer user_data)
 {
-	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
+	NMSettingsPluginClass *klass;
 
-	if (NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unrecognized_specs)
-		return NM_SETTINGS_PLUGIN_GET_CLASS (self)->get_unrecognized_specs (self);
-	return NULL;
+	g_return_if_fail (NM_IS_SETTINGS_PLUGIN (self));
+
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+	if (klass->load_connections)
+		klass->load_connections (self, entries, n_entries, callback, user_data);
 }
 
-/**
- * nm_settings_plugin_add_connection:
- * @self: the #NMSettingsPlugin
- * @connection: the source connection to create a plugin-specific
- * #NMSettingsConnection from
- * @save_to_disk: %TRUE to save the connection to disk immediately, %FALSE to
- * not save to disk
- * @error: on return, a location to store any errors that may occur
- *
- * Creates a new #NMSettingsConnection for the given source @connection.  If the
- * plugin cannot handle the given connection type, it should return %NULL and
- * set @error.  The plugin owns the returned object and the caller must reference
- * the object if it wishes to continue using it.
- *
- * Returns: the new #NMSettingsConnection or %NULL
- */
-NMSettingsConnection *
+void
+nm_settings_plugin_load_connections_done (NMSettingsPlugin *self)
+{
+	NMSettingsPluginClass *klass;
+
+	g_return_if_fail (NM_IS_SETTINGS_PLUGIN (self));
+
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+	if (klass->load_connections_done)
+		klass->load_connections_done (self);
+}
+
+gboolean
 nm_settings_plugin_add_connection (NMSettingsPlugin *self,
                                    NMConnection *connection,
-                                   gboolean save_to_disk,
+                                   NMSettingsStorage **out_storage,
+                                   NMConnection **out_connection,
                                    GError **error)
 {
 	NMSettingsPluginClass *klass;
 
-	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), NULL);
-	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+#if NM_MORE_ASSERTS > 5
+	nm_assert (nm_connection_verify (connection, NULL));
+#endif
+
+	NM_SET_OUT (out_storage, NULL);
+	NM_SET_OUT (out_connection, NULL);
 
 	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
 	if (!klass->add_connection) {
 		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_NOT_SUPPORTED,
-		                     "Plugin does not support adding connections");
-		return NULL;
+		                     "settings plugin does not support adding connections");
+		return FALSE;
 	}
-
-	return klass->add_connection (self, connection, save_to_disk, error);
+	return klass->add_connection (self,
+	                              connection,
+	                              out_storage,
+	                              out_connection,
+	                              error);
 }
 
-/*****************************************************************************/
+gboolean
+nm_settings_plugin_update_connection (NMSettingsPlugin *self,
+                                      NMSettingsStorage *storage,
+                                      NMConnection *connection,
+                                      NMSettingsStorage **out_storage,
+                                      NMConnection **out_connection,
+                                      GError **error)
+{
+	NMSettingsPluginClass *klass = NULL;
 
-void
-_nm_settings_plugin_emit_signal_connection_added (NMSettingsPlugin *self,
-                                                  NMSettingsConnection *sett_conn)
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (storage), FALSE);
+	g_return_val_if_fail (nm_settings_storage_get_plugin (storage) == self, FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+#if NM_MORE_ASSERTS > 5
+	nm_assert (nm_connection_verify (connection, NULL));
+	nm_assert (nm_streq (nm_connection_get_uuid (connection), nm_settings_storage_get_uuid (storage)));
+#endif
+
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+
+	NM_SET_OUT (out_storage, NULL);
+	NM_SET_OUT (out_connection, NULL);
+
+	if (!klass->update_connection) {
+		g_set_error (error,
+		             NM_SETTINGS_ERROR,
+		             NM_SETTINGS_ERROR_NOT_SUPPORTED,
+		             "settings plugin does not support modifying connections");
+		return FALSE;
+	}
+	return klass->update_connection (self,
+	                                 storage,
+	                                 connection,
+	                                 out_storage,
+	                                 out_connection,
+	                                 error);
+}
+
+gboolean
+nm_settings_plugin_delete_connection (NMSettingsPlugin *self,
+                                      NMSettingsStorage *storage,
+                                      GError **error)
 {
-	nm_assert (NM_IS_SETTINGS_PLUGIN (self));
-	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	NMSettingsPluginClass *klass = NULL;
+
+	g_return_val_if_fail (NM_IS_SETTINGS_PLUGIN (self), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (storage), FALSE);
+	g_return_val_if_fail (nm_settings_storage_get_plugin (storage) == self, FALSE);
+
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
 
-	g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
+	if (!klass->delete_connection) {
+		g_set_error (error,
+		             NM_SETTINGS_ERROR,
+		             NM_SETTINGS_ERROR_NOT_SUPPORTED,
+		             "settings plugin does not support deleting connections");
+		return FALSE;
+	}
+
+	return klass->delete_connection (self,
+	                                 storage,
+	                                 error);
 }
 
+/*****************************************************************************/
+
 void
 _nm_settings_plugin_emit_signal_unmanaged_specs_changed (NMSettingsPlugin *self)
 {
@@ -177,15 +292,6 @@ nm_settings_plugin_class_init (NMSettingsPluginClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-	signals[CONNECTION_ADDED] =
-	    g_signal_new (NM_SETTINGS_PLUGIN_CONNECTION_ADDED,
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_FIRST,
-	                  0, NULL, NULL,
-	                  g_cclosure_marshal_VOID__OBJECT,
-	                  G_TYPE_NONE, 1,
-	                  NM_TYPE_SETTINGS_CONNECTION);
-
 	signals[UNMANAGED_SPECS_CHANGED] =
 	    g_signal_new (NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
 	                  G_OBJECT_CLASS_TYPE (object_class),
diff --git a/src/settings/nm-settings-plugin.h b/src/settings/nm-settings-plugin.h
index fdd48f2b..4df3472e 100644
--- a/src/settings/nm-settings-plugin.h
+++ b/src/settings/nm-settings-plugin.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -24,6 +23,21 @@
 
 #include "nm-connection.h"
 
+#include "nm-settings-storage.h"
+
+typedef struct _NMSettingsPlugin NMSettingsPlugin;
+
+typedef void (*NMSettingsPluginConnectionLoadCallback) (NMSettingsPlugin *self,
+                                                        NMSettingsStorage *storage,
+                                                        NMConnection *connection,
+                                                        gpointer user_data);
+
+typedef struct {
+	const char *filename;
+	GError *error;
+	bool handled:1;
+} NMSettingsPluginConnectionLoadEntry;
+
 #define NM_TYPE_SETTINGS_PLUGIN               (nm_settings_plugin_get_type ())
 #define NM_SETTINGS_PLUGIN(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPlugin))
 #define NM_SETTINGS_PLUGIN_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTINGS_PLUGIN, NMSettingsPluginClass))
@@ -33,35 +47,14 @@
 
 #define NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED    "unmanaged-specs-changed"
 #define NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED "unrecognized-specs-changed"
-#define NM_SETTINGS_PLUGIN_CONNECTION_ADDED           "connection-added"
 
-typedef struct {
+struct _NMSettingsPlugin {
 	GObject parent;
-} NMSettingsPlugin;
+};
 
 typedef struct {
 	GObjectClass parent;
 
-	/* Called when the plugin is loaded to initialize it */
-	void (*initialize) (NMSettingsPlugin *plugin);
-
-	/* Returns a GSList of NMSettingsConnection objects that represent
-	 * connections the plugin knows about.  The returned list is freed by the
-	 * system settings service.
-	 */
-	GSList * (*get_connections) (NMSettingsPlugin *plugin);
-
-	/* Requests that the plugin load/reload a single connection, if it
-	 * recognizes the filename. Returns success or failure.
-	 */
-	gboolean (*load_connection) (NMSettingsPlugin *plugin,
-	                             const char *filename);
-
-	/* Requests that the plugin reload all connection files from disk,
-	 * and emit signals reflecting new, changed, and removed connections.
-	 */
-	void (*reload_connections) (NMSettingsPlugin *plugin);
-
 	/*
 	 * Return a string list of specifications of devices which NetworkManager
 	 * should not manage.  Returned list will be freed by the system settings
@@ -71,7 +64,7 @@ typedef struct {
 	 * Each string in the list must be in one of the formats recognized by
 	 * nm_device_spec_match_list().
 	 */
-	GSList * (*get_unmanaged_specs) (NMSettingsPlugin *plugin);
+	GSList * (*get_unmanaged_specs) (NMSettingsPlugin *self);
 
 	/*
 	 * Return a string list of specifications of devices for which at least
@@ -83,51 +76,137 @@ typedef struct {
 	 * Each string in the list must be in one of the formats recognized by
 	 * nm_device_spec_match_list().
 	 */
-	GSList * (*get_unrecognized_specs) (NMSettingsPlugin *plugin);
+	GSList * (*get_unrecognized_specs) (NMSettingsPlugin *self);
 
-	/*
-	 * Initialize the plugin-specific connection and return a new
-	 * NMSettingsConnection subclass that contains the same settings as the
-	 * original connection.  The connection should only be saved to backing
-	 * storage if @save_to_disk is TRUE.  The returned object is owned by the
-	 * plugin and must be referenced by the owner if necessary.
+	/* Requests that the plugin load/reload a set of filenames.
 	 */
-	NMSettingsConnection * (*add_connection) (NMSettingsPlugin *plugin,
-	                                          NMConnection *connection,
-	                                          gboolean save_to_disk,
-	                                          GError **error);
+	void (*load_connections) (NMSettingsPlugin *self,
+	                          NMSettingsPluginConnectionLoadEntry *entries,
+	                          gsize n_entries,
+	                          NMSettingsPluginConnectionLoadCallback callback,
+	                          gpointer user_data);
+
+	/* Requests that the plugin reload all connection files from disk,
+	 * and emit signals reflecting new, changed, and removed connections.
+	 */
+	void (*reload_connections) (NMSettingsPlugin *self,
+	                            NMSettingsPluginConnectionLoadCallback callback,
+	                            gpointer user_data);
+
+	void (*load_connections_done) (NMSettingsPlugin *self);
+
+	gboolean (*add_connection) (NMSettingsPlugin *self,
+	                            NMConnection *connection,
+	                            NMSettingsStorage **out_storage,
+	                            NMConnection **out_connection,
+	                            GError **error);
+
+	gboolean (*update_connection) (NMSettingsPlugin *self,
+	                               NMSettingsStorage *storage,
+	                               NMConnection *connection,
+	                               NMSettingsStorage **out_storage,
+	                               NMConnection **out_connection,
+	                               GError **error);
+
+	gboolean (*delete_connection) (NMSettingsPlugin *self,
+	                               NMSettingsStorage *storage,
+	                               GError **error);
+
+	const char *plugin_name;
+
 } NMSettingsPluginClass;
 
+/*****************************************************************************/
+
 GType nm_settings_plugin_get_type (void);
 
+/*****************************************************************************/
+
+#define NM_SETTINGS_STORAGE_PRINT_FMT \
+	NM_HASH_OBFUSCATE_PTR_FMT"/%s"
+
+#define NM_SETTINGS_STORAGE_PRINT_ARG(storage)  \
+	NM_HASH_OBFUSCATE_PTR (storage), \
+	nm_settings_plugin_get_plugin_name (nm_settings_storage_get_plugin (storage))
+
+static inline const char *
+nm_settings_plugin_get_plugin_name (NMSettingsPlugin *self)
+{
+	NMSettingsPluginClass *klass;
+
+	nm_assert (NM_SETTINGS_PLUGIN (self));
+
+	klass = NM_SETTINGS_PLUGIN_GET_CLASS (self);
+
+	nm_assert (klass && klass->plugin_name && strlen (klass->plugin_name) > 0);
+
+	return klass->plugin_name;
+}
+
+/*****************************************************************************/
+
+GSList *nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *self);
+GSList *nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *self);
+
+void nm_settings_plugin_reload_connections (NMSettingsPlugin *self,
+                                            NMSettingsPluginConnectionLoadCallback callback,
+                                            gpointer user_data);
+
+NMSettingsPluginConnectionLoadEntry *nm_settings_plugin_create_connection_load_entries (const char *const*filenames,
+                                                                                        gsize *out_len);
+
+void nm_settings_plugin_load_connections (NMSettingsPlugin *self,
+                                          NMSettingsPluginConnectionLoadEntry *entries,
+                                          gsize n_entries,
+                                          NMSettingsPluginConnectionLoadCallback callback,
+                                          gpointer user_data);
+
+void nm_settings_plugin_load_connections_done (NMSettingsPlugin *self);
+
+gboolean nm_settings_plugin_add_connection (NMSettingsPlugin *self,
+                                            NMConnection *connection,
+                                            NMSettingsStorage **out_storage,
+                                            NMConnection **out_connection,
+                                            GError **error);
+
+gboolean nm_settings_plugin_update_connection (NMSettingsPlugin *self,
+                                               NMSettingsStorage *storage,
+                                               NMConnection *connection,
+                                               NMSettingsStorage **out_storage,
+                                               NMConnection **out_connection,
+                                               GError **error);
+
+gboolean nm_settings_plugin_delete_connection (NMSettingsPlugin *self,
+                                               NMSettingsStorage *storage,
+                                               GError **error);
+
+/*****************************************************************************/
+
 typedef NMSettingsPlugin *(*NMSettingsPluginFactoryFunc) (void);
 
-/* Plugin's factory function that returns a #NMSettingsPlugin */
 NMSettingsPlugin *nm_settings_plugin_factory (void);
 
-void nm_settings_plugin_initialize (NMSettingsPlugin *config);
-
-GSList *nm_settings_plugin_get_connections (NMSettingsPlugin *plugin);
+/*****************************************************************************
+ * Internal API
+ *****************************************************************************/
 
-gboolean nm_settings_plugin_load_connection (NMSettingsPlugin *plugin,
-                                             const char *filename);
-void nm_settings_plugin_reload_connections (NMSettingsPlugin *plugin);
+void _nm_settings_plugin_emit_signal_unmanaged_specs_changed (NMSettingsPlugin *self);
 
-GSList *nm_settings_plugin_get_unmanaged_specs (NMSettingsPlugin *plugin);
-GSList *nm_settings_plugin_get_unrecognized_specs (NMSettingsPlugin *plugin);
+void _nm_settings_plugin_emit_signal_unrecognized_specs_changed (NMSettingsPlugin *self);
 
-NMSettingsConnection *nm_settings_plugin_add_connection (NMSettingsPlugin *plugin,
-                                                         NMConnection *connection,
-                                                         gboolean save_to_disk,
-                                                         GError **error);
+/*****************************************************************************/
 
-/* internal API */
+int nm_settings_plugin_cmp_by_priority (const NMSettingsPlugin *a,
+                                        const NMSettingsPlugin *b,
+                                        const GSList *plugin_list);
 
-void _nm_settings_plugin_emit_signal_connection_added (NMSettingsPlugin *plugin,
-                                                       NMSettingsConnection *sett_conn);
+/*****************************************************************************/
 
-void _nm_settings_plugin_emit_signal_unmanaged_specs_changed (NMSettingsPlugin *plugin);
+/* forward declare this function from NMSettings. It's used by the ifcfg-rh plugin,
+ * but that shouldn't include all "nm-settings.h" header. */
+NMSettings *nm_settings_get (void);
 
-void _nm_settings_plugin_emit_signal_unrecognized_specs_changed (NMSettingsPlugin *plugin);
+const char *nm_settings_get_dbus_path_for_uuid (NMSettings *self,
+                                                const char *uuid);
 
 #endif /* __NM_SETTINGS_PLUGIN_H__ */
diff --git a/src/settings/nm-settings-storage.c b/src/settings/nm-settings-storage.c
new file mode 100644
index 00000000..935b5b48
--- /dev/null
+++ b/src/settings/nm-settings-storage.c
@@ -0,0 +1,178 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-settings-storage.h"
+
+#include "nm-utils.h"
+#include "nm-settings-plugin.h"
+
+#include "settings/plugins/keyfile/nms-keyfile-storage.h"
+
+/*****************************************************************************/
+
+int
+nm_settings_storage_cmp (NMSettingsStorage *a,
+                         NMSettingsStorage *b,
+                         const GSList *plugin_list)
+{
+	NMSettingsStorageClass *klass;
+
+	/* Sort by priority.
+	 *
+	 * If a > b (by priority), we return a positive number (as one
+	 * would expect by a cmp() function). */
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (a));
+	nm_assert (NM_IS_SETTINGS_STORAGE (b));
+	nm_assert (a != b);
+	nm_assert (nm_streq (nm_settings_storage_get_uuid (a), nm_settings_storage_get_uuid (b)));
+
+	/* in-memory has always higher priority */
+	NM_CMP_DIRECT (nm_settings_storage_is_keyfile_run (a),
+	               nm_settings_storage_is_keyfile_run (b));
+
+	NM_CMP_RETURN (nm_settings_plugin_cmp_by_priority (nm_settings_storage_get_plugin (a),
+	                                                   nm_settings_storage_get_plugin (b),
+	                                                   plugin_list));
+
+	klass = NM_SETTINGS_STORAGE_GET_CLASS (a);
+	if (klass != NM_SETTINGS_STORAGE_GET_CLASS (b)) {
+		/* one plugin must return storages of the same type. Otherwise, it's
+		 * unclear how cmp_fcn() should compare them. */
+		nm_assert_not_reached ();
+		return 0;
+	}
+
+	if (klass->cmp_fcn)
+		NM_CMP_RETURN (klass->cmp_fcn (a, b));
+
+	return 0;
+}
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE (
+	PROP_PLUGIN,
+	PROP_UUID,
+	PROP_FILENAME,
+);
+
+G_DEFINE_TYPE (NMSettingsStorage, nm_settings_storage, G_TYPE_OBJECT)
+
+/*****************************************************************************/
+
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMSettingsStorage *self = NM_SETTINGS_STORAGE (object);
+
+	switch (prop_id) {
+	case PROP_PLUGIN:
+		/* construct-only */
+		self->_plugin = g_object_ref (g_value_get_object (value));
+		nm_assert (NM_IS_SETTINGS_PLUGIN (self->_plugin));
+		break;
+	case PROP_UUID:
+		/* construct-only */
+		self->_uuid = g_value_dup_string (value);
+		nm_assert (!self->_uuid || nm_utils_is_uuid (self->_uuid));
+		break;
+	case PROP_FILENAME:
+		/* construct-only */
+		self->_filename = g_value_dup_string (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+/*****************************************************************************/
+
+static void
+nm_settings_storage_init (NMSettingsStorage *self)
+{
+	c_list_init (&self->_storage_lst);
+	c_list_init (&self->_storage_by_uuid_lst);
+}
+
+NMSettingsStorage *
+nm_settings_storage_new (NMSettingsPlugin *plugin,
+                         const char *uuid,
+                         const char *filename)
+{
+	nm_assert (NM_IS_SETTINGS_PLUGIN (plugin));
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	return g_object_new (NM_TYPE_SETTINGS_STORAGE,
+	                     NM_SETTINGS_STORAGE_PLUGIN, plugin,
+	                     NM_SETTINGS_STORAGE_UUID, uuid,
+	                     NM_SETTINGS_STORAGE_FILENAME, filename,
+	                     NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMSettingsStorage *self = NM_SETTINGS_STORAGE (object);
+
+	c_list_unlink_stale (&self->_storage_lst);
+	c_list_unlink_stale (&self->_storage_by_uuid_lst);
+
+	g_object_unref (self->_plugin);
+	g_free (self->_uuid);
+	g_free (self->_filename);
+
+	G_OBJECT_CLASS (nm_settings_storage_parent_class)->finalize (object);
+}
+
+static void
+nm_settings_storage_class_init (NMSettingsStorageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->set_property = set_property;
+	object_class->finalize     = finalize;
+
+	obj_properties[PROP_PLUGIN] =
+	    g_param_spec_object (NM_SETTINGS_STORAGE_PLUGIN, "", "",
+	                         NM_TYPE_SETTINGS_PLUGIN,
+	                         G_PARAM_WRITABLE |
+	                         G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_UUID] =
+	    g_param_spec_string (NM_SETTINGS_STORAGE_UUID, "", "",
+	                         NULL,
+	                         G_PARAM_WRITABLE |
+	                         G_PARAM_CONSTRUCT_ONLY |
+	                         G_PARAM_STATIC_STRINGS);
+
+	obj_properties[PROP_FILENAME] =
+	    g_param_spec_string (NM_SETTINGS_STORAGE_FILENAME, "", "",
+	                         NULL,
+	                         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/settings/nm-settings-storage.h b/src/settings/nm-settings-storage.h
new file mode 100644
index 00000000..c43145b5
--- /dev/null
+++ b/src/settings/nm-settings-storage.h
@@ -0,0 +1,122 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTINGS_STORAGE_H__
+#define __NM_SETTINGS_STORAGE_H__
+
+/*****************************************************************************/
+
+#include "c-list/src/c-list.h"
+
+#define NM_TYPE_SETTINGS_STORAGE            (nm_settings_storage_get_type ())
+#define NM_SETTINGS_STORAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorage))
+#define NM_SETTINGS_STORAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorageClass))
+#define NM_IS_SETTINGS_STORAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_SETTINGS_STORAGE))
+#define NM_IS_SETTINGS_STORAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_SETTINGS_STORAGE))
+#define NM_SETTINGS_STORAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_SETTINGS_STORAGE, NMSettingsStorageClass))
+
+#define NM_SETTINGS_STORAGE_PLUGIN   "plugin"
+#define NM_SETTINGS_STORAGE_UUID     "uuid"
+#define NM_SETTINGS_STORAGE_FILENAME "filename"
+
+struct _NMSettingsPlugin;
+
+typedef struct NMSettingsStorage {
+	GObject parent;
+	struct _NMSettingsPlugin *_plugin;
+	char *_uuid;
+	char *_filename;
+	CList _storage_lst;
+	CList _storage_by_uuid_lst;
+} NMSettingsStorage;
+
+typedef struct {
+	GObjectClass parent;
+
+	int (*cmp_fcn) (NMSettingsStorage *a,
+	                NMSettingsStorage *b);
+
+} NMSettingsStorageClass;
+
+GType nm_settings_storage_get_type (void);
+
+NMSettingsStorage *nm_settings_storage_new (struct _NMSettingsPlugin *plugin,
+                                            const char *uuid,
+                                            const char *filename);
+
+static inline struct _NMSettingsPlugin *
+nm_settings_storage_get_plugin (const NMSettingsStorage *self)
+{
+	GType nm_settings_plugin_get_type (void);
+
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (self), NULL);
+
+	nm_assert (G_TYPE_CHECK_INSTANCE_TYPE (self->_plugin, nm_settings_plugin_get_type ()));
+	return self->_plugin;
+}
+
+static inline const char *
+nm_settings_storage_get_uuid (const NMSettingsStorage *self)
+{
+	gboolean nm_utils_is_uuid (const char *str);
+
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (self), NULL);
+
+	nm_assert (nm_utils_is_uuid (self->_uuid));
+	return self->_uuid;
+}
+
+static inline const char *
+nm_settings_storage_get_uuid_opt (const NMSettingsStorage *self)
+{
+	gboolean nm_utils_is_uuid (const char *str);
+
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (self), NULL);
+
+	nm_assert (!self->_uuid || nm_utils_is_uuid (self->_uuid));
+	return self->_uuid;
+}
+
+static inline const char *
+nm_settings_storage_get_filename (const NMSettingsStorage *self)
+{
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (self), NULL);
+
+	return self->_filename;
+}
+
+/*****************************************************************************/
+
+#define nm_assert_valid_settings_storage(plugin, storage) \
+	G_STMT_START { \
+		NMSettingsPlugin *const _plugin = (plugin); \
+		NMSettingsStorage *const _storage = (storage); \
+		\
+		nm_assert (!_plugin || NM_IS_SETTINGS_PLUGIN (_plugin)); \
+		nm_assert (NM_IS_SETTINGS_STORAGE (_storage)); \
+		nm_assert (!_plugin || nm_settings_storage_get_plugin (_storage) == _plugin); \
+	} G_STMT_END
+
+/*****************************************************************************/
+
+int nm_settings_storage_cmp (NMSettingsStorage *sd_a,
+                             NMSettingsStorage *sd_b,
+                             const GSList *plugin_list);
+
+#endif /* __NM_SETTINGS_STORAGE_H__ */
diff --git a/src/settings/nm-settings-utils.c b/src/settings/nm-settings-utils.c
new file mode 100644
index 00000000..0d636537
--- /dev/null
+++ b/src/settings/nm-settings-utils.c
@@ -0,0 +1,175 @@
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * 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) 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-settings-utils.h"
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "nm-settings-plugin.h"
+
+/*****************************************************************************/
+
+const struct timespec *
+nm_sett_util_stat_mtime (const char *filename,
+                         gboolean do_lstat,
+                         struct timespec *out_val)
+{
+	struct stat st;
+	struct timeval now_tv;
+
+	if (filename) {
+		if (do_lstat) {
+			if (lstat (filename, &st) == 0) {
+				*out_val = st.st_mtim;
+				return out_val;
+			}
+		} else {
+			if (stat (filename, &st) == 0) {
+				*out_val = st.st_mtim;
+				return out_val;
+			}
+		}
+	}
+
+	if (gettimeofday (&now_tv, NULL) == 0) {
+		*out_val = (struct timespec) {
+			.tv_sec  = now_tv.tv_sec,
+			.tv_nsec = now_tv.tv_usec * 1000u,
+		};
+		return out_val;
+	}
+
+	*out_val = (struct timespec) { };
+	return out_val;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_sett_util_allow_filename_cb (const char *filename,
+                                gpointer user_data)
+{
+	const NMSettUtilAllowFilenameData *allow_filename_data = user_data;
+
+	if (   allow_filename_data->allowed_filename
+	    && nm_streq (allow_filename_data->allowed_filename, filename))
+		return TRUE;
+
+	return !g_hash_table_contains (allow_filename_data->idx_by_filename, filename);
+}
+
+/*****************************************************************************/
+
+void
+nm_sett_util_storage_by_uuid_head_destroy (NMSettUtilStorageByUuidHead *sbuh)
+{
+	CList *iter;
+
+	while ((iter = c_list_first (&sbuh->_storage_by_uuid_lst_head)))
+		c_list_unlink (iter);
+	g_free (sbuh);
+}
+
+/*****************************************************************************/
+
+void
+nm_sett_util_storages_clear (NMSettUtilStorages *storages)
+{
+	nm_clear_pointer (&storages->idx_by_uuid, g_hash_table_destroy);
+	nm_clear_pointer (&storages->idx_by_filename, g_hash_table_destroy);
+	nm_assert (c_list_is_empty (&storages->_storage_lst_head));
+}
+
+void
+nm_sett_util_storages_add_take (NMSettUtilStorages *storages,
+                                gpointer storage_take_p /* NMSettingsStorage *, take reference */)
+{
+	NMSettingsStorage *storage_take = storage_take_p;
+	NMSettUtilStorageByUuidHead *sbuh;
+	const char *uuid;
+
+	nm_assert (storage_take);
+	nm_assert (c_list_is_empty (&storage_take->_storage_lst));
+	nm_assert (c_list_is_empty (&storage_take->_storage_by_uuid_lst));
+	nm_assert (nm_settings_storage_get_filename (storage_take));
+
+	if (!g_hash_table_replace (storages->idx_by_filename,
+	                           (char *) nm_settings_storage_get_filename (storage_take),
+	                           storage_take /* takes ownership of reference. */))
+		nm_assert_not_reached ();
+
+	uuid = nm_settings_storage_get_uuid_opt (storage_take);
+
+	if (uuid) {
+		sbuh = nm_sett_util_storages_lookup_by_uuid (storages, uuid);
+		if (!sbuh) {
+			gsize l = strlen (uuid) + 1;
+
+			sbuh = g_malloc (sizeof (NMSettUtilStorageByUuidHead) + l);
+			sbuh->uuid = sbuh->uuid_data;
+			c_list_init (&sbuh->_storage_by_uuid_lst_head);
+			memcpy (sbuh->uuid_data, uuid, l);
+			g_hash_table_add (storages->idx_by_uuid, sbuh);
+		}
+		c_list_link_tail (&sbuh->_storage_by_uuid_lst_head, &storage_take->_storage_by_uuid_lst);
+	}
+
+	c_list_link_tail (&storages->_storage_lst_head, &storage_take->_storage_lst);
+}
+
+gpointer /* NMSettingsStorage * */
+nm_sett_util_storages_steal (NMSettUtilStorages *storages,
+                             gpointer storage_p /* NMSettingsStorage **/)
+{
+	NMSettingsStorage *storage = storage_p;
+	NMSettUtilStorageByUuidHead *sbuh;
+	const char *uuid;
+
+	nm_assert (storage);
+	nm_assert (nm_sett_util_storages_lookup_by_filename (storages, nm_settings_storage_get_filename (storage)) == storage);
+	nm_assert (c_list_contains (&storages->_storage_lst_head, &storage->_storage_lst));
+
+	uuid = nm_settings_storage_get_uuid_opt (storage);
+
+	if (!uuid) {
+		nm_assert (c_list_is_empty (&storage->_storage_by_uuid_lst));
+	} else {
+		nm_assert (!c_list_is_empty (&storage->_storage_by_uuid_lst));
+
+		sbuh = nm_sett_util_storages_lookup_by_uuid (storages, uuid);
+
+		nm_assert (sbuh);
+		nm_assert (c_list_contains (&sbuh->_storage_by_uuid_lst_head, &storage->_storage_by_uuid_lst));
+		c_list_unlink (&storage->_storage_by_uuid_lst);
+
+		if (c_list_is_empty (&sbuh->_storage_by_uuid_lst_head))
+			g_hash_table_remove (storages->idx_by_uuid, sbuh);
+	}
+
+	c_list_unlink (&storage->_storage_lst);
+
+	g_hash_table_steal (storages->idx_by_filename, nm_settings_storage_get_filename (storage));
+
+	return storage;
+}
diff --git a/src/settings/nm-settings-utils.h b/src/settings/nm-settings-utils.h
new file mode 100644
index 00000000..a2a22dc4
--- /dev/null
+++ b/src/settings/nm-settings-utils.h
@@ -0,0 +1,110 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTINGS_UTILS_H__
+#define __NM_SETTINGS_UTILS_H__
+
+#include "nm-settings-storage.h"
+
+/*****************************************************************************/
+
+struct timespec;
+
+const struct timespec *nm_sett_util_stat_mtime (const char *filename,
+                                                gboolean do_lstat,
+                                                struct timespec *out_val);
+
+/*****************************************************************************/
+
+typedef struct {
+	const char *uuid;
+
+	CList _storage_by_uuid_lst_head;
+
+	char uuid_data[];
+} NMSettUtilStorageByUuidHead;
+
+typedef struct {
+	CList _storage_lst_head;
+	GHashTable *idx_by_filename;
+	GHashTable *idx_by_uuid;
+} NMSettUtilStorages;
+
+void nm_sett_util_storage_by_uuid_head_destroy (NMSettUtilStorageByUuidHead *sbuh);
+
+#define NM_SETT_UTIL_STORAGES_INIT(storages, storage_destroy_fcn) \
+	{ \
+		._storage_lst_head = C_LIST_INIT (((storages)._storage_lst_head)), \
+		.idx_by_filename   = g_hash_table_new_full (nm_str_hash, \
+		                                            g_str_equal, \
+		                                            NULL, \
+		                                            (GDestroyNotify) storage_destroy_fcn), \
+		.idx_by_uuid       = g_hash_table_new_full (nm_pstr_hash, \
+		                                            nm_pstr_equal, \
+		                                            NULL, \
+		                                            (GDestroyNotify) nm_sett_util_storage_by_uuid_head_destroy), \
+	}
+
+void nm_sett_util_storages_clear (NMSettUtilStorages *storages);
+
+#define nm_auto_clear_sett_util_storages nm_auto(nm_sett_util_storages_clear)
+
+void nm_sett_util_storages_add_take (NMSettUtilStorages *storages,
+                                     gpointer storage_take_p);
+
+gpointer nm_sett_util_storages_steal (NMSettUtilStorages *storages,
+                                      gpointer storage_p);
+
+/*****************************************************************************/
+
+static inline gpointer /* NMSettingsStorage * */
+nm_sett_util_storages_lookup_by_filename (NMSettUtilStorages *storages,
+                                          const char *filename)
+{
+	nm_assert (filename);
+
+	return g_hash_table_lookup (storages->idx_by_filename, filename);
+}
+
+static inline NMSettUtilStorageByUuidHead *
+nm_sett_util_storages_lookup_by_uuid (NMSettUtilStorages *storages,
+                                      const char *uuid)
+{
+	nm_assert (uuid);
+
+	return g_hash_table_lookup (storages->idx_by_uuid, &uuid);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+	GHashTable *idx_by_filename;
+	const char *allowed_filename;
+} NMSettUtilAllowFilenameData;
+
+#define NM_SETT_UTIL_ALLOW_FILENAME_DATA(_storages, _allowed_filename) \
+    (&((NMSettUtilAllowFilenameData) { \
+       .idx_by_filename  = (_storages)->idx_by_filename, \
+       .allowed_filename = (_allowed_filename), \
+    }))
+
+gboolean nm_sett_util_allow_filename_cb (const char *filename,
+                                         gpointer user_data);
+
+#endif /* __NM_SETTINGS_UTILS_H__ */
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 8e18a33e..430d2776 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * Søren Sandmann <sandmann@daimi.au.dk>
@@ -37,6 +36,8 @@
 #endif
 
 #include "nm-libnm-core-intern/nm-common-macros.h"
+#include "nm-glib-aux/nm-keyfile-aux.h"
+#include "nm-keyfile-internal.h"
 #include "nm-dbus-interface.h"
 #include "nm-connection.h"
 #include "nm-setting-8021x.h"
@@ -60,6 +61,7 @@
 #include "nm-utils.h"
 #include "nm-core-internal.h"
 
+#include "nm-std-aux/c-list-util.h"
 #include "nm-glib-aux/nm-c-list.h"
 #include "nm-dbus-object.h"
 #include "devices/nm-device-ethernet.h"
@@ -70,6 +72,7 @@
 #include "nm-auth-subject.h"
 #include "nm-session-monitor.h"
 #include "plugins/keyfile/nms-keyfile-plugin.h"
+#include "plugins/keyfile/nms-keyfile-storage.h"
 #include "nm-agent-manager.h"
 #include "nm-config.h"
 #include "nm-audit-manager.h"
@@ -79,16 +82,255 @@
 
 /*****************************************************************************/
 
-#define EXPORT(sym) void * __export_##sym = &sym;
+static NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
+
+/*****************************************************************************/
+
+typedef struct _StorageData {
+	CList sd_lst;
+	NMSettingsStorage *storage;
+	NMConnection *connection;
+	bool prioritize:1;
+} StorageData;
+
+static StorageData *
+_storage_data_new_stale (NMSettingsStorage *storage,
+                         NMConnection *connection)
+{
+	StorageData *sd;
+
+	sd = g_slice_new (StorageData);
+	sd->storage    = g_object_ref (storage);
+	sd->connection = nm_g_object_ref (connection);
+	sd->prioritize = FALSE;
+	return sd;
+}
+
+static void
+_storage_data_destroy (StorageData *sd)
+{
+	c_list_unlink_stale (&sd->sd_lst);
+	g_object_unref (sd->storage);
+	nm_g_object_unref (sd->connection);
+	g_slice_free (StorageData, sd);
+}
+
+static StorageData *
+_storage_data_find_in_lst (CList *head,
+                           NMSettingsStorage *storage)
+{
+	StorageData *sd;
+
+	nm_assert (head);
+	nm_assert (NM_IS_SETTINGS_STORAGE (storage));
+
+	c_list_for_each_entry (sd, head, sd_lst) {
+		if (sd->storage == storage)
+			return sd;
+	}
+	return NULL;
+}
+
+static void
+nm_assert_storage_data_lst (CList *head)
+{
+#if NM_MORE_ASSERTS > 5
+	const char *uuid = NULL;
+	StorageData *sd;
+	CList *iter;
 
-EXPORT(nm_settings_connection_get_type)
-EXPORT(nm_settings_connection_update)
+	nm_assert (head);
+
+	if (c_list_is_empty (head))
+		return;
+
+	c_list_for_each_entry (sd, head, sd_lst) {
+		const char *u;
+
+		nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage));
+		nm_assert (!sd->connection || NM_IS_CONNECTION (sd->connection));
+		u = nm_settings_storage_get_uuid (sd->storage);
+		if (!uuid) {
+			uuid = u;
+			nm_assert (nm_utils_is_uuid (uuid));
+		} else
+			nm_assert (nm_streq0 (uuid, u));
+	}
+
+	/* assert that all storages are unique. */
+	c_list_for_each_entry (sd, head, sd_lst) {
+		for (iter = sd->sd_lst.next; iter != head; iter = iter->next)
+			nm_assert (c_list_entry (iter, StorageData, sd_lst)->storage != sd->storage);
+	}
+#endif
+}
+
+static gboolean
+_storage_data_is_alive (StorageData *sd)
+{
+	/* If the storage tracks a connection, it is considered alive.
+	 *
+	 * Meta-data storages are special: they never track a connection.
+	 * We need to check them specially to know when to drop them. */
+	return    sd->connection
+	       || nm_settings_storage_is_meta_data_alive (sd->storage);
+}
 
 /*****************************************************************************/
 
-static NM_CACHED_QUARK_FCN ("plugin-module-path", plugin_module_path_quark)
-static NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
-static NM_CACHED_QUARK_FCN ("default-wired-device", _default_wired_device_quark)
+typedef struct {
+	const char *uuid;
+	NMSettingsConnection *sett_conn;
+	NMSettingsStorage *storage;
+	CList sd_lst_head;
+	CList dirty_sd_lst_head;
+
+	CList sce_dirty_lst;
+
+	char _uuid_data[];
+} SettConnEntry;
+
+static SettConnEntry *
+_sett_conn_entry_new (const char *uuid)
+{
+	SettConnEntry *sett_conn_entry;
+	gsize l_p_1;
+
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	l_p_1 = strlen (uuid) + 1;
+
+	sett_conn_entry = g_malloc (sizeof (SettConnEntry) + l_p_1);
+	sett_conn_entry->uuid = sett_conn_entry->_uuid_data;
+	sett_conn_entry->sett_conn = NULL;
+	sett_conn_entry->storage = NULL;
+	c_list_init (&sett_conn_entry->sd_lst_head);
+	c_list_init (&sett_conn_entry->dirty_sd_lst_head);
+	c_list_init (&sett_conn_entry->sce_dirty_lst);
+	memcpy (sett_conn_entry->_uuid_data, uuid, l_p_1);
+	return sett_conn_entry;
+}
+
+static void
+_sett_conn_entry_free (SettConnEntry *sett_conn_entry)
+{
+	c_list_unlink_stale (&sett_conn_entry->sce_dirty_lst);
+	nm_c_list_free_all (&sett_conn_entry->sd_lst_head,       StorageData, sd_lst, _storage_data_destroy);
+	nm_c_list_free_all (&sett_conn_entry->dirty_sd_lst_head, StorageData, sd_lst, _storage_data_destroy);
+	nm_g_object_unref (sett_conn_entry->sett_conn);
+	nm_g_object_unref (sett_conn_entry->storage);
+	g_free (sett_conn_entry);
+}
+
+static NMSettingsConnection *
+_sett_conn_entry_get_conn (SettConnEntry *sett_conn_entry)
+{
+	return sett_conn_entry ? sett_conn_entry->sett_conn : NULL;
+}
+
+/**
+ * _sett_conn_entry_storage_find_conflicting_storage:
+ * @sett_conn_entry: the list of settings-storages for the given UUID.
+ * @target_plugin: the settings plugin to check
+ * @storage_check_including: (allow-none): optionally compare against this storage.
+ * @plugins: the list of plugins sorted in descending priority. This determines
+ *   the priority and whether a storage conflicts.
+ *
+ * If we were to add the a storage to @target_plugin, then this function checks
+ * whether there are already other storages that would hide the storage after we
+ * add it. Those conflicting/hiding storages are a problem, because they have higher
+ * priority, so we cannot add the storage.
+ *
+ * @storage_check_including is optional, and if given then it checks whether updating
+ * the profile in this storage would result in confict. This is the check before
+ * update-connection. If this parameter is omitted, then it's about what happens
+ * when adding a new profile (add-connection).
+ *
+ * Returns: the conflicting storage or %NULL if there is none.
+ */
+static NMSettingsStorage *
+_sett_conn_entry_storage_find_conflicting_storage (SettConnEntry *sett_conn_entry,
+                                                   NMSettingsPlugin *target_plugin,
+                                                   NMSettingsStorage *storage_check_including,
+                                                   const GSList *plugins)
+{
+	StorageData *sd;
+
+	if (!sett_conn_entry)
+		return NULL;
+
+	if (   storage_check_including
+	    && nm_settings_storage_is_keyfile_run (storage_check_including)) {
+		/* the storage we check against is in-memory. It always has highest
+		 * priority, so there can be no other conflicting storages. */
+		return NULL;
+	}
+
+	/* Finds the first (highest priority) storage that has a connection.
+	 * Note that due to tombstones (that have a high priority), the connection
+	 * may not actually be exposed. This is to find hidden/shadowed storages
+	 * that provide a connection. */
+	c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+		nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage));
+
+		if (!sd->connection) {
+			/* We only consider storages with connection. In particular,
+			 * tombstones are not relevant, because we can delete them to
+			 * resolve the conflict. */
+			continue;
+		}
+
+		if (sd->storage == storage_check_including) {
+			/* ok, the storage is the one we are about to check. All other
+			 * storages are lower priority, so there is no storage that hides
+			 * our storage_check_including. */
+			return NULL;
+		}
+
+		if (nm_settings_plugin_cmp_by_priority (nm_settings_storage_get_plugin (sd->storage),
+		                                        target_plugin,
+		                                        plugins) <= 0) {
+			/* the plugin of the existing storage is less important than @target_plugin.
+			 * We have no conflicting/hiding storage. */
+			return NULL;
+		}
+
+		/* Found. If we would add the profile to @target_plugin, then it would be hidden
+		 * by existing_storage. */
+		return sd->storage;
+	}
+
+	return NULL;
+}
+
+static NMSettingsStorage *
+_sett_conn_entry_find_shadowed_storage (SettConnEntry *sett_conn_entry,
+                                        const char *shadowed_storage_filename,
+                                        NMSettingsStorage *blacklisted_storage)
+{
+	StorageData *sd;
+
+	if (!shadowed_storage_filename)
+		return NULL;
+
+	c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+
+		nm_assert (NM_IS_SETTINGS_STORAGE (sd->storage));
+
+		if (!sd->connection)
+			continue;
+
+		if (blacklisted_storage == sd->storage)
+			continue;
+
+		if (!nm_streq0 (nm_settings_storage_get_filename_for_shadowed_storage (sd->storage), shadowed_storage_filename))
+			continue;
+
+		return sd->storage;
+	}
+
+	return NULL;
+}
 
 /*****************************************************************************/
 
@@ -115,25 +357,45 @@ typedef struct {
 
 	NMConfig *config;
 
-	GSList *auths;
+	NMPlatform *platform;
+
+	NMHostnameManager *hostname_manager;
+
+	NMSessionMonitor *session_monitor;
+
+	CList auth_lst_head;
+
+	NMSKeyfilePlugin *keyfile_plugin;
 
 	GSList *plugins;
 
+	NMKeyFileDB *kf_db_timestamps;
+	NMKeyFileDB *kf_db_seen_bssids;
+
+	GHashTable *sce_idx;
+
+	CList sce_dirty_lst_head;
+
 	CList connections_lst_head;
 
 	NMSettingsConnection **connections_cached_list;
+
 	GSList *unmanaged_specs;
 	GSList *unrecognized_specs;
 
-	NMHostnameManager *hostname_manager;
-
+	GHashTable *startup_complete_idx;
 	NMSettingsConnection *startup_complete_blocked_by;
+	gulong startup_complete_platform_change_id;
+	guint startup_complete_timeout_id;
 
 	guint connections_len;
 
+	guint connections_generation;
+
+	guint kf_db_flush_idle_id_timestamps;
+	guint kf_db_flush_idle_id_seen_bssids;
+
 	bool started:1;
-	bool startup_complete:1;
-	bool connections_loaded:1;
 
 } NMSettingsPrivate;
 
@@ -152,6 +414,9 @@ G_DEFINE_TYPE (NMSettings, nm_settings, NM_TYPE_DBUS_OBJECT);
 
 /*****************************************************************************/
 
+/* FIXME: a lot of logging lines are directly connected to a profile. Set the @con_uuid
+ *   argument for structured logging. */
+
 #define _NMLOG_DOMAIN         LOGD_SETTINGS
 #define _NMLOG(level, ...) __NMLOG_DEFAULT (level, _NMLOG_DOMAIN, "settings", __VA_ARGS__)
 
@@ -161,367 +426,286 @@ static const NMDBusInterfaceInfoExtended interface_info_settings;
 static const GDBusSignalInfo signal_info_new_connection;
 static const GDBusSignalInfo signal_info_connection_removed;
 
-static void claim_connection (NMSettings *self,
-                              NMSettingsConnection *connection);
-
-static void unmanaged_specs_changed (NMSettingsPlugin *config, gpointer user_data);
-static void unrecognized_specs_changed (NMSettingsPlugin *config, gpointer user_data);
-
-static void connection_ready_changed (NMSettingsConnection *conn,
-                                      GParamSpec *pspec,
-                                      gpointer user_data);
-
 static void default_wired_clear_tag (NMSettings *self,
                                      NMDevice *device,
-                                     NMSettingsConnection *connection,
+                                     NMSettingsConnection *sett_conn,
                                      gboolean add_to_no_auto_default);
 
+static void _clear_connections_cached_list (NMSettingsPrivate *priv);
+
+static void _startup_complete_check (NMSettings *self,
+                                     gint64 now_us);
+
 /*****************************************************************************/
 
 static void
-check_startup_complete (NMSettings *self)
+_emit_connection_added (NMSettings *self,
+                        NMSettingsConnection *sett_conn)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	NMSettingsConnection *sett_conn;
-
-	if (priv->startup_complete)
-		return;
-
-	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
-		if (!nm_settings_connection_get_ready (sett_conn)) {
-			nm_g_object_ref_set (&priv->startup_complete_blocked_by, sett_conn);
-			return;
-		}
-	}
-
-	g_clear_object (&priv->startup_complete_blocked_by);
-
-	/* the connection_ready_changed signal handler is no longer needed. */
-	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst)
-		g_signal_handlers_disconnect_by_func (sett_conn, G_CALLBACK (connection_ready_changed), self);
-
-	priv->startup_complete = TRUE;
-	_notify (self, PROP_STARTUP_COMPLETE);
+	g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
 }
 
 static void
-connection_ready_changed (NMSettingsConnection *conn,
-                          GParamSpec *pspec,
-                          gpointer user_data)
+_emit_connection_updated (NMSettings *self,
+                          NMSettingsConnection *sett_conn,
+                          NMSettingsConnectionUpdateReason update_reason)
 {
-	NMSettings *self = NM_SETTINGS (user_data);
-
-	if (nm_settings_connection_get_ready (conn))
-		check_startup_complete (self);
+	_nm_settings_connection_emit_signal_updated_internal (sett_conn, update_reason);
+	g_signal_emit (self, signals[CONNECTION_UPDATED], 0, sett_conn, (guint) update_reason);
 }
 
 static void
-plugin_connection_added (NMSettingsPlugin *config,
-                         NMSettingsConnection *connection,
-                         NMSettings *self)
+_emit_connection_removed (NMSettings *self,
+                          NMSettingsConnection *sett_conn)
 {
-	claim_connection (self, connection);
+	g_signal_emit (self, signals[CONNECTION_REMOVED], 0, sett_conn);
 }
 
 static void
-load_connections (NMSettings *self)
+_emit_connection_flags_changed (NMSettings *self,
+                                NMSettingsConnection *sett_conn)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GSList *iter;
+	g_signal_emit (self, signals[CONNECTION_FLAGS_CHANGED], 0, sett_conn);
+}
 
-	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
-		GSList *plugin_connections;
-		GSList *elt;
+/*****************************************************************************/
 
-		plugin_connections = nm_settings_plugin_get_connections (plugin);
+typedef struct {
+	NMSettingsConnection *sett_conn;
+	gint64 start_at;
+	gint64 timeout;
+} StartupCompleteData;
 
-		// FIXME: ensure connections from plugins loaded with a lower priority
-		// get rejected when they conflict with connections from a higher
-		// priority plugin.
+static void
+_startup_complete_data_destroy (StartupCompleteData *scd)
+{
+	g_object_unref (scd->sett_conn);
+	g_slice_free (StartupCompleteData, scd);
+}
 
-		for (elt = plugin_connections; elt; elt = g_slist_next (elt))
-			claim_connection (self, elt->data);
+static gboolean
+_startup_complete_check_is_ready (NMPlatform *platform,
+                                  NMSettingsConnection *sett_conn)
+{
+	const NMPlatformLink *plink;
+	const char *ifname;
 
-		g_slist_free (plugin_connections);
+	/* FIXME: instead of just looking for the interface name, it would be better
+	 *        to wait for a device that is compatible with the profile. */
 
-		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_CONNECTION_ADDED,
-		                  G_CALLBACK (plugin_connection_added), self);
-		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
-		                  G_CALLBACK (unmanaged_specs_changed), self);
-		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
-		                  G_CALLBACK (unrecognized_specs_changed), self);
-	}
+	ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (sett_conn));
 
-	priv->connections_loaded = TRUE;
-	_notify (self, PROP_CONNECTIONS);
+	if (!ifname)
+		return TRUE;
 
-	unmanaged_specs_changed (NULL, self);
-	unrecognized_specs_changed (NULL, self);
+	plink = nm_platform_link_get_by_ifname (platform, ifname);
+	return plink && plink->initialized;
 }
 
-static void
-impl_settings_list_connections (NMDBusObject *obj,
-                                const NMDBusInterfaceInfoExtended *interface_info,
-                                const NMDBusMethodInfoExtended *method_info,
-                                GDBusConnection *dbus_connection,
-                                const char *sender,
-                                GDBusMethodInvocation *invocation,
-                                GVariant *parameters)
+static gboolean
+_startup_complete_timeout_cb (gpointer user_data)
 {
-	NMSettings *self = NM_SETTINGS (obj);
+	NMSettings *self = user_data;
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	gs_free const char **strv = NULL;
 
-	strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
-	                                          priv->connections_len,
-	                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
-	                                          TRUE);
-	g_dbus_method_invocation_return_value (invocation,
-	                                       g_variant_new ("(^ao)", strv));
-}
-
-NMSettingsConnection *
-nm_settings_get_connection_by_uuid (NMSettings *self, const char *uuid)
-{
-	NMSettingsPrivate *priv;
-	NMSettingsConnection *candidate;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
-	g_return_val_if_fail (uuid != NULL, NULL);
-
-	priv = NM_SETTINGS_GET_PRIVATE (self);
-
-	c_list_for_each_entry (candidate, &priv->connections_lst_head, _connections_lst) {
-		if (nm_streq (uuid, nm_settings_connection_get_uuid (candidate)))
-			return candidate;
-	}
-
-	return NULL;
+	priv->startup_complete_timeout_id = 0;
+	_startup_complete_check (self, 0);
+	return G_SOURCE_REMOVE;
 }
 
 static void
-impl_settings_get_connection_by_uuid (NMDBusObject *obj,
-                                      const NMDBusInterfaceInfoExtended *interface_info,
-                                      const NMDBusMethodInfoExtended *method_info,
-                                      GDBusConnection *dbus_connection,
-                                      const char *sender,
-                                      GDBusMethodInvocation *invocation,
-                                      GVariant *parameters)
+_startup_complete_platform_change_cb (NMPlatform *platform,
+                                      int obj_type_i,
+                                      int ifindex,
+                                      const NMPlatformLink *link,
+                                      int change_type_i,
+                                      NMSettings *self)
 {
-	NMSettings *self = NM_SETTINGS (obj);
-	NMSettingsConnection *sett_conn;
-	gs_unref_object NMAuthSubject *subject = NULL;
-	GError *error = NULL;
-	const char *uuid;
+	const NMPlatformSignalChangeType change_type = change_type_i;
+	NMSettingsPrivate *priv;
+	const char *ifname;
 
-	g_variant_get (parameters, "(&s)", &uuid);
+	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
+		return;
 
-	sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
-	if (!sett_conn) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                             "No connection with the UUID was found.");
-		goto error;
-	}
+	if (!link->initialized)
+		return;
 
-	subject = nm_auth_subject_new_unix_process_from_context (invocation);
-	if (!subject) {
-		error = g_error_new_literal (NM_SETTINGS_ERROR,
-		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
-		                             "Unable to determine UID of request.");
-		goto error;
-	}
+	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
-	                                          subject,
-	                                          NM_SETTINGS_ERROR,
-	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
-	                                          &error))
-		goto error;
+	ifname = nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by));
+	if (   ifname
+	    && !nm_streq (ifname, link->name))
+		return;
 
-	g_dbus_method_invocation_return_value (invocation,
-	                                       g_variant_new ("(o)",
-	                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn))));
-	return;
+	nm_assert (priv->startup_complete_timeout_id > 0);
 
-error:
-	g_dbus_method_invocation_take_error (invocation, error);
+	nm_clear_g_source (&priv->startup_complete_timeout_id);
+	priv->startup_complete_timeout_id = g_idle_add (_startup_complete_timeout_cb, self);
 }
 
 static void
-_clear_connections_cached_list (NMSettingsPrivate *priv)
+_startup_complete_check (NMSettings *self,
+                         gint64 now_us)
 {
-	if (!priv->connections_cached_list)
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gint64 next_expiry;
+	StartupCompleteData *scd;
+	NMSettingsConnection *next_sett_conn = NULL;
+	GHashTableIter iter;
+
+	if (!priv->started) {
+		/* before we are started, we don't setup the timers... */
 		return;
+	}
 
-	nm_assert (priv->connections_len == NM_PTRARRAY_LEN (priv->connections_cached_list));
+	if (!priv->startup_complete_idx)
+		goto ready;
 
-#if NM_MORE_ASSERTS
-	/* set the pointer to a bogus value. This makes it more apparent
-	 * if somebody has a reference to the cached list and still uses
-	 * it. That is a bug, this code just tries to make it blow up
-	 * more eagerly. */
-	memset (priv->connections_cached_list,
-	        0xdeaddead,
-	        sizeof (NMSettingsConnection *) * (priv->connections_len + 1));
-#endif
+	if (!now_us)
+		now_us = nm_utils_get_monotonic_timestamp_us ();
 
-	nm_clear_g_free (&priv->connections_cached_list);
-}
+	next_expiry = 0;
 
-/**
- * nm_settings_get_connections:
- * @self: the #NMSettings
- * @out_len: (out) (allow-none): returns the number of returned
- *   connections.
- *
- * Returns: (transfer none): a list of NMSettingsConnections. The list is
- * unsorted and NULL terminated. The result is never %NULL, in case of no
- * connections, it returns an empty list.
- * The returned list is cached internally, only valid until the next
- * NMSettings operation.
- */
-NMSettingsConnection *const*
-nm_settings_get_connections (NMSettings *self, guint *out_len)
-{
-	NMSettingsPrivate *priv;
-	NMSettingsConnection **v;
-	NMSettingsConnection *con;
-	guint i;
+	g_hash_table_iter_init (&iter, priv->startup_complete_idx);
+	while (g_hash_table_iter_next (&iter, (gpointer *) &scd, NULL)) {
+		gint64 expiry;
 
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+		if (scd->start_at == 0) {
+			/* once ready, the decision is remembered and there is nothing
+			 * left to check. */
+			continue;
+		}
 
-	priv = NM_SETTINGS_GET_PRIVATE (self);
+		expiry = scd->start_at + scd->timeout;
+		if (expiry <= now_us) {
+			scd->start_at = 0;
+			continue;
+		}
 
-	nm_assert (priv->connections_len == c_list_length (&priv->connections_lst_head));
+		if (_startup_complete_check_is_ready (priv->platform, scd->sett_conn)) {
+			scd->start_at = 0;
+			continue;
+		}
 
-	if (G_UNLIKELY (!priv->connections_cached_list)) {
-		v = g_new (NMSettingsConnection *, priv->connections_len + 1);
+		next_expiry = expiry;
+		next_sett_conn = scd->sett_conn;
+		/* we found one timeout for which to wait. that's good enough. */
+		break;
+	}
 
-		i = 0;
-		c_list_for_each_entry (con, &priv->connections_lst_head, _connections_lst) {
-			nm_assert (i < priv->connections_len);
-			v[i++] = con;
+	nm_clear_g_source (&priv->startup_complete_timeout_id);
+	nm_g_object_ref_set (&priv->startup_complete_blocked_by, next_sett_conn);
+	if (next_expiry > 0) {
+		nm_assert (priv->startup_complete_blocked_by);
+		if (priv->startup_complete_platform_change_id == 0) {
+			priv->startup_complete_platform_change_id = g_signal_connect (priv->platform,
+			                                                              NM_PLATFORM_SIGNAL_LINK_CHANGED,
+			                                                              G_CALLBACK (_startup_complete_platform_change_cb),
+			                                                              self);
 		}
-		nm_assert (i == priv->connections_len);
-		v[i] = NULL;
-
-		priv->connections_cached_list = v;
+		priv->startup_complete_timeout_id = g_timeout_add (NM_MIN (3600u*1000u, (next_expiry - now_us) / 1000u),
+		                                                   _startup_complete_timeout_cb,
+		                                                   self);
+		_LOGT ("startup-complete: wait for device \"%s\" due to connection %s (%s)",
+		       nm_connection_get_interface_name (nm_settings_connection_get_connection (priv->startup_complete_blocked_by)),
+		       nm_settings_connection_get_uuid (priv->startup_complete_blocked_by),
+		       nm_settings_connection_get_id (priv->startup_complete_blocked_by));
+		return;
 	}
 
-	NM_SET_OUT (out_len, priv->connections_len);
-	return priv->connections_cached_list;
+	nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy);
+	nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id);
+
+ready:
+	_LOGT ("startup-complete: ready, no profiles to wait for");
+	nm_assert (priv->started);
+	nm_assert (!priv->startup_complete_blocked_by);
+	nm_assert (!priv->startup_complete_idx);
+	nm_assert (priv->startup_complete_timeout_id == 0);
+	nm_assert (priv->startup_complete_platform_change_id == 0);
+	_notify (self, PROP_STARTUP_COMPLETE);
 }
 
-/**
- * nm_settings_get_connections_clone:
- * @self: the #NMSetting
- * @out_len: (allow-none): optional output argument
- * @func: caller-supplied function for filtering connections
- * @func_data: caller-supplied data passed to @func
- * @sort_compare_func: (allow-none): optional function pointer for
- *   sorting the returned list.
- * @sort_data: user data for @sort_compare_func.
- *
- * Returns: (transfer container) (element-type NMSettingsConnection):
- *   an NULL terminated array of #NMSettingsConnection objects that were
- *   filtered by @func (or all connections if no filter was specified).
- *   The order is arbitrary.
- *   Caller is responsible for freeing the returned array with free(),
- *   the contained values do not need to be unrefed.
- */
-NMSettingsConnection **
-nm_settings_get_connections_clone (NMSettings *self,
-                                   guint *out_len,
-                                   NMSettingsConnectionFilterFunc func,
-                                   gpointer func_data,
-                                   GCompareDataFunc sort_compare_func,
-                                   gpointer sort_data)
+static void
+_startup_complete_notify_connection (NMSettings *self,
+                                     NMSettingsConnection *sett_conn,
+                                     gboolean forget)
 {
-	NMSettingsConnection *const*list_cached;
-	NMSettingsConnection **list;
-	guint len, i, j;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
-
-	list_cached = nm_settings_get_connections (self, &len);
-
-#if NM_MORE_ASSERTS
-	nm_assert (list_cached);
-	for (i = 0; i < len; i++)
-		nm_assert (NM_IS_SETTINGS_CONNECTION (list_cached[i]));
-	nm_assert (!list_cached[i]);
-#endif
-
-	list = g_new (NMSettingsConnection *, ((gsize) len + 1));
-	if (func) {
-		for (i = 0, j = 0; i < len; i++) {
-			if (func (self, list_cached[i], func_data))
-				list[j++] = list_cached[i];
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gint64 timeout;
+	gint64 now_us = 0;
+
+	nm_assert (   !priv->started
+	           || priv->startup_complete_idx);
+
+	timeout = 0;
+	if (!forget) {
+		NMSettingConnection *s_con;
+		gint32 v;
+
+		s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (sett_conn));
+		v = nm_setting_connection_get_wait_device_timeout (s_con);
+		if (v > 0) {
+			nm_assert (nm_setting_connection_get_interface_name (s_con));
+			timeout = ((gint64) v) * 1000;
 		}
-		list[j] = NULL;
-		len = j;
-	} else
-		memcpy (list, list_cached, sizeof (list[0]) * ((gsize) len + 1));
-
-	if (   len > 1
-	    && sort_compare_func) {
-		g_qsort_with_data (list, len, sizeof (NMSettingsConnection *),
-		                   sort_compare_func, sort_data);
 	}
-	NM_SET_OUT (out_len, len);
-	return list;
-}
-
-NMSettingsConnection *
-nm_settings_get_connection_by_path (NMSettings *self, const char *path)
-{
-	NMSettingsPrivate *priv;
-	NMSettingsConnection *connection;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
-	g_return_val_if_fail (path, NULL);
-
-	priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	connection = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
-	                                            path);
-	if (   !connection
-	    || !NM_IS_SETTINGS_CONNECTION (connection))
-		return NULL;
+	if (timeout == 0) {
+		if (   !priv->startup_complete_idx
+		    || !g_hash_table_remove (priv->startup_complete_idx, &sett_conn))
+			return;
+	} else {
+		StartupCompleteData *scd;
+
+		if (!priv->startup_complete_idx) {
+			nm_assert (!priv->started);
+			priv->startup_complete_idx = g_hash_table_new_full (nm_pdirect_hash,
+			                                                    nm_pdirect_equal,
+			                                                    NULL,
+			                                                    (GDestroyNotify) _startup_complete_data_destroy);
+			scd = NULL;
+		} else
+			scd = g_hash_table_lookup (priv->startup_complete_idx, &sett_conn);
+		if (!scd) {
+			now_us = nm_utils_get_monotonic_timestamp_us ();
+			scd = g_slice_new (StartupCompleteData);
+			*scd = (StartupCompleteData) {
+				.sett_conn = g_object_ref (sett_conn),
+				.start_at  = now_us,
+				.timeout   = timeout,
+			};
+			g_hash_table_add (priv->startup_complete_idx, scd);
+		} else {
+			if (scd->start_at == 0) {
+				/* the entry already is ready and no longer relevant. Ignore it. */
+				return;
+			}
+			scd->timeout = timeout;
+		}
+	}
 
-	nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
-	return connection;
+	_startup_complete_check (self, now_us);
 }
 
-gboolean
-nm_settings_has_connection (NMSettings *self, NMSettingsConnection *connection)
+const char *
+nm_settings_get_startup_complete_blocked_reason (NMSettings *self)
 {
-	gboolean has;
-
-	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
-	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), FALSE);
-
-	has = !c_list_is_empty (&connection->_connections_lst);
-
-	nm_assert (has == nm_c_list_contains_entry (&NM_SETTINGS_GET_PRIVATE (self)->connections_lst_head,
-                                                connection,
-                                                _connections_lst));
-	nm_assert (({
-		NMSettingsConnection *candidate = NULL;
-		const char *path;
-
-		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
-		if (path)
-			candidate = nm_settings_get_connection_by_path (self, path);
-
-		(has == (connection == candidate));
-	}));
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	const char *uuid = NULL;
 
-	return has;
+	if (priv->started) {
+		if (!priv->startup_complete_idx)
+			return NULL;
+		if (priv->startup_complete_blocked_by)
+			uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by);
+	}
+	return uuid ?: "unknown";
 }
 
+/*****************************************************************************/
+
 const GSList *
 nm_settings_get_unmanaged_specs (NMSettings *self)
 {
@@ -531,57 +715,57 @@ nm_settings_get_unmanaged_specs (NMSettings *self)
 }
 
 static gboolean
-find_spec (GSList *spec_list, const char *spec)
-{
-	GSList *iter;
-
-	for (iter = spec_list; iter; iter = g_slist_next (iter)) {
-		if (!strcmp ((const char *) iter->data, spec))
-			return TRUE;
-	}
-	return FALSE;
-}
-
-static void
 update_specs (NMSettings *self, GSList **specs_ptr,
               GSList * (*get_specs_func) (NMSettingsPlugin *))
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *new = NULL;
 	GSList *iter;
 
-	g_slist_free_full (*specs_ptr, g_free);
-	*specs_ptr = NULL;
-
 	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		GSList *specs, *specs_iter;
+		GSList *specs;
 
-		specs = get_specs_func (NM_SETTINGS_PLUGIN (iter->data));
-		for (specs_iter = specs; specs_iter; specs_iter = specs_iter->next) {
-			if (!find_spec (*specs_ptr, (const char *) specs_iter->data)) {
-				*specs_ptr = g_slist_prepend (*specs_ptr, specs_iter->data);
-			} else
-				g_free (specs_iter->data);
+		specs = get_specs_func (iter->data);
+		while (specs) {
+			GSList *s = specs;
+
+			specs = g_slist_remove_link (specs, s);
+			if (nm_utils_g_slist_find_str (new, s->data)) {
+				g_free (s->data);
+				g_slist_free_1 (s);
+				continue;
+			}
+			s->next = new;
+			new = s;
 		}
+	}
 
-		g_slist_free (specs);
+	if (nm_utils_g_slist_strlist_cmp (new, *specs_ptr) == 0) {
+		g_slist_free_full (new, g_free);
+		return FALSE;
 	}
+
+	g_slist_free_full (*specs_ptr, g_free);
+	*specs_ptr = new;
+	return TRUE;
+
 }
 
 static void
-unmanaged_specs_changed (NMSettingsPlugin *config,
-                         gpointer user_data)
+_plugin_unmanaged_specs_changed (NMSettingsPlugin *config,
+                                 gpointer user_data)
 {
 	NMSettings *self = NM_SETTINGS (user_data);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
-	update_specs (self, &priv->unmanaged_specs,
-	              nm_settings_plugin_get_unmanaged_specs);
-	_notify (self, PROP_UNMANAGED_SPECS);
+	if (update_specs (self, &priv->unmanaged_specs,
+	                  nm_settings_plugin_get_unmanaged_specs))
+		_notify (self, PROP_UNMANAGED_SPECS);
 }
 
 static void
-unrecognized_specs_changed (NMSettingsPlugin *config,
-                               gpointer user_data)
+_plugin_unrecognized_specs_changed (NMSettingsPlugin *config,
+                                    gpointer user_data)
 {
 	NMSettings *self = NM_SETTINGS (user_data);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
@@ -590,521 +774,1605 @@ unrecognized_specs_changed (NMSettingsPlugin *config,
 	              nm_settings_plugin_get_unrecognized_specs);
 }
 
+/*****************************************************************************/
+
 static void
-add_plugin (NMSettings *self, NMSettingsPlugin *plugin, const char *path)
+connection_flags_changed (NMSettingsConnection *sett_conn,
+                          gpointer user_data)
 {
-	NMSettingsPrivate *priv;
+	_emit_connection_flags_changed (NM_SETTINGS (user_data), sett_conn);
+}
 
-	nm_assert (NM_IS_SETTINGS (self));
-	nm_assert (NM_IS_SETTINGS_PLUGIN (plugin));
+/*****************************************************************************/
 
-	priv = NM_SETTINGS_GET_PRIVATE (self);
+static SettConnEntry *
+_sett_conn_entries_get (NMSettings *self,
+                        const char *uuid)
+{
+	nm_assert (uuid);
+	return g_hash_table_lookup (NM_SETTINGS_GET_PRIVATE (self)->sce_idx, &uuid);
+}
 
-	nm_assert (!g_slist_find (priv->plugins, plugin));
+static SettConnEntry *
+_sett_conn_entries_create_and_add (NMSettings *self,
+                                   const char *uuid)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	SettConnEntry *sett_conn_entry;
 
-	priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
+	sett_conn_entry = _sett_conn_entry_new (uuid);
 
-	nm_settings_plugin_initialize (plugin);
+	if (!g_hash_table_add (priv->sce_idx, sett_conn_entry))
+		nm_assert_not_reached ();
+	else if (g_hash_table_size (priv->sce_idx) == 1)
+		g_object_ref (self);
 
-	_LOGI ("Loaded settings plugin: %s (%s%s%s)",
-	       G_OBJECT_TYPE_NAME (plugin),
-	       NM_PRINT_FMT_QUOTED (path, "\"", path, "\"", "internal"));
+	return sett_conn_entry;
 }
 
-static gboolean
-add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
+static void
+_sett_conn_entries_remove_and_destroy (NMSettings *self,
+                                       SettConnEntry *sett_conn_entry)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	gs_free char *full_name = NULL;
-	gs_free char *path = NULL;
-	gs_unref_object NMSettingsPlugin *plugin = NULL;
-	GModule *module;
-	NMSettingsPluginFactoryFunc factory_func;
-	GSList *iter;
-	struct stat st;
-	int errsv;
-
-	full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
-	path = g_module_build_path (NMPLUGINDIR, full_name);
 
-	for (iter = priv->plugins; iter; iter = iter->next) {
-		if (nm_streq0 (path,
-		               g_object_get_qdata (iter->data,
-		                                   plugin_module_path_quark ())))
-			return TRUE;
-	}
-
-	if (stat (path, &st) != 0) {
-		errsv = errno;
-		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, nm_strerror_native (errsv));
-		return TRUE;
-	}
-	if (!S_ISREG (st.st_mode)) {
-		_LOGW ("could not load plugin '%s' from file '%s': not a file", pname, path);
-		return TRUE;
-	}
-	if (st.st_uid != 0) {
-		_LOGW ("could not load plugin '%s' from file '%s': file must be owned by root", pname, path);
-		return TRUE;
-	}
-	if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
-		_LOGW ("could not load plugin '%s' from file '%s': invalid file permissions", pname, path);
-		return TRUE;
-	}
+	if (!g_hash_table_remove (priv->sce_idx, sett_conn_entry))
+		nm_assert_not_reached ();
+	else if (g_hash_table_size (priv->sce_idx) == 0)
+		g_object_unref (self);
+}
 
-	module = g_module_open (path, G_MODULE_BIND_LOCAL);
-	if (!module) {
-		_LOGW ("could not load plugin '%s' from file '%s': %s",
-		     pname, path, g_module_error ());
-		return TRUE;
-	}
+/*****************************************************************************/
 
-	/* errors after this point are fatal, because we loaded the shared library already. */
+static int
+_sett_conn_entry_sds_update_cmp_ascending (const StorageData *sd_a,
+                                           const StorageData *sd_b,
+                                           const GSList *plugins)
+{
+	const NMSettingsMetaData *meta_data_a;
+	const NMSettingsMetaData *meta_data_b;
+	bool is_keyfile_run_a;
+	bool is_keyfile_run_b;
+
+	/* Sort storages by priority. More important storages are sorted
+	 * higher (ascending sort). For example, if "sd_a" is more important than
+	 * "sd_b" (sd_a>sd_b), a positive integer is returned. */
+
+	meta_data_a = nm_settings_storage_is_meta_data (sd_a->storage);
+	meta_data_b = nm_settings_storage_is_meta_data (sd_b->storage);
+
+	/* runtime storages (both connections and meta-data) are always more
+	 * important. */
+	is_keyfile_run_a = nm_settings_storage_is_keyfile_run (sd_a->storage);
+	is_keyfile_run_b = nm_settings_storage_is_keyfile_run (sd_b->storage);
+	if (is_keyfile_run_a != is_keyfile_run_b) {
+
+		if (   !meta_data_a
+		    && !meta_data_b) {
+			/* Ok, both are non-meta-data providing actual profiles. But one is in /run and one is in
+			 * another storage. In this case we first honor whether one of the storages is explicitly
+			 * prioritized. The prioritize flag is an in-memory hack to overwrite relative priorities
+			 * contrary to what exists on-disk.
+			 *
+			 * This is done because when we use explicit D-Bus API (like update-connection)
+			 * to update a profile, then we really want to prioritize the candidate
+			 * despite having multiple other profiles.
+			 *
+			 * The example is if you have the same UUID twice in /run (one of them shadowed).
+			 * If you move it to disk, then one of the profiles gets deleted and re-created
+			 * on disk, but that on-disk profile must win against the remainging profile in
+			 * /run. At least until the next reload/restart. */
+			NM_CMP_FIELD_UNSAFE (sd_a, sd_b, prioritize);
+		}
 
-	if (!g_module_symbol (module, "nm_settings_plugin_factory", (gpointer) (&factory_func))) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "Could not find plugin '%s' factory function.",
-		             pname);
-		g_module_close (module);
-		return FALSE;
+		/* in-memory has higher priority. That is regardless of whether any of
+		 * them is meta-data/tombstone or a profile.
+		 *
+		 * That works, because if any of them are tombstones/metadata, then we are in full
+		 * control. There can by only one meta-data file, which is fully owned (and accordingly
+		 * created/deleted) by NetworkManager.
+		 *
+		 * The only case where this might not be right is if we have profiles
+		 * in /run that are shadowed. When we move such a profile to disk, then
+		 * a conflict might arise. That is handled by "prioritize" above! */
+		NM_CMP_DIRECT (is_keyfile_run_a, is_keyfile_run_b);
 	}
 
-	/* after accessing the plugin we cannot unload it anymore, because the glib
-	 * types cannot be properly unregistered. */
-	g_module_make_resident (module);
+	/* After we determined that both profiles are either in /run or not,
+	 * tombstones are always more important than non-tombstones. */
+	NM_CMP_DIRECT (meta_data_a && meta_data_a->is_tombstone,
+	               meta_data_b && meta_data_b->is_tombstone);
 
-	plugin = (*factory_func) ();
-	if (!NM_IS_SETTINGS_PLUGIN (plugin)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "plugin '%s' returned invalid settings plugin",
-		             pname);
-		return FALSE;
-	}
+	/* Again, prioritized entries are sorted first (higher priority). */
+	NM_CMP_FIELD_UNSAFE (sd_a, sd_b, prioritize);
 
-	add_plugin (self, NM_SETTINGS_PLUGIN (plugin), path);
-	g_object_set_qdata_full (G_OBJECT (plugin),
-	                         plugin_module_path_quark (),
-	                         g_steal_pointer (&path),
-	                         g_free);
-	return TRUE;
+	/* finally, compare the storages. This basically honors the timestamp
+	 * of the profile and the relative order of the source plugin (via the
+	 * @plugins list). */
+	return nm_settings_storage_cmp (sd_a->storage, sd_b->storage, plugins);
 }
 
-static void
-add_plugin_keyfile (NMSettings *self)
+static int
+_sett_conn_entry_sds_update_cmp (const CList *ls_a,
+                                 const CList *ls_b,
+                                 gconstpointer user_data)
 {
-	gs_unref_object NMSKeyfilePlugin *keyfile_plugin = NULL;
-
-	keyfile_plugin = nms_keyfile_plugin_new ();
-	add_plugin (self, NM_SETTINGS_PLUGIN (keyfile_plugin), NULL);
+	/* we sort highest priority storages first (descending). Hence, the order is swapped. */
+	return _sett_conn_entry_sds_update_cmp_ascending (c_list_entry (ls_b, StorageData, sd_lst),
+	                                                  c_list_entry (ls_a, StorageData, sd_lst),
+	                                                  user_data);
 }
 
-static gboolean
-load_plugins (NMSettings *self, const char **plugins, GError **error)
+static void
+_sett_conn_entry_sds_update (NMSettings *self,
+                             SettConnEntry *sett_conn_entry)
 {
-	const char **iter;
-	gboolean keyfile_added = FALSE;
-	gboolean success = TRUE;
-	gboolean add_ibft = FALSE;
-	gboolean has_no_ibft;
-	gssize idx_no_ibft, idx_ibft;
-
-	idx_ibft    = nm_utils_strv_find_first ((char **) plugins, -1, "ibft");
-	idx_no_ibft = nm_utils_strv_find_first ((char **) plugins, -1, "no-ibft");
-	has_no_ibft = idx_no_ibft >= 0 && idx_no_ibft > idx_ibft;
-#if WITH_SETTINGS_PLUGIN_IBFT
-	add_ibft = idx_no_ibft < 0 && idx_ibft < 0;
-#endif
+	StorageData *sd;
+	StorageData *sd_safe;
+	StorageData *sd_dirty;
+	gboolean reprioritize;
+
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+	nm_assert_storage_data_lst (&sett_conn_entry->dirty_sd_lst_head);
+
+	/* we merge the dirty list with the previous list.
+	 *
+	 * The idea is:
+	 *
+	 *  - _connection_changed_track() appends events for the same UUID. Meaning:
+	 *    if the storage is new, it get appended (having lower priority).
+	 *    If it already exist and is an update for an event that we already
+	 *    track it, it keeps the list position in @dirty_sd_lst_head unchanged.
+	 *
+	 *  - during merge, we want to preserve the previous order (with higher
+	 *    priority first in the list).
+	 */
 
-	for (iter = plugins; iter && *iter; iter++) {
-		const char *pname = *iter;
+	/* first go through all storages that we track and check whether they
+	 * got an update...*/
 
-		if (!*pname || strchr (pname, '/')) {
-			_LOGW ("ignore invalid plugin \"%s\"", pname);
-			continue;
+	reprioritize = FALSE;
+	c_list_for_each_entry (sd, &sett_conn_entry->dirty_sd_lst_head, sd_lst) {
+		if (sd->prioritize) {
+			reprioritize = TRUE;
+			break;
 		}
+	}
 
-		if (NM_IN_STRSET (pname, "ifcfg-suse", "ifnet")) {
-			_LOGW ("skipping deprecated plugin %s", pname);
-			continue;
-		}
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
 
-		if (nm_streq (pname, "no-ibft"))
-			continue;
-		if (has_no_ibft && nm_streq (pname, "ibft"))
-			continue;
+	c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
 
-		/* keyfile plugin is built-in now */
-		if (nm_streq (pname, "keyfile")) {
-			if (!keyfile_added) {
-				add_plugin_keyfile (self);
-				keyfile_added = TRUE;
-			}
+		sd_dirty = _storage_data_find_in_lst (&sett_conn_entry->dirty_sd_lst_head, sd->storage);
+		if (!sd_dirty) {
+			/* there is no update for this storage (except maybe reprioritize). */
+			if (reprioritize)
+				sd->prioritize = FALSE;
 			continue;
 		}
 
-		if (nm_utils_strv_find_first ((char **) plugins,
-		                              iter - plugins,
-		                              pname) >= 0) {
-			/* the plugin is already mentioned in the list previously.
-			 * Don't load a duplicate. */
-			continue;
-		}
+		nm_g_object_ref_set (&sd->connection, sd_dirty->connection);
+		sd->prioritize = sd_dirty->prioritize;
 
-		success = add_plugin_load_file (self, pname, error);
-		if (!success)
-			break;
+		_storage_data_destroy (sd_dirty);
+	}
 
-		if (add_ibft && nm_streq (pname, "ifcfg-rh")) {
-			/* The plugin ibft is not explicitly mentioned but we just enabled "ifcfg-rh".
-			 * Enable "ibft" by default after "ifcfg-rh". */
-			pname = "ibft";
-			add_ibft = FALSE;
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
 
-			success = add_plugin_load_file (self, "ibft", error);
-			if (!success)
-				break;
-		}
+	/* all remaining (so far unseen) dirty entries are appended to the merged list.
+	 * (append means lower priority). */
+
+	c_list_splice (&sett_conn_entry->sd_lst_head, &sett_conn_entry->dirty_sd_lst_head);
+
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+
+	/* we drop the entries that are no longer "alive" (meaning, they no longer
+	 * indicate a connection and are not a tombstone). */
+	c_list_for_each_entry_safe (sd, sd_safe, &sett_conn_entry->sd_lst_head, sd_lst) {
+		if (!_storage_data_is_alive (sd))
+			_storage_data_destroy (sd);
 	}
 
-	/* If keyfile plugin was not among configured plugins, add it as the last one */
-	if (!keyfile_added && success)
-		add_plugin_keyfile (self);
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+	nm_assert (c_list_is_empty (&sett_conn_entry->dirty_sd_lst_head));
 
-	return success;
+	/* as last, we sort the entries. Note that this is a stable-sort... */
+	c_list_sort (&sett_conn_entry->sd_lst_head,
+	             _sett_conn_entry_sds_update_cmp,
+	             NM_SETTINGS_GET_PRIVATE (self)->plugins);
+
+	nm_assert_storage_data_lst (&sett_conn_entry->sd_lst_head);
+	nm_assert (c_list_is_empty (&sett_conn_entry->dirty_sd_lst_head));
 }
 
-static void
-connection_updated (NMSettingsConnection *connection, gboolean by_user, gpointer user_data)
+/*****************************************************************************/
+
+static NMConnection *
+_connection_changed_normalize_connection (NMSettingsStorage *storage,
+                                          NMConnection *connection,
+                                          GVariant *secrets_to_merge,
+                                          NMConnection **out_connection_cloned)
 {
-	g_signal_emit (NM_SETTINGS (user_data),
-	               signals[CONNECTION_UPDATED],
-	               0,
-	               connection,
-	               by_user);
+	gs_unref_object NMConnection *connection_cloned = NULL;
+	gs_free_error GError *error = NULL;
+	const char *uuid;
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (storage));
+	nm_assert (out_connection_cloned && !*out_connection_cloned);
+
+	if (!connection)
+		return NULL;
+
+	nm_assert (NM_IS_CONNECTION (connection));
+
+	uuid = nm_settings_storage_get_uuid (storage);
+
+	if (secrets_to_merge) {
+		connection_cloned = nm_simple_connection_new_clone (connection);
+		connection = connection_cloned;
+		nm_connection_update_secrets (connection,
+		                              NULL,
+		                              secrets_to_merge,
+		                              NULL);
+	}
+
+	if (!_nm_connection_ensure_normalized (connection,
+	                                       !!connection_cloned,
+	                                       uuid,
+	                                       FALSE,
+	                                       connection_cloned ? NULL : &connection_cloned,
+	                                       &error)) {
+		/* this is most likely a bug in the plugin. It provided a connection that no longer verifies.
+		 * Well, I guess it could also happen when we merge @secrets_to_merge above. In any case
+		 * somewhere is a bug. */
+		_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: plugin provided an invalid connection: %s",
+		       uuid,
+		       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+		       error->message);
+		return NULL;
+	}
+	if (connection_cloned)
+		connection = connection_cloned;
+
+	*out_connection_cloned = g_steal_pointer (&connection_cloned);
+	return connection;
 }
 
+/*****************************************************************************/
+
 static void
-connection_flags_changed (NMSettingsConnection *connection,
-                          gpointer user_data)
+_connection_changed_update (NMSettings *self,
+                            SettConnEntry *sett_conn_entry,
+                            NMConnection *connection,
+                            NMSettingsConnectionIntFlags sett_flags,
+                            NMSettingsConnectionIntFlags sett_mask,
+                            NMSettingsConnectionUpdateReason update_reason)
 {
-	g_signal_emit (NM_SETTINGS (user_data),
-	               signals[CONNECTION_FLAGS_CHANGED],
-	               0,
-	               connection);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection_old = NULL;
+	NMSettingsStorage *storage = sett_conn_entry->storage;
+	gs_unref_object NMSettingsConnection *sett_conn = g_object_ref (sett_conn_entry->sett_conn);
+	const char *path;
+	gboolean is_new;
+
+	nm_assert (!NM_FLAGS_ANY (sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~sett_mask));
+
+	is_new = c_list_is_empty (&sett_conn->_connections_lst);
+
+	_LOGT ("update[%s]: %s connection \"%s\" ("NM_SETTINGS_STORAGE_PRINT_FMT")",
+	       nm_settings_storage_get_uuid (storage),
+	       is_new ? "adding" : "updating",
+	       nm_connection_get_id (connection),
+	       NM_SETTINGS_STORAGE_PRINT_ARG (storage));
+
+	_nm_settings_connection_set_storage (sett_conn, storage);
+
+	_nm_settings_connection_set_connection (sett_conn, connection, &connection_old, update_reason);
+
+
+	if (is_new) {
+		_nm_settings_connection_register_kf_dbs (sett_conn,
+		                                         priv->kf_db_timestamps,
+		                                         priv->kf_db_seen_bssids);
+
+		_clear_connections_cached_list (priv);
+		c_list_link_tail (&priv->connections_lst_head, &sett_conn->_connections_lst);
+		priv->connections_len++;
+		priv->connections_generation++;
+
+		g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED, G_CALLBACK (connection_flags_changed), self);
+	}
+
+	if (NM_FLAGS_HAS (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT)) {
+		nm_settings_connection_autoconnect_blocked_reason_set (sett_conn,
+		                                                       NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+		                                                       TRUE);
+	}
+
+	sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
+	if (nm_settings_connection_check_visibility (sett_conn, priv->session_monitor))
+		sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE;
+	else
+		nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE));
+
+	sett_mask |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
+	if (nm_settings_storage_is_keyfile_run (storage))
+		sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED;
+	else {
+		nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_UNSAVED));
+
+		/* Profiles that don't reside in /run, are never nm-generated
+		 * and never volatile. */
+		sett_mask |= (  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		              | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+		sett_flags &= ~(  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+	}
+
+	nm_settings_connection_set_flags_full (sett_conn,
+	                                       sett_mask,
+	                                       sett_flags);
+
+	if (is_new) {
+		/* FIXME(shutdown): The NMSettings instance can't be disposed
+		 * while there is any exported connection. Ideally we should
+		 * unexport all connections on NMSettings' disposal, but for now
+		 * leak @self on termination when there are connections alive. */
+		path = nm_dbus_object_export (NM_DBUS_OBJECT (sett_conn));
+	} else
+		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn));
+
+	if (   is_new
+	    || connection_old) {
+		nm_utils_log_connection_diff (nm_settings_connection_get_connection (sett_conn),
+		                              connection_old,
+		                              LOGL_DEBUG,
+		                              LOGD_CORE,
+		                              is_new ? "new connection" : "update connection",
+		                              "++ ",
+		                              path);
+	}
+
+	if (is_new) {
+		nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+		                            &interface_info_settings,
+		                            &signal_info_new_connection,
+		                            "(o)",
+		                            path);
+		_notify (self, PROP_CONNECTIONS);
+		_emit_connection_added (self, sett_conn);
+	} else {
+		_nm_settings_connection_emit_dbus_signal_updated (sett_conn);
+		_emit_connection_updated (self, sett_conn, update_reason);
+	}
+
+	if (   !priv->started
+	    || priv->startup_complete_idx) {
+		if (nm_settings_has_connection (self, sett_conn))
+			_startup_complete_notify_connection (self, sett_conn, FALSE);
+	}
 }
 
 static void
-connection_removed (NMSettingsConnection *connection, gpointer user_data)
+_connection_changed_delete (NMSettings *self,
+                            NMSettingsStorage *storage,
+                            NMSettingsConnection *sett_conn,
+                            gboolean allow_add_to_no_auto_default)
 {
-	NMSettings *self = NM_SETTINGS (user_data);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_unref_object NMConnection *connection_for_agents = NULL;
 	NMDevice *device;
+	const char *uuid;
 
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (!c_list_is_empty (&connection->_connections_lst));
-	nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (c_list_contains (&priv->connections_lst_head, &sett_conn->_connections_lst));
+	nm_assert (nm_dbus_object_is_exported (NM_DBUS_OBJECT (sett_conn)));
+
+	uuid = nm_settings_storage_get_uuid (storage);
+
+	_LOGT ("update[%s]: delete connection \"%s\" ("NM_SETTINGS_STORAGE_PRINT_FMT")",
+	       uuid,
+	       nm_settings_connection_get_id (sett_conn),
+	       NM_SETTINGS_STORAGE_PRINT_ARG (storage));
 
-	/* When the default wired connection is removed (either deleted or saved to
-	 * a new persistent connection by a plugin), write the MAC address of the
+	/* When the default wired sett_conn is removed (either deleted or saved to
+	 * a new persistent sett_conn by a plugin), write the MAC address of the
 	 * wired device to the config file and don't create a new default wired
-	 * connection for that device again.
+	 * sett_conn for that device again.
 	 */
-	device = g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ());
+	device = nm_settings_connection_default_wired_get_device (sett_conn);
 	if (device)
-		default_wired_clear_tag (self, device, connection, TRUE);
+		default_wired_clear_tag (self, device, sett_conn, allow_add_to_no_auto_default);
 
-	/* Disconnect signal handlers, as plugins might still keep references
-	 * to the connection (and thus the signal handlers would still be live)
-	 * even after NMSettings has dropped all its references.
-	 */
-
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_removed), self);
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_updated), self);
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_flags_changed), self);
-	if (!priv->startup_complete)
-		g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_ready_changed), self);
+	g_signal_handlers_disconnect_by_func (sett_conn, G_CALLBACK (connection_flags_changed), self);
 
-	/* Forget about the connection internally */
 	_clear_connections_cached_list (priv);
+	c_list_unlink (&sett_conn->_connections_lst);
 	priv->connections_len--;
-	c_list_unlink (&connection->_connections_lst);
+	priv->connections_generation++;
 
-	if (priv->connections_loaded) {
-		_notify (self, PROP_CONNECTIONS);
+	/* Tell agents to remove secrets for this connection */
+	connection_for_agents = nm_simple_connection_new_clone (nm_settings_connection_get_connection (sett_conn));
+	nm_connection_clear_secrets (connection_for_agents);
+	nm_agent_manager_delete_secrets (priv->agent_mgr,
+	                                 nm_dbus_object_get_path (NM_DBUS_OBJECT (self)),
+	                                 connection_for_agents);
 
-		nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
-		                            &interface_info_settings,
-		                            &signal_info_connection_removed,
-		                            "(o)",
-		                            nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)));
-	}
+	_notify (self, PROP_CONNECTIONS);
+	_nm_settings_connection_emit_dbus_signal_removed (sett_conn);
+	nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
+	                            &interface_info_settings,
+	                            &signal_info_connection_removed,
+	                            "(o)",
+	                            nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)));
 
-	nm_dbus_object_unexport (NM_DBUS_OBJECT (connection));
+	nm_dbus_object_unexport (NM_DBUS_OBJECT (sett_conn));
 
-	if (priv->connections_loaded)
-		g_signal_emit (self, signals[CONNECTION_REMOVED], 0, connection);
+	nm_settings_connection_set_flags (sett_conn,
+	                                    NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE
+	                                  | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE,
+	                                  FALSE);
 
-	check_startup_complete (self);
+	_emit_connection_removed (self, sett_conn);
 
-	g_object_unref (connection);
+	_nm_settings_connection_cleanup_after_remove (sett_conn);
 
-	g_object_unref (self);       /* Balanced by a ref in claim_connection() */
-}
+	nm_key_file_db_remove_key (priv->kf_db_timestamps, uuid);
+	nm_key_file_db_remove_key (priv->kf_db_seen_bssids, uuid);
 
-#define NM_DBUS_SERVICE_OPENCONNECT    "org.freedesktop.NetworkManager.openconnect"
-#define NM_OPENCONNECT_KEY_GATEWAY "gateway"
-#define NM_OPENCONNECT_KEY_COOKIE "cookie"
-#define NM_OPENCONNECT_KEY_GWCERT "gwcert"
-#define NM_OPENCONNECT_KEY_XMLCONFIG "xmlconfig"
-#define NM_OPENCONNECT_KEY_LASTHOST "lasthost"
-#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect"
-#define NM_OPENCONNECT_KEY_CERTSIGS "certsigs"
+	if (   !priv->started
+	    || priv->startup_complete_idx)
+		_startup_complete_notify_connection (self, sett_conn, TRUE);
+}
 
 static void
-openconnect_migrate_hack (NMConnection *connection)
+_connection_changed_process_one (NMSettings *self,
+                                 SettConnEntry *sett_conn_entry,
+                                 gboolean allow_add_to_no_auto_default,
+                                 NMSettingsConnectionIntFlags sett_flags,
+                                 NMSettingsConnectionIntFlags sett_mask,
+                                 gboolean override_sett_flags,
+                                 NMSettingsConnectionUpdateReason update_reason)
 {
-	NMSettingVpn *s_vpn;
-	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NOT_SAVED;
+	StorageData *sd_best;
 
-	/* Huge hack.  There were some openconnect changes that needed to happen
-	 * pretty late, too late to get into distros.  Migration has already
-	 * happened for many people, and their secret flags are wrong.  But we
-	 * don't want to requrie re-migration, so we have to fix it up here. Ugh.
-	 */
+	c_list_unlink (&sett_conn_entry->sce_dirty_lst);
+
+	_sett_conn_entry_sds_update (self, sett_conn_entry);
+
+	sd_best = c_list_first_entry (&sett_conn_entry->sd_lst_head, StorageData, sd_lst);;
+
+	if (   !sd_best
+	    || !sd_best->connection) {
+		gs_unref_object NMSettingsConnection *sett_conn = NULL;
+		gs_unref_object NMSettingsStorage *storage = NULL;
+
+		if (!sett_conn_entry->sett_conn) {
+
+			if (!sd_best) {
+				_sett_conn_entries_remove_and_destroy (self, sett_conn_entry);
+				return;
+			}
 
-	s_vpn = nm_connection_get_setting_vpn (connection);
-	if (s_vpn == NULL)
+			if (sett_conn_entry->storage != sd_best->storage) {
+				_LOGT ("update[%s]: shadow UUID ("NM_SETTINGS_STORAGE_PRINT_FMT")",
+				       sett_conn_entry->uuid,
+				       NM_SETTINGS_STORAGE_PRINT_ARG (sd_best->storage));
+			}
+
+			nm_g_object_ref_set (&sett_conn_entry->storage, sd_best->storage);
+			return;
+		}
+
+		sett_conn = g_steal_pointer (&sett_conn_entry->sett_conn);
+		if (sd_best) {
+			storage = g_object_ref (sd_best->storage);
+			nm_g_object_ref_set (&sett_conn_entry->storage, storage);
+			nm_assert_valid_settings_storage (NULL, storage);
+		} else {
+			storage = g_object_ref (sett_conn_entry->storage);
+			_sett_conn_entries_remove_and_destroy (self, sett_conn_entry);
+		}
+
+		_connection_changed_delete (self, storage, sett_conn, allow_add_to_no_auto_default);
 		return;
+	}
 
-	if (g_strcmp0 (nm_setting_vpn_get_service_type (s_vpn), NM_DBUS_SERVICE_OPENCONNECT) == 0) {
-		/* These are different for every login session, and should not be stored */
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL);
+	if (override_sett_flags) {
+		NMSettingsConnectionIntFlags s_f, s_m;
 
-		/* These are purely internal data for the auth-dialog, and should be stored */
-		flags = 0;
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL);
-		nm_setting_set_secret_flags (NM_SETTING (s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL);
+		nm_settings_storage_load_sett_flags (sd_best->storage, &s_f, &s_m);
+
+		nm_assert (!NM_FLAGS_ANY (s_f, ~s_m));
+
+		sett_mask |= s_m;
+		sett_flags = (sett_flags & ~s_m) | (s_f & s_m);
 	}
+
+	nm_g_object_ref_set (&sett_conn_entry->storage, sd_best->storage);
+
+	if (!sett_conn_entry->sett_conn)
+		sett_conn_entry->sett_conn = nm_settings_connection_new ();
+
+	_connection_changed_update (self,
+	                            sett_conn_entry,
+	                            sd_best->connection,
+	                            sett_flags,
+	                            sett_mask,
+	                            update_reason);
 }
 
 static void
-claim_connection (NMSettings *self, NMSettingsConnection *sett_conn)
+_connection_changed_process_all_dirty (NMSettings *self,
+                                       gboolean allow_add_to_no_auto_default,
+                                       NMSettingsConnectionIntFlags sett_flags,
+                                       NMSettingsConnectionIntFlags sett_mask,
+                                       gboolean override_sett_flags,
+                                       NMSettingsConnectionUpdateReason update_reason)
 {
-	NMSettingsPrivate *priv;
-	GError *error = NULL;
-	const char *path;
-	NMSettingsConnection *existing;
-
-	g_return_if_fail (NM_IS_SETTINGS (self));
-	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
-	g_return_if_fail (!nm_dbus_object_is_exported (NM_DBUS_OBJECT (sett_conn)));
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	SettConnEntry *sett_conn_entry;
+
+	while ((sett_conn_entry = c_list_first_entry (&priv->sce_dirty_lst_head, SettConnEntry, sce_dirty_lst))) {
+		_connection_changed_process_one (self,
+		                                 sett_conn_entry,
+		                                 allow_add_to_no_auto_default,
+		                                 sett_flags,
+		                                 sett_mask,
+		                                 override_sett_flags,
+		                                 update_reason);
+	}
+}
 
-	priv = NM_SETTINGS_GET_PRIVATE (self);
+static SettConnEntry *
+_connection_changed_track (NMSettings *self,
+                           NMSettingsStorage *storage,
+                           NMConnection *connection,
+                           gboolean prioritize)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	SettConnEntry *sett_conn_entry;
+	StorageData *sd;
+	const char *uuid;
 
-	/* prevent duplicates */
-	if (!c_list_is_empty (&sett_conn->_connections_lst)) {
-		nm_assert (c_list_contains (&priv->connections_lst_head, &sett_conn->_connections_lst));
-		return;
+	nm_assert_valid_settings_storage (NULL, storage);
+
+	uuid = nm_settings_storage_get_uuid (storage);
+
+	nm_assert (!connection || NM_IS_CONNECTION (connection));
+	nm_assert (!connection || (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+	nm_assert (!connection || nm_streq0 (uuid, nm_connection_get_uuid (connection)));
+
+	nmtst_connection_assert_unchanging (connection);
+
+	sett_conn_entry =    _sett_conn_entries_get (self, uuid)
+	                  ?: _sett_conn_entries_create_and_add (self, uuid);
+
+	if (_LOGT_ENABLED ()) {
+		const char *filename;
+		const NMSettingsMetaData *meta_data;
+		const char *shadowed_storage;
+		gboolean shadowed_owned;
+
+		filename = nm_settings_storage_get_filename (storage);
+		if (connection) {
+			shadowed_storage = nm_settings_storage_get_shadowed_storage (storage, &shadowed_owned);
+			_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event with connection \"%s\"%s%s%s%s%s%s",
+			       sett_conn_entry->uuid,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       nm_connection_get_id (connection),
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""),
+			       NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", ""));
+		} else if ((meta_data = nm_settings_storage_is_meta_data (storage))) {
+			nm_assert (meta_data->is_tombstone);
+			shadowed_storage = nm_settings_storage_get_shadowed_storage (storage, &shadowed_owned);
+			_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for %shiding profile%s%s%s%s%s%s",
+			       sett_conn_entry->uuid,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       nm_settings_storage_is_meta_data_alive  (storage) ? "" : "dropping ",
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""),
+			       NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", ""));
+		} else {
+			_LOGT ("storage[%s,"NM_SETTINGS_STORAGE_PRINT_FMT"]: change event for dropping profile%s%s%s",
+			       sett_conn_entry->uuid,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""));
+		}
 	}
 
-	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-	if (!nm_connection_normalize (nm_settings_connection_get_connection (sett_conn), NULL, NULL, &error)) {
-		_LOGW ("plugin provided invalid connection: %s", error->message);
-		g_error_free (error);
-		return;
+	/* see _sett_conn_entry_sds_update() for why we append the new events
+	 * and leave existing ones at their position. */
+	sd = _storage_data_find_in_lst (&sett_conn_entry->dirty_sd_lst_head, storage);
+	if (sd)
+		nm_g_object_ref_set (&sd->connection, connection);
+	else {
+		sd = _storage_data_new_stale (storage, connection);
+		c_list_link_tail (&sett_conn_entry->dirty_sd_lst_head, &sd->sd_lst);
 	}
 
-	existing = nm_settings_get_connection_by_uuid (self, nm_settings_connection_get_uuid (sett_conn));
-	if (existing) {
-		/* Cannot add duplicate connections per UUID. Just return without action and
-		 * log a warning.
-		 *
-		 * This means, that plugins must not provide duplicate connections (UUID).
-		 * In fact, none of the plugins currently would do that.
-		 *
-		 * But globaly, over different setting plugins, there could be duplicates
-		 * without the individual plugins being aware. Don't handle that at all, just
-		 * error out. That should not happen unless the admin misconfigured the system
-		 * to create conflicting connections. */
-		_LOGW ("plugin provided duplicate connection with UUID %s",
-		       nm_settings_connection_get_uuid (sett_conn));
-		return;
+	if (prioritize) {
+		StorageData *sd2;
+
+		/* only one entry can be prioritized. */
+		c_list_for_each_entry (sd2, &sett_conn_entry->dirty_sd_lst_head, sd_lst)
+			sd2->prioritize = FALSE;
+		sd->prioritize = TRUE;
 	}
 
-	/* Read timestamp from look-aside file and put it into the connection's data */
-	nm_settings_connection_read_and_fill_timestamp (sett_conn);
+	nm_c_list_move_tail (&priv->sce_dirty_lst_head, &sett_conn_entry->sce_dirty_lst);
 
-	/* Read seen-bssids from look-aside file and put it into the connection's data */
-	nm_settings_connection_read_and_fill_seen_bssids (sett_conn);
+	return sett_conn_entry;
+}
 
-	/* Ensure its initial visibility is up-to-date */
-	nm_settings_connection_recheck_visibility (sett_conn);
+/*****************************************************************************/
 
-	/* Evil openconnect migration hack */
-	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-	openconnect_migrate_hack (nm_settings_connection_get_connection (sett_conn));
+static void
+_plugin_connections_reload_cb (NMSettingsPlugin *plugin,
+                               NMSettingsStorage *storage,
+                               NMConnection *connection,
+                               gpointer user_data)
+{
+	_connection_changed_track (user_data, storage, connection, FALSE);
+}
 
-	/* This one unexports the connection, it needs to run late to give the active
-	 * connection a chance to deal with its reference to this settings connection. */
-	g_signal_connect_after (sett_conn, NM_SETTINGS_CONNECTION_REMOVED,
-	                        G_CALLBACK (connection_removed), self);
-	g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
-	                  G_CALLBACK (connection_updated), self);
-	g_signal_connect (sett_conn, NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
-	                  G_CALLBACK (connection_flags_changed),
-	                  self);
-	if (!priv->startup_complete) {
-		g_signal_connect (sett_conn, "notify::" NM_SETTINGS_CONNECTION_READY,
-		                  G_CALLBACK (connection_ready_changed),
-		                  self);
+static void
+_plugin_connections_reload (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSList *iter;
+
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		nm_settings_plugin_reload_connections (iter->data,
+		                                       _plugin_connections_reload_cb,
+		                                       self);
 	}
 
-	_clear_connections_cached_list (priv);
+	_connection_changed_process_all_dirty (self,
+	                                       FALSE,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       TRUE,
+	                                         NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+	                                       | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
 
-	g_object_ref (sett_conn);
-	/* FIXME(shutdown): The NMSettings instance can't be disposed
-	 * while there is any exported connection. Ideally we should
-	 * unexport all connections on NMSettings' disposal, but for now
-	 * leak @self on termination when there are connections alive. */
-	g_object_ref (self);
-	priv->connections_len++;
-	c_list_link_tail (&priv->connections_lst_head, &sett_conn->_connections_lst);
-
-	path = nm_dbus_object_export (NM_DBUS_OBJECT (sett_conn));
-
-	nm_utils_log_connection_diff (nm_settings_connection_get_connection (sett_conn),
-	                              NULL,
-	                              LOGL_DEBUG,
-	                              LOGD_CORE,
-	                              "new connection", "++ ",
-	                              path);
-
-	/* Only emit the individual connection-added signal after connections
-	 * have been initially loaded.
-	 */
-	if (priv->connections_loaded) {
-		nm_dbus_object_emit_signal (NM_DBUS_OBJECT (self),
-		                            &interface_info_settings,
-		                            &signal_info_new_connection,
-		                            "(o)",
-		                            nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)));
+	for (iter = priv->plugins; iter; iter = iter->next)
+		nm_settings_plugin_load_connections_done (iter->data);
+}
 
-		g_signal_emit (self, signals[CONNECTION_ADDED], 0, sett_conn);
-		_notify (self, PROP_CONNECTIONS);
+/*****************************************************************************/
+
+static gboolean
+_add_connection_to_first_plugin (NMSettings *self,
+                                 SettConnEntry *sett_conn_entry,
+                                 NMConnection *new_connection,
+                                 gboolean in_memory,
+                                 NMSettingsConnectionIntFlags sett_flags,
+                                 const char *shadowed_storage,
+                                 gboolean shadowed_owned,
+                                 NMSettingsStorage **out_new_storage,
+                                 NMConnection **out_new_connection,
+                                 GError **error)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GError *first_error = NULL;
+	GSList *iter;
+	const char *uuid;
+
+	uuid = nm_connection_get_uuid (new_connection);
+
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+		gs_unref_object NMSettingsStorage *storage = NULL;
+		gs_unref_object NMConnection *connection_to_add = NULL;
+		gs_unref_object NMConnection *connection_to_add_cloned = NULL;
+		NMConnection *connection_to_add_real = NULL;
+		gs_unref_variant GVariant *agent_owned_secrets = NULL;
+		gs_free_error GError *add_error = NULL;
+		gboolean success;
+		const char *filename;
+
+		if (!in_memory) {
+			NMSettingsStorage *conflicting_storage;
+
+			conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage (sett_conn_entry, plugin, NULL, priv->plugins);
+			if (conflicting_storage) {
+				/* we have a connection provided by a plugin with higher priority than the one
+				 * we would want to add the connection. We cannot do that, because doing so
+				 * would result in adding a connection that gets hidden by the existing profile.
+				 * Also, since we test the plugins in order of priority, all following plugins
+				 * are unsuitable.
+				 *
+				 * Multiple connection plugins are so cumbersome, especially if they are unable
+				 * to add the connection. I suggest to disable all plugins except keyfile. */
+				_LOGT ("add-connection: failed to add %s/'%s': there is an existing storage "NM_SETTINGS_STORAGE_PRINT_FMT" with higher priority",
+				       nm_connection_get_uuid (new_connection),
+				       nm_connection_get_id (new_connection),
+				       NM_SETTINGS_STORAGE_PRINT_ARG (conflicting_storage));
+				nm_assert (first_error);
+				break;
+			}
+		}
+
+		if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
+			success = nms_keyfile_plugin_add_connection (priv->keyfile_plugin,
+			                                             new_connection,
+			                                             in_memory,
+			                                             NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
+			                                             NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+			                                             shadowed_storage,
+			                                             shadowed_owned,
+			                                             &storage,
+			                                             &connection_to_add,
+			                                             &add_error);
+		} else {
+			if (in_memory)
+				continue;
+			nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED));
+			nm_assert (!NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+			success = nm_settings_plugin_add_connection (plugin,
+			                                             new_connection,
+			                                             &storage,
+			                                             &connection_to_add,
+			                                             &add_error);
+		}
+
+		if (!success) {
+			_LOGT ("add-connection: failed to add %s/'%s': %s",
+			       nm_connection_get_uuid (new_connection),
+			       nm_connection_get_id (new_connection),
+			       add_error->message);
+			if (!first_error)
+				first_error = g_steal_pointer (&add_error);
+			continue;
+		}
+
+		if (!nm_streq0 (nm_settings_storage_get_uuid (storage), uuid)) {
+			nm_assert_not_reached ();
+			continue;
+		}
+
+		agent_owned_secrets = nm_connection_to_dbus (new_connection,
+		                                               NM_CONNECTION_SERIALIZE_ONLY_SECRETS
+		                                             | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
+		connection_to_add_real = _connection_changed_normalize_connection (storage,
+		                                                                   connection_to_add,
+		                                                                   agent_owned_secrets,
+		                                                                   &connection_to_add_cloned);
+		if (!connection_to_add_real) {
+			nm_assert_not_reached ();
+			continue;
+		}
+
+		filename = nm_settings_storage_get_filename (storage);
+		_LOGT ("add-connection: successfully added connection %s,'%s' ("NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s",
+		       nm_settings_storage_get_uuid (storage),
+		       nm_connection_get_id (new_connection),
+		       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+		       NM_PRINT_FMT_QUOTED (filename, ", \"", filename, "\")", ")"));
+
+		*out_new_storage = g_steal_pointer (&storage);
+		*out_new_connection =    g_steal_pointer (&connection_to_add_cloned)
+		                      ?: g_steal_pointer (&connection_to_add);
+		nm_assert (NM_IS_CONNECTION (*out_new_connection));
+		return TRUE;
 	}
 
-	nm_settings_connection_added (sett_conn);
+	nm_assert (first_error);
+	g_propagate_error (error, first_error);
+	return FALSE;
 }
 
 static gboolean
-secrets_filter_cb (NMSetting *setting,
-                   const char *secret,
-                   NMSettingSecretFlags flags,
-                   gpointer user_data)
+_update_connection_to_plugin (NMSettings *self,
+                              NMSettingsStorage *storage,
+                              NMConnection *connection,
+                              NMSettingsConnectionIntFlags sett_flags,
+                              gboolean force_rename,
+                              const char *shadowed_storage,
+                              gboolean shadowed_owned,
+                              NMSettingsStorage **out_new_storage,
+                              NMConnection **out_new_connection,
+                              GError **error)
 {
-	NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT (user_data);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	NMSettingsPlugin *plugin;
+	gboolean success;
+
+	plugin = nm_settings_storage_get_plugin (storage);
+
+	if (plugin == (NMSettingsPlugin *) priv->keyfile_plugin) {
+		success = nms_keyfile_plugin_update_connection (priv->keyfile_plugin,
+		                                                storage,
+		                                                connection,
+		                                                NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED),
+		                                                NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE),
+		                                                shadowed_storage,
+		                                                shadowed_owned,
+		                                                force_rename,
+		                                                out_new_storage,
+		                                                out_new_connection,
+		                                                error);
+	} else {
+		nm_assert (!shadowed_storage);
+		nm_assert (!shadowed_owned);
+		success = nm_settings_plugin_update_connection (plugin,
+		                                                storage,
+		                                                connection,
+		                                                out_new_storage,
+		                                                out_new_connection,
+		                                                error);
+	}
 
-	/* Returns TRUE to remove the secret */
+	return success;
+}
 
-	/* Can't use bitops with SECRET_FLAG_NONE so handle that specifically */
-	if (   (flags == NM_SETTING_SECRET_FLAG_NONE)
-	    && (filter_flags == NM_SETTING_SECRET_FLAG_NONE))
-		return FALSE;
+static void
+_set_nmmeta_tombstone (NMSettings *self,
+                       const char *uuid,
+                       gboolean tombstone_on_disk,
+                       gboolean tombstone_in_memory,
+                       const char *shadowed_storage)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_unref_object NMSettingsStorage *tombstone_1_storage = NULL;
+	gs_unref_object NMSettingsStorage *tombstone_2_storage = NULL;
+
+	if (tombstone_on_disk) {
+		if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+		                                              FALSE,
+		                                              uuid,
+		                                              FALSE,
+		                                              TRUE,
+		                                              NULL,
+		                                              &tombstone_1_storage,
+		                                              NULL))
+			tombstone_in_memory = TRUE;
+		if (tombstone_1_storage)
+			_connection_changed_track (self, tombstone_1_storage, NULL, FALSE);
+	}
 
-	/* Otherwise if the secret has at least one of the desired flags keep it */
-	return (flags & filter_flags) ? FALSE : TRUE;
+	if (tombstone_in_memory) {
+		if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+		                                              FALSE,
+		                                              uuid,
+		                                              TRUE,
+		                                              TRUE,
+		                                              shadowed_storage,
+		                                              &tombstone_2_storage,
+		                                              NULL)) {
+			nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+			                                         TRUE,
+			                                         uuid,
+			                                         TRUE,
+			                                         TRUE,
+			                                         shadowed_storage,
+			                                         &tombstone_2_storage,
+			                                         NULL);
+		}
+		_connection_changed_track (self, tombstone_2_storage, NULL, FALSE);
+	}
 }
 
 /**
  * nm_settings_add_connection:
  * @self: the #NMSettings object
  * @connection: the source connection to create a new #NMSettingsConnection from
- * @save_to_disk: %TRUE to save the connection to disk immediately, %FALSE to
- * not save to disk
+ * @persist_mode: the persist-mode for this profile.
+ * @add_reason: the add-reason flags.
+ * @sett_flags: the settings flags to set.
+ * @out_sett_conn: (allow-none) (transfer none): the added settings connection on success.
  * @error: on return, a location to store any errors that may occur
  *
  * Creates a new #NMSettingsConnection for the given source @connection.
  * The returned object is owned by @self and the caller must reference
  * the object to continue using it.
  *
- * Returns: the new #NMSettingsConnection or %NULL
+ * Returns: TRUE on success.
  */
-NMSettingsConnection *
+gboolean
 nm_settings_add_connection (NMSettings *self,
                             NMConnection *connection,
-                            gboolean save_to_disk,
+                            NMSettingsConnectionPersistMode persist_mode,
+                            NMSettingsConnectionAddReason add_reason,
+                            NMSettingsConnectionIntFlags sett_flags,
+                            NMSettingsConnection **out_sett_conn,
                             GError **error)
 {
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GSList *iter;
-	NMSettingsConnection *added = NULL;
-	NMSettingsConnection *candidate = NULL;
+	NMSettingsPrivate *priv;
+	gs_unref_object NMConnection *connection_cloned_1 = NULL;
+	gs_unref_object NMConnection *new_connection = NULL;
+	gs_unref_object NMSettingsStorage *new_storage = NULL;
+	gs_unref_object NMSettingsStorage *shadowed_storage = NULL;
+	NMSettingsStorage *update_storage = NULL;
+	gs_free_error GError *local = NULL;
+	SettConnEntry *sett_conn_entry;
 	const char *uuid;
+	StorageData *sd;
+	gboolean new_in_memory;
+	gboolean success;
+	const char *shadowed_storage_filename = NULL;
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
+
+	new_in_memory = (persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK);
+
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+
+	if (NM_FLAGS_ANY (sett_flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
+	                              | NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+		nm_assert (new_in_memory);
+		new_in_memory = TRUE;
+	}
+
+	nm_assert (!NM_FLAGS_ANY (add_reason, ~NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT));
+
+	NM_SET_OUT (out_sett_conn, NULL);
 
 	uuid = nm_connection_get_uuid (connection);
 
-	/* Make sure a connection with this UUID doesn't already exist */
-	c_list_for_each_entry (candidate, &priv->connections_lst_head, _connections_lst) {
-		if (nm_streq0 (uuid, nm_settings_connection_get_uuid (candidate))) {
-			g_set_error_literal (error,
-			                     NM_SETTINGS_ERROR,
-			                     NM_SETTINGS_ERROR_UUID_EXISTS,
-			                     "A connection with this UUID already exists.");
-			return NULL;
+	sett_conn_entry = _sett_conn_entries_get (self, uuid);
+	if (_sett_conn_entry_get_conn (sett_conn_entry)) {
+		g_set_error_literal (error,
+		                     NM_SETTINGS_ERROR,
+		                     NM_SETTINGS_ERROR_UUID_EXISTS,
+		                     "a connection with this UUID already exists");
+		return FALSE;
+	}
+
+	if (!_nm_connection_ensure_normalized (connection,
+	                                       FALSE,
+	                                       NULL,
+	                                       FALSE,
+	                                       &connection_cloned_1,
+	                                       &local)) {
+		g_set_error (error,
+		             NM_SETTINGS_ERROR,
+		             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		             "connection is invalid: %s",
+		             local->message);
+		return FALSE;
+	}
+	if (connection_cloned_1)
+		connection = connection_cloned_1;
+
+	if (sett_conn_entry) {
+		c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+			if (!nm_settings_storage_is_meta_data (sd->storage))
+				continue;
+			shadowed_storage = nm_g_object_ref (_sett_conn_entry_find_shadowed_storage (sett_conn_entry,
+			                                                                            nm_settings_storage_get_shadowed_storage (sd->storage, NULL),
+			                                                                            NULL));
+			if (shadowed_storage) {
+				/* We have a nmmeta tombstone that indicates that a storage is shadowed.
+				 *
+				 * This happens when deleting a in-memory profile that was decoupled from
+				 * the persitant storage with NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED.
+				 * We need to take over this storage again... */
+				break;
+			}
 		}
 	}
 
-	/* 1) plugin writes the NMConnection to disk
-	 * 2) plugin creates a new NMSettingsConnection subclass with the settings
-	 *     from the NMConnection and returns it to the settings service
-	 * 3) settings service exports the new NMSettingsConnection subclass
-	 * 4) plugin notices that something on the filesystem has changed
-	 * 5) plugin reads the changes and ignores them because they will
-	 *     contain the same data as the connection it already knows about
-	 */
-	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
-		GError *add_error = NULL;
-		gs_unref_object NMConnection *simple = NULL;
-		gs_unref_variant GVariant *secrets = NULL;
-
-		/* Make a copy of agent-owned secrets because they won't be present in
-		 * the connection returned by plugins, as plugins return only what was
-		 * reread from the file. */
-		simple = nm_simple_connection_new_clone (connection);
-		nm_connection_clear_secrets_with_flags (simple,
-		                                        secrets_filter_cb,
-		                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
-		secrets = nm_connection_to_dbus (simple, NM_CONNECTION_SERIALIZE_ONLY_SECRETS);
-
-		added = nm_settings_plugin_add_connection (plugin, connection, save_to_disk, &add_error);
-		if (added) {
-			if (secrets) {
-				/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-				nm_connection_update_secrets (nm_settings_connection_get_connection (added),
-				                              NULL,
-				                              secrets,
-				                              NULL);
+	if (   shadowed_storage
+	    && !new_in_memory) {
+		NMSettingsStorage *conflicting_storage;
+
+		conflicting_storage = _sett_conn_entry_storage_find_conflicting_storage (sett_conn_entry,
+		                                                                         nm_settings_storage_get_plugin (shadowed_storage),
+		                                                                         shadowed_storage,
+		                                                                         priv->plugins);
+		if (conflicting_storage) {
+			/* We cannot add the profile as @shadowed_storage, because there is another, existing storage
+			 * that would hide it. Just add it as new storage. In general, this leads to duplication of profiles,
+			 * but the circumstances where this happens are very exotic (you need at least one additional settings
+			 * plugin, then going through the paths of making shadowed_storage in-memory-detached and delete it,
+			 * and finally adding the conflicting storage outside of NM and restart/reload). */
+			_LOGT ("ignore shadowed storage "NM_SETTINGS_STORAGE_PRINT_FMT" due to conflicting storage "NM_SETTINGS_STORAGE_PRINT_FMT,
+			       NM_SETTINGS_STORAGE_PRINT_ARG (shadowed_storage),
+			       NM_SETTINGS_STORAGE_PRINT_ARG (conflicting_storage));
+		} else
+			update_storage = shadowed_storage;
+	}
+
+	shadowed_storage_filename =   (   shadowed_storage
+	                               && !update_storage)
+	                            ? nm_settings_storage_get_filename_for_shadowed_storage (shadowed_storage)
+	                            : NULL;
+
+again_add_connection:
+
+	if (!update_storage) {
+		success = _add_connection_to_first_plugin (self,
+		                                           sett_conn_entry,
+		                                           connection,
+		                                           new_in_memory,
+		                                           sett_flags,
+		                                           shadowed_storage_filename,
+		                                           FALSE,
+		                                           &new_storage,
+		                                           &new_connection,
+		                                           &local);
+	} else {
+		success = _update_connection_to_plugin (self,
+		                                        update_storage,
+		                                        connection,
+		                                        sett_flags,
+		                                        FALSE,
+		                                        shadowed_storage_filename,
+		                                        FALSE,
+		                                        &new_storage,
+		                                        &new_connection,
+		                                        &local);
+		if (!success) {
+			if (!NMS_IS_KEYFILE_STORAGE (update_storage)) {
+				/* hm, the intended storage is not keyfile (it's ifcfg-rh). This settings
+				 * plugin may not support the new connection. So step back and retry adding
+				 * the profile anew. */
+				_LOGT ("failure to add profile as existing storage \"%s\": %s",
+				       nm_settings_storage_get_filename (update_storage),
+				       local->message);
+				update_storage = NULL;
+				g_clear_object (&shadowed_storage);
+				shadowed_storage_filename = NULL;
+				g_clear_error (&local);
+				goto again_add_connection;
 			}
-			claim_connection (self, added);
-			return added;
 		}
-		_LOGD ("Failed to add %s/'%s': %s",
-		       nm_connection_get_uuid (connection),
-		       nm_connection_get_id (connection),
-		       add_error->message);
-		g_clear_error (&add_error);
 	}
 
-	g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-	                     "No plugin supported adding this connection");
-	return NULL;
+	if (!success) {
+		if (!update_storage) {
+			g_set_error (error,
+			             NM_SETTINGS_ERROR,
+			             NM_SETTINGS_ERROR_FAILED,
+			             "failure adding connection: %s",
+			             local->message);
+		} else {
+			g_set_error (error,
+			             NM_SETTINGS_ERROR,
+			             NM_SETTINGS_ERROR_FAILED,
+			             "failure writing connection to existing storage \"%s\": %s",
+			             nm_settings_storage_get_filename (update_storage),
+			             local->message);
+		}
+		return FALSE;
+	}
+
+	sett_conn_entry = _connection_changed_track (self, new_storage, new_connection, TRUE);
+
+	c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+		const NMSettingsMetaData *meta_data;
+		gs_unref_object NMSettingsStorage *new_tombstone_storage = NULL;
+		gboolean in_memory;
+		gboolean simulate;
+
+		meta_data = nm_settings_storage_is_meta_data_alive (sd->storage);
+		if (   !meta_data
+		    || !meta_data->is_tombstone)
+			continue;
+
+		if (nm_settings_storage_is_keyfile_run (sd->storage))
+			in_memory = TRUE;
+		else {
+			if (nm_settings_storage_is_keyfile_run (new_storage)) {
+				/* Don't remove the file from /etc if we just wrote an in-memory connection */
+				continue;
+			}
+			in_memory = FALSE;
+		}
+
+		simulate = FALSE;
+again_delete_tombstone:
+		if (!nms_keyfile_plugin_set_nmmeta_tombstone (priv->keyfile_plugin,
+		                                              simulate,
+		                                              uuid,
+		                                              in_memory,
+		                                              FALSE,
+		                                              NULL,
+		                                              &new_tombstone_storage,
+		                                              NULL)) {
+			/* Ups, something went wrong. We really need to get rid of the tombstone. At least
+			 * forget about it in-memory. Upong next restart/reload, this might be reverted
+			 * however :( .*/
+			if (!simulate) {
+				simulate = TRUE;
+				goto again_delete_tombstone;
+			}
+		}
+		if (new_tombstone_storage)
+			_connection_changed_track (self, new_tombstone_storage, NULL, FALSE);
+	}
+
+	_connection_changed_process_all_dirty (self,
+	                                       FALSE,
+	                                       sett_flags,
+	                                       _NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK,
+	                                       FALSE,
+	                                         NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+	                                       | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
+	                                       | (  NM_FLAGS_HAS (add_reason, NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT)
+	                                          ? NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT
+	                                          : NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE));
+
+	nm_assert (sett_conn_entry == _sett_conn_entries_get (self, sett_conn_entry->uuid));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn_entry->sett_conn));
+
+	NM_SET_OUT (out_sett_conn, _sett_conn_entry_get_conn (sett_conn_entry));
+	return TRUE;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_settings_update_connection (NMSettings *self,
+                               NMSettingsConnection *sett_conn,
+                               NMConnection *connection,
+                               NMSettingsConnectionPersistMode persist_mode,
+                               NMSettingsConnectionIntFlags sett_flags,
+                               NMSettingsConnectionIntFlags sett_mask,
+                               NMSettingsConnectionUpdateReason update_reason,
+                               const char *log_context_name,
+                               GError **error)
+{
+	gs_unref_object NMConnection *connection_cloned_1 = NULL;
+	gs_unref_object NMConnection *new_connection_cloned = NULL;
+	gs_unref_object NMConnection *new_connection = NULL;
+	NMConnection *new_connection_real;
+	gs_unref_object NMSettingsStorage *cur_storage = NULL;
+	gs_unref_object NMSettingsStorage *new_storage = NULL;
+	NMSettingsStorage *drop_storage = NULL;
+	SettConnEntry *sett_conn_entry;
+	gboolean cur_in_memory;
+	gboolean new_in_memory;
+	const char *uuid;
+	gboolean tombstone_in_memory = FALSE;
+	gboolean tombstone_on_disk = FALSE;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn), FALSE);
+	g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), FALSE);
+
+	nm_assert (!NM_FLAGS_ANY (sett_mask, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~sett_mask));
+	nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY));
+
+	cur_storage = g_object_ref (nm_settings_connection_get_storage (sett_conn));
+
+	uuid = nm_settings_storage_get_uuid (cur_storage);
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage));
+
+	sett_conn_entry = _sett_conn_entries_get (self, uuid);
+
+	nm_assert (_sett_conn_entry_get_conn (sett_conn_entry) == sett_conn);
+
+	if (connection) {
+		gs_free_error GError *local = NULL;
+
+		if (!_nm_connection_ensure_normalized (connection,
+		                                       FALSE,
+		                                       uuid,
+		                                       TRUE,
+		                                       &connection_cloned_1,
+		                                       &local)) {
+			_LOGT ("update[%s]: %s: failed because profile is invalid: %s",
+			       nm_settings_storage_get_uuid (cur_storage),
+			       log_context_name,
+			       local->message);
+			g_set_error (error,
+			             NM_SETTINGS_ERROR,
+			             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+			             "connection is invalid: %s",
+			             local->message);
+			return FALSE;
+		}
+		if (connection_cloned_1)
+			connection = connection_cloned_1;
+	} else
+		connection = nm_settings_connection_get_connection (sett_conn);
+
+	cur_in_memory = nm_settings_storage_is_keyfile_run (cur_storage);
+
+	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP) {
+		persist_mode =   cur_in_memory
+		               ? NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY
+		               : NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+	}
+
+	if (   NM_FLAGS_HAS (sett_mask, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)
+	    && !NM_FLAGS_HAS (sett_flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+		NMDevice *device;
+
+		/* The connection has been changed by the user, it should no longer be
+		 * considered a default wired connection, and should no longer affect
+		 * the no-auto-default configuration option.
+		 */
+		device = nm_settings_connection_default_wired_get_device (sett_conn);
+		if (device) {
+			nm_assert (cur_in_memory);
+			nm_assert (!NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
+			                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+			                          | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+
+			default_wired_clear_tag (self, device, sett_conn, FALSE);
+
+			if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST)) {
+				/* making a default-wired-connection a regular connection implies persisting
+				 * it to disk (unless specified differently).
+				 *
+				 * Actually, this line is probably unreached, because we should not use
+				 * NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST to toggle the nm-generated
+				 * flag. */
+				nm_assert_not_reached ();
+				persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+			}
+		}
+	}
+
+	if (   persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST
+	    && NM_FLAGS_ANY (sett_mask,   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)
+	    && NM_FLAGS_ANY ((sett_flags ^ nm_settings_connection_get_flags (sett_conn)) & sett_mask,
+	                       NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	                     | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE)) {
+		/* we update the nm-generated/volatile setting of a profile (which is inherrently
+		 * in-memory. The caller did not request to persist this to disk, however we need
+		 * to store the flags in run. */
+		nm_assert (cur_in_memory);
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY;
+	}
+
+	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK)
+		new_in_memory = FALSE;
+	else if (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+	                                  NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED,
+	                                  NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY))
+		new_in_memory = TRUE;
+	else {
+		nm_assert (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST);
+		new_in_memory = cur_in_memory;
+	}
+
+	if (!new_in_memory) {
+		/* Persistent connections cannot be volatile nor nm-generated.
+		 *
+		 * That is obviously true for volatile, as it is enforced by Update2() API.
+		 *
+		 * For nm-generated profiles also, because the nm-generated flag is only stored
+		 * for in-memory profiles. If we would persist the profile to /etc it would loose
+		 * the nm-generated flag after restart/reload, and that cannot be right. If a profile
+		 * ends up on disk, the information who created it gets lost. */
+		nm_assert (!NM_FLAGS_ANY (sett_flags,   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		                                      | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE));
+		sett_mask |=   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+		sett_flags &= ~(  NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+		                | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE);
+	}
+
+	if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_NO_PERSIST) {
+		new_storage = g_object_ref (cur_storage);
+		new_connection_real = connection;
+		_LOGT ("update[%s]: %s: update profile \"%s\" (not persisted)",
+		       nm_settings_storage_get_uuid (cur_storage),
+		       log_context_name,
+		       nm_connection_get_id (connection));
+	} else {
+		NMSettingsStorage *shadowed_storage;
+		const char *cur_shadowed_storage_filename;
+		const char *new_shadowed_storage_filename = NULL;
+		gboolean cur_shadowed_owned;
+		gboolean new_shadowed_owned = FALSE;
+		NMSettingsStorage *update_storage = NULL;
+		gs_free_error GError *local = NULL;
+		gboolean success;
+
+		cur_shadowed_storage_filename = nm_settings_storage_get_shadowed_storage (cur_storage, &cur_shadowed_owned);
+
+		shadowed_storage = _sett_conn_entry_find_shadowed_storage (sett_conn_entry, cur_shadowed_storage_filename, cur_storage);
+		if (!shadowed_storage) {
+			cur_shadowed_storage_filename = NULL;
+			cur_shadowed_owned = FALSE;
+		}
+
+		if (   new_in_memory
+		    && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) {
+			if (cur_in_memory) {
+				drop_storage = shadowed_storage;
+				update_storage = cur_storage;
+			} else
+				drop_storage = cur_storage;
+		} else if (   !new_in_memory
+		           && cur_in_memory
+		           && shadowed_storage) {
+			drop_storage = cur_storage;
+			update_storage = shadowed_storage;
+		} else if (new_in_memory != cur_in_memory) {
+			if (!new_in_memory)
+				drop_storage = cur_storage;
+			else if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY)
+				drop_storage = cur_storage;
+			else {
+				nm_assert (NM_IN_SET (persist_mode, NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+				                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED));
+			}
+		} else if (nm_settings_storage_is_keyfile_lib (cur_storage)) {
+			/* the profile is a keyfile in /usr/lib. It cannot be overwritten, we must migrate it
+			 * from /usr/lib to /etc. */
+		} else
+			update_storage = cur_storage;
+
+		if (new_in_memory) {
+			if (persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY) {
+				/* pass */
+			} else if (!cur_in_memory) {
+				new_shadowed_storage_filename = nm_settings_storage_get_filename_for_shadowed_storage (cur_storage);
+				if (   new_shadowed_storage_filename
+				    && persist_mode != NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED)
+					new_shadowed_owned = TRUE;
+			} else {
+				new_shadowed_storage_filename = cur_shadowed_storage_filename;
+				if (   new_shadowed_storage_filename
+				    && persist_mode == NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY)
+					new_shadowed_owned = TRUE;
+			}
+		}
+
+		if (!update_storage) {
+			success = _add_connection_to_first_plugin (self,
+			                                           sett_conn_entry,
+			                                           connection,
+			                                           new_in_memory,
+			                                           sett_flags,
+			                                           new_shadowed_storage_filename,
+			                                           new_shadowed_owned,
+			                                           &new_storage,
+			                                           &new_connection,
+			                                           &local);
+		} else {
+			success = _update_connection_to_plugin (self,
+			                                        update_storage,
+			                                        connection,
+			                                        sett_flags,
+			                                        update_reason,
+			                                        new_shadowed_storage_filename,
+			                                        new_shadowed_owned,
+			                                        &new_storage,
+			                                        &new_connection,
+			                                        &local);
+		}
+		if (!success) {
+			gboolean ignore_failure;
+
+			ignore_failure = NM_FLAGS_ANY (update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_IGNORE_PERSIST_FAILURE);
+
+			_LOGT ("update[%s]: %s: %sfailure to %s connection \"%s\" on storage: %s",
+			       nm_settings_storage_get_uuid (cur_storage),
+			       log_context_name,
+			       ignore_failure ? "ignore " : "",
+			       update_storage ? "update" : "write",
+			       nm_connection_get_id (connection),
+			       local->message);
+			if (!ignore_failure) {
+				g_set_error (error,
+				             NM_SETTINGS_ERROR,
+				             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+				             "failed to %s connection: %s",
+				             update_storage ? "update" : "write",
+				             local->message);
+				return FALSE;
+			}
+
+			new_storage = g_object_ref (cur_storage);
+			new_connection_real = connection;
+		} else {
+			gs_unref_variant GVariant *agent_owned_secrets = NULL;
+
+			_LOGT ("update[%s]: %s: %s profile \"%s\"",
+			       nm_settings_storage_get_uuid (cur_storage),
+			       log_context_name,
+			       update_storage ? "update" : "write",
+			       nm_connection_get_id (connection));
+
+			nm_assert_valid_settings_storage (NULL, new_storage);
+			nm_assert (NM_IS_CONNECTION (new_connection));
+			nm_assert (nm_streq (uuid, nm_settings_storage_get_uuid (new_storage)));
+
+
+			agent_owned_secrets = nm_connection_to_dbus (connection,
+			                                               NM_CONNECTION_SERIALIZE_ONLY_SECRETS
+			                                             | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED);
+			new_connection_real = _connection_changed_normalize_connection (new_storage,
+			                                                                new_connection,
+			                                                                agent_owned_secrets,
+			                                                                &new_connection_cloned);
+			if (!new_connection_real) {
+				nm_assert_not_reached ();
+				new_connection_real = new_connection;
+			}
+		}
+	}
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (new_storage));
+	nm_assert (NM_IS_CONNECTION (new_connection_real));
+
+	_connection_changed_track (self, new_storage, new_connection_real, TRUE);
+
+	if (   drop_storage
+	    && drop_storage != new_storage) {
+		gs_free_error GError *local = NULL;
+
+		if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (drop_storage),
+		                                           drop_storage,
+		                                           &local)) {
+			const char *filename;
+
+			filename = nm_settings_storage_get_filename (drop_storage);
+			_LOGT ("update[%s]: failed to delete moved storage "NM_SETTINGS_STORAGE_PRINT_FMT"%s%s%s: %s",
+			       nm_settings_storage_get_uuid (drop_storage),
+			       NM_SETTINGS_STORAGE_PRINT_ARG (drop_storage),
+			       NM_PRINT_FMT_QUOTED (filename, " (file \"", filename, "\")", ""),
+			       local->message);
+			/* there is no aborting back form this. We must get rid of the connection and
+			 * cannot do better than log a message. Proceed, but remember to write tombstones. */
+			if (nm_settings_storage_is_keyfile_run (cur_storage))
+				tombstone_in_memory = TRUE;
+			else
+				tombstone_on_disk = TRUE;
+		} else
+			_connection_changed_track (self, drop_storage, NULL, FALSE);
+	}
+
+	_set_nmmeta_tombstone (self,
+	                       uuid,
+	                       tombstone_on_disk,
+	                       tombstone_in_memory,
+	                       NULL);
+
+	_connection_changed_process_all_dirty (self,
+	                                       FALSE,
+	                                       sett_flags,
+	                                       sett_mask,
+	                                       FALSE,
+	                                       update_reason);
+
+	return TRUE;
 }
 
+void
+nm_settings_delete_connection (NMSettings *self,
+                               NMSettingsConnection *sett_conn,
+                               gboolean allow_add_to_no_auto_default)
+{
+	NMSettingsStorage *cur_storage;
+	NMSettingsStorage *shadowed_storage;
+	NMSettingsStorage *shadowed_storage_unowned = NULL;
+	NMSettingsStorage *drop_storages[2] = { };
+	gs_free_error GError *local = NULL;
+	SettConnEntry *sett_conn_entry;
+	const char *cur_shadowed_storage_filename;
+	const char *new_shadowed_storage_filename = NULL;
+	gboolean cur_shadowed_owned;
+	const char *uuid;
+	gboolean tombstone_in_memory = FALSE;
+	gboolean tombstone_on_disk = FALSE;
+	int i;
+
+	g_return_if_fail (NM_IS_SETTINGS (self));
+	g_return_if_fail (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	g_return_if_fail (nm_settings_has_connection (self, sett_conn));
+
+	cur_storage = nm_settings_connection_get_storage (sett_conn);
+
+	nm_assert (NM_IS_SETTINGS_STORAGE (cur_storage));
+
+	uuid = nm_settings_storage_get_uuid (cur_storage);
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	sett_conn_entry = _sett_conn_entries_get (self, uuid);
+
+	g_return_if_fail (sett_conn_entry);
+	nm_assert (sett_conn_entry->sett_conn == sett_conn);
+	g_return_if_fail (sett_conn_entry->storage == cur_storage);
+
+	if (NMS_IS_KEYFILE_STORAGE (cur_storage)) {
+		NMSKeyfileStorage *s = NMS_KEYFILE_STORAGE (cur_storage);
+
+		if (NM_IN_SET (s->storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
+		                                NMS_KEYFILE_STORAGE_TYPE_ETC))
+			drop_storages[0] = cur_storage;
+		else
+			tombstone_on_disk = TRUE;
+	} else
+		drop_storages[0] = cur_storage;
+
+	cur_shadowed_storage_filename = nm_settings_storage_get_shadowed_storage (cur_storage, &cur_shadowed_owned);
+
+	shadowed_storage = _sett_conn_entry_find_shadowed_storage (sett_conn_entry, cur_shadowed_storage_filename, cur_storage);
+	if (shadowed_storage) {
+		if (!cur_shadowed_owned)
+			shadowed_storage_unowned = g_steal_pointer (&shadowed_storage);
+	}
+	drop_storages[1] = shadowed_storage;
+
+	for (i = 0; i < (int) G_N_ELEMENTS (drop_storages); i++) {
+		NMSettingsStorage *storage;
+		StorageData *sd;
+
+		storage = drop_storages[i];
+		if (!storage)
+			continue;
+
+		if (!nm_settings_plugin_delete_connection (nm_settings_storage_get_plugin (storage),
+		                                           storage,
+		                                           &local)) {
+			_LOGT ("delete-connection: failed to delete storage "NM_SETTINGS_STORAGE_PRINT_FMT": %s",
+			       NM_SETTINGS_STORAGE_PRINT_ARG (storage),
+			       local->message);
+			g_clear_error (&local);
+			/* there is no aborting back form this. We must get rid of the connection and
+			 * cannot do better than log a message. Proceed, but remember to write tombstones. */
+			if (nm_settings_storage_is_keyfile_run (cur_storage))
+				tombstone_in_memory = TRUE;
+			else
+				tombstone_on_disk = TRUE;
+			sett_conn_entry = _sett_conn_entries_get (self, uuid);
+		} else
+			sett_conn_entry = _connection_changed_track (self, storage, NULL, FALSE);
+
+		c_list_for_each_entry (sd, &sett_conn_entry->sd_lst_head, sd_lst) {
+			if (NM_IN_SET (sd->storage, drop_storages[0],
+			                            drop_storages[1]))
+				continue;
+			if (!_storage_data_is_alive (sd))
+				continue;
+			if (nm_settings_storage_is_meta_data (sd->storage))
+				continue;
+
+			if (sd->storage == shadowed_storage_unowned) {
+				/* this only happens if we leak a profile on disk after NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_DETACHED.
+				 * We need to write a tombstone and remember the shadowed-storage. */
+				tombstone_in_memory = TRUE;
+				new_shadowed_storage_filename = nm_settings_storage_get_filename (shadowed_storage_unowned);
+				continue;
+			}
+
+			/* we have still conflicting storages. We need to hide them with tombstones. */
+			if (nm_settings_storage_is_keyfile_run (sd->storage)) {
+				tombstone_in_memory = TRUE;
+				continue;
+			}
+			tombstone_on_disk = TRUE;
+		}
+	}
+
+	_set_nmmeta_tombstone (self,
+	                       uuid,
+	                       tombstone_on_disk,
+	                       tombstone_in_memory,
+	                       new_shadowed_storage_filename);
+
+	_connection_changed_process_all_dirty (self,
+	                                       allow_add_to_no_auto_default,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+	                                       FALSE,
+	                                       NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE);
+}
+
+/*****************************************************************************/
+
 static void
 send_agent_owned_secrets (NMSettings *self,
                           NMSettingsConnection *sett_conn,
@@ -1118,9 +2386,8 @@ send_agent_owned_secrets (NMSettings *self,
 	 * Only send secrets to agents of the same UID that called update too.
 	 */
 	for_agent = nm_simple_connection_new_clone (nm_settings_connection_get_connection (sett_conn));
-	nm_connection_clear_secrets_with_flags (for_agent,
-	                                        secrets_filter_cb,
-	                                        GUINT_TO_POINTER (NM_SETTING_SECRET_FLAG_AGENT_OWNED));
+	_nm_connection_clear_secrets_by_secret_flags (for_agent,
+	                                              NM_SETTING_SECRET_FLAG_AGENT_OWNED);
 	nm_agent_manager_save_secrets (priv->agent_mgr,
 	                               nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn)),
 	                               for_agent,
@@ -1129,52 +2396,50 @@ send_agent_owned_secrets (NMSettings *self,
 
 static void
 pk_add_cb (NMAuthChain *chain,
-           GError *chain_error,
            GDBusMethodInvocation *context,
            gpointer user_data)
 {
 	NMSettings *self = NM_SETTINGS (user_data);
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	NMAuthCallResult result;
-	GError *error = NULL;
+	gs_free_error GError *error = NULL;
 	NMConnection *connection = NULL;
 	gs_unref_object NMSettingsConnection *added = NULL;
 	NMSettingsAddCallback callback;
 	gpointer callback_data;
 	NMAuthSubject *subject;
 	const char *perm;
-	gboolean save_to_disk;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auths = g_slist_remove (priv->auths, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
 	perm = nm_auth_chain_get_data (chain, "perm");
-	g_assert (perm);
+	nm_assert (perm);
+
 	result = nm_auth_chain_get_result (chain, perm);
 
-	if (chain_error) {
-		error = g_error_new (NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_FAILED,
-		                     "Error checking authorization: %s",
-		                     chain_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
 		                             "Insufficient privileges.");
 	} else {
 		/* Authorized */
 		connection = nm_auth_chain_get_data (chain, "connection");
-		nm_assert (connection);
+		nm_assert (NM_IS_CONNECTION (connection));
 
-		save_to_disk = GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "save-to-disk"));
-		added = nm_settings_add_connection (self, connection, save_to_disk, &error);
+		nm_settings_add_connection (self,
+		                            connection,
+		                            GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "persist-mode")),
+		                            GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "add-reason")),
+		                            GPOINTER_TO_UINT (nm_auth_chain_get_data (chain, "sett-flags")),
+		                            &added,
+		                            &error);
 
 		/* The callback may remove the connection from the settings manager (e.g.
 		 * because it's found to be incompatible with the device on AddAndActivate).
 		 * But we need to keep it alive for a bit longer, precisely to check wehther
 		 * it's still known to the setting manager. */
-		g_object_ref (added);
+		nm_g_object_ref (added);
 	}
 
 	callback = nm_auth_chain_get_data (chain, "callback");
@@ -1184,49 +2449,17 @@ pk_add_cb (NMAuthChain *chain,
 	callback (self, added, error, context, subject, callback_data);
 
 	/* Send agent-owned secrets to the agents */
-	if (!error && added && nm_settings_has_connection (self, added))
+	if (   added
+	    && nm_settings_has_connection (self, added))
 		send_agent_owned_secrets (self, added, subject);
-
-	g_clear_error (&error);
-	nm_auth_chain_destroy (chain);
-}
-
-/* FIXME: remove if/when kernel supports adhoc wpa */
-static gboolean
-is_adhoc_wpa (NMConnection *connection)
-{
-	NMSettingWireless *s_wifi;
-	NMSettingWirelessSecurity *s_wsec;
-	const char *mode, *key_mgmt;
-
-	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
-	 * and turns them into open networks.  It's been this way since at least
-	 * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
-	 */
-
-	s_wifi = nm_connection_get_setting_wireless (connection);
-	if (!s_wifi)
-		return FALSE;
-
-	mode = nm_setting_wireless_get_mode (s_wifi);
-	if (g_strcmp0 (mode, NM_SETTING_WIRELESS_MODE_ADHOC) != 0)
-		return FALSE;
-
-	s_wsec = nm_connection_get_setting_wireless_security (connection);
-	if (!s_wsec)
-		return FALSE;
-
-	key_mgmt = nm_setting_wireless_security_get_key_mgmt (s_wsec);
-	if (g_strcmp0 (key_mgmt, "wpa-none") != 0)
-		return FALSE;
-
-	return TRUE;
 }
 
 void
 nm_settings_add_connection_dbus (NMSettings *self,
                                  NMConnection *connection,
-                                 gboolean save_to_disk,
+                                 NMSettingsConnectionPersistMode persist_mode,
+                                 NMSettingsConnectionAddReason add_reason,
+                                 NMSettingsConnectionIntFlags sett_flags,
                                  NMAuthSubject *subject,
                                  GDBusMethodInvocation *context,
                                  NMSettingsAddCallback callback,
@@ -1242,8 +2475,10 @@ nm_settings_add_connection_dbus (NMSettings *self,
 	g_return_if_fail (NM_IS_AUTH_SUBJECT (subject));
 	g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (context));
 
+	nm_assert (!NM_FLAGS_ANY (sett_flags, ~_NM_SETTINGS_CONNECTION_INT_FLAGS_PERSISTENT_MASK));
+
 	/* Connection must be valid, of course */
-	if (!nm_connection_verify (connection, &tmp_error)) {
+	if (_nm_connection_verify (connection, &tmp_error) != NM_SETTING_VERIFY_SUCCESS) {
 		error = g_error_new (NM_SETTINGS_ERROR,
 		                     NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		                     "The connection was invalid: %s",
@@ -1252,11 +2487,11 @@ nm_settings_add_connection_dbus (NMSettings *self,
 		goto done;
 	}
 
-	/* The kernel doesn't support Ad-Hoc WPA connections well at this time,
+	/* FIXME: The kernel doesn't support Ad-Hoc WPA connections well at this time,
 	 * and turns them into open networks.  It's been this way since at least
 	 * 2.6.30 or so; until that's fixed, disable WPA-protected Ad-Hoc networks.
 	 */
-	if (is_adhoc_wpa (connection)) {
+	if (nm_utils_connection_is_adhoc_wpa (connection)) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		                             "WPA Ad-Hoc disabled due to kernel bugs");
@@ -1275,7 +2510,7 @@ nm_settings_add_connection_dbus (NMSettings *self,
 	 * request affects more than just the caller, require 'modify.system'.
 	 */
 	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
+	nm_assert (s_con);
 	if (nm_setting_connection_get_num_permissions (s_con) == 1)
 		perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
 	else
@@ -1290,14 +2525,17 @@ nm_settings_add_connection_dbus (NMSettings *self,
 		goto done;
 	}
 
-	priv->auths = g_slist_append (priv->auths, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
+
 	nm_auth_chain_set_data (chain, "perm", (gpointer) perm, NULL);
 	nm_auth_chain_set_data (chain, "connection", g_object_ref (connection), g_object_unref);
 	nm_auth_chain_set_data (chain, "callback", callback, NULL);
 	nm_auth_chain_set_data (chain, "callback-data", user_data, NULL);
 	nm_auth_chain_set_data (chain, "subject", g_object_ref (subject), g_object_unref);
-	nm_auth_chain_set_data (chain, "save-to-disk", GUINT_TO_POINTER (save_to_disk), NULL);
-	nm_auth_chain_add_call (chain, perm, TRUE);
+	nm_auth_chain_set_data (chain, "persist-mode", GUINT_TO_POINTER (persist_mode), NULL);
+	nm_auth_chain_set_data (chain, "add-reason", GUINT_TO_POINTER (add_reason), NULL);
+	nm_auth_chain_set_data (chain, "sett-flags", GUINT_TO_POINTER (sett_flags), NULL);
+	nm_auth_chain_add_call_unsafe (chain, perm, TRUE);
 	return;
 
 done:
@@ -1314,27 +2552,42 @@ settings_add_connection_add_cb (NMSettings *self,
                                 NMAuthSubject *subject,
                                 gpointer user_data)
 {
+	gboolean is_add_connection_2 = GPOINTER_TO_INT (user_data);
+
 	if (error) {
 		g_dbus_method_invocation_return_gerror (context, error);
 		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, NULL, FALSE, NULL, subject, error->message);
+		return;
+	}
+
+	if (is_add_connection_2) {
+		GVariantBuilder builder;
+
+		g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+		g_dbus_method_invocation_return_value (context,
+		                                       g_variant_new ("(oa{sv})",
+		                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (connection)),
+		                                                      &builder));
 	} else {
 		g_dbus_method_invocation_return_value (context,
 		                                       g_variant_new ("(o)",
 		                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (connection))));
-		nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, connection, TRUE, NULL,
-		                            subject, NULL);
 	}
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONN_ADD, connection, TRUE, NULL,
+	                            subject, NULL);
 }
 
 static void
 settings_add_connection_helper (NMSettings *self,
                                 GDBusMethodInvocation *context,
+                                gboolean is_add_connection_2,
                                 GVariant *settings,
-                                gboolean save_to_disk)
+                                NMSettingsAddConnection2Flags flags)
 {
 	gs_unref_object NMConnection *connection = NULL;
 	GError *error = NULL;
 	gs_unref_object NMAuthSubject *subject = NULL;
+	NMSettingsConnectionPersistMode persist_mode;
 
 	connection = _nm_simple_connection_new_from_dbus (settings,
 	                                                    NM_SETTING_PARSE_FLAGS_STRICT
@@ -1356,13 +2609,24 @@ settings_add_connection_helper (NMSettings *self,
 		return;
 	}
 
+	if (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK))
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_TO_DISK;
+	else {
+		nm_assert (NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY));
+		persist_mode = NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY;
+	}
+
 	nm_settings_add_connection_dbus (self,
 	                                 connection,
-	                                 save_to_disk,
+	                                 persist_mode,
+	                                   NM_FLAGS_HAS (flags, NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)
+	                                 ? NM_SETTINGS_CONNECTION_ADD_REASON_BLOCK_AUTOCONNECT
+	                                 : NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                                 NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
 	                                 subject,
 	                                 context,
 	                                 settings_add_connection_add_cb,
-	                                 NULL);
+	                                 GINT_TO_POINTER (!!is_add_connection_2));
 }
 
 static void
@@ -1378,7 +2642,7 @@ impl_settings_add_connection (NMDBusObject *obj,
 	gs_unref_variant GVariant *settings = NULL;
 
 	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
-	settings_add_connection_helper (self, invocation, settings, TRUE);
+	settings_add_connection_helper (self, invocation, FALSE, settings, NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK);
 }
 
 static void
@@ -1394,14 +2658,79 @@ impl_settings_add_connection_unsaved (NMDBusObject *obj,
 	gs_unref_variant GVariant *settings = NULL;
 
 	g_variant_get (parameters, "(@a{sa{sv}})", &settings);
-	settings_add_connection_helper (self, invocation, settings, FALSE);
+	settings_add_connection_helper (self, invocation, FALSE, settings, NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY);
+}
+
+static void
+impl_settings_add_connection2 (NMDBusObject *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended *method_info,
+                               GDBusConnection *connection,
+                               const char *sender,
+                               GDBusMethodInvocation *invocation,
+                               GVariant *parameters)
+{
+	NMSettings *self = NM_SETTINGS (obj);
+	gs_unref_variant GVariant *settings = NULL;
+	gs_unref_variant GVariant *args = NULL;
+	NMSettingsAddConnection2Flags flags;
+	const char *args_name;
+	GVariantIter iter;
+	guint32 flags_u;
+
+	g_variant_get (parameters, "(@a{sa{sv}}u@a{sv})", &settings, &flags_u, &args);
+
+	if (NM_FLAGS_ANY (flags_u, ~((guint32) (  NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+	                                        | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY
+	                                        | NM_SETTINGS_ADD_CONNECTION2_FLAG_BLOCK_AUTOCONNECT)))) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new_literal (NM_SETTINGS_ERROR,
+		                                                          NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                          "Unknown flags"));
+		return;
+	}
+
+	flags = flags_u;
+
+	if (!NM_FLAGS_ANY (flags,   NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+	                          | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new_literal (NM_SETTINGS_ERROR,
+		                                                          NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                          "Requires either to-disk (0x1) or in-memory (0x2) flags"));
+		return;
+	}
+
+	if (NM_FLAGS_ALL (flags,   NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK
+	                         | NM_SETTINGS_ADD_CONNECTION2_FLAG_IN_MEMORY)) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new_literal (NM_SETTINGS_ERROR,
+		                                                          NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                          "Cannot set to-disk (0x1) and in-memory (0x2) flags together"));
+		return;
+	}
+
+	nm_assert (g_variant_is_of_type (args, G_VARIANT_TYPE ("a{sv}")));
+
+	g_variant_iter_init (&iter, args);
+	while (g_variant_iter_next (&iter, "{&sv}", &args_name, NULL)) {
+		g_dbus_method_invocation_take_error (invocation,
+		                                     g_error_new (NM_SETTINGS_ERROR,
+		                                                  NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
+		                                                  "Unsupported argument '%s'", args_name));
+		return;
+	}
+
+	settings_add_connection_helper (self, invocation, TRUE, settings, flags);
 }
 
+/*****************************************************************************/
+
 static void
 impl_settings_load_connections (NMDBusObject *obj,
                                 const NMDBusInterfaceInfoExtended *interface_info,
                                 const NMDBusMethodInfoExtended *method_info,
-                                GDBusConnection *connection,
+                                GDBusConnection *dbus_connection,
                                 const char *sender,
                                 GDBusMethodInvocation *invocation,
                                 GVariant *parameters)
@@ -1409,9 +2738,8 @@ impl_settings_load_connections (NMDBusObject *obj,
 	NMSettings *self = NM_SETTINGS (obj);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 	gs_unref_ptrarray GPtrArray *failures = NULL;
-	GSList *iter;
-	guint i;
 	gs_free const char **filenames = NULL;
+	gs_free char *op_result_str = NULL;
 
 	g_variant_get (parameters, "(^a&s)", &filenames);
 
@@ -1426,31 +2754,68 @@ impl_settings_load_connections (NMDBusObject *obj,
 	                                 NM_SETTINGS_ERROR_PERMISSION_DENIED))
 		return;
 
-	if (filenames) {
-		for (i = 0; filenames[i]; i++) {
-			for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-				NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+	if (   filenames
+	    && filenames[0]) {
+		NMSettingsPluginConnectionLoadEntry *entries;
+		gsize n_entries;
+		gsize i;
+		GSList *iter;
 
-				if (nm_settings_plugin_load_connection (plugin, filenames[i]))
-					break;
-			}
+		entries = nm_settings_plugin_create_connection_load_entries (filenames, &n_entries);
 
-			if (!iter) {
-				if (!g_path_is_absolute (filenames[i]))
-					_LOGW ("connection filename '%s' is not an absolute path", filenames[i]);
-				if (!failures)
-					failures = g_ptr_array_new ();
-				g_ptr_array_add (failures, (char *) filenames[i]);
-			}
+		for (iter = priv->plugins; iter; iter = iter->next) {
+			NMSettingsPlugin *plugin = iter->data;
+
+			nm_settings_plugin_load_connections (plugin,
+			                                     entries,
+			                                     n_entries,
+			                                     _plugin_connections_reload_cb,
+			                                     self);
+		}
+
+		for (i = 0; i < n_entries; i++) {
+			NMSettingsPluginConnectionLoadEntry *entry = &entries[i];
+
+			if (!entry->handled)
+				_LOGW ("load: no settings plugin could load \"%s\"", entry->filename);
+			else if (entry->error) {
+				_LOGW ("load: failure to load \"%s\": %s", entry->filename, entry->error->message);
+				g_clear_error (&entry->error);
+			} else
+				continue;
+
+			if (!failures)
+				failures = g_ptr_array_new ();
+			g_ptr_array_add (failures, (char *) entry->filename);
 		}
+
+		nm_clear_g_free (&entries);
+
+		_connection_changed_process_all_dirty (self,
+		                                       TRUE,
+		                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+		                                       NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+		                                       TRUE,
+		                                         NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
+		                                       | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS);
+
+		for (iter = priv->plugins; iter; iter = iter->next)
+			nm_settings_plugin_load_connections_done (iter->data);
 	}
 
 	if (failures)
 		g_ptr_array_add (failures, NULL);
 
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONNS_LOAD,
+	                            NULL,
+	                            !failures,
+	                            (op_result_str = g_strjoinv (",", (char **) filenames)),
+	                            invocation,
+	                            NULL);
+
 	g_dbus_method_invocation_return_value (invocation,
 	                                       g_variant_new ("(b^as)",
-	                                                      (gboolean) (!!failures),
+	                                                      (gboolean) (!failures),
 	                                                      failures
 	                                                        ? (const char **) failures->pdata
 	                                                        : NM_PTRARRAY_EMPTY (const char *)));
@@ -1466,8 +2831,6 @@ impl_settings_reload_connections (NMDBusObject *obj,
                                   GVariant *parameters)
 {
 	NMSettings *self = NM_SETTINGS (obj);
-	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	GSList *iter;
 
 	/* The permission is already enforced by the D-Bus daemon, but we ensure
 	 * that the caller is still alive so that clients are forced to wait and
@@ -1480,11 +2843,9 @@ impl_settings_reload_connections (NMDBusObject *obj,
 	                                 NM_SETTINGS_ERROR_PERMISSION_DENIED))
 		return;
 
-	for (iter = priv->plugins; iter; iter = g_slist_next (iter)) {
-		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+	_plugin_connections_reload (self);
 
-		nm_settings_plugin_reload_connections (plugin);
-	}
+	nm_audit_log_connection_op (NM_AUDIT_OP_CONNS_RELOAD, NULL, TRUE, NULL, invocation, NULL);
 
 	g_dbus_method_invocation_return_value (invocation, g_variant_new ("(b)", TRUE));
 }
@@ -1492,8 +2853,427 @@ impl_settings_reload_connections (NMDBusObject *obj,
 /*****************************************************************************/
 
 static void
+_clear_connections_cached_list (NMSettingsPrivate *priv)
+{
+	if (!priv->connections_cached_list)
+		return;
+
+	nm_assert (priv->connections_len == NM_PTRARRAY_LEN (priv->connections_cached_list));
+
+#if NM_MORE_ASSERTS
+	/* set the pointer to a bogus value. This makes it more apparent
+	 * if somebody has a reference to the cached list and still uses
+	 * it. That is a bug, this code just tries to make it blow up
+	 * more eagerly. */
+	memset (priv->connections_cached_list,
+	        0xdeaddead,
+	        sizeof (NMSettingsConnection *) * (priv->connections_len + 1));
+#endif
+
+	nm_clear_g_free (&priv->connections_cached_list);
+}
+
+static void
+impl_settings_list_connections (NMDBusObject *obj,
+                                const NMDBusInterfaceInfoExtended *interface_info,
+                                const NMDBusMethodInfoExtended *method_info,
+                                GDBusConnection *dbus_connection,
+                                const char *sender,
+                                GDBusMethodInvocation *invocation,
+                                GVariant *parameters)
+{
+	NMSettings *self = NM_SETTINGS (obj);
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	gs_free const char **strv = NULL;
+
+	strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
+	                                          priv->connections_len,
+	                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
+	                                          TRUE);
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(^ao)", strv));
+}
+
+NMSettingsConnection *
+nm_settings_get_connection_by_uuid (NMSettings *self, const char *uuid)
+{
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+	g_return_val_if_fail (uuid != NULL, NULL);
+
+	return _sett_conn_entry_get_conn (_sett_conn_entries_get (self, uuid));
+}
+
+const char *
+nm_settings_get_dbus_path_for_uuid (NMSettings *self,
+                                    const char *uuid)
+{
+	NMSettingsConnection *sett_conn;
+
+	sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
+
+	if (!sett_conn)
+		return NULL;
+
+	return nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn));
+}
+
+static void
+impl_settings_get_connection_by_uuid (NMDBusObject *obj,
+                                      const NMDBusInterfaceInfoExtended *interface_info,
+                                      const NMDBusMethodInfoExtended *method_info,
+                                      GDBusConnection *dbus_connection,
+                                      const char *sender,
+                                      GDBusMethodInvocation *invocation,
+                                      GVariant *parameters)
+{
+	NMSettings *self = NM_SETTINGS (obj);
+	NMSettingsConnection *sett_conn;
+	gs_unref_object NMAuthSubject *subject = NULL;
+	GError *error = NULL;
+	const char *uuid;
+
+	g_variant_get (parameters, "(&s)", &uuid);
+
+	sett_conn = nm_settings_get_connection_by_uuid (self, uuid);
+	if (!sett_conn) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		                             "No connection with the UUID was found.");
+		goto error;
+	}
+
+	subject = nm_auth_subject_new_unix_process_from_context (invocation);
+	if (!subject) {
+		error = g_error_new_literal (NM_SETTINGS_ERROR,
+		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
+		                             "Unable to determine UID of request.");
+		goto error;
+	}
+
+	if (!nm_auth_is_subject_in_acl_set_error (nm_settings_connection_get_connection (sett_conn),
+	                                          subject,
+	                                          NM_SETTINGS_ERROR,
+	                                          NM_SETTINGS_ERROR_PERMISSION_DENIED,
+	                                          &error))
+		goto error;
+
+	g_dbus_method_invocation_return_value (invocation,
+	                                       g_variant_new ("(o)",
+	                                                      nm_dbus_object_get_path (NM_DBUS_OBJECT (sett_conn))));
+	return;
+
+error:
+	g_dbus_method_invocation_take_error (invocation, error);
+}
+
+/**
+ * nm_settings_get_connections:
+ * @self: the #NMSettings
+ * @out_len: (out) (allow-none): returns the number of returned
+ *   connections.
+ *
+ * Returns: (transfer none): a list of NMSettingsConnections. The list is
+ * unsorted and NULL terminated. The result is never %NULL, in case of no
+ * connections, it returns an empty list.
+ * The returned list is cached internally, only valid until the next
+ * NMSettings operation.
+ */
+NMSettingsConnection *const*
+nm_settings_get_connections (NMSettings *self, guint *out_len)
+{
+	NMSettingsPrivate *priv;
+	NMSettingsConnection **v;
+	NMSettingsConnection *con;
+	guint i;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	nm_assert (priv->connections_len == c_list_length (&priv->connections_lst_head));
+
+	if (G_UNLIKELY (!priv->connections_cached_list)) {
+		v = g_new (NMSettingsConnection *, priv->connections_len + 1);
+
+		i = 0;
+		c_list_for_each_entry (con, &priv->connections_lst_head, _connections_lst) {
+			nm_assert (i < priv->connections_len);
+			v[i++] = con;
+		}
+		nm_assert (i == priv->connections_len);
+		v[i] = NULL;
+
+		priv->connections_cached_list = v;
+	}
+
+	NM_SET_OUT (out_len, priv->connections_len);
+	return priv->connections_cached_list;
+}
+
+/**
+ * nm_settings_get_connections_clone:
+ * @self: the #NMSetting
+ * @out_len: (allow-none): optional output argument
+ * @func: caller-supplied function for filtering connections
+ * @func_data: caller-supplied data passed to @func
+ * @sort_compare_func: (allow-none): optional function pointer for
+ *   sorting the returned list.
+ * @sort_data: user data for @sort_compare_func.
+ *
+ * Returns: (transfer container) (element-type NMSettingsConnection):
+ *   an NULL terminated array of #NMSettingsConnection objects that were
+ *   filtered by @func (or all connections if no filter was specified).
+ *   The order is arbitrary.
+ *   Caller is responsible for freeing the returned array with free(),
+ *   the contained values do not need to be unrefed.
+ */
+NMSettingsConnection **
+nm_settings_get_connections_clone (NMSettings *self,
+                                   guint *out_len,
+                                   NMSettingsConnectionFilterFunc func,
+                                   gpointer func_data,
+                                   GCompareDataFunc sort_compare_func,
+                                   gpointer sort_data)
+{
+	NMSettingsConnection *const*list_cached;
+	NMSettingsConnection **list;
+	guint len, i, j;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+
+	list_cached = nm_settings_get_connections (self, &len);
+
+#if NM_MORE_ASSERTS
+	nm_assert (list_cached);
+	for (i = 0; i < len; i++)
+		nm_assert (NM_IS_SETTINGS_CONNECTION (list_cached[i]));
+	nm_assert (!list_cached[i]);
+#endif
+
+	list = g_new (NMSettingsConnection *, ((gsize) len + 1));
+	if (func) {
+		for (i = 0, j = 0; i < len; i++) {
+			if (func (self, list_cached[i], func_data))
+				list[j++] = list_cached[i];
+		}
+		list[j] = NULL;
+		len = j;
+	} else
+		memcpy (list, list_cached, sizeof (list[0]) * ((gsize) len + 1));
+
+	if (   len > 1
+	    && sort_compare_func) {
+		g_qsort_with_data (list, len, sizeof (NMSettingsConnection *),
+		                   sort_compare_func, sort_data);
+	}
+	NM_SET_OUT (out_len, len);
+	return list;
+}
+
+NMSettingsConnection *
+nm_settings_get_connection_by_path (NMSettings *self, const char *path)
+{
+	NMSettingsPrivate *priv;
+	NMSettingsConnection *connection;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), NULL);
+	g_return_val_if_fail (path, NULL);
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	connection = nm_dbus_manager_lookup_object (nm_dbus_object_get_manager (NM_DBUS_OBJECT (self)),
+	                                            path);
+	if (   !connection
+	    || !NM_IS_SETTINGS_CONNECTION (connection))
+		return NULL;
+
+	nm_assert (c_list_contains (&priv->connections_lst_head, &connection->_connections_lst));
+	return connection;
+}
+
+gboolean
+nm_settings_has_connection (NMSettings *self, NMSettingsConnection *connection)
+{
+	gboolean has;
+
+	g_return_val_if_fail (NM_IS_SETTINGS (self), FALSE);
+	g_return_val_if_fail (NM_IS_SETTINGS_CONNECTION (connection), FALSE);
+
+	has = !c_list_is_empty (&connection->_connections_lst);
+
+	nm_assert (has == nm_c_list_contains_entry (&NM_SETTINGS_GET_PRIVATE (self)->connections_lst_head,
+                                                connection,
+                                                _connections_lst));
+	nm_assert (({
+		NMSettingsConnection *candidate = NULL;
+		const char *path;
+
+		path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
+		if (path)
+			candidate = nm_settings_get_connection_by_path (self, path);
+
+		(has == (connection == candidate));
+	}));
+
+	return has;
+}
+
+/*****************************************************************************/
+
+static void
+add_plugin (NMSettings *self,
+            NMSettingsPlugin *plugin,
+            const char *pname,
+            const char *path)
+{
+	NMSettingsPrivate *priv;
+
+	nm_assert (NM_IS_SETTINGS (self));
+	nm_assert (NM_IS_SETTINGS_PLUGIN (plugin));
+
+	nm_assert (pname);
+	nm_assert (nm_streq0 (pname, nm_settings_plugin_get_plugin_name (plugin)));
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	nm_assert (!g_slist_find (priv->plugins, plugin));
+
+	priv->plugins = g_slist_append (priv->plugins, g_object_ref (plugin));
+
+	nm_shutdown_wait_obj_register_full (G_OBJECT (plugin),
+	                                    g_strdup_printf ("%s-settings-plugin", pname),
+	                                    TRUE);
+
+	_LOGI ("Loaded settings plugin: %s (%s%s%s)",
+	       pname,
+	       NM_PRINT_FMT_QUOTED (path, "\"", path, "\"", "internal"));
+}
+
+static gboolean
+add_plugin_load_file (NMSettings *self, const char *pname, GError **error)
+{
+	gs_free char *full_name = NULL;
+	gs_free char *path = NULL;
+	gs_unref_object NMSettingsPlugin *plugin = NULL;
+	GModule *module;
+	NMSettingsPluginFactoryFunc factory_func;
+	struct stat st;
+	int errsv;
+
+	full_name = g_strdup_printf ("nm-settings-plugin-%s", pname);
+	path = g_module_build_path (NMPLUGINDIR, full_name);
+
+	if (stat (path, &st) != 0) {
+		errsv = errno;
+		_LOGW ("could not load plugin '%s' from file '%s': %s", pname, path, nm_strerror_native (errsv));
+		return TRUE;
+	}
+	if (!S_ISREG (st.st_mode)) {
+		_LOGW ("could not load plugin '%s' from file '%s': not a file", pname, path);
+		return TRUE;
+	}
+	if (st.st_uid != 0) {
+		_LOGW ("could not load plugin '%s' from file '%s': file must be owned by root", pname, path);
+		return TRUE;
+	}
+	if (st.st_mode & (S_IWGRP | S_IWOTH | S_ISUID)) {
+		_LOGW ("could not load plugin '%s' from file '%s': invalid file permissions", pname, path);
+		return TRUE;
+	}
+
+	module = g_module_open (path, G_MODULE_BIND_LOCAL);
+	if (!module) {
+		_LOGW ("could not load plugin '%s' from file '%s': %s",
+		     pname, path, g_module_error ());
+		return TRUE;
+	}
+
+	/* errors after this point are fatal, because we loaded the shared library already. */
+
+	if (!g_module_symbol (module, "nm_settings_plugin_factory", (gpointer) (&factory_func))) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "Could not find plugin '%s' factory function.",
+		             pname);
+		g_module_close (module);
+		return FALSE;
+	}
+
+	/* after accessing the plugin we cannot unload it anymore, because the glib
+	 * types cannot be properly unregistered. */
+	g_module_make_resident (module);
+
+	plugin = (*factory_func) ();
+	if (!NM_IS_SETTINGS_PLUGIN (plugin)) {
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		             "plugin '%s' returned invalid settings plugin",
+		             pname);
+		return FALSE;
+	}
+
+	add_plugin (self, NM_SETTINGS_PLUGIN (plugin), pname, path);
+	return TRUE;
+}
+
+static void
+add_plugin_keyfile (NMSettings *self)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+
+	if (priv->keyfile_plugin)
+		return;
+	priv->keyfile_plugin = nms_keyfile_plugin_new ();
+	add_plugin (self, NM_SETTINGS_PLUGIN (priv->keyfile_plugin), "keyfile", NULL);
+}
+
+static gboolean
+load_plugins (NMSettings *self, const char *const*plugins, GError **error)
+{
+	const char *const*iter;
+	gboolean success = TRUE;
+
+	for (iter = plugins; iter && *iter; iter++) {
+		const char *pname = *iter;
+
+		if (!*pname || strchr (pname, '/')) {
+			_LOGW ("ignore invalid plugin \"%s\"", pname);
+			continue;
+		}
+
+		if (NM_IN_STRSET (pname, "ifcfg-suse", "ifnet", "ibft", "no-ibft")) {
+			_LOGW ("skipping deprecated plugin %s", pname);
+			continue;
+		}
+
+		/* keyfile plugin is built-in now */
+		if (nm_streq (pname, "keyfile")) {
+			add_plugin_keyfile (self);
+			continue;
+		}
+
+		if (nm_utils_strv_find_first ((char **) plugins,
+		                              iter - plugins,
+		                              pname) >= 0) {
+			/* the plugin is already mentioned in the list previously.
+			 * Don't load a duplicate. */
+			continue;
+		}
+
+		success = add_plugin_load_file (self, pname, error);
+		if (!success)
+			break;
+	}
+
+	/* If keyfile plugin was not among configured plugins, add it as the last one */
+	if (success)
+		add_plugin_keyfile (self);
+
+	return success;
+}
+
+/*****************************************************************************/
+
+static void
 pk_hostname_cb (NMAuthChain *chain,
-                GError *chain_error,
                 GDBusMethodInvocation *context,
                 gpointer user_data)
 {
@@ -1503,19 +3283,14 @@ pk_hostname_cb (NMAuthChain *chain,
 	GError *error = NULL;
 	const char *hostname;
 
-	g_assert (context);
+	nm_assert (G_IS_DBUS_METHOD_INVOCATION (context));
 
-	priv->auths = g_slist_remove (priv->auths, chain);
+	c_list_unlink (nm_auth_chain_parent_lst_list (chain));
 
 	result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME);
 
 	/* If our NMSettingsConnection is already gone, do nothing */
-	if (chain_error) {
-		error = g_error_new (NM_SETTINGS_ERROR,
-		                     NM_SETTINGS_ERROR_FAILED,
-		                     "Error checking authorization: %s",
-		                     chain_error->message);
-	} else if (result != NM_AUTH_CALL_RESULT_YES) {
+	if (result != NM_AUTH_CALL_RESULT_YES) {
 		error = g_error_new_literal (NM_SETTINGS_ERROR,
 		                             NM_SETTINGS_ERROR_PERMISSION_DENIED,
 		                             "Insufficient privileges.");
@@ -1533,8 +3308,6 @@ pk_hostname_cb (NMAuthChain *chain,
 		g_dbus_method_invocation_take_error (context, error);
 	else
 		g_dbus_method_invocation_return_value (context, NULL);
-
-	nm_auth_chain_destroy (chain);
 }
 
 static void
@@ -1571,18 +3344,27 @@ impl_settings_save_hostname (NMDBusObject *obj,
 		return;
 	}
 
-	priv->auths = g_slist_append (priv->auths, chain);
+	c_list_link_tail (&priv->auth_lst_head, nm_auth_chain_parent_lst_list (chain));
 	nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_SETTINGS_MODIFY_HOSTNAME, TRUE);
 	nm_auth_chain_set_data (chain, "hostname", g_strdup (hostname), g_free);
 }
 
 /*****************************************************************************/
 
+static void
+_hostname_changed_cb (NMHostnameManager *hostname_manager,
+                      GParamSpec *pspec,
+                      gpointer user_data)
+{
+	_notify (user_data, PROP_HOSTNAME);
+}
+
+/*****************************************************************************/
+
 static gboolean
 have_connection_for_device (NMSettings *self, NMDevice *device)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
 	NMSettingWired *s_wired;
 	const char *setting_hwaddr;
 	const char *perm_hw_addr;
@@ -1595,32 +3377,36 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 	/* Find a wired connection locked to the given MAC address, if any */
 	c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
 		NMConnection *connection = nm_settings_connection_get_connection (sett_conn);
-		const char *ctype, *iface;
+		NMSettingConnection *s_con = nm_connection_get_setting_connection (connection);
+		const char *ctype;
+		const char *iface;
+
+		ctype = nm_setting_connection_get_connection_type (s_con);
+		if (!NM_IN_STRSET (ctype, NM_SETTING_WIRED_SETTING_NAME,
+		                          NM_SETTING_PPPOE_SETTING_NAME))
+			continue;
 
 		if (!nm_device_check_connection_compatible (device, connection, NULL))
 			continue;
 
-		s_con = nm_connection_get_setting_connection (connection);
+		if (nm_settings_connection_default_wired_get_device (sett_conn))
+			continue;
 
-		iface = nm_setting_connection_get_interface_name (s_con);
-		if (iface && strcmp (iface, nm_device_get_iface (device)) != 0)
+		if (NM_FLAGS_ANY (nm_settings_connection_get_flags (sett_conn),
+		                  NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE))
 			continue;
 
-		ctype = nm_setting_connection_get_connection_type (s_con);
-		if (   strcmp (ctype, NM_SETTING_WIRED_SETTING_NAME)
-		    && strcmp (ctype, NM_SETTING_PPPOE_SETTING_NAME))
+		iface = nm_setting_connection_get_interface_name (s_con);
+		if (!nm_streq0 (iface, nm_device_get_iface (device)))
 			continue;
 
 		s_wired = nm_connection_get_setting_wired (connection);
-
 		if (   !s_wired
 		    && nm_streq (ctype, NM_SETTING_PPPOE_SETTING_NAME)) {
 			/* No wired setting; therefore the PPPoE connection applies to any device */
 			return TRUE;
 		}
 
-		nm_assert (s_wired);
-
 		setting_hwaddr = nm_setting_wired_get_mac_address (s_wired);
 		if (setting_hwaddr) {
 			/* A connection mac-locked to this device */
@@ -1641,38 +3427,26 @@ have_connection_for_device (NMSettings *self, NMDevice *device)
 }
 
 static void
-default_wired_connection_updated_by_user_cb (NMSettingsConnection *connection, gboolean by_user, NMSettings *self)
-{
-	NMDevice *device;
-
-	if (!by_user)
-		return;
-
-	/* The connection has been changed by the user, it should no longer be
-	 * considered a default wired connection, and should no longer affect
-	 * the no-auto-default configuration option.
-	 */
-	device = g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ());
-	if (device)
-		default_wired_clear_tag (self, device, connection, FALSE);
-}
-
-static void
 default_wired_clear_tag (NMSettings *self,
                          NMDevice *device,
-                         NMSettingsConnection *connection,
+                         NMSettingsConnection *sett_conn,
                          gboolean add_to_no_auto_default)
 {
 	nm_assert (NM_IS_SETTINGS (self));
 	nm_assert (NM_IS_DEVICE (device));
-	nm_assert (NM_IS_SETTINGS_CONNECTION (connection));
-	nm_assert (device == g_object_get_qdata (G_OBJECT (connection), _default_wired_device_quark ()));
-	nm_assert (connection == g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()));
+	nm_assert (NM_IS_SETTINGS_CONNECTION (sett_conn));
+	nm_assert (device == nm_settings_connection_default_wired_get_device (sett_conn));
+	nm_assert (sett_conn == g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ()));
 
-	g_object_set_qdata (G_OBJECT (connection), _default_wired_device_quark (), NULL);
-	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), NULL);
+	_LOGT ("auto-default: forget association between %s (%s) and device %s (%s)",
+	       nm_settings_connection_get_uuid (sett_conn),
+	       nm_settings_connection_get_id (sett_conn),
+	       nm_device_get_iface (device),
+	       add_to_no_auto_default ? "persisted" : "temporary");
+
+	nm_settings_connection_default_wired_set_device (sett_conn, NULL);
 
-	g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (default_wired_connection_updated_by_user_cb), self);
+	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), NULL);
 
 	if (add_to_no_auto_default)
 		nm_config_set_no_auto_default_for_device (NM_SETTINGS_GET_PRIVATE (self)->config, device);
@@ -1681,7 +3455,8 @@ default_wired_clear_tag (NMSettings *self,
 static void
 device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
+	NMSettingsPrivate *priv;
 	NMSettingsConnection *added;
 	GError *error = NULL;
 
@@ -1692,22 +3467,33 @@ device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 	                                      G_CALLBACK (device_realized),
 	                                      self);
 
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+
 	/* If the device isn't managed or it already has a default wired connection,
 	 * ignore it.
 	 */
 	if (   !nm_device_get_managed (device, FALSE)
 	    || g_object_get_qdata (G_OBJECT (device), _default_wired_connection_quark ())
-	    || have_connection_for_device (self, device))
+	    || have_connection_for_device (self, device)
+	    || nm_config_get_no_auto_default_for_device (priv->config, device))
 		return;
 
 	connection = nm_device_new_default_connection (device);
 	if (!connection)
 		return;
 
-	/* Add the connection */
-	added = nm_settings_add_connection (self, connection, FALSE, &error);
-	g_object_unref (connection);
-
+	_LOGT ("auto-default: creating in-memory connection %s (%s) for device %s",
+	       nm_connection_get_uuid (connection),
+	       nm_connection_get_id (connection),
+	       nm_device_get_iface (device));
+
+	nm_settings_add_connection (self,
+	                            connection,
+	                            NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+	                            NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+	                            NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+	                            &added,
+	                            &error);
 	if (!added) {
 		if (!g_error_matches (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_UUID_EXISTS)) {
 			_LOGW ("(%s) couldn't create default wired connection: %s",
@@ -1718,11 +3504,9 @@ device_realized (NMDevice *device, GParamSpec *pspec, NMSettings *self)
 		return;
 	}
 
-	g_object_set_qdata (G_OBJECT (added), _default_wired_device_quark (), device);
-	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), added);
+	nm_settings_connection_default_wired_set_device (added, device);
 
-	g_signal_connect (added, NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
-	                  G_CALLBACK (default_wired_connection_updated_by_user_cb), self);
+	g_object_set_qdata (G_OBJECT (device), _default_wired_connection_quark (), added);
 
 	_LOGI ("(%s): created default wired connection '%s'",
 	       nm_device_get_iface (device),
@@ -1735,6 +3519,8 @@ nm_settings_device_added (NMSettings *self, NMDevice *device)
 	if (nm_device_is_real (device))
 		device_realized (device, NULL, self);
 	else {
+		/* FIXME(shutdown): we need to disconnect this signal handler during
+		 *   shutdown. */
 		g_signal_connect_after (device, "notify::" NM_DEVICE_REAL,
 		                        G_CALLBACK (device_realized),
 		                        self);
@@ -1758,33 +3544,166 @@ 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);
+			nm_settings_connection_delete (connection, TRUE);
 	}
 }
 
 /*****************************************************************************/
 
-const char *
-nm_settings_get_startup_complete_blocked_reason (NMSettings *self)
+static void
+session_monitor_changed_cb (NMSessionMonitor *session_monitor,
+                            NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	const char *uuid = NULL;
-
-	if (priv->startup_complete)
-		return NULL;
-	if (priv->startup_complete_blocked_by)
-		uuid = nm_settings_connection_get_uuid (priv->startup_complete_blocked_by);
-	return uuid ?: "unknown";
+	NMSettingsConnection *const*list;
+	guint i, len;
+	guint generation;
+
+again:
+	list = nm_settings_get_connections (self, &len);
+	generation = priv->connections_generation;
+	for (i = 0; i < len; i++) {
+		gboolean is_visible;
+
+		is_visible = nm_settings_connection_check_visibility (list[i],
+		                                                      session_monitor);
+		nm_settings_connection_set_flags (list[i],
+		                                  NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE,
+		                                  is_visible);
+		if (generation != priv->connections_generation) {
+			/* the cached list was invalidated. Start again.
+			 *
+			 * Note that nm_settings_connection_recheck_visibility() will do nothing
+			 * if the visibility didn't change (including emitting no signals,
+			 * and not invalidating the list).
+			 *
+			 * Hence, for this to be an endless loop, the settings would have
+			 * to constantly change the visibility flag and also invalidate the list. */
+			goto again;
+		}
+	}
 }
 
 /*****************************************************************************/
 
+G_GNUC_PRINTF (4, 5)
 static void
-_hostname_changed_cb (NMHostnameManager *hostname_manager,
-                      GParamSpec *pspec,
+_kf_db_log_fcn (NMKeyFileDB *kf_db,
+                int syslog_level,
+                gpointer user_data,
+                const char *fmt,
+                ...)
+{
+	NMSettings *self = user_data;
+	NMLogLevel level = nm_log_level_from_syslog (syslog_level);
+
+	if (_NMLOG_ENABLED (level)) {
+		NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+		gs_free char *msg = NULL;
+		va_list ap;
+		const char *prefix;
+
+		va_start (ap, fmt);
+		msg = g_strdup_vprintf (fmt, ap);
+		va_end (ap);
+
+		if (priv->kf_db_timestamps == kf_db)
+			prefix = "timestamps";
+		else if (priv->kf_db_seen_bssids == kf_db)
+			prefix = "seen-bssids";
+		else {
+			nm_assert_not_reached ();
+			prefix = "???";
+		}
+
+		_NMLOG (level, "[%s-keyfile]: %s", prefix, msg);
+	}
+}
+
+static gboolean
+_kf_db_got_dirty_flush (NMSettings *self,
+                        gboolean is_timestamps)
+{
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	const char *prefix;
+	NMKeyFileDB *kf_db;
+
+	if (is_timestamps) {
+		prefix = "timestamps";
+		kf_db = priv->kf_db_timestamps;
+		priv->kf_db_flush_idle_id_timestamps = 0;
+	} else {
+		prefix = "seen-bssids";
+		kf_db = priv->kf_db_seen_bssids;
+		priv->kf_db_flush_idle_id_seen_bssids = 0;
+	}
+
+	if (nm_key_file_db_is_dirty (kf_db))
+		nm_key_file_db_to_file (kf_db, FALSE);
+	else {
+		_LOGT ("[%s-keyfile]: skip saving changes to \"%s\"",
+		       prefix,
+		       nm_key_file_db_get_filename (kf_db));
+	}
+
+	return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_kf_db_got_dirty_flush_timestamps_cb (gpointer user_data)
+{
+	return _kf_db_got_dirty_flush (user_data,
+	                               TRUE);
+}
+
+static gboolean
+_kf_db_got_dirty_flush_seen_bssids_cb (gpointer user_data)
+{
+	return _kf_db_got_dirty_flush (user_data,
+	                               FALSE);
+}
+
+static void
+_kf_db_got_dirty_fcn (NMKeyFileDB *kf_db,
                       gpointer user_data)
 {
-	_notify (user_data, PROP_HOSTNAME);
+	NMSettings *self = user_data;
+	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	GSourceFunc idle_func;
+	guint *p_id;
+	const char *prefix;
+
+	if (priv->kf_db_timestamps == kf_db) {
+		prefix = "timestamps";
+		p_id = &priv->kf_db_flush_idle_id_timestamps;
+		idle_func = _kf_db_got_dirty_flush_timestamps_cb;
+	} else if (priv->kf_db_seen_bssids == kf_db) {
+		prefix = "seen-bssids";
+		p_id = &priv->kf_db_flush_idle_id_seen_bssids;
+		idle_func = _kf_db_got_dirty_flush_seen_bssids_cb;
+	} else {
+		nm_assert_not_reached ();
+		return;
+	}
+
+	if (*p_id != 0)
+		return;
+	_LOGT ("[%s-keyfile]: schedule flushing changes to disk", prefix);
+	*p_id = g_idle_add_full (G_PRIORITY_LOW, idle_func, self, NULL);
+}
+
+void
+nm_settings_kf_db_write (NMSettings *self)
+{
+	NMSettingsPrivate *priv;
+
+	g_return_if_fail (NM_IS_SETTINGS (self));
+
+	priv = NM_SETTINGS_GET_PRIVATE (self);
+	if (priv->kf_db_timestamps)
+		nm_key_file_db_to_file (priv->kf_db_timestamps, TRUE);
+	if (priv->kf_db_seen_bssids)
+		nm_key_file_db_to_file (priv->kf_db_seen_bssids, TRUE);
 }
 
 /*****************************************************************************/
@@ -1794,19 +3713,47 @@ nm_settings_start (NMSettings *self, GError **error)
 {
 	NMSettingsPrivate *priv;
 	gs_strfreev char **plugins = NULL;
+	GSList *iter;
 
 	priv = NM_SETTINGS_GET_PRIVATE (self);
 
+	nm_assert (!priv->started);
+
+	priv->hostname_manager = g_object_ref (nm_hostname_manager_get ());
+
+	priv->kf_db_timestamps = nm_key_file_db_new (NMSTATEDIR "/timestamps",
+	                                             "timestamps",
+	                                             _kf_db_log_fcn,
+	                                             _kf_db_got_dirty_fcn,
+	                                             self);
+	priv->kf_db_seen_bssids = nm_key_file_db_new (NMSTATEDIR "/seen-bssids",
+	                                              "seen-bssids",
+	                                              _kf_db_log_fcn,
+	                                              _kf_db_got_dirty_fcn,
+	                                              self);
+	nm_key_file_db_start (priv->kf_db_timestamps);
+	nm_key_file_db_start (priv->kf_db_seen_bssids);
+
 	/* Load the plugins; fail if a plugin is not found. */
 	plugins = nm_config_data_get_plugins (nm_config_get_data_orig (priv->config), TRUE);
 
-	if (!load_plugins (self, (const char **) plugins, error))
+	if (!load_plugins (self, (const char *const*) plugins, error))
 		return FALSE;
 
-	load_connections (self);
-	check_startup_complete (self);
+	for (iter = priv->plugins; iter; iter = iter->next) {
+		NMSettingsPlugin *plugin = NM_SETTINGS_PLUGIN (iter->data);
+
+		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNMANAGED_SPECS_CHANGED,
+		                  G_CALLBACK (_plugin_unmanaged_specs_changed), self);
+		g_signal_connect (plugin, NM_SETTINGS_PLUGIN_UNRECOGNIZED_SPECS_CHANGED,
+		                  G_CALLBACK (_plugin_unrecognized_specs_changed), self);
+	}
+
+	_plugin_unmanaged_specs_changed (NULL, self);
+	_plugin_unrecognized_specs_changed (NULL, self);
+
+	_plugin_connections_reload (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),
@@ -1814,6 +3761,14 @@ nm_settings_start (NMSettings *self, GError **error)
 	if (nm_hostname_manager_get_hostname (priv->hostname_manager))
 		_notify (self, PROP_HOSTNAME);
 
+	priv->started = TRUE;
+	_startup_complete_check (self, 0);
+
+	/* FIXME(shutdown): we also need a nm_settings_stop() during shutdown.
+	 *
+	 * In particular, we need to remove all in-memory keyfiles from /run that are nm-generated.
+	 * alternatively, the nm-generated flag must also be persisted and loaded to /run. */
+
 	return TRUE;
 }
 
@@ -1825,39 +3780,29 @@ get_property (GObject *object, guint prop_id,
 {
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
-	const GSList *specs, *iter;
-	guint i;
-	char **strvs;
 	const char **strv;
 
 	switch (prop_id) {
 	case PROP_UNMANAGED_SPECS:
-		specs = nm_settings_get_unmanaged_specs (self);
-		strvs = g_new (char *, g_slist_length ((GSList *) specs) + 1);
-		i = 0;
-		for (iter = specs; iter; iter = iter->next)
-			strvs[i++] = g_strdup (iter->data);
-		strvs[i] = NULL;
-		g_value_take_boxed (value, strvs);
+		g_value_take_boxed (value,
+		                    _nm_utils_slist_to_strv (nm_settings_get_unmanaged_specs (self),
+		                                             TRUE));
 		break;
 	case PROP_HOSTNAME:
 		g_value_set_string (value,
-		                    priv->hostname_manager
-		                      ? nm_hostname_manager_get_hostname (priv->hostname_manager)
-		                      : NULL);
+		                      priv->hostname_manager
+		                    ? nm_hostname_manager_get_hostname (priv->hostname_manager)
+		                    : NULL);
 		break;
 	case PROP_CAN_MODIFY:
 		g_value_set_boolean (value, TRUE);
 		break;
 	case PROP_CONNECTIONS:
-		if (priv->connections_loaded) {
-			strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
-			                                          priv->connections_len,
-			                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
-			                                          TRUE);
-			g_value_take_boxed (value, nm_utils_strv_make_deep_copied (strv));
-		} else
-			g_value_set_boxed (value, NULL);
+		strv = nm_dbus_utils_get_paths_for_clist (&priv->connections_lst_head,
+		                                          priv->connections_len,
+		                                          G_STRUCT_OFFSET (NMSettingsConnection, _connections_lst),
+		                                          TRUE);
+		g_value_take_boxed (value, nm_utils_strv_make_deep_copied (strv));
 		break;
 	case PROP_STARTUP_COMPLETE:
 		g_value_set_boolean (value, !nm_settings_get_startup_complete_blocked_reason (self));
@@ -1875,10 +3820,24 @@ nm_settings_init (NMSettings *self)
 {
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
 
+	c_list_init (&priv->auth_lst_head);
 	c_list_init (&priv->connections_lst_head);
 
-	priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
+	c_list_init (&priv->sce_dirty_lst_head);
+	priv->sce_idx = g_hash_table_new_full (nm_pstr_hash, nm_pstr_equal,
+	                                       NULL, (GDestroyNotify) _sett_conn_entry_free);
+
 	priv->config = g_object_ref (nm_config_get ());
+
+	priv->agent_mgr = g_object_ref (nm_agent_manager_get ());
+
+	priv->platform = g_object_ref (NM_PLATFORM_GET);
+
+	priv->session_monitor = g_object_ref (nm_session_monitor_get ());
+	g_signal_connect (priv->session_monitor,
+	                  NM_SESSION_MONITOR_CHANGED,
+	                  G_CALLBACK (session_monitor_changed_cb),
+	                  self);
 }
 
 NMSettings *
@@ -1892,11 +3851,18 @@ dispose (GObject *object)
 {
 	NMSettings *self = NM_SETTINGS (object);
 	NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE (self);
+	CList *iter;
 
+	nm_assert (c_list_is_empty (&priv->sce_dirty_lst_head));
+	nm_assert (g_hash_table_size (priv->sce_idx) == 0);
+
+	nm_clear_g_source (&priv->startup_complete_timeout_id);
+	nm_clear_g_signal_handler (priv->platform, &priv->startup_complete_platform_change_id);
+	nm_clear_pointer (&priv->startup_complete_idx, g_hash_table_destroy);
 	g_clear_object (&priv->startup_complete_blocked_by);
 
-	g_slist_free_full (priv->auths, (GDestroyNotify) nm_auth_chain_destroy);
-	priv->auths = NULL;
+	while ((iter = c_list_first (&priv->auth_lst_head)))
+		nm_auth_chain_destroy (nm_auth_chain_parent_lst_entry (iter));
 
 	if (priv->hostname_manager) {
 		g_signal_handlers_disconnect_by_func (priv->hostname_manager,
@@ -1905,6 +3871,13 @@ dispose (GObject *object)
 		g_clear_object (&priv->hostname_manager);
 	}
 
+	if (priv->session_monitor) {
+		g_signal_handlers_disconnect_by_func (priv->session_monitor,
+		                                      G_CALLBACK (session_monitor_changed_cb),
+		                                      self);
+		g_clear_object (&priv->session_monitor);
+	}
+
 	G_OBJECT_CLASS (nm_settings_parent_class)->dispose (object);
 }
 
@@ -1919,6 +3892,11 @@ finalize (GObject *object)
 
 	nm_assert (c_list_is_empty (&priv->connections_lst_head));
 
+	nm_assert (c_list_is_empty (&priv->sce_dirty_lst_head));
+	nm_assert (g_hash_table_size (priv->sce_idx) == 0);
+
+	nm_clear_pointer (&priv->sce_idx, g_hash_table_destroy);
+
 	g_slist_free_full (priv->unmanaged_specs, g_free);
 	g_slist_free_full (priv->unrecognized_specs, g_free);
 
@@ -1929,11 +3907,22 @@ finalize (GObject *object)
 		g_signal_handlers_disconnect_by_data (plugin, self);
 	}
 
+	g_clear_object (&priv->keyfile_plugin);
+
 	g_clear_object (&priv->agent_mgr);
 
-	g_clear_object (&priv->config);
+	nm_clear_g_source (&priv->kf_db_flush_idle_id_timestamps);
+	nm_clear_g_source (&priv->kf_db_flush_idle_id_seen_bssids);
+	nm_key_file_db_to_file (priv->kf_db_timestamps, FALSE);
+	nm_key_file_db_to_file (priv->kf_db_seen_bssids, FALSE);
+	nm_key_file_db_destroy (priv->kf_db_timestamps);
+	nm_key_file_db_destroy (priv->kf_db_seen_bssids);
 
 	G_OBJECT_CLASS (nm_settings_parent_class)->finalize (object);
+
+	g_clear_object (&priv->config);
+
+	g_clear_object (&priv->platform);
 }
 
 static const GDBusSignalInfo signal_info_new_connection = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT (
@@ -2001,6 +3990,21 @@ static const NMDBusInterfaceInfoExtended interface_info_settings = {
 			),
 			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
 				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
+					"AddConnection2",
+					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("settings", "a{sa{sv}}"),
+						NM_DEFINE_GDBUS_ARG_INFO ("flags",    "u"),
+						NM_DEFINE_GDBUS_ARG_INFO ("args",     "a{sv}"),
+					),
+					.out_args = NM_DEFINE_GDBUS_ARG_INFOS (
+						NM_DEFINE_GDBUS_ARG_INFO ("path", "o"),
+						NM_DEFINE_GDBUS_ARG_INFO ("result", "a{sv}"),
+					),
+				),
+				.handle = impl_settings_add_connection2,
+			),
+			NM_DEFINE_DBUS_METHOD_INFO_EXTENDED (
+				NM_DEFINE_GDBUS_METHOD_INFO_INIT (
 					"LoadConnections",
 					.in_args = NM_DEFINE_GDBUS_ARG_INFOS (
 						NM_DEFINE_GDBUS_ARG_INFO ("filenames", "as"),
@@ -2104,7 +4108,7 @@ nm_settings_class_init (NMSettingsClass *class)
 	                  G_SIGNAL_RUN_FIRST,
 	                  0, NULL, NULL,
 	                  NULL,
-	                  G_TYPE_NONE, 2, NM_TYPE_SETTINGS_CONNECTION, G_TYPE_BOOLEAN);
+	                  G_TYPE_NONE, 2, NM_TYPE_SETTINGS_CONNECTION, G_TYPE_UINT);
 
 	signals[CONNECTION_REMOVED] =
 	    g_signal_new (NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
diff --git a/src/settings/nm-settings.h b/src/settings/nm-settings.h
index eb74c09c..d2bf72d6 100644
--- a/src/settings/nm-settings.h
+++ b/src/settings/nm-settings.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * Søren Sandmann <sandmann@daimi.au.dk>
@@ -28,6 +27,8 @@
 
 #include "nm-connection.h"
 
+#include "nm-settings-connection.h"
+
 #define NM_TYPE_SETTINGS            (nm_settings_get_type ())
 #define NM_SETTINGS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_SETTINGS, NMSettings))
 #define NM_SETTINGS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_SETTINGS, NMSettingsClass))
@@ -68,6 +69,7 @@ NMSettings *nm_settings_get (void);
 #define NM_SETTINGS_GET (nm_settings_get ())
 
 NMSettings *nm_settings_new (void);
+
 gboolean nm_settings_start (NMSettings *self, GError **error);
 
 typedef void (*NMSettingsAddCallback) (NMSettings *settings,
@@ -79,7 +81,9 @@ typedef void (*NMSettingsAddCallback) (NMSettings *settings,
 
 void nm_settings_add_connection_dbus (NMSettings *self,
                                       NMConnection *connection,
-                                      gboolean save_to_disk,
+                                      NMSettingsConnectionPersistMode persist_mode,
+                                      NMSettingsConnectionAddReason add_reason,
+                                      NMSettingsConnectionIntFlags sett_flags,
                                       NMAuthSubject *subject,
                                       GDBusMethodInvocation *context,
                                       NMSettingsAddCallback callback,
@@ -94,16 +98,37 @@ NMSettingsConnection **nm_settings_get_connections_clone (NMSettings *self,
                                                           GCompareDataFunc sort_compare_func,
                                                           gpointer sort_data);
 
-NMSettingsConnection *nm_settings_add_connection (NMSettings *settings,
-                                                  NMConnection *connection,
-                                                  gboolean save_to_disk,
-                                                  GError **error);
+gboolean nm_settings_add_connection (NMSettings *settings,
+                                     NMConnection *connection,
+                                     NMSettingsConnectionPersistMode persist_mode,
+                                     NMSettingsConnectionAddReason add_reason,
+                                     NMSettingsConnectionIntFlags sett_flags,
+                                     NMSettingsConnection **out_sett_conn,
+                                     GError **error);
+
+gboolean nm_settings_update_connection (NMSettings *self,
+                                        NMSettingsConnection *sett_conn,
+                                        NMConnection *new_connection,
+                                        NMSettingsConnectionPersistMode persist_mode,
+                                        NMSettingsConnectionIntFlags sett_flags,
+                                        NMSettingsConnectionIntFlags sett_mask,
+                                        NMSettingsConnectionUpdateReason update_reason,
+                                        const char *log_context_name,
+                                        GError **error);
+
+void nm_settings_delete_connection (NMSettings *self,
+                                    NMSettingsConnection *sett_conn,
+                                    gboolean allow_add_to_no_auto_default);
+
 NMSettingsConnection *nm_settings_get_connection_by_path (NMSettings *settings,
                                                           const char *path);
 
 NMSettingsConnection *nm_settings_get_connection_by_uuid (NMSettings *settings,
                                                           const char *uuid);
 
+const char *nm_settings_get_dbus_path_for_uuid (NMSettings *self,
+                                                const char *uuid);
+
 gboolean nm_settings_has_connection (NMSettings *self, NMSettingsConnection *connection);
 
 const GSList *nm_settings_get_unmanaged_specs (NMSettings *self);
@@ -114,4 +139,6 @@ void nm_settings_device_removed (NMSettings *self, NMDevice *device, gboolean qu
 
 const char *nm_settings_get_startup_complete_blocked_reason (NMSettings *self);
 
+void nm_settings_kf_db_write (NMSettings *settings);
+
 #endif  /* __NM_SETTINGS_H__ */
diff --git a/src/settings/plugins/ibft/meson.build b/src/settings/plugins/ibft/meson.build
deleted file mode 100644
index c33b24cc..00000000
--- a/src/settings/plugins/ibft/meson.build
+++ /dev/null
@@ -1,41 +0,0 @@
-name = 'nm-settings-plugin-ibft'
-
-libnms_ibft_core = static_library(
-  'nms-ibft-core',
-  'nms-ibft-reader.c',
-  dependencies: nm_dep,
-)
-
-sources = files(
-  'nms-ibft-connection.c',
-  'nms-ibft-plugin.c',
-)
-
-libnm_settings_plugin_ibft = shared_module(
-  name,
-  sources: sources,
-  dependencies: nm_dep,
-  link_with: libnms_ibft_core,
-  link_args: ldflags_linker_script_settings,
-  link_depends: linker_script_settings,
-  install: true,
-  install_dir: nm_plugindir,
-)
-
-core_plugins += libnm_settings_plugin_ibft
-
-# FIXME: check_so_symbols replacement
-'''
-run_target(
-  'check-local-symbols-settings-ibft',
-  command: [check_so_symbols, libnm_settings_plugin_ibft.full_path()],
-  depends: libnm_settings_plugin_ibft,
-)
-
-check-local-symbols-settings-ibft: src/settings/plugins/ibft/libnm-settings-plugin-ibft.la
-  $(call check_so_symbols,$(builddir)/src/settings/plugins/ibft/.libs/libnm-settings-plugin-ibft.so)
-'''
-
-if enable_tests
-  subdir('tests')
-endif
diff --git a/src/settings/plugins/ibft/nms-ibft-connection.c b/src/settings/plugins/ibft/nms-ibft-connection.c
deleted file mode 100644
index a36d8a31..00000000
--- a/src/settings/plugins/ibft/nms-ibft-connection.c
+++ /dev/null
@@ -1,77 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ibft-connection.h"
-
-#include <net/ethernet.h>
-#include <netinet/ether.h>
-#include <glib/gstdio.h>
-
-#include "nms-ibft-reader.h"
-
-/*****************************************************************************/
-
-struct _NMSIbftConnection {
-	NMSettingsConnection parent;
-};
-
-struct _NMSIbftConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMSIbftConnection, nms_ibft_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-/*****************************************************************************/
-
-static void
-nms_ibft_connection_init (NMSIbftConnection *connection)
-{
-}
-
-NMSIbftConnection *
-nms_ibft_connection_new (const GPtrArray *block, GError **error)
-{
-	NMConnection *source;
-	GObject *object;
-
-	source = nms_ibft_reader_get_connection_from_block (block, error);
-	if (!source)
-		return NULL;
-
-	object = g_object_new (NMS_TYPE_IBFT_CONNECTION, NULL);
-	/* Update settings with what was read from iscsiadm */
-	if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
-	                                    source,
-	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-	                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                                    NULL,
-	                                    error))
-		g_clear_object (&object);
-
-	return (NMSIbftConnection *) object;
-}
-
-static void
-nms_ibft_connection_class_init (NMSIbftConnectionClass *ibft_connection_class)
-{
-}
-
diff --git a/src/settings/plugins/ibft/nms-ibft-connection.h b/src/settings/plugins/ibft/nms-ibft-connection.h
deleted file mode 100644
index de050662..00000000
--- a/src/settings/plugins/ibft/nms-ibft-connection.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2014 Red Hat, Inc.
- */
-
-#ifndef __NMS_IBFT_CONNECTION_H__
-#define __NMS_IBFT_CONNECTION_H__
-
-#include "settings/nm-settings-connection.h"
-
-#define NMS_TYPE_IBFT_CONNECTION            (nms_ibft_connection_get_type ())
-#define NMS_IBFT_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IBFT_CONNECTION, NMSIbftConnection))
-#define NMS_IBFT_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IBFT_CONNECTION, NMSIbftConnectionClass))
-#define NMS_IS_IBFT_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IBFT_CONNECTION))
-#define NMS_IS_IBFT_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IBFT_CONNECTION))
-#define NMS_IBFT_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IBFT_CONNECTION, NMSIbftConnectionClass))
-
-typedef struct _NMSIbftConnection NMSIbftConnection;
-typedef struct _NMSIbftConnectionClass NMSIbftConnectionClass;
-
-GType nms_ibft_connection_get_type (void);
-
-NMSIbftConnection *nms_ibft_connection_new (const GPtrArray *block,
-                                           GError **error);
-
-#endif /* __NMS_IBFT_CONNECTION_H__ */
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.c b/src/settings/plugins/ibft/nms-ibft-plugin.c
deleted file mode 100644
index 47051995..00000000
--- a/src/settings/plugins/ibft/nms-ibft-plugin.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ibft-plugin.h"
-
-#include <unistd.h>
-#include <gmodule.h>
-
-#include "nm-setting-connection.h"
-#include "settings/nm-settings-plugin.h"
-#include "NetworkManagerUtils.h"
-
-#include "nms-ibft-reader.h"
-#include "nms-ibft-connection.h"
-
-/*****************************************************************************/
-
-typedef struct {
-	GHashTable *connections;  /* uuid::connection */
-	gboolean initialized;
-} NMSIbftPluginPrivate;
-
-struct _NMSIbftPlugin {
-	NMSettingsPlugin parent;
-	NMSIbftPluginPrivate _priv;
-};
-
-struct _NMSIbftPluginClass {
-	NMSettingsPluginClass parent;
-};
-
-G_DEFINE_TYPE (NMSIbftPlugin, nms_ibft_plugin, NM_TYPE_SETTINGS_PLUGIN);
-
-#define NMS_IBFT_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSIbftPlugin, NMS_IS_IBFT_PLUGIN)
-
-/*****************************************************************************/
-
-static NMSIbftPlugin *nms_ibft_plugin_get (void);
-
-NM_DEFINE_SINGLETON_GETTER (NMSIbftPlugin, nms_ibft_plugin_get, NMS_TYPE_IBFT_PLUGIN);
-
-/*****************************************************************************/
-
-static void
-read_connections (NMSIbftPlugin *self)
-{
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	GSList *iter;
-	gs_free_error GError *error = NULL;
-	NMSIbftConnection *connection;
-
-	if (!nms_ibft_reader_load_blocks ("/sbin/iscsiadm", &blocks, &error)) {
-		nm_log_dbg (LOGD_SETTINGS, "ibft: failed to read iscsiadm records: %s", error->message);
-		return;
-	}
-
-	for (iter = blocks; iter; iter = iter->next) {
-		connection = nms_ibft_connection_new (iter->data, &error);
-		if (!connection) {
-			nm_log_warn (LOGD_SETTINGS, "ibft: failed to read iscsiadm record: %s", error->message);
-			g_clear_error (&error);
-			continue;
-		}
-
-		nm_log_info (LOGD_SETTINGS, "ibft: read connection '%s'",
-		             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (connection)));
-		g_hash_table_insert (priv->connections,
-		                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection))),
-		                     connection);
-	}
-}
-
-static GSList *
-get_connections (NMSettingsPlugin *config)
-{
-	NMSIbftPlugin *self = NMS_IBFT_PLUGIN (config);
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-	GSList *list = NULL;
-	GHashTableIter iter;
-	NMSIbftConnection *connection;
-
-	if (!priv->initialized) {
-		read_connections (self);
-		priv->initialized = TRUE;
-	}
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &connection))
-		list = g_slist_prepend (list, connection);
-
-	return list;
-}
-
-/*****************************************************************************/
-
-static void
-nms_ibft_plugin_init (NMSIbftPlugin *self)
-{
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-
-	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
-}
-
-static void
-dispose (GObject *object)
-{
-	NMSIbftPlugin *self = NMS_IBFT_PLUGIN (object);
-	NMSIbftPluginPrivate *priv = NMS_IBFT_PLUGIN_GET_PRIVATE (self);
-
-	if (priv->connections) {
-		g_hash_table_destroy (priv->connections);
-		priv->connections = NULL;
-	}
-
-	G_OBJECT_CLASS (nms_ibft_plugin_parent_class)->dispose (object);
-}
-
-static void
-nms_ibft_plugin_class_init (NMSIbftPluginClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
-
-	object_class->dispose = dispose;
-
-	plugin_class->get_connections = get_connections;
-}
-
-/*****************************************************************************/
-
-G_MODULE_EXPORT NMSettingsPlugin *
-nm_settings_plugin_factory (void)
-{
-	return NM_SETTINGS_PLUGIN (g_object_ref (nms_ibft_plugin_get ()));
-}
diff --git a/src/settings/plugins/ibft/nms-ibft-plugin.h b/src/settings/plugins/ibft/nms-ibft-plugin.h
deleted file mode 100644
index 33a7620b..00000000
--- a/src/settings/plugins/ibft/nms-ibft-plugin.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2014 Red Hat, Inc.
- */
-
-#ifndef __NMS_IBFT_PLUGIN_H__
-#define __NMS_IBFT_PLUGIN_H__
-
-#define NMS_TYPE_IBFT_PLUGIN            (nms_ibft_plugin_get_type ())
-#define NMS_IBFT_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IBFT_PLUGIN, NMSIbftPlugin))
-#define NMS_IBFT_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IBFT_PLUGIN, NMSIbftPluginClass))
-#define NMS_IS_IBFT_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IBFT_PLUGIN))
-#define NMS_IS_IBFT_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IBFT_PLUGIN))
-#define NMS_IBFT_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IBFT_PLUGIN, NMSIbftPluginClass))
-
-typedef struct _NMSIbftPlugin NMSIbftPlugin;
-typedef struct _NMSIbftPluginClass NMSIbftPluginClass;
-
-GType nms_ibft_plugin_get_type (void);
-
-#endif /* __NMS_IBFT_PLUGIN_H__ */
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.c b/src/settings/plugins/ibft/nms-ibft-reader.c
deleted file mode 100644
index fde30383..00000000
--- a/src/settings/plugins/ibft/nms-ibft-reader.c
+++ /dev/null
@@ -1,535 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ibft-reader.h"
-
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <arpa/inet.h>
-#include <sys/wait.h>
-#include <sys/inotify.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include "nm-core-internal.h"
-#include "platform/nm-platform.h"
-#include "NetworkManagerUtils.h"
-
-/*****************************************************************************/
-
-#define PARSE_WARNING(msg...) nm_log_warn (LOGD_SETTINGS, "    " msg)
-
-/* Removes trailing whitespace and whitespace before and immediately after the '=' */
-static char *
-remove_most_whitespace (const char *src)
-{
-	char *s_new, *s2;
-	const char *svalue;
-
-	src = nm_str_skip_leading_spaces (src);
-
-	svalue = strchr (src, '=');
-	if (!svalue || svalue == src)
-		return NULL;
-
-	s_new = g_new (char, strlen (src) + 1);
-
-	memcpy (s_new, src, svalue - src);
-	s_new[svalue - src] = '\0';
-	g_strchomp (s_new);
-
-	svalue++;
-	while (*svalue && g_ascii_isspace (*svalue))
-		svalue++;
-
-	s2 = strchr (s_new, '\0');
-	s2[0] = '=';
-	strcpy (++s2, svalue);
-	g_strchomp (s2);
-
-	return s_new;
-}
-
-#define TAG_BEGIN "# BEGIN RECORD"
-#define TAG_END   "# END RECORD"
-
-/**
- * nms_ibft_reader_load_blocks:
- * @iscsiadm_path: path to iscsiadm program
- * @out_blocks: on return if successful, a #GSList of #GPtrArray, or %NULL on
- * failure
- * @error: location for an error on failure
- *
- * Parses iscsiadm output and returns a #GSList of #GPtrArray in the @out_blocks
- * argument on success, otherwise @out_blocks is set to %NULL.  Each #GPtrArray
- * in @out_blocks contains the lines from an iscsiadm interface block.
- *
- * Returns: %TRUE on success, %FALSE on errors
- */
-gboolean
-nms_ibft_reader_load_blocks (const char *iscsiadm_path,
-                             GSList **out_blocks,
-                             GError **error)
-{
-	const char *argv[4] = { iscsiadm_path, "-m", "fw", NULL };
-	const char *envp[1] = { NULL };
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	gs_free char *out = NULL;
-	gs_free char *err = NULL;
-	gs_free const char **lines = NULL;
-	GPtrArray *block_lines = NULL;
-	gsize i;
-	int status = 0;
-
-	g_return_val_if_fail (iscsiadm_path != NULL, FALSE);
-	g_return_val_if_fail (out_blocks != NULL && *out_blocks == NULL, FALSE);
-
-	if (!g_spawn_sync ("/", (char **) argv, (char **) envp, 0,
-	                   NULL, NULL, &out, &err, &status, error))
-		return FALSE;
-
-	if (!WIFEXITED (status)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "iBFT: %s exited abnormally.", iscsiadm_path);
-		return FALSE;
-	}
-
-	if (WEXITSTATUS (status) != 0) {
-		if (err) {
-			char *nl;
-
-			/* the error message contains newlines. concatenate the lines with whitespace */
-			for (nl = err; *nl; nl++) {
-				if (*nl == '\n')
-					*nl = ' ';
-			}
-		}
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		             "iBFT: %s exited with error %d.  Message: '%s'",
-		             iscsiadm_path, WEXITSTATUS (status), err ?: "(none)");
-		return FALSE;
-	}
-
-	nm_log_dbg (LOGD_SETTINGS, "iBFT records:\n%s", out);
-
-	lines = nm_utils_strsplit_set (out, "\n\r");
-	for (i = 0; lines && lines[i]; i++) {
-		const char *ss = lines[i];
-
-		if (!g_ascii_strncasecmp (ss, TAG_BEGIN, NM_STRLEN (TAG_BEGIN))) {
-			if (block_lines) {
-				PARSE_WARNING ("malformed iscsiadm record: missing END RECORD.");
-				nm_clear_pointer (&block_lines, g_ptr_array_unref);
-			}
-			/* Start new record */
-			block_lines = g_ptr_array_new_full (15, g_free);
-		} else if (!g_ascii_strncasecmp (ss, TAG_END, NM_STRLEN (TAG_END))) {
-			if (block_lines) {
-				if (block_lines->len)
-					blocks = g_slist_prepend (blocks, g_steal_pointer (&block_lines));
-				else
-					g_ptr_array_unref (g_steal_pointer (&block_lines));
-			}
-		} else if (block_lines) {
-			char *s = remove_most_whitespace (ss);
-
-			if (!s) {
-				PARSE_WARNING ("malformed iscsiadm record: no = in '%s'.", ss);
-				nm_clear_pointer (&block_lines, g_ptr_array_unref);
-			} else
-				g_ptr_array_add (block_lines, s);
-		}
-	}
-
-	if (block_lines) {
-		PARSE_WARNING ("malformed iscsiadm record: missing # END RECORD.");
-		nm_clear_pointer (&block_lines, g_ptr_array_unref);
-	}
-
-	*out_blocks = g_steal_pointer (&blocks);
-	return TRUE;
-}
-
-#define ISCSI_HWADDR_TAG     "iface.hwaddress"
-#define ISCSI_BOOTPROTO_TAG  "iface.bootproto"
-#define ISCSI_IPADDR_TAG     "iface.ipaddress"
-#define ISCSI_SUBNET_TAG     "iface.subnet_mask"
-#define ISCSI_GATEWAY_TAG    "iface.gateway"
-#define ISCSI_DNS1_TAG       "iface.primary_dns"
-#define ISCSI_DNS2_TAG       "iface.secondary_dns"
-#define ISCSI_VLAN_ID_TAG    "iface.vlan_id"
-#define ISCSI_IFACE_TAG      "iface.net_ifacename"
-
-static const char *
-match_iscsiadm_tag (const char *line, const char *tag)
-{
-	gsize taglen = strlen (tag);
-
-	if (g_ascii_strncasecmp (line, tag, taglen) != 0)
-		return NULL;
-	if (line[taglen] != '=')
-		return NULL;
-	return line + taglen + 1;
-}
-
-/**
- * nms_ibft_reader_parse_block:
- * @block: an array of iscsiadm interface block lines
- * @error: return location for errors
- * @...: pairs of key (const char *) : location (const char **) indicating the
- * key to look for and the location to store the retrieved value in
- *
- * Parses an iscsiadm interface block into variables requested by the caller.
- * Callers should verify the returned data is complete and valid.  Returned
- * strings are owned by @block and should not be used after @block is freed.
- *
- * Returns: %TRUE if at least , %FALSE on failure
- */
-gboolean
-nms_ibft_reader_parse_block (const GPtrArray *block, GError **error, ...)
-{
-	gboolean success = FALSE;
-	const char **out_value, *p;
-	va_list ap;
-	const char *key;
-	guint i;
-
-	g_return_val_if_fail (block != NULL, FALSE);
-	g_return_val_if_fail (block->len > 0, FALSE);
-
-	/* Find requested keys and populate return values */
-	va_start (ap, error);
-	while ((key = va_arg (ap, const char *))) {
-		out_value = va_arg (ap, const char **);
-		*out_value = NULL;
-		for (i = 0; i < block->len; i++) {
-			p = match_iscsiadm_tag (g_ptr_array_index (block, i), key);
-			if (p) {
-				*out_value = p;
-				success = TRUE;
-				break;
-			}
-		}
-	}
-	va_end (ap);
-
-	if (!success) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: failed to match at least one iscsiadm block field");
-	}
-	return success;
-}
-
-static gboolean
-ip4_setting_add_from_block (const GPtrArray *block,
-                            NMConnection *connection,
-                            GError **error)
-{
-	NMSettingIPConfig *s_ip4 = NULL;
-	NMIPAddress *addr;
-	const char *s_method = NULL;
-	const char *s_ipaddr = NULL;
-	const char *s_gateway = NULL;
-	const char *s_dns1 = NULL;
-	const char *s_dns2 = NULL;
-	const char *s_netmask = NULL;
-	guint32 netmask = 0;
-	guint32 prefix;
-
-	g_assert (block);
-
-	if (!nms_ibft_reader_parse_block (block, error,
-	                                  ISCSI_BOOTPROTO_TAG, &s_method,
-	                                  ISCSI_IPADDR_TAG,    &s_ipaddr,
-	                                  ISCSI_SUBNET_TAG,    &s_netmask,
-	                                  ISCSI_GATEWAY_TAG,   &s_gateway,
-	                                  ISCSI_DNS1_TAG,      &s_dns1,
-	                                  ISCSI_DNS2_TAG,      &s_dns2,
-	                                  NULL))
-		goto error;
-
-	if (!s_method) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_BOOTPROTO_TAG);
-		goto error;
-	}
-
-	s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
-
-	if (!g_ascii_strcasecmp (s_method, "dhcp")) {
-		g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
-		goto success;
-	} else if (g_ascii_strcasecmp (s_method, "static") != 0) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: unknown " ISCSI_BOOTPROTO_TAG " '%s'.",
-		             s_method);
-		goto error;
-	}
-
-	/* Static configuration stuff */
-	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
-
-	/* IP address */
-	if (!s_ipaddr || !nm_utils_ipaddr_valid (AF_INET, s_ipaddr)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid IP address '%s'.",
-		             s_ipaddr);
-		goto error;
-	}
-
-	/* Subnet/prefix */
-	if (!s_netmask || inet_pton (AF_INET, s_netmask, &netmask) != 1) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid subnet mask '%s'.",
-		             s_netmask);
-		goto error;
-	}
-	prefix = nm_utils_ip4_netmask_to_prefix (netmask);
-
-	if (s_gateway && !nm_utils_ipaddr_valid (AF_INET, s_gateway)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid IP gateway '%s'.",
-		             s_gateway);
-		goto error;
-	}
-
-	if (s_dns1 && !nm_utils_ipaddr_valid (AF_INET, s_dns1)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid DNS1 address '%s'.",
-		             s_dns1);
-		goto error;
-	}
-
-	if (s_dns2 && !nm_utils_ipaddr_valid (AF_INET, s_dns2)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid DNS2 address '%s'.",
-		             s_dns2);
-		goto error;
-	}
-
-	addr = nm_ip_address_new (AF_INET, s_ipaddr, prefix, error);
-	if (!addr) {
-		g_prefix_error (error, "iBFT: malformed iscsiadm record: ");
-		goto error;
-	}
-
-	nm_setting_ip_config_add_address (s_ip4, addr);
-	nm_ip_address_unref (addr);
-
-	g_object_set (s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, s_gateway, NULL);
-
-	if (s_dns1)
-		nm_setting_ip_config_add_dns (s_ip4, s_dns1);
-	if (s_dns2)
-		nm_setting_ip_config_add_dns (s_ip4, s_dns2);
-
-success:
-	nm_connection_add_setting (connection, NM_SETTING (s_ip4));
-	return TRUE;
-
-error:
-	g_clear_object (&s_ip4);
-	return FALSE;
-}
-
-static gboolean
-connection_setting_add (const GPtrArray *block,
-                        NMConnection *connection,
-                        const char *type,
-                        const char *prefix,
-                        const char *iface,
-                        GError **error)
-{
-	NMSetting *s_con;
-	char *id, *uuid;
-	const char *s_hwaddr = NULL, *s_ip4addr = NULL, *s_vlanid;
-
-	if (!nms_ibft_reader_parse_block (block, error,
-	                                  ISCSI_VLAN_ID_TAG, &s_vlanid,
-	                                  ISCSI_HWADDR_TAG,  &s_hwaddr,
-	                                  ISCSI_IPADDR_TAG,  &s_ip4addr,
-	                                  NULL))
-		return FALSE;
-	if (!s_hwaddr) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_HWADDR_TAG);
-		return FALSE;
-	}
-
-	id = g_strdup_printf ("iBFT%s%s %s",
-	                      prefix ? " " : "",
-	                      prefix ?: "",
-	                      iface);
-
-	uuid = _nm_utils_uuid_generate_from_strings ("ibft",
-	                                             s_hwaddr,
-	                                             s_vlanid ? "V" : "v",
-	                                             s_vlanid ?: "",
-	                                             s_ip4addr ? "A" : "DHCP",
-	                                             s_ip4addr ?: "",
-	                                             NULL);
-
-	s_con = nm_setting_connection_new ();
-	g_object_set (s_con,
-	              NM_SETTING_CONNECTION_TYPE, type,
-	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_ID, id,
-	              NM_SETTING_CONNECTION_READ_ONLY, TRUE,
-	              NULL);
-
-	g_free (uuid);
-	g_free (id);
-
-	nm_connection_add_setting (connection, NM_SETTING (s_con));
-	return TRUE;
-}
-
-static gboolean
-is_ibft_vlan_device (const GPtrArray *block)
-{
-	char *s_vlan_id = NULL;
-
-	if (nms_ibft_reader_parse_block (block, NULL, ISCSI_VLAN_ID_TAG, &s_vlan_id, NULL)) {
-		g_assert (s_vlan_id);
-
-		/* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it
-		 * means "no VLAN".
-		 */
-		if (_nm_utils_ascii_str_to_int64 (s_vlan_id, 10, 1, 4095, -1) != -1)
-			return TRUE;
-	}
-	return FALSE;
-}
-
-static gboolean
-vlan_setting_add_from_block (const GPtrArray *block,
-                             NMConnection *connection,
-                             GError **error)
-{
-	NMSetting *s_vlan = NULL;
-	const char *vlan_id_str = NULL;
-	gint64 vlan_id = -1;
-	gboolean success;
-
-	g_assert (block);
-	g_assert (connection);
-
-	/* This won't fail since this function shouldn't be called unless the
-	 * iBFT VLAN ID exists and is > 0.
-	 */
-	success = nms_ibft_reader_parse_block (block, NULL, ISCSI_VLAN_ID_TAG, &vlan_id_str, NULL);
-	g_assert (success);
-	g_assert (vlan_id_str);
-
-	/* VLAN 0 is normally a valid VLAN ID, but in the iBFT case it means "no VLAN" */
-	vlan_id = _nm_utils_ascii_str_to_int64 (vlan_id_str, 10, 1, 4095, -1);
-	if (vlan_id == -1) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Invalid VLAN_ID '%s'", vlan_id_str);
-		return FALSE;
-	}
-
-	s_vlan = nm_setting_vlan_new ();
-	g_object_set (s_vlan, NM_SETTING_VLAN_ID, (guint32) vlan_id, NULL);
-	nm_connection_add_setting (connection, NM_SETTING (s_vlan));
-
-	return TRUE;
-}
-
-static gboolean
-wired_setting_add_from_block (const GPtrArray *block,
-                              NMConnection *connection,
-                              GError **error)
-{
-	NMSetting *s_wired = NULL;
-	const char *hwaddr = NULL;
-
-	g_assert (block);
-	g_assert (connection);
-
-	if (!nms_ibft_reader_parse_block (block, NULL, ISCSI_HWADDR_TAG, &hwaddr, NULL)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_HWADDR_TAG);
-		return FALSE;
-	}
-
-	if (!nm_utils_hwaddr_valid (hwaddr, ETH_ALEN)) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "iBFT: malformed iscsiadm record: invalid " ISCSI_HWADDR_TAG " '%s'.",
-		             hwaddr);
-		return FALSE;
-	}
-
-	s_wired = nm_setting_wired_new ();
-	g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, hwaddr, NULL);
-
-	nm_connection_add_setting (connection, s_wired);
-	return TRUE;
-}
-
-NMConnection *
-nms_ibft_reader_get_connection_from_block (const GPtrArray *block, GError **error)
-{
-	NMConnection *connection = NULL;
-	gboolean is_vlan = FALSE;
-	const char *iface = NULL;
-
-	g_assert (block);
-
-	if (!nms_ibft_reader_parse_block (block, error, ISCSI_IFACE_TAG, &iface, NULL)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		                     "iBFT: malformed iscsiadm record: missing " ISCSI_IFACE_TAG);
-		return NULL;
-	}
-
-	connection = nm_simple_connection_new ();
-
-	is_vlan = is_ibft_vlan_device (block);
-	if (is_vlan && !vlan_setting_add_from_block (block, connection, error))
-		goto error;
-
-	/* Always have a wired setting; for VLAN it defines the parent */
-	if (!wired_setting_add_from_block (block, connection, error))
-		goto error;
-
-	if (!ip4_setting_add_from_block (block, connection, error))
-		goto error;
-
-	if (!connection_setting_add (block,
-	                             connection,
-	                             is_vlan ? NM_SETTING_VLAN_SETTING_NAME : NM_SETTING_WIRED_SETTING_NAME,
-	                             is_vlan ? "VLAN" : NULL,
-	                             iface,
-	                             error))
-		goto error;
-
-	if (!nm_connection_normalize (connection, NULL, NULL, error))
-		goto error;
-
-	return connection;
-
-error:
-	g_object_unref (connection);
-	return NULL;
-}
-
diff --git a/src/settings/plugins/ibft/nms-ibft-reader.h b/src/settings/plugins/ibft/nms-ibft-reader.h
deleted file mode 100644
index baa81e99..00000000
--- a/src/settings/plugins/ibft/nms-ibft-reader.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2014 Red Hat, Inc.
- */
-
-#ifndef __NMS_IBFT_READER_H__
-#define __NMS_IBFT_READER_H__
-
-#include "nm-connection.h"
-
-static inline void
-_nm_auto_free_ibft_blocks (GSList **p_blocks)
-{
-	if (*p_blocks)
-		g_slist_free_full (*p_blocks, (GDestroyNotify) g_ptr_array_unref);
-}
-#define nm_auto_free_ibft_blocks nm_auto (_nm_auto_free_ibft_blocks)
-
-gboolean nms_ibft_reader_load_blocks (const char *iscsiadm_path,
-                                      GSList **out_blocks,
-                                      GError **error);
-
-NMConnection *nms_ibft_reader_get_connection_from_block (const GPtrArray *block, GError **error);
-
-gboolean nms_ibft_reader_parse_block (const GPtrArray *block, GError **error, ...) G_GNUC_NULL_TERMINATED;
-
-#endif  /* __NMS_IBFT_READER_H__ */
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1 b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
deleted file mode 100755
index 54f02da6..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns1
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = STATIC
-iface.ipaddress = 192.168.32.72
-iface.subnet_mask = 255.255.252.0
-iface.gateway = 192.168.35.254
-iface.primary_dns = 10000.500.250.1
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2 b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2
deleted file mode 100755
index ebd7a9ca..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-dns2
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = STATIC
-iface.ipaddress = 192.168.32.72
-iface.subnet_mask = 255.255.252.0
-iface.gateway = 192.168.35.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = blah.foo.bar.baz
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry
deleted file mode 100755
index 4e326048..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-entry
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = STATIC
-iface.ipaddress 192.168.32.72
-iface.subnet_mask = 255.255.252.0
-iface.gateway = 192.168.35.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway
deleted file mode 100755
index 5390a6c3..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-gateway
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = STATIC
-iface.ipaddress = 192.168.32.72
-iface.subnet_mask = 255.255.252.0
-iface.gateway = bb.cc.dd.ee
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr
deleted file mode 100755
index b41cd1f1..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-ipaddr
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = STATIC
-iface.ipaddress = aa.bb.cc.dd
-iface.subnet_mask = 255.255.252.0
-iface.gateway = 192.168.35.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record b/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record
deleted file mode 100755
index 22b34e6f..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-bad-record
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = DHCP
-iface.gateway = 10.16.52.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp b/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp
deleted file mode 100755
index 556b0586..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-dhcp
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = DHCP
-iface.gateway = 10.16.52.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f1
-iface.bootproto = DHCP
-iface.gateway = 10.16.52.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth1
-node.name = iqn.1.2008-11.com.blahblah:iscsi1
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-static b/src/settings/plugins/ibft/tests/iscsiadm-test-static
deleted file mode 100755
index 51711480..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-static
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = STATIC
-iface.ipaddress = 192.168.32.72
-iface.subnet_mask = 255.255.252.0
-iface.gateway = 192.168.35.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth0
-node.name = iqn.0.2008-11.com.blahblah:iscsi0
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-# BEGIN RECORD
-iface.initiatorname = iqn.pjones6
-iface.hwaddress = 00:33:21:98:b9:f1
-iface.bootproto = DHCP
-iface.gateway = 10.16.52.254
-iface.primary_dns = 10.16.255.2
-iface.secondary_dns = 10.16.255.3
-iface.vlan_id = 0
-iface.net_ifacename = eth1
-node.name = iqn.1.2008-11.com.blahblah:iscsi1
-node.conn[0].address = 10.16.52.16
-node.conn[0].port = 3260
-node.boot_lun = 00000000
-# END RECORD
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/iscsiadm-test-vlan b/src/settings/plugins/ibft/tests/iscsiadm-test-vlan
deleted file mode 100755
index 59b80bd0..00000000
--- a/src/settings/plugins/ibft/tests/iscsiadm-test-vlan
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-cat << EOF
-# BEGIN RECORD 6.2.0.873-21
-iface.initiatorname = iqn.2010-04.org.ipxe:d05faa97-c4be-44f6-a723-efde9aa399a0
-iface.transport_name = tcp
-iface.hwaddress = 00:33:21:98:b9:f0
-iface.bootproto = STATIC
-iface.ipaddress = 192.168.6.200
-iface.subnet_mask = 255.255.255.0
-iface.vlan_id = 123
-iface.net_ifacename = eth0
-node.name = iqn.2003-01.org.x:disk1
-node.conn[0].address = 192.168.6.32
-node.conn[0].port = 3260
-node.boot_lun = 01000000
-# END RECORD
-EOF
-
diff --git a/src/settings/plugins/ibft/tests/meson.build b/src/settings/plugins/ibft/tests/meson.build
deleted file mode 100644
index e2f9ca7e..00000000
--- a/src/settings/plugins/ibft/tests/meson.build
+++ /dev/null
@@ -1,16 +0,0 @@
-test_unit = 'test-ibft'
-
-test_ibft_dir = meson.current_source_dir()
-
-exe = executable(
-  test_unit,
-  test_unit + '.c',
-  dependencies: test_nm_dep,
-  link_with: libnms_ibft_core,
-)
-
-test(
-  'ibft/' + test_unit,
-  test_script,
-  args: test_args + [exe.full_path()],
-)
diff --git a/src/settings/plugins/ibft/tests/test-ibft.c b/src/settings/plugins/ibft/tests/test-ibft.c
deleted file mode 100644
index f5b584a1..00000000
--- a/src/settings/plugins/ibft/tests/test-ibft.c
+++ /dev/null
@@ -1,292 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2014 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <netinet/ether.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-
-#include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
-
-#include "settings/plugins/ibft/nms-ibft-reader.h"
-
-#include "nm-test-utils-core.h"
-
-#define TEST_DIR       NM_BUILD_SRCDIR"/src/settings/plugins/ibft/tests"
-
-static GPtrArray *
-read_block (const char *iscsiadm_path, const char *expected_mac)
-{
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	GSList *iter;
-	GPtrArray *block = NULL;
-	GError *error = NULL;
-	gboolean success;
-
-	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	nmtst_assert_success (success, error);
-
-	g_assert (blocks);
-	for (iter = blocks; iter; iter = iter->next) {
-		const char *s_hwaddr = NULL;
-
-		if (!nms_ibft_reader_parse_block (iter->data, NULL, "iface.hwaddress", &s_hwaddr, NULL))
-			continue;
-		g_assert (s_hwaddr);
-		if (nm_utils_hwaddr_matches (s_hwaddr, -1, expected_mac, -1)) {
-			block = g_ptr_array_ref (iter->data);
-			break;
-		}
-	}
-	g_assert (block);
-
-	return block;
-}
-
-static void
-test_read_ibft_dhcp (void)
-{
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingWired *s_wired;
-	NMSettingIPConfig *s_ip4;
-	GError *error = NULL;
-	const char *mac_address;
-	const char *expected_mac_address = "00:33:21:98:b9:f1";
-	GPtrArray *block;
-
-	block = read_block (TEST_DIR "/iscsiadm-test-dhcp", expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_no_error (error);
-	nmtst_assert_connection_verifies_without_normalization (connection);
-
-	g_assert (!nm_connection_get_setting_vlan (connection));
-
-	/* ===== CONNECTION SETTING ===== */
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
-	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "iBFT eth1");
-	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
-	g_assert (nm_setting_connection_get_autoconnect (s_con));
-	g_assert (nm_setting_connection_get_read_only (s_con));
-
-	/* ===== WIRED SETTING ===== */
-	s_wired = nm_connection_get_setting_wired (connection);
-	g_assert (s_wired);
-	mac_address = nm_setting_wired_get_mac_address (s_wired);
-	g_assert (mac_address);
-	g_assert (nm_utils_hwaddr_matches (mac_address, -1, expected_mac_address, -1));
-	g_assert_cmpint (nm_setting_wired_get_mtu (s_wired), ==, 0);
-
-	/* ===== IPv4 SETTING ===== */
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	g_assert (s_ip4);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-
-	g_object_unref (connection);
-	g_ptr_array_unref (block);
-}
-
-static void
-test_read_ibft_static (void)
-{
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingWired *s_wired;
-	NMSettingIPConfig *s_ip4;
-	GError *error = NULL;
-	const char *mac_address;
-	const char *expected_mac_address = "00:33:21:98:b9:f0";
-	NMIPAddress *ip4_addr;
-	GPtrArray *block;
-
-	block = read_block (TEST_DIR "/iscsiadm-test-static", expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_no_error (error);
-	nmtst_assert_connection_verifies_without_normalization (connection);
-
-	g_assert (!nm_connection_get_setting_vlan (connection));
-
-	/* ===== CONNECTION SETTING ===== */
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
-	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "iBFT eth0");
-	g_assert_cmpint (nm_setting_connection_get_timestamp (s_con), ==, 0);
-	g_assert (nm_setting_connection_get_autoconnect (s_con));
-	g_assert (nm_setting_connection_get_read_only (s_con));
-
-	/* ===== WIRED SETTING ===== */
-	s_wired = nm_connection_get_setting_wired (connection);
-	g_assert (s_wired);
-	mac_address = nm_setting_wired_get_mac_address (s_wired);
-	g_assert (mac_address);
-	g_assert (nm_utils_hwaddr_matches (mac_address, -1, expected_mac_address, -1));
-	g_assert_cmpint (nm_setting_wired_get_mtu (s_wired), ==, 0);
-
-	/* ===== IPv4 SETTING ===== */
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	g_assert (s_ip4);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 2);
-	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 0), ==, "10.16.255.2");
-	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 1), ==, "10.16.255.3");
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 1);
-	ip4_addr = nm_setting_ip_config_get_address (s_ip4, 0);
-	g_assert (ip4_addr);
-	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "192.168.32.72");
-	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 22);
-
-	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "192.168.35.254");
-
-	g_object_unref (connection);
-	g_ptr_array_unref (block);
-}
-
-static void
-test_read_ibft_malformed (gconstpointer user_data)
-{
-	const char *iscsiadm_path = user_data;
-	nm_auto_free_ibft_blocks GSList *blocks = NULL;
-	GError *error = NULL;
-	gboolean success;
-
-	g_assert (g_file_test (iscsiadm_path, G_FILE_TEST_EXISTS));
-
-	NMTST_EXPECT_NM_WARN ("*malformed iscsiadm record*");
-
-	success = nms_ibft_reader_load_blocks (iscsiadm_path, &blocks, &error);
-	nmtst_assert_success (success, error);
-
-	g_assert (!blocks);
-
-	g_test_assert_expected_messages ();
-}
-
-static void
-test_read_ibft_bad_address (gconstpointer user_data)
-{
-	const char *iscsiadm_path = user_data;
-	NMConnection *connection;
-	const char *expected_mac_address = "00:33:21:98:b9:f0";
-	GPtrArray *block;
-	GError *error = NULL;
-
-	g_assert (g_file_test (iscsiadm_path, G_FILE_TEST_EXISTS));
-
-	block = read_block (iscsiadm_path, expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION);
-	g_assert (strstr (error->message, "iBFT: malformed iscsiadm record: invalid"));
-	g_clear_error (&error);
-	g_assert (connection == NULL);
-
-	g_ptr_array_unref (block);
-}
-
-static void
-test_read_ibft_vlan (void)
-{
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingWired *s_wired;
-	NMSettingVlan *s_vlan;
-	NMSettingIPConfig *s_ip4;
-	const char *mac_address;
-	const char *expected_mac_address = "00:33:21:98:b9:f0";
-	NMIPAddress *ip4_addr;
-	GError *error = NULL;
-	GPtrArray *block;
-
-	block = read_block (TEST_DIR "/iscsiadm-test-vlan", expected_mac_address);
-
-	connection = nms_ibft_reader_get_connection_from_block (block, &error);
-	g_assert_no_error (error);
-	nmtst_assert_connection_verifies_without_normalization (connection);
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_VLAN_SETTING_NAME);
-
-	/* ===== WIRED SETTING ===== */
-	s_wired = nm_connection_get_setting_wired (connection);
-	g_assert (s_wired);
-	mac_address = nm_setting_wired_get_mac_address (s_wired);
-	g_assert (mac_address);
-	g_assert (nm_utils_hwaddr_matches (mac_address, -1, expected_mac_address, -1));
-
-	/* ===== VLAN SETTING ===== */
-	s_vlan = nm_connection_get_setting_vlan (connection);
-	g_assert (s_vlan);
-	g_assert_cmpint (nm_setting_vlan_get_id (s_vlan), ==, 123);
-	g_assert_cmpstr (nm_setting_vlan_get_parent (s_vlan), ==, NULL);
-
-	/* ===== IPv4 SETTING ===== */
-	s_ip4 = nm_connection_get_setting_ip4_config (connection);
-	g_assert (s_ip4);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 0);
-
-	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 1);
-	ip4_addr = nm_setting_ip_config_get_address (s_ip4, 0);
-	g_assert (ip4_addr);
-	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "192.168.6.200");
-	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 24);
-
-	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, NULL);
-
-	g_object_unref (connection);
-	g_ptr_array_unref (block);
-}
-
-NMTST_DEFINE ();
-
-#define TPATH "/settings/plugins/ibft/"
-
-int main (int argc, char **argv)
-{
-	nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT");
-
-	g_test_add_func (TPATH "ibft/dhcp", test_read_ibft_dhcp);
-	g_test_add_func (TPATH "ibft/static", test_read_ibft_static);
-	g_test_add_func (TPATH "ibft/vlan", test_read_ibft_vlan);
-	g_test_add_data_func (TPATH "ibft/bad-record-read", TEST_DIR "/iscsiadm-test-bad-record", test_read_ibft_malformed);
-	g_test_add_data_func (TPATH "ibft/bad-entry-read", TEST_DIR "/iscsiadm-test-bad-entry", test_read_ibft_malformed);
-	g_test_add_data_func (TPATH "ibft/bad-ipaddr-read", TEST_DIR "/iscsiadm-test-bad-ipaddr", test_read_ibft_bad_address);
-	g_test_add_data_func (TPATH "ibft/bad-gateway-read", TEST_DIR "/iscsiadm-test-bad-gateway", test_read_ibft_bad_address);
-	g_test_add_data_func (TPATH "ibft/bad-dns1-read", TEST_DIR "/iscsiadm-test-bad-dns1", test_read_ibft_bad_address);
-	g_test_add_data_func (TPATH "ibft/bad-dns2-read", TEST_DIR "/iscsiadm-test-bad-dns2", test_read_ibft_bad_address);
-
-	return g_test_run ();
-}
-
diff --git a/src/settings/plugins/ifcfg-rh/meson.build b/src/settings/plugins/ifcfg-rh/meson.build
index 9024782a..58acdcfc 100644
--- a/src/settings/plugins/ifcfg-rh/meson.build
+++ b/src/settings/plugins/ifcfg-rh/meson.build
@@ -19,7 +19,6 @@ libnmdbus_ifcfg_rh = static_library(
 )
 
 core_sources = files(
-  'nm-inotify-helper.c',
   'nms-ifcfg-rh-reader.c',
   'nms-ifcfg-rh-utils.c',
   'nms-ifcfg-rh-writer.c',
@@ -36,7 +35,7 @@ libnms_ifcfg_rh_core = static_library(
   dependencies: deps,
 )
 
-sources = [dbus_sources] + core_sources + files('nms-ifcfg-rh-connection.c', 'nms-ifcfg-rh-plugin.c')
+sources = [dbus_sources] + core_sources + files('nms-ifcfg-rh-storage.c', 'nms-ifcfg-rh-plugin.c')
 
 libnm_settings_plugin_ifcfg_rh = shared_module(
   'nm-settings-plugin-ifcfg-rh',
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
deleted file mode 100644
index 04cbb5bc..00000000
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
+++ /dev/null
@@ -1,213 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2008 - 2017 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nm-inotify-helper.h"
-
-#include <unistd.h>
-#include <sys/inotify.h>
-
-#include "NetworkManagerUtils.h"
-
-/* NOTE: this code should be killed once we depend on a new enough glib to
- * include the patches from https://bugzilla.gnome.org/show_bug.cgi?id=532815
- */
-
-/*****************************************************************************/
-
-enum {
-	EVENT,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	int ifd;
-	guint inotify_id;
-	GHashTable *wd_refs;
-} NMInotifyHelperPrivate;
-
-struct _NMInotifyHelper {
-	GObject parent;
-	NMInotifyHelperPrivate _priv;
-};
-
-struct _NMInotifyHelperClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMInotifyHelper, nm_inotify_helper, G_TYPE_OBJECT)
-
-#define NM_INOTIFY_HELPER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMInotifyHelper, NM_IS_INOTIFY_HELPER)
-
-/*****************************************************************************/
-
-NM_DEFINE_SINGLETON_GETTER (NMInotifyHelper, nm_inotify_helper_get, NM_TYPE_INOTIFY_HELPER);
-
-/*****************************************************************************/
-
-int
-nm_inotify_helper_add_watch (NMInotifyHelper *self, const char *path)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-	int wd;
-	guint refcount;
-
-	if (priv->ifd < 0)
-		return -1;
-
-	/* We only care about modifications since we're just trying to get change
-	 * notifications on hardlinks.
-	 */
-
-	wd = inotify_add_watch (priv->ifd, path, IN_CLOSE_WRITE);
-	if (wd < 0)
-		return -1;
-
-	refcount = GPOINTER_TO_UINT (g_hash_table_lookup (priv->wd_refs, GINT_TO_POINTER (wd)));
-	refcount++;
-	g_hash_table_replace (priv->wd_refs, GINT_TO_POINTER (wd), GUINT_TO_POINTER (refcount));
-
-	return wd;
-}
-
-void
-nm_inotify_helper_remove_watch (NMInotifyHelper *self, int wd)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-	guint refcount;
-
-	if (priv->ifd < 0)
-		return;
-
-	refcount = GPOINTER_TO_UINT (g_hash_table_lookup (priv->wd_refs, GINT_TO_POINTER (wd)));
-	if (!refcount)
-		return;
-
-	refcount--;
-	if (!refcount) {
-		g_hash_table_remove (priv->wd_refs, GINT_TO_POINTER (wd));
-		inotify_rm_watch (priv->ifd, wd);
-	} else
-		g_hash_table_replace (priv->wd_refs, GINT_TO_POINTER (wd), GUINT_TO_POINTER (refcount));
-}
-
-static gboolean
-inotify_event_handler (GIOChannel *channel, GIOCondition cond, gpointer user_data)
-{
-	NMInotifyHelper *self = NM_INOTIFY_HELPER (user_data);
-	struct inotify_event evt;
-
-	/* read the notifications from the watch descriptor */
-	while (g_io_channel_read_chars (channel, (char *) &evt, sizeof (struct inotify_event), NULL, NULL) == G_IO_STATUS_NORMAL) {
-		char filename[PATH_MAX + 1];
-
-		filename[0] = '\0';
-		if (evt.len > 0) {
-			g_io_channel_read_chars (channel,
-			                        filename,
-			                        evt.len > PATH_MAX ? PATH_MAX : evt.len,
-			                        NULL, NULL);
-		}
-
-		if (!(evt.mask & IN_IGNORED))
-			g_signal_emit (self, signals[EVENT], 0, &evt, &filename[0]);
-	}
-
-	return TRUE;
-}
-
-static gboolean
-init_inotify (NMInotifyHelper *self)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-	GIOChannel *channel;
-	int errsv;
-
-	priv->ifd = inotify_init1 (IN_CLOEXEC);
-	if (priv->ifd == -1) {
-		errsv = errno;
-		nm_log_warn (LOGD_SETTINGS, "couldn't initialize inotify: %s (%d)", nm_strerror_native (errsv), errsv);
-		return FALSE;
-	}
-
-	/* Watch the inotify descriptor for file/directory change events */
-	channel = g_io_channel_unix_new (priv->ifd);
-	g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
-	g_io_channel_set_encoding (channel, NULL, NULL);
-
-	priv->inotify_id = g_io_add_watch (channel,
-	                                   G_IO_IN | G_IO_ERR,
-	                                   (GIOFunc) inotify_event_handler,
-	                                   (gpointer) self);
-	g_io_channel_unref (channel);
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-nm_inotify_helper_init (NMInotifyHelper *self)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
-
-	priv->wd_refs = g_hash_table_new (nm_direct_hash, NULL);
-}
-
-static void
-constructed (GObject *object)
-{
-	G_OBJECT_CLASS (nm_inotify_helper_parent_class)->constructed (object);
-
-	init_inotify (NM_INOTIFY_HELPER (object));
-}
-
-static void
-finalize (GObject *object)
-{
-	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE ((NMInotifyHelper *) object);
-
-	nm_clear_g_source (&priv->inotify_id);
-	nm_close (priv->ifd);
-
-	g_hash_table_destroy (priv->wd_refs);
-
-	G_OBJECT_CLASS (nm_inotify_helper_parent_class)->finalize (object);
-}
-
-static void
-nm_inotify_helper_class_init (NMInotifyHelperClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->constructed = constructed;
-	object_class->finalize = finalize;
-
-	signals[EVENT] =
-	    g_signal_new ("event",
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0,
-	                  NULL, NULL, NULL,
-	                  G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_STRING);
-}
-
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.h b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.h
deleted file mode 100644
index b887ae37..00000000
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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 2008 Red Hat, Inc.
- */
-
-#ifndef __NM_INOTIFY_HELPER_H__
-#define __NM_INOTIFY_HELPER_H__
-
-/* NOTE: this code should be killed once we depend on a new enough glib to
- * include the patches from https://bugzilla.gnome.org/show_bug.cgi?id=532815
- */
-
-#define NM_TYPE_INOTIFY_HELPER            (nm_inotify_helper_get_type ())
-#define NM_INOTIFY_HELPER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_INOTIFY_HELPER, NMInotifyHelper))
-#define NM_INOTIFY_HELPER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_INOTIFY_HELPER, NMInotifyHelperClass))
-#define NM_IS_INOTIFY_HELPER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_INOTIFY_HELPER))
-#define NM_IS_INOTIFY_HELPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_INOTIFY_HELPER))
-#define NM_INOTIFY_HELPER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_INOTIFY_HELPER, NMInotifyHelperClass))
-
-typedef struct _NMInotifyHelper NMInotifyHelper;
-typedef struct _NMInotifyHelperClass NMInotifyHelperClass;
-
-GType nm_inotify_helper_get_type (void);
-
-NMInotifyHelper * nm_inotify_helper_get (void);
-
-int nm_inotify_helper_add_watch (NMInotifyHelper *helper, const char *path);
-
-void nm_inotify_helper_remove_watch (NMInotifyHelper *helper, int wd);
-
-static inline gboolean
-nm_inotify_helper_clear_watch (NMInotifyHelper *helper, int *wd)
-{
-	int x;
-
-	if (wd && ((x = *wd) >= 0)) {
-		*wd = -1;
-		nm_inotify_helper_remove_watch (helper, x);
-		return TRUE;
-	}
-	return FALSE;
-}
-
-#endif  /* __NM_INOTIFY_HELPER_H__ */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
index eaff2db4..ff01fc7a 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-common.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
deleted file mode 100644
index 4f769c5f..00000000
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
+++ /dev/null
@@ -1,511 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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) 2008 - 2011 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifcfg-rh-connection.h"
-
-#include <sys/inotify.h>
-#include <glib/gstdio.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-setting-connection.h"
-#include "nm-setting-wired.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-gsm.h"
-#include "nm-setting-cdma.h"
-#include "nm-setting-pppoe.h"
-#include "nm-setting-wireless-security.h"
-#include "nm-setting-8021x.h"
-#include "platform/nm-platform.h"
-#include "nm-config.h"
-
-#include "nms-ifcfg-rh-common.h"
-#include "nms-ifcfg-rh-reader.h"
-#include "nms-ifcfg-rh-writer.h"
-#include "nms-ifcfg-rh-utils.h"
-#include "nm-inotify-helper.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_UNMANAGED_SPEC,
-	PROP_UNRECOGNIZED_SPEC,
-);
-
-enum {
-	IFCFG_CHANGED,
-	LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-typedef struct {
-	gulong ih_event_id;
-
-	int file_wd;
-
-	char *keyfile;
-	int keyfile_wd;
-
-	char *routefile;
-	int routefile_wd;
-
-	char *route6file;
-	int route6file_wd;
-
-	char *unmanaged_spec;
-	char *unrecognized_spec;
-
-	gulong devtimeout_link_changed_handler;
-	guint devtimeout_timeout_id;
-
-	NMInotifyHelper *inotify_helper;
-} NMIfcfgConnectionPrivate;
-
-struct _NMIfcfgConnection {
-	NMSettingsConnection parent;
-	NMIfcfgConnectionPrivate _priv;
-};
-
-struct _NMIfcfgConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMIfcfgConnection, nm_ifcfg_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-#define NM_IFCFG_CONNECTION_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMIfcfgConnection, NM_IS_IFCFG_CONNECTION)
-
-/*****************************************************************************/
-
-static gboolean
-devtimeout_ready (gpointer user_data)
-{
-	NMIfcfgConnection *self = user_data;
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	priv->devtimeout_timeout_id = 0;
-	nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
-	return FALSE;
-}
-
-static void
-link_changed (NMPlatform *platform, int obj_type_i, int ifindex, const NMPlatformLink *link,
-              int change_type_i,
-              NMConnection *self)
-{
-	const NMPlatformSignalChangeType change_type = change_type_i;
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) self);
-	const char *ifname;
-
-	ifname = nm_connection_get_interface_name (self);
-	if (g_strcmp0 (link->name, ifname) != 0)
-		return;
-
-	if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
-		return;
-
-	nm_log_info (LOGD_SETTINGS, "Device %s appeared; connection '%s' now ready",
-	             ifname, nm_connection_get_id (self));
-
-	g_signal_handler_disconnect (platform, priv->devtimeout_link_changed_handler);
-	priv->devtimeout_link_changed_handler = 0;
-	g_source_remove (priv->devtimeout_timeout_id);
-
-	/* Don't declare the connection ready right away, since NMManager may not have
-	 * started processing the device yet.
-	 */
-	priv->devtimeout_timeout_id = g_idle_add (devtimeout_ready, self);
-}
-
-static gboolean
-devtimeout_expired (gpointer user_data)
-{
-	NMIfcfgConnection *self = user_data;
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	nm_log_info (LOGD_SETTINGS, "Device for connection '%s' did not appear before timeout",
-	             nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (self)));
-
-	g_signal_handler_disconnect (NM_PLATFORM_GET, priv->devtimeout_link_changed_handler);
-	priv->devtimeout_link_changed_handler = 0;
-	priv->devtimeout_timeout_id = 0;
-
-	nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), TRUE);
-	return FALSE;
-}
-
-static void
-nm_ifcfg_connection_check_devtimeout (NMIfcfgConnection *self)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-	NMSettingConnection *s_con;
-	const char *ifname;
-	const char *filename;
-	guint devtimeout;
-	const NMPlatformLink *pllink;
-
-	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (self)));
-
-	if (!nm_setting_connection_get_autoconnect (s_con))
-		return;
-	ifname = nm_setting_connection_get_interface_name (s_con);
-	if (!ifname)
-		return;
-	filename = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (self));
-	if (!filename)
-		return;
-
-	pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname);
-	if (pllink && pllink->initialized)
-		return;
-
-	devtimeout = devtimeout_from_file (filename);
-	if (!devtimeout)
-		return;
-
-	/* ONBOOT=yes, DEVICE and DEVTIMEOUT are set, but device is not present */
-	nm_settings_connection_set_ready (NM_SETTINGS_CONNECTION (self), FALSE);
-
-	nm_log_info (LOGD_SETTINGS, "Waiting %u seconds for %s to appear for connection '%s'",
-	             devtimeout, ifname, nm_settings_connection_get_id (NM_SETTINGS_CONNECTION (self)));
-
-	priv->devtimeout_link_changed_handler =
-	    g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED,
-	                      G_CALLBACK (link_changed), self);
-	priv->devtimeout_timeout_id = g_timeout_add_seconds (devtimeout, devtimeout_expired, self);
-}
-
-static void
-files_changed_cb (NMInotifyHelper *ih,
-                  struct inotify_event *evt,
-                  const char *path,
-                  gpointer user_data)
-{
-	NMIfcfgConnection *self = NM_IFCFG_CONNECTION (user_data);
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	if (   (evt->wd != priv->file_wd)
-	    && (evt->wd != priv->keyfile_wd)
-	    && (evt->wd != priv->routefile_wd)
-	    && (evt->wd != priv->route6file_wd))
-		return;
-
-	/* push the event up to the plugin */
-	g_signal_emit (self, signals[IFCFG_CHANGED], 0);
-}
-
-static void
-path_watch_stop (NMIfcfgConnection *self)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-
-	nm_clear_g_signal_handler (priv->inotify_helper, &priv->ih_event_id);
-
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->file_wd);
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->keyfile_wd);
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->routefile_wd);
-	nm_inotify_helper_clear_watch (priv->inotify_helper, &priv->route6file_wd);
-
-	nm_clear_g_free (&priv->keyfile);
-	nm_clear_g_free (&priv->routefile);
-	nm_clear_g_free (&priv->route6file);
-}
-
-static void
-filename_changed (GObject *object,
-                  GParamSpec *pspec,
-                  gpointer user_data)
-{
-	NMIfcfgConnection *self = NM_IFCFG_CONNECTION (object);
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (self);
-	const char *ifcfg_path;
-
-	path_watch_stop (self);
-
-	ifcfg_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (self));
-	if (!ifcfg_path)
-		return;
-
-	priv->keyfile = utils_get_keys_path (ifcfg_path);
-	priv->routefile = utils_get_route_path (ifcfg_path);
-	priv->route6file = utils_get_route6_path (ifcfg_path);
-
-	if (nm_config_get_monitor_connection_files (nm_config_get ())) {
-		NMInotifyHelper *ih;
-
-		if (!priv->inotify_helper)
-			priv->inotify_helper = g_object_ref (nm_inotify_helper_get ());
-		ih = priv->inotify_helper;
-
-		priv->ih_event_id = g_signal_connect (ih, "event", G_CALLBACK (files_changed_cb), self);
-		priv->file_wd = nm_inotify_helper_add_watch (ih, ifcfg_path);
-		priv->keyfile_wd = nm_inotify_helper_add_watch (ih, priv->keyfile);
-		priv->routefile_wd = nm_inotify_helper_add_watch (ih, priv->routefile);
-		priv->route6file_wd = nm_inotify_helper_add_watch (ih, priv->route6file);
-	}
-}
-
-const char *
-nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self)
-{
-	g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
-
-	return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unmanaged_spec;
-}
-
-const char *
-nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self)
-{
-	g_return_val_if_fail (NM_IS_IFCFG_CONNECTION (self), NULL);
-
-	return NM_IFCFG_CONNECTION_GET_PRIVATE (self)->unrecognized_spec;
-}
-
-static gboolean
-commit_changes (NMSettingsConnection *connection,
-                NMConnection *new_connection,
-                NMSettingsConnectionCommitReason commit_reason,
-                NMConnection **out_reread_connection,
-                char **out_logmsg_change,
-                GError **error)
-{
-	const char *filename;
-	gs_unref_object NMConnection *reread = NULL;
-	gboolean reread_same = TRUE;
-	const char *operation_message;
-	gs_free char *ifcfg_path = NULL;
-
-	nm_assert (out_reread_connection && !*out_reread_connection);
-	nm_assert (!out_logmsg_change || !*out_logmsg_change);
-
-	filename = nm_settings_connection_get_filename (connection);
-	if (!nms_ifcfg_rh_writer_write_connection (new_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 gboolean
-delete (NMSettingsConnection *connection,
-        GError **error)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) connection);
-	const char *filename;
-
-	filename = nm_settings_connection_get_filename (connection);
-	if (filename) {
-		g_unlink (filename);
-		if (priv->keyfile)
-			g_unlink (priv->keyfile);
-		if (priv->routefile)
-			g_unlink (priv->routefile);
-		if (priv->route6file)
-			g_unlink (priv->route6file);
-	}
-
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-get_property (GObject *object, guint prop_id,
-              GValue *value, GParamSpec *pspec)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) object);
-
-	switch (prop_id) {
-	case PROP_UNMANAGED_SPEC:
-		g_value_set_string (value, priv->unmanaged_spec);
-		break;
-	case PROP_UNRECOGNIZED_SPEC:
-		g_value_set_string (value, priv->unrecognized_spec);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) object);
-
-	switch (prop_id) {
-	case PROP_UNMANAGED_SPEC:
-		priv->unmanaged_spec = g_value_dup_string (value);
-		break;
-	case PROP_UNRECOGNIZED_SPEC:
-		priv->unrecognized_spec = g_value_dup_string (value);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_ifcfg_connection_init (NMIfcfgConnection *connection)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE (connection);
-
-	priv->file_wd = -1;
-	priv->keyfile_wd = -1;
-	priv->routefile_wd = -1;
-	priv->route6file_wd = -1;
-
-	g_signal_connect (connection, "notify::" NM_SETTINGS_CONNECTION_FILENAME,
-	                  G_CALLBACK (filename_changed), NULL);
-}
-
-NMIfcfgConnection *
-nm_ifcfg_connection_new (NMConnection *source,
-                         const char *full_path,
-                         GError **error,
-                         gboolean *out_ignore_error)
-{
-	GObject *object;
-	NMConnection *tmp;
-	char *unhandled_spec = NULL;
-	const char *unmanaged_spec = NULL, *unrecognized_spec = NULL;
-
-	g_assert (source || full_path);
-
-	if (out_ignore_error)
-		*out_ignore_error = FALSE;
-
-	/* If we're given a connection already, prefer that instead of re-reading */
-	if (source)
-		tmp = g_object_ref (source);
-	else {
-		tmp = connection_from_file (full_path,
-		                            &unhandled_spec,
-		                            error,
-		                            out_ignore_error);
-		if (!tmp)
-			return NULL;
-	}
-
-	if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unmanaged:"))
-		unmanaged_spec = unhandled_spec + strlen ("unmanaged:");
-	else if (unhandled_spec && g_str_has_prefix (unhandled_spec, "unrecognized:"))
-		unrecognized_spec = unhandled_spec + strlen ("unrecognized:");
-
-	object = (GObject *) g_object_new (NM_TYPE_IFCFG_CONNECTION,
-	                                   NM_SETTINGS_CONNECTION_FILENAME, full_path,
-	                                   NM_IFCFG_CONNECTION_UNMANAGED_SPEC, unmanaged_spec,
-	                                   NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, unrecognized_spec,
-	                                   NULL);
-	/* Update our settings with what was read from the file */
-	if (nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
-	                                   tmp,
-	                                   full_path
-	                                     ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED
-	                                     : NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED,
-	                                   NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                                   NULL,
-	                                   error))
-		nm_ifcfg_connection_check_devtimeout (NM_IFCFG_CONNECTION (object));
-	else
-		g_clear_object (&object);
-
-	g_object_unref (tmp);
-	g_free (unhandled_spec);
-	return (NMIfcfgConnection *) object;
-}
-
-static void
-dispose (GObject *object)
-{
-	NMIfcfgConnectionPrivate *priv = NM_IFCFG_CONNECTION_GET_PRIVATE ((NMIfcfgConnection *) object);
-
-	path_watch_stop (NM_IFCFG_CONNECTION (object));
-
-	nm_clear_g_signal_handler (NM_PLATFORM_GET, &priv->devtimeout_link_changed_handler);
-	nm_clear_g_source (&priv->devtimeout_timeout_id);
-
-	g_clear_object (&priv->inotify_helper);
-
-	g_clear_pointer (&priv->unmanaged_spec, g_free);
-	g_clear_pointer (&priv->unrecognized_spec, g_free);
-
-	G_OBJECT_CLASS (nm_ifcfg_connection_parent_class)->dispose (object);
-}
-
-static void
-nm_ifcfg_connection_class_init (NMIfcfgConnectionClass *ifcfg_connection_class)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (ifcfg_connection_class);
-	NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (ifcfg_connection_class);
-
-	object_class->set_property = set_property;
-	object_class->get_property = get_property;
-	object_class->dispose      = dispose;
-
-	settings_class->delete = delete;
-	settings_class->commit_changes = commit_changes;
-
-	obj_properties[PROP_UNMANAGED_SPEC] =
-	     g_param_spec_string (NM_IFCFG_CONNECTION_UNMANAGED_SPEC, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_UNRECOGNIZED_SPEC] =
-	     g_param_spec_string (NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, "", "",
-	                          NULL,
-	                          G_PARAM_READWRITE |
-	                          G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-
-	signals[IFCFG_CHANGED] =
-	    g_signal_new ("ifcfg-changed",
-	                  G_OBJECT_CLASS_TYPE (object_class),
-	                  G_SIGNAL_RUN_LAST,
-	                  0, NULL, NULL,
-	                  g_cclosure_marshal_VOID__VOID,
-	                  G_TYPE_NONE, 0);
-}
-
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h
deleted file mode 100644
index fd68f2d3..00000000
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service
- *
- * 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) 2008 - 2011 Red Hat, Inc.
- */
-
-#ifndef __NETWORKMANAGER_IFCFG_CONNECTION_H__
-#define __NETWORKMANAGER_IFCFG_CONNECTION_H__
-
-#include "nm-dbus-interface.h"
-#include "settings/nm-settings-connection.h"
-
-#define NM_TYPE_IFCFG_CONNECTION            (nm_ifcfg_connection_get_type ())
-#define NM_IFCFG_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnection))
-#define NM_IFCFG_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionClass))
-#define NM_IS_IFCFG_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IFCFG_CONNECTION))
-#define NM_IS_IFCFG_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_IFCFG_CONNECTION))
-#define NM_IFCFG_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IFCFG_CONNECTION, NMIfcfgConnectionClass))
-
-#define NM_IFCFG_CONNECTION_UNMANAGED_SPEC    "unmanaged-spec"
-#define NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC "unrecognized-spec"
-
-typedef struct _NMIfcfgConnection NMIfcfgConnection;
-typedef struct _NMIfcfgConnectionClass NMIfcfgConnectionClass;
-
-GType nm_ifcfg_connection_get_type (void);
-
-NMIfcfgConnection *nm_ifcfg_connection_new (NMConnection *source,
-                                            const char *full_path,
-                                            GError **error,
-                                            gboolean *out_ignore_error);
-
-const char *nm_ifcfg_connection_get_unmanaged_spec (NMIfcfgConnection *self);
-const char *nm_ifcfg_connection_get_unrecognized_spec (NMIfcfgConnection *self);
-
-gboolean nm_ifcfg_connection_update (NMIfcfgConnection *self,
-                                     GHashTable *new_settings,
-                                     GError **error);
-
-#endif /* __NETWORKMANAGER_IFCFG_CONNECTION_H__ */
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 5160dbf0..cc4fe4ce 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * Dan Williams <dcbw@redhat.com>
@@ -25,23 +24,26 @@
 
 #include "nms-ifcfg-rh-plugin.h"
 
-#include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <gmodule.h>
+#include <unistd.h>
 
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "nm-std-aux/nm-dbus-compat.h"
-#include "nm-setting-connection.h"
-#include "settings/nm-settings-plugin.h"
+#include "nm-utils.h"
+#include "nm-core-internal.h"
 #include "nm-config.h"
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-utils.h"
 #include "NetworkManagerUtils.h"
 
-#include "nms-ifcfg-rh-connection.h"
+#include "nms-ifcfg-rh-storage.h"
 #include "nms-ifcfg-rh-common.h"
+#include "nms-ifcfg-rh-utils.h"
 #include "nms-ifcfg-rh-reader.h"
 #include "nms-ifcfg-rh-writer.h"
-#include "nms-ifcfg-rh-utils.h"
-#include "shvar.h"
 
 #define IFCFGRH1_BUS_NAME                               "com.redhat.ifcfgrh1"
 #define IFCFGRH1_OBJECT_PATH                            "/com/redhat/ifcfgrh1"
@@ -60,31 +62,25 @@ typedef struct {
 		guint regist_id;
 	} dbus;
 
-	GHashTable *connections;  /* uuid::connection */
-	gboolean initialized;
+	NMSettUtilStorages storages;
 
-	GFileMonitor *ifcfg_monitor;
-	gulong ifcfg_monitor_id;
-} SettingsPluginIfcfgPrivate;
+	GHashTable *unmanaged_specs;
+	GHashTable *unrecognized_specs;
 
-struct _SettingsPluginIfcfg {
+} NMSIfcfgRHPluginPrivate;
+
+struct _NMSIfcfgRHPlugin {
 	NMSettingsPlugin parent;
-	SettingsPluginIfcfgPrivate _priv;
+	NMSIfcfgRHPluginPrivate _priv;
 };
 
-struct _SettingsPluginIfcfgClass {
+struct _NMSIfcfgRHPluginClass {
 	NMSettingsPluginClass parent;
 };
 
-G_DEFINE_TYPE (SettingsPluginIfcfg, settings_plugin_ifcfg, NM_TYPE_SETTINGS_PLUGIN)
+G_DEFINE_TYPE (NMSIfcfgRHPlugin, nms_ifcfg_rh_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
-#define SETTINGS_PLUGIN_IFCFG_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfcfg, SETTINGS_IS_PLUGIN_IFCFG)
-
-/*****************************************************************************/
-
-static SettingsPluginIfcfg *settings_plugin_ifcfg_get (void);
-
-NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfcfg, settings_plugin_ifcfg_get, SETTINGS_TYPE_PLUGIN_IFCFG);
+#define NMS_IFCFG_RH_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSIfcfgRHPlugin, NMS_IS_IFCFG_RH_PLUGIN, NMSettingsPlugin)
 
 /*****************************************************************************/
 
@@ -99,610 +95,811 @@ NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfcfg, settings_plugin_ifcfg_get, SETT
 
 /*****************************************************************************/
 
-static NMIfcfgConnection *update_connection (SettingsPluginIfcfg *plugin,
-                                             NMConnection *source,
-                                             const char *full_path,
-                                             NMIfcfgConnection *connection,
-                                             gboolean protect_existing_connection,
-                                             GHashTable *protected_connections,
-                                             GError **error);
+static void _unhandled_specs_reset (NMSIfcfgRHPlugin *self);
+
+static void _unhandled_specs_merge_storages (NMSIfcfgRHPlugin *self,
+                                             NMSettUtilStorages *storages);
 
 /*****************************************************************************/
 
 static void
-connection_ifcfg_changed (NMIfcfgConnection *connection, gpointer user_data)
+nm_assert_self (NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (user_data);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	const char *path;
+	nm_assert (NMS_IS_IFCFG_RH_PLUGIN (self));
 
-	path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
-	g_return_if_fail (path != NULL);
+#if NM_MORE_ASSERTS > 5
+	{
+		NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+		NMSIfcfgRHStorage *storage;
+		gsize n_uuid;
+		gs_unref_hashtable GHashTable *h_unmanaged = NULL;
+		gs_unref_hashtable GHashTable *h_unrecognized = NULL;
 
-	if (!priv->ifcfg_monitor) {
-		_LOGD ("connection_ifcfg_changed("NM_IFCFG_CONNECTION_LOG_FMTD"): %s", NM_IFCFG_CONNECTION_LOG_ARGD (connection), "ignore event");
-		return;
-	}
+		nm_assert (g_hash_table_size (priv->storages.idx_by_filename) == c_list_length (&priv->storages._storage_lst_head));
+
+		h_unmanaged = g_hash_table_new (nm_str_hash, g_str_equal);
+		h_unrecognized = g_hash_table_new (nm_str_hash, g_str_equal);
+
+		n_uuid = 0;
+
+		c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) {
+			const char *uuid;
+			const char *filename;
+
+			filename = nms_ifcfg_rh_storage_get_filename (storage);
+
+			nm_assert (filename && NM_STR_HAS_PREFIX (filename, IFCFG_DIR"/"));
+
+			uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
+
+			nm_assert ((!!uuid) + (!!storage->unmanaged_spec) + (!!storage->unrecognized_spec) == 1);
 
-	_LOGD ("connection_ifcfg_changed("NM_IFCFG_CONNECTION_LOG_FMTD"): %s", NM_IFCFG_CONNECTION_LOG_ARGD (connection), "reload");
+			nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, filename));
+
+			if (uuid) {
+				NMSettUtilStorageByUuidHead *sbuh;
+				NMSettUtilStorageByUuidHead *sbuh2;
+
+				if (storage->connection)
+					nm_assert (nm_streq0 (nm_connection_get_uuid (storage->connection), uuid));
+
+				if (!g_hash_table_lookup_extended (priv->storages.idx_by_uuid, &uuid, (gpointer *) &sbuh, (gpointer *) &sbuh2))
+					nm_assert_not_reached ();
+
+				nm_assert (sbuh);
+				nm_assert (nm_streq (uuid, sbuh->uuid));
+				nm_assert (sbuh == sbuh2);
+				nm_assert (c_list_contains (&sbuh->_storage_by_uuid_lst_head, &storage->parent._storage_by_uuid_lst));
+
+				if (c_list_first (&sbuh->_storage_by_uuid_lst_head) == &storage->parent._storage_by_uuid_lst)
+					n_uuid++;
+			} else if (storage->unmanaged_spec) {
+				nm_assert (strlen (storage->unmanaged_spec) > 0);
+				g_hash_table_add (h_unmanaged, storage->unmanaged_spec);
+			} else if (storage->unrecognized_spec) {
+				nm_assert (strlen (storage->unrecognized_spec) > 0);
+				g_hash_table_add (h_unrecognized, storage->unrecognized_spec);
+			} else
+				nm_assert_not_reached ();
+
+			nm_assert (!storage->connection);
+		}
 
-	update_connection (self, NULL, path, connection, TRUE, NULL, NULL);
+		nm_assert (g_hash_table_size (priv->storages.idx_by_uuid) == n_uuid);
+
+		if (unhandled_specs_consistent) {
+			nm_assert (nm_utils_hashtable_same_keys (h_unmanaged, priv->unmanaged_specs));
+			nm_assert (nm_utils_hashtable_same_keys (h_unrecognized, priv->unrecognized_specs));
+		}
+	}
+#endif
 }
 
-static void
-connection_removed_cb (NMSettingsConnection *obj, gpointer user_data)
+/*****************************************************************************/
+
+static NMSIfcfgRHStorage *
+_load_file (NMSIfcfgRHPlugin *self,
+            const char *filename,
+            GError **error)
 {
-	g_hash_table_remove (SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) user_data)->connections,
-	                     nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (obj)));
+	gs_unref_object NMConnection *connection = NULL;
+	gs_free_error GError *load_error = NULL;
+	gs_free char *unhandled_spec = NULL;
+	gboolean load_error_ignore;
+	struct stat st;
+
+	if (stat (filename, &st) != 0) {
+		int errsv = errno;
+
+		if (error) {
+			nm_utils_error_set_errno (error, errsv,
+			                          "failure to stat file \%s\": %s",
+			                          filename);
+		} else
+			_LOGT ("load[%s]: failure to stat file: %s", filename, nm_strerror_native (errsv));
+		return NULL;
+	}
+
+	connection = connection_from_file (filename,
+	                                   &unhandled_spec,
+	                                   &load_error,
+	                                   &load_error_ignore);
+	if (load_error) {
+		if (error) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "failure to read file \"%s\": %s",
+			                    filename, load_error->message);
+		} else {
+			_NMLOG (load_error_ignore ? LOGL_TRACE : LOGL_WARN,
+			        "load[%s]: failure to read file: %s", filename, load_error->message);
+		}
+		return NULL;
+	}
+
+	if (unhandled_spec) {
+		const char *unmanaged_spec;
+		const char *unrecognized_spec;
+
+		if (!nms_ifcfg_rh_util_parse_unhandled_spec (unhandled_spec,
+		                                             &unmanaged_spec,
+		                                             &unrecognized_spec)) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "invalid unhandled spec \"%s\"",
+			                    unhandled_spec);
+			nm_assert_not_reached ();
+			return NULL;
+		}
+		return nms_ifcfg_rh_storage_new_unhandled (self,
+		                                           filename,
+		                                           unmanaged_spec,
+		                                           unrecognized_spec);
+	}
+
+	return nms_ifcfg_rh_storage_new_connection (self,
+	                                            filename,
+	                                            g_steal_pointer (&connection),
+	                                            &st.st_mtim);
 }
 
 static void
-remove_connection (SettingsPluginIfcfg *self, NMIfcfgConnection *connection)
+_load_dir (NMSIfcfgRHPlugin *self,
+           NMSettUtilStorages *storages)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	gboolean unmanaged, unrecognized;
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+	gs_free_error GError *local = NULL;
+	const char *f_filename;
+	GDir *dir;
 
-	g_return_if_fail (self != NULL);
-	g_return_if_fail (connection != NULL);
+	dir = g_dir_open (IFCFG_DIR, 0, &local);
+	if (!dir) {
+		_LOGT ("Could not read directory '%s': %s", IFCFG_DIR, local->message);
+		return;
+	}
 
-	_LOGI ("remove "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection));
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
 
-	unmanaged = !!nm_ifcfg_connection_get_unmanaged_spec (connection);
-	unrecognized = !!nm_ifcfg_connection_get_unrecognized_spec (connection);
+	while ((f_filename = g_dir_read_name (dir))) {
+		gs_free char *full_path = NULL;
+		NMSIfcfgRHStorage *storage;
+		char *full_filename;
 
-	g_object_ref (connection);
-	g_hash_table_remove (priv->connections, nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
-	if (!unmanaged && !unrecognized)
-		nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
-	g_object_unref (connection);
+		full_path = g_build_filename (IFCFG_DIR, f_filename, NULL);
+		full_filename = utils_detect_ifcfg_path (full_path, TRUE);
+		if (!full_filename)
+			continue;
 
-	/* Emit changes _after_ removing the connection */
-	if (unmanaged)
-		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-	if (unrecognized)
-		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
+		if (!g_hash_table_add (dupl_filenames, full_filename))
+			continue;
+
+		nm_assert (!nm_sett_util_storages_lookup_by_filename (storages, full_filename));
+
+		storage = _load_file (self,
+		                      full_filename,
+		                      NULL);
+		if (storage)
+			nm_sett_util_storages_add_take (storages, storage);
+	}
+	g_dir_close (dir);
 }
 
-static NMIfcfgConnection *
-find_by_path (SettingsPluginIfcfg *self, const char *path)
+static void
+_storages_consolidate (NMSIfcfgRHPlugin *self,
+                       NMSettUtilStorages *storages_new,
+                       gboolean replace_all,
+                       GHashTable *storages_replaced,
+                       NMSettingsPluginConnectionLoadCallback callback,
+                       gpointer user_data)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	GHashTableIter iter;
-	NMSettingsConnection *candidate = NULL;
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	CList lst_conn_info_deleted = C_LIST_INIT (lst_conn_info_deleted);
+	gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+	CList storages_deleted;
+	NMSIfcfgRHStorage *storage_safe;
+	NMSIfcfgRHStorage *storage_new;
+	NMSIfcfgRHStorage *storage_old;
+	NMSIfcfgRHStorage *storage;
+	guint i;
 
-	g_return_val_if_fail (path != NULL, NULL);
+	/* when we reload all files, we must signal add/update/modify of profiles one-by-one.
+	 * NMSettings then goes ahead and emits further signals and a lot of things happen.
+	 *
+	 * So, first, emit an update of the unmanaged/unrecognized specs that contains *all*
+	 * the unmanaged/unrecognized devices from before and after. Since both unmanaged/unrecognized
+	 * specs have the meaning of "not doing something", it makes sense that we temporarily
+	 * disable that action for the sum of before and after. */
+	_unhandled_specs_merge_storages (self, storages_new);
+
+	storages_modified = g_ptr_array_new_with_free_func (g_object_unref);
+	c_list_init (&storages_deleted);
+
+	c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst)
+		storage_old->dirty = TRUE;
+
+	c_list_for_each_entry_safe (storage_new, storage_safe, &storages_new->_storage_lst_head, parent._storage_lst) {
+		storage_old = nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_ifcfg_rh_storage_get_filename (storage_new));
+
+		nm_sett_util_storages_steal (storages_new, storage_new);
+
+		if (   !storage_old
+		    || !nms_ifcfg_rh_storage_equal_type (storage_new, storage_old)) {
+			if (storage_old) {
+				nm_sett_util_storages_steal (&priv->storages, storage_old);
+				if (nms_ifcfg_rh_storage_get_uuid_opt (storage_old))
+					c_list_link_tail (&storages_deleted, &storage_old->parent._storage_lst);
+				else
+					nms_ifcfg_rh_storage_destroy (storage_old);
+			}
+			storage_new->dirty = FALSE;
+			nm_sett_util_storages_add_take (&priv->storages, storage_new);
+			g_ptr_array_add (storages_modified, g_object_ref (storage_new));
+			continue;
+		}
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
-		if (g_strcmp0 (path, nm_settings_connection_get_filename (candidate)) == 0)
-			return NM_IFCFG_CONNECTION (candidate);
+		storage_old->dirty = FALSE;
+		nms_ifcfg_rh_storage_copy_content (storage_old, storage_new);
+		nms_ifcfg_rh_storage_destroy (storage_new);
+		g_ptr_array_add (storages_modified, g_object_ref (storage_old));
 	}
-	return NULL;
-}
 
-static NMIfcfgConnection *
-update_connection (SettingsPluginIfcfg *self,
-                   NMConnection *source,
-                   const char *full_path,
-                   NMIfcfgConnection *connection,
-                   gboolean protect_existing_connection,
-                   GHashTable *protected_connections,
-                   GError **error)
-{
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
-	NMIfcfgConnection *connection_new;
-	NMIfcfgConnection *connection_by_uuid;
-	GError *local = NULL;
-	const char *new_unmanaged = NULL, *old_unmanaged = NULL;
-	const char *new_unrecognized = NULL, *old_unrecognized = NULL;
-	gboolean unmanaged_changed = FALSE, unrecognized_changed = FALSE;
-	const char *uuid;
-	gboolean ignore_error = FALSE;
-
-	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
-	g_return_val_if_fail (full_path || source, NULL);
-
-	if (full_path)
-		_LOGD ("loading from file \"%s\"...", full_path);
-
-	/* Create a NMIfcfgConnection instance, either by reading from @full_path or
-	 * based on @source. */
-	connection_new = nm_ifcfg_connection_new (source, full_path, &local, &ignore_error);
-	if (!connection_new) {
-		/* Unexpected failure. Probably the file is invalid? */
-		if (   connection
-		    && !protect_existing_connection
-		    && (!protected_connections || !g_hash_table_contains (protected_connections, connection)))
-			remove_connection (self, connection);
-		if (!source) {
-			_NMLOG (ignore_error ? LOGL_DEBUG : LOGL_WARN,
-			        "loading \"%s\" fails: %s", full_path, local ? local->message : "(unknown reason)");
+	c_list_for_each_entry_safe (storage_old, storage_safe, &priv->storages._storage_lst_head, parent._storage_lst) {
+		if (!storage_old->dirty)
+			continue;
+		if (   replace_all
+		    || (   storages_replaced
+		        && g_hash_table_contains (storages_replaced, storage_old))) {
+			nm_sett_util_storages_steal (&priv->storages, storage_old);
+			if (nms_ifcfg_rh_storage_get_uuid_opt (storage_old))
+				c_list_link_tail (&storages_deleted, &storage_old->parent._storage_lst);
+			else
+				nms_ifcfg_rh_storage_destroy (storage_old);
 		}
-		g_propagate_error (error, local);
-		return NULL;
 	}
 
-	uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
-	connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);
+	/* raise events. */
 
-	if (   connection
-	    && connection != connection_by_uuid) {
+	for (i = 0; i < storages_modified->len; i++) {
+		storage = storages_modified->pdata[i];
+		storage->dirty = TRUE;
+	}
 
-		if (   (protect_existing_connection && connection_by_uuid != NULL)
-		    || (protected_connections && g_hash_table_contains (protected_connections, connection))) {
-			NMIfcfgConnection *conflicting = (protect_existing_connection && connection_by_uuid != NULL) ? connection_by_uuid : connection;
+	for (i = 0; i < storages_modified->len; i++) {
+		gs_unref_object NMConnection *connection = NULL;
+		storage = storages_modified->pdata[i];
 
-			if (source)
-				_LOGW ("cannot update protected connection "NM_IFCFG_CONNECTION_LOG_FMT" due to conflicting UUID %s", NM_IFCFG_CONNECTION_LOG_ARG (conflicting), uuid);
-			else
-				_LOGW ("cannot load %s due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, full_path, NM_IFCFG_CONNECTION_LOG_ARG (conflicting));
-			g_object_unref (connection_new);
-			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			                     "Cannot update protected connection due to conflicting UUID");
-			return NULL;
+		if (!storage->dirty) {
+			/* the entry is no longer dirty. In the meantime we already emited
+			 * another signal for it. */
+			continue;
+		}
+		storage->dirty = FALSE;
+		if (storage != nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_ifcfg_rh_storage_get_filename (storage))) {
+			/* hm? The profile was deleted in the meantime? That is only possible
+			 * if the signal handler called again into the plugin. In any case, the event
+			 * was already emitted. Skip. */
+			continue;
 		}
 
-		/* The new connection has a different UUID then the original one that we
-		 * are about to update. Remove @connection. */
-		remove_connection (self, connection);
+		connection = nms_ifcfg_rh_storage_steal_connection (storage);
+		if (!connection) {
+			nm_assert (!nms_ifcfg_rh_storage_get_uuid_opt (storage));
+			continue;
+		}
+
+		nm_assert (NM_IS_CONNECTION (connection));
+		nm_assert (nms_ifcfg_rh_storage_get_uuid_opt (storage));
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          connection,
+		          user_data);
 	}
 
-	/* Check if the found connection with the same UUID is not protected from updating. */
-	if (   connection_by_uuid
-	    && (   (!connection && protect_existing_connection)
-	        || (protected_connections && g_hash_table_contains (protected_connections, connection_by_uuid)))) {
-		if (source)
-			_LOGW ("cannot update connection due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_by_uuid));
-		else
-			_LOGW ("cannot load %s due to conflicting UUID for "NM_IFCFG_CONNECTION_LOG_FMT, full_path, NM_IFCFG_CONNECTION_LOG_ARG (connection_by_uuid));
-		g_object_unref (connection_new);
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		                      "Skip updating protected connection during reload");
-		return NULL;
+	while ((storage = c_list_first_entry (&storages_deleted, NMSIfcfgRHStorage, parent._storage_lst))) {
+		c_list_unlink (&storage->parent._storage_lst);
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          NULL,
+		          user_data);
+		nms_ifcfg_rh_storage_destroy (storage);
 	}
+}
 
-	/* Evaluate unmanaged/unrecognized flags. */
-	if (connection_by_uuid)
-		old_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (connection_by_uuid);
-	new_unmanaged = nm_ifcfg_connection_get_unmanaged_spec (connection_new);
-	unmanaged_changed = g_strcmp0 (old_unmanaged, new_unmanaged);
-
-	if (connection_by_uuid)
-		old_unrecognized = nm_ifcfg_connection_get_unrecognized_spec (connection_by_uuid);
-	new_unrecognized = nm_ifcfg_connection_get_unrecognized_spec (connection_new);
-	unrecognized_changed = g_strcmp0 (old_unrecognized, new_unrecognized);
-
-	if (connection_by_uuid) {
-		const char *old_path;
-
-		old_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid));
-
-		if (   !unmanaged_changed
-		    && !unrecognized_changed
-		    && nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
-		                              nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-		                              NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
-		                              NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
-			if (   old_path
-			    && !nm_streq0 (old_path, full_path)) {
-				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT" without other changes",
-				       nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid)),
-				       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-			}
-		} else {
+/*****************************************************************************/
+
+static void
+load_connections (NMSettingsPlugin *plugin,
+                  NMSettingsPluginConnectionLoadEntry *entries,
+                  gsize n_entries,
+                  NMSettingsPluginConnectionLoadCallback callback,
+                  gpointer user_data)
+{
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_ifcfg_rh_storage_destroy);
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+	gs_unref_hashtable GHashTable *storages_replaced = NULL;
+	gs_unref_hashtable GHashTable *loaded_uuids = NULL;
+	const char *loaded_uuid;
+	GHashTableIter h_iter;
+	gsize i;
+
+	if (n_entries == 0)
+		return;
 
-			/*******************************************************
-			 * UPDATE
-			 *******************************************************/
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
-			if (source)
-				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT" from %s", NM_IFCFG_CONNECTION_LOG_ARG (connection_new), NM_IFCFG_CONNECTION_LOG_PATH (old_path));
-			else if (nm_streq0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
-				_LOGI ("update "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-			else if (old_path)
-				_LOGI ("rename \"%s\" to "NM_IFCFG_CONNECTION_LOG_FMT, old_path, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-			else
-				_LOGI ("update and persist "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-
-			g_object_set (connection_by_uuid,
-			              NM_IFCFG_CONNECTION_UNMANAGED_SPEC, new_unmanaged,
-			              NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC, new_unrecognized,
-			              NULL);
-
-			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
-			                                    nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-			                                    "ifcfg-update",
-			                                    &local)) {
-				/* Shouldn't ever get here as 'connection_new' was verified by the reader already
-				 * and the UUID did not change. */
-				g_assert_not_reached ();
-			}
-			g_assert_no_error (local);
-
-			if (new_unmanaged || new_unrecognized) {
-				if (!old_unmanaged && !old_unrecognized) {
-					/* ref connection first, because we put it into priv->connections below.
-					 * Emitting signal-removed might otherwise delete it. */
-					g_object_ref (connection_by_uuid);
-
-					/* Unexport the connection by telling the settings service it's
-					 * been removed.
-					 */
-					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection_by_uuid));
-
-					/* signal_remove() will end up removing the connection from our hash,
-					 * so add it back now.
-					 */
-					g_hash_table_insert (priv->connections,
-					                     g_strdup (nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_by_uuid))),
-					                     connection_by_uuid /* we took reference above and pass it on */);
-				}
-			} else {
-				if (old_unmanaged /* && !new_unmanaged */) {
-					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" and its device because NM_CONTROLLED was true.",
-					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
-				} else if (old_unrecognized /* && !new_unrecognized */) {
-					_LOGI ("Managing connection "NM_IFCFG_CONNECTION_LOG_FMT" because it is now a recognized type.",
-					       NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-					_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-					                                                  NM_SETTINGS_CONNECTION (connection_by_uuid));
-				}
-			}
+	loaded_uuids = g_hash_table_new (nm_str_hash, g_str_equal);
+
+	storages_replaced = g_hash_table_new_full (nm_direct_hash, NULL, g_object_unref, NULL);
+
+	for (i = 0; i < n_entries; i++) {
+		NMSettingsPluginConnectionLoadEntry *const entry = &entries[i];
+		gs_free_error GError *local = NULL;
+		const char *full_filename;
+		const char *uuid;
+		gs_free char *full_filename_keep = NULL;
+		NMSettingsPluginConnectionLoadEntry *dupl_content_entry;
+		gs_unref_object NMSIfcfgRHStorage *storage = NULL;
+
+		if (entry->handled)
+			continue;
 
-			if (unmanaged_changed)
-				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-			if (unrecognized_changed)
-				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
+		if (entry->filename[0] != '/')
+			continue;
+
+		full_filename_keep = utils_detect_ifcfg_path (entry->filename, FALSE);
+
+		if (!full_filename_keep) {
+			if (nm_utils_file_is_in_path (entry->filename, IFCFG_DIR)) {
+				nm_utils_error_set (&entry->error,
+				                    NM_UTILS_ERROR_UNKNOWN,
+				                    ("path is not a valid name for an ifcfg-rh file"));
+				entry->handled = TRUE;
+			}
+			continue;
 		}
-		nm_settings_connection_set_filename (NM_SETTINGS_CONNECTION (connection_by_uuid), full_path);
-		g_object_unref (connection_new);
-		return connection_by_uuid;
-	} else {
 
-		/*******************************************************
-		 * ADD
-		 *******************************************************/
+		if ((dupl_content_entry = g_hash_table_lookup (dupl_filenames, full_filename_keep))) {
+			/* we already visited this file. */
+			entry->handled = dupl_content_entry->handled;
+			if (dupl_content_entry->error) {
+				g_set_error_literal (&entry->error,
+				                     dupl_content_entry->error->domain,
+				                     dupl_content_entry->error->code,
+				                     dupl_content_entry->error->message);
+			}
+			continue;
+		}
 
-		if (source)
-			_LOGI ("add connection "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-		else
-			_LOGI ("new connection "NM_IFCFG_CONNECTION_LOG_FMT, NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-		g_hash_table_insert (priv->connections,
-		                     g_strdup (uuid),
-		                     connection_new /* take reference */);
-
-		g_signal_connect (connection_new, NM_SETTINGS_CONNECTION_REMOVED,
-		                  G_CALLBACK (connection_removed_cb),
-		                  self);
-
-		if (nm_ifcfg_connection_get_unmanaged_spec (connection_new)) {
-			_LOGI ("Ignoring connection "NM_IFCFG_CONNECTION_LOG_FMT" due to NM_CONTROLLED=no. Unmanaged: %s.",
-			       NM_IFCFG_CONNECTION_LOG_ARG (connection_new),
-			       nm_ifcfg_connection_get_unmanaged_spec (connection_new));
-		} else if (nm_ifcfg_connection_get_unrecognized_spec (connection_new))
-			_LOGW ("Ignoring connection "NM_IFCFG_CONNECTION_LOG_FMT" of unrecognized type.", NM_IFCFG_CONNECTION_LOG_ARG (connection_new));
-
-		/* watch changes of ifcfg hardlinks */
-		g_signal_connect (G_OBJECT (connection_new), "ifcfg-changed",
-		                  G_CALLBACK (connection_ifcfg_changed), self);
-
-		if (!source) {
-			/* Only raise the signal if we were called without source, i.e. if we read the connection from file.
-			 * Otherwise, we were called by add_connection() which does not expect the signal. */
-			if (nm_ifcfg_connection_get_unmanaged_spec (connection_new))
-				_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-			else if (nm_ifcfg_connection_get_unrecognized_spec (connection_new))
-				_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
-			else {
-				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-				                                                  NM_SETTINGS_CONNECTION (connection_new));
+		entry->handled = TRUE;
+
+		full_filename = full_filename_keep;
+		if (!g_hash_table_insert (dupl_filenames, g_steal_pointer (&full_filename_keep), entry))
+			nm_assert_not_reached ();
+
+		storage = _load_file (self,
+		                      full_filename,
+		                      &local);
+		if (!storage) {
+			if (nm_utils_file_stat (full_filename, NULL) == -ENOENT) {
+				NMSIfcfgRHStorage *storage2;
+
+				/* the file does not exist. We take that as indication to unload the file
+				 * that was previously loaded... */
+				storage2 = nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename);
+				if (storage2)
+					g_hash_table_add (storages_replaced, g_object_ref (storage2));
+				continue;
 			}
+			g_propagate_error (&entry->error, g_steal_pointer (&local));
+			continue;
 		}
-		return connection_new;
+
+		uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
+		if (uuid)
+			g_hash_table_add (loaded_uuids, (char *) uuid);
+
+		nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage));
 	}
-}
 
-static void
-ifcfg_dir_changed (GFileMonitor *monitor,
-                   GFile *file,
-                   GFile *other_file,
-                   GFileMonitorEvent event_type,
-                   gpointer user_data)
-{
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (user_data);
-	char *path, *ifcfg_path;
-	NMIfcfgConnection *connection;
-
-	path = g_file_get_path (file);
-
-	ifcfg_path = utils_detect_ifcfg_path (path, FALSE);
-	_LOGD ("ifcfg_dir_changed(%s) = %d // %s", path, event_type, ifcfg_path ?: "(none)");
-	if (ifcfg_path) {
-		connection = find_by_path (plugin, ifcfg_path);
-		switch (event_type) {
-		case G_FILE_MONITOR_EVENT_DELETED:
-			if (connection)
-				remove_connection (plugin, connection);
-			break;
-		case G_FILE_MONITOR_EVENT_CREATED:
-		case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
-			/* Update or new */
-			update_connection (plugin, NULL, ifcfg_path, connection, TRUE, NULL, NULL);
-			break;
-		default:
-			break;
+	/* now we visit all UUIDs that are about to change... */
+	g_hash_table_iter_init (&h_iter, loaded_uuids);
+	while (g_hash_table_iter_next (&h_iter, (gpointer *) &loaded_uuid, NULL)) {
+		NMSIfcfgRHStorage *storage;
+		NMSettUtilStorageByUuidHead *sbuh;
+
+		sbuh = nm_sett_util_storages_lookup_by_uuid (&priv->storages, loaded_uuid);
+		if (!sbuh)
+			continue;
+
+		c_list_for_each_entry (storage, &sbuh->_storage_by_uuid_lst_head, parent._storage_by_uuid_lst) {
+			const char *full_filename = nms_ifcfg_rh_storage_get_filename (storage);
+			gs_unref_object NMSIfcfgRHStorage *storage_new = NULL;
+			gs_free_error GError *local = NULL;
+
+			if (g_hash_table_contains (dupl_filenames, full_filename)) {
+				/* already re-loaded. */
+				continue;
+			}
+
+			/* @storage has a UUID that was just loaded from disk, but we have an entry in cache.
+			 * Reload that file too despite not being told to do so. The reason is to get
+			 * the latest file timestamp so that we get the priorities right. */
+
+			storage_new = _load_file (self,
+			                          full_filename,
+			                          &local);
+			if (   storage_new
+			    && !nm_streq0 (loaded_uuid, nms_ifcfg_rh_storage_get_uuid_opt (storage_new))) {
+				/* the file now references a different UUID. We are not told to reload
+				 * that file, so this means the existing storage (with the previous
+				 * filename and UUID tuple) is no longer valid. */
+				g_clear_object (&storage_new);
+			}
+
+			g_hash_table_add (storages_replaced, g_object_ref (storage));
+			if (storage_new)
+				nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage_new));
 		}
-		g_free (ifcfg_path);
 	}
-	g_free (path);
+
+	nm_clear_pointer (&loaded_uuids, g_hash_table_destroy);
+	nm_clear_pointer (&dupl_filenames, g_hash_table_destroy);
+
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       FALSE,
+	                       storages_replaced,
+	                       callback,
+	                       user_data);
 }
 
 static void
-setup_ifcfg_monitoring (SettingsPluginIfcfg *plugin)
+reload_connections (NMSettingsPlugin *plugin,
+                    NMSettingsPluginConnectionLoadCallback callback,
+                    gpointer user_data)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
-	GFile *file;
-	GFileMonitor *monitor;
-
-	file = g_file_new_for_path (IFCFG_DIR "/");
-	monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
-	g_object_unref (file);
-
-	if (monitor) {
-		priv->ifcfg_monitor_id = g_signal_connect (monitor, "changed",
-		                                           G_CALLBACK (ifcfg_dir_changed), plugin);
-		priv->ifcfg_monitor = monitor;
-	}
-}
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_ifcfg_rh_storage_destroy);
 
-static GHashTable *
-_paths_from_connections (GHashTable *connections)
-{
-	GHashTableIter iter;
-	NMIfcfgConnection *connection;
-	GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
+	nm_assert_self (self, TRUE);
 
-	g_hash_table_iter_init (&iter, connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		const char *path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
+	_load_dir (self, &storages_new);
 
-		if (path)
-			g_hash_table_add (paths, (void *) path);
-	}
-	return paths;
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       TRUE,
+	                       NULL,
+	                       callback,
+	                       user_data);
+
+	nm_assert_self (self, FALSE);
 }
 
-static int
-_sort_paths (const char **f1, const char **f2, GHashTable *paths)
+static void
+load_connections_done (NMSettingsPlugin *plugin)
 {
-	struct stat st;
-	gboolean c1, c2;
-	gint64 m1, m2;
-
-	c1 = !!g_hash_table_contains (paths, *f1);
-	c2 = !!g_hash_table_contains (paths, *f2);
-	if (c1 != c2)
-		return c1 ? -1 : 1;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
 
-	m1 = stat (*f1, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	m2 = stat (*f2, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	if (m1 != m2)
-		return m1 > m2 ? -1 : 1;
+	/* at the beginning of a load, we emit a change signal for unmanaged/unrecognized
+	 * specs that contain the sum of before and after (_unhandled_specs_merge_storages()).
+	 *
+	 * The idea is that while we emit signals about changes to connection, we have
+	 * the sum of all unmanaged/unrecognized devices from before and after.
+	 *
+	 * This if triggered at the end, to reset the specs. */
+	_unhandled_specs_reset (self);
 
-	return strcmp (*f1, *f2);
+	nm_assert_self (self, TRUE);
 }
 
-static void
-read_connections (SettingsPluginIfcfg *plugin)
-{
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
-	GDir *dir;
-	GError *err = NULL;
-	const char *item;
-	GHashTable *alive_connections;
-	GHashTableIter iter;
-	NMIfcfgConnection *connection;
-	GPtrArray *dead_connections = NULL;
-	guint i;
-	GPtrArray *filenames;
-	GHashTable *paths;
+/*****************************************************************************/
 
-	dir = g_dir_open (IFCFG_DIR, 0, &err);
-	if (!dir) {
-		_LOGW ("Could not read directory '%s': %s", IFCFG_DIR, err->message);
-		g_error_free (err);
-		return;
+static gboolean
+add_connection (NMSettingsPlugin *plugin,
+                NMConnection *connection,
+                NMSettingsStorage **out_storage,
+                NMConnection **out_connection,
+                GError **error)
+{
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gs_unref_object NMSIfcfgRHStorage *storage = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	gs_free char *full_filename = NULL;
+	GError *local = NULL;
+	gboolean reread_same;
+	struct timespec mtime;
+
+	nm_assert_self (self, TRUE);
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (out_storage && !*out_storage);
+	nm_assert (out_connection && !*out_connection);
+
+	if (!nms_ifcfg_rh_writer_write_connection (connection,
+	                                           IFCFG_DIR,
+	                                           NULL,
+	                                           nm_sett_util_allow_filename_cb,
+	                                           NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, NULL),
+	                                           &full_filename,
+	                                           &reread,
+	                                           &reread_same,
+	                                           &local)) {
+		_LOGT ("commit: %s (%s): failed to add: %s",
+		       nm_connection_get_uuid (connection),
+		       nm_connection_get_id (connection),
+		       local->message);
+		g_propagate_error (error, local);
+		return FALSE;
 	}
 
-	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	filenames = g_ptr_array_new_with_free_func (g_free);
-	while ((item = g_dir_read_name (dir))) {
-		char *full_path, *real_path;
+	nm_assert (full_filename && full_filename[0] == '/');
 
-		full_path = g_build_filename (IFCFG_DIR, item, NULL);
-		real_path = utils_detect_ifcfg_path (full_path, TRUE);
+	_LOGT ("commit: %s (%s) added as \"%s\"",
+	       nm_connection_get_uuid (reread),
+	       nm_connection_get_id (reread),
+	       full_filename);
 
-		if (real_path)
-			g_ptr_array_add (filenames, real_path);
-		g_free (full_path);
-	}
-	g_dir_close (dir);
+	storage = nms_ifcfg_rh_storage_new_connection (self,
+	                                               full_filename,
+	                                               g_steal_pointer (&reread),
+	                                               nm_sett_util_stat_mtime (full_filename, FALSE, &mtime));
 
-	/* While reloading, we don't replace connections that we already loaded while
-	 * iterating over the files.
-	 *
-	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time preferring older files.
-	 */
-	paths = _paths_from_connections (priv->connections);
-	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
-	g_hash_table_destroy (paths);
-
-	for (i = 0; i < filenames->len; i++) {
-		connection = update_connection (plugin, NULL, filenames->pdata[i], NULL, FALSE, alive_connections, NULL);
-		if (connection)
-			g_hash_table_add (alive_connections, connection);
-	}
-	g_ptr_array_free (filenames, TRUE);
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		if (   !g_hash_table_contains (alive_connections, connection)
-		    && nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection))) {
-			if (!dead_connections)
-				dead_connections = g_ptr_array_new ();
-			g_ptr_array_add (dead_connections, connection);
-		}
-	}
-	g_hash_table_destroy (alive_connections);
+	nm_sett_util_storages_add_take (&priv->storages, g_object_ref (storage));
 
-	if (dead_connections) {
-		for (i = 0; i < dead_connections->len; i++)
-			remove_connection (plugin, dead_connections->pdata[i]);
-		g_ptr_array_free (dead_connections, TRUE);
-	}
+	*out_connection = nms_ifcfg_rh_storage_steal_connection (storage);
+	*out_storage = NM_SETTINGS_STORAGE (g_steal_pointer (&storage));
+
+	nm_assert_self (self, TRUE);
+
+	return TRUE;
 }
 
-static GSList *
-get_connections (NMSettingsPlugin *config)
+static gboolean
+update_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage_x,
+                   NMConnection *connection,
+                   NMSettingsStorage **out_storage,
+                   NMConnection **out_connection,
+                   GError **error)
 {
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (plugin);
-	GSList *list = NULL;
-	GHashTableIter iter;
-	NMIfcfgConnection *connection;
-
-	if (!priv->initialized) {
-		if (nm_config_get_monitor_connection_files (nm_config_get ()))
-			setup_ifcfg_monitoring (plugin);
-		read_connections (plugin);
-		priv->initialized = TRUE;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	NMSIfcfgRHStorage *storage = NMS_IFCFG_RH_STORAGE (storage_x);
+	const char *full_filename;
+	const char *uuid;
+	GError *local = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	gboolean reread_same;
+	struct timespec mtime;
+
+	nm_assert_self (self, TRUE);
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (storage));
+	nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (!error || !*error);
+
+	uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
+
+	nm_assert (uuid && nm_streq0 (uuid, nm_connection_get_uuid (connection)));
+
+	full_filename = nms_ifcfg_rh_storage_get_filename (storage);
+
+	nm_assert (full_filename);
+	nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename));
+
+	if (!nms_ifcfg_rh_writer_write_connection (connection,
+	                                           IFCFG_DIR,
+	                                           full_filename,
+	                                           nm_sett_util_allow_filename_cb,
+	                                           NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, full_filename),
+	                                           NULL,
+	                                           &reread,
+	                                           &reread_same,
+	                                           &local)) {
+		_LOGT ("commit: failure to write %s (%s) to \"%s\": %s",
+		       nm_connection_get_uuid (connection),
+		       nm_connection_get_id (connection),
+		       full_filename,
+		       local->message);
+		g_propagate_error (error, local);
+		return FALSE;
 	}
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &connection)) {
-		if (   !nm_ifcfg_connection_get_unmanaged_spec (connection)
-		    && !nm_ifcfg_connection_get_unrecognized_spec (connection))
-			list = g_slist_prepend (list, connection);
-	}
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	return list;
+	_LOGT ("commit: \"%s\": profile %s (%s) written",
+	       full_filename,
+	       uuid,
+	       nm_connection_get_id (connection));
+
+	storage->stat_mtime = *nm_sett_util_stat_mtime (full_filename, FALSE, &mtime);
+
+	*out_storage = NM_SETTINGS_STORAGE (g_object_ref (storage));
+	*out_connection = g_steal_pointer (&reread);
+
+	nm_assert_self (self, TRUE);
+
+	return TRUE;
 }
 
 static gboolean
-load_connection (NMSettingsPlugin *config,
-                 const char *filename)
+delete_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage_x,
+                   GError **error)
 {
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
-	NMIfcfgConnection *connection;
-	char *ifcfg_path;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	NMSIfcfgRHStorage *storage = NMS_IFCFG_RH_STORAGE (storage_x);
+	const char *operation_message;
+	const char *full_filename;
 
-	if (!nm_utils_file_is_in_path (filename, IFCFG_DIR))
-		return FALSE;
+	nm_assert_self (self, TRUE);
+	nm_assert (!error || !*error);
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (storage));
 
-	/* get the real ifcfg-path. This allows us to properly
-	 * handle load command using a route-* file etc. */
-	ifcfg_path = utils_detect_ifcfg_path (filename, FALSE);
-	if (!ifcfg_path)
-		return FALSE;
+	full_filename = nms_ifcfg_rh_storage_get_filename (storage);
+	nm_assert (full_filename);
+
+	nm_assert (nms_ifcfg_rh_storage_get_uuid_opt (storage));
+
+	nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename));
+
+	{
+		gs_free char *keyfile = utils_get_keys_path (full_filename);
+		gs_free char *routefile = utils_get_route_path (full_filename);
+		gs_free char *route6file = utils_get_route6_path (full_filename);
+		const char *const files[] = { full_filename, keyfile, routefile, route6file };
+		gboolean any_deleted = FALSE;
+		gboolean any_failure = FALSE;
+		int i;
+
+		for (i = 0; i < G_N_ELEMENTS (files); i++) {
+			int errsv;
+
+			if (unlink (files[i]) == 0) {
+				any_deleted = TRUE;
+				continue;
+			}
+			errsv = errno;
+			if (errsv == ENOENT)
+				continue;
+
+			_LOGW ("commit: failure to delete file \"%s\": %s",
+			       files[i],
+			       nm_strerror_native (errsv));
+			any_failure = TRUE;
+		}
+		if (any_failure)
+			operation_message = "failed to delete files from disk";
+		else if (any_deleted)
+			operation_message = "deleted from disk";
+		else
+			operation_message = "does not exist on disk";
+	}
+
+	_LOGT ("commit: deleted \"%s\", profile %s (%s)",
+	       full_filename,
+	       nms_ifcfg_rh_storage_get_uuid_opt (storage),
+	       operation_message);
 
-	connection = find_by_path (plugin, ifcfg_path);
-	update_connection (plugin, NULL, ifcfg_path, connection, TRUE, NULL, NULL);
-	if (!connection)
-		connection = find_by_path (plugin, ifcfg_path);
+	nm_sett_util_storages_steal (&priv->storages, storage);
+	nms_ifcfg_rh_storage_destroy (storage);
 
-	g_free (ifcfg_path);
-	return (connection != NULL);
+	nm_assert_self (self, TRUE);
+
+	return TRUE;
 }
 
+/*****************************************************************************/
+
 static void
-reload_connections (NMSettingsPlugin *config)
+_unhandled_specs_reset (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfg *plugin = SETTINGS_PLUGIN_IFCFG (config);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *unmanaged_specs = NULL;
+	gs_unref_hashtable GHashTable *unrecognized_specs = NULL;
+	NMSIfcfgRHStorage *storage;
+
+	unmanaged_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+	unrecognized_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+
+	c_list_for_each_entry (storage, &priv->storages._storage_lst_head, parent._storage_lst) {
+		if (storage->unmanaged_spec)
+			g_hash_table_add (unmanaged_specs, g_strdup (storage->unmanaged_spec));
+		if (storage->unrecognized_spec)
+			g_hash_table_add (unrecognized_specs, g_strdup (storage->unrecognized_spec));
+	}
 
-	read_connections (plugin);
+	if (!nm_utils_hashtable_same_keys (unmanaged_specs, priv->unmanaged_specs)) {
+		g_hash_table_unref (priv->unmanaged_specs);
+		priv->unmanaged_specs = g_steal_pointer (&unmanaged_specs);
+	}
+	if (!nm_utils_hashtable_same_keys (unrecognized_specs, priv->unrecognized_specs)) {
+		g_hash_table_unref (priv->unrecognized_specs);
+		priv->unrecognized_specs = g_steal_pointer (&unrecognized_specs);
+	}
+
+	if (!unmanaged_specs)
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
+	if (!unrecognized_specs)
+		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
-static GSList *
-get_unhandled_specs (NMSettingsPlugin *config,
-                     const char *property)
+static void
+_unhandled_specs_merge_storages (NMSIfcfgRHPlugin *self,
+                                 NMSettUtilStorages *storages)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) config);
-	GSList *list = NULL, *list_iter;
-	GHashTableIter iter;
-	gpointer connection;
-	char *spec;
-	gboolean found;
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, &connection)) {
-		g_object_get (connection, property, &spec, NULL);
-		if (spec) {
-			/* Ignore duplicates */
-			for (list_iter = list, found = FALSE; list_iter; list_iter = g_slist_next (list_iter)) {
-				if (g_str_equal (list_iter->data, spec)) {
-					found = TRUE;
-					break;
-				}
-			}
-			if (found)
-				g_free (spec);
-			else
-				list = g_slist_prepend (list, spec);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gboolean unmanaged_changed = FALSE;
+	gboolean unrecognized_changed = FALSE;
+	NMSIfcfgRHStorage *storage;
+
+	c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst) {
+		if (   storage->unmanaged_spec
+		    && !g_hash_table_contains (priv->unmanaged_specs, storage->unmanaged_spec)) {
+			unmanaged_changed = TRUE;
+			g_hash_table_add (priv->unmanaged_specs, g_strdup (storage->unmanaged_spec));
+		}
+		if (   storage->unrecognized_spec
+		    && !g_hash_table_contains (priv->unrecognized_specs, storage->unrecognized_spec)) {
+			unrecognized_changed = TRUE;
+			g_hash_table_add (priv->unrecognized_specs, g_strdup (storage->unrecognized_spec));
 		}
 	}
-	return list;
+
+	if (unmanaged_changed)
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
+	if (unrecognized_changed)
+		_nm_settings_plugin_emit_signal_unrecognized_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
 static GSList *
-get_unmanaged_specs (NMSettingsPlugin *config)
+_unhandled_specs_from_hashtable (GHashTable *hash)
 {
-	return get_unhandled_specs (config, NM_IFCFG_CONNECTION_UNMANAGED_SPEC);
+	gs_free const char **keys = NULL;
+	GSList *list = NULL;
+	guint i, l;
+
+	keys = nm_utils_strdict_get_keys (hash, TRUE, &l);
+	for (i = l; i > 0; ) {
+		i--;
+		list = g_slist_prepend (list, g_strdup (keys[i]));
+	}
+	return list;
 }
 
 static GSList *
-get_unrecognized_specs (NMSettingsPlugin *config)
+get_unmanaged_specs (NMSettingsPlugin *plugin)
 {
-	return get_unhandled_specs (config, NM_IFCFG_CONNECTION_UNRECOGNIZED_SPEC);
+	return _unhandled_specs_from_hashtable (NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (plugin)->unmanaged_specs);
 }
 
-static NMSettingsConnection *
-add_connection (NMSettingsPlugin *config,
-                NMConnection *connection,
-                gboolean save_to_disk,
-                GError **error)
+static GSList *
+get_unrecognized_specs (NMSettingsPlugin *plugin)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (config);
-	gs_free char *path = NULL;
-	gs_unref_object NMConnection *reread = NULL;
-
-	if (save_to_disk) {
-		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, reread ?: connection, path, NULL, FALSE, NULL, error));
+	return _unhandled_specs_from_hashtable (NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (plugin)->unrecognized_specs);
 }
 
+/*****************************************************************************/
+
 static void
-impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
+impl_ifcfgrh_get_ifcfg_details (NMSIfcfgRHPlugin *self,
                                 GDBusMethodInvocation *context,
                                 const char *in_ifcfg)
 {
-	NMIfcfgConnection *connection;
-	NMSettingConnection *s_con;
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+	gs_free char *ifcfg_path = NULL;
+	NMSIfcfgRHStorage *storage;
 	const char *uuid;
 	const char *path;
-	gs_free char *ifcfg_path = NULL;
 
-	if (!g_path_is_absolute (in_ifcfg)) {
+	if (in_ifcfg[0] != '/') {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
 		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -719,10 +916,8 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 		return;
 	}
 
-	connection = find_by_path (plugin, ifcfg_path);
-	if (   !connection
-	    || nm_ifcfg_connection_get_unmanaged_spec (connection)
-	    || nm_ifcfg_connection_get_unrecognized_spec (connection)) {
+	storage = nm_sett_util_storages_lookup_by_filename (&priv->storages, ifcfg_path);
+	if (!storage) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
 		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -730,25 +925,23 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 		return;
 	}
 
-	s_con = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection)));
-	if (!s_con) {
-		g_dbus_method_invocation_return_error (context,
-		                                       NM_SETTINGS_ERROR,
-		                                       NM_SETTINGS_ERROR_FAILED,
-		                                       "unable to retrieve the connection setting");
-		return;
-	}
-
-	uuid = nm_setting_connection_get_uuid (s_con);
+	uuid = nms_ifcfg_rh_storage_get_uuid_opt (storage);
 	if (!uuid) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
-		                                       NM_SETTINGS_ERROR_FAILED,
-		                                       "unable to get the UUID");
+		                                       NM_SETTINGS_ERROR_INVALID_CONNECTION,
+		                                       "ifcfg file '%s' not managed by NetworkManager", in_ifcfg);
 		return;
 	}
 
-	path = nm_dbus_object_get_path (NM_DBUS_OBJECT (connection));
+	/* It is ugly that the ifcfg-rh plugin needs to call back into NMSettings this
+	 * way.
+	 * There are alternatives (like invoking a signal), but they are all significant
+	 * extra code (and performance overhead). So the quick and dirty solution here
+	 * is likely to be simpler than getting this right (also from point of readability!).
+	 */
+	path = nm_settings_get_dbus_path_for_uuid (nm_settings_get (), uuid);
+
 	if (!path) {
 		g_dbus_method_invocation_return_error (context,
 		                                       NM_SETTINGS_ERROR,
@@ -764,9 +957,9 @@ impl_ifcfgrh_get_ifcfg_details (SettingsPluginIfcfg *plugin,
 /*****************************************************************************/
 
 static void
-_dbus_clear (SettingsPluginIfcfg *self)
+_dbus_clear (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 	guint id;
 
 	nm_clear_g_signal_handler (priv->dbus.connection, &priv->dbus.signal_id);
@@ -788,7 +981,7 @@ _dbus_connection_closed (GDBusConnection *connection,
                          gpointer         user_data)
 {
 	_LOGW ("dbus: %s bus closed", IFCFGRH1_BUS_NAME);
-	_dbus_clear (SETTINGS_PLUGIN_IFCFG (user_data));
+	_dbus_clear (NMS_IFCFG_RH_PLUGIN (user_data));
 
 	/* Retry or recover? */
 }
@@ -803,25 +996,27 @@ _method_call (GDBusConnection *connection,
               GDBusMethodInvocation *invocation,
               gpointer user_data)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (user_data);
-	const char *ifcfg;
-
-	if (   !nm_streq (interface_name, IFCFGRH1_IFACE1_NAME)
-	    || !nm_streq (method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) {
-		g_dbus_method_invocation_return_error (invocation,
-		                                       G_DBUS_ERROR,
-		                                       G_DBUS_ERROR_UNKNOWN_METHOD,
-		                                       "Unknown method %s",
-		                                       method_name);
-		return;
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (user_data);
+
+	if (nm_streq (interface_name, IFCFGRH1_IFACE1_NAME)) {
+		if (nm_streq (method_name, IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS)) {
+			const char *ifcfg;
+
+			g_variant_get (parameters, "(&s)", &ifcfg);
+			impl_ifcfgrh_get_ifcfg_details (self, invocation, ifcfg);
+			return;
+		}
 	}
 
-	g_variant_get (parameters, "(&s)", &ifcfg);
-	impl_ifcfgrh_get_ifcfg_details (self, invocation, ifcfg);
+	g_dbus_method_invocation_return_error (invocation,
+	                                       G_DBUS_ERROR,
+	                                       G_DBUS_ERROR_UNKNOWN_METHOD,
+	                                       "Unknown method %s",
+	                                       method_name);
 }
 
 static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO (
-	IFCFGRH1_BUS_NAME,
+	IFCFGRH1_IFACE1_NAME,
 	.methods = NM_DEFINE_GDBUS_METHOD_INFOS (
 		NM_DEFINE_GDBUS_METHOD_INFO (
 			IFCFGRH1_IFACE1_METHOD_GET_IFCFG_DETAILS,
@@ -842,8 +1037,8 @@ _dbus_request_name_done (GObject *source_object,
                          gpointer user_data)
 {
 	GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
-	SettingsPluginIfcfg *self;
-	SettingsPluginIfcfgPrivate *priv;
+	NMSIfcfgRHPlugin *self;
+	NMSIfcfgRHPluginPrivate *priv;
 	gs_free_error GError *error = NULL;
 	gs_unref_variant GVariant *ret = NULL;
 	guint32 result;
@@ -852,8 +1047,8 @@ _dbus_request_name_done (GObject *source_object,
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
-	self = SETTINGS_PLUGIN_IFCFG (user_data);
-	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	self = NMS_IFCFG_RH_PLUGIN (user_data);
+	priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
 	g_clear_object (&priv->dbus.cancellable);
 
@@ -900,8 +1095,8 @@ _dbus_create_done (GObject *source_object,
                    GAsyncResult *res,
                    gpointer user_data)
 {
-	SettingsPluginIfcfg *self;
-	SettingsPluginIfcfgPrivate *priv;
+	NMSIfcfgRHPlugin *self;
+	NMSIfcfgRHPluginPrivate *priv;
 	gs_free_error GError *error = NULL;
 	GDBusConnection *connection;
 
@@ -909,8 +1104,8 @@ _dbus_create_done (GObject *source_object,
 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 		return;
 
-	self = SETTINGS_PLUGIN_IFCFG (user_data);
-	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	self = NMS_IFCFG_RH_PLUGIN (user_data);
+	priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
 	g_clear_object (&priv->dbus.cancellable);
 
@@ -944,9 +1139,9 @@ _dbus_create_done (GObject *source_object,
 }
 
 static void
-_dbus_setup (SettingsPluginIfcfg *self)
+_dbus_setup (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 	gs_free char *address = NULL;
 	gs_free_error GError *error = NULL;
 
@@ -974,9 +1169,9 @@ config_changed_cb (NMConfig *config,
                    NMConfigData *config_data,
                    NMConfigChangeFlags changes,
                    NMConfigData *old_data,
-                   SettingsPluginIfcfg *self)
+                   NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv;
+	NMSIfcfgRHPluginPrivate *priv;
 
 	/* If the dbus connection for some reason is borked the D-Bus service
 	 * won't be offered.
@@ -988,7 +1183,7 @@ config_changed_cb (NMConfig *config,
 	                            | NM_CONFIG_CHANGE_CAUSE_SIGUSR1))
 		return;
 
-	priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 	if (   !priv->dbus.connection
 	    && !priv->dbus.cancellable)
 		_dbus_setup (self);
@@ -997,23 +1192,26 @@ config_changed_cb (NMConfig *config,
 /*****************************************************************************/
 
 static void
-settings_plugin_ifcfg_init (SettingsPluginIfcfg *plugin)
+nms_ifcfg_rh_plugin_init (NMSIfcfgRHPlugin *self)
 {
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE ((SettingsPluginIfcfg *) plugin);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
+
+	priv->config = g_object_ref (nm_config_get ());
+
+	priv->unmanaged_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
+	priv->unrecognized_specs = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
-	priv->connections = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
+	priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT (priv->storages, nms_ifcfg_rh_storage_destroy);
 }
 
 static void
 constructed (GObject *object)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (object);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (object);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
-	G_OBJECT_CLASS (settings_plugin_ifcfg_parent_class)->constructed (object);
+	G_OBJECT_CLASS (nms_ifcfg_rh_plugin_parent_class)->constructed (object);
 
-	priv->config = nm_config_get ();
-	g_object_add_weak_pointer ((GObject *) priv->config, (gpointer *) &priv->config);
 	g_signal_connect (priv->config,
 	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
 	                  G_CALLBACK (config_changed_cb),
@@ -1025,48 +1223,44 @@ constructed (GObject *object)
 static void
 dispose (GObject *object)
 {
-	SettingsPluginIfcfg *self = SETTINGS_PLUGIN_IFCFG (object);
-	SettingsPluginIfcfgPrivate *priv = SETTINGS_PLUGIN_IFCFG_GET_PRIVATE (self);
+	NMSIfcfgRHPlugin *self = NMS_IFCFG_RH_PLUGIN (object);
+	NMSIfcfgRHPluginPrivate *priv = NMS_IFCFG_RH_PLUGIN_GET_PRIVATE (self);
 
-	if (priv->config) {
-		g_object_remove_weak_pointer ((GObject *) priv->config, (gpointer *) &priv->config);
+	if (priv->config)
 		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, self);
-		priv->config = NULL;
-	}
 
+	/* FIXME(shutdown) we need a stop method so that we can unregistering the D-Bus service
+	 * when NMSettings is shutting down, and not when the instance gets destroyed. */
 	_dbus_clear (self);
 
-	if (priv->connections) {
-		g_hash_table_destroy (priv->connections);
-		priv->connections = NULL;
-	}
+	nm_sett_util_storages_clear (&priv->storages);
 
-	if (priv->ifcfg_monitor) {
-		if (priv->ifcfg_monitor_id)
-			g_signal_handler_disconnect (priv->ifcfg_monitor, priv->ifcfg_monitor_id);
+	g_clear_object (&priv->config);
 
-		g_file_monitor_cancel (priv->ifcfg_monitor);
-		g_object_unref (priv->ifcfg_monitor);
-	}
+	G_OBJECT_CLASS (nms_ifcfg_rh_plugin_parent_class)->dispose (object);
 
-	G_OBJECT_CLASS (settings_plugin_ifcfg_parent_class)->dispose (object);
+	nm_clear_pointer (&priv->unmanaged_specs, g_hash_table_destroy);
+	nm_clear_pointer (&priv->unrecognized_specs, g_hash_table_destroy);
 }
 
 static void
-settings_plugin_ifcfg_class_init (SettingsPluginIfcfgClass *klass)
+nms_ifcfg_rh_plugin_class_init (NMSIfcfgRHPluginClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->constructed = constructed;
-	object_class->dispose = dispose;
+	object_class->dispose     = dispose;
 
-	plugin_class->get_connections = get_connections;
-	plugin_class->add_connection = add_connection;
-	plugin_class->load_connection = load_connection;
-	plugin_class->reload_connections = reload_connections;
-	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+	plugin_class->plugin_name            = "ifcfg-rh";
+	plugin_class->get_unmanaged_specs    = get_unmanaged_specs;
 	plugin_class->get_unrecognized_specs = get_unrecognized_specs;
+	plugin_class->reload_connections     = reload_connections;
+	plugin_class->load_connections       = load_connections;
+	plugin_class->load_connections_done  = load_connections_done;
+	plugin_class->add_connection         = add_connection;
+	plugin_class->update_connection      = update_connection;
+	plugin_class->delete_connection      = delete_connection;
 }
 
 /*****************************************************************************/
@@ -1074,5 +1268,5 @@ settings_plugin_ifcfg_class_init (SettingsPluginIfcfgClass *klass)
 G_MODULE_EXPORT NMSettingsPlugin *
 nm_settings_plugin_factory (void)
 {
-	return NM_SETTINGS_PLUGIN (g_object_ref (settings_plugin_ifcfg_get ()));
+	return g_object_new (NMS_TYPE_IFCFG_RH_PLUGIN, NULL);
 }
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
index d815cc51..1db36083 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * Dan Williams <dcbw@redhat.com>
@@ -21,19 +20,19 @@
  * Copyright (C) 2007 - 2008 Red Hat, Inc.
  */
 
-#ifndef _PLUGIN_H_
-#define _PLUGIN_H_
+#ifndef __NMS_IFCFG_RH_PLUGIN_H__
+#define __NMS_IFCFG_RH_PLUGIN_H__
 
-#define SETTINGS_TYPE_PLUGIN_IFCFG            (settings_plugin_ifcfg_get_type ())
-#define SETTINGS_PLUGIN_IFCFG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SETTINGS_TYPE_PLUGIN_IFCFG, SettingsPluginIfcfg))
-#define SETTINGS_PLUGIN_IFCFG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SETTINGS_TYPE_PLUGIN_IFCFG, SettingsPluginIfcfgClass))
-#define SETTINGS_IS_PLUGIN_IFCFG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SETTINGS_TYPE_PLUGIN_IFCFG))
-#define SETTINGS_IS_PLUGIN_IFCFG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SETTINGS_TYPE_PLUGIN_IFCFG))
-#define SETTINGS_PLUGIN_IFCFG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SETTINGS_TYPE_PLUGIN_IFCFG, SettingsPluginIfcfgClass))
+#define NMS_TYPE_IFCFG_RH_PLUGIN            (nms_ifcfg_rh_plugin_get_type ())
+#define NMS_IFCFG_RH_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPlugin))
+#define NMS_IFCFG_RH_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass))
+#define NMS_IS_IFCFG_RH_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IFCFG_RH_PLUGIN))
+#define NMS_IS_IFCFG_RH_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IFCFG_RH_PLUGIN))
+#define NMS_IFCFG_RH_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IFCFG_RH_PLUGIN, NMSIfcfgRHPluginClass))
 
-typedef struct _SettingsPluginIfcfg SettingsPluginIfcfg;
-typedef struct _SettingsPluginIfcfgClass SettingsPluginIfcfgClass;
+typedef struct _NMSIfcfgRHPlugin NMSIfcfgRHPlugin;
+typedef struct _NMSIfcfgRHPluginClass NMSIfcfgRHPluginClass;
 
-GType settings_plugin_ifcfg_get_type (void);
+GType nms_ifcfg_rh_plugin_get_type (void);
 
-#endif /* _PLUGIN_H_ */
+#endif /* __NMS_IFCFG_RH_PLUGIN_H__ */
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 e5423b18..900a3fc1 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -543,6 +542,37 @@ make_connection_setting (const char *file,
 	vint64 = svGetValueInt64 (ifcfg, "AUTH_RETRIES", 10, -1, G_MAXINT32, -1);
 	g_object_set (s_con, NM_SETTING_CONNECTION_AUTH_RETRIES, (int) vint64, NULL);
 
+	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "DEVTIMEOUT", &value);
+	if (v) {
+		vint64 = _nm_utils_ascii_str_to_int64 (v, 10, 0, ((gint64) G_MAXINT32) / 1000, -1);
+		if (vint64 != -1)
+			vint64 *= 1000;
+		else {
+			char *endptr;
+			double d;
+
+			d = g_ascii_strtod (v, &endptr);
+			if (   errno == 0
+			    && endptr[0] == '\0'
+			    && d >= 0.0) {
+				d *= 1000.0;
+
+				/* We round. Yes, this is not correct to round IEEE 754 floats in general,
+				 * but sufficient for our case where we know that NetworkManager wrote the
+				 * setting with up to 3 digits for the milliseconds. */
+				d += 0.5;
+				if (   d >= 0.0
+				    && d <= (double) G_MAXINT32)
+					vint64 = (gint64) d;
+			}
+		}
+		if (vint64 == -1)
+			PARSE_WARNING ("invalid DEVTIMEOUT setting");
+		else
+			g_object_set (s_con, NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, (int) vint64, NULL);
+	}
+
 	i_val = NM_SETTING_CONNECTION_MDNS_DEFAULT;
 	if (!svGetValueEnum (ifcfg, "MDNS",
 	                     nm_setting_connection_mdns_get_type (),
@@ -1906,7 +1936,10 @@ make_ip6_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingIPConfig *s_ip6 = NULL;
 	const char *v;
 	gs_free char *value = NULL;
-	gboolean ipv6init, ipv6forwarding, dhcp6 = FALSE;
+	gboolean ipv6init;
+	gboolean ipv6forwarding;
+	gboolean disabled;
+	gboolean dhcp6 = FALSE;
 	char *method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
 	const char *ipv6addr, *ipv6addr_secondaries;
 	gs_free char *ipv6addr_to_free = NULL;
@@ -1964,6 +1997,7 @@ make_ip6_setting (shvarFile *ifcfg,
 
 	/* Find out method property */
 	/* Is IPV6 enabled? Set method to "ignored", when not enabled */
+	disabled = svGetValueBoolean(ifcfg, "IPV6_DISABLED", FALSE);
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "IPV6INIT", &value);
 	ipv6init = svGetValueBoolean (ifcfg, "IPV6INIT", FALSE);
@@ -1972,8 +2006,10 @@ make_ip6_setting (shvarFile *ifcfg,
 			ipv6init = svGetValueBoolean (network_ifcfg, "IPV6INIT", FALSE);
 	}
 
-	if (!ipv6init)
-		method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;  /* IPv6 is disabled */
+	if (disabled)
+		method = NM_SETTING_IP6_CONFIG_METHOD_DISABLED;
+	else if (!ipv6init)
+		method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
 	else {
 		ipv6forwarding = svGetValueBoolean (ifcfg, "IPV6FORWARDING", FALSE);
 		nm_clear_g_free (&value);
@@ -2039,7 +2075,8 @@ make_ip6_setting (shvarFile *ifcfg,
 	              NULL);
 
 	/* Don't bother to read IP, DNS and routes when IPv6 is disabled */
-	if (strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0)
+	if (NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+	                          NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
 		return NM_SETTING (g_steal_pointer (&s_ip6));
 
 	nm_clear_g_free (&value);
@@ -3071,12 +3108,17 @@ eap_tls_reader (const char *eap_method,
 	gs_unref_bytes GBytes *privkey = NULL;
 	gs_unref_bytes GBytes *client_cert = NULL;
 	gs_free char *identity_free = NULL;
+	gs_free char *value_to_free = NULL;
+	const char *client_cert_var;
+	const char *client_cert_prop;
+	NMSetting8021xCKFormat format;
 
 	g_object_set (s_8021x,
 	              NM_SETTING_802_1X_IDENTITY,
 	              svGetValueStr (ifcfg, "IEEE_8021X_IDENTITY", &identity_free),
 	              NULL);
 
+	/* CA certificate */
 	if (!_cert_set_from_ifcfg (s_8021x,
 	                           ifcfg,
 	                           phase2 ? "IEEE_8021X_INNER_CA_CERT" : "IEEE_8021X_CA_CERT",
@@ -3090,6 +3132,7 @@ eap_tls_reader (const char *eap_method,
 	                        phase2 ? "IEEE_8021X_INNER_CA_CERT_PASSWORD" : "IEEE_8021X_CA_CERT_PASSWORD",
 	                        phase2 ? NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD : NM_SETTING_802_1X_CA_CERT_PASSWORD);
 
+	/* Private key */
 	if (!_cert_set_from_ifcfg (s_8021x,
 	                           ifcfg,
 	                           phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY",
@@ -3102,35 +3145,39 @@ eap_tls_reader (const char *eap_method,
 	                        keys_ifcfg,
 	                        phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD" : "IEEE_8021X_PRIVATE_KEY_PASSWORD",
 	                        phase2 ? NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD : NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD);
-	if (!privkey) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Missing %s for EAP method '%s'.",
-		             phase2 ? "IEEE_8021X_INNER_PRIVATE_KEY" : "IEEE_8021X_PRIVATE_KEY",
-		             eap_method);
-		return FALSE;
-	}
 
+	/* Client certificate */
+	client_cert_var = phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT";
+	client_cert_prop = phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT;
 	if (!_cert_set_from_ifcfg (s_8021x,
 	                           ifcfg,
-	                           phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT",
-	                           phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT : NM_SETTING_802_1X_CLIENT_CERT,
+	                           client_cert_var,
+	                           client_cert_prop,
 	                           &client_cert,
 	                           error))
 		return FALSE;
-	/* FIXME: writer does not actually write IEEE_8021X_CLIENT_CERT_PASSWORD and other
-	 * certificate related passwords. It should, because otherwise persisting such profiles
-	 * to ifcfg looses information. As this currently only matters for PKCS11 URIs, it seems
-	 * a seldom used feature so that it is not fixed yet. */
 	_secret_set_from_ifcfg (s_8021x,
 	                        ifcfg,
 	                        keys_ifcfg,
 	                        phase2 ? "IEEE_8021X_INNER_CLIENT_CERT_PASSWORD" : "IEEE_8021X_CLIENT_CERT_PASSWORD",
 	                        phase2 ? NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD : NM_SETTING_802_1X_CLIENT_CERT_PASSWORD);
-	if (!client_cert) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "Missing certificate for EAP method '%s'.",
-		             eap_method);
-		return FALSE;
+
+	/* In the past when the private key and client certificate
+	 * were the same PKCS #12 file we used to write only the
+	 * private key variable. Still support that even if it means
+	 * that we have to look into the file content, which makes
+	 * the connection not self-contained.
+	 */
+	if (   !client_cert
+	    && privkey
+	    && !svGetValue (ifcfg, client_cert_var, &value_to_free)) {
+		if (phase2)
+			format = nm_setting_802_1x_get_phase2_private_key_format (s_8021x);
+		else
+			format = nm_setting_802_1x_get_private_key_format (s_8021x);
+
+		if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12)
+			g_object_set (s_8021x, client_cert_prop, privkey, NULL);
 	}
 
 	return TRUE;
@@ -4369,9 +4416,11 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 	gboolean autoneg = FALSE;
 	guint32 speed = 0;
 	const char *duplex = NULL;
+	gboolean wired_found = FALSE;
 
 	ethtool_opts = svGetValue (ifcfg, "ETHTOOL_OPTS", &ethtool_opts_free);
 	if (ethtool_opts) {
+		wired_found = TRUE;
 		/* WAKE_ON_LAN_IGNORE is inferred from a specified but empty ETHTOOL_OPTS */
 		if (!ethtool_opts[0])
 			wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
@@ -4395,7 +4444,9 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 	}
 
 	/* ETHTOOL_WAKE_ON_LAN = ignore overrides WoL settings in ETHTOOL_OPTS */
-	tmp = svGetValueStr (ifcfg, "ETHTOOL_WAKE_ON_LAN", &wol_value_free);
+	tmp = svGetValue (ifcfg, "ETHTOOL_WAKE_ON_LAN", &wol_value_free);
+	if (tmp)
+		wired_found = TRUE;
 	if (nm_streq0 (tmp, "ignore"))
 		wol_flags = NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE;
 	else if (tmp)
@@ -4408,6 +4459,10 @@ parse_ethtool_options (shvarFile *ifcfg, NMConnection *connection)
 	}
 
 	s_wired = nm_connection_get_setting_wired (connection);
+	if (!s_wired && wired_found) {
+		s_wired = (NMSettingWired *) nm_setting_wired_new ();
+		nm_connection_add_setting (connection, NM_SETTING (s_wired));
+	}
 	if (s_wired) {
 		g_object_set (s_wired,
 		              NM_SETTING_WIRED_WAKE_ON_LAN, wol_flags,
@@ -4433,86 +4488,100 @@ make_wired_setting (shvarFile *ifcfg,
 	gs_unref_object NMSettingWired *s_wired = NULL;
 	const char *cvalue;
 	gs_free char *value = NULL;
-	char *nettype;
+	gboolean found = FALSE;
 
 	s_wired = NM_SETTING_WIRED (nm_setting_wired_new ());
 
-	value = svGetValueStr_cp (ifcfg, "MTU");
-	if (value) {
+	cvalue = svGetValue (ifcfg, "MTU", &value);
+	if (cvalue) {
 		int mtu;
 
-		mtu = _nm_utils_ascii_str_to_int64 (value, 0, 0, 65535, -1);
+		mtu = _nm_utils_ascii_str_to_int64 (cvalue, 0, 0, 65535, -1);
 		if (mtu >= 0)
 			g_object_set (s_wired, NM_SETTING_WIRED_MTU, (guint) mtu, NULL);
 		else
-			PARSE_WARNING ("invalid MTU '%s'", value);
+			PARSE_WARNING ("invalid MTU '%s'", cvalue);
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "HWADDR");
+	value = svGetValue_cp (ifcfg, "HWADDR");
 	if (value) {
-		value = g_strstrip (value);
-		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, value, NULL);
+		if (value[0] != '\0') {
+			value = g_strstrip (value);
+			g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS, value, NULL);
+		}
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "SUBCHANNELS");
-	if (value) {
-		const char *p = value;
-		gboolean success = TRUE;
-
-		/* basic sanity checks */
-		while (*p) {
-			if (!g_ascii_isxdigit (*p) && (*p != ',') && (*p != '.')) {
-				PARSE_WARNING ("invalid SUBCHANNELS '%s'", value);
-				success = FALSE;
-				break;
+	cvalue = svGetValue (ifcfg, "SUBCHANNELS", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0') {
+			const char *p = cvalue;
+			gboolean success = TRUE;
+
+			/* basic sanity checks */
+			while (*p) {
+				if (!g_ascii_isxdigit (*p) && (*p != ',') && (*p != '.')) {
+					PARSE_WARNING ("invalid SUBCHANNELS '%s'", cvalue);
+					success = FALSE;
+					break;
+				}
+				p++;
 			}
-			p++;
-		}
 
-		if (success) {
-			gs_free const char **chans = NULL;
-			guint32 num_chans;
+			if (success) {
+				gs_free const char **chans = NULL;
+				guint32 num_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' (%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);
+				chans = nm_utils_strsplit_set (cvalue, ",");
+				num_chans = NM_PTRARRAY_LEN (chans);
+				if (num_chans < 2 || num_chans > 3) {
+					PARSE_WARNING ("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
+					               cvalue, (unsigned) NM_PTRARRAY_LEN (chans));
+				} else
+					g_object_set (s_wired, NM_SETTING_WIRED_S390_SUBCHANNELS, chans, NULL);
+			}
 		}
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "PORTNAME");
-	if (value) {
-		nm_setting_wired_add_s390_option (s_wired, "portname", value);
+	cvalue = svGetValue (ifcfg, "PORTNAME", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0')
+			nm_setting_wired_add_s390_option (s_wired, "portname", cvalue);
+		found = TRUE;
 		nm_clear_g_free (&value);
 	}
 
-	value = svGetValueStr_cp (ifcfg, "CTCPROT");
-	if (value) {
-		nm_setting_wired_add_s390_option (s_wired, "ctcprot", value);
+	cvalue = svGetValue (ifcfg, "CTCPROT", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0')
+			nm_setting_wired_add_s390_option (s_wired, "ctcprot", cvalue);
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	nettype = svGetValueStr_cp (ifcfg, "NETTYPE");
-	if (nettype) {
-		if (!strcmp (nettype, "qeth") || !strcmp (nettype, "lcs") || !strcmp (nettype, "ctc"))
-			g_object_set (s_wired, NM_SETTING_WIRED_S390_NETTYPE, nettype, NULL);
+	cvalue = svGetValue (ifcfg, "NETTYPE", &value);
+	if (cvalue) {
+		if (NM_IN_STRSET (cvalue, "qeth", "lcs", "ctc"))
+			g_object_set (s_wired, NM_SETTING_WIRED_S390_NETTYPE, cvalue, NULL);
 		else
-			PARSE_WARNING ("unknown s390 NETTYPE '%s'", nettype);
-		g_free (nettype);
+			PARSE_WARNING ("unknown s390 NETTYPE '%s'", cvalue);
+		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "OPTIONS");
-	if (value) {
+	cvalue = svGetValue (ifcfg, "OPTIONS", &value);
+	if (cvalue)
+		found = TRUE;
+	if (cvalue && cvalue[0]) {
 		gs_free const char **options = NULL;
 		gsize i;
 
-		options = nm_utils_strsplit_set_with_empty (value, " ");
+		options = nm_utils_escaped_tokens_split (cvalue, NM_ASCII_SPACES);
 		for (i = 0; options && options[i]; i++) {
 			const char *line = options[i];
 			const char *equals;
@@ -4526,20 +4595,33 @@ make_wired_setting (shvarFile *ifcfg,
 			if (!valid)
 				PARSE_WARNING ("invalid s390 OPTION '%s'", line);
 		}
-		nm_clear_g_free (&value);
+		found = TRUE;
 	}
-
-	g_object_set (s_wired,
-	              NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
-	              svGetValueStr (ifcfg, "MACADDR", &value),
-	              NULL);
 	nm_clear_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);
+	cvalue = svGetValueStr (ifcfg, "MACADDR", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0') {
+			g_object_set (s_wired,
+			              NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+			              cvalue,
+			              NULL);
+		}
+		nm_clear_g_free (&value);
+		found = TRUE;
+	}
+
+	cvalue = svGetValueStr (ifcfg, "GENERATE_MAC_ADDRESS_MASK", &value);
+	if (cvalue) {
+		if (cvalue[0] != '\0') {
+			g_object_set (s_wired,
+			              NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK,
+			              cvalue,
+			              NULL);
+		}
+		nm_clear_g_free (&value);
+		found = TRUE;
+	}
 
 	cvalue = svGetValueStr (ifcfg, "HWADDR_BLACKLIST", &value);
 	if (cvalue) {
@@ -4548,20 +4630,31 @@ make_wired_setting (shvarFile *ifcfg,
 		strv = transform_hwaddr_blacklist (cvalue);
 		g_object_set (s_wired, NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, strv, NULL);
 		nm_clear_g_free (&value);
+		found = TRUE;
 	}
 
-	value = svGetValueStr_cp (ifcfg, "KEY_MGMT");
-	if (value) {
-		if (!strcmp (value, "IEEE8021X")) {
-			*s_8021x = fill_8021x (ifcfg, file, value, FALSE, error);
+	cvalue = svGetValue (ifcfg, "KEY_MGMT", &value);
+	if (cvalue)
+		found = TRUE;
+	if (cvalue && cvalue[0] != '\0') {
+		if (!strcmp (cvalue, "IEEE8021X")) {
+			*s_8021x = fill_8021x (ifcfg, file, cvalue, FALSE, error);
 			if (!*s_8021x)
 				return NULL;
 		} else {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Unknown wired KEY_MGMT type '%s'", value);
+			             "Unknown wired KEY_MGMT type '%s'", cvalue);
 			return NULL;
 		}
-		nm_clear_g_free (&value);
+	}
+	nm_clear_g_free (&value);
+
+	if (!found) {
+		g_set_error (error,
+		             NM_UTILS_ERROR,
+		             NM_UTILS_ERROR_SETTING_MISSING,
+		             "The setting is missing.");
+		return NULL;
 	}
 
 	return (NMSetting *) g_steal_pointer (&s_wired);
@@ -4576,6 +4669,7 @@ wired_connection_from_ifcfg (const char *file,
 	NMSetting *con_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -4591,12 +4685,16 @@ wired_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, con_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -4699,8 +4797,8 @@ make_infiniband_setting (shvarFile *ifcfg,
 		g_object_set (s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
 
 	if (svGetValueBoolean (ifcfg, "PKEY", FALSE)) {
+		gs_free char *parent = NULL;
 		int p_key;
-		char *parent;
 
 		if (!parse_infiniband_p_key (ifcfg, &p_key, &parent, error)) {
 			g_object_unref (s_infiniband);
@@ -4827,6 +4925,7 @@ bond_connection_from_ifcfg (const char *file,
 	NMSetting *bond_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -4849,12 +4948,16 @@ bond_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, bond_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -4862,62 +4965,27 @@ bond_connection_from_ifcfg (const char *file,
 	return connection;
 }
 
-/* Check 'error' for errors. Missing config (NULL return value) is a valid case. */
-static char *
-read_team_config (shvarFile *ifcfg, const char *key, GError **error)
-{
-	gs_free_error GError *local_error = NULL;
-	gs_free char *value = NULL;
-	size_t l;
-
-	value = svGetValueStr_cp (ifcfg, key);
-	if (!value)
-		return NULL;
-
-	l = strlen (value);
-	if (l > 1*1024*1024) {
-		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-		             "%s too long (size %zd)", key, l);
-		return NULL;
-	}
-
-	if (!nm_utils_is_json_object (value, &local_error)) {
-		PARSE_WARNING ("ignoring invalid team configuration: %s", local_error->message);
-		return NULL;
-	}
-
-	return g_steal_pointer (&value);
-}
-
 static NMSetting *
 make_team_setting (shvarFile *ifcfg,
                    const char *file,
                    GError **error)
 {
-	NMSettingTeam *s_team;
-	char *value;
-	GError *local_err = NULL;
+	NMSetting *s_team;
+	gs_free char *value_device = NULL;
+	gs_free char *value = NULL;
 
-	value = svGetValueStr_cp (ifcfg, "DEVICE");
-	if (!value) {
+	if (!svGetValueStr (ifcfg, "DEVICE", &value_device)) {
 		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 		             "mandatory DEVICE keyword missing");
 		return NULL;
 	}
-	g_free (value);
-
-	value = read_team_config (ifcfg, "TEAM_CONFIG", &local_err);
-	if (local_err) {
-		g_propagate_error (error, local_err);
-		return NULL;
-	}
 
-	s_team = NM_SETTING_TEAM (nm_setting_team_new ());
-
-	g_object_set (s_team, NM_SETTING_TEAM_CONFIG, value, NULL);
-	g_free (value);
-
-	return (NMSetting *) s_team;
+	s_team = nm_setting_team_new ();
+	g_object_set (s_team,
+	              NM_SETTING_TEAM_CONFIG,
+	              svGetValue (ifcfg, "TEAM_CONFIG", &value),
+	              NULL);
+	return s_team;
 }
 
 static NMConnection *
@@ -4930,6 +4998,7 @@ team_connection_from_ifcfg (const char *file,
 	NMSetting *team_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -4952,12 +5021,16 @@ team_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, team_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -5207,6 +5280,7 @@ bridge_connection_from_ifcfg (const char *file,
 	NMSetting *bridge_setting = NULL;
 	NMSetting *wired_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -5229,12 +5303,16 @@ bridge_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, bridge_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -5276,20 +5354,18 @@ make_bridge_port_setting (shvarFile *ifcfg)
 static NMSetting *
 make_team_port_setting (shvarFile *ifcfg)
 {
-	NMSetting *s_port = NULL;
-	char *value;
-	GError *error = NULL;
+	NMSetting *s_port;
+	gs_free char *value = NULL;
 
-	value = read_team_config (ifcfg, "TEAM_PORT_CONFIG", &error);
-	if (value) {
-		s_port = nm_setting_team_port_new ();
-		g_object_set (s_port, NM_SETTING_TEAM_PORT_CONFIG, value, NULL);
-		g_free (value);
-	} else if (error) {
-		PARSE_WARNING ("%s", error->message);
-		g_error_free (error);
-	}
+	value = svGetValueStr_cp (ifcfg, "TEAM_PORT_CONFIG");
+	if (!value)
+		return NULL;
 
+	s_port = nm_setting_team_port_new ();
+	g_object_set (s_port,
+	              NM_SETTING_TEAM_PORT_CONFIG,
+	              value,
+	              NULL);
 	return s_port;
 }
 
@@ -5486,6 +5562,7 @@ vlan_connection_from_ifcfg (const char *file,
 	NMSetting *wired_setting = NULL;
 	NMSetting *vlan_setting = NULL;
 	NMSetting8021x *s_8021x = NULL;
+	GError *local = NULL;
 
 	g_return_val_if_fail (file != NULL, NULL);
 	g_return_val_if_fail (ifcfg != NULL, NULL);
@@ -5508,12 +5585,16 @@ vlan_connection_from_ifcfg (const char *file,
 	}
 	nm_connection_add_setting (connection, vlan_setting);
 
-	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, error);
-	if (!wired_setting) {
+	wired_setting = make_wired_setting (ifcfg, file, &s_8021x, &local);
+	if (local && !g_error_matches (local, NM_UTILS_ERROR, NM_UTILS_ERROR_SETTING_MISSING)) {
+		g_propagate_error (error, local);
 		g_object_unref (connection);
 		return NULL;
 	}
-	nm_connection_add_setting (connection, wired_setting);
+	g_clear_error (&local);
+
+	if (wired_setting)
+		nm_connection_add_setting (connection, wired_setting);
 
 	if (s_8021x)
 		nm_connection_add_setting (connection, NM_SETTING (s_8021x));
@@ -5549,21 +5630,21 @@ create_unhandled_connection (const char *filename, shvarFile *ifcfg,
 	if (v) {
 		gs_free char *lower = g_ascii_strdown (v, -1);
 
-		*out_spec = g_strdup_printf ("%s:mac:%s", type, lower);
+		*out_spec = g_strdup_printf ("%s:"NM_MATCH_SPEC_MAC_TAG"%s", type, lower);
 		return connection;
 	}
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "SUBCHANNELS", &value);
 	if (v) {
-		*out_spec = g_strdup_printf ("%s:s390-subchannels:%s", type, v);
+		*out_spec = g_strdup_printf ("%s:"NM_MATCH_SPEC_S390_SUBCHANNELS_TAG"%s", type, v);
 		return connection;
 	}
 
 	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "DEVICE", &value);
 	if (v) {
-		*out_spec = g_strdup_printf ("%s:interface-name:%s", type, v);
+		*out_spec = g_strdup_printf ("%s:"NM_MATCH_SPEC_INTERFACE_NAME_TAG"=%s", type, v);
 		return connection;
 	}
 
@@ -5654,7 +5735,7 @@ connection_from_file_full (const char *filename,
 		return g_steal_pointer (&connection);
 	}
 
-	/* iBFT is handled by the iBFT settings plugin */
+	/* iBFT is handled by nm-initrd-generator during boot. */
 	bootproto = svGetValueStr_cp (main_ifcfg, "BOOTPROTO");
 	if (bootproto && !g_ascii_strcasecmp (bootproto, "ibft")) {
 		NM_SET_OUT (out_ignore_error, TRUE);
@@ -5902,12 +5983,10 @@ connection_from_file_full (const char *filename,
 	if (s_match)
 		nm_connection_add_setting (connection, s_match);
 
-	/* Bridge port? */
 	s_port = make_bridge_port_setting (main_ifcfg);
 	if (s_port)
 		nm_connection_add_setting (connection, s_port);
 
-	/* Team port? */
 	s_port = make_team_port_setting (main_ifcfg);
 	if (s_port)
 		nm_connection_add_setting (connection, s_port);
@@ -5949,20 +6028,3 @@ nmtst_connection_from_file (const char *filename,
 	                                  error,
 	                                  NULL);
 }
-
-guint
-devtimeout_from_file (const char *filename)
-{
-	shvarFile *ifcfg;
-	guint devtimeout;
-
-	g_return_val_if_fail (filename != NULL, 0);
-
-	ifcfg = svOpenFile (filename, NULL);
-	if (!ifcfg)
-		return 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 a8937ac8..8008e052 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -18,8 +17,8 @@
  * Copyright (C) 2008 Red Hat, Inc.
  */
 
-#ifndef __READER_H__
-#define __READER_H__
+#ifndef __NMS_IFCFG_RH_READER_H__
+#define __NMS_IFCFG_RH_READER_H__
 
 #include "nm-connection.h"
 
@@ -28,12 +27,10 @@ NMConnection *connection_from_file (const char *filename,
                                     GError **error,
                                     gboolean *out_ignore_error);
 
-guint devtimeout_from_file (const char *filename);
-
 NMConnection *nmtst_connection_from_file (const char *filename,
                                           const char *network_file,
                                           const char *test_type,
                                           char **out_unhandled,
                                           GError **error);
 
-#endif  /* __READER_H__ */
+#endif  /* __NMS_IFCFG_RH_READER_H__ */
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
new file mode 100644
index 00000000..2841bedb
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
@@ -0,0 +1,198 @@
+/* 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.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nms-ifcfg-rh-storage.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nm-connection.h"
+#include "nms-ifcfg-rh-plugin.h"
+
+/*****************************************************************************/
+
+struct _NMSIfcfgRHStorageClass {
+	NMSettingsStorageClass parent;
+};
+
+G_DEFINE_TYPE (NMSIfcfgRHStorage, nms_ifcfg_rh_storage, NM_TYPE_SETTINGS_STORAGE)
+
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_storage_equal_type (const NMSIfcfgRHStorage *self_a,
+                                 const NMSIfcfgRHStorage *self_b)
+{
+	return    (self_a == self_b)
+	       || (   self_a
+	           && self_b
+	           && nm_streq0 (nms_ifcfg_rh_storage_get_uuid_opt (self_a),
+	                         nms_ifcfg_rh_storage_get_uuid_opt (self_b))
+	           && nm_streq0 (self_a->unmanaged_spec,
+	                         self_b->unmanaged_spec)
+	           && nm_streq0 (self_a->unrecognized_spec,
+	                         self_b->unrecognized_spec));
+}
+
+void
+nms_ifcfg_rh_storage_copy_content (NMSIfcfgRHStorage *dst,
+                                   const NMSIfcfgRHStorage *src)
+{
+	nm_assert (src != dst);
+	nm_assert (src && dst);
+	nm_assert (nms_ifcfg_rh_storage_equal_type (dst, src));
+	nm_assert (   nms_ifcfg_rh_storage_get_filename (dst)
+	           && nm_streq (nms_ifcfg_rh_storage_get_filename (dst),
+	                        nms_ifcfg_rh_storage_get_filename (src)));
+
+	nm_g_object_ref_set (&dst->connection, src->connection);
+	g_free (dst->unmanaged_spec);
+	g_free (dst->unrecognized_spec);
+	dst->unmanaged_spec    = g_strdup (src->unmanaged_spec);
+	dst->unrecognized_spec = g_strdup (src->unrecognized_spec);
+	dst->stat_mtime        = src->stat_mtime;
+}
+
+NMConnection *
+nms_ifcfg_rh_storage_steal_connection (NMSIfcfgRHStorage *self)
+{
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (self));
+
+	return g_steal_pointer (&self->connection);
+}
+
+/*****************************************************************************/
+
+static int
+cmp_fcn (const NMSIfcfgRHStorage *a,
+         const NMSIfcfgRHStorage *b)
+{
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (a));
+	nm_assert (NMS_IS_IFCFG_RH_STORAGE (b));
+	nm_assert (a != b);
+
+	/* newer files are more important. */
+	NM_CMP_FIELD (a, b, stat_mtime.tv_sec);
+	NM_CMP_FIELD (a, b, stat_mtime.tv_nsec);
+
+	NM_CMP_DIRECT_STRCMP (nms_ifcfg_rh_storage_get_filename (a), nms_ifcfg_rh_storage_get_filename (b));
+
+	return 0;
+}
+
+/*****************************************************************************/
+
+static void
+nms_ifcfg_rh_storage_init (NMSIfcfgRHStorage *self)
+{
+}
+
+static NMSIfcfgRHStorage *
+_storage_new (NMSIfcfgRHPlugin *plugin,
+              const char *uuid,
+              const char *filename)
+{
+	nm_assert (NMS_IS_IFCFG_RH_PLUGIN (plugin));
+	nm_assert (!uuid || nm_utils_is_uuid (uuid));
+	nm_assert (filename && filename[0] == '/');
+
+	return g_object_new (NMS_TYPE_IFCFG_RH_STORAGE,
+	                     NM_SETTINGS_STORAGE_PLUGIN, plugin,
+	                     NM_SETTINGS_STORAGE_UUID, uuid,
+	                     NM_SETTINGS_STORAGE_FILENAME, filename,
+	                     NULL);
+}
+
+NMSIfcfgRHStorage *
+nms_ifcfg_rh_storage_new_connection (NMSIfcfgRHPlugin *plugin,
+                                     const char *filename,
+                                     NMConnection *connection_take,
+                                     const struct timespec *mtime)
+{
+	NMSIfcfgRHStorage *self;
+
+	nm_assert (NM_IS_CONNECTION (connection_take));
+	nm_assert (_nm_connection_verify (connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nmtst_connection_assert_unchanging (connection_take);
+
+	self = _storage_new (plugin,
+	                     nm_connection_get_uuid (connection_take),
+	                     filename);
+	self->connection = connection_take;
+	if (mtime)
+		self->stat_mtime = *mtime;
+	return self;
+}
+
+NMSIfcfgRHStorage *
+nms_ifcfg_rh_storage_new_unhandled (NMSIfcfgRHPlugin *plugin,
+                                    const char *filename,
+                                    const char *unmanaged_spec,
+                                    const char *unrecognized_spec)
+{
+	NMSIfcfgRHStorage *self;
+
+	nm_assert (unmanaged_spec || unrecognized_spec);
+
+	self = _storage_new (plugin,
+	                     NULL,
+	                     filename);
+	self->unmanaged_spec = g_strdup (unmanaged_spec);
+	self->unrecognized_spec = g_strdup (unrecognized_spec);
+	return self;
+}
+
+static void
+_storage_clear (NMSIfcfgRHStorage *self)
+{
+	c_list_unlink (&self->parent._storage_lst);
+	c_list_unlink (&self->parent._storage_by_uuid_lst);
+	nm_clear_g_free (&self->unmanaged_spec);
+	nm_clear_g_free (&self->unrecognized_spec);
+	g_clear_object (&self->connection);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMSIfcfgRHStorage *self = NMS_IFCFG_RH_STORAGE (object);
+
+	_storage_clear (self);
+
+	G_OBJECT_CLASS (nms_ifcfg_rh_storage_parent_class)->dispose (object);
+}
+
+void
+nms_ifcfg_rh_storage_destroy (NMSIfcfgRHStorage *self)
+{
+	_storage_clear (self);
+	g_object_unref (self);
+}
+
+static void
+nms_ifcfg_rh_storage_class_init (NMSIfcfgRHStorageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	storage_class->cmp_fcn = (int (*) (NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
+}
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
new file mode 100644
index 00000000..e1165f50
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.h
@@ -0,0 +1,93 @@
+/* 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.
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ */
+
+#ifndef __NMS_IFCFG_RH_STORAGE_H__
+#define __NMS_IFCFG_RH_STORAGE_H__
+
+#include "c-list/src/c-list.h"
+#include "settings/nm-settings-storage.h"
+
+/*****************************************************************************/
+
+#define NMS_TYPE_IFCFG_RH_STORAGE            (nms_ifcfg_rh_storage_get_type ())
+#define NMS_IFCFG_RH_STORAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorage))
+#define NMS_IFCFG_RH_STORAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass))
+#define NMS_IS_IFCFG_RH_STORAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IFCFG_RH_STORAGE))
+#define NMS_IS_IFCFG_RH_STORAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IFCFG_RH_STORAGE))
+#define NMS_IFCFG_RH_STORAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IFCFG_RH_STORAGE, NMSIfcfgRHStorageClass))
+
+typedef struct {
+	NMSettingsStorage parent;
+
+	NMConnection *connection;
+
+	char *unmanaged_spec;
+	char *unrecognized_spec;
+
+	/* The timestamp (stat's mtime) of the file. Newer files have
+	 * higher priority. */
+	struct timespec stat_mtime;
+
+	bool dirty:1;
+
+} NMSIfcfgRHStorage;
+
+typedef struct _NMSIfcfgRHStorageClass NMSIfcfgRHStorageClass;
+
+GType nms_ifcfg_rh_storage_get_type (void);
+
+struct _NMSIfcfgRHPlugin;
+
+NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_connection (struct _NMSIfcfgRHPlugin *plugin,
+                                                        const char *filename,
+                                                        NMConnection *connection_take,
+                                                        const struct timespec *mtime);
+
+NMSIfcfgRHStorage *nms_ifcfg_rh_storage_new_unhandled (struct _NMSIfcfgRHPlugin *plugin,
+                                                       const char *filename,
+                                                       const char *unmanaged_spec,
+                                                       const char *unrecognized_spec);
+
+void nms_ifcfg_rh_storage_destroy (NMSIfcfgRHStorage *self);
+
+/*****************************************************************************/
+
+gboolean nms_ifcfg_rh_storage_equal_type (const NMSIfcfgRHStorage *self_a,
+                                          const NMSIfcfgRHStorage *self_b);
+
+void nms_ifcfg_rh_storage_copy_content (NMSIfcfgRHStorage *dst,
+                                        const NMSIfcfgRHStorage *src);
+
+NMConnection *nms_ifcfg_rh_storage_steal_connection (NMSIfcfgRHStorage *self);
+
+/*****************************************************************************/
+
+static inline const char *
+nms_ifcfg_rh_storage_get_uuid_opt (const NMSIfcfgRHStorage *self)
+{
+	return nm_settings_storage_get_uuid_opt ((const NMSettingsStorage *) self);
+}
+
+static inline const char *
+nms_ifcfg_rh_storage_get_filename (const NMSIfcfgRHStorage *self)
+{
+	return nm_settings_storage_get_filename ((const NMSettingsStorage *) self);
+}
+
+#endif /* __NMS_IFCFG_RH_STORAGE_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 22c9061b..cb1fc23a 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -29,6 +28,32 @@
 
 #include "nms-ifcfg-rh-common.h"
 
+/*****************************************************************************/
+
+gboolean
+nms_ifcfg_rh_util_parse_unhandled_spec (const char *unhandled_spec,
+                                        const char **out_unmanaged_spec,
+                                        const char **out_unrecognized_spec)
+{
+	if (unhandled_spec) {
+		if (NM_STR_HAS_PREFIX (unhandled_spec, "unmanaged:")) {
+			NM_SET_OUT (out_unmanaged_spec, &unhandled_spec[NM_STRLEN ("unmanaged:")]);
+			NM_SET_OUT (out_unrecognized_spec, NULL);
+			return TRUE;
+		}
+		if (NM_STR_HAS_PREFIX (unhandled_spec, "unrecognized:")) {
+			NM_SET_OUT (out_unmanaged_spec, NULL);
+			NM_SET_OUT (out_unrecognized_spec, &unhandled_spec[NM_STRLEN ("unrecognized:")]);
+			return TRUE;
+		}
+	}
+	NM_SET_OUT (out_unmanaged_spec, NULL);
+	NM_SET_OUT (out_unrecognized_spec, NULL);
+	return FALSE;
+}
+
+/*****************************************************************************/
+
 /*
  * Check ';[a-fA-F0-9]{8}' file suffix used for temporary files by rpm when
  * installing packages.
@@ -317,36 +342,41 @@ utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg)
 char *
 utils_detect_ifcfg_path (const char *path, gboolean only_ifcfg)
 {
-	gs_free char *base = NULL;
-	char *ptr, *ifcfg = NULL;
+	const char *base;
 
 	g_return_val_if_fail (path != NULL, NULL);
 
 	if (utils_should_ignore_file (path, only_ifcfg))
 		return NULL;
 
-	base = g_path_get_basename (path);
+	base = strrchr (path, '/');
+	if (!base)
+		base = path;
+	else
+		base += 1;
 
-	if (strncmp (base, IFCFG_TAG, NM_STRLEN (IFCFG_TAG)) == 0) {
+	if (NM_STR_HAS_PREFIX (base, IFCFG_TAG)) {
 		if (base[NM_STRLEN (IFCFG_TAG)] == '\0')
 			return NULL;
 		if (utils_is_ifcfg_alias_file (base, NULL)) {
+			gs_free char *ifcfg = NULL;
+			char *ptr;
+
 			ifcfg = g_strdup (path);
 			ptr = strrchr (ifcfg, ':');
-			if (ptr && ptr > ifcfg) {
+			if (   ptr
+			    && ptr > ifcfg
+			    && !strchr (ptr, '/')) {
 				*ptr = '\0';
 				if (g_file_test (ifcfg, G_FILE_TEST_EXISTS)) {
 					/* the file has a colon, so it is probably an alias.
 					 * To be ~more~ certain that this is an alias file,
 					 * check whether a corresponding base file exists. */
-					if (only_ifcfg) {
-						g_free (ifcfg);
+					if (only_ifcfg)
 						return NULL;
-					}
-					return ifcfg;
+					return g_steal_pointer (&ifcfg);
 				}
 			}
-			g_free (ifcfg);
 		}
 		return g_strdup (path);
 	}
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 c7729df5..20d6f72d 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -26,6 +25,10 @@
 
 #include "shvar.h"
 
+gboolean nms_ifcfg_rh_util_parse_unhandled_spec (const char *unhandled_spec,
+                                                 const char **out_unmanaged_spec,
+                                                 const char **out_unrecognized_spec);
+
 #define NM_IFCFG_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
 #define NM_IFCFG_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
 #define NM_IFCFG_CONNECTION_LOG_ARG(con)    NM_IFCFG_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con))
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 80b1bffe..38dc5c8d 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -177,30 +176,18 @@ typedef struct {
 } Setting8021xSchemeVtable;
 
 static const Setting8021xSchemeVtable setting_8021x_scheme_vtable[] = {
-	[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_CA_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_INNER_CA_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_CLIENT_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT],
-		.ifcfg_rh_key           = "IEEE_8021X_INNER_CLIENT_CERT",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY],
-		.ifcfg_rh_key           = "IEEE_8021X_PRIVATE_KEY",
-	},
-	[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY] = {
-		.vtable                 = &nm_setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY],
-		.ifcfg_rh_key           = "IEEE_8021X_INNER_PRIVATE_KEY",
-	},
+#define _D(_scheme_type, _ifcfg_rh_key) \
+	[(_scheme_type)] = { \
+		.vtable       = &nm_setting_8021x_scheme_vtable[(_scheme_type)], \
+		.ifcfg_rh_key = ""_ifcfg_rh_key"", \
+	}
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT,            "IEEE_8021X_CA_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT,     "IEEE_8021X_INNER_CA_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT,        "IEEE_8021X_CLIENT_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, "IEEE_8021X_INNER_CLIENT_CERT"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY,        "IEEE_8021X_PRIVATE_KEY"),
+	_D (NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, "IEEE_8021X_INNER_PRIVATE_KEY"),
+#undef _D
 };
 
 static gboolean
@@ -209,6 +196,7 @@ write_object (NMSetting8021x *s_8021x,
               GHashTable *secrets,
               GHashTable *blobs,
               const Setting8021xSchemeVtable *objtype,
+              gboolean force_write,
               GError **error)
 {
 	NMSetting8021xCKScheme scheme;
@@ -216,7 +204,8 @@ write_object (NMSetting8021x *s_8021x,
 	GBytes *blob = NULL;
 	const char *password = NULL;
 	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
-	char *secret_name, *secret_flags;
+	char secret_name[100];
+	char secret_flags[sizeof (secret_name) + NM_STRLEN ("_FLAGS")];
 	const char *extension;
 	char *standard_file;
 
@@ -243,13 +232,11 @@ write_object (NMSetting8021x *s_8021x,
 	}
 
 	/* Set the password for certificate/private key. */
-	secret_name = g_strdup_printf ("%s_PASSWORD", objtype->ifcfg_rh_key);
-	secret_flags = g_strdup_printf ("%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key);
+	nm_sprintf_buf (secret_name, "%s_PASSWORD", objtype->ifcfg_rh_key);
+	nm_sprintf_buf (secret_flags, "%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key);
 	password = (*(objtype->vtable->passwd_func))(s_8021x);
 	flags = (*(objtype->vtable->pwflag_func))(s_8021x);
 	set_secret (ifcfg, secrets, secret_name, password, secret_flags, flags);
-	g_free (secret_name);
-	g_free (secret_flags);
 
 	if (!objtype->vtable->format_func)
 		extension = "der";
@@ -287,7 +274,7 @@ write_object (NMSetting8021x *s_8021x,
 	 */
 	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);
+	svSetValue (ifcfg, objtype->ifcfg_rh_key, force_write ? "" : NULL);
 	return TRUE;
 }
 
@@ -338,43 +325,41 @@ write_8021x_certs (NMSetting8021x *s_8021x,
                    shvarFile *ifcfg,
                    GError **error)
 {
-	const Setting8021xSchemeVtable *otype = NULL;
+	const Setting8021xSchemeVtable *pk_otype = NULL;
+	gs_free char *value_to_free = NULL;
 
 	/* CA certificate */
 	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],
+	                   FALSE,
 	                   error))
 		return FALSE;
 
 	/* Private key */
 	if (phase2)
-		otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY];
+		pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY];
 	else
-		otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY];
+		pk_otype = &setting_8021x_scheme_vtable[NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY];
 
 	/* Save the private key */
-	if (!write_object (s_8021x, ifcfg, secrets, blobs, otype, error))
+	if (!write_object (s_8021x, ifcfg, secrets, blobs, pk_otype, FALSE, error))
 		return FALSE;
 
-	/* Client certificate */
-	if (otype->vtable->format_func (s_8021x) == NM_SETTING_802_1X_CK_FORMAT_PKCS12) {
-		/* Don't need a client certificate with PKCS#12 since the file is both
-		 * the client certificate and the private key in one file.
-		 */
-		svSetValueStr (ifcfg,
-		               phase2 ? "IEEE_8021X_INNER_CLIENT_CERT" : "IEEE_8021X_CLIENT_CERT",
-		               NULL);
-	} else {
-		/* Save the client certificate */
-		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],
-		                   error))
-			return FALSE;
-	}
+	/* Save the client certificate.
+	 * If there is a private key, always write a property for the
+	 * client certificate even if it is empty, so that the reader
+	 * doesn't have to read the private key file to determine if it
+	 * is a PKCS #12 one which serves also as client certificate.
+	 */
+	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],
+	                   !!svGetValue (ifcfg, pk_otype->ifcfg_rh_key, &value_to_free),
+	                   error))
+		return FALSE;
 
 	return TRUE;
 }
@@ -1118,14 +1103,22 @@ write_wired_setting (NMConnection *connection, shvarFile *ifcfg, GError **error)
 			nm_setting_wired_get_s390_option (s_wired, i, &s390_key, &s390_val);
 
 			/* portname is handled separately */
-			if (!strcmp (s390_key, "portname") || !strcmp (s390_key, "ctcprot"))
+			if (NM_IN_STRSET (s390_key, "portname", "ctcprot"))
 				continue;
 
+			if (strchr (s390_key, '=')) {
+				/* this key cannot be expressed. But after all, it's not valid anyway
+				 * and the connection shouldn't even verify. */
+				continue;
+			}
+
 			if (!tmp)
 				tmp = g_string_sized_new (30);
 			else
 				g_string_append_c (tmp, ' ');
-			g_string_append_printf (tmp, "%s=%s", s390_key, s390_val);
+			nm_utils_escaped_tokens_escape_gstr (s390_key, NM_ASCII_SPACES, tmp);
+			g_string_append_c (tmp, '=');
+			nm_utils_escaped_tokens_escape_gstr (s390_val, NM_ASCII_SPACES, tmp);
 		}
 		if (tmp)
 			svSetValueStr (ifcfg, "OPTIONS", tmp->str);
@@ -1302,7 +1295,7 @@ write_wired_for_virtual (NMConnection *connection, shvarFile *ifcfg)
 		has_wired = TRUE;
 
 		device_mac = nm_setting_wired_get_mac_address (s_wired);
-		svSetValueStr (ifcfg, "HWADDR", device_mac);
+		svSetValue (ifcfg, "HWADDR", device_mac ?: "");
 
 		cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
 		svSetValueStr (ifcfg, "MACADDR", cloned_mac);
@@ -1856,6 +1849,7 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	GString *str;
 	const char *master, *master_iface = NULL, *type;
 	int vint;
+	gint32 vint32;
 	NMSettingConnectionMdns mdns;
 	NMSettingConnectionLlmnr llmnr;
 	guint32 vuint32;
@@ -2022,6 +2016,19 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 	vint = nm_setting_connection_get_auth_retries (s_con);
 	svSetValueInt64_cond (ifcfg, "AUTH_RETRIES", vint >= 0, vint);
 
+	vint32 = nm_setting_connection_get_wait_device_timeout (s_con);
+	if (vint32 == -1)
+		svUnsetValue (ifcfg, "DEVTIMEOUT");
+	else if ((vint32 % 1000) == 0)
+		svSetValueInt64 (ifcfg, "DEVTIMEOUT", vint32 / 1000);
+	else {
+		char b[100];
+
+		svSetValueStr (ifcfg,
+		               "DEVTIMEOUT",
+		               nm_sprintf_buf (b, "%.3f", ((double) vint) / 1000.0));
+	}
+
 	mdns = nm_setting_connection_get_mdns (s_con);
 	if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
 		svSetValueEnum (ifcfg, "MDNS", nm_setting_connection_mdns_get_type (),
@@ -2797,10 +2804,17 @@ write_ip6_setting (NMConnection *connection,
 
 	value = nm_setting_ip_config_get_method (s_ip6);
 	g_assert (value);
+	svUnsetValue (ifcfg, "IPV6_DISABLED");
 	if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "no");
 		svUnsetValue (ifcfg, "DHCPV6C");
 		return TRUE;
+	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+		svSetValueStr (ifcfg, "IPV6_DISABLED", "yes");
+		svSetValueStr (ifcfg, "IPV6INIT", "no");
+		svUnsetValue (ifcfg, "DHCPV6C");
+		svUnsetValue (ifcfg, "IPV6_AUTOCONF");
+		return TRUE;
 	} else if (!strcmp (value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
 		svSetValueStr (ifcfg, "IPV6INIT", "yes");
 		svSetValueStr (ifcfg, "IPV6_AUTOCONF", "yes");
@@ -3024,6 +3038,8 @@ static gboolean
 do_write_construct (NMConnection *connection,
                     const char *ifcfg_dir,
                     const char *filename,
+                    NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                    gpointer allow_filename_user_data,
                     shvarFile **out_ifcfg,
                     GHashTable **out_blobs,
                     GHashTable **out_secrets,
@@ -3067,30 +3083,31 @@ do_write_construct (NMConnection *connection,
 
 		ifcfg_name = g_strdup (filename);
 	} else if (ifcfg_dir) {
-		char *escaped;
+		gs_free char *escaped = NULL;
+		int i_path;
 
 		escaped = escape_id (nm_setting_connection_get_id (s_con));
-		ifcfg_name = g_strdup_printf ("%s/ifcfg-%s", ifcfg_dir, escaped);
 
-		/* If a file with this path already exists then we need another name.
-		 * Multiple connections can have the same ID (ie if two connections with
-		 * the same ID are visible to different users) but of course can't have
-		 * the same path.
-		 */
-		if (g_file_test (ifcfg_name, G_FILE_TEST_EXISTS)) {
-			guint32 idx = 0;
-
-			nm_clear_g_free (&ifcfg_name);
-			while (idx++ < 500) {
-				ifcfg_name = g_strdup_printf ("%s/ifcfg-%s-%u", ifcfg_dir, escaped, idx);
-				if (g_file_test (ifcfg_name, G_FILE_TEST_EXISTS) == FALSE)
-					break;
-				nm_clear_g_free (&ifcfg_name);
-			}
+		for (i_path = 0; i_path < 10000; i_path++) {
+			gs_free char *path_candidate = NULL;
+
+			if (i_path == 0)
+				path_candidate = g_strdup_printf ("%s/ifcfg-%s", ifcfg_dir, escaped);
+			else
+				path_candidate = g_strdup_printf ("%s/ifcfg-%s-%d", ifcfg_dir, escaped, i_path);
+
+			if (   allow_filename_cb
+			    && !allow_filename_cb (path_candidate, allow_filename_user_data))
+				continue;
+
+			if (g_file_test (path_candidate, G_FILE_TEST_EXISTS))
+				continue;
+
+			ifcfg_name = g_steal_pointer (&path_candidate);
+			break;
 		}
-		g_free (escaped);
 
-		if (ifcfg_name == NULL) {
+		if (!ifcfg_name) {
 			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
 			                     "Failed to find usable ifcfg file name");
 			return FALSE;
@@ -3329,54 +3346,12 @@ do_write_to_disk (NMConnection *connection,
 	return TRUE;
 }
 
-static gboolean
-do_write_reread (NMConnection *connection,
-                 const char *ifcfg_name,
-                 NMConnection **out_reread,
-                 gboolean *out_reread_same,
-                 GError **error)
-{
-	gs_unref_object NMConnection *reread = NULL;
-	gs_free_error GError *local = NULL;
-	gs_free char *unhandled = NULL;
-	gboolean reread_same = FALSE;
-
-	nm_assert (!out_reread || !*out_reread);
-
-	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
 nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
                                       const char *ifcfg_dir,
                                       const char *filename,
+                                      NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                                      gpointer allow_filename_user_data,
                                       char **out_filename,
                                       NMConnection **out_reread,
                                       gboolean *out_reread_same,
@@ -3389,13 +3364,14 @@ nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
 	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,
+	                         allow_filename_cb,
+	                         allow_filename_user_data,
 	                         &ifcfg,
 	                         &blobs,
 	                         &secrets,
@@ -3424,28 +3400,46 @@ nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
 
 	/* Note that we just wrote the connection to disk, and re-read it from there.
 	 * That is racy if somebody else modifies the connection.
+	 * That race is why we must not tread a failure to re-read the profile
+	 * as an error.
 	 *
-	 * 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
+	 * FIXME: a much 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)) {
+	if (   out_reread
+	    || out_reread_same) {
+		gs_unref_object NMConnection *reread = NULL;
+		gboolean reread_same = FALSE;
+		gs_free_error GError *local = NULL;
+		gs_free char *unhandled = NULL;
+
+		reread = connection_from_file (svFileGetName (ifcfg),
+		                               &unhandled,
+		                               &local,
+		                               NULL);
+		nm_assert ((NM_IS_CONNECTION (reread) && !local) || (!reread && local));
+
+		if (!reread) {
 			_LOGW ("write: failure to re-read connection \"%s\": %s",
 			       svFileGetName (ifcfg), local->message);
-			g_clear_error (&local);
+		} else if (unhandled) {
+			g_clear_object (&reread);
+			_LOGW ("write: failure to re-read connection \"%s\": %s",
+			       svFileGetName (ifcfg), "connection is unhandled");
 		} 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));
+			if (out_reread_same) {
+				reread_same = nm_connection_compare (reread, connection, NM_SETTING_COMPARE_FLAG_EXACT);
+				if (!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));
+				}
 			}
 		}
+
+		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 */
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 d7a255a9..0902daee 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -23,12 +22,18 @@
 
 #include "nm-connection.h"
 
+
+typedef gboolean (*NMSIfcfgRHWriterAllowFilenameCb) (const char *check_filename,
+                                                     gpointer allow_filename_user_data);
+
 gboolean nms_ifcfg_rh_writer_can_write_connection (NMConnection *connection,
                                                    GError **error);
 
 gboolean nms_ifcfg_rh_writer_write_connection (NMConnection *connection,
                                                const char *ifcfg_dir,
                                                const char *filename,
+                                               NMSIfcfgRHWriterAllowFilenameCb allow_filename_cb,
+                                               gpointer allow_filename_user_data,
                                                char **out_filename,
                                                NMConnection **out_reread,
                                                gboolean *out_reread_same,
diff --git a/src/settings/plugins/ifcfg-rh/shvar.c b/src/settings/plugins/ifcfg-rh/shvar.c
index b399a17f..94e31aac 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/settings/plugins/ifcfg-rh/shvar.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * shvar.c
  *
diff --git a/src/settings/plugins/ifcfg-rh/shvar.h b/src/settings/plugins/ifcfg-rh/shvar.h
index b38a8557..67fb5404 100644
--- a/src/settings/plugins/ifcfg-rh/shvar.h
+++ b/src/settings/plugins/ifcfg-rh/shvar.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /*
  * shvar.h
  *
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 5d81dfef..36df7712 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,6 +1,7 @@
 BONDING_OPTS="downdelay=5 miimon=100 mode=balance-rr updelay=10"
 TYPE=Bond
 BONDING_MASTER=yes
+HWADDR=
 PROXY_METHOD=none
 BROWSER_ONLY=no
 BOOTPROTO=none
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
index 339f8107..9c2a1ff0 100644
--- a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_VLAN_reorder_hdr.cexpected
@@ -5,6 +5,7 @@ VLAN_ID=444
 REORDER_HDR=yes
 GVRP=no
 MVRP=no
+HWADDR=
 PROXY_METHOD=none
 BROWSER_ONLY=no
 BOOTPROTO=dhcp
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 793713ea..44eb777c 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
@@ -8,6 +8,7 @@ VLAN_FLAGS=LOOSE_BINDING
 MVRP=no
 VLAN_INGRESS_PRIORITY_MAP=0:1,2:5
 VLAN_EGRESS_PRIORITY_MAP=3:1,12:3,14:7
+HWADDR=
 PROXY_METHOD=none
 BROWSER_ONLY=no
 BOOTPROTO=none
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 87980dc4..b01372af 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
@@ -4,6 +4,7 @@ VLAN_ID=142
 REORDER_HDR=yes
 GVRP=no
 MVRP=no
+HWADDR=
 NAME=team-slave-enp31s0f1-142
 UUID=74f435bb-ede4-415a-9d48-f580b60eba04
 DEVICE=enp31s0f1-142
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
new file mode 100644
index 00000000..cae51b89
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
@@ -0,0 +1,11 @@
+TYPE=Ethernet
+PROXY_METHOD=none
+BROWSER_ONLY=no
+BOOTPROTO=dhcp
+DEFROUTE=yes
+IPV4_FAILURE_FATAL=no
+IPV6_DISABLED=yes
+IPV6INIT=no
+NAME="Test Write Wired Disabled IP6"
+UUID=${UUID}
+ONBOOT=yes
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
new file mode 100644
index 00000000..24397473
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-wired-8021x-tls-p12-no-client-cert
@@ -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=TLS
+IEEE_8021X_IDENTITY="David Smith"
+IEEE_8021X_CA_CERT=test_ca_cert.pem
+IEEE_8021X_PRIVATE_KEY=test_client.p12
+IEEE_8021X_PRIVATE_KEY_PASSWORD="test1"
diff --git a/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12 b/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
new file mode 100644
index 00000000..edc2af75
--- /dev/null
+++ b/src/settings/plugins/ifcfg-rh/tests/network-scripts/test_client.p12
Binary files differdiff --git a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index 49ab04d4..45e90b91 100644
--- a/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -239,7 +238,7 @@ _assert_expected_content (NMConnection *connection, const char *filename, const
 		g_assert (_ifcfg_dir && _ifcfg_dir[0]); \
 		g_assert (_filename && _filename[0]); \
 		\
-		_success = nms_ifcfg_rh_writer_write_connection (_connection, _ifcfg_dir, _filename, NULL, _out_reread, _out_reread_same, &_error); \
+		_success = nms_ifcfg_rh_writer_write_connection (_connection, _ifcfg_dir, _filename, NULL, NULL, NULL, _out_reread, _out_reread_same, &_error); \
 		nmtst_assert_success (_success, _error); \
 		_assert_expected_content (_connection, _filename, _expected); \
 	} G_STMT_END
@@ -310,7 +309,7 @@ _writer_new_connection_reread (NMConnection *connection,
 	char *filename = NULL;
 	gs_unref_object NMConnection *con_verified = NULL;
 	gs_unref_object NMConnection *reread_copy = NULL;
-	NMConnection **reread = out_reread ?: ((nmtst_get_rand_int () % 2) ? &reread_copy : NULL);
+	NMConnection **reread = out_reread ?: ((nmtst_get_rand_uint32 () % 2) ? &reread_copy : NULL);
 
 	g_assert (NM_IS_CONNECTION (connection));
 	g_assert (ifcfg_dir);
@@ -320,6 +319,8 @@ _writer_new_connection_reread (NMConnection *connection,
 	success = nms_ifcfg_rh_writer_write_connection (con_verified,
 	                                                ifcfg_dir,
 	                                                NULL,
+	                                                NULL,
+	                                                NULL,
 	                                                &filename,
 	                                                reread,
 	                                                out_reread_same,
@@ -395,6 +396,8 @@ _writer_new_connection_fail (NMConnection *connection,
 	success = nms_ifcfg_rh_writer_write_connection (connection_normalized,
 	                                                ifcfg_dir,
 	                                                NULL,
+	                                                NULL,
+	                                                NULL,
 	                                                &filename,
 	                                                &reread,
 	                                                NULL,
@@ -718,7 +721,7 @@ test_read_unmanaged_unrecognized (void)
 	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-nm-controlled-unrecognized",
 	                                    NULL, NULL,
 	                                    &unhandled_spec);
-	g_assert_cmpstr (unhandled_spec, ==, "unmanaged:interface-name:ipoac0");
+	g_assert_cmpstr (unhandled_spec, ==, "unmanaged:interface-name:=ipoac0");
 
 	/* ===== CONNECTION SETTING ===== */
 	s_con = nm_connection_get_setting_connection (connection);
@@ -1974,6 +1977,27 @@ test_read_802_1x_ttls_eapgtc (void)
 }
 
 static void
+test_read_802_1x_tls_p12_no_client_cert (void)
+{
+	gs_unref_object NMConnection *connection = NULL;
+	NMSetting8021x *s_8021x;
+	const char *path;
+
+	connection = _connection_from_file (TEST_IFCFG_DIR"/ifcfg-test-wired-8021x-tls-p12-no-client-cert",
+	                                    NULL, TYPE_ETHERNET, NULL);
+
+	s_8021x = nm_connection_get_setting_802_1x (connection);
+	g_assert (s_8021x);
+
+	g_assert_cmpint (nm_setting_802_1x_get_private_key_scheme (s_8021x), ==, NM_SETTING_802_1X_CK_SCHEME_PATH);
+	path = nm_setting_802_1x_get_private_key_path (s_8021x);
+	g_assert (path);
+
+	g_assert_cmpint (nm_setting_802_1x_get_client_cert_scheme (s_8021x), ==, NM_SETTING_802_1X_CK_SCHEME_PATH);
+	g_assert_cmpstr (path, ==, nm_setting_802_1x_get_client_cert_path (s_8021x));
+}
+
+static void
 test_read_write_802_1x_password_raw (void)
 {
 	nmtst_auto_unlinkfile char *testfile = NULL;
@@ -4768,6 +4792,48 @@ test_write_wired_static_ip6_only (void)
 	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
 }
 
+static void
+test_write_ip6_disabled (void)
+{
+	nmtst_auto_unlinkfile char *testfile = NULL;
+	gs_unref_object NMConnection *connection = NULL;
+	gs_unref_object NMConnection *reread = NULL;
+	NMSettingConnection *s_con;
+	NMSettingWired *s_wired;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+
+	connection = nmtst_create_minimal_connection ("Test Write Wired Disabled IP6",
+	                                              NULL,
+	                                              NM_SETTING_WIRED_SETTING_NAME,
+	                                              &s_con);
+
+	s_wired = (NMSettingWired *) nm_setting_wired_new ();
+	nm_connection_add_setting (connection, NM_SETTING (s_wired));
+
+	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);
+
+	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_DISABLED,
+	              NULL);
+
+	nmtst_assert_connection_verifies (connection);
+
+	_writer_new_connec_exp (connection,
+	                        TEST_SCRATCH_DIR_TMP,
+	                        TEST_IFCFG_DIR"/ifcfg-test-ip6-disabled.cexpected",
+	                        &testfile);
+
+	reread = _connection_from_file (testfile, NULL, TYPE_ETHERNET, NULL);
+	nmtst_assert_connection_equals (connection, TRUE, reread, FALSE);
+}
+
 /* Test writing an IPv6 config with varying gateway address.
  * For missing gateway (::), we expect no IPV6_DEFAULTGW to be written
  * to ifcfg-rh.
@@ -8938,25 +9004,16 @@ static void
 test_read_team_master_invalid (gconstpointer user_data)
 {
 	const char *const PATH_NAME = user_data;
-	NMConnection *connection;
-	NMSettingConnection *s_con;
-	NMSettingTeam *s_team;
-
-	NMTST_EXPECT_NM_WARN ("*ignoring invalid team configuration*");
-	connection = _connection_from_file (PATH_NAME, NULL, TYPE_ETHERNET, NULL);
-	g_test_assert_expected_messages ();
-
-	g_assert_cmpstr (nm_connection_get_interface_name (connection), ==, "team0");
-
-	s_con = nm_connection_get_setting_connection (connection);
-	g_assert (s_con);
-	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_TEAM_SETTING_NAME);
+	gs_free_error GError *error = NULL;
+	gs_unref_object NMConnection *connection = NULL;
 
-	s_team = nm_connection_get_setting_team (connection);
-	g_assert (s_team);
-	g_assert (nm_setting_team_get_config (s_team) == NULL);
+	if (WITH_JSON_VALIDATION) {
+		_connection_from_file_fail (PATH_NAME, NULL, TYPE_ETHERNET, &error);
 
-	g_object_unref (connection);
+		g_assert_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY);
+		g_assert (strstr (error->message, _("invalid json")));
+	} else
+		connection = _connection_from_file (PATH_NAME, NULL, TYPE_ETHERNET, NULL);
 }
 
 static void
@@ -9237,19 +9294,19 @@ test_team_reread_slave (void)
 
 	nmtst_assert_connection_equals (connection_1, FALSE, connection_2, FALSE);
 
-	_writer_new_connection_reread ((nmtst_get_rand_int () % 2) ? connection_1 : connection_2,
+	_writer_new_connection_reread ((nmtst_get_rand_uint32 () % 2) ? connection_1 : connection_2,
 	                               TEST_SCRATCH_DIR,
 	                               &testfile,
 	                               TEST_IFCFG_DIR"/ifcfg-team-slave-enp31s0f1-142.cexpected",
 	                               &reread,
 	                               &reread_same);
-	_assert_reread_same ((nmtst_get_rand_int () % 2) ? connection_1 : connection_2, reread);
+	_assert_reread_same ((nmtst_get_rand_uint32 () % 2) ? connection_1 : connection_2, reread);
 	g_assert (reread_same);
 	g_clear_object (&reread);
 
 	reread = _connection_from_file (testfile, NULL, TYPE_VLAN,
 	                                NULL);
-	nmtst_assert_connection_equals ((nmtst_get_rand_int () % 2) ? connection_1 : connection_2, FALSE,
+	nmtst_assert_connection_equals ((nmtst_get_rand_uint32 () % 2) ? connection_1 : connection_2, FALSE,
 	                                reread, FALSE);
 }
 
@@ -9414,7 +9471,7 @@ do_svUnescape_combine_ansi (GString *str_val, GString *str_exp, const UnescapeTe
 	g_string_append (str_val, "$'");
 	if (idx < 0) {
 		for (i = -idx; i > 0; i--) {
-			j = nmtst_get_rand_int () % data_len;
+			j = nmtst_get_rand_uint32 () % data_len;
 			if (!data_ansi[j].can_concat) {
 				i++;
 				continue;
@@ -9613,7 +9670,7 @@ test_svUnescape (void)
 
 	/* different values can be just concatenated... */
 	for (i = 0; i < 200; i++) {
-		gsize num_concat = (nmtst_get_rand_int () % 5) + 2;
+		gsize num_concat = (nmtst_get_rand_uint32 () % 5) + 2;
 
 		g_string_set_size (str_val, 0);
 		g_string_set_size (str_exp, 0);
@@ -9621,12 +9678,12 @@ test_svUnescape (void)
 		while (num_concat > 0) {
 			gsize idx;
 
-			if ((nmtst_get_rand_int () % 3 == 0)) {
-				do_svUnescape_combine_ansi (str_val2, str_exp2, data_ansi, G_N_ELEMENTS (data_ansi), -((int) ((nmtst_get_rand_int () % 5) + 1)));
+			if ((nmtst_get_rand_uint32 () % 3 == 0)) {
+				do_svUnescape_combine_ansi (str_val2, str_exp2, data_ansi, G_N_ELEMENTS (data_ansi), -((int) ((nmtst_get_rand_uint32 () % 5) + 1)));
 				continue;
 			}
 
-			idx = nmtst_get_rand_int () % G_N_ELEMENTS (data_full);
+			idx = nmtst_get_rand_uint32 () % G_N_ELEMENTS (data_full);
 			if (!data_full[idx].can_concat)
 				continue;
 			g_string_append (str_val, data_full[idx].val);
@@ -9634,7 +9691,7 @@ test_svUnescape (void)
 			num_concat--;
 		}
 
-		switch (nmtst_get_rand_int () % 3) {
+		switch (nmtst_get_rand_uint32 () % 3) {
 		case 0:
 			g_string_append (str_val, " ");
 			break;
@@ -9642,7 +9699,7 @@ test_svUnescape (void)
 			g_string_append (str_val, "    ");
 			break;
 		}
-		switch (nmtst_get_rand_int () % 3) {
+		switch (nmtst_get_rand_uint32 () % 3) {
 		case 0:
 			g_string_append (str_val, " #");
 			break;
@@ -10177,6 +10234,7 @@ int main (int argc, char **argv)
 	g_test_add_data_func (TPATH "static-ip6-only-gw/::", "::", test_write_wired_static_ip6_only_gw);
 	g_test_add_data_func (TPATH "static-ip6-only-gw/2001:db8:8:4::2", "2001:db8:8:4::2", test_write_wired_static_ip6_only_gw);
 	g_test_add_data_func (TPATH "static-ip6-only-gw/::ffff:255.255.255.255", "::ffff:255.255.255.255", test_write_wired_static_ip6_only_gw);
+	g_test_add_func (TPATH "ip6/disabled", test_write_ip6_disabled);
 	g_test_add_func (TPATH "read-dns-options", test_read_dns_options);
 	g_test_add_func (TPATH "clear-master", test_clear_master);
 
@@ -10223,6 +10281,8 @@ 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_func (TPATH "802-1x/tls-p12-no-client-cert", test_read_802_1x_tls_p12_no_client_cert);
+
 	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);
diff --git a/src/settings/plugins/ifupdown/meson.build b/src/settings/plugins/ifupdown/meson.build
index 42edd438..365ae1a9 100644
--- a/src/settings/plugins/ifupdown/meson.build
+++ b/src/settings/plugins/ifupdown/meson.build
@@ -15,7 +15,6 @@ libnms_ifupdown_core = static_library(
 )
 
 sources = files(
-  'nms-ifupdown-connection.c',
   'nms-ifupdown-plugin.c',
 )
 
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c b/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
deleted file mode 100644
index d06078a9..00000000
--- a/src/settings/plugins/ifupdown/nms-ifupdown-connection.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service (ifupdown)
- *
- * Alexander Sack <asac@ubuntu.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,2008 Canonical Ltd.
- */
-
-#include "nm-default.h"
-
-#include "nms-ifupdown-connection.h"
-
-#include <glib/gstdio.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-utils.h"
-#include "nm-setting-wireless-security.h"
-#include "settings/nm-settings-connection.h"
-#include "settings/nm-settings-plugin.h"
-
-#include "nms-ifupdown-parser.h"
-
-/*****************************************************************************/
-
-struct _NMIfupdownConnection {
-	NMSettingsConnection parent;
-};
-
-struct _NMIfupdownConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMIfupdownConnection, nm_ifupdown_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME      "ifupdown"
-#define _NMLOG_DOMAIN           LOGD_SETTINGS
-#define _NMLOG(level, ...) \
-    nm_log ((level), _NMLOG_DOMAIN, NULL, NULL, \
-            "%s" _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
-            _NMLOG_PREFIX_NAME": " \
-            _NM_UTILS_MACRO_REST (__VA_ARGS__))
-
-/*****************************************************************************/
-
-static gboolean
-supports_secrets (NMSettingsConnection *connection, const char *setting_name)
-{
-	_LOGI ("supports_secrets() for setting_name: '%s'", setting_name);
-
-	return (strcmp (setting_name, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME) == 0);
-}
-
-/*****************************************************************************/
-
-static void
-nm_ifupdown_connection_init (NMIfupdownConnection *connection)
-{
-}
-
-NMIfupdownConnection *
-nm_ifupdown_connection_new (if_block *block)
-{
-	NMIfupdownConnection *connection;
-	GError *error = NULL;
-
-	g_return_val_if_fail (block != NULL, NULL);
-
-	connection = g_object_new (NM_TYPE_IFUPDOWN_CONNECTION, NULL);
-
-	/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-	if (!ifupdown_update_connection_from_if_block (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection)),
-	                                               block,
-	                                               &error)) {
-		_LOGW ("invalid connection read from /etc/network/interfaces: %s",
-		       error->message);
-		g_object_unref (connection);
-		return NULL;
-	}
-
-	return connection;
-}
-
-static void
-nm_ifupdown_connection_class_init (NMIfupdownConnectionClass *ifupdown_connection_class)
-{
-	NMSettingsConnectionClass *connection_class = NM_SETTINGS_CONNECTION_CLASS (ifupdown_connection_class);
-
-	connection_class->supports_secrets = supports_secrets;
-}
-
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-connection.h b/src/settings/plugins/ifupdown/nms-ifupdown-connection.h
deleted file mode 100644
index 7536c23e..00000000
--- a/src/settings/plugins/ifupdown/nms-ifupdown-connection.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
-/* NetworkManager system settings service (ifupdown)
- *
- * Alexander Sack <asac@ubuntu.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 2008 Canonical Ltd.
- */
-
-#ifndef __NETWORKMANAGER_IFUPDOWN_CONNECTION_H__
-#define __NETWORKMANAGER_IFUPDOWN_CONNECTION_H__
-
-#include "settings/nm-settings-connection.h"
-
-#include "nms-ifupdown-interface-parser.h"
-
-#define NM_TYPE_IFUPDOWN_CONNECTION            (nm_ifupdown_connection_get_type ())
-#define NM_IFUPDOWN_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_IFUPDOWN_CONNECTION, NMIfupdownConnection))
-#define NM_IFUPDOWN_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_IFUPDOWN_CONNECTION, NMIfupdownConnectionClass))
-#define NM_IS_IFUPDOWN_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_IFUPDOWN_CONNECTION))
-#define NM_IS_IFUPDOWN_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_IFUPDOWN_CONNECTION))
-#define NM_IFUPDOWN_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_IFUPDOWN_CONNECTION, NMIfupdownConnectionClass))
-
-typedef struct _NMIfupdownConnection NMIfupdownConnection;
-typedef struct _NMIfupdownConnectionClass NMIfupdownConnectionClass;
-
-GType nm_ifupdown_connection_get_type (void);
-
-NMIfupdownConnection *nm_ifupdown_connection_new (if_block *block);
-
-#endif /* __NETWORKMANAGER_IFUPDOWN_CONNECTION_H__ */
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index 6587fc84..75f29878 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * Tom Parker <palfrey@tevp.net>
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
index f367f626..308228a4 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-interface-parser.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager -- Network link manager
  *
  * Tom Parker <palfrey@tevp.net>
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
index 239f6415..41b20850 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -653,22 +651,21 @@ update_ip6_setting_from_if_block (NMConnection *connection,
 	return TRUE;
 }
 
-gboolean
-ifupdown_update_connection_from_if_block (NMConnection *connection,
-                                          if_block *block,
-                                          GError **error)
+NMConnection *
+ifupdown_new_connection_from_if_block (if_block *block,
+                                       gboolean autoconnect,
+                                       GError **error)
 {
+	gs_unref_object NMConnection *connection = NULL;
 	const char *type;
 	gs_free char *idstr = NULL;
 	gs_free char *uuid = NULL;
 	NMSettingConnection *s_con;
-	gboolean success = FALSE;
 
-	s_con = nm_connection_get_setting_connection (connection);
-	if (!s_con) {
-		s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
-		nm_connection_add_setting (connection, NM_SETTING (s_con));
-	}
+	connection = nm_simple_connection_new ();
+
+	s_con = NM_SETTING_CONNECTION (nm_setting_connection_new ());
+	nm_connection_add_setting (connection, NM_SETTING (s_con));
 
 	type = _ifupdownplugin_guess_connection_type (block);
 	idstr = g_strconcat ("Ifupdown (", block->name, ")", NULL);
@@ -679,11 +676,10 @@ ifupdown_update_connection_from_if_block (NMConnection *connection,
 	              NM_SETTING_CONNECTION_INTERFACE_NAME, block->name,
 	              NM_SETTING_CONNECTION_ID, idstr,
 	              NM_SETTING_CONNECTION_UUID, uuid,
-	              NM_SETTING_CONNECTION_READ_ONLY, TRUE,
-	              NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
+	              NM_SETTING_CONNECTION_AUTOCONNECT, (gboolean) (!!autoconnect),
 	              NULL);
 
-	_LOGI ("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s",
+	_LOGD ("update_connection_setting_from_if_block: name:%s, type:%s, id:%s, uuid: %s",
 	       block->name, type, idstr, nm_setting_connection_get_uuid (s_con));
 
 	if (nm_streq (type, NM_SETTING_WIRED_SETTING_NAME))
@@ -693,13 +689,16 @@ ifupdown_update_connection_from_if_block (NMConnection *connection,
 		update_wireless_security_setting_from_if_block (connection, block);
 	}
 
-	if (ifparser_haskey (block, "inet6"))
-		success = update_ip6_setting_from_if_block (connection, block, error);
-	else
-		success = update_ip4_setting_from_if_block (connection, block, error);
+	if (ifparser_haskey (block, "inet6")) {
+		if (!update_ip6_setting_from_if_block (connection, block, error))
+			return FALSE;
+	} else {
+		if (!update_ip4_setting_from_if_block (connection, block, error))
+			return FALSE;
+	}
 
-	if (success == TRUE)
-		success = nm_connection_verify (connection, error);
+	if (!nm_connection_normalize (connection, NULL, NULL, error))
+		return NULL;
 
-	return success;
+	return g_steal_pointer (&connection);
 }
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-parser.h b/src/settings/plugins/ifupdown/nms-ifupdown-parser.h
index 6a86bf86..7569648f 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-parser.h
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-parser.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -21,16 +19,14 @@
  * (C) Copyright 2008 Canonical Ltd.
  */
 
-#ifndef __PARSER_H__
-#define __PARSER_H__
+#ifndef __NMS_IFUPDOWN_PARSER_H__
+#define __NMS_IFUPDOWN_PARSER_H__
 
 #include "nm-connection.h"
-
 #include "nms-ifupdown-interface-parser.h"
 
-gboolean
-ifupdown_update_connection_from_if_block (NMConnection *connection,
-                                          if_block *block,
-                                          GError **error);
+NMConnection *ifupdown_new_connection_from_if_block (if_block *block,
+                                                     gboolean autoconnect,
+                                                     GError **error);
 
-#endif /* __PARSER_H__ */
+#endif /* __NMS_IFUPDOWN_PARSER_H__ */
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 04281f00..93f1813c 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -26,23 +24,13 @@
 
 #include "nms-ifupdown-plugin.h"
 
-#include <arpa/inet.h>
-#include <gmodule.h>
-
-#include "nm-setting-connection.h"
-#include "nm-dbus-interface.h"
-#include "settings/nm-settings-plugin.h"
-#include "nm-setting-ip4-config.h"
-#include "nm-setting-wireless.h"
-#include "nm-setting-wired.h"
-#include "nm-setting-ppp.h"
-#include "nm-utils.h"
 #include "nm-core-internal.h"
-#include "NetworkManagerUtils.h"
+#include "nm-core-utils.h"
 #include "nm-config.h"
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
 
 #include "nms-ifupdown-interface-parser.h"
-#include "nms-ifupdown-connection.h"
 #include "nms-ifupdown-parser.h"
 
 #define ENI_INTERFACES_FILE "/etc/network/interfaces"
@@ -52,31 +40,35 @@
 /*****************************************************************************/
 
 typedef struct {
+	NMConnection *connection;
+	NMSettingsStorage *storage;
+} StorageData;
+
+typedef struct {
 	/* Stores an entry for blocks/interfaces read from /e/n/i and (if exists)
-	 * the NMIfupdownConnection associated with the block.
+	 * the StorageData associated with the block.
 	 */
 	GHashTable *eni_ifaces;
 
-	bool ifupdown_managed;
-} SettingsPluginIfupdownPrivate;
+	bool ifupdown_managed:1;
 
-struct _SettingsPluginIfupdown {
+	bool initialized:1;
+
+	bool already_reloaded:1;
+} NMSIfupdownPluginPrivate;
+
+struct _NMSIfupdownPlugin {
 	NMSettingsPlugin parent;
-	SettingsPluginIfupdownPrivate _priv;
+	NMSIfupdownPluginPrivate _priv;
 };
 
-struct _SettingsPluginIfupdownClass {
+struct _NMSIfupdownPluginClass {
 	NMSettingsPluginClass parent;
 };
 
-G_DEFINE_TYPE (SettingsPluginIfupdown, settings_plugin_ifupdown, NM_TYPE_SETTINGS_PLUGIN)
-
-#define SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, SettingsPluginIfupdown, SETTINGS_IS_PLUGIN_IFUPDOWN)
+G_DEFINE_TYPE (NMSIfupdownPlugin, nms_ifupdown_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
-/*****************************************************************************/
-
-static SettingsPluginIfupdown *settings_plugin_ifupdown_get (void);
-NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfupdown, settings_plugin_ifupdown_get, SETTINGS_TYPE_PLUGIN_IFUPDOWN);
+#define NMS_IFUPDOWN_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSIfupdownPlugin, NMS_IS_IFUPDOWN_PLUGIN)
 
 /*****************************************************************************/
 
@@ -90,46 +82,146 @@ NM_DEFINE_SINGLETON_GETTER (SettingsPluginIfupdown, settings_plugin_ifupdown_get
 
 /*****************************************************************************/
 
-/* Returns the plugins currently known list of connections.  The returned
- * list is freed by the system settings service.
- */
-static GSList*
-get_connections (NMSettingsPlugin *plugin)
+static GHashTable *load_eni_ifaces (NMSIfupdownPlugin *self);
+
+/*****************************************************************************/
+
+static void
+_storage_data_destroy (StorageData *sd)
+{
+	nm_g_object_unref (sd->connection);
+	nm_g_object_unref (sd->storage);
+	g_slice_free (StorageData, sd);
+}
+
+/*****************************************************************************/
+
+static void
+initialize (NMSIfupdownPlugin *self)
 {
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	GSList *list = NULL;
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (self);
+	gboolean ifupdown_managed;
+
+	nm_assert (!priv->initialized);
+
+	priv->initialized = TRUE;
+
+	ifupdown_managed = nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
+	                                                     NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
+	                                                     NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
+	                                                     !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT);
+	_LOGI ("management mode: %s", ifupdown_managed ? "managed" : "unmanaged");
+	priv->ifupdown_managed = ifupdown_managed;
+
+	priv->eni_ifaces = load_eni_ifaces (self);
+}
+
+static void
+reload_connections (NMSettingsPlugin *plugin,
+                    NMSettingsPluginConnectionLoadCallback callback,
+                    gpointer user_data)
+{
+	NMSIfupdownPlugin *self = NMS_IFUPDOWN_PLUGIN (plugin);
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *eni_ifaces_old = NULL;
 	GHashTableIter iter;
-	void *value;
+	StorageData *sd;
+	StorageData *sd2;
+	const char *block_name;
 
-	if (!priv->ifupdown_managed) {
-		_LOGD ("get_connections: not connections due to managed=false");
-		return NULL;
+	if (!priv->initialized)
+		initialize (self);
+	else if (!priv->already_reloaded) {
+		/* This is the first call to reload, but we are already initialized.
+		 *
+		 * This happens because during start NMSettings first queries unmanaged-specs,
+		 * and then issues a reload call right away.
+		 *
+		 * On future reloads, we really want to load /e/n/i again. */
+		priv->already_reloaded = TRUE;
+	} else {
+		eni_ifaces_old = priv->eni_ifaces;
+		priv->eni_ifaces = load_eni_ifaces (self);
+
+		g_hash_table_iter_init (&iter, eni_ifaces_old);
+		while (g_hash_table_iter_next (&iter, (gpointer *) &block_name, (gpointer *) &sd)) {
+			if (!sd)
+				continue;
+
+			sd2 = g_hash_table_lookup (priv->eni_ifaces, block_name);
+			if (!sd2)
+				continue;
+
+			nm_assert (nm_streq (nm_settings_storage_get_uuid (sd->storage), nm_settings_storage_get_uuid (sd2->storage)));
+			nm_g_object_ref_set (&sd2->storage, sd->storage);
+			g_hash_table_iter_remove (&iter);
+		}
 	}
 
+	if (!priv->ifupdown_managed)
+		_LOGD ("load: no connections due to managed=false");
+
 	g_hash_table_iter_init (&iter, priv->eni_ifaces);
-	while (g_hash_table_iter_next (&iter, NULL, &value)) {
-		if (value)
-			list = g_slist_prepend (list, value);
+	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &sd)) {
+		gs_unref_object NMConnection *connection = NULL;
+
+		if (!sd)
+			continue;
+
+		connection = g_steal_pointer (&sd->connection);
+
+		if (!priv->ifupdown_managed)
+			continue;
+
+		_LOGD ("load: %s (%s)",
+		        nm_settings_storage_get_uuid (sd->storage),
+		        nm_connection_get_id (connection));
+		callback (plugin,
+		          sd->storage,
+		          connection,
+		          user_data);
 	}
+	if (   eni_ifaces_old
+	    && priv->ifupdown_managed) {
+		g_hash_table_iter_init (&iter, eni_ifaces_old);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &sd)) {
+			if (!sd)
+				continue;
+			_LOGD ("unload: %s",
+			        nm_settings_storage_get_uuid (sd->storage));
+			callback (plugin,
+			          sd->storage,
+			          NULL,
+			          user_data);
+		}
+	}
+}
+
+/*****************************************************************************/
 
-	_LOGD ("get_connections: %u connections", g_slist_length (list));
-	return list;
+static GSList *
+_unmanaged_specs (GHashTable *eni_ifaces)
+{
+	gs_free const char **keys = NULL;
+	GSList *specs = NULL;
+	guint i, len;
+
+	keys = nm_utils_strdict_get_keys (eni_ifaces, TRUE, &len);
+	for (i = len; i > 0; ) {
+		i--;
+		specs = g_slist_prepend (specs, g_strdup_printf (NM_MATCH_SPEC_INTERFACE_NAME_TAG"=%s", keys[i]));
+	}
+	return specs;
 }
 
-/*
- * Return a list of device specifications which NetworkManager should not
- * manage.  Returned list will be freed by the system settings service, and
- * each element must be allocated using g_malloc() or its variants.
- */
 static GSList*
 get_unmanaged_specs (NMSettingsPlugin *plugin)
 {
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-	GSList *specs = NULL;
-	GHashTableIter iter;
-	const char *iface;
+	NMSIfupdownPlugin *self = NMS_IFUPDOWN_PLUGIN (plugin);
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (!priv->initialized))
+		initialize (self);
 
 	if (priv->ifupdown_managed)
 		return NULL;
@@ -137,38 +229,46 @@ get_unmanaged_specs (NMSettingsPlugin *plugin)
 	_LOGD ("unmanaged-specs: unmanaged devices count %u",
 	       g_hash_table_size (priv->eni_ifaces));
 
-	g_hash_table_iter_init (&iter, priv->eni_ifaces);
-	while (g_hash_table_iter_next (&iter, (gpointer) &iface, NULL))
-		specs = g_slist_append (specs, g_strdup_printf ("interface-name:=%s", iface));
-	return specs;
+	return _unmanaged_specs (priv->eni_ifaces);
 }
 
 /*****************************************************************************/
 
-static void
-initialize (NMSettingsPlugin *plugin)
+static GHashTable *
+load_eni_ifaces (NMSIfupdownPlugin *self)
 {
-	SettingsPluginIfupdown *self = SETTINGS_PLUGIN_IFUPDOWN (plugin);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
+	gs_unref_hashtable GHashTable *eni_ifaces = NULL;
 	gs_unref_hashtable GHashTable *auto_ifaces = NULL;
 	nm_auto_ifparser if_parser *parser = NULL;
 	if_block *block;
-	GHashTableIter con_iter;
-	const char *block_name;
-	NMIfupdownConnection *conn;
+	StorageData *sd;
+
+	eni_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) _storage_data_destroy);
 
 	parser = ifparser_parse (ENI_INTERFACES_FILE, 0);
 
 	c_list_for_each_entry (block, &parser->block_lst_head, block_lst) {
-
-		if (NM_IN_STRSET (block->type, "auto", "allow-hotplug")) {
+		if (NM_IN_STRSET (block->type, "auto",
+		                               "allow-hotplug")) {
 			if (!auto_ifaces)
-				auto_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
-			g_hash_table_add (auto_ifaces, g_strdup (block->name));
-			continue;
+				auto_ifaces = g_hash_table_new (nm_str_hash, g_str_equal);
+			g_hash_table_add (auto_ifaces, (char *) block->name);
 		}
+	}
+
+	c_list_for_each_entry (block, &parser->block_lst_head, block_lst) {
+
+		if (NM_IN_STRSET (block->type, "auto",
+		                               "allow-hotplug"))
+			continue;
 
 		if (nm_streq (block->type, "iface")) {
+			gs_free_error GError *local = NULL;
+			gs_unref_object NMConnection *connection = NULL;
+			gs_unref_object NMSettingsStorage *storage = NULL;
+			const char *uuid = NULL;
+			StorageData *sd_repl;
+
 			/* Bridge configuration */
 			if (g_str_has_prefix (block->name, "br")) {
 				/* Try to find bridge ports */
@@ -201,13 +301,13 @@ initialize (NMSettingsPlugin *plugin)
 						if (nm_streq (token, "none"))
 							continue;
 						if (state == 0) {
-							conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
-							if (!conn) {
+							sd = g_hash_table_lookup (eni_ifaces, block->name);
+							if (!sd) {
 								_LOGD ("parse: adding bridge port \"%s\"", token);
-								g_hash_table_insert (priv->eni_ifaces, g_strdup (token), NULL);
+								g_hash_table_insert (eni_ifaces, g_strdup (token), NULL);
 							} else {
 								_LOGD ("parse: adding bridge port \"%s\" (have connection %s)", token,
-								       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
+								       nm_settings_storage_get_uuid (sd->storage));
 							}
 						}
 					}
@@ -219,107 +319,91 @@ initialize (NMSettingsPlugin *plugin)
 			if (nm_streq (block->name, "lo"))
 				continue;
 
-			/* Remove any connection for this block that was previously found */
-			conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
-			if (conn) {
+			sd_repl = g_hash_table_lookup (eni_ifaces, block->name);
+			if (sd_repl) {
+				storage = g_steal_pointer (&sd_repl->storage);
 				_LOGD ("parse: replace connection \"%s\" (%s)",
 				       block->name,
-				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
-				nm_settings_connection_delete (NM_SETTINGS_CONNECTION (conn), NULL);
-				g_hash_table_remove (priv->eni_ifaces, block->name);
+				       nm_settings_storage_get_uuid (sd_repl->storage));
+				g_hash_table_remove (eni_ifaces, block->name);
+			}
+
+			connection = ifupdown_new_connection_from_if_block (block,
+			                                                       auto_ifaces
+			                                                    && g_hash_table_contains (auto_ifaces, block->name),
+			                                                    &local);
+
+			if (!connection) {
+				_LOGD ("parse: adding place holder for \"%s\"%s%s%s",
+				       block->name,
+				       NM_PRINT_FMT_QUOTED (local, " (", local->message, ")", ""));
+				sd = NULL;
+			} else {
+
+				nmtst_connection_assert_unchanging (connection);
+				uuid = nm_connection_get_uuid (connection);
+
+				if (!storage)
+					storage = nm_settings_storage_new (NM_SETTINGS_PLUGIN (self), uuid, NULL);
+
+				sd = g_slice_new (StorageData);
+				*sd = (StorageData) {
+					.connection = g_steal_pointer (&connection),
+					.storage    = g_steal_pointer (&storage),
+				};
+				_LOGD ("parse: adding connection \"%s\" (%s)", block->name, uuid);
 			}
 
-			/* add the new connection */
-			conn = nm_ifupdown_connection_new (block);
-			if (conn) {
-				_LOGD ("parse: adding connection \"%s\" (%s)", block->name,
-				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
-			} else
-				_LOGD ("parse: adding place holder for connection \"%s\"", block->name);
-			g_hash_table_insert (priv->eni_ifaces, g_strdup (block->name), conn);
+			g_hash_table_replace (eni_ifaces, g_strdup (block->name), sd);
 			continue;
 		}
 
 		if (nm_streq (block->type, "mapping")) {
-			conn = g_hash_table_lookup (priv->eni_ifaces, block->name);
-			if (!conn) {
+			sd = g_hash_table_lookup (eni_ifaces, block->name);
+			if (!sd) {
 				_LOGD ("parse: adding mapping \"%s\"", block->name);
-				g_hash_table_insert (priv->eni_ifaces, g_strdup (block->name), NULL);
+				g_hash_table_insert (eni_ifaces, g_strdup (block->name), NULL);
 			} else {
 				_LOGD ("parse: adding mapping \"%s\" (have connection %s)", block->name,
-				       nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (conn)));
+				       nm_settings_storage_get_uuid (sd->storage));
 			}
 			continue;
 		}
 	}
 
-	/* Make 'auto' interfaces autoconnect=TRUE */
-	g_hash_table_iter_init (&con_iter, priv->eni_ifaces);
-	while (g_hash_table_iter_next (&con_iter, (gpointer) &block_name, (gpointer) &conn)) {
-		NMSettingConnection *setting;
+	nm_clear_pointer (&auto_ifaces, g_hash_table_destroy);
 
-		if (   !conn
-		    || !auto_ifaces
-		    || !g_hash_table_contains (auto_ifaces, block_name))
-			continue;
-
-		/* FIXME(copy-on-write-connection): avoid modifying NMConnection instances and share them via copy-on-write. */
-		setting = nm_connection_get_setting_connection (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (conn)));
-		g_object_set (setting, NM_SETTING_CONNECTION_AUTOCONNECT, TRUE, NULL);
-	}
-
-	/* Check the config file to find out whether to manage interfaces */
-	priv->ifupdown_managed = nm_config_data_get_value_boolean (NM_CONFIG_GET_DATA_ORIG,
-	                                                           NM_CONFIG_KEYFILE_GROUP_IFUPDOWN,
-	                                                           NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED,
-	                                                           !IFUPDOWN_UNMANAGE_WELL_KNOWN_DEFAULT);
-	_LOGI ("management mode: %s", priv->ifupdown_managed ? "managed" : "unmanaged");
-
-	/* Now if we're running in managed mode, let NM know there are new connections */
-	if (priv->ifupdown_managed) {
-		GHashTableIter iter;
-
-		g_hash_table_iter_init (&iter, priv->eni_ifaces);
-		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &conn)) {
-			if (conn) {
-				_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-				                                                  NM_SETTINGS_CONNECTION (conn));
-			}
-		}
-	}
+	return g_steal_pointer (&eni_ifaces);
 }
 
 /*****************************************************************************/
 
 static void
-settings_plugin_ifupdown_init (SettingsPluginIfupdown *self)
+nms_ifupdown_plugin_init (NMSIfupdownPlugin *self)
 {
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (self);
-
-	priv->eni_ifaces = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_object_unref);
 }
 
 static void
 dispose (GObject *object)
 {
-	SettingsPluginIfupdown *plugin = SETTINGS_PLUGIN_IFUPDOWN (object);
-	SettingsPluginIfupdownPrivate *priv = SETTINGS_PLUGIN_IFUPDOWN_GET_PRIVATE (plugin);
+	NMSIfupdownPlugin *plugin = NMS_IFUPDOWN_PLUGIN (object);
+	NMSIfupdownPluginPrivate *priv = NMS_IFUPDOWN_PLUGIN_GET_PRIVATE (plugin);
 
 	g_clear_pointer (&priv->eni_ifaces, g_hash_table_destroy);
 
-	G_OBJECT_CLASS (settings_plugin_ifupdown_parent_class)->dispose (object);
+	G_OBJECT_CLASS (nms_ifupdown_plugin_parent_class)->dispose (object);
 }
 
 static void
-settings_plugin_ifupdown_class_init (SettingsPluginIfupdownClass *klass)
+nms_ifupdown_plugin_class_init (NMSIfupdownPluginClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 	NMSettingsPluginClass *plugin_class = NM_SETTINGS_PLUGIN_CLASS (klass);
 
 	object_class->dispose = dispose;
 
-	plugin_class->initialize          = initialize;
-	plugin_class->get_connections     = get_connections;
+	plugin_class->plugin_name         = "ifupdown";
+	plugin_class->reload_connections  = reload_connections;
 	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
 }
 
@@ -328,5 +412,5 @@ settings_plugin_ifupdown_class_init (SettingsPluginIfupdownClass *klass)
 G_MODULE_EXPORT NMSettingsPlugin *
 nm_settings_plugin_factory (void)
 {
-	return NM_SETTINGS_PLUGIN (g_object_ref (settings_plugin_ifupdown_get ()));
+	return g_object_new (NMS_TYPE_IFUPDOWN_PLUGIN, NULL);
 }
diff --git a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h
index c2a3639c..10ea2be4 100644
--- a/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h
+++ b/src/settings/plugins/ifupdown/nms-ifupdown-plugin.h
@@ -1,5 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-
 /* NetworkManager system settings service (ifupdown)
  *
  * Alexander Sack <asac@ubuntu.com>
@@ -21,21 +19,21 @@
  * (C) Copyright 2008 Canonical Ltd.
  */
 
-#ifndef _PLUGIN_H_
-#define _PLUGIN_H_
+#ifndef __NMS_IFUPDOWN_PLUGIN_H__
+#define __NMS_IFUPDOWN_PLUGIN_H__
 
 #define PLUGIN_NAME "ifupdown"
 
-#define SETTINGS_TYPE_PLUGIN_IFUPDOWN            (settings_plugin_ifupdown_get_type ())
-#define SETTINGS_PLUGIN_IFUPDOWN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SETTINGS_TYPE_PLUGIN_IFUPDOWN, SettingsPluginIfupdown))
-#define SETTINGS_PLUGIN_IFUPDOWN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SETTINGS_TYPE_PLUGIN_IFUPDOWN, SettingsPluginIfupdownClass))
-#define SETTINGS_IS_PLUGIN_IFUPDOWN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SETTINGS_TYPE_PLUGIN_IFUPDOWN))
-#define SETTINGS_IS_PLUGIN_IFUPDOWN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SETTINGS_TYPE_PLUGIN_IFUPDOWN))
-#define SETTINGS_PLUGIN_IFUPDOWN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SETTINGS_TYPE_PLUGIN_IFUPDOWN, SettingsPluginIfupdownClass))
+#define NMS_TYPE_IFUPDOWN_PLUGIN            (nms_ifupdown_plugin_get_type ())
+#define NMS_IFUPDOWN_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPlugin))
+#define NMS_IFUPDOWN_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass))
+#define NMS_IS_IFUPDOWN_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_IFUPDOWN_PLUGIN))
+#define NMS_IS_IFUPDOWN_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_IFUPDOWN_PLUGIN))
+#define NMS_IFUPDOWN_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_IFUPDOWN_PLUGIN, NMSIfupdownPluginClass))
 
-typedef struct _SettingsPluginIfupdown SettingsPluginIfupdown;
-typedef struct _SettingsPluginIfupdownClass SettingsPluginIfupdownClass;
+typedef struct _NMSIfupdownPlugin NMSIfupdownPlugin;
+typedef struct _NMSIfupdownPluginClass NMSIfupdownPluginClass;
 
-GType settings_plugin_ifupdown_get_type (void);
+GType nms_ifupdown_plugin_get_type (void);
 
-#endif /* _PLUGIN_H_ */
+#endif /* __NMS_IFUPDOWN_PLUGIN_H__ */
diff --git a/src/settings/plugins/ifupdown/tests/test-ifupdown.c b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
index 674cb19c..4adcf085 100644
--- a/src/settings/plugins/ifupdown/tests/test-ifupdown.c
+++ b/src/settings/plugins/ifupdown/tests/test-ifupdown.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -31,6 +30,29 @@
 
 /*****************************************************************************/
 
+#define _connection_from_if_block(block) \
+	({ \
+		NMConnection *_con; \
+		if_block *_block = (block); \
+		GError *_local = NULL; \
+		\
+		g_assert (_block); \
+		_con = ifupdown_new_connection_from_if_block (_block, FALSE, &_local); \
+		nmtst_assert_success (NM_IS_CONNECTION (_con), _local); \
+		nmtst_assert_connection_verifies_without_normalization (_con); \
+		_con; \
+	})
+
+#define _connection_first_from_parser(parser) \
+	({ \
+		if_parser *_parser = (parser); \
+		\
+		g_assert (_parser); \
+		_connection_from_if_block (ifparser_getfirst (_parser)); \
+	})
+
+/*****************************************************************************/
+
 typedef struct {
 	char *key;
 	char *data;
@@ -453,26 +475,14 @@ test16_missing_newline (void)
 static void
 test17_read_static_ipv4 (void)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
 	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip4;
 	NMSettingWired *s_wired;
-	GError *error = NULL;
-	gboolean success;
 	NMIPAddress *ip4_addr;
-	if_block *block = NULL;
 	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test17-wired-static-verify-ip4");
 
-	block = ifparser_getfirst (parser);
-	connection = nm_simple_connection_new();
-	g_assert (connection);
-
-	ifupdown_update_connection_from_if_block (connection, block, &error);
-	g_assert_no_error (error);
-
-	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	connection = _connection_first_from_parser (parser);
 
 	/* ===== CONNECTION SETTING ===== */
 	s_con = nm_connection_get_setting_connection (connection);
@@ -501,32 +511,19 @@ test17_read_static_ipv4 (void)
 	g_assert_cmpint (nm_setting_ip_config_get_num_dns_searches (s_ip4), ==, 2);
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip4, 0), ==, "example.com");
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip4, 1), ==, "foo.example.com");
-
-	g_object_unref (connection);
 }
 
 static void
 test18_read_static_ipv6 (void)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
 	NMSettingConnection *s_con;
 	NMSettingIPConfig *s_ip6;
 	NMSettingWired *s_wired;
-	GError *error = NULL;
-	gboolean success;
 	NMIPAddress *ip6_addr;
-	if_block *block = NULL;
 	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test18-wired-static-verify-ip6");
 
-	block = ifparser_getfirst (parser);
-	connection = nm_simple_connection_new();
-	g_assert (connection);
-	ifupdown_update_connection_from_if_block (connection, block, &error);
-	g_assert_no_error (error);
-
-	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	connection = _connection_first_from_parser (parser);
 
 	/* ===== CONNECTION SETTING ===== */
 	s_con = nm_connection_get_setting_connection (connection);
@@ -555,30 +552,17 @@ test18_read_static_ipv6 (void)
 	g_assert_cmpint (nm_setting_ip_config_get_num_dns_searches (s_ip6), ==, 2);
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip6, 0), ==, "example.com");
 	g_assert_cmpstr (nm_setting_ip_config_get_dns_search (s_ip6, 1), ==, "foo.example.com");
-
-	g_object_unref (connection);
 }
 
 static void
 test19_read_static_ipv4_plen (void)
 {
-	NMConnection *connection;
+	gs_unref_object NMConnection *connection = NULL;
 	NMSettingIPConfig *s_ip4;
-	GError *error = NULL;
 	NMIPAddress *ip4_addr;
-	if_block *block = NULL;
-	gboolean success;
 	nm_auto_ifparser if_parser *parser = init_ifparser_with_file ("test19-wired-static-verify-ip4-plen");
 
-	block = ifparser_getfirst (parser);
-	connection = nm_simple_connection_new();
-	g_assert (connection);
-	ifupdown_update_connection_from_if_block (connection, block, &error);
-	g_assert_no_error (error);
-
-	success = nm_connection_verify (connection, &error);
-	g_assert_no_error (error);
-	g_assert (success);
+	connection = _connection_first_from_parser (parser);
 
 	/* ===== IPv4 SETTING ===== */
 	s_ip4 = nm_connection_get_setting_ip4_config (connection);
@@ -589,8 +573,6 @@ test19_read_static_ipv4_plen (void)
 	g_assert (ip4_addr != NULL);
 	g_assert_cmpstr (nm_ip_address_get_address (ip4_addr), ==, "10.0.0.3");
 	g_assert_cmpint (nm_ip_address_get_prefix (ip4_addr), ==, 8);
-
-	g_object_unref (connection);
 }
 
 static void
@@ -671,4 +653,3 @@ main (int argc, char **argv)
 
 	return g_test_run ();
 }
-
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
deleted file mode 100644
index 3b362978..00000000
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ /dev/null
@@ -1,186 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service - keyfile plugin
- *
- * 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) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2012 Red Hat, Inc.
- */
-
-#include "nm-default.h"
-
-#include "nms-keyfile-connection.h"
-
-#include <glib/gstdio.h>
-
-#include "nm-dbus-interface.h"
-#include "nm-setting-connection.h"
-#include "nm-utils.h"
-
-#include "settings/nm-settings-plugin.h"
-
-#include "nms-keyfile-reader.h"
-#include "nms-keyfile-writer.h"
-#include "nms-keyfile-utils.h"
-
-/*****************************************************************************/
-
-struct _NMSKeyfileConnection {
-	NMSettingsConnection parent;
-};
-
-struct _NMSKeyfileConnectionClass {
-	NMSettingsConnectionClass parent;
-};
-
-G_DEFINE_TYPE (NMSKeyfileConnection, nms_keyfile_connection, NM_TYPE_SETTINGS_CONNECTION)
-
-/*****************************************************************************/
-
-static gboolean
-commit_changes (NMSettingsConnection *connection,
-                NMConnection *new_connection,
-                NMSettingsConnectionCommitReason commit_reason,
-                NMConnection **out_reread_connection,
-                char **out_logmsg_change,
-                GError **error)
-{
-	gs_free char *path = NULL;
-	gs_unref_object NMConnection *reread = NULL;
-	gboolean reread_same = FALSE;
-
-	nm_assert (out_reread_connection && !*out_reread_connection);
-	nm_assert (!out_logmsg_change || !*out_logmsg_change);
-
-	if (!nms_keyfile_writer_connection (new_connection,
-	                                    TRUE,
-	                                    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))
-		return FALSE;
-
-	if (!nm_streq0 (path, nm_settings_connection_get_filename (connection))) {
-		gs_free char *old_path = g_strdup (nm_settings_connection_get_filename (connection));
-
-		nm_settings_connection_set_filename (connection, path);
-		if (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_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_SET_OUT (out_logmsg_change,
-		            g_strdup_printf ("keyfile: update "NMS_KEYFILE_CONNECTION_LOG_FMT,
-		                             NMS_KEYFILE_CONNECTION_LOG_ARG (connection)));
-	}
-
-	if (reread && !reread_same)
-		*out_reread_connection = g_steal_pointer (&reread);
-
-	return TRUE;
-}
-
-static gboolean
-delete (NMSettingsConnection *connection,
-        GError **error)
-{
-	const char *path;
-
-	path = nm_settings_connection_get_filename (connection);
-	if (path)
-		g_unlink (path);
-	return TRUE;
-}
-
-/*****************************************************************************/
-
-static void
-nms_keyfile_connection_init (NMSKeyfileConnection *connection)
-{
-}
-
-NMSKeyfileConnection *
-nms_keyfile_connection_new (NMConnection *source,
-                            const char *full_path,
-                            const char *profile_dir,
-                            GError **error)
-{
-	GObject *object;
-	NMConnection *tmp;
-	const char *uuid;
-	gboolean update_unsaved = TRUE;
-
-	nm_assert (source || full_path);
-	nm_assert (!full_path || full_path[0] == '/');
-	nm_assert (!profile_dir || profile_dir[0] == '/');
-
-	/* If we're given a connection already, prefer that instead of re-reading */
-	if (source)
-		tmp = g_object_ref (source);
-	else {
-		tmp = nms_keyfile_reader_from_file (full_path, profile_dir, error);
-		if (!tmp)
-			return NULL;
-
-		uuid = nm_connection_get_uuid (tmp);
-		if (!uuid) {
-			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			             "Connection in file %s had no UUID", full_path);
-			g_object_unref (tmp);
-			return NULL;
-		}
-
-		/* If we just read the connection from disk, it's clearly not Unsaved */
-		update_unsaved = FALSE;
-	}
-
-	object = g_object_new (NMS_TYPE_KEYFILE_CONNECTION,
-	                       NM_SETTINGS_CONNECTION_FILENAME, full_path,
-	                       NULL);
-
-	/* Update our settings with what was read from the file */
-	if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
-	                                    tmp,
-	                                    update_unsaved
-	                                      ? NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED
-	                                      : NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-	                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-	                                    NULL,
-	                                    error)) {
-		g_object_unref (object);
-		object = NULL;
-	}
-
-	g_object_unref (tmp);
-	return (NMSKeyfileConnection *) object;
-}
-
-static void
-nms_keyfile_connection_class_init (NMSKeyfileConnectionClass *keyfile_connection_class)
-{
-	NMSettingsConnectionClass *settings_class = NM_SETTINGS_CONNECTION_CLASS (keyfile_connection_class);
-
-	settings_class->commit_changes = commit_changes;
-	settings_class->delete = delete;
-}
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.h b/src/settings/plugins/keyfile/nms-keyfile-connection.h
deleted file mode 100644
index 0773ced0..00000000
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager system settings service - keyfile plugin
- *
- * 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) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2012 Red Hat, Inc.
- */
-
-#ifndef __NMS_KEYFILE_CONNECTION_H__
-#define __NMS_KEYFILE_CONNECTION_H__
-
-#include "settings/nm-settings-connection.h"
-
-#define NMS_TYPE_KEYFILE_CONNECTION            (nms_keyfile_connection_get_type ())
-#define NMS_KEYFILE_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_KEYFILE_CONNECTION, NMSKeyfileConnection))
-#define NMS_KEYFILE_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_KEYFILE_CONNECTION, NMSKeyfileConnectionClass))
-#define NMS_IS_KEYFILE_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_KEYFILE_CONNECTION))
-#define NMS_IS_KEYFILE_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_KEYFILE_CONNECTION))
-#define NMS_KEYFILE_CONNECTION_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_KEYFILE_CONNECTION, NMSKeyfileConnectionClass))
-
-typedef struct _NMSKeyfileConnection NMSKeyfileConnection;
-typedef struct _NMSKeyfileConnectionClass NMSKeyfileConnectionClass;
-
-GType nms_keyfile_connection_get_type (void);
-
-NMSKeyfileConnection *nms_keyfile_connection_new (NMConnection *source,
-                                                  const char *full_path,
-                                                  const char *profile_dir,
-                                                  GError **error);
-
-#endif /* __NMS_KEYFILE_CONNECTION_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index c13cc1ff..fbe70ef4 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -16,7 +15,7 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2008 Novell, Inc.
- * Copyright (C) 2008 - 2013 Red Hat, Inc.
+ * Copyright (C) 2008 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -26,7 +25,11 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/types.h>
-#include <glib/gstdio.h>
+#include <sys/time.h>
+
+#include "nm-std-aux/c-list-util.h"
+#include "nm-glib-aux/nm-c-list.h"
+#include "nm-glib-aux/nm-io-utils.h"
 
 #include "nm-connection.h"
 #include "nm-setting.h"
@@ -36,22 +39,42 @@
 #include "nm-core-internal.h"
 #include "nm-keyfile-internal.h"
 
+#include "systemd/nm-sd-utils-shared.h"
+
 #include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+#include "settings/nm-settings-utils.h"
 
-#include "nms-keyfile-connection.h"
+#include "nms-keyfile-storage.h"
 #include "nms-keyfile-writer.h"
+#include "nms-keyfile-reader.h"
 #include "nms-keyfile-utils.h"
 
 /*****************************************************************************/
 
 typedef struct {
-	GHashTable *connections;  /* uuid::connection */
-
-	gboolean initialized;
-	GFileMonitor *monitor;
-	gulong monitor_id;
 
 	NMConfig *config;
+
+	/* there can/could be multiple read-only directories. For example, one
+	 * could set dirname_libs to
+	 *   - /usr/lib/NetworkManager/profiles/
+	 *   - /etc/NetworkManager/system-connections
+	 * and leave dirname_etc unset. In this case, there would be multiple
+	 * read-only directories.
+	 *
+	 * Directories that come later have higher priority and shadow profiles
+	 * from earlier directories.
+	 *
+	 * Currently, this is only an array with zero or one elements. It could be
+	 * easily extended to support multiple read-only directories.
+	 */
+	char *dirname_libs[2];
+	char *dirname_etc;
+	char *dirname_run;
+
+	NMSettUtilStorages storages;
+
 } NMSKeyfilePluginPrivate;
 
 struct _NMSKeyfilePlugin {
@@ -65,7 +88,7 @@ struct _NMSKeyfilePluginClass {
 
 G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
-#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN)
+#define NMS_KEYFILE_PLUGIN_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSKeyfilePlugin, NMS_IS_KEYFILE_PLUGIN, NMSettingsPlugin)
 
 /*****************************************************************************/
 
@@ -79,494 +102,1112 @@ G_DEFINE_TYPE (NMSKeyfilePlugin, nms_keyfile_plugin, NM_TYPE_SETTINGS_PLUGIN)
 
 /*****************************************************************************/
 
-static void
-connection_removed_cb (NMSettingsConnection *sett_conn, NMSKeyfilePlugin *self)
+static const char *
+_extra_flags_to_string (char *str, gsize str_len, gboolean is_nm_generated, gboolean is_volatile)
 {
-	g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
-	                     nm_settings_connection_get_uuid (sett_conn));
+	const char *str0 = str;
+
+	if (   !is_nm_generated
+	    && !is_volatile)
+		nm_utils_strbuf_append_str (&str, &str_len, "");
+	else {
+		nm_utils_strbuf_append_str (&str, &str_len, " (");
+		if (is_nm_generated) {
+			nm_utils_strbuf_append_str (&str, &str_len, "nm-generated");
+			if (is_volatile)
+				nm_utils_strbuf_append_c (&str, &str_len, ',');
+		}
+		if (is_volatile)
+			nm_utils_strbuf_append_str (&str, &str_len, "volatile");
+		nm_utils_strbuf_append_c (&str, &str_len, ')');
+	}
+
+	return str0;
 }
 
-/* Monitoring */
+static gboolean
+_ignore_filename (NMSKeyfileStorageType storage_type,
+                  const char *filename)
+{
+	/* for backward-compatibility, we don't require an extension for
+	 * files under "/etc/...". */
+	return nm_keyfile_utils_ignore_filename (filename,
+	                                         (storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC));
+}
 
-static void
-remove_connection (NMSKeyfilePlugin *self, NMSKeyfileConnection *connection)
+static const char *
+_get_plugin_dir (NMSKeyfilePluginPrivate *priv)
 {
-	gboolean removed;
+	/* the plugin dir is only needed to generate connection.uuid value via
+	 * nm_keyfile_read_ensure_uuid(). This is either the configured /etc
+	 * directory, of the compile-time default (in case the /etc directory
+	 * is disabled). */
+	return priv->dirname_etc ?: NM_KEYFILE_PATH_NAME_ETC_DEFAULT;
+}
 
-	g_return_if_fail (connection != NULL);
+static gboolean
+_path_detect_storage_type (const char *full_filename,
+                           const char *const*dirname_libs,
+                           const char *dirname_etc,
+                           const char *dirname_run,
+                           NMSKeyfileStorageType *out_storage_type,
+                           const char **out_dirname,
+                           const char **out_filename,
+                           gboolean *out_is_nmmeta_file,
+                           gboolean *out_failed_due_to_invalid_filename)
+{
+	NMSKeyfileStorageType storage_type;
+	const char *filename = NULL;
+	const char *dirname = NULL;
+	guint i;
+	gboolean is_nmmeta_file = FALSE;
+
+	NM_SET_OUT (out_failed_due_to_invalid_filename, FALSE);
+
+	if (full_filename[0] != '/')
+		return FALSE;
+
+	if (   dirname_run
+	    && (filename = nm_utils_file_is_in_path (full_filename, dirname_run))) {
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+		dirname = dirname_run;
+	} else if (   dirname_etc
+	           && (filename = nm_utils_file_is_in_path (full_filename, dirname_etc))) {
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+		dirname = dirname_etc;
+	} else {
+		for (i = 0; dirname_libs && dirname_libs[i]; i++) {
+			if ((filename = nm_utils_file_is_in_path (full_filename, dirname_libs[i]))) {
+				storage_type = NMS_KEYFILE_STORAGE_TYPE_LIB (i);
+				dirname = dirname_libs[i];
+				break;
+			}
+		}
+		if (!dirname)
+			return FALSE;
+	}
+
+	if (_ignore_filename (storage_type, filename)) {
 
-	_LOGI ("removed " NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection));
+		/* we accept nmmeta files, but only in /etc and /run directories. */
 
-	/* Removing from the hash table should drop the last reference */
-	g_object_ref (connection);
-	g_signal_handlers_disconnect_by_func (connection, connection_removed_cb, self);
-	removed = g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
-	                               nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection)));
-	nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
-	g_object_unref (connection);
+		if (   !NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
+		                                 NMS_KEYFILE_STORAGE_TYPE_ETC)
+		    || !nms_keyfile_nmmeta_check_filename (filename, NULL)) {
+			NM_SET_OUT (out_failed_due_to_invalid_filename, TRUE);
+			return FALSE;
+		}
+
+		is_nmmeta_file = TRUE;
+	}
 
-	g_return_if_fail (removed);
+	NM_SET_OUT (out_storage_type, storage_type);
+	NM_SET_OUT (out_dirname, dirname);
+	NM_SET_OUT (out_filename, filename);
+	NM_SET_OUT (out_is_nmmeta_file, is_nmmeta_file);
+	return TRUE;
 }
 
-static NMSKeyfileConnection *
-find_by_path (NMSKeyfilePlugin *self, const char *path)
+/*****************************************************************************/
+
+static NMConnection *
+_read_from_file (const char *full_filename,
+                 const char *plugin_dir,
+                 struct stat *out_stat,
+                 NMTernary *out_is_nm_generated,
+                 NMTernary *out_is_volatile,
+                 char **out_shadowed_storage,
+                 NMTernary *out_shadowed_owned,
+                 GError **error)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
-	GHashTableIter iter;
-	NMSettingsConnection *candidate = NULL;
+	NMConnection *connection;
 
-	g_return_val_if_fail (path != NULL, NULL);
+	nm_assert (full_filename && full_filename[0] == '/');
 
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer) &candidate)) {
-		if (g_strcmp0 (path, nm_settings_connection_get_filename (candidate)) == 0)
-			return NMS_KEYFILE_CONNECTION (candidate);
-	}
-	return NULL;
+	connection = nms_keyfile_reader_from_file (full_filename,
+	                                           plugin_dir,
+	                                           out_stat,
+	                                           out_is_nm_generated,
+	                                           out_is_volatile,
+	                                           out_shadowed_storage,
+	                                           out_shadowed_owned,
+	                                           error);
+
+	nm_assert (!connection || (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
+	nm_assert (!connection || nm_utils_is_uuid (nm_connection_get_uuid (connection)));
+
+	return connection;
 }
 
-/* update_connection:
- * @self: the plugin instance
- * @source: if %NULL, this re-reads the connection from @full_path
- *   and updates it. When passing @source, this adds a connection from
- *   memory.
- * @full_path: the filename of the keyfile to be loaded
- * @connection: an existing connection that might be updated.
- *   If given, @connection must be an existing connection that is currently
- *   owned by the plugin.
- * @protect_existing_connection: if %TRUE, and !@connection, we don't allow updating
- *   an existing connection with the same UUID.
- *   If %TRUE and @connection, allow updating only if the reload would modify
- *   @connection (without changing its UUID) or if we would create a new connection.
- *   In other words, if this parameter is %TRUE, we only allow creating a
- *   new connection (with an unseen UUID) or updating the passed in @connection
- *   (whereas the UUID cannot change).
- *   Note, that this allows for @connection to be replaced by a new connection.
- * @protected_connections: (allow-none): if given, we only update an
- *   existing connection if it is not contained in this hash.
- * @error: error in case of failure
- *
- * Loads a connection from file @full_path. This can both be used to
- * load a connection initially or to update an existing connection.
- *
- * If you pass in an existing connection and the reloaded file happens
- * to have a different UUID, the connection is deleted.
- * Beware, that means that after the function, you have a dangling pointer
- * if the returned connection is different from @connection.
- *
- * Returns: the updated connection.
- * */
-static NMSKeyfileConnection *
-update_connection (NMSKeyfilePlugin *self,
-                   NMConnection *source,
-                   const char *full_path,
-                   NMSKeyfileConnection *connection,
-                   gboolean protect_existing_connection,
-                   GHashTable *protected_connections,
-                   GError **error)
+/*****************************************************************************/
+
+static void
+_nm_assert_storage (gpointer plugin  /* NMSKeyfilePlugin  */,
+                    gpointer storage /* NMSKeyfileStorage */,
+                    gboolean tracked)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
-	NMSKeyfileConnection *connection_new;
-	NMSKeyfileConnection *connection_by_uuid;
-	GError *local = NULL;
+#if NM_MORE_ASSERTS
+	NMSettUtilStorageByUuidHead *sbuh;
 	const char *uuid;
 
-	g_return_val_if_fail (!source || NM_IS_CONNECTION (source), NULL);
-	g_return_val_if_fail (full_path || source, NULL);
+	nm_assert (!plugin || NMS_IS_KEYFILE_PLUGIN (plugin));
+	nm_assert (NMS_IS_KEYFILE_STORAGE (storage));
+	nm_assert (!plugin || plugin == nm_settings_storage_get_plugin (storage));
 
-	if (full_path)
-		_LOGD ("loading from file \"%s\"...", full_path);
+	nm_assert (({
+	                const char *f = nms_keyfile_storage_get_filename (storage);
+	                f && f[0] == '/';
+	            }));
 
-	if (   !nm_utils_file_is_in_path (full_path, nms_keyfile_utils_get_path ())
-	    && !nm_utils_file_is_in_path (full_path, NM_KEYFILE_PATH_NAME_RUN)) {
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		                     "File not in recognized system-connections directory");
-		return FALSE;
-	}
+	uuid = nms_keyfile_storage_get_uuid (storage);
 
-	connection_new = nms_keyfile_connection_new (source, full_path, nms_keyfile_utils_get_path (), &local);
-	if (!connection_new) {
-		/* Error; remove the connection */
-		if (source)
-			_LOGW ("error creating connection %s: %s", nm_connection_get_uuid (source), local->message);
-		else
-			_LOGW ("error loading connection from file %s: %s", full_path, local->message);
-		if (   connection
-		    && !protect_existing_connection
-		    && (!protected_connections || !g_hash_table_contains (protected_connections, connection)))
-			remove_connection (self, connection);
-		g_propagate_error (error, local);
-		return NULL;
+	nm_assert (nm_utils_is_uuid (uuid));
+
+	nm_assert (   ((NMSKeyfileStorage *) storage)->is_meta_data
+	           || !(((NMSKeyfileStorage *) storage)->u.conn_data.connection)
+	           || (   NM_IS_CONNECTION ((((NMSKeyfileStorage *) storage)->u.conn_data.connection))
+	               && nm_streq0 (uuid, nm_connection_get_uuid ((((NMSKeyfileStorage *) storage)->u.conn_data.connection)))));
+
+	nm_assert (   !tracked
+	           || !plugin
+	           || c_list_contains (&NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages._storage_lst_head,
+	                               &NMS_KEYFILE_STORAGE (storage)->parent._storage_lst));
+
+	nm_assert (   !tracked
+	           || !plugin
+	           || storage == g_hash_table_lookup (NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.idx_by_filename,
+	                                              nms_keyfile_storage_get_filename (storage)));
+
+	if (   tracked
+	    && plugin) {
+		sbuh = g_hash_table_lookup (NMS_KEYFILE_PLUGIN_GET_PRIVATE (plugin)->storages.idx_by_uuid, &uuid);
+		nm_assert (sbuh);
+		nm_assert (c_list_contains (&sbuh->_storage_by_uuid_lst_head, &((NMSKeyfileStorage *) storage)->parent._storage_by_uuid_lst));
 	}
+#endif
+}
 
-	uuid = nm_settings_connection_get_uuid (NM_SETTINGS_CONNECTION (connection_new));
-	connection_by_uuid = g_hash_table_lookup (priv->connections, uuid);
+/*****************************************************************************/
 
-	if (   connection
-	    && connection != connection_by_uuid) {
+static NMSKeyfileStorage *
+_load_file (NMSKeyfilePlugin *self,
+            const char *dirname,
+            const char *filename,
+            NMSKeyfileStorageType storage_type,
+            GError **error)
+{
+	NMSKeyfilePluginPrivate *priv;
+	gs_unref_object NMConnection *connection = NULL;
+	NMTernary is_volatile_opt;
+	NMTernary is_nm_generated_opt;
+	NMTernary shadowed_owned_opt;
+	gs_free char *shadowed_storage = NULL;
+	gs_free_error GError *local = NULL;
+	gs_free char *full_filename = NULL;
+	struct stat st;
 
-		if (   (protect_existing_connection && connection_by_uuid != NULL)
-		    || (protected_connections && g_hash_table_contains (protected_connections, connection))) {
-			NMSKeyfileConnection *conflicting = (protect_existing_connection && connection_by_uuid != NULL) ? connection_by_uuid : connection;
+	if (_ignore_filename (storage_type, filename)) {
+		gs_free char *nmmeta = NULL;
+		gs_free char *loaded_path = NULL;
+		gs_free char *shadowed_storage_filename = NULL;
 
-			if (source)
-				_LOGW ("cannot update protected "NMS_KEYFILE_CONNECTION_LOG_FMT" connection due to conflicting UUID %s", NMS_KEYFILE_CONNECTION_LOG_ARG (conflicting), uuid);
+		if (!nms_keyfile_nmmeta_check_filename (filename, NULL)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip due to invalid filename");
+			else
+				_LOGT ("load: \"%s/%s\": skip file due to invalid filename", dirname, filename);
+			return NULL;
+		}
+		if (!nms_keyfile_nmmeta_read (dirname,
+		                              filename,
+		                              &full_filename,
+		                              &nmmeta,
+		                              &loaded_path,
+		                              &shadowed_storage_filename,
+		                              NULL)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip unreadable nmmeta file");
 			else
-				_LOGW ("cannot load %s due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, full_path, NMS_KEYFILE_CONNECTION_LOG_ARG (conflicting));
-			g_object_unref (connection_new);
-			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-			                      "Cannot update protected connection due to conflicting UUID");
+				_LOGT ("load: \"%s/%s\": skip unreadable nmmeta file", dirname, filename);
+			return NULL;
+		}
+		nm_assert (loaded_path);
+		if (!NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_RUN,
+		                              NMS_KEYFILE_STORAGE_TYPE_ETC)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip nmmeta file from read-only directory");
+			else
+				_LOGT ("load: \"%s/%s\": skip nmmeta file from read-only directory", dirname, filename);
+			return NULL;
+		}
+		if (!nm_streq (loaded_path, NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL)) {
+			if (error)
+				nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN, "skip nmmeta file not symlinking %s", NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
+			else
+				_LOGT ("load: \"%s/%s\": skip nmmeta file not symlinking to %s", dirname, filename, NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL);
 			return NULL;
 		}
 
-		/* The new connection has a different UUID then the original one.
-		 * Remove @connection. */
-		remove_connection (self, connection);
+		return nms_keyfile_storage_new_tombstone (self,
+		                                          nmmeta,
+		                                          full_filename,
+		                                          storage_type,
+		                                          shadowed_storage_filename);
 	}
 
-	if (   connection_by_uuid
-	    && (   (!connection && protect_existing_connection)
-	        || (protected_connections && g_hash_table_contains (protected_connections, connection_by_uuid)))) {
-		if (source)
-			_LOGW ("cannot update connection due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_by_uuid));
+	full_filename = g_build_filename (dirname, filename, NULL);
+
+	priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+
+	connection = _read_from_file (full_filename,
+	                              _get_plugin_dir (priv),
+	                              &st,
+	                              &is_nm_generated_opt,
+	                              &is_volatile_opt,
+	                              &shadowed_storage,
+	                              &shadowed_owned_opt,
+	                              &local);
+	if (!connection) {
+		if (error)
+			g_propagate_error (error, local);
 		else
-			_LOGW ("cannot load %s due to conflicting UUID for "NMS_KEYFILE_CONNECTION_LOG_FMT, full_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_by_uuid));
-		g_object_unref (connection_new);
-		g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-		                      "Skip updating protected connection during reload");
+			_LOGW ("load: \"%s\": failed to load connection: %s", full_filename, local->message);
 		return NULL;
 	}
 
-	if (connection_by_uuid) {
-		const char *old_path;
-
-		old_path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_by_uuid));
-
-		if (nm_connection_compare (nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_by_uuid)),
-		                           nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-		                           NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS |
-		                           NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
-			/* Nothing to do... except updating the path. */
-			if (old_path && g_strcmp0 (old_path, full_path) != 0)
-				_LOGI ("rename \"%s\" to "NMS_KEYFILE_CONNECTION_LOG_FMT" without other changes", old_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-		} else {
-			/* An existing connection changed. */
-			if (source)
-				_LOGI ("update "NMS_KEYFILE_CONNECTION_LOG_FMT" from %s", NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new), NMS_KEYFILE_CONNECTION_LOG_PATH (old_path));
-			else if (!g_strcmp0 (old_path, nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection_new))))
-				_LOGI ("update "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-			else if (old_path)
-				_LOGI ("rename \"%s\" to "NMS_KEYFILE_CONNECTION_LOG_FMT, old_path, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-			else
-				_LOGI ("update and persist "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-
-			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
-			                                    nm_settings_connection_get_connection (NM_SETTINGS_CONNECTION (connection_new)),
-			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
-			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
-			                                    "keyfile-update",
-			                                    &local)) {
-				/* Shouldn't ever get here as 'connection_new' was verified by the reader already
-				 * and the UUID did not change. */
-				g_assert_not_reached ();
-			}
-			g_assert_no_error (local);
-		}
-		nm_settings_connection_set_filename (NM_SETTINGS_CONNECTION (connection_by_uuid), full_path);
-		g_object_unref (connection_new);
-		return connection_by_uuid;
-	} else {
-		if (source)
-			_LOGI ("add connection "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-		else
-			_LOGI ("new connection "NMS_KEYFILE_CONNECTION_LOG_FMT, NMS_KEYFILE_CONNECTION_LOG_ARG (connection_new));
-		g_hash_table_insert (priv->connections, g_strdup (uuid), connection_new);
-
-		g_signal_connect (connection_new, NM_SETTINGS_CONNECTION_REMOVED,
-		                  G_CALLBACK (connection_removed_cb),
-		                  self);
-
-		if (!source) {
-			/* Only raise the signal if we were called without source, i.e. if we read the connection from file.
-			 * Otherwise, we were called by add_connection() which does not expect the signal. */
-			_nm_settings_plugin_emit_signal_connection_added (NM_SETTINGS_PLUGIN (self),
-			                                                  NM_SETTINGS_CONNECTION (connection_new));
-		}
+	return nms_keyfile_storage_new_connection (self,
+	                                           g_steal_pointer (&connection),
+	                                           full_filename,
+	                                           storage_type,
+	                                           is_nm_generated_opt,
+	                                           is_volatile_opt,
+	                                           shadowed_storage,
+	                                           shadowed_owned_opt,
+	                                           &st.st_mtim);
+}
 
-		return connection_new;
-	}
+static NMSKeyfileStorage *
+_load_file_from_path (NMSKeyfilePlugin *self,
+                      const char *full_filename,
+                      NMSKeyfileStorageType storage_type,
+                      GError **error)
+{
+	gs_free char *f_dirname_free = NULL;
+	const char *f_filename;
+	const char *f_dirname;
+
+	nm_assert (full_filename && full_filename[0] == '/');
+
+	f_filename = strrchr (full_filename, '/');
+	f_dirname = nm_strndup_a (300, full_filename, f_filename - full_filename, &f_dirname_free);
+	f_filename++;
+	return _load_file (self,
+	                   f_dirname,
+	                   f_filename,
+	                   storage_type,
+	                   error);
 }
 
 static void
-dir_changed (GFileMonitor *monitor,
-             GFile *file,
-             GFile *other_file,
-             GFileMonitorEvent event_type,
-             gpointer user_data)
+_load_dir (NMSKeyfilePlugin *self,
+           NMSKeyfileStorageType storage_type,
+           const char *dirname,
+           NMSettUtilStorages *storages)
 {
-	NMSettingsPlugin *config = NM_SETTINGS_PLUGIN (user_data);
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
-	NMSKeyfileConnection *connection;
-	char *full_path;
-	gboolean exists;
-
-	full_path = g_file_get_path (file);
-	if (nm_keyfile_utils_ignore_filename (full_path, FALSE)) {
-		g_free (full_path);
+	const char *filename;
+	GDir *dir;
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+
+	dir = g_dir_open (dirname, 0, NULL);
+	if (!dir)
 		return;
-	}
-	exists = g_file_test (full_path, G_FILE_TEST_EXISTS);
-
-	_LOGD ("dir_changed(%s) = %d; file %s", full_path, event_type, exists ? "exists" : "does not exist");
-
-	connection = find_by_path (self, full_path);
-
-	switch (event_type) {
-	case G_FILE_MONITOR_EVENT_DELETED:
-		if (!exists && connection)
-			remove_connection (NMS_KEYFILE_PLUGIN (config), connection);
-		break;
-	case G_FILE_MONITOR_EVENT_CREATED:
-	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
-		if (exists)
-			update_connection (NMS_KEYFILE_PLUGIN (config), NULL, full_path, connection, TRUE, NULL, NULL);
-		break;
-	default:
-		break;
+
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, NULL, g_free);
+
+	while ((filename = g_dir_read_name (dir))) {
+		gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+		filename = g_strdup (filename);
+		if (!g_hash_table_add (dupl_filenames, (char *) filename))
+			continue;
+
+		storage = _load_file (self,
+		                      dirname,
+		                      filename,
+		                      storage_type,
+		                      NULL);
+		if (!storage)
+			continue;
+
+		nm_sett_util_storages_add_take (storages, g_steal_pointer (&storage));
 	}
 
-	g_free (full_path);
+	g_dir_close (dir);
+
+#if NM_MORE_ASSERTS
+	{
+		NMSKeyfileStorage *storage;
+
+		c_list_for_each_entry (storage, &storages->_storage_lst_head, parent._storage_lst)
+			nm_assert (NMS_IS_KEYFILE_STORAGE (storage));
+	}
+#endif
 }
 
+/*****************************************************************************/
+
 static void
-config_changed_cb (NMConfig *config,
-                   NMConfigData *config_data,
-                   NMConfigChangeFlags changes,
-                   NMConfigData *old_data,
-                   NMSKeyfilePlugin *self)
+_storages_consolidate (NMSKeyfilePlugin *self,
+                       NMSettUtilStorages *storages_new,
+                       gboolean replace_all,
+                       GHashTable *storages_replaced,
+                       NMSettingsPluginConnectionLoadCallback callback,
+                       gpointer user_data)
 {
-	gs_free char *old_value = NULL;
-	gs_free char *new_value = NULL;
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	CList lst_conn_info_deleted = C_LIST_INIT (lst_conn_info_deleted);
+	gs_unref_ptrarray GPtrArray *storages_modified = NULL;
+	CList storages_deleted;
+	NMSKeyfileStorage *storage_safe;
+	NMSKeyfileStorage *storage_new;
+	NMSKeyfileStorage *storage_old;
+	NMSKeyfileStorage *storage;
+	guint i;
 
-	old_value = nm_config_data_get_value (old_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
-	new_value = nm_config_data_get_value (config_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+	storages_modified = g_ptr_array_new_with_free_func (g_object_unref);
+	c_list_init (&storages_deleted);
 
-	if (!nm_streq0 (old_value, new_value))
-		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
-}
+	c_list_for_each_entry (storage_old, &priv->storages._storage_lst_head, parent._storage_lst)
+		storage_old->is_dirty = TRUE;
 
-static void
-setup_monitoring (NMSettingsPlugin *config)
-{
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
-	GFile *file;
-	GFileMonitor *monitor;
-
-	if (nm_config_get_monitor_connection_files (priv->config)) {
-		file = g_file_new_for_path (nms_keyfile_utils_get_path ());
-		monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
-		g_object_unref (file);
-
-		if (monitor) {
-			priv->monitor_id = g_signal_connect (monitor, "changed", G_CALLBACK (dir_changed), config);
-			priv->monitor = monitor;
+	c_list_for_each_entry_safe (storage_new, storage_safe, &storages_new->_storage_lst_head, parent._storage_lst) {
+		storage_old = nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_keyfile_storage_get_filename (storage_new));
+
+		nm_sett_util_storages_steal (storages_new, storage_new);
+
+		if (   !storage_old
+		    || !nm_streq (nms_keyfile_storage_get_uuid (storage_new), nms_keyfile_storage_get_uuid (storage_old))) {
+			if (storage_old) {
+				nm_sett_util_storages_steal (&priv->storages, storage_old);
+				c_list_link_tail (&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+			}
+			storage_new->is_dirty = FALSE;
+			nm_sett_util_storages_add_take (&priv->storages, storage_new);
+			g_ptr_array_add (storages_modified, g_object_ref (storage_new));
+			continue;
 		}
-	}
 
-	g_signal_connect (G_OBJECT (priv->config),
-	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
-	                  G_CALLBACK (config_changed_cb),
-	                  config);
-}
+		storage_old->is_dirty = FALSE;
+		nms_keyfile_storage_copy_content (storage_old, storage_new);
+		nms_keyfile_storage_destroy (storage_new);
+		g_ptr_array_add (storages_modified, g_object_ref (storage_old));
+	}
 
-static GHashTable *
-_paths_from_connections (GHashTable *connections)
-{
-	GHashTableIter iter;
-	NMSKeyfileConnection *connection;
-	GHashTable *paths = g_hash_table_new (nm_str_hash, g_str_equal);
+	c_list_for_each_entry_safe (storage_old, storage_safe, &priv->storages._storage_lst_head, parent._storage_lst) {
+		if (!storage_old->is_dirty)
+			continue;
+		if (   replace_all
+		    || (   storages_replaced
+		        && g_hash_table_contains (storages_replaced, storage_old))) {
+			nm_sett_util_storages_steal (&priv->storages, storage_old);
+			c_list_link_tail (&storages_deleted, &storage_old->parent._storage_by_uuid_lst);
+		}
+	}
 
-	g_hash_table_iter_init (&iter, connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		const char *path = nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection));
+	/* raise events. */
 
-		if (path)
-			g_hash_table_add (paths, (void *) path);
+	for (i = 0; i < storages_modified->len; i++) {
+		storage = storages_modified->pdata[i];
+		storage->is_dirty = TRUE;
 	}
-	return paths;
-}
 
-static int
-_sort_paths (const char **f1, const char **f2, GHashTable *paths)
-{
-	struct stat st;
-	gboolean c1, c2;
-	gint64 m1, m2;
+	for (i = 0; i < storages_modified->len; i++) {
+		gs_unref_object NMConnection *connection = NULL;
 
-	c1 = !!g_hash_table_contains (paths, *f1);
-	c2 = !!g_hash_table_contains (paths, *f2);
-	if (c1 != c2)
-		return c1 ? -1 : 1;
+		storage = storages_modified->pdata[i];
 
-	m1 = stat (*f1, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	m2 = stat (*f2, &st) == 0 ? (gint64) st.st_mtime : G_MININT64;
-	if (m1 != m2)
-		return m1 > m2 ? -1 : 1;
+		if (!storage->is_dirty) {
+			/* the entry is no longer is_dirty. In the meantime we already emited
+			 * another signal for it. */
+			continue;
+		}
+		storage->is_dirty = FALSE;
 
-	return strcmp (*f1, *f2);
-}
+		if (c_list_is_empty (&storage->parent._storage_lst)) {
+			/* hm? The profile was deleted in the meantime? That is only possible
+			 * if the signal handler called again into the plugin. In any case, the event
+			 * was already emitted. Skip. */
+			continue;
+		}
 
-static void
-_read_dir (GPtrArray *filenames,
-           const char *path,
-           gboolean require_extension)
-{
-	GDir *dir;
-	const char *item;
-	GError *error = NULL;
+		nm_assert (storage == nm_sett_util_storages_lookup_by_filename (&priv->storages, nms_keyfile_storage_get_filename (storage)));
 
-	dir = g_dir_open (path, 0, &error);
-	if (!dir) {
-		_LOGD ("cannot read directory '%s': %s", path, error->message);
-		g_clear_error (&error);
-		return;
+		connection = nms_keyfile_storage_steal_connection (storage);
+
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          connection,
+		          user_data);
 	}
 
-	while ((item = g_dir_read_name (dir))) {
-		if (nm_keyfile_utils_ignore_filename (item, require_extension))
-			continue;
-		g_ptr_array_add (filenames, g_build_filename (path, item, NULL));
+	while ((storage = c_list_first_entry (&storages_deleted, NMSKeyfileStorage, parent._storage_by_uuid_lst))) {
+		c_list_unlink (&storage->parent._storage_by_uuid_lst);
+		callback (NM_SETTINGS_PLUGIN (self),
+		          NM_SETTINGS_STORAGE (storage),
+		          NULL,
+		          user_data);
+		nms_keyfile_storage_destroy (storage);
 	}
-	g_dir_close (dir);
 }
 
+static void
+reload_connections (NMSettingsPlugin *plugin,
+                    NMSettingsPluginConnectionLoadCallback callback,
+                    gpointer user_data)
+{
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_keyfile_storage_destroy);
+	int i;
+
+	_load_dir (self, NMS_KEYFILE_STORAGE_TYPE_RUN, priv->dirname_run, &storages_new);
+	if (priv->dirname_etc)
+		_load_dir (self, NMS_KEYFILE_STORAGE_TYPE_ETC, priv->dirname_etc, &storages_new);
+	for (i = 0; priv->dirname_libs[i]; i++)
+		_load_dir (self, NMS_KEYFILE_STORAGE_TYPE_LIB (i), priv->dirname_libs[i], &storages_new);
+
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       TRUE,
+	                       NULL,
+	                       callback,
+	                       user_data);
+}
 
 static void
-read_connections (NMSettingsPlugin *config)
+load_connections (NMSettingsPlugin *plugin,
+                  NMSettingsPluginConnectionLoadEntry *entries,
+                  gsize n_entries,
+                  NMSettingsPluginConnectionLoadCallback callback,
+                  gpointer user_data)
 {
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
 	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
-	GHashTable *alive_connections;
-	GHashTableIter iter;
-	NMSKeyfileConnection *connection;
-	GPtrArray *dead_connections = NULL;
-	guint i;
-	GPtrArray *filenames;
-	GHashTable *paths;
+	nm_auto_clear_sett_util_storages NMSettUtilStorages storages_new = NM_SETT_UTIL_STORAGES_INIT (storages_new, nms_keyfile_storage_destroy);
+	gs_unref_hashtable GHashTable *dupl_filenames = NULL;
+	gs_unref_hashtable GHashTable *storages_replaced = NULL;
+	gs_unref_hashtable GHashTable *loaded_uuids = NULL;
+	const char *loaded_uuid;
+	GHashTableIter h_iter;
+	gsize i;
+
+	if (n_entries == 0)
+		return;
 
-	filenames = g_ptr_array_new_with_free_func (g_free);
+	dupl_filenames = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
 
-	_read_dir (filenames, NM_KEYFILE_PATH_NAME_RUN, TRUE);
-	_read_dir (filenames, nms_keyfile_utils_get_path (), FALSE);
+	loaded_uuids = g_hash_table_new (nm_str_hash, g_str_equal);
 
-	alive_connections = g_hash_table_new (nm_direct_hash, NULL);
+	storages_replaced = g_hash_table_new_full (nm_direct_hash, NULL, g_object_unref, NULL);
 
-	/* While reloading, we don't replace connections that we already loaded while
-	 * iterating over the files.
-	 *
-	 * To have sensible, reproducible behavior, sort the paths by last modification
-	 * time preferring older files.
-	 */
-	paths = _paths_from_connections (priv->connections);
-	g_ptr_array_sort_with_data (filenames, (GCompareDataFunc) _sort_paths, paths);
-	g_hash_table_destroy (paths);
-
-	for (i = 0; i < filenames->len; i++) {
-		connection = update_connection (self, NULL, filenames->pdata[i], NULL, FALSE, alive_connections, NULL);
-		if (connection)
-			g_hash_table_add (alive_connections, connection);
-	}
-	g_ptr_array_free (filenames, TRUE);
-
-	g_hash_table_iter_init (&iter, priv->connections);
-	while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &connection)) {
-		if (   !g_hash_table_contains (alive_connections, connection)
-		    && nm_settings_connection_get_filename (NM_SETTINGS_CONNECTION (connection))) {
-			if (!dead_connections)
-				dead_connections = g_ptr_array_new ();
-			g_ptr_array_add (dead_connections, connection);
+	for (i = 0; i < n_entries; i++) {
+		NMSettingsPluginConnectionLoadEntry *const entry = &entries[i];
+		NMSKeyfileStorageType storage_type;
+		gs_free_error GError *local = NULL;
+		const char *f_filename;
+		const char *f_dirname;
+		const char *full_filename;
+		gs_free char *full_filename_keep = NULL;
+		gboolean is_nmmeta_file;
+		NMSettingsPluginConnectionLoadEntry *dupl_content_entry;
+		gboolean failed_due_to_invalid_filename;
+		gs_unref_object NMSKeyfileStorage *storage = NULL;
+
+		if (entry->handled)
+			continue;
+
+		if (!_path_detect_storage_type (entry->filename,
+		                                (const char *const*) priv->dirname_libs,
+		                                priv->dirname_etc,
+		                                priv->dirname_run,
+		                                &storage_type,
+		                                &f_dirname,
+		                                &f_filename,
+		                                &is_nmmeta_file,
+		                                &failed_due_to_invalid_filename)) {
+			if (failed_due_to_invalid_filename) {
+				entry->handled = TRUE;
+				nm_utils_error_set (&entry->error, NM_UTILS_ERROR_UNKNOWN, "filename is not valid for a keyfile");
+			}
+			continue;
 		}
-	}
-	g_hash_table_destroy (alive_connections);
 
-	if (dead_connections) {
-		for (i = 0; i < dead_connections->len; i++)
-			remove_connection (self, dead_connections->pdata[i]);
-		g_ptr_array_free (dead_connections, TRUE);
+		full_filename_keep = g_build_filename (f_dirname, f_filename, NULL);
+
+		if ((dupl_content_entry = g_hash_table_lookup (dupl_filenames, full_filename_keep))) {
+			/* we already visited this file. */
+			entry->handled = dupl_content_entry->handled;
+			if (dupl_content_entry->error) {
+				g_set_error_literal (&entry->error,
+				                     dupl_content_entry->error->domain,
+				                     dupl_content_entry->error->code,
+				                     dupl_content_entry->error->message);
+			}
+			continue;
+		}
+
+		entry->handled = TRUE;
+
+		full_filename = full_filename_keep;
+		if (!g_hash_table_insert (dupl_filenames, g_steal_pointer (&full_filename_keep), entry))
+			nm_assert_not_reached ();
+
+		storage = _load_file (self,
+		                      f_dirname,
+		                      f_filename,
+		                      storage_type,
+		                      &local);
+		if (!storage) {
+			if (nm_utils_file_stat (full_filename, NULL) == -ENOENT) {
+				NMSKeyfileStorage *storage2;
+
+				/* the file does not exist. We take that as indication to unload the file
+				 * that was previously loaded... */
+				storage2 = nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename);
+				if (storage2)
+					g_hash_table_add (storages_replaced, g_object_ref (storage2));
+				continue;
+			}
+			g_propagate_error (&entry->error, g_steal_pointer (&local));
+			continue;
+		}
+
+		g_hash_table_add (loaded_uuids, (char *) nms_keyfile_storage_get_uuid (storage));
+
+		nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage));
 	}
-}
 
-/*****************************************************************************/
+	/* now we visit all UUIDs that are about to change... */
+	g_hash_table_iter_init (&h_iter, loaded_uuids);
+	while (g_hash_table_iter_next (&h_iter, (gpointer *) &loaded_uuid, NULL)) {
+		NMSKeyfileStorage *storage;
+		NMSettUtilStorageByUuidHead *sbuh;
 
-static GSList *
-get_connections (NMSettingsPlugin *config)
-{
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
+		sbuh = nm_sett_util_storages_lookup_by_uuid (&priv->storages, loaded_uuid);
+		if (!sbuh)
+			continue;
+
+		c_list_for_each_entry (storage, &sbuh->_storage_by_uuid_lst_head, parent._storage_by_uuid_lst) {
+			const char *full_filename = nms_keyfile_storage_get_filename (storage);
+			gs_unref_object NMSKeyfileStorage *storage_new = NULL;
+			gs_free_error GError *local = NULL;
+
+			if (g_hash_table_contains (dupl_filenames, full_filename)) {
+				/* already re-loaded. */
+				continue;
+			}
+
+			/* @storage has a UUID that was just loaded from disk, but we have an entry in cache.
+			 * Reload that file too despite not being told to do so. The reason is to get
+			 * the latest file timestamp so that we get the priorities right. */
+
+			storage_new = _load_file_from_path (self,
+			                                    full_filename,
+			                                    storage->storage_type,
+			                                    &local);
+			if (   storage_new
+			    && !nm_streq (loaded_uuid, nms_keyfile_storage_get_uuid (storage_new))) {
+				/* the file now references a different UUID. We are not told to reload
+				 * that file, so this means the existing storage (with the previous
+				 * filename and UUID tuple) is no longer valid. */
+				g_clear_object (&storage_new);
+			}
 
-	if (!priv->initialized) {
-		setup_monitoring (config);
-		read_connections (config);
-		priv->initialized = TRUE;
+			g_hash_table_add (storages_replaced, g_object_ref (storage));
+			if (storage_new)
+				nm_sett_util_storages_add_take (&storages_new, g_steal_pointer (&storage_new));
+		}
 	}
-	return _nm_utils_hash_values_to_slist (priv->connections);
+
+	nm_clear_pointer (&loaded_uuids, g_hash_table_destroy);
+	nm_clear_pointer (&dupl_filenames, g_hash_table_destroy);
+
+	_storages_consolidate (self,
+	                       &storages_new,
+	                       FALSE,
+	                       storages_replaced,
+	                       callback,
+	                       user_data);
 }
 
-static gboolean
-load_connection (NMSettingsPlugin *config,
-                 const char *filename)
+gboolean
+nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
+                                   NMConnection *connection,
+                                   gboolean in_memory,
+                                   gboolean is_nm_generated,
+                                   gboolean is_volatile,
+                                   const char *shadowed_storage,
+                                   gboolean shadowed_owned,
+                                   NMSettingsStorage **out_storage,
+                                   NMConnection **out_connection,
+                                   GError **error)
 {
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN ((NMSKeyfilePlugin *) config);
-	NMSKeyfileConnection *connection;
-	gboolean require_extension;
-
-	if (nm_utils_file_is_in_path (filename, nms_keyfile_utils_get_path ()))
-		require_extension = FALSE;
-	else if (nm_utils_file_is_in_path (filename, NM_KEYFILE_PATH_NAME_RUN))
-		require_extension = TRUE;
-	else
-		return FALSE;
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	gs_unref_object NMConnection *reread = NULL;
+	gs_free char *full_filename = NULL;
+	NMSKeyfileStorageType storage_type;
+	gs_unref_object NMSKeyfileStorage *storage = NULL;
+	GError *local = NULL;
+	const char *uuid;
+	gboolean reread_same;
+	struct timespec mtime;
+	char strbuf[100];
+
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (out_storage && !*out_storage);
+	nm_assert (out_connection && !*out_connection);
+
+	nm_assert (   in_memory
+	           || (   !is_nm_generated
+	               && !is_volatile
+	               && !shadowed_storage
+	               && !shadowed_owned));
 
-	if (nm_keyfile_utils_ignore_filename (filename, require_extension))
+	uuid = nm_connection_get_uuid (connection);
+
+	/* Note that even if the caller requests persistent storage, we may switch to in-memory, if
+	 * no /etc directory is configured. */
+	storage_type =   !in_memory && priv->dirname_etc
+	               ? NMS_KEYFILE_STORAGE_TYPE_ETC
+	               : NMS_KEYFILE_STORAGE_TYPE_RUN;
+
+	if (!nms_keyfile_writer_connection (connection,
+	                                    is_nm_generated,
+	                                    is_volatile,
+	                                    shadowed_storage,
+	                                    shadowed_owned,
+	                                      storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
+	                                    ? priv->dirname_etc
+	                                    : priv->dirname_run,
+	                                    _get_plugin_dir (priv),
+	                                    NULL,
+	                                    FALSE,
+	                                    FALSE,
+	                                    nm_sett_util_allow_filename_cb,
+	                                    NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, NULL),
+	                                    &full_filename,
+	                                    &reread,
+	                                    &reread_same,
+	                                    &local)) {
+		_LOGT ("commit: %s (%s) failed to add: %s",
+		       nm_connection_get_uuid (connection),
+		       nm_connection_get_id (connection),
+		       local->message);
+		g_propagate_error (error, local);
 		return FALSE;
+	}
 
-	connection = update_connection (self, NULL, filename, find_by_path (self, filename), TRUE, NULL, NULL);
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	return (connection != NULL);
-}
+	nm_assert (_nm_connection_verify (reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (nm_streq0 (nm_connection_get_uuid (connection), nm_connection_get_uuid (reread)));
 
-static void
-reload_connections (NMSettingsPlugin *config)
-{
-	read_connections (config);
+	nm_assert (full_filename && full_filename[0] == '/');
+	nm_assert (!nm_sett_util_storages_lookup_by_filename (&priv->storages, full_filename));
+
+	_LOGT ("commit: %s (%s) added as \"%s\"%s%s%s%s",
+	       uuid,
+	       nm_connection_get_id (connection),
+	       full_filename,
+	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       NM_PRINT_FMT_QUOTED (shadowed_storage, " (shadows \"", shadowed_storage, shadowed_owned ? "\", owned)" : "\")", ""));
+
+	storage = nms_keyfile_storage_new_connection (self,
+	                                              g_steal_pointer (&reread),
+	                                              full_filename,
+	                                              storage_type,
+	                                              is_nm_generated ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+	                                              is_volatile ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+	                                              shadowed_storage,
+	                                              shadowed_owned ? NM_TERNARY_TRUE : NM_TERNARY_FALSE,
+	                                              nm_sett_util_stat_mtime (full_filename, FALSE, &mtime));
+
+	nm_sett_util_storages_add_take (&priv->storages, g_object_ref (storage));
+
+	*out_connection = nms_keyfile_storage_steal_connection (storage);
+	*out_storage = NM_SETTINGS_STORAGE (g_steal_pointer (&storage));
+
+	return TRUE;
 }
 
-static NMSettingsConnection *
-add_connection (NMSettingsPlugin *config,
+static gboolean
+add_connection (NMSettingsPlugin *plugin,
                 NMConnection *connection,
-                gboolean save_to_disk,
+                NMSettingsStorage **out_storage,
+                NMConnection **out_connection,
                 GError **error)
 {
-	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (config);
-	gs_free char *path = NULL;
+	return nms_keyfile_plugin_add_connection (NMS_KEYFILE_PLUGIN (plugin),
+	                                          connection,
+	                                          FALSE,
+	                                          FALSE,
+	                                          FALSE,
+	                                          NULL,
+	                                          FALSE,
+	                                          out_storage,
+	                                          out_connection,
+	                                          error);
+}
+
+gboolean
+nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
+                                      NMSettingsStorage *storage_x,
+                                      NMConnection *connection,
+                                      gboolean is_nm_generated,
+                                      gboolean is_volatile,
+                                      const char *shadowed_storage,
+                                      gboolean shadowed_owned,
+                                      gboolean force_rename,
+                                      NMSettingsStorage **out_storage,
+                                      NMConnection **out_connection,
+                                      GError **error)
+{
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	NMSKeyfileStorage *storage = NMS_KEYFILE_STORAGE (storage_x);
+	gs_unref_object NMConnection *connection_clone = NULL;
 	gs_unref_object NMConnection *reread = NULL;
+	gs_free char *full_filename = NULL;
+	gs_free_error GError *local = NULL;
+	struct timespec mtime;
+	const char *previous_filename;
+	gboolean reread_same;
+	const char *uuid;
+	char strbuf[100];
+
+	_nm_assert_storage (self, storage, TRUE);
+	nm_assert (NM_IS_CONNECTION (connection));
+	nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (nm_streq (nms_keyfile_storage_get_uuid (storage), nm_connection_get_uuid (connection)));
+	nm_assert (!error || !*error);
+	nm_assert (NM_IN_SET (storage->storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC,
+	                                             NMS_KEYFILE_STORAGE_TYPE_RUN));
+	nm_assert (!storage->is_meta_data);
+	nm_assert (   storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN
+	           || (   !is_nm_generated
+	               && !is_volatile
+	               && !shadowed_storage
+	               && !shadowed_owned));
+	nm_assert (!shadowed_owned || shadowed_storage);
+	nm_assert  (   priv->dirname_etc
+	            || storage->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC);
+
+	previous_filename = nms_keyfile_storage_get_filename (storage);
+	uuid = nms_keyfile_storage_get_uuid (storage);
 
 	if (!nms_keyfile_writer_connection (connection,
-	                                    save_to_disk,
-	                                    NULL,
+	                                    is_nm_generated,
+	                                    is_volatile,
+	                                    shadowed_storage,
+	                                    shadowed_owned,
+	                                      storage->storage_type == NMS_KEYFILE_STORAGE_TYPE_ETC
+	                                    ? priv->dirname_etc
+	                                    : priv->dirname_run,
+	                                    _get_plugin_dir (priv),
+	                                    previous_filename,
+	                                    FALSE,
 	                                    FALSE,
-	                                    &path,
+	                                    nm_sett_util_allow_filename_cb,
+	                                    NM_SETT_UTIL_ALLOW_FILENAME_DATA (&priv->storages, previous_filename),
+	                                    &full_filename,
 	                                    &reread,
-	                                    NULL,
-	                                    error))
-		return NULL;
+	                                    &reread_same,
+	                                    &local)) {
+		_LOGW ("commit: failure to write %s (%s) to \"%s\": %s",
+		       uuid,
+		       nm_connection_get_id (connection_clone),
+		       previous_filename,
+		       local->message);
+		g_propagate_error (error, g_steal_pointer (&local));
+		return FALSE;
+	}
+
+	nm_assert (   full_filename
+	           && nm_streq (full_filename, previous_filename));
+
+	if (   !reread
+	    || reread_same)
+		nm_g_object_ref_set (&reread, connection);
 
-	return NM_SETTINGS_CONNECTION (update_connection (self, reread ?: connection, path, NULL, FALSE, NULL, error));
+	nm_assert (_nm_connection_verify (reread, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (nm_streq (nm_connection_get_uuid (reread), uuid));
+
+	_LOGT ("commit: \"%s\": profile %s (%s) written%s%s%s%s",
+	       full_filename,
+	       uuid,
+	       nm_connection_get_id (connection),
+	       _extra_flags_to_string (strbuf, sizeof (strbuf), is_nm_generated, is_volatile),
+	       NM_PRINT_FMT_QUOTED (shadowed_storage, shadowed_owned ? " (owns \"" : " (shadows \"", shadowed_storage, "\")", ""));
+
+	storage->u.conn_data.is_nm_generated = is_nm_generated;
+	storage->u.conn_data.is_volatile     = is_volatile;
+	storage->u.conn_data.stat_mtime      = *nm_sett_util_stat_mtime (full_filename, FALSE, &mtime);
+	storage->u.conn_data.shadowed_owned  = shadowed_owned;
+
+	*out_storage = g_object_ref (NM_SETTINGS_STORAGE (storage));
+	*out_connection = g_steal_pointer (&reread);
+	return TRUE;
+}
+
+static gboolean
+update_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage,
+                   NMConnection *connection,
+                   NMSettingsStorage **out_storage,
+                   NMConnection **out_connection,
+                   GError **error)
+{
+	return nms_keyfile_plugin_update_connection (NMS_KEYFILE_PLUGIN (plugin),
+	                                             storage,
+	                                             connection,
+	                                             FALSE,
+	                                             FALSE,
+	                                             NULL,
+	                                             FALSE,
+	                                             FALSE,
+	                                             out_storage,
+	                                             out_connection,
+	                                             error);
+}
+
+static gboolean
+delete_connection (NMSettingsPlugin *plugin,
+                   NMSettingsStorage *storage_x,
+                   GError **error)
+{
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (plugin);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+	gs_unref_object NMSKeyfileStorage *storage = g_object_ref (NMS_KEYFILE_STORAGE (storage_x));
+	const char *remove_from_disk_errmsg = NULL;
+	const char *operation_message;
+	const char *previous_filename;
+	const char *uuid;
+	gboolean success = TRUE;
+
+	_nm_assert_storage (self, storage, TRUE);
+	nm_assert (!error || !*error);
+
+	previous_filename = nms_keyfile_storage_get_filename (storage);
+	uuid = nms_keyfile_storage_get_uuid (storage);
+
+	if (!NM_IN_SET (storage->storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC,
+	                                       NMS_KEYFILE_STORAGE_TYPE_RUN)) {
+		nm_utils_error_set (error,
+		                    NM_UTILS_ERROR_UNKNOWN,
+		                    "profile in read-only storage cannot be deleted");
+		success = FALSE;
+		operation_message = "dropped readonly file from memory";
+	} else if (unlink (previous_filename) != 0) {
+		int errsv;
+
+		errsv = errno;
+		if (errsv != ENOENT) {
+			remove_from_disk_errmsg = nm_strerror_native (errsv);
+			operation_message = "failed to delete from disk";
+			success = FALSE;
+			nm_utils_error_set_errno (error,
+			                          errsv,
+			                          "failure to delete \"%s\": %s",
+			                          previous_filename);
+		} else
+			operation_message = "does not exist on disk";
+	} else
+		operation_message = "deleted from disk";
+
+	_LOGT ("commit: deleted \"%s\", %s %s (%s%s%s%s)",
+	       previous_filename,
+	       storage->is_meta_data ? "meta-data" : "profile",
+	       uuid,
+	       operation_message,
+	       NM_PRINT_FMT_QUOTED (remove_from_disk_errmsg, ": ", remove_from_disk_errmsg, "", ""));
+
+	if (success) {
+		nm_sett_util_storages_steal (&priv->storages, storage);
+		nms_keyfile_storage_destroy (storage);
+	}
+
+	return success;
+}
+
+/**
+ * nms_keyfile_plugin_set_nmmeta_tombstone:
+ * @self: the #NMSKeyfilePlugin instance
+ * @simulate: if %TRUE, don't do anything on the filename but just pretend
+ *   that the loaded UUID file gets tracked/untracked. In this mode, the function
+ *   cannot fail (except on hard-failure, see below).
+ *   The idea is that you first try without simulate to write to disk.
+ *   If that fails, you might still want to forcefully pretend (in-memory
+ *   only) that this uuid is marked as tombstone (or not), as desired.
+ *   So you repeate the call with @simulate %TRUE.
+ * @uuid: the UUID for which to write/delete the nmmeta file
+ * @in_memory: the storage type, either /etc or /run. Note that if @self
+ *   has no /etc directory configured, this results in a hard failure.
+ * @set: if %TRUE, write the symlink to point to /dev/null. If %FALSE,
+ *   delete the nmmeta file (if it exists).
+ * @shadowed_storage: a tombstone can also shadow an existing storage.
+ *   In combination with @set and @in_memory, this is allowed to store
+ *   the shadowed storage filename.
+ * @out_storage: (transfer full) (allow-none): the storage element that changes, or
+ *   NULL if nothing changed. Note that the file on disk is already as
+ *   we want to write it, then this still counts as a change. No change only
+ *   means if we try to delete a storage (@set %FALSE) that did not
+ *   exist previously.
+ * @out_hard_failure: (allow-none): on failure, indicate that this is a hard failure.
+ *
+ * The function writes or deletes nmmeta files to/from filesystem. In this case,
+ * the nmmeta files can only be symlinks to /dev/null (to indicate tombstones).
+ *
+ * A hard failure can only happen if @self has no /etc directory configured
+ * and @in_memory is FALSE. In such case even @simulate call fails (which
+ * otherwise would always succeed).
+ * Also, if you get a hard-failure (with @simulate %FALSE) there is no point
+ * in retrying with @simulate %TRUE (contrary to all other cases!).
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self,
+                                         gboolean simulate,
+                                         const char *uuid,
+                                         gboolean in_memory,
+                                         gboolean set,
+                                         const char *shadowed_storage,
+                                         NMSettingsStorage **out_storage,
+                                         gboolean *out_hard_failure)
+{
+	NMSKeyfilePluginPrivate *priv;
+	gboolean hard_failure = FALSE;
+	NMSKeyfileStorage *storage;
+	gs_unref_object NMSKeyfileStorage *storage_result = NULL;
+	gboolean nmmeta_success = FALSE;
+	gs_free char *nmmeta_filename = NULL;
+	NMSKeyfileStorageType storage_type;
+	const char *loaded_path;
+	const char *dirname;
+
+	nm_assert (NMS_IS_KEYFILE_PLUGIN (self));
+	nm_assert (nm_utils_is_uuid (uuid));
+	nm_assert (!out_storage || !*out_storage);
+	nm_assert (!shadowed_storage || (set && in_memory));
+
+	priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
+
+	loaded_path =   set
+	              ? NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL
+	              : NULL;
+
+	if (in_memory) {
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_RUN;
+		dirname = priv->dirname_run;
+	} else {
+		if (!priv->dirname_etc) {
+			_LOGT ("commit: cannot %s%s nmmeta file for %s as there is no /etc directory",
+			       simulate ? "simulate " : "",
+			       loaded_path ? "write" : "delete",
+			       uuid);
+			hard_failure = TRUE;
+			goto out;
+		}
+		storage_type = NMS_KEYFILE_STORAGE_TYPE_ETC;
+		dirname = priv->dirname_etc;
+	}
+
+	if (simulate) {
+		nmmeta_success = TRUE;
+		nmmeta_filename = nms_keyfile_nmmeta_filename (dirname, uuid, FALSE);
+	} else {
+		nmmeta_success = nms_keyfile_nmmeta_write (dirname,
+		                                           uuid,
+		                                           loaded_path,
+		                                           FALSE,
+		                                           shadowed_storage,
+		                                           &nmmeta_filename);
+	}
+
+	_LOGT ("commit: %s nmmeta file \"%s\"%s%s%s%s%s%s %s",
+	       loaded_path ? "writing" : "deleting",
+	       nmmeta_filename,
+	       NM_PRINT_FMT_QUOTED (loaded_path, " (pointing to \"", loaded_path, "\")", ""),
+	       NM_PRINT_FMT_QUOTED (shadowed_storage, " (shadows \"", shadowed_storage, "\")", ""),
+	       simulate
+	       ? "simulated"
+	       : (  nmmeta_success
+	          ? "succeeded"
+	          : "failed"));
+
+	if (!nmmeta_success)
+		goto out;
+
+	storage = nm_sett_util_storages_lookup_by_filename (&priv->storages, nmmeta_filename);
+
+	nm_assert (   !storage
+	           || (   storage->is_meta_data
+	               && storage->storage_type == storage_type
+	               && nm_streq (nms_keyfile_storage_get_uuid (storage), uuid)));
+
+	if (loaded_path) {
+
+		if (!storage) {
+			storage = nms_keyfile_storage_new_tombstone (self,
+			                                             uuid,
+			                                             nmmeta_filename,
+			                                             storage_type,
+			                                             shadowed_storage);
+			nm_sett_util_storages_add_take (&priv->storages, storage);
+		} else {
+			g_free (storage->u.meta_data.shadowed_storage);
+			storage->u.meta_data.shadowed_storage = g_strdup (shadowed_storage);
+		}
+
+		storage_result = g_object_ref (storage);
+	} else {
+		if (storage)
+			storage_result = nm_sett_util_storages_steal (&priv->storages, storage);
+	}
+
+out:
+	nm_assert (!nmmeta_success || !hard_failure);
+	nm_assert (nmmeta_success  || !storage_result);
+
+	NM_SET_OUT (out_hard_failure, hard_failure);
+	NM_SET_OUT (out_storage, (NMSettingsStorage *) g_steal_pointer (&storage_result));
+	return nmmeta_success;
+}
+
+/*****************************************************************************/
+
+static void
+config_changed_cb (NMConfig *config,
+                   NMConfigData *config_data,
+                   NMConfigChangeFlags changes,
+                   NMConfigData *old_data,
+                   NMSKeyfilePlugin *self)
+{
+	gs_free char *old_value = NULL;
+	gs_free char *new_value = NULL;
+
+	old_value = nm_config_data_get_value (old_data,    NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+	new_value = nm_config_data_get_value (config_data, NM_CONFIG_KEYFILE_GROUP_KEYFILE, NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, NM_CONFIG_GET_VALUE_TYPE_SPEC);
+
+	if (!nm_streq0 (old_value, new_value))
+		_nm_settings_plugin_emit_signal_unmanaged_specs_changed (NM_SETTINGS_PLUGIN (self));
 }
 
 static GSList *
 get_unmanaged_specs (NMSettingsPlugin *config)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) config);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (config);
 	gs_free char *value = NULL;
 
 	value = nm_config_data_get_value (nm_config_get_data (priv->config),
@@ -584,13 +1225,48 @@ 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 (nm_str_hash, g_str_equal, g_free, g_object_unref);
+
+	priv->storages = (NMSettUtilStorages) NM_SETT_UTIL_STORAGES_INIT (priv->storages, nms_keyfile_storage_destroy);
+
+	/* dirname_libs are a set of read-only directories with lower priority than /etc or /run.
+	 * There is nothing complicated about having multiple of such directories, so dirname_libs
+	 * is a list (which currently only has at most one directory). */
+	priv->dirname_libs[0] = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_LIB), FALSE);
+	priv->dirname_libs[1] = NULL;
+	priv->dirname_run = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_RUN), FALSE);
+	priv->dirname_etc = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
+	                                              NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+	                                              NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
+	                                              NM_CONFIG_GET_VALUE_STRIP);
+	if (priv->dirname_etc && priv->dirname_etc[0] == '\0') {
+		/* special case: configure an empty keyfile path so that NM has no writable keyfile
+		 * directory. In this case, NM will only honor dirname_libs and dirname_run, meaning
+		 * it cannot persist profile to non-volatile memory. */
+		nm_clear_g_free (&priv->dirname_etc);
+	} else if (!priv->dirname_etc || priv->dirname_etc[0] != '/') {
+		/* either invalid path or unspecified. Use the default. */
+		g_free (priv->dirname_etc);
+		priv->dirname_etc = nm_sd_utils_path_simplify (g_strdup (NM_KEYFILE_PATH_NAME_ETC_DEFAULT), FALSE);
+	} else
+		nm_sd_utils_path_simplify (priv->dirname_etc, FALSE);
+
+	/* no duplicates */
+	if (NM_IN_STRSET (priv->dirname_libs[0], priv->dirname_etc,
+	                                         priv->dirname_run))
+		nm_clear_g_free (&priv->dirname_libs[0]);
+	if (NM_IN_STRSET (priv->dirname_etc, priv->dirname_run))
+		nm_clear_g_free (&priv->dirname_etc);
+
+	nm_assert (!priv->dirname_libs[0] || priv->dirname_libs[0][0] == '/');
+	nm_assert (!priv->dirname_etc     || priv->dirname_etc[0]     == '/');
+	nm_assert ( priv->dirname_run     && priv->dirname_run[0]     == '/');
 }
 
 static void
 constructed (GObject *object)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) object);
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (object);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
 
 	G_OBJECT_CLASS (nms_keyfile_plugin_parent_class)->constructed (object);
 
@@ -599,6 +1275,17 @@ constructed (GObject *object)
 	                              NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME,
 	                              NM_CONFIG_GET_VALUE_RAW))
 		_LOGW ("'hostname' option is deprecated and has no effect");
+
+	if (nm_config_data_has_value (nm_config_get_data_orig (priv->config),
+	                              NM_CONFIG_KEYFILE_GROUP_MAIN,
+	                              NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
+	                              NM_CONFIG_GET_VALUE_RAW))
+		_LOGW ("'monitor-connection-files' option is deprecated and has no effect");
+
+	g_signal_connect (G_OBJECT (priv->config),
+	                  NM_CONFIG_SIGNAL_CONFIG_CHANGED,
+	                  G_CALLBACK (config_changed_cb),
+	                  self);
 }
 
 NMSKeyfilePlugin *
@@ -610,24 +1297,19 @@ nms_keyfile_plugin_new (void)
 static void
 dispose (GObject *object)
 {
-	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE ((NMSKeyfilePlugin *) object);
+	NMSKeyfilePlugin *self = NMS_KEYFILE_PLUGIN (object);
+	NMSKeyfilePluginPrivate *priv = NMS_KEYFILE_PLUGIN_GET_PRIVATE (self);
 
-	if (priv->monitor) {
-		nm_clear_g_signal_handler (priv->monitor, &priv->monitor_id);
+	if (priv->config)
+		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, object);
 
-		g_file_monitor_cancel (priv->monitor);
-		g_clear_object (&priv->monitor);
-	}
+	nm_sett_util_storages_clear (&priv->storages);
 
-	if (priv->connections) {
-		g_hash_table_destroy (priv->connections);
-		priv->connections = NULL;
-	}
+	nm_clear_g_free (&priv->dirname_libs[0]);
+	nm_clear_g_free (&priv->dirname_etc);
+	nm_clear_g_free (&priv->dirname_run);
 
-	if (priv->config) {
-		g_signal_handlers_disconnect_by_func (priv->config, config_changed_cb, object);
-		g_clear_object (&priv->config);
-	}
+	g_clear_object (&priv->config);
 
 	G_OBJECT_CLASS (nms_keyfile_plugin_parent_class)->dispose (object);
 }
@@ -641,9 +1323,11 @@ nms_keyfile_plugin_class_init (NMSKeyfilePluginClass *klass)
 	object_class->constructed = constructed;
 	object_class->dispose     = dispose;
 
-	plugin_class->get_connections     = get_connections;
-	plugin_class->load_connection     = load_connection;
+	plugin_class->plugin_name         = "keyfile";
+	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
 	plugin_class->reload_connections  = reload_connections;
+	plugin_class->load_connections    = load_connections;
 	plugin_class->add_connection      = add_connection;
-	plugin_class->get_unmanaged_specs = get_unmanaged_specs;
+	plugin_class->update_connection   = update_connection;
+	plugin_class->delete_connection   = delete_connection;
 }
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.h b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
index 41f47aac..48440964 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -22,6 +21,11 @@
 #ifndef __NMS_KEYFILE_PLUGIN_H__
 #define __NMS_KEYFILE_PLUGIN_H__
 
+#include "settings/nm-settings-plugin.h"
+#include "settings/nm-settings-storage.h"
+
+#include "nms-keyfile-utils.h"
+
 #define NMS_TYPE_KEYFILE_PLUGIN            (nms_keyfile_plugin_get_type ())
 #define NMS_KEYFILE_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePlugin))
 #define NMS_KEYFILE_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_KEYFILE_PLUGIN, NMSKeyfilePluginClass))
@@ -36,4 +40,36 @@ GType nms_keyfile_plugin_get_type (void);
 
 NMSKeyfilePlugin *nms_keyfile_plugin_new (void);
 
+gboolean nms_keyfile_plugin_add_connection (NMSKeyfilePlugin *self,
+                                            NMConnection *connection,
+                                            gboolean in_memory,
+                                            gboolean is_nm_generated,
+                                            gboolean is_volatile,
+                                            const char *shadowed_storage,
+                                            gboolean shadowed_owned,
+                                            NMSettingsStorage **out_storage,
+                                            NMConnection **out_connection,
+                                            GError **error);
+
+gboolean nms_keyfile_plugin_update_connection (NMSKeyfilePlugin *self,
+                                               NMSettingsStorage *storage,
+                                               NMConnection *connection,
+                                               gboolean is_nm_generated,
+                                               gboolean is_volatile,
+                                               const char *shadowed_storage,
+                                               gboolean shadowed_owned,
+                                               gboolean force_rename,
+                                               NMSettingsStorage **out_storage,
+                                               NMConnection **out_connection,
+                                               GError **error);
+
+gboolean nms_keyfile_plugin_set_nmmeta_tombstone (NMSKeyfilePlugin *self,
+                                                  gboolean simulate,
+                                                  const char *uuid,
+                                                  gboolean in_memory,
+                                                  gboolean set,
+                                                  const char *shadowed_storage,
+                                                  NMSettingsStorage **out_storage,
+                                                  gboolean *out_hard_failure);
+
 #endif /* __NMS_KEYFILE_PLUGIN_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.c b/src/settings/plugins/keyfile/nms-keyfile-reader.c
index 5778f13c..8d1f5599 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -162,6 +161,11 @@ nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
 NMConnection *
 nms_keyfile_reader_from_file (const char *full_filename,
                               const char *profile_dir,
+                              struct stat *out_stat,
+                              NMTernary *out_is_nm_generated,
+                              NMTernary *out_is_volatile,
+                              char **out_shadowed_storage,
+                              NMTernary *out_shadowed_owned,
                               GError **error)
 {
 	gs_unref_keyfile GKeyFile *key_file = NULL;
@@ -171,9 +175,12 @@ nms_keyfile_reader_from_file (const char *full_filename,
 	nm_assert (full_filename && full_filename[0] == '/');
 	nm_assert (!profile_dir || profile_dir[0] == '/');
 
+	NM_SET_OUT (out_is_nm_generated, NM_TERNARY_DEFAULT);
+	NM_SET_OUT (out_is_volatile, NM_TERNARY_DEFAULT);
+
 	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_KEYFILE,
 	                                               full_filename,
-	                                               NULL,
+	                                               out_stat,
 	                                               error))
 		return NULL;
 
@@ -195,6 +202,26 @@ nms_keyfile_reader_from_file (const char *full_filename,
 		connection = NULL;
 	}
 
+	NM_SET_OUT (out_is_nm_generated, nm_key_file_get_boolean (key_file,
+	                                                          NM_KEYFILE_GROUP_NMMETA,
+	                                                          NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+	                                                          NM_TERNARY_DEFAULT));
+
+	NM_SET_OUT (out_is_volatile, nm_key_file_get_boolean (key_file,
+	                                                      NM_KEYFILE_GROUP_NMMETA,
+	                                                      NM_KEYFILE_KEY_NMMETA_VOLATILE,
+	                                                      NM_TERNARY_DEFAULT));
+
+	NM_SET_OUT (out_shadowed_storage, g_key_file_get_string (key_file,
+	                                                         NM_KEYFILE_GROUP_NMMETA,
+	                                                         NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+	                                                         NULL));
+
+	NM_SET_OUT (out_shadowed_owned, nm_key_file_get_boolean (key_file,
+	                                                         NM_KEYFILE_GROUP_NMMETA,
+	                                                         NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+	                                                         NM_TERNARY_DEFAULT));
+
 	return connection;
 }
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-reader.h b/src/settings/plugins/keyfile/nms-keyfile-reader.h
index c0fb06d1..f20e6d93 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-reader.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-reader.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -31,8 +30,15 @@ NMConnection *nms_keyfile_reader_from_keyfile (GKeyFile *key_file,
                                                gboolean verbose,
                                                GError **error);
 
+struct stat;
+
 NMConnection *nms_keyfile_reader_from_file (const char *full_filename,
                                             const char *profile_dir,
+                                            struct stat *out_stat,
+                                            NMTernary *out_is_nm_generated,
+                                            NMTernary *out_is_volatile,
+                                            char **out_shadowed_storage,
+                                            NMTernary *out_shadowed_owned,
                                             GError **error);
 
 #endif /* __NMS_KEYFILE_READER_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.c b/src/settings/plugins/keyfile/nms-keyfile-storage.c
new file mode 100644
index 00000000..d68d60c8
--- /dev/null
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -0,0 +1,277 @@
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nms-keyfile-storage.h"
+
+#include "nm-utils.h"
+#include "nm-core-internal.h"
+#include "nms-keyfile-plugin.h"
+
+/*****************************************************************************/
+
+struct _NMSKeyfileStorageClass {
+	NMSettingsStorageClass parent;
+};
+
+G_DEFINE_TYPE (NMSKeyfileStorage, nms_keyfile_storage, NM_TYPE_SETTINGS_STORAGE)
+
+/*****************************************************************************/
+
+void
+nms_keyfile_storage_copy_content (NMSKeyfileStorage *dst,
+                                  const NMSKeyfileStorage *src)
+{
+	nm_assert (src != dst);
+	nm_assert (nm_streq (nms_keyfile_storage_get_uuid (dst), nms_keyfile_storage_get_uuid (src)));
+	nm_assert (   nms_keyfile_storage_get_filename (dst)
+	           && nm_streq (nms_keyfile_storage_get_filename (dst), nms_keyfile_storage_get_filename (src)));
+	nm_assert (dst->storage_type == src->storage_type);
+	nm_assert (dst->is_meta_data == src->is_meta_data);
+
+	if (dst->is_meta_data) {
+		gs_free char *shadowed_storage_to_free = NULL;
+
+		shadowed_storage_to_free = g_steal_pointer (&dst->u.meta_data.shadowed_storage);
+		dst->u.meta_data = src->u.meta_data;
+		dst->u.meta_data.shadowed_storage = g_strdup (dst->u.meta_data.shadowed_storage);
+	} else {
+		gs_unref_object NMConnection *connection_to_free = NULL;
+		gs_free char *shadowed_storage_to_free = NULL;
+
+		connection_to_free = g_steal_pointer (&dst->u.conn_data.connection);
+		shadowed_storage_to_free = g_steal_pointer (&dst->u.conn_data.shadowed_storage);
+		dst->u.conn_data = src->u.conn_data;
+		nm_g_object_ref (dst->u.conn_data.connection);
+		dst->u.conn_data.shadowed_storage = g_strdup (dst->u.conn_data.shadowed_storage);
+	}
+}
+
+NMConnection *
+nms_keyfile_storage_steal_connection (NMSKeyfileStorage *self)
+{
+	nm_assert (NMS_IS_KEYFILE_STORAGE (self));
+	nm_assert (   self->is_meta_data
+	           || NM_IS_CONNECTION (self->u.conn_data.connection));
+
+	return   self->is_meta_data
+	       ? NULL
+	       : g_steal_pointer (&self->u.conn_data.connection);
+}
+
+/*****************************************************************************/
+
+static int
+cmp_fcn (const NMSKeyfileStorage *a,
+         const NMSKeyfileStorage *b)
+{
+	nm_assert (NMS_IS_KEYFILE_STORAGE (a));
+	nm_assert (NMS_IS_KEYFILE_STORAGE (b));
+	nm_assert (a != b);
+
+	/* sort by storage-type, which also has a numeric value according to their
+	 * (inverse) priority. */
+	NM_CMP_FIELD_UNSAFE (b, a, storage_type);
+
+	/* meta-data is more important. */
+	NM_CMP_FIELD_UNSAFE (a, b, is_meta_data);
+
+	if (a->is_meta_data) {
+		nm_assert (nm_streq (nms_keyfile_storage_get_filename (a), nms_keyfile_storage_get_filename (b)));
+		NM_CMP_FIELD_UNSAFE (a, b, u.meta_data.is_tombstone);
+	} else {
+		/* newer files are more important. */
+		NM_CMP_FIELD (a, b, u.conn_data.stat_mtime.tv_sec);
+		NM_CMP_FIELD (a, b, u.conn_data.stat_mtime.tv_nsec);
+
+		NM_CMP_DIRECT_STRCMP (nms_keyfile_storage_get_filename (a), nms_keyfile_storage_get_filename (b));
+	}
+
+	return 0;
+}
+
+/*****************************************************************************/
+
+static void
+nms_keyfile_storage_init (NMSKeyfileStorage *self)
+{
+}
+
+static NMSKeyfileStorage *
+_storage_new (NMSKeyfilePlugin *plugin,
+              const char *uuid,
+              const char *filename,
+              gboolean is_meta_data,
+              NMSKeyfileStorageType storage_type)
+
+{
+	NMSKeyfileStorage *self;
+
+	nm_assert (NMS_IS_KEYFILE_PLUGIN (plugin));
+	nm_assert (nm_utils_is_uuid (uuid));
+	nm_assert (filename && filename[0] == '/');
+
+	self = g_object_new (NMS_TYPE_KEYFILE_STORAGE,
+	                     NM_SETTINGS_STORAGE_PLUGIN, plugin,
+	                     NM_SETTINGS_STORAGE_UUID, uuid,
+	                     NM_SETTINGS_STORAGE_FILENAME, filename,
+	                     NULL);
+
+	*((bool *) &self->is_meta_data) = is_meta_data;
+	*((NMSKeyfileStorageType *) &self->storage_type) = storage_type;
+
+	return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_tombstone (NMSKeyfilePlugin *plugin,
+                                   const char *uuid,
+                                   const char *filename,
+                                   NMSKeyfileStorageType storage_type,
+                                   const char *shadowed_storage)
+{
+	NMSKeyfileStorage *self;
+
+	nm_assert (nm_utils_is_uuid (uuid));
+	nm_assert (filename && filename[0] == '/');
+	nm_assert (nms_keyfile_nmmeta_check_filename (filename, NULL));
+	nm_assert (NM_IN_SET (storage_type, NMS_KEYFILE_STORAGE_TYPE_ETC,
+	                                    NMS_KEYFILE_STORAGE_TYPE_RUN));
+
+	self = _storage_new (plugin, uuid, filename, TRUE, storage_type);
+	self->u.meta_data.is_tombstone = TRUE;
+	if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN)
+		self->u.meta_data.shadowed_storage = g_strdup (shadowed_storage);
+	return self;
+}
+
+NMSKeyfileStorage *
+nms_keyfile_storage_new_connection (NMSKeyfilePlugin *plugin,
+                                    NMConnection *connection_take /* pass reference */,
+                                    const char *filename,
+                                    NMSKeyfileStorageType storage_type,
+                                    NMTernary is_nm_generated_opt,
+                                    NMTernary is_volatile_opt,
+                                    const char *shadowed_storage,
+                                    NMTernary shadowed_owned_opt,
+                                    const struct timespec *stat_mtime)
+{
+	NMSKeyfileStorage *self;
+
+	nm_assert (NMS_IS_KEYFILE_PLUGIN (plugin));
+	nm_assert (NM_IS_CONNECTION (connection_take));
+	nm_assert (_nm_connection_verify (connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
+	nm_assert (filename && filename[0] == '/');
+	nm_assert (   storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN
+	           && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST);
+	nmtst_connection_assert_unchanging (connection_take);
+
+	self = _storage_new (plugin, nm_connection_get_uuid (connection_take), filename, FALSE, storage_type);
+
+	self->u.conn_data.connection = connection_take; /* take reference. */
+
+	self->u.conn_data.shadowed_storage = g_strdup (shadowed_storage);
+
+	if (stat_mtime)
+		self->u.conn_data.stat_mtime = *stat_mtime;
+
+	if (storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+		self->u.conn_data.is_nm_generated = (is_nm_generated_opt == NM_TERNARY_TRUE);
+		self->u.conn_data.is_volatile     = (is_volatile_opt == NM_TERNARY_TRUE);
+		self->u.conn_data.shadowed_owned  =    shadowed_storage
+		                                    && (shadowed_owned_opt == NM_TERNARY_TRUE);
+	}
+
+	return self;
+}
+
+static void
+_storage_clear (NMSKeyfileStorage *self)
+{
+	c_list_unlink (&self->parent._storage_lst);
+	c_list_unlink (&self->parent._storage_by_uuid_lst);
+	if (self->is_meta_data)
+		nm_clear_g_free (&self->u.meta_data.shadowed_storage);
+	else {
+		g_clear_object (&self->u.conn_data.connection);
+		nm_clear_g_free (&self->u.conn_data.shadowed_storage);
+		self->u.conn_data.shadowed_owned = FALSE;
+	}
+}
+
+static void
+dispose (GObject *object)
+{
+	NMSKeyfileStorage *self = NMS_KEYFILE_STORAGE (object);
+
+	_storage_clear (self);
+
+	G_OBJECT_CLASS (nms_keyfile_storage_parent_class)->dispose (object);
+}
+
+void
+nms_keyfile_storage_destroy (NMSKeyfileStorage *self)
+{
+	_storage_clear (self);
+	g_object_unref (self);
+}
+
+static void
+nms_keyfile_storage_class_init (NMSKeyfileStorageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMSettingsStorageClass *storage_class = NM_SETTINGS_STORAGE_CLASS (klass);
+
+	object_class->dispose = dispose;
+
+	storage_class->cmp_fcn = (int (*) (NMSettingsStorage *, NMSettingsStorage *)) cmp_fcn;
+}
+
+/*****************************************************************************/
+
+#include "settings/nm-settings-connection.h"
+
+void
+nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
+                                     NMSettingsConnectionIntFlags *sett_flags,
+                                     NMSettingsConnectionIntFlags *sett_mask)
+{
+	NMSKeyfileStorage *s;
+
+	*sett_flags = NM_SETTINGS_CONNECTION_INT_FLAGS_NONE;
+	*sett_mask =   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED
+	             | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+
+	if (!NMS_IS_KEYFILE_STORAGE (self))
+		return;
+
+	s = NMS_KEYFILE_STORAGE (self);
+
+	if (s->is_meta_data)
+		return;
+	if (s->storage_type != NMS_KEYFILE_STORAGE_TYPE_RUN)
+		return;
+
+	if (s->u.conn_data.is_nm_generated)
+		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED;
+
+	if (s->u.conn_data.is_volatile)
+		*sett_flags |= NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE;
+}
diff --git a/src/settings/plugins/keyfile/nms-keyfile-storage.h b/src/settings/plugins/keyfile/nms-keyfile-storage.h
new file mode 100644
index 00000000..2252b47b
--- /dev/null
+++ b/src/settings/plugins/keyfile/nms-keyfile-storage.h
@@ -0,0 +1,264 @@
+/* NetworkManager system settings service - keyfile plugin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef __NMS_KEYFILE_STORAGE_H__
+#define __NMS_KEYFILE_STORAGE_H__
+
+#include "c-list/src/c-list.h"
+#include "settings/nm-settings-storage.h"
+#include "nms-keyfile-utils.h"
+
+/*****************************************************************************/
+
+#define NMS_TYPE_KEYFILE_STORAGE            (nms_keyfile_storage_get_type ())
+#define NMS_KEYFILE_STORAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorage))
+#define NMS_KEYFILE_STORAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+#define NMS_IS_KEYFILE_STORAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_IS_KEYFILE_STORAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMS_TYPE_KEYFILE_STORAGE))
+#define NMS_KEYFILE_STORAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NMS_TYPE_KEYFILE_STORAGE, NMSKeyfileStorageClass))
+
+typedef struct {
+	/* whether this is a tombstone to hide a UUID (via symlink to /dev/null). */
+	char *shadowed_storage;
+	bool is_tombstone:1;
+} NMSettingsMetaData;
+
+typedef struct {
+	NMSettingsStorage parent;
+
+	/* The connection. Note that there are tombstones (loaded-uuid files to /dev/null)
+	 * that don't have a connection.
+	 *
+	 * Also, we don't actually remember the loaded connection after returning it
+	 * to NMSettings. So, also for regular storages (non-tombstones) this field
+	 * is often cleared. */
+	union {
+		struct {
+			NMConnection *connection;
+
+			/* when we move a profile from permanent storage to unsaved (/run), then
+			 * we may leave the profile on disk (depending on options for Update2()).
+			 *
+			 * Later, when we save the profile again to disk, we want to re-use that filename.
+			 * Likewise, we delete the (now in-memory) profile, we may want to also delete
+			 * the original filename.
+			 *
+			 * This is the original filename, and we store it inside [.nmmeta] in the
+			 * keyfile in /run. Note that we don't store this in the .nmmeta file, because
+			 * the information is tied to the particular keyfile in /run, not to all UUIDs
+			 * in general. */
+			char *shadowed_storage;
+
+			/* the timestamp (stat's mtime) of the keyfile. For meta-data this
+			 * is irrelevant. The purpose is that if the same storage type (directory) has
+			 * multiple files with the same UUID, then the newer file gets preferred. */
+			struct timespec stat_mtime;
+
+			/* these flags are only relevant for storages with %NMS_KEYFILE_STORAGE_TYPE_RUN
+			 * (and non-metadata). This is to persist and reload these settings flags to
+			 * /run.
+			 *
+			 * Note that these flags are not stored in as meta-data. The reason is that meta-data
+			 * is per UUID. But these flags are only relevant for a particular keyfile on disk.
+			 * That is, it must be tied to the actual keyfile, and not to the UUID. */
+			bool is_nm_generated:1;
+			bool is_volatile:1;
+
+			/* if shadowed_storage is set, then this flag indicates whether the file
+			 * is owned. The difference comes into play when deleting the in-memory,
+			 * shadowing profile: a owned profile will also be deleted. */
+			bool shadowed_owned:1;
+
+		} conn_data;
+
+		/* the content from the .nmmeta file. Note that the nmmeta file has the UUID
+		 * in the filename, that means there can be only two variants of this file:
+		 * in /etc and in /run. As such, this is really meta-data about the entire profile
+		 * (the UUID), and not about the individual keyfile. */
+		NMSettingsMetaData meta_data;
+
+	} u;
+
+	/* The storage type. This is directly related to the filename. Since
+	 * the filename cannot change, this value is unchanging. */
+	const NMSKeyfileStorageType storage_type;
+
+	/* whether union "u" has meta_data or conn_data. Since the type of the storage
+	 * depends on the (immutable) filename, this is also const. */
+	const bool is_meta_data;
+
+	/* this flag is only used during reload to mark and prune old entries. */
+	bool is_dirty:1;
+
+} NMSKeyfileStorage;
+
+typedef struct _NMSKeyfileStorageClass NMSKeyfileStorageClass;
+
+GType nms_keyfile_storage_get_type (void);
+
+struct _NMSKeyfilePlugin;
+
+NMSKeyfileStorage *nms_keyfile_storage_new_tombstone (struct _NMSKeyfilePlugin *self,
+                                                      const char *uuid,
+                                                      const char *filename,
+                                                      NMSKeyfileStorageType storage_type,
+                                                      const char *shadowed_storage);
+
+NMSKeyfileStorage *nms_keyfile_storage_new_connection (struct _NMSKeyfilePlugin *self,
+                                                       NMConnection *connection_take /* pass reference */,
+                                                       const char *filename,
+                                                       NMSKeyfileStorageType storage_type,
+                                                       NMTernary is_nm_generated_opt,
+                                                       NMTernary is_volatile_opt,
+                                                       const char *shadowed_storage,
+                                                       NMTernary shadowed_owned_opt,
+                                                       const struct timespec *stat_mtime);
+
+void nms_keyfile_storage_destroy (NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+void nms_keyfile_storage_copy_content (NMSKeyfileStorage *dst,
+                                       const NMSKeyfileStorage *src);
+
+NMConnection *nms_keyfile_storage_steal_connection (NMSKeyfileStorage *storage);
+
+/*****************************************************************************/
+
+static inline const char *
+nms_keyfile_storage_get_uuid (const NMSKeyfileStorage *self)
+{
+	return nm_settings_storage_get_uuid ((const NMSettingsStorage *) self);
+}
+
+static inline const char *
+nms_keyfile_storage_get_filename (const NMSKeyfileStorage *self)
+{
+	return nm_settings_storage_get_filename ((const NMSettingsStorage *) self);
+}
+
+/*****************************************************************************/
+
+static inline gboolean
+nm_settings_storage_is_keyfile_run (const NMSettingsStorage *self)
+{
+	return    NMS_IS_KEYFILE_STORAGE (self)
+	       && (((NMSKeyfileStorage *) self)->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN);
+}
+
+static inline gboolean
+nm_settings_storage_is_keyfile_lib (const NMSettingsStorage *self)
+{
+	return    NMS_IS_KEYFILE_STORAGE (self)
+	       && (((NMSKeyfileStorage *) self)->storage_type >= NMS_KEYFILE_STORAGE_TYPE_LIB_BASE);
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data (const NMSettingsStorage *storage)
+{
+	const NMSKeyfileStorage *self;
+
+	if (!NMS_IS_KEYFILE_STORAGE (storage))
+		return NULL;
+
+	self = (NMSKeyfileStorage *) storage;
+
+	if (!self->is_meta_data)
+		return NULL;
+
+	return &self->u.meta_data;
+}
+
+static inline const NMSettingsMetaData *
+nm_settings_storage_is_meta_data_alive (const NMSettingsStorage *storage)
+{
+	const NMSettingsMetaData *meta_data;
+
+	meta_data = nm_settings_storage_is_meta_data (storage);
+
+	if (!meta_data)
+		return NULL;
+
+	/* Regular (all other) storages are alive as long as they report a NMConnection, and
+	 * they will be dropped, once they have no more connection.
+	 *
+	 * Meta-data storages are special: they never report a NMConnection.
+	 * So, a meta-data storage is alive as long as it is tracked by the
+	 * settings plugin.
+	 *
+	 * This function is used to ckeck for that. */
+
+	if (c_list_is_empty (&storage->_storage_lst))
+		return NULL;
+
+	return meta_data;
+}
+
+static inline const char *
+nm_settings_storage_get_shadowed_storage (const NMSettingsStorage *storage,
+                                          gboolean *out_shadowed_owned)
+{
+	if (NMS_IS_KEYFILE_STORAGE (storage)) {
+		const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+		if (self->storage_type == NMS_KEYFILE_STORAGE_TYPE_RUN) {
+			if (!self->is_meta_data) {
+				if (self->u.conn_data.shadowed_storage) {
+					NM_SET_OUT (out_shadowed_owned, self->u.conn_data.shadowed_owned);
+					return self->u.conn_data.shadowed_storage;
+				}
+			} else {
+				NM_SET_OUT (out_shadowed_owned, FALSE);
+				return self->u.meta_data.shadowed_storage;
+			}
+		}
+	}
+
+	NM_SET_OUT (out_shadowed_owned, FALSE);
+	return NULL;
+}
+
+static inline const char *
+nm_settings_storage_get_filename_for_shadowed_storage (const NMSettingsStorage *storage)
+{
+	g_return_val_if_fail (NM_IS_SETTINGS_STORAGE (storage), NULL);
+
+	if (!storage->_filename)
+		return NULL;
+
+	if (NMS_IS_KEYFILE_STORAGE (storage)) {
+		const NMSKeyfileStorage *self = (const NMSKeyfileStorage *) storage;
+
+		if (   self->is_meta_data
+		    || self->storage_type != NMS_KEYFILE_STORAGE_TYPE_ETC)
+			return NULL;
+	}
+
+	return storage->_filename;
+}
+
+/*****************************************************************************/
+
+enum _NMSettingsConnectionIntFlags;
+
+void nm_settings_storage_load_sett_flags (NMSettingsStorage *self,
+                                          enum _NMSettingsConnectionIntFlags *sett_flags,
+                                          enum _NMSettingsConnectionIntFlags *sett_mask);
+
+#endif /* __NMS_KEYFILE_STORAGE_H__ */
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.c b/src/settings/plugins/keyfile/nms-keyfile-utils.c
index 3c4b0288..ea03e1b6 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -15,7 +14,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2010 Red Hat, Inc.
+ * (C) Copyright 2010 - 2018 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -25,6 +24,7 @@
 #include <stdlib.h>
 #include <sys/stat.h>
 
+#include "nm-glib-aux/nm-io-utils.h"
 #include "nm-keyfile-internal.h"
 #include "nm-utils.h"
 #include "nm-setting-wired.h"
@@ -34,108 +34,155 @@
 
 /*****************************************************************************/
 
+#define NMMETA_KF_GROUP_NAME_NMMETA                 "nmmeta"
+#define NMMETA_KF_KEY_NAME_NMMETA_UUID              "uuid"
+#define NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH       "loaded-path"
+#define NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE  "shadowed-storage"
+
+/*****************************************************************************/
+
+const char *
+nms_keyfile_nmmeta_check_filename (const char *filename,
+                                   guint *out_uuid_len)
+{
+	const char *uuid;
+	const char *s;
+	gsize len;
+
+	s = strrchr (filename, '/');
+	if (s)
+		filename = &s[1];
+
+	len = strlen (filename);
+	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA)
+	    || memcmp (&filename[len - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA)],
+	               NM_KEYFILE_PATH_SUFFIX_NMMETA,
+	               NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA)) != 0) {
+		/* the filename does not have the right suffix. */
+		return NULL;
+	}
+
+	len -= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMMETA);
+
+	if (!NM_IN_SET (len, 36, 40)) {
+		/* the remaining part of the filename has not the right length to
+		 * contain a UUID (according to nm_utils_is_uuid()). */
+		return NULL;
+	}
+
+	uuid = nm_strndup_a (100, filename, len, NULL);
+	if (!nm_utils_is_uuid (uuid))
+		return NULL;
+
+	NM_SET_OUT (out_uuid_len, len);
+	return filename;
+}
+
 char *
-nms_keyfile_loaded_uuid_filename (const char *dirname,
-                                  const char *uuid,
-                                  gboolean temporary)
+nms_keyfile_nmmeta_filename (const char *dirname,
+                             const char *uuid,
+                             gboolean temporary)
 {
 	char filename[250];
+	char *s;
 
 	nm_assert (dirname && dirname[0] == '/');
-	nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+	nm_assert (   nm_utils_is_uuid (uuid)
+	           && !strchr (uuid, '/'));
 
 	if (g_snprintf (filename,
 	                sizeof (filename),
-	                "%s%s%s%s",
-	                NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	                "%s%s%s",
 	                uuid,
-	                NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
+	                NM_KEYFILE_PATH_SUFFIX_NMMETA,
 	                temporary ? "~" : "") >= sizeof (filename)) {
-		/* valid uuids are limited in length. The buffer should always be large
-		 * enough. */
+		/* valid uuids are limited in length (nm_utils_is_uuid). The buffer should always
+		 * be large enough. */
 		nm_assert_not_reached ();
-		return NULL;
 	}
 
-	return g_build_filename (dirname, filename, NULL);
+	s = g_build_filename (dirname, filename, NULL);
+
+	nm_assert (nm_keyfile_utils_ignore_filename (s, FALSE));
+
+	return s;
 }
 
 gboolean
-nms_keyfile_loaded_uuid_read (const char *dirname,
-                              const char *filename,
-                              char **out_full_filename,
-                              char **out_uuid,
-                              char **out_loaded_path)
+nms_keyfile_nmmeta_read (const char *dirname,
+                         const char *filename,
+                         char **out_full_filename,
+                         char **out_uuid,
+                         char **out_loaded_path,
+                         char **out_shadowed_storage,
+                         struct stat *out_st)
 {
 	const char *uuid;
-	const char *tmp;
-	gsize len;
+	guint uuid_len;
 	gs_free char *full_filename = NULL;
-	gs_free char *ln = NULL;
+	gs_free char *loaded_path = NULL;
+	gs_free char *shadowed_storage = NULL;
+	struct stat st_stack;
+	struct stat *st = out_st ?: &st_stack;
 
 	nm_assert (dirname && dirname[0] == '/');
 	nm_assert (filename && filename[0] && !strchr (filename, '/'));
 
-	if (filename[0] != '.') {
-		/* the hidden-uuid filename must start with '.'. That is,
-		 * so that it does not conflict with regular keyfiles according
-		 * to nm_keyfile_utils_ignore_filename(). */
+	uuid = nms_keyfile_nmmeta_check_filename (filename, &uuid_len);
+	if (!uuid)
 		return FALSE;
-	}
 
-	len = strlen (filename);
-	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)
-	    || memcmp (filename, NM_KEYFILE_PATH_PREFIX_NMLOADED, NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)) != 0) {
-		/* the filename does not have the right prefix. */
+	full_filename = g_build_filename (dirname, filename, NULL);
+
+	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMMETA,
+	                                               full_filename,
+	                                               st,
+	                                               NULL))
 		return FALSE;
-	}
 
-	tmp = &filename[NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED)];
-	len -= NM_STRLEN (NM_KEYFILE_PATH_PREFIX_NMLOADED);
+	if (S_ISREG (st->st_mode)) {
+		gs_unref_keyfile GKeyFile *kf = NULL;
+		gs_free char *v_uuid = NULL;
 
-	if (   len <= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)
-	    || memcmp (&tmp[len - NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)],
-	               NM_KEYFILE_PATH_SUFFIX_NMCONNECTION,
-	               NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) != 0) {
-		/* the file does not have the right suffix. */
-		return FALSE;
-	}
-	len -= NM_STRLEN (NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+		kf = g_key_file_new ();
 
-	if (!NM_IN_SET (len, 36, 40)) {
-		/* the remaining part of the filename has not the right length to
-		 * contain a UUID (according to nm_utils_is_uuid()). */
-		return FALSE;
-	}
+		if (!g_key_file_load_from_file (kf, full_filename, G_KEY_FILE_NONE, NULL))
+			return FALSE;
 
-	uuid = nm_strndup_a (100, tmp, len, NULL);
-	if (!nm_utils_is_uuid (uuid))
-		return FALSE;
+		v_uuid = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_UUID, NULL);
+		if (!nm_streq0 (v_uuid, uuid))
+			return FALSE;
 
-	full_filename = g_build_filename (dirname, filename, NULL);
+		loaded_path = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, NULL);
+		shadowed_storage = g_key_file_get_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, NULL);
 
-	if (!nms_keyfile_utils_check_file_permissions (NMS_KEYFILE_FILETYPE_NMLOADED,
-	                                               full_filename,
-	                                               NULL,
-	                                               NULL))
-		return FALSE;
+		if (   !loaded_path
+		    && !shadowed_storage) {
+			/* if there is no useful information in the file, it is the same as if
+			 * the file is not present. Signal failure. */
+			return FALSE;
+		}
 
-	ln = nm_utils_read_link_absolute (full_filename, NULL);
-	if (!ln)
-		return FALSE;
+	} else {
+		loaded_path = nm_utils_read_link_absolute (full_filename, NULL);
+		if (!loaded_path)
+			return FALSE;
+	}
 
-	NM_SET_OUT (out_uuid, g_strdup (uuid));
+	NM_SET_OUT (out_uuid, g_strndup (uuid, uuid_len));
 	NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
-	NM_SET_OUT (out_loaded_path, g_steal_pointer (&ln));
+	NM_SET_OUT (out_loaded_path, g_steal_pointer (&loaded_path));
+	NM_SET_OUT (out_shadowed_storage, g_steal_pointer (&shadowed_storage));
 	return TRUE;
 }
 
 gboolean
-nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
-                                        char **out_dirname,
-                                        char **out_filename,
-                                        char **out_uuid,
-                                        char **out_loaded_path)
+nms_keyfile_nmmeta_read_from_file (const char *full_filename,
+                                   char **out_dirname,
+                                   char **out_filename,
+                                   char **out_uuid,
+                                   char **out_loaded_path,
+                                   char **out_shadowed_storage)
 {
 	gs_free char *dirname = NULL;
 	gs_free char *filename = NULL;
@@ -145,11 +192,13 @@ nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
 	filename = g_path_get_basename (full_filename);
 	dirname = g_path_get_dirname (full_filename);
 
-	if (!nms_keyfile_loaded_uuid_read (dirname,
-	                                   filename,
-	                                   NULL,
-	                                   out_uuid,
-	                                   out_loaded_path))
+	if (!nms_keyfile_nmmeta_read (dirname,
+	                              filename,
+	                              NULL,
+	                              out_uuid,
+	                              out_loaded_path,
+	                              out_shadowed_storage,
+	                              NULL))
 		return FALSE;
 
 	NM_SET_OUT (out_dirname, g_steal_pointer (&dirname));
@@ -158,20 +207,23 @@ nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
 }
 
 gboolean
-nms_keyfile_loaded_uuid_write (const char *dirname,
-                               const char *uuid,
-                               const char *loaded_path,
-                               gboolean allow_relative,
-                               char **out_full_filename)
+nms_keyfile_nmmeta_write (const char *dirname,
+                          const char *uuid,
+                          const char *loaded_path,
+                          gboolean loaded_path_allow_relative,
+                          const char *shadowed_storage,
+                          char **out_full_filename)
 {
 	gs_free char *full_filename_tmp = NULL;
 	gs_free char *full_filename = NULL;
 
 	nm_assert (dirname && dirname[0] == '/');
-	nm_assert (uuid && nm_utils_is_uuid (uuid) && !strchr (uuid, '/'));
+	nm_assert (   nm_utils_is_uuid (uuid)
+	           && !strchr (uuid, '/'));
 	nm_assert (!loaded_path || loaded_path[0] == '/');
+	nm_assert (!shadowed_storage || loaded_path);
 
-	full_filename_tmp = nms_keyfile_loaded_uuid_filename (dirname, uuid, TRUE);
+	full_filename_tmp = nms_keyfile_nmmeta_filename (dirname, uuid, TRUE);
 
 	nm_assert (g_str_has_suffix (full_filename_tmp, "~"));
 	nm_assert (nm_utils_file_is_in_path (full_filename_tmp, dirname));
@@ -188,7 +240,7 @@ nms_keyfile_loaded_uuid_write (const char *dirname,
 		return success;
 	}
 
-	if (allow_relative) {
+	if (loaded_path_allow_relative) {
 		const char *f;
 
 		f = nm_utils_file_is_in_path (loaded_path, dirname);
@@ -199,18 +251,40 @@ nms_keyfile_loaded_uuid_write (const char *dirname,
 		}
 	}
 
-	if (symlink (loaded_path, full_filename_tmp) != 0) {
-		full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
-		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
-		return FALSE;
-	}
+	full_filename = g_strndup (full_filename_tmp, strlen (full_filename_tmp) - 1);
 
-	full_filename = g_strdup (full_filename_tmp);
-	full_filename[strlen (full_filename) - 1] = '\0';
-	if (rename (full_filename_tmp, full_filename) != 0) {
-		(void) unlink (full_filename_tmp);
-		NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
-		return FALSE;
+	if (shadowed_storage) {
+		gs_unref_keyfile GKeyFile *kf = NULL;
+		gs_free char *contents = NULL;
+		gsize length;
+
+		kf = g_key_file_new ();
+
+		g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_UUID, uuid);
+		g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_LOADED_PATH, loaded_path);
+		g_key_file_set_string (kf, NMMETA_KF_GROUP_NAME_NMMETA, NMMETA_KF_KEY_NAME_NMMETA_SHADOWED_STORAGE, shadowed_storage);
+
+		contents = g_key_file_to_data (kf, &length, NULL);
+
+		if (!nm_utils_file_set_contents (full_filename, contents, length, 0600, NULL)) {
+			NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+			return FALSE;
+		}
+	} else {
+		/* we only have the "loaded_path" to store. That is commonly used for the tombstones to
+		 * link to /dev/null. A symlink is sufficient to store that ammount of information.
+		 * No need to bother with a keyfile. */
+		if (symlink (loaded_path, full_filename_tmp) != 0) {
+			full_filename_tmp[strlen (full_filename_tmp) - 1] = '\0';
+			NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename_tmp));
+			return FALSE;
+		}
+
+		if (rename (full_filename_tmp, full_filename) != 0) {
+			(void) unlink (full_filename_tmp);
+			NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
+			return FALSE;
+		}
 	}
 
 	NM_SET_OUT (out_full_filename, g_steal_pointer (&full_filename));
@@ -232,10 +306,11 @@ nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
 			                     "file is not a regular file");
 			return FALSE;
 		}
-	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
-		if (!S_ISLNK (st->st_mode)) {
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
+		if (   !S_ISLNK (st->st_mode)
+		    && !S_ISREG (st->st_mode)) {
 			g_set_error_literal (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
-			                     "file is not a slink");
+			                     "file is neither a symlink nor a regular file");
 			return FALSE;
 		}
 	} else
@@ -249,7 +324,7 @@ nms_keyfile_utils_check_file_permissions_stat (NMSKeyfileFiletype filetype,
 			return FALSE;
 		}
 
-		if (   filetype == NMS_KEYFILE_FILETYPE_KEYFILE
+		if (   S_ISREG (st->st_mode)
 		    && (st->st_mode & 0077)) {
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
 			             "File permissions (%03o) are insecure",
@@ -279,7 +354,7 @@ nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
 			             "cannot access file: %s", nm_strerror_native (errsv));
 			return FALSE;
 		}
-	} else if (filetype == NMS_KEYFILE_FILETYPE_NMLOADED) {
+	} else if (filetype == NMS_KEYFILE_FILETYPE_NMMETA) {
 		if (lstat (filename, &st) != 0) {
 			errsv = errno;
 			g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -295,22 +370,3 @@ nms_keyfile_utils_check_file_permissions (NMSKeyfileFiletype filetype,
 	NM_SET_OUT (out_st, st);
 	return TRUE;
 }
-
-/*****************************************************************************/
-
-const char *
-nms_keyfile_utils_get_path (void)
-{
-	static char *path = NULL;
-
-	if (G_UNLIKELY (!path)) {
-		path = nm_config_data_get_value (NM_CONFIG_GET_DATA_ORIG,
-		                                 NM_CONFIG_KEYFILE_GROUP_KEYFILE,
-		                                 NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
-		                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
-		if (!path)
-			path = g_strdup (""NM_KEYFILE_PATH_NAME_ETC_DEFAULT"");
-	}
-	return path;
-}
-
diff --git a/src/settings/plugins/keyfile/nms-keyfile-utils.h b/src/settings/plugins/keyfile/nms-keyfile-utils.h
index bc601dad..723c4436 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-utils.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-utils.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service
  *
  * This program is free software; you can redistribute it and/or modify
@@ -15,7 +14,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2010-2016 Red Hat, Inc.
+ * (C) Copyright 2010 - 2018 Red Hat, Inc.
  */
 
 #ifndef __NMS_KEYFILE_UTILS_H__
@@ -23,42 +22,56 @@
 
 #include "NetworkManagerUtils.h"
 
-#define NMS_KEYFILE_CONNECTION_LOG_PATH(path)  ((path) ?: "in-memory")
-#define NMS_KEYFILE_CONNECTION_LOG_FMT         "%s (%s,\"%s\")"
-#define NMS_KEYFILE_CONNECTION_LOG_ARG(con)    NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con))
-#define NMS_KEYFILE_CONNECTION_LOG_FMTD        "%s (%s,\"%s\",%p)"
-#define NMS_KEYFILE_CONNECTION_LOG_ARGD(con)   NMS_KEYFILE_CONNECTION_LOG_PATH (nm_settings_connection_get_filename ((NMSettingsConnection *) (con))), nm_settings_connection_get_uuid ((NMSettingsConnection *) (con)), nm_settings_connection_get_id ((NMSettingsConnection *) (con)), (con)
-
 typedef enum {
 	NMS_KEYFILE_FILETYPE_KEYFILE,
-	NMS_KEYFILE_FILETYPE_NMLOADED,
+	NMS_KEYFILE_FILETYPE_NMMETA,
 } NMSKeyfileFiletype;
 
-const char *nms_keyfile_utils_get_path (void);
+typedef enum {
+	NMS_KEYFILE_STORAGE_TYPE_RUN       = 1, /* read-write, runtime only, e.g. /run */
+	NMS_KEYFILE_STORAGE_TYPE_ETC       = 2, /* read-write, persistent,   e.g. /etc     */
+	NMS_KEYFILE_STORAGE_TYPE_LIB_BASE  = 3, /* read-only,                e.g. /usr/lib */
+
+	_NMS_KEYFILE_STORAGE_TYPE_LIB_LAST = 1000,
+} NMSKeyfileStorageType;
+
+static inline NMSKeyfileStorageType
+NMS_KEYFILE_STORAGE_TYPE_LIB (guint run_idx)
+{
+	nm_assert (run_idx <= (_NMS_KEYFILE_STORAGE_TYPE_LIB_LAST - NMS_KEYFILE_STORAGE_TYPE_LIB_BASE));
+	return NMS_KEYFILE_STORAGE_TYPE_LIB_BASE + run_idx;
+}
 
 /*****************************************************************************/
 
-char *nms_keyfile_loaded_uuid_filename (const char *dirname,
-                                        const char *uuid,
-                                        gboolean temporary);
-
-gboolean nms_keyfile_loaded_uuid_read (const char *dirname,
-                                       const char *filename,
-                                       char **out_full_filename,
-                                       char **out_uuid,
-                                       char **out_loaded_path);
-
-gboolean nms_keyfile_loaded_uuid_read_from_file (const char *full_filename,
-                                                 char **out_dirname,
-                                                 char **out_filename,
-                                                 char **out_uuid,
-                                                 char **out_loaded_path);
-
-gboolean nms_keyfile_loaded_uuid_write (const char *dirname,
-                                        const char *uuid,
-                                        const char *loaded_path,
-                                        gboolean allow_relative,
-                                        char **out_full_filename);
+const char *nms_keyfile_nmmeta_check_filename (const char *filename,
+                                               guint *out_uuid_len);
+
+char *nms_keyfile_nmmeta_filename (const char *dirname,
+                                   const char *uuid,
+                                   gboolean temporary);
+
+gboolean nms_keyfile_nmmeta_read (const char *dirname,
+                                  const char *filename,
+                                  char **out_full_filename,
+                                  char **out_uuid,
+                                  char **out_loaded_path,
+                                  char **out_shadowed_storage,
+                                  struct stat *out_st);
+
+gboolean nms_keyfile_nmmeta_read_from_file (const char *full_filename,
+                                            char **out_dirname,
+                                            char **out_filename,
+                                            char **out_uuid,
+                                            char **out_loaded_path,
+                                            char **out_shadowed_storage);
+
+gboolean nms_keyfile_nmmeta_write (const char *dirname,
+                                   const char *uuid,
+                                   const char *loaded_path,
+                                   gboolean loaded_path_allow_relative,
+                                   const char *shadowed_storage,
+                                   char **out_full_filename);
 
 /*****************************************************************************/
 
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.c b/src/settings/plugins/keyfile/nms-keyfile-writer.c
index 2e06aeb9..5fbbb7a1 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -169,6 +168,10 @@ _handler_write (NMConnection *connection,
 
 static gboolean
 _internal_write_connection (NMConnection *connection,
+                            gboolean is_nm_generated,
+                            gboolean is_volatile,
+                            const char *shadowed_storage,
+                            gboolean shadowed_owned,
                             const char *keyfile_dir,
                             const char *profile_dir,
                             gboolean with_extension,
@@ -177,6 +180,8 @@ _internal_write_connection (NMConnection *connection,
                             const char *existing_path,
                             gboolean existing_path_read_only,
                             gboolean force_rename,
+                            NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                            gpointer allow_filename_user_data,
                             char **out_path,
                             NMConnection **out_reread,
                             gboolean *out_reread_same,
@@ -191,33 +196,57 @@ _internal_write_connection (NMConnection *connection,
 	GError *local_err = NULL;
 	int errsv;
 	gboolean rename;
+	int i_path;
 
 	g_return_val_if_fail (!out_path || !*out_path, FALSE);
 	g_return_val_if_fail (keyfile_dir && keyfile_dir[0] == '/', FALSE);
 
+	nm_assert (_nm_connection_verify (connection, NULL) == NM_SETTING_VERIFY_SUCCESS);
+
+	nm_assert (!shadowed_owned || shadowed_storage);
+
 	rename =    force_rename
 	         || existing_path_read_only
 	         || (   existing_path
 	             && !nm_utils_file_is_in_path (existing_path, keyfile_dir));
 
-	switch (_nm_connection_verify (connection, error)) {
-	case NM_SETTING_VERIFY_NORMALIZABLE:
-		nm_assert_not_reached ();
-		/* fall-through */
-	case NM_SETTING_VERIFY_SUCCESS:
-		break;
-	default:
-		g_return_val_if_reached (FALSE);
-	}
-
 	id = nm_connection_get_id (connection);
-	g_assert (id && *id);
+	nm_assert (id && *id);
 
 	info.keyfile_dir = keyfile_dir;
 
 	kf_file = nm_keyfile_write (connection, _handler_write, &info, error);
 	if (!kf_file)
 		return FALSE;
+
+	if (is_nm_generated) {
+		g_key_file_set_boolean (kf_file,
+		                        NM_KEYFILE_GROUP_NMMETA,
+		                        NM_KEYFILE_KEY_NMMETA_NM_GENERATED,
+		                        TRUE);
+	}
+
+	if (is_volatile) {
+		g_key_file_set_boolean (kf_file,
+		                        NM_KEYFILE_GROUP_NMMETA,
+		                        NM_KEYFILE_KEY_NMMETA_VOLATILE,
+		                        TRUE);
+	}
+
+	if (shadowed_storage) {
+		g_key_file_set_string (kf_file,
+		                       NM_KEYFILE_GROUP_NMMETA,
+		                       NM_KEYFILE_KEY_NMMETA_SHADOWED_STORAGE,
+		                       shadowed_storage);
+	}
+
+	if (shadowed_owned) {
+		g_key_file_set_boolean (kf_file,
+		                        NM_KEYFILE_GROUP_NMMETA,
+		                        NM_KEYFILE_KEY_NMMETA_SHADOWED_OWNED,
+		                        TRUE);
+	}
+
 	kf_content_buf = g_key_file_to_data (kf_file, &kf_content_len, error);
 	if (!kf_content_buf)
 		return FALSE;
@@ -225,64 +254,59 @@ _internal_write_connection (NMConnection *connection,
 	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.
-	 */
-	if (   existing_path
-	    && !rename)
-		path = g_strdup (existing_path);
-	else {
-		gs_free char *filename_escaped = NULL;
+	for (i_path = -2; i_path < 10000; i_path++) {
+		gs_free char *path_candidate = NULL;
+		gboolean is_existing_path;
 
-		filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
-		path = g_build_filename (keyfile_dir, filename_escaped, NULL);
-	}
-
-	/* If a file with this path already exists (but isn't the existing path
-	 * of the connection) then we need another name.  Multiple connections
-	 * can have the same ID (ie if two connections with the same ID are visible
-	 * to different users) but of course can't have the same path.  Yeah,
-	 * there's a race here, but there's not a lot we can do about it, and
-	 * we shouldn't get more than one connection with the same UUID either.
-	 */
-	if (   !nm_streq0 (path, existing_path)
-	    && g_file_test (path, G_FILE_TEST_EXISTS)) {
-		guint i;
-		gboolean name_found = FALSE;
+		if (i_path == -2) {
+			if (   !existing_path
+			    || rename)
+				continue;
+			path_candidate = g_strdup (existing_path);
+		} else if (i_path == -1) {
+			gs_free char *filename_escaped = NULL;
 
-		/* A keyfile with this connection's ID already exists. Pick another name. */
-		for (i = 0; i < 100; i++) {
+			filename_escaped = nm_keyfile_utils_create_filename (id, with_extension);
+			path_candidate = g_build_filename (keyfile_dir, filename_escaped, NULL);
+		} else {
 			gs_free char *filename_escaped = NULL;
 			gs_free char *filename = NULL;
 
-			if (i == 0)
+			if (i_path == 0)
 				filename = g_strdup_printf ("%s-%s", id, nm_connection_get_uuid (connection));
 			else
-				filename = g_strdup_printf ("%s-%s-%u", id, nm_connection_get_uuid (connection), i);
+				filename = g_strdup_printf ("%s-%s-%d", id, nm_connection_get_uuid (connection), i_path);
 
 			filename_escaped = nm_keyfile_utils_create_filename (filename, with_extension);
 
-			g_free (path);
-			path = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped);
-
-			if (   nm_streq0 (path, existing_path)
-			    || !g_file_test (path, G_FILE_TEST_EXISTS)) {
-				name_found = TRUE;
-				break;
-			}
+			path_candidate = g_strdup_printf ("%s/%s", keyfile_dir, filename_escaped);
 		}
-		if (!name_found) {
-			if (existing_path_read_only || !existing_path) {
-				/* this really should not happen, we tried hard to find an unused name... bail out. */
-				g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
-				                    "could not find suitable keyfile file name (%s already used)", path);
-				return FALSE;
-			}
-			/* Both our preferred path based on connection id and id-uuid are taken.
-			 * Fallback to @existing_path */
-			g_free (path);
-			path = g_strdup (existing_path);
+
+		is_existing_path =    existing_path
+		                   && nm_streq (existing_path, path_candidate);
+
+		if (   is_existing_path
+		    && rename)
+			continue;
+
+		if (   allow_filename_cb
+		    && !allow_filename_cb (path_candidate, allow_filename_user_data))
+			continue;
+
+		if (!is_existing_path) {
+			if (g_file_test (path_candidate, G_FILE_TEST_EXISTS))
+				continue;
 		}
+
+		path = g_steal_pointer (&path_candidate);
+		break;
+	}
+
+	if (!path) {
+		/* this really should not happen, we tried hard to find an unused name... bail out. */
+		g_set_error (error, NM_SETTINGS_ERROR, NM_SETTINGS_ERROR_FAILED,
+		                    "could not find suitable keyfile file name (%s already used)", path);
+		return FALSE;
 	}
 
 	nm_utils_file_set_contents (path, kf_content_buf, kf_content_len, 0600, &local_err);
@@ -348,30 +372,37 @@ _internal_write_connection (NMConnection *connection,
 
 gboolean
 nms_keyfile_writer_connection (NMConnection *connection,
-                               gboolean save_to_disk,
+                               gboolean is_nm_generated,
+                               gboolean is_volatile,
+                               const char *shadowed_storage,
+                               gboolean shadowed_owned,
+                               const char *keyfile_dir,
+                               const char *profile_dir,
                                const char *existing_path,
+                               gboolean existing_path_read_only,
                                gboolean force_rename,
+                               NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                               gpointer allow_filename_user_data,
                                char **out_path,
                                NMConnection **out_reread,
                                gboolean *out_reread_same,
                                GError **error)
 {
-	const char *keyfile_dir;
-
-	if (save_to_disk)
-		keyfile_dir = nms_keyfile_utils_get_path ();
-	else
-		keyfile_dir = NM_KEYFILE_PATH_NAME_RUN;
-
 	return _internal_write_connection (connection,
+	                                   is_nm_generated,
+	                                   is_volatile,
+	                                   shadowed_storage,
+	                                   shadowed_owned,
 	                                   keyfile_dir,
-	                                   nms_keyfile_utils_get_path (),
+	                                   profile_dir,
 	                                   TRUE,
 	                                   0,
 	                                   0,
 	                                   existing_path,
-	                                   FALSE,
+	                                   existing_path_read_only,
 	                                   force_rename,
+	                                   allow_filename_cb,
+	                                   allow_filename_user_data,
 	                                   out_path,
 	                                   out_reread,
 	                                   out_reread_same,
@@ -389,6 +420,10 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
                                     GError **error)
 {
 	return _internal_write_connection (connection,
+	                                   FALSE,
+	                                   FALSE,
+	                                   NULL,
+	                                   FALSE,
 	                                   keyfile_dir,
 	                                   keyfile_dir,
 	                                   FALSE,
@@ -397,6 +432,8 @@ nms_keyfile_writer_test_connection (NMConnection *connection,
 	                                   NULL,
 	                                   FALSE,
 	                                   FALSE,
+	                                   NULL,
+	                                   NULL,
 	                                   out_path,
 	                                   out_reread,
 	                                   out_reread_same,
diff --git a/src/settings/plugins/keyfile/nms-keyfile-writer.h b/src/settings/plugins/keyfile/nms-keyfile-writer.h
index 030fb7cc..99e86025 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-writer.h
+++ b/src/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -24,10 +23,21 @@
 
 #include "nm-connection.h"
 
+typedef gboolean (*NMSKeyfileWriterAllowFilenameCb) (const char *check_filename,
+                                                     gpointer allow_filename_user_data);
+
 gboolean nms_keyfile_writer_connection (NMConnection *connection,
-                                        gboolean save_to_disk,
+                                        gboolean is_nm_generated,
+                                        gboolean is_volatile,
+                                        const char *shadowed_storage,
+                                        gboolean shadowed_owned,
+                                        const char *keyfile_dir,
+                                        const char *profile_dir,
                                         const char *existing_path,
+                                        gboolean existing_path_read_only,
                                         gboolean force_rename,
+                                        NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
+                                        gpointer allow_filename_user_data,
                                         char **out_path,
                                         NMConnection **out_reread,
                                         gboolean *out_reread_same,
diff --git a/src/settings/plugins/keyfile/tests/meson.build b/src/settings/plugins/keyfile/tests/meson.build
index 752b6d7b..f1e96bdf 100644
--- a/src/settings/plugins/keyfile/tests/meson.build
+++ b/src/settings/plugins/keyfile/tests/meson.build
@@ -1,6 +1,4 @@
-test_unit = 'test-keyfile'
-
-test_keyfiles_dir = join_paths(meson.current_source_dir(), 'keyfiles')
+test_unit = 'test-keyfile-settings'
 
 exe = executable(
   test_unit,
@@ -9,7 +7,7 @@ exe = executable(
 )
 
 test(
-  'keyfile/' + test_unit,
+  test_unit,
   test_script,
   args: test_args + [exe.full_path()],
   timeout: default_test_timeout,
diff --git a/src/settings/plugins/keyfile/tests/test-keyfile.c b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
index baecac13..f96111a2 100644
--- a/src/settings/plugins/keyfile/tests/test-keyfile.c
+++ b/src/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -1,4 +1,3 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 /* NetworkManager system settings service - keyfile plugin
  *
  * This program is free software; you can redistribute it and/or modify
@@ -73,7 +72,12 @@ check_ip_route (NMSettingIPConfig *config, int idx, const char *destination, int
 	\
 	_connection = nms_keyfile_reader_from_file (full_filename, \
 	                                            NULL, \
-	                                            (nmtst_get_rand_int () % 2) ? &_error : NULL); \
+	                                            NULL, \
+	                                            NULL, \
+	                                            NULL, \
+	                                            NULL, \
+	                                            NULL, \
+	                                            (nmtst_get_rand_uint32 () % 2) ? &_error : NULL); \
 	nmtst_assert_success (_connection, _error); \
 	nmtst_assert_connection_verifies_without_normalization (_connection); \
 	\
@@ -128,7 +132,7 @@ write_test_connection_reread (NMConnection *connection,
 	gid_t owner_grp;
 	gboolean success;
 	GError *error = NULL;
-	GError **p_error = (nmtst_get_rand_int () % 2) ? &error : NULL;
+	GError **p_error = (nmtst_get_rand_uint32 () % 2) ? &error : NULL;
 	gs_unref_object NMConnection *connection_normalized = NULL;
 
 	g_assert (NM_IS_CONNECTION (connection));
@@ -2508,14 +2512,14 @@ test_nm_keyfile_plugin_utils_escape_filename (void)
 /*****************************************************************************/
 
 static void
-_assert_keyfile_loaded_uuid (const char *dirname,
-                             const char *uuid,
-                             const char *loaded_path,
-                             gboolean allow_relative,
-                             const char *exp_full_filename,
-                             const char *exp_uuid,
-                             const char *exp_symlink_target,
-                             const char *exp_loaded_path)
+_assert_keyfile_nmmeta (const char *dirname,
+                        const char *uuid,
+                        const char *loaded_path,
+                        gboolean allow_relative,
+                        const char *exp_full_filename,
+                        const char *exp_uuid,
+                        const char *exp_symlink_target,
+                        const char *exp_loaded_path)
 {
 	gs_free char *full_filename = NULL;
 	gs_free char *symlink_target = NULL;
@@ -2534,12 +2538,12 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 
 	filename = g_path_get_basename (exp_full_filename);
 
-	full_filename = nms_keyfile_loaded_uuid_filename (dirname, uuid, FALSE);
+	full_filename = nms_keyfile_nmmeta_filename (dirname, uuid, FALSE);
 	g_assert_cmpstr (full_filename, ==, full_filename);
 	nm_clear_g_free (&full_filename);
 
 
-	g_assert (nms_keyfile_loaded_uuid_write (dirname, uuid, loaded_path, allow_relative, &full_filename));
+	g_assert (nms_keyfile_nmmeta_write (dirname, uuid, loaded_path, allow_relative, NULL, &full_filename));
 	g_assert_cmpstr (full_filename, ==, exp_full_filename);
 	nm_clear_g_free (&full_filename);
 
@@ -2551,7 +2555,7 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 	g_assert_cmpstr (symlink_target, ==, exp_symlink_target);
 
 
-	success = nms_keyfile_loaded_uuid_read (dirname, filename, &full_filename, &uuid2, &loaded_path2);
+	success = nms_keyfile_nmmeta_read (dirname, filename, &full_filename, &uuid2, &loaded_path2, NULL, NULL);
 	g_assert_cmpint (!!exp_uuid, ==, success);
 	if (success)
 		g_assert_cmpstr (full_filename, ==, exp_full_filename);
@@ -2562,7 +2566,7 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 	g_assert_cmpstr (loaded_path2, ==, exp_loaded_path);
 
 
-	success = nms_keyfile_loaded_uuid_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3);
+	success = nms_keyfile_nmmeta_read_from_file (exp_full_filename, &dirname3, &filename3, &uuid3, &loaded_path3, NULL);
 	g_assert_cmpint (!!exp_uuid, ==, success);
 	if (success) {
 		g_assert_cmpstr (dirname3, ==, dirname);
@@ -2576,34 +2580,33 @@ _assert_keyfile_loaded_uuid (const char *dirname,
 }
 
 static void
-test_loaded_uuid (void)
+test_nmmeta (void)
 {
 	const char *uuid = "3c03fd17-ddc3-4100-a954-88b6fafff959";
-	gs_free char *filename = g_strdup_printf ("%s%s%s",
-	                                          NM_KEYFILE_PATH_PREFIX_NMLOADED,
+	gs_free char *filename = g_strdup_printf ("%s%s",
 	                                          uuid,
-	                                          NM_KEYFILE_PATH_SUFFIX_NMCONNECTION);
+	                                          NM_KEYFILE_PATH_SUFFIX_NMMETA);
 	gs_free char *full_filename = g_strdup_printf ("%s/%s",
 	                                               TEST_SCRATCH_DIR,
 	                                               filename);
-	const char *loaded_path0 = NM_KEYFILE_PATH_NMLOADED_NULL;
+	const char *loaded_path0 = NM_KEYFILE_PATH_NMMETA_SYMLINK_NULL;
 	const char *loaded_path1 = "/some/where/but/not/scratch/dir";
 	const char *filename2 = "foo1";
 	gs_free char *loaded_path2 = g_strdup_printf ("%s/%s",
 	                                              TEST_SCRATCH_DIR,
 	                                              filename2);
 
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL,         FALSE, full_filename, NULL, NULL,         NULL);
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, NULL,         TRUE,  full_filename, NULL, NULL,         NULL);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, NULL,         FALSE, full_filename, NULL, NULL,         NULL);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, NULL,         TRUE,  full_filename, NULL, NULL,         NULL);
 
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, FALSE, full_filename, uuid, loaded_path0, loaded_path0);
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path0, TRUE,  full_filename, uuid, loaded_path0, loaded_path0);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path0, FALSE, full_filename, uuid, loaded_path0, loaded_path0);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path0, TRUE,  full_filename, uuid, loaded_path0, loaded_path0);
 
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, FALSE, full_filename, uuid, loaded_path1, loaded_path1);
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path1, TRUE,  full_filename, uuid, loaded_path1, loaded_path1);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path1, FALSE, full_filename, uuid, loaded_path1, loaded_path1);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path1, TRUE,  full_filename, uuid, loaded_path1, loaded_path1);
 
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, FALSE, full_filename, uuid, loaded_path2, loaded_path2);
-	_assert_keyfile_loaded_uuid (TEST_SCRATCH_DIR, uuid, loaded_path2, TRUE,  full_filename, uuid, filename2,    loaded_path2);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path2, FALSE, full_filename, uuid, loaded_path2, loaded_path2);
+	_assert_keyfile_nmmeta (TEST_SCRATCH_DIR, uuid, loaded_path2, TRUE,  full_filename, uuid, filename2,    loaded_path2);
 
 	(void) unlink (full_filename);
 }
@@ -2697,7 +2700,7 @@ int main (int argc, char **argv)
 
 	g_test_add_func ("/keyfile/test_nm_keyfile_plugin_utils_escape_filename", test_nm_keyfile_plugin_utils_escape_filename);
 
-	g_test_add_func ("/keyfile/test_loaded_uuid", test_loaded_uuid);
+	g_test_add_func ("/keyfile/test_nmmeta", test_nmmeta);
 
 	return g_test_run ();
 }
diff --git a/src/settings/plugins/meson.build b/src/settings/plugins/meson.build
index a1aa7823..83981aab 100644
--- a/src/settings/plugins/meson.build
+++ b/src/settings/plugins/meson.build
@@ -1,7 +1,3 @@
-if enable_ibft
-  subdir('ibft')
-endif
-
 if enable_ifcfg_rh
   subdir('ifcfg-rh')
 endif
diff --git a/src/supplicant/nm-supplicant-config.c b/src/supplicant/nm-supplicant-config.c
index 7708224b..806c087c 100644
--- a/src/supplicant/nm-supplicant-config.c
+++ b/src/supplicant/nm-supplicant-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -50,6 +49,8 @@ typedef struct {
 	gboolean   dispose_has_run;
 	gboolean   support_pmf;
 	gboolean   support_fils;
+	gboolean   support_ft;
+	gboolean   support_sha384;
 } NMSupplicantConfigPrivate;
 
 struct _NMSupplicantConfig {
@@ -68,7 +69,8 @@ G_DEFINE_TYPE (NMSupplicantConfig, nm_supplicant_config, G_TYPE_OBJECT)
 /*****************************************************************************/
 
 NMSupplicantConfig *
-nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils)
+nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
+                          gboolean support_ft, gboolean support_sha384)
 {
 	NMSupplicantConfigPrivate *priv;
 	NMSupplicantConfig *self;
@@ -78,6 +80,8 @@ nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils)
 
 	priv->support_pmf = support_pmf;
 	priv->support_fils = support_fils;
+	priv->support_ft = support_ft;
+	priv->support_sha384 = support_sha384;
 
 	return self;
 }
@@ -454,7 +458,7 @@ nm_supplicant_config_add_setting_wireless (NMSupplicantConfig * self,
                                            GError **error)
 {
 	NMSupplicantConfigPrivate *priv;
-	gboolean is_adhoc, is_ap;
+	gboolean is_adhoc, is_ap, is_mesh;
 	const char *mode, *band;
 	guint32 channel;
 	GBytes *ssid;
@@ -469,6 +473,7 @@ nm_supplicant_config_add_setting_wireless (NMSupplicantConfig * self,
 	mode = nm_setting_wireless_get_mode (setting);
 	is_adhoc = (mode && !strcmp (mode, "adhoc")) ? TRUE : FALSE;
 	is_ap = (mode && !strcmp (mode, "ap")) ? TRUE : FALSE;
+	is_mesh = (mode && !strcmp (mode, "mesh")) ? TRUE : FALSE;
 	if (is_adhoc || is_ap)
 		priv->ap_scan = 2;
 	else
@@ -498,7 +503,12 @@ nm_supplicant_config_add_setting_wireless (NMSupplicantConfig * self,
 			return FALSE;
 	}
 
-	if ((is_adhoc || is_ap) && fixed_freq) {
+	if (is_mesh) {
+		if (!nm_supplicant_config_add_option (self, "mode", "5", -1, NULL, error))
+			return FALSE;
+	}
+
+	if ((is_adhoc || is_ap || is_mesh) && fixed_freq) {
 		gs_free char *str_freq = NULL;
 
 		str_freq = g_strdup_printf ("%u", fixed_freq);
@@ -506,10 +516,10 @@ nm_supplicant_config_add_setting_wireless (NMSupplicantConfig * self,
 			return FALSE;
 	}
 
-	/* Except for Ad-Hoc and Hotspot, request that the driver probe for the
+	/* Except for Ad-Hoc, Hotspot and Mesh, request that the driver probe for the
 	 * specific SSID we want to associate with.
 	 */
-	if (!(is_adhoc || is_ap)) {
+	if (!(is_adhoc || is_ap || is_mesh)) {
 		if (!nm_supplicant_config_add_option (self, "scan_ssid", "1", -1, NULL, error))
 			return FALSE;
 	}
@@ -755,7 +765,8 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
                                                     GError **error)
 {
 	NMSupplicantConfigPrivate *priv = NM_SUPPLICANT_CONFIG_GET_PRIVATE (self);
-	const char *key_mgmt, *key_mgmt_conf, *auth_alg;
+	nm_auto_free_gstring GString *key_mgmt_conf = NULL;
+	const char *key_mgmt, *auth_alg;
 	const char *psk;
 	gboolean set_pmf;
 
@@ -774,28 +785,43 @@ nm_supplicant_config_add_setting_wireless_security (NMSupplicantConfig *self,
 			fils = NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE;
 	}
 
-	key_mgmt = key_mgmt_conf = nm_setting_wireless_security_get_key_mgmt (setting);
+	key_mgmt = nm_setting_wireless_security_get_key_mgmt (setting);
+	key_mgmt_conf = g_string_new (key_mgmt);
 	if (nm_streq (key_mgmt, "wpa-psk")) {
 		if (priv->support_pmf)
-			key_mgmt_conf = "wpa-psk wpa-psk-sha256";
+			g_string_append (key_mgmt_conf, " wpa-psk-sha256");
+		if (priv->support_ft)
+			g_string_append (key_mgmt_conf, " ft-psk");
 	} else if (nm_streq (key_mgmt, "wpa-eap")) {
+		if (priv->support_pmf)
+			g_string_append (key_mgmt_conf, " wpa-eap-sha256");
+		if (priv->support_ft)
+			g_string_append (key_mgmt_conf, " ft-eap");
+		if (priv->support_ft && priv->support_sha384)
+			g_string_append (key_mgmt_conf, " ft-eap-sha384");
 		switch (fils) {
-		case NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL:
-			key_mgmt_conf = priv->support_pmf
-				? "wpa-eap wpa-eap-sha256 fils-sha256 fils-sha384"
-				: "wpa-eap fils-sha256 fils-sha384";
-			break;
 		case NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED:
-			key_mgmt_conf = "fils-sha256 fils-sha384";
+			g_string_truncate (key_mgmt_conf, 0);
+			if (!priv->support_pmf)
+				g_string_assign (key_mgmt_conf, "fils-sha256 fils-sha384");
+			/* fall-through */
+		case NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL:
+			if (priv->support_pmf)
+				g_string_append (key_mgmt_conf, " fils-sha256 fils-sha384");
+			if (priv->support_pmf && priv->support_ft)
+				g_string_append (key_mgmt_conf, " ft-fils-sha256");
+			if (priv->support_pmf && priv->support_ft & priv->support_sha384)
+				g_string_append (key_mgmt_conf, " ft-fils-sha384");
 			break;
 		default:
-			if (priv->support_pmf)
-				key_mgmt_conf = "wpa-eap wpa-eap-sha256";
 			break;
 		}
+	} else if (nm_streq (key_mgmt, "sae")) {
+		if (priv->support_ft)
+			g_string_append (key_mgmt_conf, " ft-sae");
 	}
 
-	if (!add_string_val (self, key_mgmt_conf, "key_mgmt", TRUE, NULL, error))
+	if (!add_string_val (self, key_mgmt_conf->str, "key_mgmt", TRUE, NULL, error))
 		return FALSE;
 
 	auth_alg = nm_setting_wireless_security_get_auth_alg (setting);
diff --git a/src/supplicant/nm-supplicant-config.h b/src/supplicant/nm-supplicant-config.h
index f6c845a3..c4e7310d 100644
--- a/src/supplicant/nm-supplicant-config.h
+++ b/src/supplicant/nm-supplicant-config.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -40,7 +39,8 @@ typedef struct _NMSupplicantConfigClass NMSupplicantConfigClass;
 
 GType nm_supplicant_config_get_type (void);
 
-NMSupplicantConfig *nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils);
+NMSupplicantConfig *nm_supplicant_config_new (gboolean support_pmf, gboolean support_fils,
+                                              gboolean support_ft, gboolean support_sha384);
 
 guint32 nm_supplicant_config_get_ap_scan (NMSupplicantConfig *self);
 
diff --git a/src/supplicant/nm-supplicant-interface.c b/src/supplicant/nm-supplicant-interface.c
index 12d21aba..079afca3 100644
--- a/src/supplicant/nm-supplicant-interface.c
+++ b/src/supplicant/nm-supplicant-interface.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -81,6 +80,12 @@ typedef struct _AddNetworkData {
 	AssocData *assoc_data;
 } AddNetworkData;
 
+typedef struct {
+	NMSupplicantInterface *self;
+	NMSupplicantInterfaceDisconnectCb callback;
+	gpointer user_data;
+} DisconnectData;
+
 enum {
 	STATE,                   /* change in the interface's state */
 	REMOVED,                 /* interface was removed by the supplicant */
@@ -113,7 +118,10 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMSupplicantInterface,
 	PROP_PMF_SUPPORT,
 	PROP_FILS_SUPPORT,
 	PROP_P2P_SUPPORT,
+	PROP_MESH_SUPPORT,
 	PROP_WFD_SUPPORT,
+	PROP_FT_SUPPORT,
+	PROP_SHA384_SUPPORT,
 );
 
 typedef struct {
@@ -125,7 +133,10 @@ typedef struct {
 	NMSupplicantFeature pmf_support;
 	NMSupplicantFeature fils_support;
 	NMSupplicantFeature p2p_support;
+	NMSupplicantFeature mesh_support;
 	NMSupplicantFeature wfd_support;
+	NMSupplicantFeature ft_support;
+	NMSupplicantFeature sha384_support;
 	guint32        max_scan_ssids;
 	guint32        ready_count;
 
@@ -782,11 +793,29 @@ nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self)
 }
 
 NMSupplicantFeature
+nm_supplicant_interface_get_mesh_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->mesh_support;
+}
+
+NMSupplicantFeature
 nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self)
 {
 	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->wfd_support;
 }
 
+NMSupplicantFeature
+nm_supplicant_interface_get_ft_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->ft_support;
+}
+
+NMSupplicantFeature
+nm_supplicant_interface_get_sha384_support (NMSupplicantInterface *self)
+{
+	return NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self)->sha384_support;
+}
+
 void
 nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                         NMSupplicantFeature ap_support)
@@ -837,6 +866,15 @@ nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
 }
 
 void
+nm_supplicant_interface_set_mesh_support (NMSupplicantInterface *self,
+                                          NMSupplicantFeature mesh_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->mesh_support = mesh_support;
+}
+
+void
 nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
                                          NMSupplicantFeature wfd_support)
 {
@@ -845,6 +883,24 @@ nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
 	priv->wfd_support = wfd_support;
 }
 
+void
+nm_supplicant_interface_set_ft_support (NMSupplicantInterface *self,
+                                        NMSupplicantFeature ft_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->ft_support = ft_support;
+}
+
+void
+nm_supplicant_interface_set_sha384_support (NMSupplicantInterface *self,
+                                            NMSupplicantFeature sha384_support)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+
+	priv->sha384_support = sha384_support;
+}
+
 /*****************************************************************************/
 
 static void
@@ -2118,6 +2174,60 @@ nm_supplicant_interface_disconnect (NMSupplicantInterface * self)
 }
 
 static void
+disconnect_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	DisconnectData *disconnect_data = user_data;
+	gs_unref_object NMSupplicantInterface *self = disconnect_data->self;
+	gs_unref_variant GVariant *reply = NULL;
+	gs_free_error GError *error = NULL;
+
+	reply = g_dbus_proxy_call_finish (proxy, result, &error);
+
+	/* an already disconnected interface is not an error*/
+	if (   !reply
+	    && !strstr (error->message, "fi.w1.wpa_supplicant1.NotConnected")) {
+		g_clear_error(&error);
+	}
+
+	disconnect_data->callback(self, error, disconnect_data->user_data);
+	g_slice_free (DisconnectData, disconnect_data);
+}
+
+void
+nm_supplicant_interface_disconnect_async ( NMSupplicantInterface * self,
+                                           GCancellable * cancellable,
+                                           NMSupplicantInterfaceDisconnectCb callback,
+                                           gpointer user_data)
+{
+	NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE (self);
+	DisconnectData *disconnect_data;
+
+	/* Don't do anything if there is no connection to the supplicant yet. */
+	if (!priv->iface_proxy)
+		return;
+
+	g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (self));
+	g_return_if_fail (NULL != callback);
+
+	disconnect_data = g_slice_new0(DisconnectData);
+
+	/* Keep interface alive until disconnect finishes */
+	disconnect_data->self = g_object_ref (self);
+	disconnect_data->callback = callback;
+	disconnect_data->user_data = user_data;
+
+	/* Disconnect the interface */
+	g_dbus_proxy_call (priv->iface_proxy,
+	                   "Disconnect",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   -1,
+	                   cancellable,
+	                   (GAsyncReadyCallback) disconnect_cb,
+	                   disconnect_data);
+}
+
+static void
 assoc_select_network_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
 	NMSupplicantInterface *self;
@@ -2681,10 +2791,22 @@ set_property (GObject *object,
 		/* construct-only */
 		priv->p2p_support = g_value_get_int (value);
 		break;
+	case PROP_MESH_SUPPORT:
+		/* construct-only */
+		priv->mesh_support = g_value_get_int (value);
+		break;
 	case PROP_WFD_SUPPORT:
 		/* construct-only */
 		priv->wfd_support = g_value_get_int (value);
 		break;
+	case PROP_FT_SUPPORT:
+		/* construct-only */
+		priv->ft_support = g_value_get_int (value);
+		break;
+	case PROP_SHA384_SUPPORT:
+		/* construct-only */
+		priv->sha384_support = g_value_get_int (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -2710,7 +2832,10 @@ nm_supplicant_interface_new (const char *ifname,
                              NMSupplicantFeature pmf_support,
                              NMSupplicantFeature fils_support,
                              NMSupplicantFeature p2p_support,
-                             NMSupplicantFeature wfd_support)
+                             NMSupplicantFeature mesh_support,
+                             NMSupplicantFeature wfd_support,
+                             NMSupplicantFeature ft_support,
+                             NMSupplicantFeature sha384_support)
 {
 	/* One of ifname or path need to be set */
 	g_return_val_if_fail (ifname != NULL || object_path != NULL, NULL);
@@ -2725,7 +2850,10 @@ nm_supplicant_interface_new (const char *ifname,
 	                     NM_SUPPLICANT_INTERFACE_PMF_SUPPORT, (int) pmf_support,
 	                     NM_SUPPLICANT_INTERFACE_FILS_SUPPORT, (int) fils_support,
 	                     NM_SUPPLICANT_INTERFACE_P2P_SUPPORT, (int) p2p_support,
+	                     NM_SUPPLICANT_INTERFACE_MESH_SUPPORT, (int) mesh_support,
 	                     NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, (int) wfd_support,
+	                     NM_SUPPLICANT_INTERFACE_FT_SUPPORT, (int) ft_support,
+	                     NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT, (int) sha384_support,
 	                     NULL);
 }
 
@@ -2876,6 +3004,14 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                      G_PARAM_WRITABLE |
 	                      G_PARAM_CONSTRUCT_ONLY |
 	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_MESH_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_MESH_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 	obj_properties[PROP_WFD_SUPPORT] =
 	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_WFD_SUPPORT, "", "",
 	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
@@ -2884,6 +3020,22 @@ nm_supplicant_interface_class_init (NMSupplicantInterfaceClass *klass)
 	                      G_PARAM_WRITABLE |
 	                      G_PARAM_CONSTRUCT_ONLY |
 	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_FT_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_FT_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
+	obj_properties[PROP_SHA384_SUPPORT] =
+	    g_param_spec_int (NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT, "", "",
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      NM_SUPPLICANT_FEATURE_YES,
+	                      NM_SUPPLICANT_FEATURE_UNKNOWN,
+	                      G_PARAM_WRITABLE |
+	                      G_PARAM_CONSTRUCT_ONLY |
+	                      G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/src/supplicant/nm-supplicant-interface.h b/src/supplicant/nm-supplicant-interface.h
index 0aa7732a..653830da 100644
--- a/src/supplicant/nm-supplicant-interface.h
+++ b/src/supplicant/nm-supplicant-interface.h
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -68,7 +67,10 @@ typedef enum {
 #define NM_SUPPLICANT_INTERFACE_PMF_SUPPORT      "pmf-support"
 #define NM_SUPPLICANT_INTERFACE_FILS_SUPPORT     "fils-support"
 #define NM_SUPPLICANT_INTERFACE_P2P_SUPPORT      "p2p-support"
+#define NM_SUPPLICANT_INTERFACE_MESH_SUPPORT     "mesh-support"
 #define NM_SUPPLICANT_INTERFACE_WFD_SUPPORT      "wfd-support"
+#define NM_SUPPLICANT_INTERFACE_FT_SUPPORT       "ft-support"
+#define NM_SUPPLICANT_INTERFACE_SHA384_SUPPORT   "sha384-support"
 
 /* Signals */
 #define NM_SUPPLICANT_INTERFACE_STATE            "state"
@@ -96,7 +98,10 @@ NMSupplicantInterface * nm_supplicant_interface_new (const char *ifname,
                                                      NMSupplicantFeature pmf_support,
                                                      NMSupplicantFeature fils_support,
                                                      NMSupplicantFeature p2p_support,
-                                                     NMSupplicantFeature wfd_support);
+                                                     NMSupplicantFeature mesh_support,
+                                                     NMSupplicantFeature wfd_support,
+                                                     NMSupplicantFeature ft_support,
+                                                     NMSupplicantFeature sha384_support);
 
 void nm_supplicant_interface_set_supplicant_available (NMSupplicantInterface *self,
                                                        gboolean available);
@@ -113,6 +118,16 @@ nm_supplicant_interface_assoc (NMSupplicantInterface *self,
 
 void nm_supplicant_interface_disconnect (NMSupplicantInterface * iface);
 
+typedef void (*NMSupplicantInterfaceDisconnectCb) (NMSupplicantInterface *iface,
+                                                   GError *error,
+                                                   gpointer user_data);
+
+void
+nm_supplicant_interface_disconnect_async (NMSupplicantInterface * self,
+                                          GCancellable * cancellable,
+                                          NMSupplicantInterfaceDisconnectCb callback,
+                                          gpointer user_data);
+
 const char *nm_supplicant_interface_get_object_path (NMSupplicantInterface * iface);
 
 void nm_supplicant_interface_request_scan (NMSupplicantInterface *self,
@@ -161,7 +176,10 @@ NMSupplicantFeature nm_supplicant_interface_get_ap_support (NMSupplicantInterfac
 NMSupplicantFeature nm_supplicant_interface_get_pmf_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_fils_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_p2p_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_mesh_support (NMSupplicantInterface *self);
 NMSupplicantFeature nm_supplicant_interface_get_wfd_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_ft_support (NMSupplicantInterface *self);
+NMSupplicantFeature nm_supplicant_interface_get_sha384_support (NMSupplicantInterface *self);
 
 void nm_supplicant_interface_set_ap_support (NMSupplicantInterface *self,
                                              NMSupplicantFeature apmode);
@@ -178,9 +196,18 @@ void nm_supplicant_interface_set_fils_support (NMSupplicantInterface *self,
 void nm_supplicant_interface_set_p2p_support (NMSupplicantInterface *self,
                                               NMSupplicantFeature p2p_support);
 
+void nm_supplicant_interface_set_mesh_support (NMSupplicantInterface *self,
+                                               NMSupplicantFeature mesh_support);
+
 void nm_supplicant_interface_set_wfd_support (NMSupplicantInterface *self,
                                               NMSupplicantFeature wfd_support);
 
+void nm_supplicant_interface_set_ft_support (NMSupplicantInterface *self,
+                                             NMSupplicantFeature ft_support);
+
+void nm_supplicant_interface_set_sha384_support (NMSupplicantInterface *self,
+                                                 NMSupplicantFeature sha384_support);
+
 void nm_supplicant_interface_enroll_wps (NMSupplicantInterface *self,
                                          const char *const type,
                                          const char *bssid,
diff --git a/src/supplicant/nm-supplicant-manager.c b/src/supplicant/nm-supplicant-manager.c
index 104aeee7..df641c51 100644
--- a/src/supplicant/nm-supplicant-manager.c
+++ b/src/supplicant/nm-supplicant-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -40,7 +39,10 @@ typedef struct {
 	NMSupplicantFeature pmf_support;
 	NMSupplicantFeature fils_support;
 	NMSupplicantFeature p2p_support;
+	NMSupplicantFeature mesh_support;
 	NMSupplicantFeature wfd_support;
+	NMSupplicantFeature ft_support;
+	NMSupplicantFeature sha384_support;
 	guint             die_count_reset_id;
 	guint             die_count;
 } NMSupplicantManagerPrivate;
@@ -232,7 +234,10 @@ nm_supplicant_manager_create_interface (NMSupplicantManager *self,
 	                                     priv->pmf_support,
 	                                     priv->fils_support,
 	                                     priv->p2p_support,
-	                                     priv->wfd_support);
+	                                     priv->mesh_support,
+	                                     priv->wfd_support,
+	                                     priv->ft_support,
+	                                     priv->sha384_support);
 
 	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
 	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
@@ -289,7 +294,10 @@ nm_supplicant_manager_create_interface_from_path (NMSupplicantManager *self,
 	                                     priv->pmf_support,
 	                                     priv->fils_support,
 	                                     priv->p2p_support,
-	                                     priv->wfd_support);
+	                                     priv->mesh_support,
+	                                     priv->wfd_support,
+	                                     priv->ft_support,
+	                                     priv->sha384_support);
 
 	priv->ifaces = g_slist_prepend (priv->ifaces, iface);
 	g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self);
@@ -325,8 +333,11 @@ update_capabilities (NMSupplicantManager *self)
 	priv->ap_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 	priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 	priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
-	/* P2P support is newer than the capabilities property */
+	/* Support for the following is newer than the capabilities property */
 	priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
+	priv->ft_support = NM_SUPPLICANT_FEATURE_NO;
+	priv->sha384_support = NM_SUPPLICANT_FEATURE_NO;
+	priv->mesh_support = NM_SUPPLICANT_FEATURE_NO;
 
 	value = g_dbus_proxy_get_cached_property (priv->proxy, "Capabilities");
 	if (value) {
@@ -336,6 +347,9 @@ update_capabilities (NMSupplicantManager *self)
 			priv->pmf_support = NM_SUPPLICANT_FEATURE_NO;
 			priv->fils_support = NM_SUPPLICANT_FEATURE_NO;
 			priv->p2p_support = NM_SUPPLICANT_FEATURE_NO;
+			priv->ft_support = NM_SUPPLICANT_FEATURE_NO;
+			priv->sha384_support = NM_SUPPLICANT_FEATURE_NO;
+			priv->mesh_support = NM_SUPPLICANT_FEATURE_NO;
 			if (array) {
 				if (g_strv_contains (array, "ap"))
 					priv->ap_support = NM_SUPPLICANT_FEATURE_YES;
@@ -345,18 +359,27 @@ update_capabilities (NMSupplicantManager *self)
 					priv->fils_support = NM_SUPPLICANT_FEATURE_YES;
 				if (g_strv_contains (array, "p2p"))
 					priv->p2p_support = NM_SUPPLICANT_FEATURE_YES;
+				if (g_strv_contains (array, "ft"))
+					priv->ft_support = NM_SUPPLICANT_FEATURE_YES;
+				if (g_strv_contains (array, "sha384"))
+					priv->sha384_support = NM_SUPPLICANT_FEATURE_YES;
+				if (g_strv_contains (array, "mesh"))
+					priv->mesh_support = NM_SUPPLICANT_FEATURE_YES;
 				g_free (array);
 			}
 		}
 		g_variant_unref (value);
 	}
 
-	/* Tell all interfaces about results of the AP/PMF/FILS/P2P check */
+	/* Tell all interfaces about results of the AP/PMF/FILS/P2P/FT/SHA384 check */
 	for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) {
 		nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support);
 		nm_supplicant_interface_set_pmf_support (ifaces->data, priv->pmf_support);
 		nm_supplicant_interface_set_fils_support (ifaces->data, priv->fils_support);
 		nm_supplicant_interface_set_p2p_support (ifaces->data, priv->p2p_support);
+		nm_supplicant_interface_set_ft_support (ifaces->data, priv->ft_support);
+		nm_supplicant_interface_set_sha384_support (ifaces->data, priv->sha384_support);
+		nm_supplicant_interface_set_mesh_support (ifaces->data, priv->mesh_support);
 	}
 
 	_LOGD ("AP mode is %ssupported",
@@ -371,6 +394,15 @@ update_capabilities (NMSupplicantManager *self)
 	_LOGD ("P2P is %ssupported",
 	       (priv->p2p_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
 	           (priv->p2p_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	_LOGD ("FT is %ssupported",
+	       (priv->ft_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->ft_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	_LOGD ("SHA384 is %ssupported",
+	       (priv->sha384_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->sha384_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
+	_LOGD ("Mesh is %ssupported",
+	       (priv->mesh_support == NM_SUPPLICANT_FEATURE_YES) ? "" :
+	           (priv->mesh_support == NM_SUPPLICANT_FEATURE_NO) ? "not " : "possibly ");
 
 	/* EAP-FAST */
 	priv->fast_support = NM_SUPPLICANT_FEATURE_NO;
@@ -509,6 +541,8 @@ name_owner_cb (GDBusProxy *proxy, GParamSpec *pspec, gpointer user_data)
 		priv->fast_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 		priv->pmf_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 		priv->fils_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+		priv->ft_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
+		priv->sha384_support = NM_SUPPLICANT_FEATURE_UNKNOWN;
 
 		set_running (self, FALSE);
 	}
diff --git a/src/supplicant/nm-supplicant-manager.h b/src/supplicant/nm-supplicant-manager.h
index 058745fb..a1f23f53 100644
--- a/src/supplicant/nm-supplicant-manager.h
+++ b/src/supplicant/nm-supplicant-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/supplicant/nm-supplicant-settings-verify.c b/src/supplicant/nm-supplicant-settings-verify.c
index f10bbb04..1bd71320 100644
--- a/src/supplicant/nm-supplicant-settings-verify.c
+++ b/src/supplicant/nm-supplicant-settings-verify.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -67,8 +66,8 @@ 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-PSK-SHA256",
-                                    "WPA-EAP", "WPA-EAP-SHA256",
+const char * key_mgmt_allowed[] = { "WPA-PSK", "WPA-PSK-SHA256", "FT-PSK",
+                                    "WPA-EAP", "WPA-EAP-SHA256", "FT-EAP", "FT-EAP-SHA384",
                                     "FILS-SHA256", "FILS-SHA384",
                                     "IEEE8021X", "WPA-NONE", "SAE",
                                     "NONE", NULL };
@@ -94,7 +93,6 @@ static const struct Opt opt_table[] = {
 	{ "ssid",               TYPE_BYTES,   0, 32,FALSE,  NULL },
 	{ "bssid",              TYPE_KEYWORD, 0, 0, FALSE,  NULL },
 	{ "scan_ssid",          TYPE_INT,     0, 1, FALSE,  NULL },
-	{ "mode",               TYPE_INT,     0, 2, FALSE,  NULL },
 	{ "frequency",          TYPE_INT,     2412, 5825, FALSE,  NULL },
 	{ "auth_alg",           TYPE_KEYWORD, 0, 0, FALSE,  auth_alg_allowed },
 	{ "psk",                TYPE_BYTES,   0, 0, FALSE,  NULL },
@@ -255,6 +253,12 @@ nm_supplicant_settings_verify_setting (const char * key,
 	g_return_val_if_fail (key != NULL, FALSE);
 	g_return_val_if_fail (value != NULL, FALSE);
 
+	if (strcmp (key, "mode") == 0) {
+		if (strcmp (value, "1") && strcmp (value, "2") && strcmp (value, "5"))
+			return TYPE_INVALID;
+		return TYPE_INT;
+	}
+
 	for (i = 0; i < opt_count; i++) {
 		if (strcmp (opt_table[i].key, key) != 0)
 			continue;
diff --git a/src/supplicant/nm-supplicant-settings-verify.h b/src/supplicant/nm-supplicant-settings-verify.h
index 1eec136d..ea2482d3 100644
--- a/src/supplicant/nm-supplicant-settings-verify.h
+++ b/src/supplicant/nm-supplicant-settings-verify.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/supplicant/nm-supplicant-types.h b/src/supplicant/nm-supplicant-types.h
index 747cf152..b28a1efa 100644
--- a/src/supplicant/nm-supplicant-types.h
+++ b/src/supplicant/nm-supplicant-types.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/supplicant/tests/test-supplicant-config.c b/src/supplicant/tests/test-supplicant-config.c
index 2c7a71a3..819256fb 100644
--- a/src/supplicant/tests/test-supplicant-config.c
+++ b/src/supplicant/tests/test-supplicant-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -111,7 +110,7 @@ build_supplicant_config (NMConnection *connection,
 	NMSetting8021x *s_8021x;
 	gboolean success;
 
-	config = nm_supplicant_config_new (support_pmf, support_fils);
+	config = nm_supplicant_config_new (support_pmf, support_fils, FALSE, FALSE);
 
 	s_wifi = nm_connection_get_setting_wireless (connection);
 	g_assert (s_wifi);
diff --git a/src/systemd/meson.build b/src/systemd/meson.build
index 257a9462..af1d0c8b 100644
--- a/src/systemd/meson.build
+++ b/src/systemd/meson.build
@@ -23,9 +23,9 @@ libnm_systemd_core = static_library(
     'src/libsystemd/sd-event/sd-event.c',
     'src/libsystemd/sd-id128/id128-util.c',
     'src/libsystemd/sd-id128/sd-id128.c',
-    'src/shared/dns-domain.c',
     'nm-sd.c',
     'nm-sd-utils-core.c',
+    'nm-sd-utils-dhcp.c',
   ),
   include_directories: [
     src_inc,
@@ -33,7 +33,6 @@ libnm_systemd_core = static_library(
       'sd-adapt-core',
       'src/libsystemd-network',
       'src/libsystemd/sd-event',
-      'src/shared',
       'src/systemd',
     )
   ],
diff --git a/src/systemd/nm-sd-utils-dhcp.c b/src/systemd/nm-sd-utils-dhcp.c
new file mode 100644
index 00000000..49a924b6
--- /dev/null
+++ b/src/systemd/nm-sd-utils-dhcp.c
@@ -0,0 +1,54 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#include "nm-default.h"
+
+#include "nm-sd-utils-dhcp.h"
+
+#include "sd-adapt-core/nm-sd-adapt-core.h"
+#include "src/libsystemd-network/dhcp-lease-internal.h"
+
+int
+nm_sd_dhcp_lease_get_private_options (sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options)
+{
+	struct sd_dhcp_raw_option *raw_option;
+	int cnt = 0;
+
+	g_return_val_if_fail (lease, -EINVAL);
+	g_return_val_if_fail (out_options, -EINVAL);
+	g_return_val_if_fail (*out_options == NULL, -EINVAL);
+
+	if (lease->private_options == NULL)
+		return -ENODATA;
+
+	LIST_FOREACH (options, raw_option, lease->private_options)
+		cnt++;
+
+	*out_options = g_new (nm_sd_dhcp_option, cnt);
+	cnt = 0;
+
+	LIST_FOREACH (options, raw_option, lease->private_options) {
+		(*out_options)[cnt].code = raw_option->tag;
+		(*out_options)[cnt].data = raw_option->data;
+		(*out_options)[cnt].data_len = raw_option->length;
+		cnt++;
+	}
+
+	return cnt;
+}
diff --git a/src/systemd/nm-sd-utils-dhcp.h b/src/systemd/nm-sd-utils-dhcp.h
new file mode 100644
index 00000000..496bf374
--- /dev/null
+++ b/src/systemd/nm-sd-utils-dhcp.h
@@ -0,0 +1,34 @@
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2019 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__
+#define __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__
+
+#include "nm-sd.h"
+
+typedef struct {
+	uint8_t code;
+	uint8_t data_len;
+	void *data;
+} nm_sd_dhcp_option;
+
+int
+nm_sd_dhcp_lease_get_private_options (sd_dhcp_lease *lease, nm_sd_dhcp_option **out_options);
+
+#endif /* __NETWORKMANAGER_DHCP_SYSTEMD_UTILS_H__ */
diff --git a/src/systemd/nm-sd.c b/src/systemd/nm-sd.c
index 4009c617..bcab328f 100644
--- a/src/systemd/nm-sd.c
+++ b/src/systemd/nm-sd.c
@@ -1,4 +1,3 @@
-/* -*- 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)
@@ -133,10 +132,6 @@ nm_sd_event_attach_default (void)
 
 /*****************************************************************************/
 
-const bool mempool_use_allowed = true;
-
-/*****************************************************************************/
-
 /* ensure that defines in nm-sd.h correspond to the internal defines. */
 
 #include "nm-sd-adapt-core.h"
diff --git a/src/systemd/nm-sd.h b/src/systemd/nm-sd.h
index f988dfd0..07fc2ad4 100644
--- a/src/systemd/nm-sd.h
+++ b/src/systemd/nm-sd.h
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/systemd/sd-adapt-core/nm-sd-adapt-core.c b/src/systemd/sd-adapt-core/nm-sd-adapt-core.c
index d7ed687e..c461752e 100644
--- a/src/systemd/sd-adapt-core/nm-sd-adapt-core.c
+++ b/src/systemd/sd-adapt-core/nm-sd-adapt-core.c
@@ -1,4 +1,3 @@
-/* -*- 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)
diff --git a/src/systemd/sd-adapt-core/nm-sd-adapt-core.h b/src/systemd/sd-adapt-core/nm-sd-adapt-core.h
index 8c07c53a..bd71dd18 100644
--- a/src/systemd/sd-adapt-core/nm-sd-adapt-core.h
+++ b/src/systemd/sd-adapt-core/nm-sd-adapt-core.h
@@ -50,8 +50,6 @@
 #include <sys/syscall.h>
 #include <sys/ioctl.h>
 
-#include <net/if_arp.h>
-
 /* Missing in Linux 3.2.0, in Ubuntu 12.04 */
 #ifndef BPF_XOR
 #define BPF_XOR 0xa0
diff --git a/src/systemd/src/libsystemd-network/arp-util.c b/src/systemd/src/libsystemd-network/arp-util.c
index 154ba6b7..e48e4bfe 100644
--- a/src/systemd/src/libsystemd-network/arp-util.c
+++ b/src/systemd/src/libsystemd-network/arp-util.c
@@ -5,8 +5,9 @@
 
 #include "nm-sd-adapt-core.h"
 
-#include <linux/filter.h>
 #include <arpa/inet.h>
+#include <linux/filter.h>
+#include <netinet/if_ether.h>
 
 #include "arp-util.h"
 #include "fd-util.h"
diff --git a/src/systemd/src/libsystemd-network/arp-util.h b/src/systemd/src/libsystemd-network/arp-util.h
index 10c68486..9a4427e8 100644
--- a/src/systemd/src/libsystemd-network/arp-util.h
+++ b/src/systemd/src/libsystemd-network/arp-util.h
@@ -5,7 +5,7 @@
   Copyright © 2014 Axis Communications AB. All rights reserved.
 ***/
 
-#include <netinet/if_ether.h>
+#include <net/ethernet.h>
 
 #include "socket-util.h"
 #include "sparse-endian.h"
diff --git a/src/systemd/src/libsystemd-network/dhcp-identifier.c b/src/systemd/src/libsystemd-network/dhcp-identifier.c
index e3af362d..b28e0ba0 100644
--- a/src/systemd/src/libsystemd-network/dhcp-identifier.c
+++ b/src/systemd/src/libsystemd-network/dhcp-identifier.c
@@ -197,7 +197,7 @@ int dhcp_identifier_set_iaid(
                                 /* device is under renaming */
                                 return -EBUSY;
 
-                        name = net_get_name(device);
+                        name = net_get_name_persistent(device);
                 }
         }
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-internal.h b/src/systemd/src/libsystemd-network/dhcp-internal.h
index a0f9c229..e0269b54 100644
--- a/src/systemd/src/libsystemd-network/dhcp-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp-internal.h
@@ -51,5 +51,5 @@ int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, ui
 #define DHCP_CLIENT_DONT_DESTROY(client) \
         _cleanup_(sd_dhcp_client_unrefp) _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client)
 
-#define log_dhcp_client_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__)
+#define log_dhcp_client_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__)
 #define log_dhcp_client(client, fmt, ...) log_dhcp_client_errno(client, 0, fmt, ##__VA_ARGS__)
diff --git a/src/systemd/src/libsystemd-network/dhcp-network.c b/src/systemd/src/libsystemd-network/dhcp-network.c
index 45d2ed89..858fe57c 100644
--- a/src/systemd/src/libsystemd-network/dhcp-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp-network.c
@@ -5,6 +5,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if.h>
 #include <net/if_arp.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp-option.c b/src/systemd/src/libsystemd-network/dhcp-option.c
index eaf044bf..50238b0a 100644
--- a/src/systemd/src/libsystemd-network/dhcp-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp-option.c
@@ -5,6 +5,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <string.h>
@@ -199,7 +200,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo
                                 if (memchr(option, 0, len - 1))
                                         return -EINVAL;
 
-                                string = strndup((const char *) option, len);
+                                string = memdup_suffix0((const char *) option, len);
                                 if (!string)
                                         return -ENOMEM;
 
diff --git a/src/systemd/src/libsystemd-network/dhcp-packet.c b/src/systemd/src/libsystemd-network/dhcp-packet.c
index 91e8a54e..9e565e28 100644
--- a/src/systemd/src/libsystemd-network/dhcp-packet.c
+++ b/src/systemd/src/libsystemd-network/dhcp-packet.c
@@ -5,6 +5,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 #include <string.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp6-internal.h b/src/systemd/src/libsystemd-network/dhcp6-internal.h
index 157fc0aa..f28ba68d 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-internal.h
+++ b/src/systemd/src/libsystemd-network/dhcp6-internal.h
@@ -79,7 +79,7 @@ struct DHCP6IA {
 
 typedef struct DHCP6IA DHCP6IA;
 
-#define log_dhcp6_client_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__)
+#define log_dhcp6_client_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__)
 #define log_dhcp6_client(p, fmt, ...) log_dhcp6_client_errno(p, 0, fmt, ##__VA_ARGS__)
 
 int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
diff --git a/src/systemd/src/libsystemd-network/dhcp6-network.c b/src/systemd/src/libsystemd-network/dhcp6-network.c
index d786756e..73c195a7 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-network.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-network.c
@@ -5,6 +5,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <netinet/in.h>
 #include <netinet/ip6.h>
 #include <stdio.h>
diff --git a/src/systemd/src/libsystemd-network/dhcp6-option.c b/src/systemd/src/libsystemd-network/dhcp6-option.c
index 6172c368..562a34b5 100644
--- a/src/systemd/src/libsystemd-network/dhcp6-option.c
+++ b/src/systemd/src/libsystemd-network/dhcp6-option.c
@@ -5,6 +5,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <netinet/in.h>
 #include <string.h>
 
diff --git a/src/systemd/src/libsystemd-network/lldp-internal.h b/src/systemd/src/libsystemd-network/lldp-internal.h
index 88b54933..9598438d 100644
--- a/src/systemd/src/libsystemd-network/lldp-internal.h
+++ b/src/systemd/src/libsystemd-network/lldp-internal.h
@@ -32,7 +32,7 @@ struct sd_lldp {
         struct ether_addr filter_address;
 };
 
-#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
+#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
 #define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__)
 
 const char* lldp_event_to_string(sd_lldp_event e) _const_;
diff --git a/src/systemd/src/libsystemd-network/network-internal.c b/src/systemd/src/libsystemd-network/network-internal.c
index de219a6d..209ce884 100644
--- a/src/systemd/src/libsystemd-network/network-internal.c
+++ b/src/systemd/src/libsystemd-network/network-internal.c
@@ -14,6 +14,7 @@
 #include "conf-parser.h"
 #include "device-util.h"
 #include "dhcp-lease-internal.h"
+#include "env-util.h"
 #include "ether-addr-util.h"
 #include "hexdecoct.h"
 #include "log.h"
@@ -27,7 +28,7 @@
 #include "util.h"
 
 #if 0 /* NM_IGNORED */
-const char *net_get_name(sd_device *device) {
+const char *net_get_name_persistent(sd_device *device) {
         const char *name, *field;
 
         assert(device);
@@ -42,7 +43,7 @@ const char *net_get_name(sd_device *device) {
 
 #define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a)
 
-int net_get_unique_predictable_data(sd_device *device, uint64_t *result) {
+int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result) {
         size_t l, sz = 0;
         const char *name;
         int r;
@@ -50,10 +51,10 @@ int net_get_unique_predictable_data(sd_device *device, uint64_t *result) {
 
         assert(device);
 
-        /* net_get_name() will return one of the device names based on stable information about the
-         * device. If this is not available, we fall back to using the device name. */
-        name = net_get_name(device);
-        if (!name)
+        /* net_get_name_persistent() will return one of the device names based on stable information about
+         * the device. If this is not available, we fall back to using the actual device name. */
+        name = net_get_name_persistent(device);
+        if (!name && use_sysname)
                 (void) sd_device_get_sysname(device, &name);
         if (!name)
                 return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENODATA),
@@ -76,26 +77,66 @@ int net_get_unique_predictable_data(sd_device *device, uint64_t *result) {
         return 0;
 }
 
-static bool net_condition_test_strv(char * const *raw_patterns,
-                                    const char *string) {
-        if (strv_isempty(raw_patterns))
+static bool net_condition_test_strv(char * const *patterns, const char *string) {
+        char * const *p;
+        bool match = false, has_positive_rule = false;
+
+        if (strv_isempty(patterns))
                 return true;
 
-        /* If the patterns begin with "!", edit it out and negate the test. */
-        if (raw_patterns[0][0] == '!') {
-                char **patterns;
-                size_t i, length;
+        STRV_FOREACH(p, patterns) {
+                const char *q = *p;
+                bool invert;
+
+                invert = *q == '!';
+                q += invert;
+
+                if (!invert)
+                        has_positive_rule = true;
+
+                if (string && fnmatch(q, string, 0) == 0) {
+                        if (invert)
+                                return false;
+                        else
+                                match = true;
+                }
+        }
+
+        return has_positive_rule ? match : true;
+}
+
+static int net_condition_test_property(char * const *match_property, sd_device *device) {
+        char * const *p;
+
+        if (strv_isempty(match_property))
+                return true;
+
+        STRV_FOREACH(p, match_property) {
+                _cleanup_free_ char *key = NULL;
+                const char *val, *dev_val;
+                bool invert, v;
+
+                invert = **p == '!';
+
+                val = strchr(*p + invert, '=');
+                if (!val)
+                        return -EINVAL;
 
-                length = strv_length(raw_patterns) + 1; /* Include the NULL. */
-                patterns = newa(char*, length);
-                patterns[0] = raw_patterns[0] + 1; /* Skip the "!". */
-                for (i = 1; i < length; i++)
-                        patterns[i] = raw_patterns[i];
+                key = strndup(*p + invert, val - *p - invert);
+                if (!key)
+                        return -ENOMEM;
+
+                val++;
 
-                return !string || !strv_fnmatch(patterns, string, 0);
+                v = device &&
+                        sd_device_get_property_value(device, key, &dev_val) >= 0 &&
+                        fnmatch(val, dev_val, 0) == 0;
+
+                if (invert ? v : !v)
+                        return false;
         }
 
-        return string && strv_fnmatch(raw_patterns, string, 0);
+        return true;
 }
 
 bool net_match_config(Set *match_mac,
@@ -103,12 +144,25 @@ bool net_match_config(Set *match_mac,
                       char * const *match_drivers,
                       char * const *match_types,
                       char * const *match_names,
+                      char * const *match_property,
+                      sd_device *device,
                       const struct ether_addr *dev_mac,
-                      const char *dev_path,
-                      const char *dev_driver,
-                      const char *dev_type,
                       const char *dev_name) {
 
+        const char *dev_path = NULL, *dev_driver = NULL, *dev_type = NULL, *mac_str;
+
+        if (device) {
+                (void) sd_device_get_property_value(device, "ID_PATH", &dev_path);
+                (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &dev_driver);
+                (void) sd_device_get_devtype(device, &dev_type);
+
+                if (!dev_name)
+                        (void) sd_device_get_sysname(device, &dev_name);
+                if (!dev_mac &&
+                    sd_device_get_sysattr_value(device, "address", &mac_str) >= 0)
+                        dev_mac = ether_aton(mac_str);
+        }
+
         if (match_mac && (!dev_mac || !set_contains(match_mac, dev_mac)))
                 return false;
 
@@ -124,6 +178,9 @@ bool net_match_config(Set *match_mac,
         if (!net_condition_test_strv(match_names, dev_name))
                 return false;
 
+        if (!net_condition_test_property(match_property, device))
+                return false;
+
         return true;
 }
 
@@ -167,7 +224,7 @@ int config_parse_net_condition(const char *unit,
         return 0;
 }
 
-int config_parse_ifnames(
+int config_parse_match_strv(
                 const char *unit,
                 const char *filename,
                 unsigned line,
@@ -179,7 +236,9 @@ int config_parse_ifnames(
                 void *data,
                 void *userdata) {
 
+        const char *p = rvalue;
         char ***sv = data;
+        bool invert;
         int r;
 
         assert(filename);
@@ -187,30 +246,154 @@ int config_parse_ifnames(
         assert(rvalue);
         assert(data);
 
+        if (isempty(rvalue)) {
+                *sv = strv_free(*sv);
+                return 0;
+        }
+
+        invert = *p == '!';
+        p += invert;
+
         for (;;) {
-                _cleanup_free_ char *word = NULL;
+                _cleanup_free_ char *word = NULL, *k = NULL;
 
-                r = extract_first_word(&rvalue, &word, NULL, 0);
+                r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
+                if (r == 0)
+                        return 0;
+                if (r == -ENOMEM)
+                        return log_oom();
                 if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse interface name list: %s", rvalue);
+                        log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
                         return 0;
                 }
+
+                if (invert) {
+                        k = strjoin("!", word);
+                        if (!k)
+                                return log_oom();
+                } else
+                        k = TAKE_PTR(word);
+
+                r = strv_consume(sv, TAKE_PTR(k));
+                if (r < 0)
+                        return log_oom();
+        }
+}
+
+int config_parse_match_ifnames(
+                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) {
+
+        const char *p = rvalue;
+        char ***sv = data;
+        bool invert;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        invert = *p == '!';
+        p += invert;
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL, *k = NULL;
+
+                r = extract_first_word(&p, &word, NULL, 0);
                 if (r == 0)
-                        break;
+                        return 0;
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Failed to parse interface name list: %s", rvalue);
+                        return 0;
+                }
 
                 if (!ifname_valid(word)) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue);
-                        return 0;
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Interface name is not valid or too long, ignoring assignment: %s", word);
+                        continue;
                 }
 
-                r = strv_push(sv, word);
+                if (invert) {
+                        k = strjoin("!", word);
+                        if (!k)
+                                return log_oom();
+                } else
+                        k = TAKE_PTR(word);
+
+                r = strv_consume(sv, TAKE_PTR(k));
                 if (r < 0)
                         return log_oom();
-
-                word = NULL;
         }
+}
 
-        return 0;
+int config_parse_match_property(
+                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) {
+
+        const char *p = rvalue;
+        char ***sv = data;
+        bool invert;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        invert = *p == '!';
+        p += invert;
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL, *k = NULL;
+
+                r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE);
+                if (r == 0)
+                        return 0;
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Invalid syntax, ignoring: %s", rvalue);
+                        return 0;
+                }
+
+                if (!env_assignment_is_valid(word)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Invalid property or value, ignoring assignment: %s", word);
+                        continue;
+                }
+
+                if (invert) {
+                        k = strjoin("!", word);
+                        if (!k)
+                                return log_oom();
+                } else
+                        k = TAKE_PTR(word);
+
+                r = strv_consume(sv, TAKE_PTR(k));
+                if (r < 0)
+                        return log_oom();
+        }
 }
 
 int config_parse_ifalias(const char *unit,
@@ -616,7 +799,7 @@ int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t siz
         assert(data);
 
         hex_buf = hexmem(data, size);
-        if (hex_buf == NULL)
+        if (!hex_buf)
                 return -ENOMEM;
 
         fprintf(f, "%s=%s\n", key, hex_buf);
diff --git a/src/systemd/src/libsystemd-network/network-internal.h b/src/systemd/src/libsystemd-network/network-internal.h
index d895103b..487421fb 100644
--- a/src/systemd/src/libsystemd-network/network-internal.h
+++ b/src/systemd/src/libsystemd-network/network-internal.h
@@ -14,27 +14,28 @@
 #define LINK_BRIDGE_PORT_PRIORITY_INVALID 128
 #define LINK_BRIDGE_PORT_PRIORITY_MAX 63
 
+#if 0 /* NM_IGNORED */
 bool net_match_config(Set *match_mac,
                       char * const *match_path,
                       char * const *match_driver,
                       char * const *match_type,
                       char * const *match_name,
+                      char * const *match_property,
+                      sd_device *device,
                       const struct ether_addr *dev_mac,
-                      const char *dev_path,
-                      const char *dev_driver,
-                      const char *dev_type,
                       const char *dev_name);
 
-#if 0 /* NM_IGNORED */
 CONFIG_PARSER_PROTOTYPE(config_parse_net_condition);
 CONFIG_PARSER_PROTOTYPE(config_parse_hwaddr);
 CONFIG_PARSER_PROTOTYPE(config_parse_hwaddrs);
-CONFIG_PARSER_PROTOTYPE(config_parse_ifnames);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_strv);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_ifnames);
+CONFIG_PARSER_PROTOTYPE(config_parse_match_property);
 CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
 CONFIG_PARSER_PROTOTYPE(config_parse_bridge_port_priority);
 
-int net_get_unique_predictable_data(sd_device *device, uint64_t *result);
-const char *net_get_name(sd_device *device);
+int net_get_unique_predictable_data(sd_device *device, bool use_sysname, uint64_t *result);
+const char *net_get_name_persistent(sd_device *device);
 #endif /* NM_IGNORED */
 
 size_t serialize_in_addrs(FILE *f,
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index cadcc8a8..2f531bc7 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -5,6 +5,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 #include <stdio.h>
@@ -89,7 +90,8 @@ struct sd_dhcp_client {
         uint32_t mtu;
         uint32_t xid;
         usec_t start_time;
-        unsigned attempt;
+        uint64_t attempt;
+        uint64_t max_attempts;
         usec_t request_sent;
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
@@ -524,10 +526,18 @@ int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) {
         return 0;
 }
 
+int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempts) {
+        assert_return(client, -EINVAL);
+
+        client->max_attempts = max_attempts;
+
+        return 0;
+}
+
 int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         assert_return(client, -EINVAL);
 
-        if (!IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
+        if (!IN_SET(client->state, DHCP_STATE_SELECTING, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
                 return -EADDRNOTAVAIL;
 
         if (ret)
@@ -536,11 +546,13 @@ int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         return 0;
 }
 
-static void client_notify(sd_dhcp_client *client, int event) {
+static int client_notify(sd_dhcp_client *client, int event) {
         assert(client);
 
         if (client->callback)
-                client->callback(client, event, client->userdata);
+                return client->callback(client, event, client->userdata);
+
+        return 0;
 }
 
 static int client_initialize(sd_dhcp_client *client) {
@@ -555,7 +567,7 @@ static int client_initialize(sd_dhcp_client *client) {
         (void) event_source_disable(client->timeout_t2);
         (void) event_source_disable(client->timeout_expire);
 
-        client->attempt = 1;
+        client->attempt = 0;
 
         client->state = DHCP_STATE_INIT;
         client->xid = 0;
@@ -569,7 +581,7 @@ static void client_stop(sd_dhcp_client *client, int error) {
         assert(client);
 
         if (error < 0)
-                log_dhcp_client(client, "STOPPED: %s", strerror(-error));
+                log_dhcp_client_errno(client, error, "STOPPED: %m");
         else if (error == SD_DHCP_CLIENT_EVENT_STOP)
                 log_dhcp_client(client, "STOPPED");
         else
@@ -599,7 +611,7 @@ static int client_message_init(
         assert(ret);
         assert(_optlen);
         assert(_optoffset);
-        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST));
+        assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE));
 
         optlen = DHCP_MIN_OPTIONS_SIZE;
         size = sizeof(DHCPPacket) + optlen;
@@ -690,7 +702,7 @@ static int client_message_init(
            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) {
+        if (client->req_opts_size > 0 && type != DHCP_RELEASE) {
                 r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
                                        SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
                                        client->req_opts_size, client->req_opts);
@@ -722,7 +734,7 @@ static int client_message_init(
          */
         /* RFC7844 section 3:
            SHOULD NOT contain any other option. */
-        if (!client->anonymize) {
+        if (!client->anonymize && type != DHCP_RELEASE) {
                 max_size = htobe16(size);
                 r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
                                        SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
@@ -854,6 +866,41 @@ static int client_send_discover(sd_dhcp_client *client) {
         return 0;
 }
 
+static int client_send_release(sd_dhcp_client *client) {
+        _cleanup_free_ DHCPPacket *release = NULL;
+        size_t optoffset, optlen;
+        int r;
+
+        assert(client);
+        assert(!IN_SET(client->state, DHCP_STATE_STOPPED));
+
+        r = client_message_init(client, &release, DHCP_RELEASE,
+                                &optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        /* Fill up release IP and MAC */
+        release->dhcp.ciaddr = client->lease->address;
+        memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+        r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+                               SD_DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        r = dhcp_network_send_udp_socket(client->fd,
+                                         client->lease->server_address,
+                                         DHCP_PORT_SERVER,
+                                         &release->dhcp,
+                                         sizeof(DHCPMessage) + optoffset);
+        if (r < 0)
+                return r;
+
+        log_dhcp_client(client, "RELEASE");
+
+        return 0;
+}
+
 static int client_send_request(sd_dhcp_client *client) {
         _cleanup_free_ DHCPPacket *request = NULL;
         size_t optoffset, optlen;
@@ -1054,10 +1101,12 @@ static int client_timeout_resend(
         case DHCP_STATE_REQUESTING:
         case DHCP_STATE_BOUND:
 
-                if (client->attempt < 64)
-                        client->attempt *= 2;
+                if (client->attempt < client->max_attempts)
+                        client->attempt++;
+                else
+                        goto error;
 
-                next_timeout = time_now + (client->attempt - 1) * USEC_PER_SEC;
+                next_timeout = time_now + ((UINT64_C(1) << MIN(client->attempt, (uint64_t) 6)) - 1) * USEC_PER_SEC;
 
                 break;
 
@@ -1081,17 +1130,15 @@ static int client_timeout_resend(
                 r = client_send_discover(client);
                 if (r >= 0) {
                         client->state = DHCP_STATE_SELECTING;
-                        client->attempt = 1;
-                } else {
-                        if (client->attempt >= 64)
-                                goto error;
-                }
+                        client->attempt = 0;
+                } else if (client->attempt >= client->max_attempts)
+                        goto error;
 
                 break;
 
         case DHCP_STATE_SELECTING:
                 r = client_send_discover(client);
-                if (r < 0 && client->attempt >= 64)
+                if (r < 0 && client->attempt >= client->max_attempts)
                         goto error;
 
                 break;
@@ -1101,7 +1148,7 @@ static int client_timeout_resend(
         case DHCP_STATE_RENEWING:
         case DHCP_STATE_REBINDING:
                 r = client_send_request(client);
-                if (r < 0 && client->attempt >= 64)
+                if (r < 0 && client->attempt >= client->max_attempts)
                          goto error;
 
                 if (client->state == DHCP_STATE_INIT_REBOOT)
@@ -1253,7 +1300,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata)
         client->fd = asynchronous_close(client->fd);
 
         client->state = DHCP_STATE_REBINDING;
-        client->attempt = 1;
+        client->attempt = 0;
 
         r = dhcp_network_bind_raw_socket(client->ifindex, &client->link,
                                          client->xid, client->mac_addr,
@@ -1273,7 +1320,7 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata)
         DHCP_CLIENT_DONT_DESTROY(client);
 
         client->state = DHCP_STATE_RENEWING;
-        client->attempt = 1;
+        client->attempt = 0;
 
         return client_initialize_time_events(client);
 }
@@ -1323,6 +1370,9 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_
         sd_dhcp_lease_unref(client->lease);
         client->lease = TAKE_PTR(lease);
 
+        if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
+                return -ENOMSG;
+
         log_dhcp_client(client, "OFFER");
 
         return 0;
@@ -1340,6 +1390,23 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force,
         return 0;
 }
 
+static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
+        if (a->address != b->address)
+                return false;
+
+        if (a->subnet_mask != b->subnet_mask)
+                return false;
+
+        if (a->router_size != b->router_size)
+                return false;
+
+        for (size_t i = 0; i < a->router_size; i++)
+                if (a->router[i].s_addr != b->router[i].s_addr)
+                        return false;
+
+        return true;
+}
+
 static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) {
         _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
         _cleanup_free_ char *error_message = NULL;
@@ -1392,12 +1459,10 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le
 
         r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
         if (client->lease) {
-                if (client->lease->address != lease->address ||
-                    client->lease->subnet_mask != lease->subnet_mask ||
-                    client->lease->router != lease->router) {
-                        r = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
-                } else
+                if (lease_equal(client->lease, lease))
                         r = SD_DHCP_CLIENT_EVENT_RENEW;
+                else
+                        r = SD_DHCP_CLIENT_EVENT_IP_CHANGE;
 
                 client->lease = sd_dhcp_lease_unref(client->lease);
         }
@@ -1557,7 +1622,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                 if (r >= 0) {
 
                         client->state = DHCP_STATE_REQUESTING;
-                        client->attempt = 1;
+                        client->attempt = 0;
 
                         r = event_reset_time(client->event, &client->timeout_resend,
                                              clock_boottime_or_monotonic(),
@@ -1592,7 +1657,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
                                 notify_event = r;
 
                         client->state = DHCP_STATE_BOUND;
-                        client->attempt = 1;
+                        client->attempt = 0;
 
                         client->last_addr = client->lease->address;
 
@@ -1848,6 +1913,14 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
         return r;
 }
 
+int sd_dhcp_client_send_release(sd_dhcp_client *client) {
+        assert_return(client, -EINVAL);
+
+        client_send_release(client);
+
+        return 0;
+}
+
 int sd_dhcp_client_stop(sd_dhcp_client *client) {
         DHCP_CLIENT_DONT_DESTROY(client);
 
@@ -1931,10 +2004,10 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
                 .state = DHCP_STATE_INIT,
                 .ifindex = -1,
                 .fd = -1,
-                .attempt = 1,
                 .mtu = DHCP_DEFAULT_MIN_SIZE,
                 .port = DHCP_PORT_CLIENT,
                 .anonymize = !!anonymize,
+                .max_attempts = (uint64_t) -1,
         };
         /* NOTE: this could be moved to a function. */
         if (anonymize) {
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
index 6c7aecc8..7559d066 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-lease.c
@@ -6,8 +6,8 @@
 #include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
+#include <errno.h>
 #include <stdio.h>
-#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
@@ -335,7 +335,7 @@ static int lease_parse_string(const uint8_t *option, size_t len, char **ret) {
                 if (memchr(option, 0, len - 1))
                         return -EINVAL;
 
-                string = strndup((const char *) option, len);
+                string = memdup_suffix0((const char *) option, len);
                 if (!string)
                         return -ENOMEM;
 
@@ -833,7 +833,6 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
         if (r < 0)
                 goto fail;
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
         (void) fchmod(fileno(f), 0644);
 
         fprintf(f,
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
index b72cd82d..f67a45bd 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-client.c
@@ -5,8 +5,10 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <string.h>
 #include <sys/ioctl.h>
+#include <linux/if_arp.h>
 #include <linux/if_infiniband.h>
 
 #include "sd-dhcp6-client.h"
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
index 7263c96f..35caec7a 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp6-lease.c
@@ -5,6 +5,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 
 #include "alloc-util.h"
 #include "dhcp6-lease-internal.h"
@@ -206,12 +207,8 @@ int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) {
         r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns,
                                         lease->dns_count,
                                         &lease->dns_allocated);
-        if (r < 0) {
-                log_dhcp6_client(client, "Invalid DNS server option: %s",
-                                 strerror(-r));
-
-                return r;
-        }
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Invalid DNS server option: %m");
 
         lease->dns_count = r;
 
@@ -337,12 +334,8 @@ int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen)
         r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp,
                                         lease->ntp_count,
                                         &lease->ntp_allocated);
-        if (r < 0) {
-                log_dhcp6_client(client, "Invalid SNTP server option: %s",
-                                 strerror(-r));
-
-                return r;
-        }
+        if (r < 0)
+                return log_dhcp6_client_errno(client, r, "Invalid SNTP server option: %m");
 
         lease->ntp_count = r;
 
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4acd.c b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
index 53ce8929..e7561480 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4acd.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4acd.c
@@ -6,6 +6,8 @@
 #include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/if_ether.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -73,7 +75,7 @@ struct sd_ipv4acd {
         void* userdata;
 };
 
-#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__)
+#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__)
 #define log_ipv4acd(acd, fmt, ...) log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__)
 
 static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) {
diff --git a/src/systemd/src/libsystemd-network/sd-ipv4ll.c b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
index 4f186762..daac8839 100644
--- a/src/systemd/src/libsystemd-network/sd-ipv4ll.c
+++ b/src/systemd/src/libsystemd-network/sd-ipv4ll.c
@@ -6,6 +6,7 @@
 #include "nm-sd-adapt-core.h"
 
 #include <arpa/inet.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -51,7 +52,7 @@ struct sd_ipv4ll {
         void* userdata;
 };
 
-#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__)
+#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, PROJECT_FILE, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__)
 #define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__)
 
 static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata);
diff --git a/src/systemd/src/libsystemd/sd-event/event-util.c b/src/systemd/src/libsystemd/sd-event/event-util.c
index 2afacfe6..e8384cfd 100644
--- a/src/systemd/src/libsystemd/sd-event/event-util.c
+++ b/src/systemd/src/libsystemd/sd-event/event-util.c
@@ -2,6 +2,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 
 #include "event-source.h"
 #include "event-util.h"
diff --git a/src/systemd/src/libsystemd/sd-event/sd-event.c b/src/systemd/src/libsystemd/sd-event/sd-event.c
index dad0801b..c3781706 100644
--- a/src/systemd/src/libsystemd/sd-event/sd-event.c
+++ b/src/systemd/src/libsystemd/sd-event/sd-event.c
@@ -25,6 +25,7 @@
 #include "signal-util.h"
 #include "string-table.h"
 #include "string-util.h"
+#include "strxcpyx.h"
 #include "time-util.h"
 
 #define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC)
@@ -341,6 +342,12 @@ fail:
 
 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free);
 
+_public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) {
+        if (s)
+                (void) sd_event_source_set_enabled(s, SD_EVENT_OFF);
+        return sd_event_source_unref(s);
+}
+
 static bool event_pid_changed(sd_event *e) {
         assert(e);
 
@@ -3026,7 +3033,7 @@ static void event_close_inode_data_fds(sd_event *e) {
 
         /* Close the fds pointing to the inodes to watch now. We need to close them as they might otherwise pin
          * filesystems. But we can't close them right-away as we need them as long as the user still wants to make
-         * adjustments to the even source, such as changing the priority (which requires us to remove and readd a watch
+         * adjustments to the even source, such as changing the priority (which requires us to remove and re-add a watch
          * for the inode). Hence, let's close them when entering the first iteration after they were added, as a
          * compromise. */
 
@@ -3246,15 +3253,16 @@ _public_ int sd_event_dispatch(sd_event *e) {
 }
 
 static void event_log_delays(sd_event *e) {
-        char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1];
-        unsigned i;
-        int o;
+        char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1], *p;
+        size_t l, i;
 
-        for (i = o = 0; i < ELEMENTSOF(e->delays); i++) {
-                o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]);
+        p = b;
+        l = sizeof(b);
+        for (i = 0; i < ELEMENTSOF(e->delays); i++) {
+                l = strpcpyf(&p, l, "%u ", e->delays[i]);
                 e->delays[i] = 0;
         }
-        log_debug("Event loop iterations: %.*s", o, b);
+        log_debug("Event loop iterations: %s", b);
 }
 
 _public_ int sd_event_run(sd_event *e, uint64_t timeout) {
diff --git a/src/systemd/src/libsystemd/sd-id128/id128-util.c b/src/systemd/src/libsystemd/sd-id128/id128-util.c
index 6245e9df..f8f0883c 100644
--- a/src/systemd/src/libsystemd/sd-id128/id128-util.c
+++ b/src/systemd/src/libsystemd/sd-id128/id128-util.c
@@ -2,6 +2,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
 
diff --git a/src/systemd/src/libsystemd/sd-id128/sd-id128.c b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
index d0cae981..eeb29e97 100644
--- a/src/systemd/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/systemd/src/libsystemd/sd-id128/sd-id128.c
@@ -2,6 +2,7 @@
 
 #include "nm-sd-adapt-core.h"
 
+#include <errno.h>
 #include <fcntl.h>
 #include <unistd.h>
 
diff --git a/src/systemd/src/shared/dns-domain.c b/src/systemd/src/shared/dns-domain.c
deleted file mode 100644
index ebea861f..00000000
--- a/src/systemd/src/shared/dns-domain.c
+++ /dev/null
@@ -1,1387 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-
-#include "nm-sd-adapt-core.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>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/socket.h>
-
-#include "alloc-util.h"
-#include "dns-domain.h"
-#include "hashmap.h"
-#include "hexdecoct.h"
-#include "hostname-util.h"
-#include "in-addr-util.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "string-util.h"
-#include "strv.h"
-#include "utf8.h"
-
-int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags) {
-        const char *n;
-        char *d, last_char = 0;
-        int r = 0;
-
-        assert(name);
-        assert(*name);
-
-        n = *name;
-        d = dest;
-
-        for (;;) {
-                if (*n == 0 || *n == '.') {
-                        if (FLAGS_SET(flags, DNS_LABEL_LDH) && last_char == '-')
-                                /* Trailing dash */
-                                return -EINVAL;
-
-                        if (*n == '.')
-                                n++;
-                        break;
-                }
-
-                if (r >= DNS_LABEL_MAX)
-                        return -EINVAL;
-
-                if (sz <= 0)
-                        return -ENOBUFS;
-
-                if (*n == '\\') {
-                        /* Escaped character */
-                        if (FLAGS_SET(flags, DNS_LABEL_NO_ESCAPES))
-                                return -EINVAL;
-
-                        n++;
-
-                        if (*n == 0)
-                                /* Ending NUL */
-                                return -EINVAL;
-
-                        else if (IN_SET(*n, '\\', '.')) {
-                                /* Escaped backslash or dot */
-
-                                if (FLAGS_SET(flags, DNS_LABEL_LDH))
-                                        return -EINVAL;
-
-                                last_char = *n;
-                                if (d)
-                                        *(d++) = *n;
-                                sz--;
-                                r++;
-                                n++;
-
-                        } else if (n[0] >= '0' && n[0] <= '9') {
-                                unsigned k;
-
-                                /* Escaped literal ASCII character */
-
-                                if (!(n[1] >= '0' && n[1] <= '9') ||
-                                    !(n[2] >= '0' && n[2] <= '9'))
-                                        return -EINVAL;
-
-                                k = ((unsigned) (n[0] - '0') * 100) +
-                                        ((unsigned) (n[1] - '0') * 10) +
-                                        ((unsigned) (n[2] - '0'));
-
-                                /* Don't allow anything that doesn't
-                                 * fit in 8bit. Note that we do allow
-                                 * control characters, as some servers
-                                 * (e.g. cloudflare) are happy to
-                                 * generate labels with them
-                                 * inside. */
-                                if (k > 255)
-                                        return -EINVAL;
-
-                                if (FLAGS_SET(flags, DNS_LABEL_LDH) &&
-                                    !valid_ldh_char((char) k))
-                                        return -EINVAL;
-
-                                last_char = (char) k;
-                                if (d)
-                                        *(d++) = (char) k;
-                                sz--;
-                                r++;
-
-                                n += 3;
-                        } else
-                                return -EINVAL;
-
-                } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) {
-
-                        /* Normal character */
-
-                        if (FLAGS_SET(flags, DNS_LABEL_LDH)) {
-                                if (!valid_ldh_char(*n))
-                                        return -EINVAL;
-                                if (r == 0 && *n == '-')
-                                        /* Leading dash */
-                                        return -EINVAL;
-                        }
-
-                        last_char = *n;
-                        if (d)
-                                *(d++) = *n;
-                        sz--;
-                        r++;
-                        n++;
-                } else
-                        return -EINVAL;
-        }
-
-        /* Empty label that is not at the end? */
-        if (r == 0 && *n)
-                return -EINVAL;
-
-        /* More than one trailing dot? */
-        if (*n == '.')
-                return -EINVAL;
-
-        if (sz >= 1 && d)
-                *d = 0;
-
-        *name = n;
-        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. */
-int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) {
-        const char *terminal;
-        int r;
-
-        assert(name);
-        assert(label_terminal);
-        assert(dest);
-
-        /* no more labels */
-        if (!*label_terminal) {
-                if (sz >= 1)
-                        *dest = 0;
-
-                return 0;
-        }
-
-        terminal = *label_terminal;
-        assert(IN_SET(*terminal, 0, '.'));
-
-        /* Skip current terminal character (and accept domain names ending it ".") */
-        if (*terminal == 0)
-                terminal--;
-        if (terminal >= name && *terminal == '.')
-                terminal--;
-
-        /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */
-        for (;;) {
-                if (terminal < name) {
-                        /* Reached the first label, so indicate that there are no more */
-                        terminal = NULL;
-                        break;
-                }
-
-                /* Find the start of the last label */
-                if (*terminal == '.') {
-                        const char *y;
-                        unsigned slashes = 0;
-
-                        for (y = terminal - 1; y >= name && *y == '\\'; y--)
-                                slashes++;
-
-                        if (slashes % 2 == 0) {
-                                /* The '.' was not escaped */
-                                name = terminal + 1;
-                                break;
-                        } else {
-                                terminal = y;
-                                continue;
-                        }
-                }
-
-                terminal--;
-        }
-
-        r = dns_label_unescape(&name, dest, sz, 0);
-        if (r < 0)
-                return r;
-
-        *label_terminal = terminal;
-
-        return r;
-}
-#endif /* NM_IGNORED */
-
-int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) {
-        char *q;
-
-        /* DNS labels must be between 1 and 63 characters long. A
-         * zero-length label does not exist. See RFC 2182, Section
-         * 11. */
-
-        if (l <= 0 || l > DNS_LABEL_MAX)
-                return -EINVAL;
-        if (sz < 1)
-                return -ENOBUFS;
-
-        assert(p);
-        assert(dest);
-
-        q = dest;
-        while (l > 0) {
-
-                if (IN_SET(*p, '.', '\\')) {
-
-                        /* Dot or backslash */
-
-                        if (sz < 3)
-                                return -ENOBUFS;
-
-                        *(q++) = '\\';
-                        *(q++) = *p;
-
-                        sz -= 2;
-
-                } else if (IN_SET(*p, '_', '-') ||
-                           (*p >= '0' && *p <= '9') ||
-                           (*p >= 'a' && *p <= 'z') ||
-                           (*p >= 'A' && *p <= 'Z')) {
-
-                        /* Proper character */
-
-                        if (sz < 2)
-                                return -ENOBUFS;
-
-                        *(q++) = *p;
-                        sz -= 1;
-
-                } else {
-
-                        /* Everything else */
-
-                        if (sz < 5)
-                                return -ENOBUFS;
-
-                        *(q++) = '\\';
-                        *(q++) = '0' + (char) ((uint8_t) *p / 100);
-                        *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10);
-                        *(q++) = '0' + (char) ((uint8_t) *p % 10);
-
-                        sz -= 4;
-                }
-
-                p++;
-                l--;
-        }
-
-        *q = 0;
-        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;
-
-        assert(p);
-        assert(ret);
-
-        if (l <= 0 || l > DNS_LABEL_MAX)
-                return -EINVAL;
-
-        s = new(char, DNS_LABEL_ESCAPED_MAX);
-        if (!s)
-                return -ENOMEM;
-
-        r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX);
-        if (r < 0)
-                return r;
-
-        *ret = TAKE_PTR(s);
-
-        return r;
-}
-
-#if HAVE_LIBIDN
-int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
-        _cleanup_free_ uint32_t *input = NULL;
-        size_t input_size, l;
-        const char *p;
-        bool contains_8bit = false;
-        char buffer[DNS_LABEL_MAX+1];
-
-        assert(encoded);
-        assert(decoded);
-
-        /* Converts an U-label into an A-label */
-
-        if (encoded_size <= 0)
-                return -EINVAL;
-
-        for (p = encoded; p < encoded + encoded_size; p++)
-                if ((uint8_t) *p > 127)
-                        contains_8bit = true;
-
-        if (!contains_8bit) {
-                if (encoded_size > DNS_LABEL_MAX)
-                        return -EINVAL;
-
-                return 0;
-        }
-
-        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
-        if (!input)
-                return -ENOMEM;
-
-        if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0)
-                return -EINVAL;
-
-        l = strlen(buffer);
-
-        /* Verify that the result is not longer than one DNS label. */
-        if (l <= 0 || l > DNS_LABEL_MAX)
-                return -EINVAL;
-        if (l > decoded_max)
-                return -ENOBUFS;
-
-        memcpy(decoded, buffer, l);
-
-        /* If there's room, append a trailing NUL byte, but only then */
-        if (decoded_max > l)
-                decoded[l] = 0;
-
-        return (int) l;
-}
-
-int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
-        size_t input_size, output_size;
-        _cleanup_free_ uint32_t *input = NULL;
-        _cleanup_free_ char *result = NULL;
-        uint32_t *output = NULL;
-        size_t w;
-
-        /* To be invoked after unescaping. Converts an A-label into an U-label. */
-
-        assert(encoded);
-        assert(decoded);
-
-        if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX)
-                return -EINVAL;
-
-        if (!memory_startswith(encoded, encoded_size, IDNA_ACE_PREFIX))
-                return 0;
-
-        input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size);
-        if (!input)
-                return -ENOMEM;
-
-        output_size = input_size;
-        output = newa(uint32_t, output_size);
-
-        idna_to_unicode_44i(input, input_size, output, &output_size, 0);
-
-        result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w);
-        if (!result)
-                return -ENOMEM;
-        if (w <= 0)
-                return -EINVAL;
-        if (w > decoded_max)
-                return -ENOBUFS;
-
-        memcpy(decoded, result, w);
-
-        /* Append trailing NUL byte if there's space, but only then. */
-        if (decoded_max > w)
-                decoded[w] = 0;
-
-        return w;
-}
-#endif
-#endif /* NM_IGNORED */
-
-int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_ret) {
-        _cleanup_free_ char *ret = NULL;
-        size_t n = 0, allocated = 0;
-        const char *p;
-        bool first = true;
-        int r;
-
-        if (a)
-                p = a;
-        else if (b)
-                p = TAKE_PTR(b);
-        else
-                goto finish;
-
-        for (;;) {
-                char label[DNS_LABEL_MAX];
-
-                r = dns_label_unescape(&p, label, sizeof label, flags);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        if (*p != 0)
-                                return -EINVAL;
-
-                        if (b) {
-                                /* Now continue with the second string, if there is one */
-                                p = TAKE_PTR(b);
-                                continue;
-                        }
-
-                        break;
-                }
-
-                if (_ret) {
-                        if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
-                                return -ENOMEM;
-
-                        r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX);
-                        if (r < 0)
-                                return r;
-
-                        if (!first)
-                                ret[n] = '.';
-                } else {
-                        char escaped[DNS_LABEL_ESCAPED_MAX];
-
-                        r = dns_label_escape(label, r, escaped, sizeof(escaped));
-                        if (r < 0)
-                                return r;
-                }
-
-                if (!first)
-                        n++;
-                else
-                        first = false;
-
-                n += r;
-        }
-
-finish:
-        if (n > DNS_HOSTNAME_MAX)
-                return -EINVAL;
-
-        if (_ret) {
-                if (n == 0) {
-                        /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */
-                        if (!GREEDY_REALLOC(ret, allocated, 2))
-                                return -ENOMEM;
-
-                        ret[n++] = '.';
-                } else {
-                        if (!GREEDY_REALLOC(ret, allocated, n + 1))
-                                return -ENOMEM;
-                }
-
-                ret[n] = 0;
-                *_ret = TAKE_PTR(ret);
-        }
-
-        return 0;
-}
-
-#if 0 /* NM_IGNORED */
-void dns_name_hash_func(const char *p, struct siphash *state) {
-        int r;
-
-        assert(p);
-
-        for (;;) {
-                char label[DNS_LABEL_MAX+1];
-
-                r = dns_label_unescape(&p, label, sizeof label, 0);
-                if (r < 0)
-                        break;
-                if (r == 0)
-                        break;
-
-                ascii_strlower_n(label, r);
-                siphash24_compress(label, r, state);
-                siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */
-        }
-
-        /* enforce that all names are terminated by the empty label */
-        string_hash_func("", state);
-}
-
-int dns_name_compare_func(const char *a, const char *b) {
-        const char *x, *y;
-        int r, q;
-
-        assert(a);
-        assert(b);
-
-        x = a + strlen(a);
-        y = b + strlen(b);
-
-        for (;;) {
-                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
-
-                if (x == NULL && y == NULL)
-                        return 0;
-
-                r = dns_label_unescape_suffix(a, &x, la, sizeof(la));
-                q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb));
-                if (r < 0 || q < 0)
-                        return CMP(r, q);
-
-                r = ascii_strcasecmp_nn(la, r, lb, q);
-                if (r != 0)
-                        return r;
-        }
-}
-
-DEFINE_HASH_OPS(dns_name_hash_ops, char, dns_name_hash_func, dns_name_compare_func);
-
-int dns_name_equal(const char *x, const char *y) {
-        int r, q;
-
-        assert(x);
-        assert(y);
-
-        for (;;) {
-                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
-
-                r = dns_label_unescape(&x, la, sizeof la, 0);
-                if (r < 0)
-                        return r;
-
-                q = dns_label_unescape(&y, lb, sizeof lb, 0);
-                if (q < 0)
-                        return q;
-
-                if (r != q)
-                        return false;
-                if (r == 0)
-                        return true;
-
-                if (ascii_strcasecmp_n(la, lb, r) != 0)
-                        return false;
-        }
-}
-
-int dns_name_endswith(const char *name, const char *suffix) {
-        const char *n, *s, *saved_n = NULL;
-        int r, q;
-
-        assert(name);
-        assert(suffix);
-
-        n = name;
-        s = suffix;
-
-        for (;;) {
-                char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
-
-                r = dns_label_unescape(&n, ln, sizeof ln, 0);
-                if (r < 0)
-                        return r;
-
-                if (!saved_n)
-                        saved_n = n;
-
-                q = dns_label_unescape(&s, ls, sizeof ls, 0);
-                if (q < 0)
-                        return q;
-
-                if (r == 0 && q == 0)
-                        return true;
-                if (r == 0 && saved_n == n)
-                        return false;
-
-                if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
-
-                        /* Not the same, let's jump back, and try with the next label again */
-                        s = suffix;
-                        n = TAKE_PTR(saved_n);
-                }
-        }
-}
-
-int dns_name_startswith(const char *name, const char *prefix) {
-        const char *n, *p;
-        int r, q;
-
-        assert(name);
-        assert(prefix);
-
-        n = name;
-        p = prefix;
-
-        for (;;) {
-                char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX];
-
-                r = dns_label_unescape(&p, lp, sizeof lp, 0);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        return true;
-
-                q = dns_label_unescape(&n, ln, sizeof ln, 0);
-                if (q < 0)
-                        return q;
-
-                if (r != q)
-                        return false;
-                if (ascii_strcasecmp_n(ln, lp, r) != 0)
-                        return false;
-        }
-}
-
-int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) {
-        const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix;
-        int r, q;
-
-        assert(name);
-        assert(old_suffix);
-        assert(new_suffix);
-        assert(ret);
-
-        n = name;
-        s = old_suffix;
-
-        for (;;) {
-                char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX];
-
-                if (!saved_before)
-                        saved_before = n;
-
-                r = dns_label_unescape(&n, ln, sizeof ln, 0);
-                if (r < 0)
-                        return r;
-
-                if (!saved_after)
-                        saved_after = n;
-
-                q = dns_label_unescape(&s, ls, sizeof ls, 0);
-                if (q < 0)
-                        return q;
-
-                if (r == 0 && q == 0)
-                        break;
-                if (r == 0 && saved_after == n) {
-                        *ret = NULL; /* doesn't match */
-                        return 0;
-                }
-
-                if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) {
-
-                        /* Not the same, let's jump back, and try with the next label again */
-                        s = old_suffix;
-                        n = TAKE_PTR(saved_after);
-                        saved_before = NULL;
-                }
-        }
-
-        /* Found it! Now generate the new name */
-        prefix = strndupa(name, saved_before - name);
-
-        r = dns_name_concat(prefix, new_suffix, 0, ret);
-        if (r < 0)
-                return r;
-
-        return 1;
-}
-
-int dns_name_between(const char *a, const char *b, const char *c) {
-        /* Determine if b is strictly greater than a and strictly smaller than c.
-           We consider the order of names to be circular, so that if a is
-           strictly greater than c, we consider b to be between them if it is
-           either greater than a or smaller than c. This is how the canonical
-           DNS name order used in NSEC records work. */
-
-        if (dns_name_compare_func(a, c) < 0)
-                /*
-                   a and c are properly ordered:
-                   a<---b--->c
-                */
-                return dns_name_compare_func(a, b) < 0 &&
-                       dns_name_compare_func(b, c) < 0;
-        else
-                /*
-                   a and c are equal or 'reversed':
-                   <--b--c         a----->
-                   or:
-                   <-----c         a--b-->
-                */
-                return dns_name_compare_func(b, c) < 0 ||
-                       dns_name_compare_func(a, b) < 0;
-}
-
-int dns_name_reverse(int family, const union in_addr_union *a, char **ret) {
-        const uint8_t *p;
-        int r;
-
-        assert(a);
-        assert(ret);
-
-        p = (const uint8_t*) a;
-
-        if (family == AF_INET)
-                r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]);
-        else if (family == AF_INET6)
-                r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa",
-                             hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4),
-                             hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4),
-                             hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4),
-                             hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4),
-                             hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4),
-                             hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4),
-                             hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4),
-                             hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4));
-        else
-                return -EAFNOSUPPORT;
-        if (r < 0)
-                return -ENOMEM;
-
-        return 0;
-}
-
-int dns_name_address(const char *p, int *family, union in_addr_union *address) {
-        int r;
-
-        assert(p);
-        assert(family);
-        assert(address);
-
-        r = dns_name_endswith(p, "in-addr.arpa");
-        if (r < 0)
-                return r;
-        if (r > 0) {
-                uint8_t a[4];
-                unsigned i;
-
-                for (i = 0; i < ELEMENTSOF(a); i++) {
-                        char label[DNS_LABEL_MAX+1];
-
-                        r = dns_label_unescape(&p, label, sizeof label, 0);
-                        if (r < 0)
-                                return r;
-                        if (r == 0)
-                                return -EINVAL;
-                        if (r > 3)
-                                return -EINVAL;
-
-                        r = safe_atou8(label, &a[i]);
-                        if (r < 0)
-                                return r;
-                }
-
-                r = dns_name_equal(p, "in-addr.arpa");
-                if (r <= 0)
-                        return r;
-
-                *family = AF_INET;
-                address->in.s_addr = htobe32(((uint32_t) a[3] << 24) |
-                                             ((uint32_t) a[2] << 16) |
-                                             ((uint32_t) a[1] << 8) |
-                                              (uint32_t) a[0]);
-
-                return 1;
-        }
-
-        r = dns_name_endswith(p, "ip6.arpa");
-        if (r < 0)
-                return r;
-        if (r > 0) {
-                struct in6_addr a;
-                unsigned i;
-
-                for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
-                        char label[DNS_LABEL_MAX+1];
-                        int x, y;
-
-                        r = dns_label_unescape(&p, label, sizeof label, 0);
-                        if (r <= 0)
-                                return r;
-                        if (r != 1)
-                                return -EINVAL;
-                        x = unhexchar(label[0]);
-                        if (x < 0)
-                                return -EINVAL;
-
-                        r = dns_label_unescape(&p, label, sizeof label, 0);
-                        if (r <= 0)
-                                return r;
-                        if (r != 1)
-                                return -EINVAL;
-                        y = unhexchar(label[0]);
-                        if (y < 0)
-                                return -EINVAL;
-
-                        a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x;
-                }
-
-                r = dns_name_equal(p, "ip6.arpa");
-                if (r <= 0)
-                        return r;
-
-                *family = AF_INET6;
-                address->in6 = a;
-                return 1;
-        }
-
-        return 0;
-}
-#endif /* NM_IGNORED */
-
-bool dns_name_is_root(const char *name) {
-
-        assert(name);
-
-        /* There are exactly two ways to encode the root domain name:
-         * as empty string, or with a single dot. */
-
-        return STR_IN_SET(name, "", ".");
-}
-
-bool dns_name_is_single_label(const char *name) {
-        int r;
-
-        assert(name);
-
-        r = dns_name_parent(&name);
-        if (r <= 0)
-                return false;
-
-        return dns_name_is_root(name);
-}
-
-/* Encode a domain name according to RFC 1035 Section 3.1, without compression */
-int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) {
-        uint8_t *label_length, *out;
-        int r;
-
-        assert(domain);
-        assert(buffer);
-
-        out = buffer;
-
-        do {
-                /* Reserve a byte for label length */
-                if (len <= 0)
-                        return -ENOBUFS;
-                len--;
-                label_length = out;
-                out++;
-
-                /* Convert and copy a single label. Note that
-                 * dns_label_unescape() returns 0 when it hits the end
-                 * of the domain name, which we rely on here to encode
-                 * the trailing NUL byte. */
-                r = dns_label_unescape(&domain, (char *) out, len, 0);
-                if (r < 0)
-                        return r;
-
-                /* Optionally, output the name in DNSSEC canonical
-                 * format, as described in RFC 4034, section 6.2. Or
-                 * in other words: in lower-case. */
-                if (canonical)
-                        ascii_strlower_n((char*) out, (size_t) r);
-
-                /* Fill label length, move forward */
-                *label_length = r;
-                out += r;
-                len -= r;
-
-        } while (r != 0);
-
-        /* Verify the maximum size of the encoded name. The trailing
-         * dot + NUL byte account are included this time, hence
-         * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this
-         * time. */
-        if (out - buffer > DNS_HOSTNAME_MAX + 2)
-                return -EINVAL;
-
-        return out - buffer;
-}
-
-#if 0 /* NM_IGNORED */
-static bool srv_type_label_is_valid(const char *label, size_t n) {
-        size_t k;
-
-        assert(label);
-
-        if (n < 2) /* Label needs to be at least 2 chars long */
-                return false;
-
-        if (label[0] != '_') /* First label char needs to be underscore */
-                return false;
-
-        /* Second char must be a letter */
-        if (!(label[1] >= 'A' && label[1] <= 'Z') &&
-            !(label[1] >= 'a' && label[1] <= 'z'))
-                return false;
-
-        /* Third and further chars must be alphanumeric or a hyphen */
-        for (k = 2; k < n; k++) {
-                if (!(label[k] >= 'A' && label[k] <= 'Z') &&
-                    !(label[k] >= 'a' && label[k] <= 'z') &&
-                    !(label[k] >= '0' && label[k] <= '9') &&
-                    label[k] != '-')
-                        return false;
-        }
-
-        return true;
-}
-
-bool dns_srv_type_is_valid(const char *name) {
-        unsigned c = 0;
-        int r;
-
-        if (!name)
-                return false;
-
-        for (;;) {
-                char label[DNS_LABEL_MAX];
-
-                /* This more or less implements RFC 6335, Section 5.1 */
-
-                r = dns_label_unescape(&name, label, sizeof label, 0);
-                if (r < 0)
-                        return false;
-                if (r == 0)
-                        break;
-
-                if (c >= 2)
-                        return false;
-
-                if (!srv_type_label_is_valid(label, r))
-                        return false;
-
-                c++;
-        }
-
-        return c == 2; /* exactly two labels */
-}
-
-bool dnssd_srv_type_is_valid(const char *name) {
-        return dns_srv_type_is_valid(name) &&
-                ((dns_name_endswith(name, "_tcp") > 0) ||
-                 (dns_name_endswith(name, "_udp") > 0)); /* Specific to DNS-SD. RFC 6763, Section 7 */
-}
-
-bool dns_service_name_is_valid(const char *name) {
-        size_t l;
-
-        /* This more or less implements RFC 6763, Section 4.1.1 */
-
-        if (!name)
-                return false;
-
-        if (!utf8_is_valid(name))
-                return false;
-
-        if (string_has_cc(name, NULL))
-                return false;
-
-        l = strlen(name);
-        if (l <= 0)
-                return false;
-        if (l > 63)
-                return false;
-
-        return true;
-}
-
-int dns_service_join(const char *name, const char *type, const char *domain, char **ret) {
-        char escaped[DNS_LABEL_ESCAPED_MAX];
-        _cleanup_free_ char *n = NULL;
-        int r;
-
-        assert(type);
-        assert(domain);
-        assert(ret);
-
-        if (!dns_srv_type_is_valid(type))
-                return -EINVAL;
-
-        if (!name)
-                return dns_name_concat(type, domain, 0, ret);
-
-        if (!dns_service_name_is_valid(name))
-                return -EINVAL;
-
-        r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped));
-        if (r < 0)
-                return r;
-
-        r = dns_name_concat(type, domain, 0, &n);
-        if (r < 0)
-                return r;
-
-        return dns_name_concat(escaped, n, 0, ret);
-}
-
-static bool dns_service_name_label_is_valid(const char *label, size_t n) {
-        char *s;
-
-        assert(label);
-
-        if (memchr(label, 0, n))
-                return false;
-
-        s = strndupa(label, n);
-        return dns_service_name_is_valid(s);
-}
-
-int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) {
-        _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
-        const char *p = joined, *q = NULL, *d = NULL;
-        char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX];
-        int an, bn, cn, r;
-        unsigned x = 0;
-
-        assert(joined);
-
-        /* Get first label from the full name */
-        an = dns_label_unescape(&p, a, sizeof(a), 0);
-        if (an < 0)
-                return an;
-
-        if (an > 0) {
-                x++;
-
-                /* If there was a first label, try to get the second one */
-                bn = dns_label_unescape(&p, b, sizeof(b), 0);
-                if (bn < 0)
-                        return bn;
-
-                if (bn > 0) {
-                        x++;
-
-                        /* If there was a second label, try to get the third one */
-                        q = p;
-                        cn = dns_label_unescape(&p, c, sizeof(c), 0);
-                        if (cn < 0)
-                                return cn;
-
-                        if (cn > 0)
-                                x++;
-                } else
-                        cn = 0;
-        } else
-                an = 0;
-
-        if (x >= 2 && srv_type_label_is_valid(b, bn)) {
-
-                if (x >= 3 && srv_type_label_is_valid(c, cn)) {
-
-                        if (dns_service_name_label_is_valid(a, an)) {
-                                /* OK, got <name> . <type> . <type2> . <domain> */
-
-                                name = strndup(a, an);
-                                if (!name)
-                                        return -ENOMEM;
-
-                                type = strjoin(b, ".", c);
-                                if (!type)
-                                        return -ENOMEM;
-
-                                d = p;
-                                goto finish;
-                        }
-
-                } else if (srv_type_label_is_valid(a, an)) {
-
-                        /* OK, got <type> . <type2> . <domain> */
-
-                        name = NULL;
-
-                        type = strjoin(a, ".", b);
-                        if (!type)
-                                return -ENOMEM;
-
-                        d = q;
-                        goto finish;
-                }
-        }
-
-        name = NULL;
-        type = NULL;
-        d = joined;
-
-finish:
-        r = dns_name_normalize(d, 0, &domain);
-        if (r < 0)
-                return r;
-
-        if (_domain)
-                *_domain = TAKE_PTR(domain);
-
-        if (_type)
-                *_type = TAKE_PTR(type);
-
-        if (_name)
-                *_name = TAKE_PTR(name);
-
-        return 0;
-}
-
-static int dns_name_build_suffix_table(const char *name, const char *table[]) {
-        const char *p;
-        unsigned n = 0;
-        int r;
-
-        assert(name);
-        assert(table);
-
-        p = name;
-        for (;;) {
-                if (n > DNS_N_LABELS_MAX)
-                        return -EINVAL;
-
-                table[n] = p;
-                r = dns_name_parent(&p);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                n++;
-        }
-
-        return (int) n;
-}
-
-int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) {
-        const char* labels[DNS_N_LABELS_MAX+1];
-        int n;
-
-        assert(name);
-        assert(ret);
-
-        n = dns_name_build_suffix_table(name, labels);
-        if (n < 0)
-                return n;
-
-        if ((unsigned) n < n_labels)
-                return -EINVAL;
-
-        *ret = labels[n - n_labels];
-        return (int) (n - n_labels);
-}
-
-int dns_name_skip(const char *a, unsigned n_labels, const char **ret) {
-        int r;
-
-        assert(a);
-        assert(ret);
-
-        for (; n_labels > 0; n_labels--) {
-                r = dns_name_parent(&a);
-                if (r < 0)
-                        return r;
-                if (r == 0) {
-                        *ret = "";
-                        return 0;
-                }
-        }
-
-        *ret = a;
-        return 1;
-}
-
-int dns_name_count_labels(const char *name) {
-        unsigned n = 0;
-        const char *p;
-        int r;
-
-        assert(name);
-
-        p = name;
-        for (;;) {
-                r = dns_name_parent(&p);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                if (n >= DNS_N_LABELS_MAX)
-                        return -EINVAL;
-
-                n++;
-        }
-
-        return (int) n;
-}
-
-int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) {
-        int r;
-
-        assert(a);
-        assert(b);
-
-        r = dns_name_skip(a, n_labels, &a);
-        if (r <= 0)
-                return r;
-
-        return dns_name_equal(a, b);
-}
-
-int dns_name_common_suffix(const char *a, const char *b, const char **ret) {
-        const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1];
-        int n = 0, m = 0, k = 0, r, q;
-
-        assert(a);
-        assert(b);
-        assert(ret);
-
-        /* Determines the common suffix of domain names a and b */
-
-        n = dns_name_build_suffix_table(a, a_labels);
-        if (n < 0)
-                return n;
-
-        m = dns_name_build_suffix_table(b, b_labels);
-        if (m < 0)
-                return m;
-
-        for (;;) {
-                char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX];
-                const char *x, *y;
-
-                if (k >= n || k >= m) {
-                        *ret = a_labels[n - k];
-                        return 0;
-                }
-
-                x = a_labels[n - 1 - k];
-                r = dns_label_unescape(&x, la, sizeof la, 0);
-                if (r < 0)
-                        return r;
-
-                y = b_labels[m - 1 - k];
-                q = dns_label_unescape(&y, lb, sizeof lb, 0);
-                if (q < 0)
-                        return q;
-
-                if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) {
-                        *ret = a_labels[n - k];
-                        return 0;
-                }
-
-                k++;
-        }
-}
-
-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 = TAKE_PTR(t);
-
-                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 hyphens — 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;
-        int r, q;
-
-        assert(name);
-        assert(ret);
-
-        for (;;) {
-                char label[DNS_LABEL_MAX];
-
-                r = dns_label_unescape(&name, label, sizeof label, 0);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        break;
-
-                q = dns_label_apply_idna(label, r, label, sizeof label);
-                if (q < 0)
-                        return q;
-                if (q > 0)
-                        r = q;
-
-                if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX))
-                        return -ENOMEM;
-
-                r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX);
-                if (r < 0)
-                        return r;
-
-                if (first)
-                        first = false;
-                else
-                        buf[n++] = '.';
-
-                n += r;
-        }
-
-        if (n > DNS_HOSTNAME_MAX)
-                return -EINVAL;
-
-        if (!GREEDY_REALLOC(buf, allocated, n + 1))
-                return -ENOMEM;
-
-        buf[n] = 0;
-        *ret = TAKE_PTR(buf);
-
-        return 1;
-#else
-        return 0;
-#endif
-}
-
-int dns_name_is_valid_or_address(const char *name) {
-        /* Returns > 0 if the specified name is either a valid IP address formatted as string or a valid DNS name */
-
-        if (isempty(name))
-                return 0;
-
-        if (in_addr_from_string_auto(name, NULL, NULL) >= 0)
-                return 1;
-
-        return dns_name_is_valid(name);
-}
-#endif /* NM_IGNORED */
diff --git a/src/systemd/src/shared/dns-domain.h b/src/systemd/src/shared/dns-domain.h
deleted file mode 100644
index 17db7c52..00000000
--- a/src/systemd/src/shared/dns-domain.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1+ */
-#pragma once
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-#include "hashmap.h"
-#include "in-addr-util.h"
-
-/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */
-#define DNS_LABEL_MAX 63
-
-/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */
-#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1)
-
-/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */
-#define DNS_HOSTNAME_MAX 253
-
-/* Maximum length of a full hostname, on the wire, including the final NUL byte */
-#define DNS_WIRE_FORMAT_HOSTNAME_MAX 255
-
-/* Maximum number of labels per valid hostname */
-#define DNS_N_LABELS_MAX 127
-
-typedef enum DNSLabelFlags {
-        DNS_LABEL_LDH        = 1 << 0, /* Follow the "LDH" rule — only letters, digits, and internal hyphens. */
-        DNS_LABEL_NO_ESCAPES = 1 << 1, /* Do not treat backslashes specially */
-} DNSLabelFlags;
-
-int dns_label_unescape(const char **name, char *dest, size_t sz, DNSLabelFlags flags);
-int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz);
-int dns_label_escape(const char *p, size_t l, char *dest, size_t sz);
-int dns_label_escape_new(const char *p, size_t l, char **ret);
-
-static inline int dns_name_parent(const char **name) {
-        return dns_label_unescape(name, NULL, DNS_LABEL_MAX, 0);
-}
-
-#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, DNSLabelFlags flags, char **ret);
-
-static inline int dns_name_normalize(const char *s, DNSLabelFlags flags, char **ret) {
-        /* dns_name_concat() normalizes as a side-effect */
-        return dns_name_concat(s, NULL, flags, ret);
-}
-
-static inline int dns_name_is_valid(const char *s) {
-        int r;
-
-        /* dns_name_normalize() verifies as a side effect */
-        r = dns_name_normalize(s, 0, NULL);
-        if (r == -EINVAL)
-                return 0;
-        if (r < 0)
-                return r;
-        return 1;
-}
-
-static inline int dns_name_is_valid_ldh(const char *s) {
-        int r;
-
-        r = dns_name_concat(s, NULL, DNS_LABEL_LDH|DNS_LABEL_NO_ESCAPES, NULL);
-        if (r == -EINVAL)
-                return 0;
-        if (r < 0)
-                return r;
-        return 1;
-}
-
-void dns_name_hash_func(const char *s, struct siphash *state);
-int dns_name_compare_func(const char *a, const char *b);
-extern const struct hash_ops dns_name_hash_ops;
-
-int dns_name_between(const char *a, const char *b, const char *c);
-int dns_name_equal(const char *x, const char *y);
-int dns_name_endswith(const char *name, const char *suffix);
-int dns_name_startswith(const char *name, const char *prefix);
-
-int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret);
-
-int dns_name_reverse(int family, const union in_addr_union *a, char **ret);
-int dns_name_address(const char *p, int *family, union in_addr_union *a);
-
-bool dns_name_is_root(const char *name);
-bool dns_name_is_single_label(const char *name);
-
-int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical);
-
-bool dns_srv_type_is_valid(const char *name);
-bool dnssd_srv_type_is_valid(const char *name);
-bool dns_service_name_is_valid(const char *name);
-
-int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
-int dns_service_split(const char *joined, char **name, char **type, char **domain);
-
-int dns_name_suffix(const char *name, unsigned n_labels, const char **ret);
-int dns_name_count_labels(const char *name);
-
-int dns_name_skip(const char *a, unsigned n_labels, const char **ret);
-int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b);
-
-int dns_name_common_suffix(const char *a, const char *b, const char **ret);
-
-int dns_name_apply_idna(const char *name, char **ret);
-
-int dns_name_is_valid_or_address(const char *name);
diff --git a/src/systemd/src/systemd/sd-dhcp-client.h b/src/systemd/src/systemd/sd-dhcp-client.h
index bd0d429d..ab62368e 100644
--- a/src/systemd/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/src/systemd/sd-dhcp-client.h
@@ -38,6 +38,7 @@ enum {
         SD_DHCP_CLIENT_EVENT_IP_CHANGE          = 2,
         SD_DHCP_CLIENT_EVENT_EXPIRED            = 3,
         SD_DHCP_CLIENT_EVENT_RENEW              = 4,
+        SD_DHCP_CLIENT_EVENT_SELECTING          = 5,
 };
 
 enum {
@@ -98,7 +99,7 @@ enum {
 
 typedef struct sd_dhcp_client sd_dhcp_client;
 
-typedef void (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata);
+typedef int (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata);
 int sd_dhcp_client_set_callback(
                 sd_dhcp_client *client,
                 sd_dhcp_client_callback_t cb,
@@ -154,6 +155,9 @@ int sd_dhcp_client_get_client_id(
 int sd_dhcp_client_set_mtu(
                 sd_dhcp_client *client,
                 uint32_t mtu);
+int sd_dhcp_client_set_max_attempts(
+                sd_dhcp_client *client,
+                uint64_t attempt);
 int sd_dhcp_client_set_client_port(
                 sd_dhcp_client *client,
                 uint16_t port);
@@ -172,6 +176,7 @@ int sd_dhcp_client_get_lease(
 
 int sd_dhcp_client_stop(sd_dhcp_client *client);
 int sd_dhcp_client_start(sd_dhcp_client *client);
+int sd_dhcp_client_send_release(sd_dhcp_client *client);
 
 sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
 sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);
diff --git a/src/systemd/src/systemd/sd-event.h b/src/systemd/src/systemd/sd-event.h
index 7bb86093..b14c9269 100644
--- a/src/systemd/src/systemd/sd-event.h
+++ b/src/systemd/src/systemd/sd-event.h
@@ -113,6 +113,7 @@ int sd_event_get_iteration(sd_event *e, uint64_t *ret);
 
 sd_event_source* sd_event_source_ref(sd_event_source *s);
 sd_event_source* sd_event_source_unref(sd_event_source *s);
+sd_event_source* sd_event_source_disable_unref(sd_event_source *s);
 
 sd_event *sd_event_source_get_event(sd_event_source *s);
 void* sd_event_source_get_userdata(sd_event_source *s);
@@ -149,6 +150,7 @@ int sd_event_source_set_floating(sd_event_source *s, int b);
 /* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_disable_unref);
 
 _SD_END_DECLARATIONS;
 
diff --git a/src/systemd/src/systemd/sd-id128.h b/src/systemd/src/systemd/sd-id128.h
index c5fbe0a4..9b00b76e 100644
--- a/src/systemd/src/systemd/sd-id128.h
+++ b/src/systemd/src/systemd/sd-id128.h
@@ -54,7 +54,6 @@ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret);
 #define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \
         ((const sd_id128_t) SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15))
 
-
 /* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16
  * times. It is hence not a good idea to call this macro with an
  * expensive function as parameter or an expression with side
diff --git a/src/tests/config/nm-test-device.c b/src/tests/config/nm-test-device.c
index 3a8ef266..dd624b44 100644
--- a/src/tests/config/nm-test-device.c
+++ b/src/tests/config/nm-test-device.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/tests/config/nm-test-device.h b/src/tests/config/nm-test-device.h
index c193b860..586bbe6d 100644
--- a/src/tests/config/nm-test-device.h
+++ b/src/tests/config/nm-test-device.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/tests/config/test-config.c b/src/tests/config/test-config.c
index 10084b7f..a5ef1431 100644
--- a/src/tests/config/test-config.c
+++ b/src/tests/config/test-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 17857676..b2bc13ab 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -1,8 +1,8 @@
 subdir('config')
 
 test_units = [
-  'test-general',
-  'test-general-with-expect',
+  'test-core',
+  'test-core-with-expect',
   'test-ip4-config',
   'test-ip6-config',
   'test-dcb',
@@ -18,7 +18,7 @@ foreach test_unit: test_units
   )
 
   test(
-    'src/' + test_unit,
+    test_unit,
     test_script,
     args: test_args + [exe.full_path()],
     timeout: default_test_timeout,
@@ -34,7 +34,7 @@ cflags = [
 
 exe = executable(
   test_unit,
-  [test_unit + '.c'],
+  test_unit + '.c',
   include_directories: src_inc,
   dependencies: libnm_core_dep,
   c_args: cflags,
@@ -45,7 +45,7 @@ exe = executable(
 )
 
 test(
-  'src/' + test_unit,
+  test_unit,
   test_script,
   args: test_args + [exe.full_path()],
 )
diff --git a/src/tests/test-general-with-expect.c b/src/tests/test-core-with-expect.c
index 8339fa5d..d38025ec 100644
--- a/src/tests/test-general-with-expect.c
+++ b/src/tests/test-core-with-expect.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -385,7 +384,12 @@ test_nm_utils_kill_child (void)
 		err = waitpid (child_pid, &exit_status, 0);
 	} while (err == -1 && errno == EINTR);
 	g_assert (err == child_pid);
-	g_assert (WIFEXITED (exit_status) && WEXITSTATUS(exit_status) == 0);
+	if (WIFEXITED (exit_status))
+		g_assert_cmpint (WEXITSTATUS (exit_status), ==, 0);
+	else {
+		g_assert_cmpint (exit_status, ==, 0);
+		g_assert_not_reached ();
+	}
 }
 
 /*****************************************************************************/
@@ -541,7 +545,7 @@ test_nm_utils_new_vlan_name (void)
 			guint vlan_id;
 
 			/* Create a random VLAN id between 0 and 4094 */
-			vlan_id = nmtst_get_rand_int () % 4095;
+			vlan_id = nmtst_get_rand_uint32 () % 4095;
 
 			vlan_id_s = g_strdup_printf (".%d", vlan_id);
 
diff --git a/src/tests/test-general.c b/src/tests/test-core.c
index 0dee566e..3eaad104 100644
--- a/src/tests/test-general.c
+++ b/src/tests/test-core.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -109,6 +108,17 @@ test_nm_utils_ip6_address_clear_host_address (void)
 /*****************************************************************************/
 
 static void
+test_logging_domains (void)
+{
+	const char *s;
+
+	s = nm_logging_all_domains_to_string ();
+	g_assert (s && s[0]);
+}
+
+/*****************************************************************************/
+
+static void
 _test_same_prefix (const char *a1, const char *a2, guint8 plen)
 {
 	struct in6_addr a = *nmtst_inet6_from_string (a1);
@@ -136,7 +146,7 @@ test_nm_utils_ip6_address_same_prefix (void)
 		nmtst_rand_buf (NULL, a.ptr, sizeof (a));
 		nmtst_rand_buf (NULL, b.ptr, sizeof (b));
 again_plen:
-		plen = nmtst_get_rand_int () % 129;
+		plen = nmtst_get_rand_uint32 () % 129;
 		if (!is_same && NM_IN_SET (plen, 0, 128))
 			goto again_plen;
 
@@ -170,7 +180,7 @@ again_plen:
 	for (n = 0; n < N; n++) {
 		nmtst_rand_buf (NULL, a.ptr, sizeof (a));
 		nmtst_rand_buf (NULL, b.ptr, sizeof (b));
-		plen = nmtst_get_rand_int () % 129;
+		plen = nmtst_get_rand_uint32 () % 129;
 
 		memset (addrmask.ptr, 0xFF, sizeof (addrmask));
 		nm_utils_ip6_address_clear_host_address (&addrmask.val, &addrmask.val, plen);
@@ -187,7 +197,7 @@ again_plen:
 
 		nmtst_rand_buf (NULL, a.ptr, sizeof (a));
 		nmtst_rand_buf (NULL, b.ptr, sizeof (b));
-		plen = nmtst_get_rand_int () % 129;
+		plen = nmtst_get_rand_uint32 () % 129;
 
 		if (!plen)
 			continue;
@@ -1445,7 +1455,7 @@ test_nm_utils_strbuf_append (void)
 		const char *_str_iter; \
 		gs_free char *_str = NULL; \
 		\
-		switch (nmtst_get_rand_int () % 4) { \
+		switch (nmtst_get_rand_uint32 () % 4) { \
 		case 0: \
 			nm_utils_strbuf_append (_buf, _len, (format), __VA_ARGS__); \
 			break; \
@@ -1473,7 +1483,7 @@ test_nm_utils_strbuf_append (void)
 		gsize *_len = (len); \
 		const char *_str = (str); \
 		\
-		switch (nmtst_get_rand_int () % 4) { \
+		switch (nmtst_get_rand_uint32 () % 4) { \
 		case 0: \
 			nm_utils_strbuf_append (_buf, _len, "%s", _str ?: ""); \
 			break; \
@@ -1498,7 +1508,7 @@ test_nm_utils_strbuf_append (void)
 		gsize *_len = (len); \
 		char _ch = (ch); \
 		\
-		switch (nmtst_get_rand_int () % 4) { \
+		switch (nmtst_get_rand_uint32 () % 4) { \
 		case 0: \
 			nm_utils_strbuf_append (_buf, _len, "%c", _ch); \
 			break; \
@@ -1516,7 +1526,7 @@ test_nm_utils_strbuf_append (void)
 
 	for (buf_len = 0; buf_len < 10; buf_len++) {
 		for (rep = 0; rep < 50; rep++) {
-			const int s_len = nmtst_get_rand_int () % (sizeof (str) - 5);
+			const int s_len = nmtst_get_rand_uint32 () % (sizeof (str) - 5);
 			char *t_buf;
 			gsize t_len;
 			int test_mode;
@@ -1532,7 +1542,7 @@ test_nm_utils_strbuf_append (void)
 			t_buf = buf;
 			t_len = buf_len;
 
-			test_mode = nmtst_get_rand_int () % 5;
+			test_mode = nmtst_get_rand_uint32 () % 5;
 
 			switch (test_mode) {
 			case 0:
@@ -1557,7 +1567,7 @@ test_nm_utils_strbuf_append (void)
 				g_snprintf (t_buf, t_len, "%s", str);
 				if (   t_len > 0
 				    && strlen (str) >= buf_len
-				    && (nmtst_get_rand_int () % 2)) {
+				    && (nmtst_get_rand_uint32 () % 2)) {
 					/* the string was truncated by g_snprintf(). That means, at the last position in the
 					 * buffer is now NUL.
 					 * Replace the NUL by the actual character, and check that nm_utils_strbuf_seek_end()
@@ -1677,7 +1687,7 @@ test_duplicate_decl_specifier (void)
 		(_a > _b ? _a : _b); \
 	})
 
-	v_result[0] = TEST_MAX (v_const[0], nmtst_get_rand_int () % 5) + v2;
+	v_result[0] = TEST_MAX (v_const[0], nmtst_get_rand_uint32 () % 5) + v2;
 
 	NM_PRAGMA_WARNING_REENABLE
 }
@@ -2180,6 +2190,8 @@ main (int argc, char **argv)
 {
 	nmtst_init_with_logging (&argc, &argv, NULL, "ALL");
 
+	g_test_add_func ("/general/test_logging_domains", test_logging_domains);
+
 	g_test_add_func ("/general/nm_utils_strbuf_append", test_nm_utils_strbuf_append);
 
 	g_test_add_func ("/general/nm_utils_ip6_address_clear_host_address", test_nm_utils_ip6_address_clear_host_address);
diff --git a/src/tests/test-dcb.c b/src/tests/test-dcb.c
index 6291f254..55206f1d 100644
--- a/src/tests/test-dcb.c
+++ b/src/tests/test-dcb.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/tests/test-ip4-config.c b/src/tests/test-ip4-config.c
index f98c84c3..d4d0c1bb 100644
--- a/src/tests/test-ip4-config.c
+++ b/src/tests/test-ip4-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index 8f5f41d7..bc212ba7 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/tests/test-secret-agent.py b/src/tests/test-secret-agent.py
index d3f82802..d8cabc65 100755
--- a/src/tests/test-secret-agent.py
+++ b/src/tests/test-secret-agent.py
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# -*- Mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 
 from gi.repository import GLib
 import sys
diff --git a/src/tests/test-systemd.c b/src/tests/test-systemd.c
index 20cbd50e..090dd691 100644
--- a/src/tests/test-systemd.c
+++ b/src/tests/test-systemd.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -157,7 +156,7 @@ test_sd_event (void)
 
 		g_assert_cmpint (sd_event_default (NULL), ==, 0);
 
-		for (i = 0, n = (nmtst_get_rand_int () % (G_N_ELEMENTS (other_events) + 1)); i < n; i++) {
+		for (i = 0, n = (nmtst_get_rand_uint32 () % (G_N_ELEMENTS (other_events) + 1)); i < n; i++) {
 			r = sd_event_default (&other_events[i]);
 			g_assert (r >= 0 && other_events[i]);
 		}
@@ -177,7 +176,7 @@ test_sd_event (void)
 		g_assert (!user_data.event_source);
 
 		event = sd_event_unref (event);
-		for (i = 0, n = (nmtst_get_rand_int () % (G_N_ELEMENTS (other_events) + 1)); i < n; i++)
+		for (i = 0, n = (nmtst_get_rand_uint32 () % (G_N_ELEMENTS (other_events) + 1)); i < n; i++)
 			other_events[i] = sd_event_unref (other_events[i]);
 		nm_clear_g_source (&sd_id);
 		for (i = 0, n = G_N_ELEMENTS (other_events); i < n; i++)
@@ -311,15 +310,15 @@ test_nm_sd_utils_unbase64mem (void)
 	_test_unbase64mem ("YQ==", "a");
 	_test_unbase64mem_inval ("YQ==a");
 
-	rnd_len = nmtst_get_rand_int () % sizeof (rnd_buf);
+	rnd_len = nmtst_get_rand_uint32 () % sizeof (rnd_buf);
 	for (i = 0; i < rnd_len; i++)
-		rnd_buf[i] = nmtst_get_rand_int () % 256;
+		rnd_buf[i] = nmtst_get_rand_uint32 () % 256;
 	rnd_base64 = g_base64_encode (rnd_buf, rnd_len);
 	_test_unbase64mem_mem (rnd_base64, rnd_buf, rnd_len);
 
 	_test_unbase64char ('=', FALSE);
 	for (i = 0; i < 10; i++) {
-		char ch = nmtst_get_rand_int () % 256;
+		char ch = nmtst_get_rand_uint32 () % 256;
 
 		if (ch != '=')
 			_test_unbase64char (ch, TRUE);
diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c
index c326f790..f3eb66ef 100644
--- a/src/tests/test-utils.c
+++ b/src/tests/test-utils.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/tests/test-wired-defname.c b/src/tests/test-wired-defname.c
index 97a6c319..27e0590b 100644
--- a/src/tests/test-wired-defname.c
+++ b/src/tests/test-wired-defname.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c
index 5acf491a..598af04e 100644
--- a/src/vpn/nm-vpn-connection.c
+++ b/src/vpn/nm-vpn-connection.c
@@ -1,4 +1,3 @@
-/* -*- 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
@@ -103,7 +102,7 @@ typedef struct {
 	char *username;
 
 	VpnState vpn_state;
-	guint dispatcher_id;
+	NMDispatcherCallId *dispatcher_id;
 	NMActiveConnectionStateReason failure_reason;
 
 	NMVpnServiceState service_state;
@@ -124,8 +123,7 @@ typedef struct {
 	GVariant *connect_hash;
 	guint connect_timeout;
 	NMProxyConfig *proxy_config;
-	NMPacrunnerManager *pacrunner_manager;
-	NMPacrunnerCallId *pacrunner_call_id;
+	NMPacrunnerConfId *pacrunner_conf_id;
 	gboolean has_ip4;
 	NMIP4Config *ip4_config;
 	guint32 ip4_internal_gw;
@@ -419,22 +417,28 @@ vpn_cleanup (NMVpnConnection *self, NMDevice *parent_dev)
 }
 
 static void
-dispatcher_pre_down_done (guint call_id, gpointer user_data)
+dispatcher_pre_down_done (NMDispatcherCallId *call_id, gpointer user_data)
 {
 	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
-	priv->dispatcher_id = 0;
+	nm_assert (call_id);
+	nm_assert (priv->dispatcher_id == call_id);
+
+	priv->dispatcher_id = NULL;
 	_set_vpn_state (self, STATE_DISCONNECTED, NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED, FALSE);
 }
 
 static void
-dispatcher_pre_up_done (guint call_id, gpointer user_data)
+dispatcher_pre_up_done (NMDispatcherCallId *call_id, gpointer user_data)
 {
 	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
-	priv->dispatcher_id = 0;
+	nm_assert (call_id);
+	nm_assert (priv->dispatcher_id == call_id);
+
+	priv->dispatcher_id = NULL;
 	_set_vpn_state (self, STATE_ACTIVATED, NM_ACTIVE_CONNECTION_STATE_REASON_NONE, FALSE);
 }
 
@@ -443,10 +447,8 @@ dispatcher_cleanup (NMVpnConnection *self)
 {
 	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
-	if (priv->dispatcher_id) {
-		nm_dispatcher_call_cancel (priv->dispatcher_id);
-		priv->dispatcher_id = 0;
-	}
+	if (priv->dispatcher_id)
+		nm_dispatcher_call_cancel (g_steal_pointer (&priv->dispatcher_id));
 }
 
 static void
@@ -552,18 +554,12 @@ _set_vpn_state (NMVpnConnection *self,
 		                        NULL);
 
 		if (priv->proxy_config) {
-			nm_pacrunner_manager_remove_clear (priv->pacrunner_manager,
-			                                   &priv->pacrunner_call_id);
-			if (!priv->pacrunner_manager) {
-				/* the pending call doesn't keep NMPacrunnerManager alive.
-				 * Take a reference to it. */
-				priv->pacrunner_manager = g_object_ref (nm_pacrunner_manager_get ());
-			}
-			priv->pacrunner_call_id = nm_pacrunner_manager_send (priv->pacrunner_manager,
-			                                                     priv->ip_iface,
-			                                                     priv->proxy_config,
-			                                                     priv->ip4_config,
-			                                                     priv->ip6_config);
+			nm_pacrunner_manager_remove_clear (&priv->pacrunner_conf_id);
+			priv->pacrunner_conf_id = nm_pacrunner_manager_add (nm_pacrunner_manager_get (),
+			                                                    priv->proxy_config,
+			                                                    priv->ip_iface,
+			                                                    priv->ip4_config,
+			                                                    priv->ip6_config);
 		}
 		break;
 	case STATE_DEACTIVATING:
@@ -594,8 +590,7 @@ _set_vpn_state (NMVpnConnection *self,
 			}
 		}
 
-		nm_pacrunner_manager_remove_clear (priv->pacrunner_manager,
-		                                   &priv->pacrunner_call_id);
+		nm_pacrunner_manager_remove_clear (&priv->pacrunner_conf_id);
 		break;
 	case STATE_FAILED:
 	case STATE_DISCONNECTED:
@@ -2801,9 +2796,7 @@ dispose (GObject *object)
 
 	fw_call_cleanup (self);
 
-	nm_pacrunner_manager_remove_clear (priv->pacrunner_manager,
-	                                   &priv->pacrunner_call_id);
-	g_clear_object (&priv->pacrunner_manager);
+	nm_pacrunner_manager_remove_clear (&priv->pacrunner_conf_id);
 
 	G_OBJECT_CLASS (nm_vpn_connection_parent_class)->dispose (object);
 }
diff --git a/src/vpn/nm-vpn-connection.h b/src/vpn/nm-vpn-connection.h
index e70590b2..1527b689 100644
--- a/src/vpn/nm-vpn-connection.h
+++ b/src/vpn/nm-vpn-connection.h
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/vpn/nm-vpn-manager.c b/src/vpn/nm-vpn-manager.c
index 0b27b9de..36043c93 100644
--- a/src/vpn/nm-vpn-manager.c
+++ b/src/vpn/nm-vpn-manager.c
@@ -1,4 +1,3 @@
-/* -*- 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
diff --git a/src/vpn/nm-vpn-manager.h b/src/vpn/nm-vpn-manager.h
index 8b1bec1e..bcbd19f1 100644
--- a/src/vpn/nm-vpn-manager.h
+++ b/src/vpn/nm-vpn-manager.h
@@ -1,4 +1,3 @@
-/* -*- 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